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<string, string> 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.
This commit is contained in:
Jean-Marc Lasgouttes 2022-10-28 17:21:33 +02:00
parent 22e5903bf3
commit c8015878e1
4 changed files with 99 additions and 89 deletions

View File

@ -13,13 +13,14 @@
#include <config.h>
#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,
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<string> 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
}
}

View File

@ -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();

View File

@ -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 <string,string> latexEnvironment(string const & path, string const & lpath)
{
map<string, string> 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 + "\" & ";
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;
}

View File

@ -14,9 +14,10 @@
#include "support/docstring.h"
#include <utility>
#include <string>
#include <map>
#include <set>
#include <string>
#include <utility>
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 <std::string, std::string>
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.