Task: refine task

This commit is contained in:
Le Tan 2021-12-10 20:32:56 +08:00
parent 87ed9250ef
commit 70c984353c
63 changed files with 2207 additions and 1995 deletions

View File

@ -36,6 +36,10 @@ const QString ConfigMgr::c_configFileName = "vnotex.json";
const QString ConfigMgr::c_sessionFileName = "session.json";
const QString ConfigMgr::c_userFilesFolder = "user_files";
const QString ConfigMgr::c_appFilesFolder = "vnotex_files";
const QJsonObject &ConfigMgr::Settings::getJson() const
{
return m_jobj;
@ -68,17 +72,21 @@ ConfigMgr::ConfigMgr(bool p_isUnitTest, QObject *p_parent)
qWarning() << "failed to init ConfigMgr for UnitTest";
return;
}
m_appConfigFolderPath = m_dirForUnitTest->filePath("vnotex_files");
m_userConfigFolderPath = m_dirForUnitTest->filePath("user_files");
FileUtils::copyFile(getConfigFilePath(Source::Default), PathUtils::concatenateFilePath(m_appConfigFolderPath, c_configFileName));
} else {
locateConfigFolder();
QDir dir(m_dirForUnitTest->path());
dir.mkdir(c_appFilesFolder);
dir.mkdir(c_userFilesFolder);
bool needUpdate = checkAppConfig();
if (needUpdate) {
checkUserConfig();
}
m_appConfigFolderPath = m_dirForUnitTest->filePath(c_appFilesFolder);
m_userConfigFolderPath = m_dirForUnitTest->filePath(c_userFilesFolder);
return;
}
locateConfigFolder();
bool needUpdate = checkAppConfig();
if (needUpdate) {
checkUserConfig();
}
m_config->init();
@ -106,8 +114,7 @@ void ConfigMgr::locateConfigFolder()
qInfo() << "app folder" << appDirPath;
// Check app config.
{
const QString configFolderName("vnotex_files");
QString folderPath(appDirPath + '/' + configFolderName);
QString folderPath(appDirPath + '/' + c_appFilesFolder);
if (QDir(folderPath).exists()) {
// Config folder in app/.
m_appConfigFolderPath = PathUtils::cleanPath(folderPath);
@ -118,8 +125,7 @@ void ConfigMgr::locateConfigFolder()
// Check user config.
{
const QString configFolderName("user_files");
QString folderPath(appDirPath + '/' + configFolderName);
QString folderPath(appDirPath + '/' + c_userFilesFolder);
if (QDir(folderPath).exists()) {
// Config folder in app/.
m_userConfigFolderPath = PathUtils::cleanPath(folderPath);
@ -564,3 +570,14 @@ QString ConfigMgr::getApplicationVersion()
return appVersion;
}
QJsonValue ConfigMgr::parseAndReadConfig(const QString &p_exp) const
{
if (p_exp.startsWith(QStringLiteral("main."))) {
return Utils::parseAndReadJson(m_config->toJson(), p_exp.mid(5));
} else if (p_exp.startsWith(QStringLiteral("session."))) {
return Utils::parseAndReadJson(m_sessionConfig->toJson(), p_exp.mid(8));
} else {
return QJsonValue();
}
}

View File

@ -106,6 +106,9 @@ namespace vnotex
QString getConfigFilePath(Source p_src) const;
// Parse exp like "[main|session].core.shortcuts.FullScreen" and return the config value.
QJsonValue parseAndReadConfig(const QString &p_exp) const;
// Called at boostrap without QApplication instance.
static QString locateSessionConfigFilePathAtBootstrap();
@ -168,6 +171,10 @@ namespace vnotex
// Name of the session config file.
static const QString c_sessionFileName;
static const QString c_userFilesFolder;
static const QString c_appFilesFolder;
};
} // ns vnotex

View File

@ -29,11 +29,6 @@ SOURCES += \
$$PWD/texteditorconfig.cpp \
$$PWD/vnotex.cpp \
$$PWD/thememgr.cpp \
$$PWD/task.cpp \
$$PWD/taskhelper.cpp \
$$PWD/taskmgr.cpp \
$$PWD/taskvariablemgr.cpp \
$$PWD/shellexecution.cpp \
$$PWD/notebookmgr.cpp \
$$PWD/theme.cpp \
$$PWD/sessionconfig.cpp \
@ -65,11 +60,6 @@ HEADERS += \
$$PWD/texteditorconfig.h \
$$PWD/vnotex.h \
$$PWD/thememgr.h \
$$PWD/task.h \
$$PWD/taskhelper.h \
$$PWD/taskmgr.h \
$$PWD/taskvariablemgr.h \
$$PWD/shellexecution.h \
$$PWD/global.h \
$$PWD/namebasedserver.h \
$$PWD/exception.h \

View File

@ -150,7 +150,11 @@ QJsonObject EditorConfig::toJson() const
obj[m_markdownEditorConfig->getSessionName()] = m_markdownEditorConfig->toJson();
obj[QStringLiteral("core")] = saveCore();
obj[QStringLiteral("image_host")] = saveImageHost();
obj[QStringLiteral("vi")] = m_viConfig->toJson();
// In UT, it may be nullptr.
if (m_viConfig) {
obj[QStringLiteral("vi")] = m_viConfig->toJson();
}
return obj;
}

View File

@ -265,6 +265,11 @@ ID NotebookMgr::getCurrentNotebookId() const
return m_currentNotebookId;
}
QSharedPointer<Notebook> NotebookMgr::getCurrentNotebook() const
{
return findNotebookById(m_currentNotebookId);
}
void NotebookMgr::setCurrentNotebook(ID p_notebookId)
{
auto lastId = m_currentNotebookId;

View File

@ -62,6 +62,8 @@ namespace vnotex
ID getCurrentNotebookId() const;
QSharedPointer<Notebook> getCurrentNotebook() const;
// Find the notebook with the same directory as root folder.
QSharedPointer<Notebook> findNotebookByRootFolderPath(const QString &p_rootFolderPath) const;

View File

@ -1,102 +0,0 @@
#include "shellexecution.h"
#include <QFileInfo>
using namespace vnotex;
ShellExecution::ShellExecution()
{
auto shell = defaultShell();
setShellExecutable(shell);
setShellArguments(defaultShellArguments(shell));
}
void ShellExecution::setProgram(const QString &p_prog)
{
m_program = p_prog;
}
void ShellExecution::setArguments(const QStringList &p_args)
{
m_args = p_args;
}
void ShellExecution::setShellExecutable(const QString &p_exec)
{
m_shell_executable = p_exec;
}
void ShellExecution::setShellArguments(const QStringList &p_args)
{
m_shell_args = p_args;
}
void ShellExecution::setupProcess(QProcess *p_process,
const QString &p_program,
const QStringList &p_args,
const QString &p_shell_exec,
const QStringList &p_shell_args)
{
auto shell_exec = p_shell_exec.isNull() ? defaultShell() : p_shell_exec;
auto shell_args = p_shell_args.isEmpty() ? defaultShellArguments(shell_exec)
: p_shell_args;
p_process->setProgram(shell_exec);
auto args = p_args;
auto shell = shellBasename(shell_exec);
if (!p_program.isEmpty() && !p_args.isEmpty()) {
args = shellQuote(args, shell_exec);
}
QStringList allArgs(shell_args);
if (shell == "bash") {
allArgs << (QStringList() << p_program << args).join(' ');
} else {
allArgs << p_program << args;
}
p_process->setArguments(allArgs);
}
QString ShellExecution::shellBasename(const QString &p_shell)
{
return QFileInfo(p_shell).baseName().toLower();
}
QString ShellExecution::defaultShell()
{
#ifdef Q_OS_WIN
return "PowerShell.exe";
#else
return "/bin/bash";
#endif
}
QStringList ShellExecution::defaultShellArguments(const QString &p_shell)
{
auto shell = shellBasename(p_shell);
if (shell == "cmd") {
return {"/C"};
} else if (shell == "powershell" || p_shell == "pwsh") {
return {"-Command"};
} else if (shell == "bash") {
return {"-c"};
}
return {};
}
QString ShellExecution::shellQuote(const QString &p_text, const QString &)
{
if (p_text.contains(' ')) {
return QString("\"%1\"").arg(p_text);
}
return p_text;
}
QStringList ShellExecution::shellQuote(const QStringList &p_list, const QString &p_shell)
{
auto shell = shellBasename(p_shell);
QStringList list;
for (const auto &s : p_list) {
list << shellQuote(s, shell);
}
return list;
}

View File

@ -1,42 +0,0 @@
#ifndef SHELLEXECUTION_H
#define SHELLEXECUTION_H
#include <QProcess>
namespace vnotex {
class ShellExecution : public QProcess
{
Q_OBJECT
public:
ShellExecution();
void setProgram(const QString &p_prog);
void setArguments(const QStringList &p_args);
void setShellExecutable(const QString &p_exec);
void setShellArguments(const QStringList &p_args);
static void setupProcess(QProcess *p_process,
const QString &p_program,
const QStringList &p_args = QStringList(),
const QString &p_shell_exec = QString(),
const QStringList &p_shell_args = QStringList());
static QString defaultShell();
static QStringList defaultShellArguments(const QString &p_shell);
static QString shellQuote(const QString &p_text,
const QString &p_shell);
static QStringList shellQuote(const QStringList &p_list,
const QString &p_shell);
private:
QString m_shell_executable;
QStringList m_shell_args;
QString m_program;
QStringList m_args;
static QString shellBasename(const QString &p_shell);
};
}
#endif // SHELLEXECUTION_H

View File

@ -1,557 +0,0 @@
#include "task.h"
#include <QJsonDocument>
#include <QVersionNumber>
#include <QDebug>
#include <QJsonValue>
#include <QJsonArray>
#include <QAction>
#include <QRegularExpression>
#include <QInputDialog>
#include <QTextCodec>
#include <QRandomGenerator>
#include "utils/fileutils.h"
#include "utils/pathutils.h"
#include "vnotex.h"
#include "exception.h"
#include "taskhelper.h"
#include "notebook/notebook.h"
#include "shellexecution.h"
using namespace vnotex;
QString Task::s_latestVersion = "0.1.3";
TaskVariableMgr Task::s_vars;
Task *Task::fromFile(const QString &p_file,
const QJsonDocument &p_json,
const QString &p_locale,
QObject *p_parent)
{
auto task = new Task(p_locale, p_file, p_parent);
return fromJson(task, p_json.object());
}
Task* Task::fromJson(Task *p_task,
const QJsonObject &p_obj)
{
if (p_obj.contains("version")) {
p_task->dto.version = p_obj["version"].toString();
}
auto version = QVersionNumber::fromString(p_task->getVersion());
if (version < QVersionNumber(1, 0, 0)) {
return fromJsonV0(p_task, p_obj);
}
qWarning() << "Unknow Task Version" << version << p_task->dto._source;
return p_task;
}
Task *Task::fromJsonV0(Task *p_task,
const QJsonObject &p_obj,
bool mergeTasks)
{
if (p_obj.contains("type")) {
p_task->dto.type = p_obj["type"].toString();
}
if (p_obj.contains("icon")) {
QString path = p_obj["icon"].toString();
QDir iconPath(path);
if (iconPath.isRelative()) {
QDir taskDir(p_task->dto._source);
taskDir.cdUp();
path = QDir(taskDir.filePath(path)).absolutePath();
}
if (QFile::exists(path)) {
p_task->dto.icon = path;
} else {
qWarning() << "task icon not exists" << path;
}
}
if (p_obj.contains("shortcut")) {
p_task->dto.shortcut = p_obj["shortcut"].toString();
}
if (p_obj.contains("type")) {
p_task->dto.type = p_obj["type"].toString();
}
if (p_obj.contains("command")) {
p_task->dto.command = getLocaleString(p_obj["command"], p_task->m_locale);
}
if (p_obj.contains("args")) {
p_task->dto.args = getLocaleStringList(p_obj["args"], p_task->m_locale);
}
if (p_obj.contains("label")) {
p_task->dto.label = getLocaleString(p_obj["label"], p_task->m_locale);
} else if (p_task->dto.label.isNull() && !p_task->dto.command.isNull()) {
p_task->dto.label = p_task->dto.command;
}
if (p_obj.contains("options")) {
auto options = p_obj["options"].toObject();
if (options.contains("cwd")) {
p_task->dto.options.cwd = options["cwd"].toString();
}
if (options.contains("env")) {
p_task->dto.options.env.clear();
auto env = options["env"].toObject();
for (auto i = env.begin(); i != env.end(); i++) {
auto key = i.key();
auto value = getLocaleString(i.value(), p_task->m_locale);
p_task->dto.options.env.insert(key, value);
}
}
if (options.contains("shell") && p_task->getType() == "shell") {
auto shell = options["shell"].toObject();
if (shell.contains("executable")) {
p_task->dto.options.shell.executable = shell["executable"].toString();
}
if (shell.contains("args")) {
p_task->dto.options.shell.args.clear();
for (auto arg : shell["args"].toArray()) {
p_task->dto.options.shell.args << arg.toString();
}
}
}
}
if (p_obj.contains("tasks")) {
if (!mergeTasks) p_task->m_tasks.clear();
auto tasks = p_obj["tasks"].toArray();
for (const auto &task : tasks) {
auto t = new Task(p_task->m_locale,
p_task->getFile(),
p_task);
p_task->m_tasks.append(fromJson(t, task.toObject()));
}
}
if (p_obj.contains("inputs")) {
p_task->dto.inputs.clear();
auto inputs = p_obj["inputs"].toArray();
for (const auto &input : inputs) {
auto in = input.toObject();
InputDTO i;
if (in.contains("id")) {
i.id = in["id"].toString();
} else {
qWarning() << "Input configuration not contain id";
}
if (in.contains("type")) {
i.type = in["type"].toString();
} else {
i.type = "promptString";
}
if (in.contains("description")) {
i.description = getLocaleString(in["description"], p_task->m_locale);
}
if (in.contains("default")) {
i.default_ = getLocaleString(in["default"], p_task->m_locale);
}
if (i.type == "promptString" && in.contains("password")) {
i.password = in["password"].toBool();
} else {
i.password = false;
}
if (i.type == "pickString" && in.contains("options")) {
i.options = getLocaleStringList(in["options"], p_task->m_locale);
}
if (i.type == "pickString" && !i.default_.isNull() && !i.options.contains(i.default_)) {
qWarning() << "default must be one of the option values";
}
p_task->dto.inputs << i;
}
}
if (p_obj.contains("messages")) {
p_task->dto.messages.clear();
auto messages = p_obj["messages"].toArray();
for (const auto &message : messages) {
auto msg = message.toObject();
MessageDTO m;
if (msg.contains("id")) {
m.id = msg["id"].toString();
} else {
qWarning() << "Message configuration not contain id";
}
if (msg.contains("type")) {
m.type = msg["type"].toString();
} else {
m.type = "information";
}
if (msg.contains("title")) {
m.title = getLocaleString(msg["title"], p_task->m_locale);
}
if (msg.contains("text")) {
m.text = getLocaleString(msg["text"], p_task->m_locale);
}
if (msg.contains("detailedText")) {
m.detailedText = getLocaleString(msg["detailedText"], p_task->m_locale);
}
if (msg.contains("buttons")) {
auto buttons = msg["buttons"].toArray();
for (auto button : buttons) {
auto btn = button.toObject();
ButtonDTO b;
b.text = getLocaleString(btn["text"], p_task->m_locale);
m.buttons << b;
}
}
p_task->dto.messages << m;
}
}
// OS-specific task configuration
#if defined (Q_OS_WIN)
#define OS_SPEC "windows"
#endif
#if defined (Q_OS_MACOS)
#define OS_SPEC "osx"
#endif
#if defined (Q_OS_LINUX)
#define OS_SPEC "linux"
#endif
if (p_obj.contains(OS_SPEC)) {
auto os = p_obj[OS_SPEC].toObject();
fromJsonV0(p_task, os, true);
}
#undef OS_SPEC
return p_task;
}
QString Task::getVersion() const
{
return dto.version;
}
QString Task::getType() const
{
return dto.type;
}
QString Task::getCommand() const
{
return s_vars.evaluate(dto.command, this);
}
QStringList Task::getArgs() const
{
return s_vars.evaluate(dto.args, this);
}
QString Task::getLabel() const
{
return dto.label;
}
QString Task::getIcon() const
{
return dto.icon;
}
QString Task::getShortcut() const
{
return dto.shortcut;
}
QString Task::getOptionsCwd() const
{
auto cwd = dto.options.cwd;
if (!cwd.isNull()) {
return s_vars.evaluate(cwd, this);
}
auto notebook = TaskHelper::getCurrentNotebook();
if (notebook) cwd = notebook->getRootFolderAbsolutePath();
if (!cwd.isNull()) {
return cwd;
}
cwd = TaskHelper::getCurrentFile();
if (!cwd.isNull()) {
return QFileInfo(cwd).dir().absolutePath();
}
return QFileInfo(dto._source).dir().absolutePath();
}
const QMap<QString, QString> &Task::getOptionsEnv() const
{
return dto.options.env;
}
QString Task::getOptionsShellExecutable() const
{
return dto.options.shell.executable;
}
QStringList Task::getOptionsShellArgs() const
{
if (dto.options.shell.args.isEmpty()) {
return ShellExecution::defaultShellArguments(dto.options.shell.executable);
} else {
return s_vars.evaluate(dto.options.shell.args, this);
}
}
const QVector<Task *> &Task::getTasks() const
{
return m_tasks;
}
const QVector<InputDTO> &Task::getInputs() const
{
return dto.inputs;
}
InputDTO Task::getInput(const QString &p_id) const
{
for (auto i : dto.inputs) {
if (i.id == p_id) {
return i;
}
}
qDebug() << getLabel();
qWarning() << "input" << p_id << "not found";
throw "Input variable can not found";
}
MessageDTO Task::getMessage(const QString &p_id) const
{
for (auto msg : dto.messages) {
if (msg.id == p_id) {
return msg;
}
}
qDebug() << getLabel();
qWarning() << "message" << p_id << "not found";
throw "Message can not found";
}
QString Task::getFile() const
{
return dto._source;
}
Task::Task(const QString &p_locale,
const QString &p_file,
QObject *p_parent)
: QObject(p_parent)
{
dto._source = p_file;
dto.version = s_latestVersion;
dto.type = "shell";
dto.options.shell.executable = ShellExecution::defaultShell();
// inherit configuration
m_parent = qobject_cast<Task*>(p_parent);
if (m_parent) {
dto.version = m_parent->dto.version;
dto.type = m_parent->dto.type;
dto.command = m_parent->dto.command;
dto.args = m_parent->dto.args;
dto.options.cwd = m_parent->dto.options.cwd;
dto.options.env = m_parent->dto.options.env;
dto.options.shell.executable = m_parent->dto.options.shell.executable;
dto.options.shell.args = m_parent->dto.options.shell.args;
// not inherit label/inputs/tasks
} else {
dto.label = QFileInfo(p_file).baseName();
}
if (!p_locale.isNull()) {
m_locale = p_locale;
}
}
QProcess *Task::setupProcess() const
{
// Set process property
auto command = getCommand();
if (command.isEmpty()) return nullptr;
auto process = new QProcess(this->parent());
process->setWorkingDirectory(getOptionsCwd());
auto options_env = getOptionsEnv();
if (!options_env.isEmpty()) {
auto env = QProcessEnvironment::systemEnvironment();
for (auto i = options_env.begin(); i != options_env.end(); i++) {
env.insert(i.key(), i.value());
}
process->setProcessEnvironment(env);
}
auto args = getArgs();
auto type = getType();
// set program and args
if (type == "shell") {
ShellExecution::setupProcess(process,
command,
args,
getOptionsShellExecutable(),
getOptionsShellArgs());
} else if (getType() == "process") {
process->setProgram(command);
process->setArguments(args);
}
// connect signal and slot
connect(process, &QProcess::started,
this, [this]() {
emit showOutput(tr("[Task %1 started]\n").arg(getLabel()));
});
connect(process, &QProcess::readyReadStandardOutput,
this, [process, this]() {
auto text = textDecode(process->readAllStandardOutput());
text = TaskHelper::handleCommand(text, process, this);
emit showOutput(text);
});
connect(process, &QProcess::readyReadStandardError,
this, [process, this]() {
auto text = process->readAllStandardError();
emit showOutput(textDecode(text));
});
connect(process, &QProcess::errorOccurred,
this, [this](QProcess::ProcessError error) {
emit showOutput(tr("[Task %1 error occurred with code %2]\n").arg(getLabel(), QString::number(error)));
});
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, process](int exitCode) {
emit showOutput(tr("\n[Task %1 finished with exit code %2]\n")
.arg(getLabel(), QString::number(exitCode)));
process->deleteLater();
});
return process;
}
void Task::run() const
{
QProcess *process;
try {
process = setupProcess();
} catch (const char *msg) {
qDebug() << msg;
return ;
}
if (process) {
// start process
qInfo() << "run task" << process->program() << process->arguments();
process->start();
}
}
TaskDTO Task::getDTO() const
{
return dto;
}
QString Task::textDecode(const QByteArray &p_text)
{
static QByteArrayList codecNames = {
"UTF-8",
"System",
"UTF-16",
"GB18030"
};
for (auto name : codecNames) {
auto text = textDecode(p_text, name);
if (!text.isNull()) return text;
}
return p_text;
}
QString Task::textDecode(const QByteArray &p_text, const QByteArray &name)
{
auto codec = QTextCodec::codecForName(name);
if (codec) {
QTextCodec::ConverterState state;
auto text = codec->toUnicode(p_text.data(), p_text.size(), &state);
if (state.invalidChars > 0) return QString();
return text;
}
return QString();
}
bool Task::isValidTaskFile(const QString &p_file,
QJsonDocument &p_json)
{
QFile file(p_file);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QJsonParseError error;
p_json = QJsonDocument::fromJson(file.readAll(), &error);
file.close();
if (p_json.isNull()) {
qDebug() << "load task" << p_file << "failed: " << error.errorString()
<< "at offset" << error.offset;
return false;
}
return true;
}
QString Task::getLocaleString(const QJsonValue &p_value, const QString &p_locale)
{
if (p_value.isObject()) {
auto obj = p_value.toObject();
if (obj.contains(p_locale)) {
return obj.value(p_locale).toString();
} else {
qWarning() << "current locale" << p_locale << "not found";
if (!obj.isEmpty()){
return obj.begin().value().toString();
} else {
return QString();
}
}
} else {
return p_value.toString();
}
}
QStringList Task::getLocaleStringList(const QJsonValue &p_value, const QString &p_locale)
{
QStringList list;
for (auto value : p_value.toArray()) {
list << getLocaleString(value, p_locale);
}
return list;
}
QStringList Task::getStringList(const QJsonValue &p_value)
{
QStringList list;
for (auto value : p_value.toArray()) {
list << value.toString();
}
return list;
}

View File

@ -1,185 +0,0 @@
#ifndef TASK_H
#define TASK_H
#include <QObject>
#include <QJsonObject>
#include <QVector>
#include <QMap>
#include <QProcess>
#include "taskvariablemgr.h"
class QAction;
namespace vnotex {
struct ButtonDTO {
QString text;
};
struct InputDTO {
QString id;
QString type;
QString description;
QString default_;
bool password;
QStringList options;
};
struct MessageDTO {
QString id;
QString type;
QString title;
QString text;
QString detailedText;
QVector<ButtonDTO> buttons;
};
struct ShellOptionsDTO {
QString executable;
QStringList args;
};
struct TaskOptionsDTO {
QString cwd;
QMap<QString, QString> env;
ShellOptionsDTO shell;
};
struct TaskDTO {
QString version;
QString type;
QString command;
QStringList args;
QString label;
QString icon;
QString shortcut;
QVector<InputDTO> inputs;
QVector<MessageDTO> messages;
TaskOptionsDTO options;
QString _scope;
QString _source;
};
class Notebook;
class Task : public QObject
{
Q_OBJECT
public:
static Task* fromFile(const QString &p_file,
const QJsonDocument &p_json,
const QString &p_locale,
QObject *p_parent = nullptr);
void run() const;
TaskDTO getDTO() const;
QString getVersion() const;
QString getType() const;
QString getCommand() const;
QStringList getArgs() const;
QString getLabel() const;
QString getIcon() const;
QString getShortcut() const;
QString getOptionsCwd() const;
const QMap<QString, QString> &getOptionsEnv() const;
QString getOptionsShellExecutable() const;
QStringList getOptionsShellArgs() const;
const QVector<Task*> &getTasks() const;
const QVector<InputDTO> &getInputs() const;
InputDTO getInput(const QString &p_id) const;
MessageDTO getMessage(const QString &p_id) const;
QString getFile() const;
static QString s_latestVersion;
static bool isValidTaskFile(const QString &p_file,
QJsonDocument &p_json);
static QString getLocaleString(const QJsonValue &p_value,
const QString &p_locale);
static QStringList getLocaleStringList(const QJsonValue &p_value,
const QString &p_locale);
static QStringList getStringList(const QJsonValue &p_value);
signals:
void showOutput(const QString &p_text) const;
private:
static Task* fromJson(Task *p_task,
const QJsonObject &p_obj);
static Task* fromJsonV0(Task *p_task,
const QJsonObject &p_obj,
bool mergeTasks = false);
explicit Task(const QString &p_locale,
const QString &p_file = QString(),
QObject *p_parent = nullptr);
QProcess *setupProcess() const;
static QString textDecode(const QByteArray &p_text);
static QString textDecode(const QByteArray &p_text, const QByteArray &name);
TaskDTO dto;
Task *m_parent = nullptr;
QVector<Task*> m_tasks;
QString m_locale;
static TaskVariableMgr s_vars;
};
} // ns vnotex
#endif // TASK_H

View File

@ -1,233 +0,0 @@
#include "taskhelper.h"
#include <QRegularExpression>
#include <QJsonArray>
#include <QMessageBox>
#include <QPushButton>
#include <QInputDialog>
#include "vnotex.h"
#include "notebookmgr.h"
#include <widgets/mainwindow.h>
#include <widgets/viewarea.h>
#include <widgets/viewwindow.h>
#include <widgets/dialogs/selectdialog.h>
#include <widgets/markdownviewwindow.h>
#include <widgets/textviewwindow.h>
#include <utils/pathutils.h>
using namespace vnotex;
QString TaskHelper::normalPath(const QString &p_text)
{
#if defined (Q_OS_WIN)
return QString(p_text).replace('/', '\\');
#endif
return p_text;
}
QString TaskHelper::getCurrentFile()
{
auto win = VNoteX::getInst().getMainWindow()->getViewArea()->getCurrentViewWindow();
QString file;
if (win && win->getBuffer()) {
file = win->getBuffer()->getPath();
}
return file;
}
QSharedPointer<Notebook> TaskHelper::getCurrentNotebook()
{
const auto &notebookMgr = VNoteX::getInst().getNotebookMgr();
auto id = notebookMgr.getCurrentNotebookId();
if (id == Notebook::InvalidId) return nullptr;
return notebookMgr.findNotebookById(id);
}
QString TaskHelper::getFileNotebookFolder(const QString p_currentFile)
{
const auto &notebookMgr = VNoteX::getInst().getNotebookMgr();
const auto &notebooks = notebookMgr.getNotebooks();
for (auto notebook : notebooks) {
auto rootPath = notebook->getRootFolderAbsolutePath();
if (PathUtils::pathContains(rootPath, p_currentFile)) {
return rootPath;
}
}
return QString();
}
QString TaskHelper::getSelectedText()
{
auto window = VNoteX::getInst().getMainWindow()->getViewArea()->getCurrentViewWindow();
{
auto win = dynamic_cast<MarkdownViewWindow*>(window);
if (win) {
return win->selectedText();
}
}
{
auto win = dynamic_cast<TextViewWindow*>(window);
if (win) {
return win->selectedText();
}
}
return QString();
}
QStringList TaskHelper::getAllSpecialVariables(const QString &p_name, const QString &p_text)
{
QStringList list;
QRegularExpression re(QString(R"(\$\{[\t ]*%1[\t ]*:[\t ]*(.*?)[\t ]*\})").arg(p_name));
auto it = re.globalMatch(p_text);
while (it.hasNext()) {
list << it.next().captured(1);
}
return list;
}
QString TaskHelper::replaceAllSepcialVariables(const QString &p_name,
const QString &p_text,
const QMap<QString, QString> &p_map)
{
auto text = p_text;
for (auto i = p_map.begin(); i != p_map.end(); i++) {
auto key = QString(i.key()).replace(".", "\\.").replace("[", "\\[").replace("]", "\\]");
auto pattern = QRegularExpression(QString(R"(\$\{[\t ]*%1[\t ]*:[\t ]*%2[\t ]*\})").arg(p_name, key));
text = text.replace(pattern, i.value());
}
return text;
}
QString TaskHelper::evaluateJsonExpr(const QJsonObject &p_obj, const QString &p_expr)
{
QJsonValue value = p_obj;
for (auto token : p_expr.split('.')) {
auto pos = token.indexOf('[');
auto name = token.mid(0, pos);
value = value.toObject().value(name);
if (pos == -1) continue;
if (token.back() == ']') {
for (auto idx : token.mid(pos+1, token.length()-pos-2).split("][")) {
bool ok;
auto index = idx.toInt(&ok);
if (!ok) throw "Config variable syntax error!";
value = value.toArray().at(index);
}
} else {
throw "Config variable syntax error!";
}
}
if (value.isBool()) {
if (value.toBool()) return "true";
else return "false";
} else if (value.isDouble()) {
return QString::number(value.toDouble());
} else if (value.isNull()) {
return "null";
} else if (value.isUndefined()) {
return "undefined";
}
return value.toString();
}
QString TaskHelper::getPathSeparator()
{
#if defined (Q_OS_WIN)
return "\\";
#else
return "/";
#endif
}
QString TaskHelper::handleCommand(const QString &p_text,
QProcess *p_process,
const Task *p_task)
{
QRegularExpression re(R"(^::([a-zA-Z-]+)(.*?)?::(.*?)$)",
QRegularExpression::MultilineOption);
auto i = re.globalMatch(p_text);
while (i.hasNext()) {
auto match = i.next();
auto cmd = match.captured(1).toLower();
auto args = match.captured(2).trimmed().split(',');
auto value = match.captured(3);
QMap<QString, QString> arg;
for (const auto &i : args) {
auto s = i.trimmed();
auto p = s.indexOf('=');
auto name = s.mid(0, p);
QString val;
if (p != -1) {
val = s.mid(p+1);
}
arg.insert(name, val);
}
if (cmd == "show-message") {
QMessageBox box;
// fill message dto
auto msgId = arg.value("id");
QVector<QPushButton*> buttons;
if (!msgId.isEmpty()) {
MessageDTO msgd = p_task->getMessage(msgId);
box.setWindowTitle(msgd.title);
box.setText(msgd.text);
box.setDetailedText(msgd.detailedText);
for (auto button : msgd.buttons) {
buttons.append(box.addButton(button.text, QMessageBox::ActionRole));
}
}
// fill args
if (arg.contains("title")) box.setWindowTitle(arg["title"]);
if (arg.contains("text")) box.setText(arg["text"]);
if (arg.contains("detailedText")) box.setWindowTitle(arg["detailedText"]);
if (arg.contains("buttons")) {
buttons.clear();
for (auto button : arg["buttons"].split('|')) {
buttons.append(box.addButton(button, QMessageBox::ActionRole));
}
}
box.exec();
int clickedBtnId;
for (clickedBtnId = 0; clickedBtnId < buttons.size(); clickedBtnId++) {
if (box.clickedButton() == buttons.at(clickedBtnId)) {
break;
}
}
if (p_process) {
if (p_process->state() == QProcess::Running) {
p_process->write(QByteArray::number(clickedBtnId)+"\n");
}
} else {
qWarning() << "process finished!";
}
} else if (cmd == "show-inputdialog") {
QInputDialog dialog;
dialog.setWindowTitle(arg.value("title"));
} else if (cmd == "show-info") {
QMessageBox::information(VNoteX::getInst().getMainWindow(),
arg.value("title"),
value);
} else if (cmd == "show-question") {
auto ret = QMessageBox::question(VNoteX::getInst().getMainWindow(),
arg.value("title"),
value);
if (p_process) {
if (p_process->state() == QProcess::Running) {
p_process->write(QByteArray::number(ret)+"\n");
}
} else {
qWarning() << "process finished!";
}
}
}
auto text = p_text;
return text.replace(re, "");
}
TaskHelper::TaskHelper()
{
}

View File

@ -1,50 +0,0 @@
#ifndef TASKHELPER_H
#define TASKHELPER_H
#include <QString>
#include <QSharedPointer>
class QProcess;
namespace vnotex {
class Notebook;
class Task;
class TaskHelper
{
public:
// helper functions
static QString normalPath(const QString &p_text);
static QString getCurrentFile();
static QSharedPointer<Notebook> getCurrentNotebook();
static QString getFileNotebookFolder(const QString p_currentFile);
static QString getSelectedText();
static QStringList getAllSpecialVariables(const QString &p_name, const QString &p_text);
static QString replaceAllSepcialVariables(const QString &p_name,
const QString &p_text,
const QMap<QString, QString> &p_map);
static QString evaluateJsonExpr(const QJsonObject &p_obj,
const QString &p_expr);
static QString getPathSeparator();
static QString handleCommand(const QString &p_text,
QProcess *p_process,
const Task *p_task);
private:
TaskHelper();
};
} // ns vnotex
#endif // TASKHELPER_H

View File

@ -1,192 +0,0 @@
#include "taskmgr.h"
#include <QDir>
#include <QDebug>
#include <QFileSystemWatcher>
#include <QJsonDocument>
#include "configmgr.h"
#include "coreconfig.h"
#include "utils/pathutils.h"
#include "utils/fileutils.h"
#include "vnotex.h"
#include "notebookmgr.h"
#include "notebookconfigmgr/bundlenotebookconfigmgr.h"
using namespace vnotex;
QStringList TaskMgr::s_searchPaths;
TaskMgr::TaskMgr(QObject *parent)
: QObject(parent)
{
m_watcher = new QFileSystemWatcher(this);
}
void TaskMgr::init()
{
// load all tasks and watch them
loadAllTask();
connect(&VNoteX::getInst().getNotebookMgr(), &NotebookMgr::currentNotebookChanged,
this, &TaskMgr::loadAllTask);
connect(m_watcher, &QFileSystemWatcher::directoryChanged,
this, &TaskMgr::loadAllTask);
connect(m_watcher, &QFileSystemWatcher::fileChanged,
this, &TaskMgr::loadAllTask);
}
void TaskMgr::refresh()
{
loadAvailableTasks();
}
QVector<Task*> TaskMgr::getAllTasks() const
{
QVector<Task*> tasks;
tasks.append(m_appTasks);
tasks.append(m_userTasks);
tasks.append(m_notebookTasks);
return tasks;
}
const QVector<Task *> &TaskMgr::getAppTasks() const
{
return m_appTasks;
}
const QVector<Task *> &TaskMgr::getUserTasks() const
{
return m_userTasks;
}
const QVector<Task *> &TaskMgr::getNotebookTasks() const
{
return m_notebookTasks;
}
void TaskMgr::addSearchPath(const QString &p_path)
{
s_searchPaths << p_path;
}
QString TaskMgr::getNotebookTaskFolder()
{
const auto &notebookMgr = VNoteX::getInst().getNotebookMgr();
auto id = notebookMgr.getCurrentNotebookId();
if (id == Notebook::InvalidId) return QString();
auto notebook = notebookMgr.findNotebookById(id);
if (!notebook) return QString();
auto configMgr = notebook->getConfigMgr();
if (!configMgr) return QString();
auto configMgrName = configMgr->getName();
if (configMgrName == "vx.vnotex") {
QDir dir(notebook->getRootFolderAbsolutePath());
dir.cd(BundleNotebookConfigMgr::getConfigFolderName());
if (!dir.cd("tasks")) return QString();
return dir.absolutePath();
} else {
qWarning() << "Unknow notebook config type"<< configMgrName <<"task will not be load.";
}
return QString();
}
void TaskMgr::addWatchPaths(const QStringList &list)
{
if (list.isEmpty()) return ;
qDebug() << "addWatchPaths" << list;
m_watcher->addPaths(list);
}
void TaskMgr::clearWatchPaths()
{
auto entrys = m_watcher->files();
if (!entrys.isEmpty()) {
m_watcher->removePaths(entrys);
}
entrys = m_watcher->directories();
if (!entrys.isEmpty()) {
m_watcher->removePaths(entrys);
}
}
void TaskMgr::clearTasks()
{
m_appTasks.clear();
m_userTasks.clear();
m_notebookTasks.clear();
m_files.clear();
}
void TaskMgr::addAllTaskFolder()
{
s_searchPaths.clear();
auto &configMgr = ConfigMgr::getInst();
// App scope task folder
addSearchPath(configMgr.getAppTaskFolder());
// User scope task folder
addSearchPath(configMgr.getUserTaskFolder());
// Notebook scope task folder
auto path = getNotebookTaskFolder();
if (!path.isNull()) addSearchPath(path);
}
void TaskMgr::loadAllTask()
{
addAllTaskFolder();
loadAvailableTasks();
watchTaskEntrys();
emit taskChanged();
}
void TaskMgr::watchTaskEntrys()
{
clearWatchPaths();
addWatchPaths(s_searchPaths);
for (const auto &pa : s_searchPaths) {
addWatchPaths(FileUtils::entryListRecursively(pa, QStringList(), QDir::AllDirs));
}
addWatchPaths(m_files);
}
void TaskMgr::loadAvailableTasks()
{
m_files.clear();
auto &configMgr = ConfigMgr::getInst();
loadAvailableTasks(m_appTasks, configMgr.getAppTaskFolder());
loadAvailableTasks(m_userTasks, configMgr.getUserTaskFolder());
loadAvailableTasks(m_notebookTasks, getNotebookTaskFolder());
}
void TaskMgr::loadAvailableTasks(QVector<Task *> &p_tasks, const QString &p_searchPath)
{
p_tasks.clear();
if (p_searchPath.isEmpty()) return ;
const auto taskFiles = FileUtils::entryListRecursively(p_searchPath, {"*.json"}, QDir::Files);
for (auto &file : taskFiles) {
m_files << file;
checkAndAddTaskFile(p_tasks, file);
}
{
QStringList list;
for (auto task : p_tasks) {
list << QFileInfo(task->getFile()).fileName();
}
if (!p_tasks.isEmpty()) qDebug() << "load tasks" << list;
}
}
void TaskMgr::checkAndAddTaskFile(QVector<Task *> &p_tasks, const QString &p_file)
{
QJsonDocument json;
if (Task::isValidTaskFile(p_file, json)) {
const auto localeStr = ConfigMgr::getInst().getCoreConfig().getLocaleToUse();
p_tasks.push_back(Task::fromFile(p_file, json, localeStr, this));
}
}
void TaskMgr::deleteTask(Task *p_task)
{
Q_UNUSED(p_task);
}

View File

@ -1,79 +0,0 @@
#ifndef TASKMGR_H
#define TASKMGR_H
#include <QObject>
#include <QVector>
#include "task.h"
class QFileSystemWatcher;
namespace vnotex
{
class TaskMgr : public QObject
{
Q_OBJECT
public:
explicit TaskMgr(QObject *parent = nullptr);
// It will be invoked after MainWindow show
void init();
void refresh();
QVector<Task*> getAllTasks() const;
const QVector<Task*> &getAppTasks() const;
const QVector<Task*> &getUserTasks() const;
const QVector<Task*> &getNotebookTasks() const;
void deleteTask(Task *p_task);
static void addSearchPath(const QString &p_path);
static QString getNotebookTaskFolder();
signals:
void taskChanged();
private:
void addWatchPaths(const QStringList &list);
void clearWatchPaths();
void clearTasks();
void addAllTaskFolder();
void loadAllTask();
void watchTaskEntrys();
void loadAvailableTasks();
void loadAvailableTasks(QVector<Task*> &p_tasks, const QString &p_searchPath);
void loadTasks(const QString &p_path);
void checkAndAddTaskFile(QVector<Task*> &p_tasks, const QString &p_file);
QVector<Task*> m_appTasks;
QVector<Task*> m_userTasks;
QVector<Task*> m_notebookTasks;
// all json files in task folder
// maybe invalid
QStringList m_files;
QFileSystemWatcher *m_watcher;
// List of path to search for tasks.
static QStringList s_searchPaths;
};
} // ns vnotex
#endif // TASKMGR_H

View File

@ -1,334 +0,0 @@
#include "taskvariablemgr.h"
#include <QRegularExpression>
#include <QInputDialog>
#include <QApplication>
#include <QRandomGenerator>
#include <QTimeZone>
#include "vnotex.h"
#include "task.h"
#include "taskhelper.h"
#include "shellexecution.h"
#include "configmgr.h"
#include "mainconfig.h"
#include "notebook/notebook.h"
#include <widgets/mainwindow.h>
#include <widgets/dialogs/selectdialog.h>
using namespace vnotex;
TaskVariable::TaskVariable(TaskVariable::Type p_type,
const QString &p_name,
TaskVariable::Func p_func)
: m_type(p_type), m_name(p_name), m_func(p_func)
{
}
TaskVariableMgr::TaskVariableMgr()
: m_initialized(false)
{
}
void TaskVariableMgr::refresh()
{
init();
}
QString TaskVariableMgr::evaluate(const QString &p_text,
const Task *p_task) const
{
auto text = p_text;
auto eval = [&text](const QString &p_name, std::function<QString()> p_func) {
auto reg = QRegularExpression(QString(R"(\$\{[\t ]*%1[\t ]*\})").arg(p_name));
if (text.contains(reg)) {
text.replace(reg, p_func());
}
};
// current notebook variables
{
eval("notebookFolder", []() {
auto notebook = TaskHelper::getCurrentNotebook();
if (notebook) {
return TaskHelper::normalPath(notebook->getRootFolderAbsolutePath());
} else return QString();
});
eval("notebookFolderBasename", []() {
auto notebook = TaskHelper::getCurrentNotebook();
if (notebook) {
auto folder = notebook->getRootFolderAbsolutePath();
return QDir(folder).dirName();
} else return QString();
});
eval("notebookName", []() {
auto notebook = TaskHelper::getCurrentNotebook();
if (notebook) {
return notebook->getName();
} else return QString();
});
eval("notebookName", []() {
auto notebook = TaskHelper::getCurrentNotebook();
if (notebook) {
return notebook->getDescription();
} else return QString();
});
}
// current file variables
{
eval("file", []() {
return TaskHelper::normalPath(TaskHelper::getCurrentFile());
});
eval("fileNotebookFolder", []() {
auto file = TaskHelper::getCurrentFile();
return TaskHelper::normalPath(TaskHelper::getFileNotebookFolder(file));
});
eval("relativeFile", []() {
auto file = TaskHelper::getCurrentFile();
auto folder = TaskHelper::getFileNotebookFolder(file);
return QDir(folder).relativeFilePath(file);
});
eval("fileBasename", []() {
auto file = TaskHelper::getCurrentFile();
return QFileInfo(file).fileName();
});
eval("fileBasename", []() {
auto file = TaskHelper::getCurrentFile();
return QFileInfo(file).completeBaseName();
});
eval("fileDirname", []() {
auto file = TaskHelper::getCurrentFile();
return TaskHelper::normalPath(QFileInfo(file).dir().absolutePath());
});
eval("fileExtname", []() {
auto file = TaskHelper::getCurrentFile();
return QFileInfo(file).suffix();
});
eval("selectedText", []() {
return TaskHelper::getSelectedText();
});
eval("cwd", [p_task]() {
return TaskHelper::normalPath(p_task->getOptionsCwd());
});
eval("taskFile", [p_task]() {
return TaskHelper::normalPath(p_task->getFile());
});
eval("taskDirname", [p_task]() {
return TaskHelper::normalPath(QFileInfo(p_task->getFile()).dir().absolutePath());
});
eval("execPath", []() {
return TaskHelper::normalPath(qApp->applicationFilePath());
});
eval("pathSeparator", []() {
return TaskHelper::getPathSeparator();
});
eval("notebookTaskFolder", []() {
return TaskHelper::normalPath(TaskMgr::getNotebookTaskFolder());
});
eval("userTaskFolder", []() {
return TaskHelper::normalPath(ConfigMgr::getInst().getUserTaskFolder());
});
eval("appTaskFolder", []() {
return TaskHelper::normalPath(ConfigMgr::getInst().getAppTaskFolder());
});
eval("userThemeFolder", []() {
return TaskHelper::normalPath(ConfigMgr::getInst().getUserThemeFolder());
});
eval("appThemeFolder", []() {
return TaskHelper::normalPath(ConfigMgr::getInst().getAppThemeFolder());
});
eval("userDocsFolder", []() {
return TaskHelper::normalPath(ConfigMgr::getInst().getUserDocsFolder());
});
eval("appDocsFolder", []() {
return TaskHelper::normalPath(ConfigMgr::getInst().getAppDocsFolder());
});
}
// Magic variables
{
auto cDT = QDateTime::currentDateTime();
for(auto s : {
"d", "dd", "ddd", "dddd", "M", "MM", "MMM", "MMMM",
"yy", "yyyy", "h", "hh", "H", "HH", "m", "mm",
"s", "ss", "z", "zzz", "AP", "A", "ap", "a"
}) eval(QString("magic:%1").arg(s), [s]() {
return QDateTime::currentDateTime().toString(s);
});
eval("magic:random", []() {
return QString::number(QRandomGenerator::global()->generate());
});
eval("magic:random_d", []() {
return QString::number(QRandomGenerator::global()->generate());
});
eval("magic:date", []() {
return QDate::currentDate().toString("yyyy-MM-dd");
});
eval("magic:da", []() {
return QDate::currentDate().toString("yyyyMMdd");
});
eval("magic:time", []() {
return QTime::currentTime().toString("hh:mm:ss");
});
eval("magic:datetime", []() {
return QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
});
eval("magic:dt", []() {
return QDateTime::currentDateTime().toString("yyyyMMdd hh:mm:ss");
});
eval("magic:note", []() {
auto file = TaskHelper::getCurrentFile();
return QFileInfo(file).fileName();
});
eval("magic:no", []() {
auto file = TaskHelper::getCurrentFile();
return QFileInfo(file).completeBaseName();
});
eval("magic:t", []() {
auto dt = QDateTime::currentDateTime();
return dt.timeZone().displayName(dt, QTimeZone::ShortName);
});
eval("magic:w", []() {
return QString::number(QDate::currentDate().weekNumber());
});
eval("magic:att", []() {
// TODO
return QString();
});
}
// environment variables
do {
QMap<QString, QString> map;
auto list = TaskHelper::getAllSpecialVariables("env", p_text);
list.erase(std::unique(list.begin(), list.end()), list.end());
if (list.isEmpty()) break;
for (const auto &name : list) {
auto value = QProcessEnvironment::systemEnvironment().value(name);
map.insert(name, value);
}
text = TaskHelper::replaceAllSepcialVariables("env", text, map);
} while(0);
// config variables
do {
const auto config_obj = ConfigMgr::getInst().getConfig().toJson();
QMap<QString, QString> map;
auto list = TaskHelper::getAllSpecialVariables("config", p_text);
if (list.isEmpty()) break;
list.erase(std::unique(list.begin(), list.end()), list.end());
for (const auto &name : list) {
auto value = TaskHelper::evaluateJsonExpr(config_obj, name);
qDebug() << "insert" << name << value;
map.insert(name, value);
}
text = TaskHelper::replaceAllSepcialVariables("config", text, map);
} while(0);
// input variables
text = evaluateInputVariables(text, p_task);
// shell variables
text = evaluateShellVariables(text, p_task);
return text;
}
QStringList TaskVariableMgr::evaluate(const QStringList &p_list,
const Task *p_task) const
{
QStringList list;
for (const auto &s : p_list) {
list << evaluate(s, p_task);
}
return list;
}
void TaskVariableMgr::init()
{
if (m_initialized) return ;
m_initialized = true;
add(TaskVariable::FunctionBased,
"file",
[](const TaskVariable *, const Task *) {
return QString();
});
}
void TaskVariableMgr::add(TaskVariable::Type p_type,
const QString &p_name,
TaskVariable::Func p_func)
{
m_predefs.insert(p_name, TaskVariable(p_type, p_name, p_func));
}
QString TaskVariableMgr::evaluateInputVariables(const QString &p_text,
const Task *p_task) const
{
QMap<QString, QString> map;
auto list = TaskHelper::getAllSpecialVariables("input", p_text);
list.erase(std::unique(list.begin(), list.end()), list.end());
if (list.isEmpty()) return p_text;
for (const auto &id : list) {
auto input = p_task->getInput(id);
QString text;
auto mainwin = VNoteX::getInst().getMainWindow();
if (input.type == "promptString") {
auto desc = evaluate(input.description, p_task);
auto defaultText = evaluate(input.default_, p_task);
QInputDialog dialog(mainwin);
dialog.setInputMode(QInputDialog::TextInput);
if (input.password) dialog.setTextEchoMode(QLineEdit::Password);
else dialog.setTextEchoMode(QLineEdit::Normal);
dialog.setWindowTitle(p_task->getLabel());
dialog.setLabelText(desc);
dialog.setTextValue(defaultText);
if (dialog.exec() == QDialog::Accepted) {
text = dialog.textValue();
} else {
throw "TaskCancle";
}
} else if (input.type == "pickString") {
// TODO: select description
SelectDialog dialog(p_task->getLabel(), input.description, mainwin);
for (int i = 0; i < input.options.size(); i++) {
dialog.addSelection(input.options.at(i), i);
}
if (dialog.exec() == QDialog::Accepted) {
int selection = dialog.getSelection();
text = input.options.at(selection);
} else {
throw "TaskCancle";
}
}
map.insert(input.id, text);
}
return TaskHelper::replaceAllSepcialVariables("input", p_text, map);
}
QString TaskVariableMgr::evaluateShellVariables(const QString &p_text,
const Task *p_task) const
{
QMap<QString, QString> map;
auto list = TaskHelper::getAllSpecialVariables("shell", p_text);
list.erase(std::unique(list.begin(), list.end()), list.end());
if (list.isEmpty()) return p_text;
for (const auto &cmd : list) {
QProcess process;
process.setWorkingDirectory(p_task->getOptionsCwd());
ShellExecution::setupProcess(&process, cmd);
process.start();
if (!process.waitForStarted(1000) || !process.waitForFinished(1000)) {
throw "Shell variable execution timeout";
}
auto res = process.readAllStandardOutput().trimmed();
map.insert(cmd, res);
}
return TaskHelper::replaceAllSepcialVariables("shell", p_text, map);
}

View File

@ -1,73 +0,0 @@
#ifndef TASKVARIABLEMGR_H
#define TASKVARIABLEMGR_H
#include <functional>
#include <QHash>
#include <QString>
#include <QSharedPointer>
namespace vnotex {
class Task;
class TaskVariable;
class TaskVariableMgr;
class Notebook;
class TaskVariable {
public:
enum Type
{
// Definition is plain text.
Simple = 0,
// Definition is a function call to get the value.
FunctionBased,
// Like FunctionBased, but should re-evaluate the value for each occurence.
Dynamic,
Invalid
};
typedef std::function<QString(const TaskVariable *,
const Task*)> Func;
TaskVariable(Type p_type,
const QString &p_name,
Func p_func = nullptr);
private:
Type m_type;
QString m_name;
Func m_func;
};
class TaskVariableMgr
{
public:
TaskVariableMgr();
void refresh();
QString evaluate(const QString &p_text,
const Task *p_task) const;
QStringList evaluate(const QStringList &p_list,
const Task *p_task) const;
private:
void init();
void add(TaskVariable::Type p_type,
const QString &p_name,
TaskVariable::Func p_func = nullptr);
QString evaluateInputVariables(const QString &p_text,
const Task *p_task) const;
QString evaluateShellVariables(const QString &p_text,
const Task *p_task) const;
QHash<QString, TaskVariable> m_predefs;
bool m_initialized;
};
} // ns vnotex
#endif // TASKVARIABLEMGR_H

View File

@ -14,6 +14,7 @@
#include "quickaccesshelper.h"
#include <utils/docsutils.h>
#include <task/taskmgr.h>
using namespace vnotex;
@ -63,6 +64,8 @@ void VNoteX::initTaskMgr()
{
Q_ASSERT(!m_taskMgr);
m_taskMgr = new TaskMgr(this);
connect(m_taskMgr, &TaskMgr::taskOutputRequested,
this, &VNoteX::showOutputRequested);
}
ThemeMgr &VNoteX::getThemeMgr() const

View File

@ -6,7 +6,6 @@
#include "noncopyable.h"
#include "thememgr.h"
#include "taskmgr.h"
#include "global.h"
namespace vnotex
@ -19,6 +18,7 @@ namespace vnotex
class Event;
class Notebook;
struct ComplexLocation;
class TaskMgr;
class VNoteX : public QObject, private Noncopyable
{

View File

@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1611462360764" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2562" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M585.142857 804.571429h365.714286v-73.142858H585.142857v73.142858z m-219.428571-292.571429h585.142857v-73.142857H365.714286v73.142857z m365.714285-292.571429h219.428572V146.285714h-219.428572v73.142857z m292.571429 475.428572v146.285714c0 20.004571-16.566857 36.571429-36.571429 36.571429H36.571429c-20.004571 0-36.571429-16.566857-36.571429-36.571429v-146.285714c0-20.004571 16.566857-36.571429 36.571429-36.571429h950.857142c20.004571 0 36.571429 16.566857 36.571429 36.571429z m0-292.571429v146.285715c0 20.004571-16.566857 36.571429-36.571429 36.571428H36.571429c-20.004571 0-36.571429-16.566857-36.571429-36.571428v-146.285715c0-20.004571 16.566857-36.571429 36.571429-36.571428h950.857142c20.004571 0 36.571429 16.566857 36.571429 36.571428z m0-292.571428v146.285714c0 20.004571-16.566857 36.571429-36.571429 36.571429H36.571429c-20.004571 0-36.571429-16.566857-36.571429-36.571429V109.714286c0-20.004571 16.566857-36.571429 36.571429-36.571429h950.857142c20.004571 0 36.571429 16.566857 36.571429 36.571429z" fill="" p-id="2563"></path></svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1639102667660" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17408" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M967.857329 886.684588 67.056232 886.684588c-27.567869 0-49.995671-22.427802-49.995671-49.995671L17.060561 698.103582c0-27.567869 22.427802-49.995671 49.995671-49.995671L967.857329 648.107911c27.567869 0 49.995671 22.427802 49.995671 49.995671L1017.853 836.688917C1017.854023 864.256786 995.426221 886.684588 967.857329 886.684588zM67.056232 678.807097c-10.460236 0-19.296485 8.837272-19.296485 19.296485L47.759747 836.688917c0 10.460236 8.836249 19.296485 19.296485 19.296485L967.857329 855.985402c10.460236 0 19.296485-8.837272 19.296485-19.296485L987.153814 698.103582c0-10.460236-8.837272-19.296485-19.296485-19.296485L67.056232 678.807097zM948.561867 817.392432 571.399855 817.392432l0-99.991342 377.162012 0L948.561867 817.392432zM602.099041 786.693246l315.76364 0 0-38.59297L602.099041 748.100276 602.099041 786.693246zM967.857329 609.514941 67.056232 609.514941c-27.567869 0-49.995671-22.427802-49.995671-49.995671L17.060561 420.933935c0-27.567869 22.427802-49.995671 49.995671-49.995671L967.857329 370.938264c27.567869 0 49.995671 22.427802 49.995671 49.995671l0 138.584312C1017.854023 587.087139 995.426221 609.514941 967.857329 609.514941zM67.056232 401.63745c-10.460236 0-19.296485 8.837272-19.296485 19.296485l0 138.584312c0 10.460236 8.836249 19.296485 19.296485 19.296485L967.857329 578.814732c10.460236 0 19.296485-8.837272 19.296485-19.296485L987.153814 420.933935c0-10.460236-8.837272-19.296485-19.296485-19.296485L67.056232 401.63745zM948.561867 540.221762 363.522364 540.221762l0-99.991342 585.039503 0L948.561867 540.221762zM394.22155 509.523599l523.641131 0 0-38.59297L394.22155 470.930629 394.22155 509.523599zM967.857329 332.345294 67.056232 332.345294c-27.567869 0-49.995671-22.427802-49.995671-49.995671L17.060561 143.764288c0-27.567869 22.427802-49.995671 49.995671-49.995671L967.857329 93.768617c27.567869 0 49.995671 22.427802 49.995671 49.995671l0 138.585335C1017.854023 309.917492 995.426221 332.345294 967.857329 332.345294zM67.056232 124.467803c-10.460236 0-19.296485 8.836249-19.296485 19.296485l0 138.585335c0 10.460236 8.836249 19.296485 19.296485 19.296485L967.857329 301.646108c10.460236 0 19.296485-8.837272 19.296485-19.296485L987.153814 143.764288c0-10.460236-8.837272-19.296485-19.296485-19.296485L67.056232 124.467803zM948.561867 263.053138 709.984167 263.053138l0-99.991342 238.5777 0L948.561867 263.053138zM740.683353 232.353952l177.179328 0 0-38.59297L740.683353 193.760982 740.683353 232.353952z" p-id="17409" fill="#000000"></path></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -37,12 +37,12 @@
"id": "msg",
"type": "promptString",
"description": {
"en_US": "Please provide a commit message",
"en_US": "Please input the commit message",
"zh_CN": "请输入提交信息",
"ja_JP": "コミットメッセージを提供してください"
},
"default": {
"en_US": "Update note on ${magic:datetime}",
"en_US": "Update note at ${magic:datetime}",
"zh_CN": "更新笔记于 ${magic:datetime}",
"ja_JP": "アップデート ${magic:datetime}"
}
@ -77,4 +77,4 @@
"command": "git log -10 --graph --pretty=format:'%h -%d %s (%cr) <%an>' --abbrev-commit"
}
]
}
}

View File

@ -192,13 +192,15 @@ bool WebViewExporter::writeHtmlFile(const QString &p_file,
bool p_embedImages)
{
const auto baseName = QFileInfo(p_file).completeBaseName();
auto title = QString("%1 - %2").arg(baseName, ConfigMgr::c_appName);
const QString resourceFolderName = baseName + "_files";
auto resourceFolder = PathUtils::concatenateFilePath(PathUtils::parentDirPath(p_file), resourceFolderName);
qDebug() << "HTML files folder" << resourceFolder;
auto htmlContent = m_exportHtmlTemplate;
const auto title = QString("%1").arg(baseName);
HtmlTemplateHelper::fillTitle(htmlContent, title);
if (!p_styleContent.isEmpty() && p_embedStyles) {

View File

@ -20,7 +20,9 @@
using namespace vnotex;
const QString SnippetMgr::c_snippetSymbolRegExp = QString("%([^%]+)%");
const QChar SnippetMgr::c_snippetSymbolGuard = QLatin1Char('%');
const QString SnippetMgr::c_snippetSymbolRegExp = QString("%1([^%]+)%1").arg(c_snippetSymbolGuard);
SnippetMgr::SnippetMgr()
{
@ -231,10 +233,11 @@ void SnippetMgr::applySnippet(const QString &p_name,
p_textEdit->setTextCursor(cursor);
}
QString SnippetMgr::applySnippetBySymbol(const QString &p_content) const
QString SnippetMgr::applySnippetBySymbol(const QString &p_content,
const OverrideMap &p_overrides) const
{
int offset = 0;
return applySnippetBySymbol(p_content, QString(), offset);
return applySnippetBySymbol(p_content, QString(), offset, p_overrides);
}
QString SnippetMgr::applySnippetBySymbol(const QString &p_content,
@ -244,11 +247,11 @@ QString SnippetMgr::applySnippetBySymbol(const QString &p_content,
{
QString content(p_content);
int maxTimes = 100;
int maxTimesAtSamePos = 100;
QRegularExpression regExp(c_snippetSymbolRegExp);
int pos = 0;
while (pos < content.size() && maxTimes-- > 0) {
while (pos < content.size()) {
QRegularExpressionMatch match;
int idx = content.indexOf(regExp, pos, &match);
if (idx == -1) {
@ -288,6 +291,13 @@ QString SnippetMgr::applySnippetBySymbol(const QString &p_content,
}
// @afterText may still contains snippet symbol.
if (pos == idx) {
if (--maxTimesAtSamePos == 0) {
break;
}
} else {
maxTimesAtSamePos = 100;
}
pos = idx;
}
@ -426,8 +436,10 @@ void SnippetMgr::addDynamicSnippet(QVector<QSharedPointer<Snippet>> &p_snippets,
SnippetMgr::OverrideMap SnippetMgr::generateOverrides(const Buffer *p_buffer)
{
OverrideMap overrides;
overrides.insert(QStringLiteral("note"), p_buffer->getName());
overrides.insert(QStringLiteral("no"), QFileInfo(p_buffer->getName()).completeBaseName());
if (p_buffer) {
overrides.insert(QStringLiteral("note"), p_buffer->getName());
overrides.insert(QStringLiteral("no"), QFileInfo(p_buffer->getName()).completeBaseName());
}
return overrides;
}
@ -438,3 +450,8 @@ SnippetMgr::OverrideMap SnippetMgr::generateOverrides(const QString &p_fileName)
overrides.insert(QStringLiteral("no"), QFileInfo(p_fileName).completeBaseName());
return overrides;
}
QString SnippetMgr::generateSnippetSymbol(const QString &p_snippetName)
{
return c_snippetSymbolGuard + p_snippetName + c_snippetSymbolGuard;
}

View File

@ -60,14 +60,19 @@ namespace vnotex
int &p_cursorOffset,
const OverrideMap &p_overrides = OverrideMap()) const;
QString applySnippetBySymbol(const QString &p_content) const;
QString applySnippetBySymbol(const QString &p_content,
const OverrideMap &p_overrides = OverrideMap()) const;
// Generate standard overrides for given buffer.
static OverrideMap generateOverrides(const Buffer *p_buffer);
// Generate standard overrides.
// Generate standard overrides for given file name.
static OverrideMap generateOverrides(const QString &p_fileName);
static QString generateSnippetSymbol(const QString &p_snippetName);
static const QChar c_snippetSymbolGuard;
// %name%.
// Captured texts:
// 1 - The name of the snippet.

View File

@ -56,6 +56,8 @@ include($$PWD/snippet/snippet.pri)
include($$PWD/imagehost/imagehost.pri)
include($$PWD/task/task.pri)
include($$PWD/core/core.pri)
include($$PWD/widgets/widgets.pri)

View File

@ -0,0 +1,72 @@
#include "shellexecution.h"
#include <QFileInfo>
#include <QProcess>
using namespace vnotex;
void ShellExecution::setupProcess(QProcess *p_process,
const QString &p_program,
const QStringList &p_args,
const QString &p_shellExec,
const QStringList &p_shellArgs)
{
auto shellExec = p_shellExec.isNull() ? defaultShell() : p_shellExec;
auto shellArgs = p_shellArgs.isEmpty() ? defaultShellArguments(shellExec) : p_shellArgs;
p_process->setProgram(shellExec);
const auto shell = shellBasename(p_shellExec);
QStringList allArgs(shellArgs);
if (shell == "bash") {
allArgs << (QStringList() << p_program << quoteSpaces(p_args)).join(' ');
} else {
allArgs << p_program << p_args;
}
p_process->setArguments(allArgs);
}
QString ShellExecution::shellBasename(const QString &p_shell)
{
return QFileInfo(p_shell).baseName().toLower();
}
QString ShellExecution::defaultShell()
{
#ifdef Q_OS_WIN
return QStringLiteral("PowerShell.exe");
#else
return QStringLiteral("/bin/bash");
#endif
}
QStringList ShellExecution::defaultShellArguments(const QString &p_shell)
{
auto shell = shellBasename(p_shell);
if (shell == "cmd") {
return {"/C"};
} else if (shell == "powershell" || p_shell == "pwsh") {
return {"-Command"};
} else if (shell == "bash") {
return {"-c"};
}
return {};
}
QString ShellExecution::quoteSpace(const QString &p_arg)
{
if (p_arg.contains(QLatin1Char(' '))) {
return QLatin1Char('"') + p_arg + QLatin1Char('"');
} else {
return p_arg;
}
}
QStringList ShellExecution::quoteSpaces(const QStringList &p_args)
{
QStringList args;
for (const auto &arg : p_args) {
args << quoteSpace(arg);
}
return args;
}

34
src/task/shellexecution.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef SHELLEXECUTION_H
#define SHELLEXECUTION_H
#include <QStringList>
class QProcess;
namespace vnotex
{
class ShellExecution
{
public:
ShellExecution() = delete;
static void setupProcess(QProcess *p_process,
const QString &p_program,
const QStringList &p_args = QStringList(),
const QString &p_shellExec = QString(),
const QStringList &p_shellArgs = QStringList());
static QString defaultShell();
static QStringList defaultShellArguments(const QString &p_shell);
private:
static QString shellBasename(const QString &p_shell);
static QString quoteSpace(const QString &p_arg);
static QStringList quoteSpaces(const QStringList &p_args);
};
}
#endif // SHELLEXECUTION_H

588
src/task/task.cpp Normal file
View File

@ -0,0 +1,588 @@
#include "task.h"
#include <QJsonDocument>
#include <QVersionNumber>
#include <QDebug>
#include <QJsonValue>
#include <QJsonArray>
#include <QAction>
#include <QRegularExpression>
#include <QInputDialog>
#include <QTextCodec>
#include <QRandomGenerator>
#include <QJsonObject>
#include <QProcess>
#include <QScopedPointer>
#include <utils/fileutils.h>
#include <utils/pathutils.h>
#include <core/vnotex.h>
#include <core/exception.h>
#include <notebook/notebook.h>
#include <buffer/buffer.h>
#include "shellexecution.h"
#include "taskmgr.h"
using namespace vnotex;
QString Task::s_latestVersion = "0.1.3";
QSharedPointer<Task> Task::fromFile(const QString &p_file, const QString &p_locale, TaskMgr *p_taskMgr)
{
QSharedPointer<Task> task(new Task(p_locale, p_file, p_taskMgr, nullptr));
const auto obj = FileUtils::readJsonFile(p_file);
if (fromJson(task.data(), obj)) {
return task;
}
return nullptr;
}
bool Task::fromJson(Task *p_task, const QJsonObject &p_obj)
{
// For child task, it will inherit the version from parent.
if (p_obj.contains("version")) {
p_task->m_dto.version = p_obj["version"].toString();
}
const auto version = QVersionNumber::fromString(p_task->getVersion());
if (version.isNull()) {
qWarning() << "invalid task" << p_task->m_dto._source;
return false;
}
if (version < QVersionNumber(1, 0, 0)) {
return fromJsonV0(p_task, p_obj);
} else {
qWarning() << "unknown task version" << version << p_task->m_dto._source;
return false;
}
}
bool Task::fromJsonV0(Task *p_task, const QJsonObject &p_obj, bool p_mergeTasks)
{
if (p_obj.contains("type")) {
p_task->m_dto.type = p_obj["type"].toString();
}
if (p_obj.contains("icon")) {
QString iconPath = p_obj["icon"].toString();
if (!iconPath.isEmpty()) {
if (QDir::isRelativePath(iconPath)) {
iconPath = QFileInfo(p_task->m_dto._source).dir().absoluteFilePath(iconPath);
}
if (QFileInfo::exists(iconPath)) {
p_task->m_dto.icon = iconPath;
} else {
qWarning() << "task icon does not exist" << p_task->getLabel() << iconPath;
}
}
}
if (p_obj.contains("shortcut")) {
p_task->m_dto.shortcut = p_obj["shortcut"].toString();
}
if (p_obj.contains("type")) {
p_task->m_dto.type = p_obj["type"].toString();
}
if (p_obj.contains("command")) {
p_task->m_dto.command = getLocaleString(p_obj["command"], p_task->m_locale);
}
if (p_obj.contains("args")) {
p_task->m_dto.args = getLocaleStringList(p_obj["args"], p_task->m_locale);
}
if (p_obj.contains("label")) {
p_task->m_dto.label = getLocaleString(p_obj["label"], p_task->m_locale);
} else if (p_task->m_dto.label.isNull() && !p_task->m_dto.command.isNull()) {
p_task->m_dto.label = p_task->m_dto.command;
}
if (p_obj.contains("options")) {
auto options = p_obj["options"].toObject();
if (options.contains("cwd")) {
p_task->m_dto.options.cwd = options["cwd"].toString();
}
if (options.contains("env")) {
p_task->m_dto.options.env.clear();
auto env = options["env"].toObject();
for (auto it = env.begin(); it != env.end(); it++) {
auto value = getLocaleString(it.value(), p_task->m_locale);
p_task->m_dto.options.env.insert(it.key(), value);
}
}
if (options.contains("shell") && p_task->getType() == "shell") {
auto shell = options["shell"].toObject();
if (shell.contains("executable")) {
p_task->m_dto.options.shell.executable = shell["executable"].toString();
}
if (shell.contains("args")) {
p_task->m_dto.options.shell.args.clear();
const auto arr = shell["args"].toArray();
for (int i = 0; i < arr.size(); ++i) {
p_task->m_dto.options.shell.args << arr[i].toString();
}
}
}
}
if (p_obj.contains("tasks")) {
if (!p_mergeTasks) {
p_task->m_children.clear();
}
auto arr = p_obj["tasks"].toArray();
for (int i = 0; i < arr.size(); ++i) {
QScopedPointer<Task> childTask(new Task(p_task->m_locale, p_task->getFile(), p_task->m_taskMgr, p_task));
if (fromJson(childTask.data(), arr[i].toObject())) {
connect(childTask.data(), &Task::outputRequested,
p_task, &Task::outputRequested);
p_task->m_children.append(childTask.take());
}
}
}
if (p_obj.contains("inputs")) {
p_task->m_dto.inputs.clear();
auto arr = p_obj["inputs"].toArray();
for (int i = 0; i < arr.size(); ++i) {
const auto inputObj = arr[i].toObject();
InputDTO input;
if (inputObj.contains("id")) {
input.id = inputObj["id"].toString();
} else {
qWarning() << "Input configuration not contains id";
}
if (inputObj.contains("type")) {
input.type = inputObj["type"].toString();
} else {
input.type = "promptString";
}
if (inputObj.contains("description")) {
input.description = getLocaleString(inputObj["description"], p_task->m_locale);
}
if (inputObj.contains("default")) {
input.default_ = getLocaleString(inputObj["default"], p_task->m_locale);
}
if (input.type == "promptString" && inputObj.contains("password")) {
input.password = inputObj["password"].toBool();
} else {
input.password = false;
}
if (input.type == "pickString") {
if (inputObj.contains("options")) {
input.options = getLocaleStringList(inputObj["options"], p_task->m_locale);
}
if (!input.default_.isNull() && !input.options.contains(input.default_)) {
qWarning() << "default of input must be one of the option values";
}
}
p_task->m_dto.inputs << input;
}
}
if (p_obj.contains("messages")) {
p_task->m_dto.messages.clear();
auto arr = p_obj["messages"].toArray();
for (int i = 0; i < arr.size(); ++i) {
const auto msgObj = arr[i].toObject();
MessageDTO msg;
if (msgObj.contains("id")) {
msg.id = msgObj["id"].toString();
} else {
qWarning() << "Message configuration not contain id";
}
if (msgObj.contains("type")) {
msg.type = msgObj["type"].toString();
} else {
msg.type = "information";
}
if (msgObj.contains("title")) {
msg.title = getLocaleString(msgObj["title"], p_task->m_locale);
}
if (msgObj.contains("text")) {
msg.text = getLocaleString(msgObj["text"], p_task->m_locale);
}
if (msgObj.contains("detailedText")) {
msg.detailedText = getLocaleString(msgObj["detailedText"], p_task->m_locale);
}
if (msgObj.contains("buttons")) {
auto buttonsArr = msgObj["buttons"].toArray();
for (int j = 0; j < buttonsArr.size(); ++j) {
const auto btnObj = buttonsArr[j].toObject();
ButtonDTO btn;
btn.text = getLocaleString(btnObj["text"], p_task->m_locale);
msg.buttons << btn;
}
}
p_task->m_dto.messages << msg;
}
}
// OS-specific task configuration
#if defined (Q_OS_WIN)
#define OS_SPEC "windows"
#elif defined (Q_OS_MACOS)
#define OS_SPEC "osx"
#else
#define OS_SPEC "linux"
#endif
if (p_obj.contains(OS_SPEC)) {
const auto osObj = p_obj[OS_SPEC].toObject();
fromJsonV0(p_task, osObj, true);
}
#undef OS_SPEC
return true;
}
const QString &Task::getVersion() const
{
return m_dto.version;
}
const QString &Task::getType() const
{
return m_dto.type;
}
QString Task::getCommand()
{
return variableMgr().evaluate(this, m_dto.command);
}
QStringList Task::getArgs()
{
return variableMgr().evaluate(this, m_dto.args);
}
const QString &Task::getLabel() const
{
return m_dto.label;
}
const QString &Task::getIcon() const
{
return m_dto.icon;
}
const QString &Task::getShortcut() const
{
return m_dto.shortcut;
}
QString Task::getOptionsCwd()
{
auto cwd = m_dto.options.cwd;
if (!cwd.isNull()) {
return variableMgr().evaluate(this, cwd);
}
auto notebook = TaskVariableMgr::getCurrentNotebook();
if (notebook) {
cwd = notebook->getRootFolderAbsolutePath();
}
if (!cwd.isNull()) {
return cwd;
}
auto buffer = TaskVariableMgr::getCurrentBuffer();
if (buffer) {
return QFileInfo(buffer->getPath()).dir().absolutePath();
}
return QFileInfo(m_dto._source).dir().absolutePath();
}
const QMap<QString, QString> &Task::getOptionsEnv() const
{
return m_dto.options.env;
}
const QString &Task::getOptionsShellExecutable() const
{
return m_dto.options.shell.executable;
}
QStringList Task::getOptionsShellArgs()
{
if (m_dto.options.shell.args.isEmpty()) {
return ShellExecution::defaultShellArguments(m_dto.options.shell.executable);
} else {
return variableMgr().evaluate(this, m_dto.options.shell.args);
}
}
const QVector<Task *> &Task::getChildren() const
{
return m_children;
}
const QVector<InputDTO> &Task::getInputs() const
{
return m_dto.inputs;
}
const InputDTO *Task::findInput(const QString &p_id) const
{
for (const auto &input : m_dto.inputs) {
if (input.id == p_id) {
return &input;
}
}
qWarning() << "input" << p_id << "not found for task" << getLabel();
return nullptr;
}
const MessageDTO *Task::findMessage(const QString &p_id) const
{
for (const auto &msg : m_dto.messages) {
if (msg.id == p_id) {
return &msg;
}
}
qWarning() << "message" << p_id << "not found for task" << getLabel();
return nullptr;
}
const QString &Task::getFile() const
{
return m_dto._source;
}
Task::Task(const QString &p_locale,
const QString &p_file,
TaskMgr *p_taskMgr,
QObject *p_parent)
: QObject(p_parent),
m_taskMgr(p_taskMgr),
m_locale(p_locale)
{
m_dto._source = p_file;
m_dto.version = s_latestVersion;
m_dto.type = "shell";
m_dto.options.shell.executable = ShellExecution::defaultShell();
// Inherit configuration.
m_parent = qobject_cast<Task *>(p_parent);
if (m_parent) {
m_dto.version = m_parent->m_dto.version;
m_dto.type = m_parent->m_dto.type;
m_dto.command = m_parent->m_dto.command;
m_dto.args = m_parent->m_dto.args;
m_dto.options.cwd = m_parent->m_dto.options.cwd;
m_dto.options.env = m_parent->m_dto.options.env;
m_dto.options.shell.executable = m_parent->m_dto.options.shell.executable;
m_dto.options.shell.args = m_parent->m_dto.options.shell.args;
// Do not inherit label/inputs/tasks.
} else {
m_dto.label = QFileInfo(p_file).baseName();
}
}
QProcess *Task::setupProcess()
{
setCancelled(false);
auto command = getCommand();
if (command.isEmpty()) {
return nullptr;
}
QScopedPointer<QProcess> scopedProcess(new QProcess(this));
auto process = scopedProcess.data();
process->setWorkingDirectory(getOptionsCwd());
const auto &optionsEnv = getOptionsEnv();
if (!optionsEnv.isEmpty()) {
auto env = QProcessEnvironment::systemEnvironment();
for (auto it = optionsEnv.begin(); it != optionsEnv.end(); it++) {
env.insert(it.key(), it.value());
}
process->setProcessEnvironment(env);
}
const auto args = getArgs();
const auto &type = getType();
if (type == "shell") {
ShellExecution::setupProcess(process,
command,
args,
getOptionsShellExecutable(),
getOptionsShellArgs());
} else if (getType() == "process") {
process->setProgram(command);
process->setArguments(args);
}
if (isCancelled()) {
return nullptr;
}
scopedProcess.take();
connect(process, &QProcess::started,
this, [this]() {
emit outputRequested(tr("[Task (%1) started]\n").arg(getLabel()));
});
connect(process, &QProcess::readyReadStandardOutput,
this, [this, process]() {
auto text = decodeText(process->readAllStandardOutput());
// TODO: interaction with process.
emit outputRequested(text);
});
connect(process, &QProcess::readyReadStandardError,
this, [this, process]() {
auto text = process->readAllStandardError();
emit outputRequested(decodeText(text));
});
connect(process, &QProcess::errorOccurred,
this, [this](QProcess::ProcessError error) {
emit outputRequested(tr("[Task (%1) error occurred (%2)]\n").arg(getLabel()).arg(error));
});
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, process](int exitCode) {
emit outputRequested(tr("\n[Task (%1) finished (%2)]\n").arg(getLabel()).arg(exitCode));
process->deleteLater();
});
return process;
}
void Task::run()
{
QProcess *process;
try {
process = setupProcess();
} catch (const char *msg) {
qWarning() << "exception while setup process" << msg;
return ;
}
if (process) {
qDebug() << "run task" << process->program() << process->arguments();
process->start();
}
}
const TaskDTO &Task::getDTO() const
{
return m_dto;
}
QString Task::decodeText(const QByteArray &p_text)
{
static QByteArrayList codecNames = {
"UTF-8",
"System",
"UTF-16",
"GB18030"
};
for (const auto &name : codecNames) {
auto text = decodeText(p_text, name);
if (!text.isNull()) {
return text;
}
}
return QString::fromLocal8Bit(p_text);
}
QString Task::decodeText(const QByteArray &p_text, const QByteArray &p_name)
{
auto codec = QTextCodec::codecForName(p_name);
if (codec) {
QTextCodec::ConverterState state;
auto text = codec->toUnicode(p_text.data(), p_text.size(), &state);
if (state.invalidChars > 0) {
return QString();
}
return text;
}
return QString();
}
QString Task::getLocaleString(const QJsonValue &p_value, const QString &p_locale)
{
if (p_value.isObject()) {
auto obj = p_value.toObject();
if (obj.contains(p_locale)) {
return obj.value(p_locale).toString();
} else {
qWarning() << "value of locale not found" << p_locale;
if (!obj.isEmpty()){
return obj.begin().value().toString();
} else {
return QString();
}
}
} else {
return p_value.toString();
}
}
QStringList Task::getLocaleStringList(const QJsonValue &p_value, const QString &p_locale)
{
QStringList strs;
const auto arr = p_value.toArray();
for (int i = 0; i < arr.size(); ++i) {
strs << getLocaleString(arr[i], p_locale);
}
return strs;
}
QStringList Task::getStringList(const QJsonValue &p_value)
{
QStringList strs;
const auto arr = p_value.toArray();
for (int i = 0; i < arr.size(); ++i) {
strs << arr[i].toString();
}
return strs;
}
const TaskVariableMgr &Task::variableMgr() const
{
return m_taskMgr->getVariableMgr();
}
bool Task::isCancelled() const
{
return m_cancelled;
}
void Task::setCancelled(bool p_cancelled)
{
m_cancelled = p_cancelled;
}

197
src/task/task.h Normal file
View File

@ -0,0 +1,197 @@
#ifndef TASK_H
#define TASK_H
#include <QObject>
#include <QVector>
#include <QMap>
#include <QSharedPointer>
class QAction;
class QProcess;
class QJsonObject;
namespace tests
{
class TestTask;
}
namespace vnotex
{
struct ButtonDTO
{
QString text;
};
struct InputDTO
{
QString id;
QString type;
QString description;
QString default_;
bool password;
QStringList options;
};
struct MessageDTO
{
QString id;
QString type;
QString title;
QString text;
QString detailedText;
QVector<ButtonDTO> buttons;
};
struct ShellOptionsDTO
{
QString executable;
QStringList args;
};
struct TaskOptionsDTO
{
QString cwd;
QMap<QString, QString> env;
ShellOptionsDTO shell;
};
struct TaskDTO
{
QString version;
QString type;
QString command;
QStringList args;
QString label;
QString icon;
QString shortcut;
QVector<InputDTO> inputs;
QVector<MessageDTO> messages;
TaskOptionsDTO options;
QString _scope;
QString _source;
};
class TaskMgr;
class TaskVariableMgr;
class Task : public QObject
{
Q_OBJECT
public:
friend class tests::TestTask;
// For top level Task, use QSharedPointer instead of QObject to manage ownership.
static QSharedPointer<Task> fromFile(const QString &p_file, const QString &p_locale, TaskMgr *p_taskMgr);
void run();
const TaskDTO &getDTO() const;
const QString &getVersion() const;
const QString &getType() const;
QString getCommand();
QStringList getArgs();
const QString &getLabel() const;
const QString &getIcon() const;
const QString &getShortcut() const;
QString getOptionsCwd();
const QMap<QString, QString> &getOptionsEnv() const;
const QString &getOptionsShellExecutable() const;
QStringList getOptionsShellArgs();
const QVector<Task *> &getChildren() const;
const QVector<InputDTO> &getInputs() const;
const InputDTO *findInput(const QString &p_id) const;
const MessageDTO *findMessage(const QString &p_id) const;
const QString &getFile() const;
bool isCancelled() const;
void setCancelled(bool p_cancelled);
static QString s_latestVersion;
static QString getLocaleString(const QJsonValue &p_value,
const QString &p_locale);
static QStringList getLocaleStringList(const QJsonValue &p_value,
const QString &p_locale);
static QStringList getStringList(const QJsonValue &p_value);
static QString decodeText(const QByteArray &p_text);
signals:
void outputRequested(const QString &p_text) const;
private:
Task(const QString &p_locale,
const QString &p_file,
TaskMgr *p_taskMgr,
QObject *p_parent = nullptr);
// Must call start() or delete the returned QProcess.
QProcess *setupProcess();
const TaskVariableMgr &variableMgr() const;
static bool fromJson(Task *p_task, const QJsonObject &p_obj);
static bool fromJsonV0(Task *p_task, const QJsonObject &p_obj, bool p_mergeTasks = false);
static QString decodeText(const QByteArray &p_text, const QByteArray &p_name);
Task *m_parent = nullptr;
QVector<Task *> m_children;
TaskMgr *m_taskMgr = nullptr;
TaskDTO m_dto;
QString m_locale;
bool m_cancelled = false;
};
} // ns vnotex
#endif // TASK_H

13
src/task/task.pri Normal file
View File

@ -0,0 +1,13 @@
QT += widgets
SOURCES += \
$$PWD/task.cpp \
$$PWD/taskmgr.cpp \
$$PWD/taskvariablemgr.cpp \
$$PWD/shellexecution.cpp
HEADERS += \
$$PWD/task.h \
$$PWD/taskmgr.h \
$$PWD/taskvariablemgr.h \
$$PWD/shellexecution.h

111
src/task/taskmgr.cpp Normal file
View File

@ -0,0 +1,111 @@
#include "taskmgr.h"
#include <QDir>
#include <QDebug>
#include <QJsonDocument>
#include <core/configmgr.h>
#include <core/coreconfig.h>
#include <core/vnotex.h>
#include <core/notebookmgr.h>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
using namespace vnotex;
TaskMgr::TaskMgr(QObject *p_parent)
: QObject(p_parent),
m_variableMgr(this)
{
}
void TaskMgr::init()
{
m_variableMgr.init();
// Load all tasks and watch the location.
loadAllTasks();
connect(&VNoteX::getInst().getNotebookMgr(), &NotebookMgr::currentNotebookChanged,
this, [this]() {
loadNotebookTasks();
emit tasksUpdated();
});
}
void TaskMgr::reload()
{
loadAllTasks();
}
const QVector<QSharedPointer<Task>> &TaskMgr::getAppTasks() const
{
return m_appTasks;
}
const QVector<QSharedPointer<Task>> &TaskMgr::getUserTasks() const
{
return m_userTasks;
}
const QVector<QSharedPointer<Task>> &TaskMgr::getNotebookTasks() const
{
return m_notebookTasks;
}
QString TaskMgr::getNotebookTaskFolder()
{
return QString();
}
void TaskMgr::loadAllTasks()
{
loadGlobalTasks();
loadNotebookTasks();
emit tasksUpdated();
}
void TaskMgr::loadNotebookTasks()
{
loadTasksFromFolder(m_notebookTasks, getNotebookTaskFolder());
}
void TaskMgr::loadGlobalTasks()
{
loadTasksFromFolder(m_appTasks, ConfigMgr::getInst().getAppTaskFolder());
loadTasksFromFolder(m_userTasks, ConfigMgr::getInst().getUserTaskFolder());
}
void TaskMgr::loadTasksFromFolder(QVector<QSharedPointer<Task>> &p_tasks, const QString &p_folder)
{
p_tasks.clear();
if (p_folder.isEmpty()) {
return;
}
const auto taskFiles = FileUtils::entryListRecursively(p_folder, {"*.json"}, QDir::Files);
for (const auto &file : taskFiles) {
auto task = loadTask(file);
if (task) {
qDebug() << "loaded task" << task->getLabel();
connect(task.data(), &Task::outputRequested,
this, &TaskMgr::taskOutputRequested);
p_tasks.append(task);
}
}
}
QSharedPointer<Task> TaskMgr::loadTask(const QString &p_taskFile)
{
const auto localeStr = ConfigMgr::getInst().getCoreConfig().getLocaleToUse();
auto task = Task::fromFile(p_taskFile, localeStr, this);
return task;
}
const TaskVariableMgr &TaskMgr::getVariableMgr() const
{
return m_variableMgr;
}

63
src/task/taskmgr.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef TASKMGR_H
#define TASKMGR_H
#include <QObject>
#include <core/noncopyable.h>
#include <QVector>
#include <QSharedPointer>
#include "task.h"
#include "taskvariablemgr.h"
namespace vnotex
{
class TaskMgr : public QObject, private Noncopyable
{
Q_OBJECT
public:
explicit TaskMgr(QObject *p_parent = nullptr);
// It will be invoked after MainWindow show.
void init();
void reload();
const QVector<QSharedPointer<Task>> &getAppTasks() const;
const QVector<QSharedPointer<Task>> &getUserTasks() const;
const QVector<QSharedPointer<Task>> &getNotebookTasks() const;
static QString getNotebookTaskFolder();
const TaskVariableMgr &getVariableMgr() const;
signals:
void tasksUpdated();
void taskOutputRequested(const QString &p_text) const;
private:
void loadAllTasks();
void loadNotebookTasks();
void loadGlobalTasks();
void loadTasksFromFolder(QVector<QSharedPointer<Task>> &p_tasks, const QString &p_folder);
// Return nullptr if not a valid task.
QSharedPointer<Task> loadTask(const QString &p_taskFile);
QVector<QSharedPointer<Task>> m_appTasks;
QVector<QSharedPointer<Task>> m_userTasks;
QVector<QSharedPointer<Task>> m_notebookTasks;
TaskVariableMgr m_variableMgr;
};
} // ns vnotex
#endif // TASKMGR_H

View File

@ -0,0 +1,435 @@
#include "taskvariablemgr.h"
#include <QRegularExpression>
#include <QInputDialog>
#include <QApplication>
#include <QRandomGenerator>
#include <QTimeZone>
#include <QProcess>
#include <core/vnotex.h>
#include <core/notebookmgr.h>
#include <core/configmgr.h>
#include <core/mainconfig.h>
#include <core/exception.h>
#include <widgets/mainwindow.h>
#include <widgets/dialogs/selectdialog.h>
#include <notebook/notebook.h>
#include <notebook/node.h>
#include <utils/pathutils.h>
#include <buffer/buffer.h>
#include <widgets/viewwindow.h>
#include <widgets/viewarea.h>
#include <snippet/snippetmgr.h>
#include "task.h"
#include "taskmgr.h"
#include "shellexecution.h"
using namespace vnotex;
TaskVariable::TaskVariable(const QString &p_name, const Func &p_func)
: m_name(p_name),
m_func(p_func)
{
}
QString TaskVariable::evaluate(Task *p_task, const QString &p_value) const
{
return m_func(p_task, p_value);
}
const QString TaskVariableMgr::c_variableSymbolRegExp = QString(R"(\$\{([^${}:]+)(?::([^${}:]+))?\})");
TaskVariableMgr::TaskVariableMgr(TaskMgr *p_taskMgr)
: m_taskMgr(p_taskMgr)
{
}
void TaskVariableMgr::init()
{
initVariables();
}
void TaskVariableMgr::initVariables()
{
m_variables.clear();
m_needUpdateSystemEnvironment = true;
initNotebookVariables();
initBufferVariables();
initTaskVariables();
initMagicVariables();
initEnvironmentVariables();
initConfigVariables();
initInputVariables();
initShellVariables();
}
void TaskVariableMgr::initNotebookVariables()
{
addVariable("notebookFolder", [](Task *, const QString &) {
auto notebook = TaskVariableMgr::getCurrentNotebook();
if (notebook) {
return PathUtils::cleanPath(notebook->getRootFolderAbsolutePath());
} else {
return QString();
}
});
addVariable("notebookFolderName", [](Task *, const QString &) {
auto notebook = TaskVariableMgr::getCurrentNotebook();
if (notebook) {
return PathUtils::dirName(notebook->getRootFolderPath());
} else {
return QString();
}
});
addVariable("notebookName", [](Task *, const QString &) {
auto notebook = TaskVariableMgr::getCurrentNotebook();
if (notebook) {
return notebook->getName();
} else {
return QString();
}
});
addVariable("notebookDescription", [](Task *, const QString &) {
auto notebook = TaskVariableMgr::getCurrentNotebook();
if (notebook) {
return notebook->getDescription();
} else {
return QString();
}
});
}
void TaskVariableMgr::initBufferVariables()
{
addVariable("buffer", [](Task *, const QString &) {
auto buffer = getCurrentBuffer();
if (buffer) {
return PathUtils::cleanPath(buffer->getPath());
}
return QString();
});
addVariable("bufferNotebookFolder", [](Task *, const QString &) {
auto buffer = getCurrentBuffer();
if (buffer) {
auto node = buffer->getNode();
if (node) {
return PathUtils::cleanPath(node->getNotebook()->getRootFolderAbsolutePath());
}
}
return QString();
});
addVariable("bufferRelativePath", [](Task *, const QString &) {
auto buffer = getCurrentBuffer();
if (buffer) {
auto node = buffer->getNode();
if (node) {
return PathUtils::cleanPath(node->fetchPath());
} else {
return PathUtils::cleanPath(buffer->getPath());
}
}
return QString();
});
addVariable("bufferName", [](Task *, const QString &) {
auto buffer = getCurrentBuffer();
if (buffer) {
return PathUtils::fileName(buffer->getPath());
}
return QString();
});
addVariable("bufferBaseName", [](Task *, const QString &) {
auto buffer = getCurrentBuffer();
if (buffer) {
return QFileInfo(buffer->getPath()).completeBaseName();
}
return QString();
});
addVariable("bufferDir", [](Task *, const QString &) {
auto buffer = getCurrentBuffer();
if (buffer) {
return PathUtils::parentDirPath(buffer->getPath());
}
return QString();
});
addVariable("bufferExt", [](Task *, const QString &) {
auto buffer = getCurrentBuffer();
if (buffer) {
return QFileInfo(buffer->getPath()).suffix();
}
return QString();
});
addVariable("selectedText", [](Task *, const QString &) {
auto win = getCurrentViewWindow();
if (win) {
return win->selectedText();
}
return QString();
});
}
void TaskVariableMgr::initTaskVariables()
{
addVariable("cwd", [](Task *task, const QString &) {
return PathUtils::cleanPath(task->getOptionsCwd());
});
addVariable("taskFile", [](Task *task, const QString &) {
return PathUtils::cleanPath(task->getFile());
});
addVariable("taskDir", [](Task *task, const QString &) {
return PathUtils::parentDirPath(task->getFile());
});
addVariable("exeFile", [](Task *, const QString &) {
return PathUtils::cleanPath(qApp->applicationFilePath());
});
addVariable("pathSeparator", [](Task *, const QString &) {
return QDir::separator();
});
addVariable("notebookTaskFolder", [this](Task *, const QString &) {
return PathUtils::cleanPath(m_taskMgr->getNotebookTaskFolder());
});
addVariable("userTaskFolder", [](Task *, const QString &) {
return PathUtils::cleanPath(ConfigMgr::getInst().getUserTaskFolder());
});
addVariable("appTaskFolder", [](Task *, const QString &) {
return PathUtils::cleanPath(ConfigMgr::getInst().getAppTaskFolder());
});
addVariable("userThemeFolder", [](Task *, const QString &) {
return PathUtils::cleanPath(ConfigMgr::getInst().getUserThemeFolder());
});
addVariable("appThemeFolder", [](Task *, const QString &) {
return PathUtils::cleanPath(ConfigMgr::getInst().getAppThemeFolder());
});
addVariable("userDocsFolder", [](Task *, const QString &) {
return PathUtils::cleanPath(ConfigMgr::getInst().getUserDocsFolder());
});
addVariable("appDocsFolder", [](Task *, const QString &) {
return PathUtils::cleanPath(ConfigMgr::getInst().getAppDocsFolder());
});
}
void TaskVariableMgr::initMagicVariables()
{
addVariable("magic", [](Task *, const QString &val) {
if (val.isEmpty()) {
return QString();
}
auto overrides = SnippetMgr::generateOverrides(getCurrentBuffer());
return SnippetMgr::getInst().applySnippetBySymbol(SnippetMgr::generateSnippetSymbol(val), overrides);
});
}
void TaskVariableMgr::initEnvironmentVariables()
{
addVariable("env", [this](Task *, const QString &val) {
if (val.isEmpty()) {
return QString();
}
if (m_needUpdateSystemEnvironment) {
m_needUpdateSystemEnvironment = false;
m_systemEnv = QProcessEnvironment::systemEnvironment();
}
return m_systemEnv.value(val);
});
}
void TaskVariableMgr::initConfigVariables()
{
// ${config:main.core.shortcuts.FullScreen}.
addVariable("config", [](Task *, const QString &val) {
if (val.isEmpty()) {
return QString();
}
auto jsonVal = ConfigMgr::getInst().parseAndReadConfig(val);
switch (jsonVal.type()) {
case QJsonValue::Bool:
return jsonVal.toBool() ? QStringLiteral("1") : QStringLiteral("0");
break;
case QJsonValue::Double:
return QString::number(jsonVal.toDouble());
break;
case QJsonValue::String:
return jsonVal.toString();
break;
default:
return QString();
}
});
}
void TaskVariableMgr::initInputVariables()
{
// ${input:inputId}.
addVariable("input", [this](Task *task, const QString &val) {
if (val.isEmpty()) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("task (%1) with empty input id").arg(task->getLabel()));
}
auto input = task->findInput(val);
if (!input) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("task (%1) with invalid input id (%2)").arg(task->getLabel(), val));
}
if (input->type == "promptString") {
const auto desc = evaluate(task, input->description);
const auto defaultText = evaluate(task, input->default_);
QInputDialog dialog(VNoteX::getInst().getMainWindow());
dialog.setInputMode(QInputDialog::TextInput);
dialog.setTextEchoMode(input->password ? QLineEdit::Password : QLineEdit::Normal);
dialog.setWindowTitle(task->getLabel());
dialog.setLabelText(desc);
dialog.setTextValue(defaultText);
if (dialog.exec() == QDialog::Accepted) {
return dialog.textValue();
} else {
task->setCancelled(true);
return QString();
}
} else if (input->type == "pickString") {
const auto desc = evaluate(task, input->description);
SelectDialog dialog(task->getLabel(), desc, VNoteX::getInst().getMainWindow());
for (int i = 0; i < input->options.size(); i++) {
dialog.addSelection(input->options.at(i), i);
}
if (dialog.exec() == QDialog::Accepted) {
int selection = dialog.getSelection();
return input->options.at(selection);
} else {
task->setCancelled(true);
return QString();
}
} else {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("task (%1) with invalid input type (%2)(%3)").arg(task->getLabel(), input->id, input->type));
}
return QString();
});
}
void TaskVariableMgr::initShellVariables()
{
// ${shell:command}.
addVariable("shell", [this](Task *task, const QString &val) {
QProcess process;
process.setWorkingDirectory(task->getOptionsCwd());
ShellExecution::setupProcess(&process, val);
process.start();
const int timeout = 1000;
if (!process.waitForStarted(timeout) || !process.waitForFinished(timeout)) {
Exception::throwOne(Exception::Type::InvalidArgument,
QString("task (%1) failed to fetch shell variable (%2)").arg(task->getLabel(), val));
}
return Task::decodeText(process.readAllStandardOutput());
});
}
void TaskVariableMgr::addVariable(const QString &p_name, const TaskVariable::Func &p_func)
{
Q_ASSERT(!m_variables.contains(p_name));
m_variables.insert(p_name, TaskVariable(p_name, p_func));
}
const ViewWindow *TaskVariableMgr::getCurrentViewWindow()
{
return VNoteX::getInst().getMainWindow()->getViewArea()->getCurrentViewWindow();
}
Buffer *TaskVariableMgr::getCurrentBuffer()
{
auto win = getCurrentViewWindow();
if (win) {
return win->getBuffer();
}
return nullptr;
}
QSharedPointer<Notebook> TaskVariableMgr::getCurrentNotebook()
{
return VNoteX::getInst().getNotebookMgr().getCurrentNotebook();
}
QString TaskVariableMgr::evaluate(Task *p_task, const QString &p_text) const
{
QString content(p_text);
int maxTimesAtSamePos = 100;
QRegularExpression regExp(c_variableSymbolRegExp);
int pos = 0;
while (pos < content.size()) {
QRegularExpressionMatch match;
int idx = content.indexOf(regExp, pos, &match);
if (idx == -1) {
break;
}
const auto varName = match.captured(1).trimmed();
const auto varValue = match.captured(2).trimmed();
auto var = findVariable(varName);
if (!var) {
// Skip it.
pos = idx + match.capturedLength(0);
continue;
}
const auto afterText = var->evaluate(p_task, varValue);
content.replace(idx, match.capturedLength(0), afterText);
// @afterText may still contains variable symbol.
if (pos == idx) {
if (--maxTimesAtSamePos == 0) {
break;
}
} else {
maxTimesAtSamePos = 100;
}
pos = idx;
}
return content;
}
QStringList TaskVariableMgr::evaluate(Task *p_task, const QStringList &p_texts) const
{
QStringList strs;
for (const auto &str : p_texts) {
strs << evaluate(p_task, str);
}
return strs;
}
const TaskVariable *TaskVariableMgr::findVariable(const QString &p_name) const
{
auto it = m_variables.find(p_name);
if (it != m_variables.end()) {
return &(it.value());
}
return nullptr;
}
void TaskVariableMgr::overrideVariable(const QString &p_name, const TaskVariable::Func &p_func)
{
m_variables.insert(p_name, TaskVariable(p_name, p_func));
}

105
src/task/taskvariablemgr.h Normal file
View File

@ -0,0 +1,105 @@
#ifndef TASKVARIABLEMGR_H
#define TASKVARIABLEMGR_H
#include <core/noncopyable.h>
#include <functional>
#include <QHash>
#include <QString>
#include <QSharedPointer>
#include <QScopedPointer>
#include <QProcessEnvironment>
namespace vnotex
{
class Task;
class Notebook;
class Buffer;
class ViewWindow;
class TaskMgr;
class TaskVariable
{
public:
typedef std::function<QString(Task *, const QString &)> Func;
TaskVariable(const QString &p_name, const Func &p_func);
QString evaluate(Task *p_task, const QString &p_value) const;
private:
QString m_name;
Func m_func;
};
class TaskVariableMgr : private Noncopyable
{
public:
explicit TaskVariableMgr(TaskMgr *p_taskMgr);
void init();
QString evaluate(Task *p_task, const QString &p_text) const;
QStringList evaluate(Task *p_task, const QStringList &p_texts) const;
// Used for UT.
void overrideVariable(const QString &p_name, const TaskVariable::Func &p_func);
static Buffer *getCurrentBuffer();
static QSharedPointer<Notebook> getCurrentNotebook();
private:
void initVariables();
void initNotebookVariables();
void initBufferVariables();
void initTaskVariables();
void initMagicVariables();
void initEnvironmentVariables();
void initConfigVariables();
void initInputVariables();
void initShellVariables();
void addVariable(const QString &p_name, const TaskVariable::Func &p_func);
const TaskVariable *findVariable(const QString &p_name) const;
/*
QString evaluateInputVariables(const QString &p_text,
const Task *p_task) const;
QString evaluateShellVariables(const QString &p_text,
const Task *p_task) const;
*/
static const ViewWindow *getCurrentViewWindow();
TaskMgr *m_taskMgr = nullptr;
QHash<QString, TaskVariable> m_variables;
bool m_needUpdateSystemEnvironment = true;
QProcessEnvironment m_systemEnv;
// %{name[:value]}%.
// Captured texts:
// 1 - The name of the variable (trim needed).
// 2 - The value option of the variable if available (trim needed).
static const QString c_variableSymbolRegExp;
};
} // ns vnotex
#endif // TASKVARIABLEMGR_H

View File

@ -362,17 +362,23 @@ QStringList FileUtils::entryListRecursively(const QString &p_dirPath,
const QStringList &p_nameFilters,
QDir::Filters p_filters)
{
QStringList entries;
QDir dir(p_dirPath);
if (dir.isEmpty()) return {};
QStringList entrys;
const auto curEntrys = dir.entryList(p_nameFilters, p_filters | QDir::NoDotAndDotDot);
for (const auto &e : curEntrys) {
entrys.append(PathUtils::concatenateFilePath(p_dirPath, e));
if (!dir.exists()) {
return entries;
}
auto subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
const auto curEntries = dir.entryList(p_nameFilters, p_filters | QDir::NoDotAndDotDot);
for (const auto &e : curEntries) {
entries.append(PathUtils::concatenateFilePath(p_dirPath, e));
}
const auto subdirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
for (const auto &subdir : subdirs) {
const auto dirPath = PathUtils::concatenateFilePath(p_dirPath, subdir);
entrys.append(entryListRecursively(dirPath, p_nameFilters, p_filters));
entries.append(entryListRecursively(dirPath, p_nameFilters, p_filters));
}
return entrys;
return entries;
}

View File

@ -83,7 +83,7 @@ namespace vnotex
// @p_nameFilters is for each dir, not for all.
static QStringList entryListRecursively(const QString &p_dirPath,
const QStringList &p_nameFilters,
QDir::Filters p_filters=QDir::NoFilter);
QDir::Filters p_filters = QDir::NoFilter);
};
} // ns vnotex

View File

@ -16,7 +16,6 @@ QString PathUtils::parentDirPath(const QString &p_path)
}
QFileInfo info(p_path);
Q_ASSERT(info.isAbsolute());
return cleanPath(info.absolutePath());
}

View File

@ -4,13 +4,15 @@
#include <QDir>
#include <QKeySequence>
#include <QWidget>
#include <QVariant>
#include <QFontDatabase>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QSvgRenderer>
#include <QPainter>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QDebug>
#include <cmath>
@ -139,3 +141,54 @@ QJsonObject Utils::fromJsonString(const QByteArray &p_data)
{
return QJsonDocument::fromJson(p_data).object();
}
QJsonValue Utils::parseAndReadJson(const QJsonObject &p_obj, const QString &p_exp)
{
// abc[0] or abc.
QRegularExpression regExp(R"(^([^\[\]\s]+)(?:\[(\d+)\])?$)");
QJsonValue val(p_obj);
bool valid = true;
const auto tokens = p_exp.split(QLatin1Char('.'));
for (int i = 0; i < tokens.size(); ++i) {
const auto &token = tokens[i];
if (token.isEmpty()) {
continue;
}
auto match = regExp.match(token);
if (!match.hasMatch()) {
valid = false;
break;
}
const auto key = match.captured(1);
const auto obj = val.toObject();
if (obj.contains(key)) {
val = obj.value(key);
} else {
valid = false;
break;
}
if (!match.captured(2).isEmpty()) {
// Array.
const auto arr = val.toArray();
int idx = match.captured(2).toInt();
if (idx < 0 || idx >= arr.size()) {
valid = false;
break;
}
val = arr[idx];
}
}
if (!valid) {
qWarning() << "invalid expression to parse for JSON" << p_exp;
return QJsonValue();
}
return val;
}

View File

@ -57,6 +57,10 @@ namespace vnotex
static QByteArray toJsonString(const QJsonObject &p_obj);
static QJsonObject fromJsonString(const QByteArray &p_data);
// Parse @p_exp into tokens and read the target value from @p_obj.
// Format: obj1.obj2.arr[2].obj3.
static QJsonValue parseAndReadJson(const QJsonObject &p_obj, const QString &p_exp);
};
} // ns vnotex

View File

@ -13,13 +13,8 @@ using namespace vnotex;
const QChar SelectDialog::c_cancelShortcut = QLatin1Char('z');
SelectDialog::SelectDialog(const QString &p_title, QWidget *p_parent)
: QDialog(p_parent)
: SelectDialog(p_title, QString(), p_parent)
{
const auto &themeMgr = VNoteX::getInst().getThemeMgr();
m_shortcutIconForeground = themeMgr.paletteColor(QStringLiteral("widgets#quickselector#item_icon#fg"));
m_shortcutIconBorder = themeMgr.paletteColor(QStringLiteral("widgets#quickselector#item_icon#border"));
setupUI(p_title);
}
SelectDialog::SelectDialog(const QString &p_title,

View File

@ -25,6 +25,7 @@
#include "snippetpanel.h"
#include "historypanel.h"
#include "tagexplorer.h"
#include "terminalviewer.h"
using namespace vnotex;
@ -113,7 +114,7 @@ void DockWidgetHelper::setupDocks()
setupOutlineDock();
setupOutputDock();
setupTerminalDock();
setupLocationListDock();
@ -148,17 +149,17 @@ void DockWidgetHelper::setupOutlineDock()
m_mainWindow->addDockWidget(Qt::RightDockWidgetArea, dock);
}
void DockWidgetHelper::setupOutputDock()
void DockWidgetHelper::setupTerminalDock()
{
auto dock = createDockWidget(DockIndex::OutputDock, tr("Output"), m_mainWindow);
auto dock = createDockWidget(DockIndex::TerminalDock, tr("Terminal"), m_mainWindow);
dock->setObjectName(QStringLiteral("OutputDock.vnotex"));
dock->setAllowedAreas(Qt::BottomDockWidgetArea);
dock->setObjectName(QStringLiteral("TerminalDock.vnotex"));
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
dock->setWidget(m_mainWindow->m_outputViewer);
dock->setFocusProxy(m_mainWindow->m_outputViewer);
dock->hide();
dock->setWidget(m_mainWindow->m_terminalViewer);
dock->setFocusProxy(m_mainWindow->m_terminalViewer);
m_mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dock);
dock->hide();
}
void DockWidgetHelper::setupSearchDock()

View File

@ -29,7 +29,7 @@ namespace vnotex
SearchDock,
SnippetDock,
OutlineDock,
OutputDock,
TerminalDock,
LocationListDock,
MaxDock
};
@ -98,7 +98,7 @@ namespace vnotex
void setupOutlineDock();
void setupOutputDock();
void setupTerminalDock();
void setupSearchDock();

View File

@ -230,10 +230,4 @@ void FramelessMainWindowLinux::showEvent(QShowEvent *p_event)
}
}
#else
using namespace vnotex;
FramelessMainWindowLinuxDummy::FramelessMainWindowLinuxDummy()
{
}
#endif

View File

@ -65,7 +65,7 @@ namespace vnotex
class FramelessMainWindowLinuxDummy
{
public:
FramelessMainWindowLinuxDummy();
FramelessMainWindowLinuxDummy() = default;
};
#endif
}

View File

@ -221,10 +221,4 @@ void FramelessMainWindowWin::forceRedraw()
}
}
#else
using namespace vnotex;
FramelessMainWindowWinDummy::FramelessMainWindowWinDummy()
{
}
#endif

View File

@ -41,7 +41,7 @@ namespace vnotex
class FramelessMainWindowWinDummy
{
public:
FramelessMainWindowWinDummy();
FramelessMainWindowWinDummy() = default;
};
#endif
}

View File

@ -56,6 +56,7 @@
#include "tagexplorer.h"
#include "toolbarhelper.h"
#include "statusbarhelper.h"
#include "terminalviewer.h"
using namespace vnotex;
@ -257,7 +258,7 @@ void MainWindow::setupDocks()
setupOutlineViewer();
setupOutputViewer();
setupTerminalViewer();
setupHistoryPanel();
@ -514,23 +515,15 @@ void MainWindow::setupOutlineViewer()
this, &MainWindow::focusViewArea);
}
void MainWindow::setupOutputViewer()
void MainWindow::setupTerminalViewer()
{
m_outputViewer = new QTextEdit(this);
m_outputViewer->setObjectName("OutputViewer.vnotex");
m_outputViewer->setReadOnly(true);
m_terminalViewer = new TerminalViewer(this);
m_terminalViewer->setObjectName("TerminalViewer.vnotex");
connect(&VNoteX::getInst(), &VNoteX::showOutputRequested,
m_outputViewer, [this](const QString &p_text) {
auto cursor = m_outputViewer->textCursor();
cursor.movePosition(QTextCursor::End);
m_outputViewer->setTextCursor(cursor);
m_outputViewer->insertPlainText(p_text);
auto scrollBar = m_outputViewer->verticalScrollBar();
if (scrollBar) {
scrollBar->setSliderPosition(scrollBar->maximum());
}
m_dockWidgetHelper.getDock(DockWidgetHelper::OutputDock)->show();
this, [this](const QString &p_text) {
m_terminalViewer->append(p_text);
m_dockWidgetHelper.activateDock(DockWidgetHelper::TerminalDock);
});
}

View File

@ -28,6 +28,7 @@ namespace vnotex
class SnippetPanel;
class HistoryPanel;
class ExportDialog;
class TerminalViewer;
enum { RESTART_EXIT_CODE = 1000 };
@ -106,7 +107,7 @@ namespace vnotex
void setupOutlineViewer();
void setupOutputViewer();
void setupTerminalViewer();
void setupSearchPanel();
@ -167,7 +168,7 @@ namespace vnotex
OutlineViewer *m_outlineViewer = nullptr;
QTextEdit *m_outputViewer = nullptr;
TerminalViewer *m_terminalViewer = nullptr;
LocationList *m_locationList = nullptr;

View File

@ -838,7 +838,7 @@ QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
break;
case Action::OpenLocation:
act = new QAction(tr("Open &Location"), p_parent);
act = new QAction(tr("Open Locat&ion"), p_parent);
connect(act, &QAction::triggered,
this, [this]() {
auto item = m_masterExplorer->currentItem();

View File

@ -0,0 +1,62 @@
#include "terminalviewer.h"
#include <QVBoxLayout>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QToolButton>
#include <utils/widgetutils.h>
#include "widgetsfactory.h"
#include "titlebar.h"
using namespace vnotex;
TerminalViewer::TerminalViewer(QWidget *p_parent)
: QFrame(p_parent)
{
setupUI();
}
void TerminalViewer::setupUI()
{
auto mainLayout = new QVBoxLayout(this);
WidgetUtils::setContentsMargins(mainLayout);
{
setupTitleBar(QString(), this);
mainLayout->addWidget(m_titleBar);
}
m_consoleEdit = new QPlainTextEdit(this);
m_consoleEdit->setReadOnly(true);
mainLayout->addWidget(m_consoleEdit);
setFocusProxy(m_consoleEdit);
}
void TerminalViewer::setupTitleBar(const QString &p_title, QWidget *p_parent)
{
m_titleBar = new TitleBar(p_title, true, TitleBar::Action::None, p_parent);
m_titleBar->setActionButtonsAlwaysShown(true);
{
auto clearBtn = m_titleBar->addActionButton(QStringLiteral("clear.svg"), tr("Clear"));
connect(clearBtn, &QToolButton::triggered,
this, &TerminalViewer::clear);
}
}
void TerminalViewer::append(const QString &p_text)
{
m_consoleEdit->appendPlainText(p_text);
auto scrollBar = m_consoleEdit->verticalScrollBar();
if (scrollBar) {
scrollBar->setValue(scrollBar->maximum());
}
}
void TerminalViewer::clear()
{
m_consoleEdit->clear();
}

View File

@ -0,0 +1,33 @@
#ifndef TERMINALVIEWER_H
#define TERMINALVIEWER_H
#include <QFrame>
class QPlainTextEdit;
namespace vnotex
{
class TitleBar;
class TerminalViewer : public QFrame
{
Q_OBJECT
public:
explicit TerminalViewer(QWidget *p_parent = nullptr);
void append(const QString &p_text);
void clear();
private:
void setupUI();
void setupTitleBar(const QString &p_title, QWidget *p_parent = nullptr);
TitleBar *m_titleBar = nullptr;
QPlainTextEdit *m_consoleEdit = nullptr;
};
}
#endif // TERMINALVIEWER_H

View File

@ -27,6 +27,7 @@
#include <core/fileopenparameters.h>
#include <core/htmltemplatehelper.h>
#include <core/exception.h>
#include <task/taskmgr.h>
#include "propertydefs.h"
#include "dialogs/settings/settingsdialog.h"
#include "dialogs/updater.h"
@ -286,52 +287,80 @@ QToolBar *ToolBarHelper::setupQuickAccessToolBar(MainWindow *p_win, QToolBar *p_
void ToolBarHelper::setupTaskMenu(QMenu *p_menu)
{
p_menu->clear();
setupTaskActionMenu(p_menu);
p_menu->addSeparator();
const auto &taskMgr = VNoteX::getInst().getTaskMgr();
for (auto task : taskMgr.getAppTasks()) {
addTaskMenu(p_menu, task);
for (const auto &task : taskMgr.getAppTasks()) {
addTaskMenu(p_menu, task.data());
}
p_menu->addSeparator();
for (auto task : taskMgr.getUserTasks()) {
addTaskMenu(p_menu, task);
for (const auto &task : taskMgr.getUserTasks()) {
addTaskMenu(p_menu, task.data());
}
p_menu->addSeparator();
for (auto task : taskMgr.getNotebookTasks()) {
addTaskMenu(p_menu, task);
for (const auto &task : taskMgr.getNotebookTasks()) {
addTaskMenu(p_menu, task.data());
}
}
void ToolBarHelper::setupTaskActionMenu(QMenu *p_menu)
{
p_menu->addAction(MainWindow::tr("Add Task"),
p_menu,
[]() {
WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(ConfigMgr::getInst().getUserTaskFolder()));
});
p_menu->addAction(MainWindow::tr("Reload"),
p_menu,
[]() {
VNoteX::getInst().getTaskMgr().reload();
});
}
void ToolBarHelper::addTaskMenu(QMenu *p_menu, Task *p_task)
{
MainWindow::connect(p_task, &Task::showOutput,
&VNoteX::getInst(), &VNoteX::showOutputRequested);
QAction *action = nullptr;
const auto &tasks = p_task->getTasks();
const auto &children = p_task->getChildren();
auto label = p_task->getLabel();
label = label.replace("&", "&&");
// '&' will be considered shortuct symbol in QAction.
label.replace("&", "&&");
if (children.isEmpty()) {
action = p_menu->addAction(label);
} else {
auto subMenu = p_menu->addMenu(label);
for (auto task : children) {
addTaskMenu(subMenu, task);
}
action = subMenu->menuAction();
}
QIcon icon;
try {
auto taskIcon = p_task->getIcon();
if (!taskIcon.isEmpty()) {
icon = generateIcon(p_task->getIcon());
}
} catch (Exception e) {
} catch (Exception &e) {
if (e.m_type != Exception::Type::FailToReadFile) {
throw;
throw e;
}
}
if (tasks.isEmpty()) {
action = p_menu->addAction(label);
} else {
auto menu = p_menu->addMenu(label);
for (auto task : tasks) {
addTaskMenu(menu, task);
}
action = menu->menuAction();
}
action->setIcon(icon);
action->setData(reinterpret_cast<qulonglong>(p_task));
WidgetUtils::addActionShortcut(action, p_task->getShortcut());
MainWindow::connect(action, &QAction::triggered,
p_task, &Task::run);
}
QToolBar *ToolBarHelper::setupTaskToolBar(MainWindow *p_win, QToolBar *p_toolBar)
@ -347,11 +376,19 @@ QToolBar *ToolBarHelper::setupTaskToolBar(MainWindow *p_win, QToolBar *p_toolBar
btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
auto taskMenu = WidgetsFactory::createMenu(tb);
MainWindow::connect(&VNoteX::getInst().getTaskMgr(), &TaskMgr::taskChanged,
setupTaskActionMenu(taskMenu);
btn->setMenu(taskMenu);
MainWindow::connect(taskMenu, &QMenu::triggered,
taskMenu, [](QAction *act) {
auto task = reinterpret_cast<Task *>(act->data().toULongLong());
Q_ASSERT(task);
task->run();
});
MainWindow::connect(&VNoteX::getInst().getTaskMgr(), &TaskMgr::tasksUpdated,
taskMenu, [taskMenu]() {
setupTaskMenu(taskMenu);
});
btn->setMenu(taskMenu);
return tb;
}
@ -685,6 +722,12 @@ void ToolBarHelper::setupMenuButton(MainWindow *p_win, QToolBar *p_toolBar)
WidgetUtils::openUrlByDesktop(QUrl("https://vnotex.github.io/vnote"));
});
helpMenu->addAction(MainWindow::tr("Documentation"),
helpMenu,
[]() {
WidgetUtils::openUrlByDesktop(QUrl("https://vnotex.github.io/vnote/en_us/#!docs/vx.json"));
});
helpMenu->addAction(MainWindow::tr("Feedback and Discussions"),
helpMenu,
[]() {

View File

@ -36,6 +36,8 @@ namespace vnotex
static void setupTaskMenu(QMenu *p_menu);
static void setupTaskActionMenu(QMenu *p_menu);
static void addTaskMenu(QMenu *p_menu, Task *p_task);
static QToolBar *setupTaskToolBar(MainWindow *p_win, QToolBar *p_toolBar);

View File

@ -97,6 +97,8 @@ namespace vnotex
bool isSessionEnabled() const;
virtual QString selectedText() const;
public slots:
virtual void handleEditorConfigChange() = 0;
@ -226,8 +228,6 @@ namespace vnotex
virtual void zoom(bool p_zoomIn) = 0;
virtual QString selectedText() const;
void showZoomFactor(qreal p_factor);
void showZoomDelta(int p_delta);

View File

@ -89,6 +89,7 @@ SOURCES += \
$$PWD/tagexplorer.cpp \
$$PWD/tagpopup.cpp \
$$PWD/tagviewer.cpp \
$$PWD/terminalviewer.cpp \
$$PWD/textviewwindow.cpp \
$$PWD/toolbarhelper.cpp \
$$PWD/treeview.cpp \
@ -218,6 +219,7 @@ HEADERS += \
$$PWD/tagexplorer.h \
$$PWD/tagpopup.h \
$$PWD/tagviewer.h \
$$PWD/terminalviewer.h \
$$PWD/textviewwindow.h \
$$PWD/textviewwindowhelper.h \
$$PWD/toolbarhelper.h \

29
tests/commonfull.pri Normal file
View File

@ -0,0 +1,29 @@
include($$PWD/common.pri)
QT += sql
SRC_FOLDER = $$PWD/../src
LIBS_FOLDER = $$PWD/../libs
INCLUDEPATH *= $$SRC_FOLDER
include($$LIBS_FOLDER/vtextedit/src/editor/editor_export.pri)
include($$LIBS_FOLDER/vtextedit/src/libs/syntax-highlighting/syntax-highlighting_export.pri)
include($$SRC_FOLDER/utils/utils.pri)
include($$SRC_FOLDER/export/export.pri)
include($$SRC_FOLDER/search/search.pri)
include($$SRC_FOLDER/snippet/snippet.pri)
include($$SRC_FOLDER/imagehost/imagehost.pri)
include($$SRC_FOLDER/task/task.pri)
include($$SRC_FOLDER/core/core.pri)
include($$SRC_FOLDER/widgets/widgets.pri)

View File

@ -1,29 +1,8 @@
include($$PWD/../../common.pri)
QT += sql
include($$PWD/../../commonfull.pri)
TARGET = test_notebook
TEMPLATE = app
SRC_FOLDER = $$PWD/../../../src
CORE_FOLDER = $$SRC_FOLDER/core
INCLUDEPATH *= $$SRC_FOLDER
LIBS_FOLDER = $$PWD/../../../libs
include($$LIBS_FOLDER/vtextedit/src/editor/editor_export.pri)
include($$LIBS_FOLDER/vtextedit/src/libs/syntax-highlighting/syntax-highlighting_export.pri)
include($$CORE_FOLDER/core.pri)
include($$SRC_FOLDER/widgets/widgets.pri)
include($$SRC_FOLDER/utils/utils.pri)
include($$SRC_FOLDER/export/export.pri)
include($$SRC_FOLDER/search/search.pri)
include($$SRC_FOLDER/snippet/snippet.pri)
include($$SRC_FOLDER/imagehost/imagehost.pri)
SOURCES += \
dummynode.cpp \
dummynotebook.cpp \

View File

@ -0,0 +1,77 @@
#include "test_task.h"
#include <QDebug>
#include <QProcessEnvironment>
#include <task/taskvariablemgr.h>
#include <task/task.h>
#include <core/configmgr.h>
#include <core/coreconfig.h>
#include <core/sessionconfig.h>
using namespace tests;
using namespace vnotex;
TestTask::TestTask(QObject *p_parent)
: QObject(p_parent)
{
}
void TestTask::initTestCase()
{
ConfigMgr::initForUnitTest();
}
void TestTask::TestTaskVariableMgr()
{
TaskVariableMgr mgr(nullptr);
mgr.init();
mgr.overrideVariable("notebookFolder", [](const Task *, const QString &val) {
Q_ASSERT(val.isEmpty());
return "/home/vnotex/vnote";
});
mgr.overrideVariable("notebookFolderName", [](const Task *, const QString &val) {
Q_ASSERT(val.isEmpty());
return "vnote";
});
mgr.overrideVariable("magic", [](const Task *, const QString &val) {
if (val.isEmpty()) {
return QString();
} else {
return val;
}
});
auto task = createTask();
auto result = mgr.evaluate(task.data(), "start ${notebookFolder} end");
QCOMPARE(result, "start /home/vnotex/vnote end");
result = mgr.evaluate(task.data(), "start ${notebookFolder} mid ${notebookFolderName} end");
QCOMPARE(result, "start /home/vnotex/vnote mid vnote end");
result = mgr.evaluate(task.data(), "${magic:yyyy} ${magic:MM} ${magic:dd}");
QCOMPARE("yyyy MM dd", result);
{
const auto env = QProcessEnvironment::systemEnvironment();
result = mgr.evaluate(task.data(), "${env:PATH} ${env:QT_PATH} ${env:nonexist}");
QCOMPARE(result, QString("%1 %2 %3").arg(env.value("PATH"), env.value("QT_PATH"), env.value("nonexist")));
}
result = mgr.evaluate(task.data(), "${config:main.core.toolbar_icon_size} ${config:main.core.nonexists} ${config:session.core.system_title_bar}");
QCOMPARE(result, QString("%1 %2").arg(ConfigMgr::getInst().getCoreConfig().getToolBarIconSize())
.arg(ConfigMgr::getInst().getSessionConfig().getSystemTitleBarEnabled()));
}
QSharedPointer<vnotex::Task> TestTask::createTask() const
{
return QSharedPointer<Task>(new Task("en_US", "dummy_file", nullptr, nullptr));
}
QTEST_MAIN(tests::TestTask)

View File

@ -0,0 +1,31 @@
#ifndef TESTS_TASK_TEST_TASK_H
#define TESTS_TASK_TEST_TASK_H
#include <QtTest>
#include <QSharedPointer>
namespace vnotex
{
class Task;
}
namespace tests
{
class TestTask : public QObject
{
Q_OBJECT
public:
explicit TestTask(QObject *p_parent = nullptr);
private slots:
void initTestCase();
// Define test cases here per slot.
void TestTaskVariableMgr();
private:
QSharedPointer<vnotex::Task> createTask() const;
};
} // ns tests
#endif // TESTS_UTILS_TEST_UTILS_H

View File

@ -0,0 +1,10 @@
include($$PWD/../commonfull.pri)
TARGET = test_task
TEMPLATE = app
SOURCES += \
test_task.cpp
HEADERS += \
test_task.h

View File

@ -2,7 +2,9 @@
#include <QDebug>
#include <QTemporaryDir>
#include <QJsonArray>
#include <utils/utils.h>
#include <utils/pathutils.h>
#include <utils/fileutils.h>
@ -10,6 +12,68 @@ using namespace tests;
using namespace vnotex;
TestUtils::TestUtils(QObject *p_parent)
: QObject(p_parent)
{
}
void TestUtils::initTestCase()
{
m_obj["a"] = "a";
{
QJsonObject objb;
objb["a"] = "ba";
objb["b"] = 2;
{
QJsonObject objbc;
objbc["a"] = "bca";
objb["c"] = objbc;
}
m_obj["b"] = objb;
}
{
QJsonObject objc;
objc["a"] = "ca";
QJsonArray arr;
arr.append("cb0");
arr.append("cb1");
arr.append("cb2");
objc["b"] = arr;
m_obj["c"] = objc;
}
}
void TestUtils::testParseAndReadJson_data()
{
QTest::addColumn<QString>("exp");
QTest::addColumn<QJsonValue>("result");
QTest::newRow("empty") << "" << QJsonValue(m_obj);
QTest::newRow("a") << "a" << QJsonValue("a");
QTest::newRow("ba") << "b.a" << QJsonValue("ba");
QTest::newRow("bb") << "b.b" << QJsonValue(2);
QTest::newRow("bca") << "b.c.a" << QJsonValue("bca");
QTest::newRow("ca") << "c.a" << QJsonValue("ca");
QTest::newRow("cb0") << "c.b[0]" << QJsonValue("cb0");
QTest::newRow("cb1") << "c.b[1]" << QJsonValue("cb1");
QTest::newRow("cb2") << "c.b[2]" << QJsonValue("cb2");
}
void TestUtils::testParseAndReadJson()
{
QFETCH(QString, exp);
QFETCH(QJsonValue, result);
QCOMPARE(Utils::parseAndReadJson(m_obj, exp), result);
}
void TestUtils::testParentDirPath_data()
{
QTest::addColumn<QString>("path");

View File

@ -2,16 +2,25 @@
#define TESTS_UTILS_TEST_UTILS_H
#include <QtTest>
#include <QJsonObject>
namespace tests
{
class TestUtils : public QObject
{
Q_OBJECT
public:
explicit TestUtils(QObject *p_parent = nullptr);
private slots:
void initTestCase();
// Define test cases here per slot.
// Utils tests.
void testParseAndReadJson_data();
void testParseAndReadJson();
// PathUtils Tests.
void testParentDirPath_data();
void testParentDirPath();
@ -32,6 +41,9 @@ namespace tests
void testRenameFile();
void testIsText();
private:
QJsonObject m_obj;
};
} // ns tests

View File

@ -10,10 +10,12 @@ INCLUDEPATH *= $$SRC_FOLDER
SOURCES += \
test_utils.cpp \
$$UTILS_FOLDER/utils.cpp \
$$UTILS_FOLDER/pathutils.cpp \
$$UTILS_FOLDER/fileutils.cpp
HEADERS += \
test_utils.h \
$$UTILS_FOLDER/utils.h \
$$UTILS_FOLDER/pathutils.h \
$$UTILS_FOLDER/fileutils.h

View File

@ -2,4 +2,5 @@ TEMPLATE = subdirs
SUBDIRS = \
test_utils \
test_core
test_core \
test_task