From c8015878e1375f83574a8497faadd7e8c6aa8859 Mon Sep 17 00:00:00 2001 From: Jean-Marc Lasgouttes Date: Fri, 28 Oct 2022 17:21:33 +0200 Subject: [PATCH] WIP: refactor Systemcall **WARNING; only compilation has been ested; even that does not work** The goal of this commit is to use the list-based API to QProcess::start, to avoid annoying syntax issues (see issues with pasting from LaTeX). * Create a new latexEnvironment() in filetools.h that returns a map containing the variables and their values. * Rewrite parsecmd() so that it returns a QStringList of tokenized parameters. * Use this in startProcess. This is the part is is not finished yet. Obviously, this will not be possible to get right for 2.4.0. --- src/support/Systemcall.cpp | 112 +++++++++++++++----------------- src/support/SystemcallPrivate.h | 2 +- src/support/filetools.cpp | 60 +++++++++-------- src/support/filetools.h | 14 +++- 4 files changed, 99 insertions(+), 89 deletions(-) diff --git a/src/support/Systemcall.cpp b/src/support/Systemcall.cpp index d339e11b80..5262da7a26 100644 --- a/src/support/Systemcall.cpp +++ b/src/support/Systemcall.cpp @@ -13,13 +13,14 @@ #include +#include "support/Systemcall.h" +#include "support/SystemcallPrivate.h" + #include "support/debug.h" #include "support/filetools.h" #include "support/gettext.h" #include "support/lstrings.h" #include "support/qstring_helpers.h" -#include "support/Systemcall.h" -#include "support/SystemcallPrivate.h" #include "support/os.h" #include "support/ProgressInterface.h" @@ -163,19 +164,20 @@ namespace { * "\a" -> "\a" * "a\"b" -> "a"""b" */ -string const parsecmd(string const & incmd, string & infile, string & outfile, - string & errfile) +QStringList const parsecmd(string const & incmd, string & infile, string & outfile, + string & errfile) { bool in_single_quote = false; bool in_double_quote = false; bool escaped = false; string const python_call = os::python(); + QStringList arguments; vector outcmd(4); size_t start = 0; - if (prefixIs(incmd, python_call)) { - outcmd[0] = os::python(); - start = python_call.length(); + if (prefixIs(incmd, python_call + ' ')) { + arguments << QString::fromLocal8Bit(trim(os::python()).c_str()); + start = python_call.length() + 1; } for (size_t i = start, o = 0; i < incmd.length(); ++i) { @@ -194,30 +196,12 @@ string const parsecmd(string const & incmd, string & infile, string & outfile, outcmd[o] += c; continue; } - if (c == '"') { - if (escaped) { - // Don't triple double-quotes for redirection - // files as these won't be parsed by QProcess - outcmd[o] += string(o ? "\"" : "\"\"\""); - escaped = false; - } else { - outcmd[o] += c; - in_double_quote = !in_double_quote; - } - } else if (c == '\\' && !escaped) { - escaped = true; - } else if (c == '>' && !(in_double_quote || escaped)) { - if (suffixIs(outcmd[o], " 2")) { - outcmd[o] = rtrim(outcmd[o], "2"); - o = 2; - } else { - if (suffixIs(outcmd[o], " 1")) - outcmd[o] = rtrim(outcmd[o], "1"); - o = 1; - } - } else if (c == '<' && !(in_double_quote || escaped)) { - o = 3; + if (c == ' ' && !(in_double_quote || escaped)) { + if (o == 0) + arguments << QString::fromLocal8Bit(trim(outcmd[o]).c_str()); + o = 0; #if defined (USE_MACOSX_PACKAGING) + // FIXME!!!! } else if (o == 0 && i > 4 && c == ' ' && !(in_double_quote || escaped)) { // if a macOS app is detected with an additional argument // use open command as prefix to get it work @@ -232,6 +216,28 @@ string const parsecmd(string const & incmd, string & infile, string & outfile, } outcmd[o] += c; #endif + } else if (c == '"') { + if (escaped) { + outcmd[o] += c; + escaped = false; + } else + in_double_quote = !in_double_quote; + } else if (c == '\\' && !escaped) { + escaped = true; + } else if (c == '>' && o == 0 && !(in_double_quote || escaped)) { + if (outcmd[o] == "2") { + outcmd[o].clear(); + o = 2; + outcmd[o].clear(); + } else if (outcmd[o] == "1" || outcmd[o].empty()) { + outcmd[o].clear(); + o = 1; + outcmd[o].clear(); + } + } else if (c == '<' && o == 0 && outcmd[o].empty() && !(in_double_quote || escaped)) { + outcmd[o].clear(); + o = 3; + outcmd[o].clear(); } else { if (escaped && in_double_quote) outcmd[o] += '\\'; @@ -242,7 +248,7 @@ string const parsecmd(string const & incmd, string & infile, string & outfile, infile = trim(outcmd[3], " \""); outfile = trim(outcmd[1], " \""); errfile = trim(outcmd[2], " \""); - return trim(outcmd[0]); + return arguments; } } // namespace @@ -267,8 +273,7 @@ int Systemcall::startscript(Starttype how, string const & what, string infile; string outfile; string errfile; - QString const cmd = QString::fromLocal8Bit( - parsecmd(what_ss, infile, outfile, errfile).c_str()); + QStringList const cmd = parsecmd(what_ss, infile, outfile, errfile); SystemcallPrivate d(infile, outfile, errfile); bool do_events = process_events || how == WaitLoop; @@ -379,27 +384,21 @@ SystemcallPrivate::SystemcallPrivate(std::string const & sf, std::string const & } -void SystemcallPrivate::startProcess(QString const & cmd, string const & path, +void SystemcallPrivate::startProcess(string const & cmd, string const & path, string const & lpath, bool detached) { - cmd_ = cmd; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - // FIXME pass command and arguments separated in the first place - /* The versions of startDetached() and start() that accept a - * QStringList object exist since Qt4, but it is only in Qt 5.15 - * that splitCommand() was introduced and the plain versions of - * start/startDetached() have been deprecated. - * The cleanest solution would be to have parsecmd() produce a - * QStringList for arguments, instead of transforming the string - * into something that the QProcess splitter accepts. - */ - QStringList arguments = QProcess::splitCommand(toqstr(latexEnvCmdPrefix(path, lpath)) + cmd_); - QString command = (arguments.empty()) ? QString() : arguments.first(); - if (arguments.size() == 1) - arguments.clear(); - else if (!arguments.empty()) - arguments.removeFirst(); -#endif + cmd_ = toqstr(cmd); + + // Set the environment for LaTeX + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + for (auto const & v : latexEnvironment(path, lpath)) + latexenv.insert(toqstr(v.first), QString::fromLocal8Bit(v.second.c_str())); + process_->setProcessEnvironment(env); + + // Parse the command line + QStringList arguments = parsecmd(cmd); + QString command = arguments.empty() ? QString() : arguments.takeFirst(); + if (detached) { state = SystemcallPrivate::Running; #ifdef Q_OS_WIN32 @@ -411,11 +410,8 @@ void SystemcallPrivate::startProcess(QString const & cmd, string const & path, if (err_file_.empty()) process_->setStandardErrorFile(QProcess::nullDevice()); #endif -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + if (!QProcess::startDetached(command, arguments)) { -#else - if (!QProcess::startDetached(toqstr(latexEnvCmdPrefix(path, lpath)) + cmd_)) { -#endif state = SystemcallPrivate::Error; return; } @@ -423,11 +419,7 @@ void SystemcallPrivate::startProcess(QString const & cmd, string const & path, delete released; } else if (process_) { state = SystemcallPrivate::Starting; -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) process_->start(command, arguments); -#else - process_->start(toqstr(latexEnvCmdPrefix(path, lpath)) + cmd_); -#endif } } diff --git a/src/support/SystemcallPrivate.h b/src/support/SystemcallPrivate.h index 19771dfc73..f9b27c3430 100644 --- a/src/support/SystemcallPrivate.h +++ b/src/support/SystemcallPrivate.h @@ -44,7 +44,7 @@ public: State state; bool waitWhile(State, bool processEvents, int timeout = -1); - void startProcess(QString const & cmd, std::string const & path, + void startProcess(std::string const & cmd, std::string const & path, std::string const & lpath, bool detach); int exitCode(); diff --git a/src/support/filetools.cpp b/src/support/filetools.cpp index d554f477eb..1bff01460d 100644 --- a/src/support/filetools.cpp +++ b/src/support/filetools.cpp @@ -737,12 +737,13 @@ string const replaceEnvironmentPath(string const & path) // Return a command prefix for setting the environment of the TeX engine. -string latexEnvCmdPrefix(string const & path, string const & lpath) +map latexEnvironment(string const & path, string const & lpath) { + map env; bool use_lpath = !(lpath.empty() || lpath == "." || lpath == "./"); if (path.empty() || (lyxrc.texinputs_prefix.empty() && !use_lpath)) - return string(); + return env; string texinputs_prefix = lyxrc.texinputs_prefix.empty() ? string() : os::latex_path_list( @@ -766,30 +767,37 @@ string latexEnvCmdPrefix(string const & path, string const & lpath) texinputs_prefix.append(sep + abslpath); } - if (os::shell() == os::UNIX) - return "env TEXINPUTS=\"." + sep + texinputs_prefix - + sep + texinputs + "\" " - + "BIBINPUTS=\"." + sep + allother_prefix - + sep + bibinputs + "\" " - + "BSTINPUTS=\"." + sep + allother_prefix - + sep + bstinputs + "\" " - + "TEXFONTS=\"." + sep + allother_prefix - + sep + texfonts + "\" "; - else - // NOTE: the dummy blank dirs are necessary to force the - // QProcess parser to quote the argument (see bug 9453) - return "cmd /d /c set \"TEXINPUTS=." + sep + " " - + sep + texinputs_prefix - + sep + texinputs + "\" & " - + "set \"BIBINPUTS=." + sep + " " - + sep + allother_prefix - + sep + bibinputs + "\" & " - + "set \"BSTINPUTS=." + sep + " " - + sep + allother_prefix - + sep + bstinputs + "\" & " - + "set \"TEXFONTS=." + sep + " " - + sep + allother_prefix - + sep + texfonts + "\" & "; + // NOTE: the dummy blank dirs are necessary to force the + // QProcess parser to quote the argument (see bug 9453) + string const dummy = (os::shell() == os::UNIX) ? string() : (" " + sep); + + env.insert({ "TEXINPUTS", "." + sep + dummy + texinputs_prefix + sep + texinputs }); + env.insert({ "BIBINPUTS", "." + sep + dummy + allother_prefix + sep + bibinputs }); + env.insert({ "BSTINPUTS", "." + sep + dummy + allother_prefix + sep + bstinputs }); + env.insert({ "TEXFONTS", "." + sep + dummy + allother_prefix + sep + texfonts }); + + return env; +} + + +// Return a command prefix for setting the environment of the TeX engine. +string latexEnvCmdPrefix(string const & path, string const & lpath) +{ + auto const env = latexEnvironment(path, lpath); + if (env.empty()) + return string(); + + string prefix; + if (os::shell() == os::UNIX) { + prefix = "env "; + for (auto const & v : latexEnvironment(path, lpath)) + prefix += v.first + "=" + v.second + " "; + } else { + prefix = "cmd /d /c "; + for (auto const & v : latexEnvironment(path, lpath)) + prefix += "set \"" + v.first + "=" + v.second + "\" & "; + } + return prefix; } diff --git a/src/support/filetools.h b/src/support/filetools.h index 404dec210f..a4ee8e3380 100644 --- a/src/support/filetools.h +++ b/src/support/filetools.h @@ -14,9 +14,10 @@ #include "support/docstring.h" -#include -#include +#include #include +#include +#include namespace lyx { namespace support { @@ -289,6 +290,15 @@ std::string const onlyFileName(std::string const & fname); */ std::string const replaceEnvironmentPath(std::string const & path); + +/** + Return a map to be used to set the environment of the TeX engine + with respect to the paths \p path and \p lpath. + */ +std::map +latexEnvironment(std::string const & path, std::string const & lpath); + + /** Return a string to be used as a prefix to a command for setting the environment of the TeX engine with respect to the paths \p path and \p lpath.