From 6d29beb91b019e1995cdb7c4aaf7a043de4bf053 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Sep 2024 15:13:43 -0400 Subject: Add ability to pass command-line arguments to a Lua script. Introduce `ScriptCommand` class that parses a command line into a script name and optional args, using bash-like quoting and escaping. `ScriptCommand` searches for a file with that script name on a passed list of directories; the directories may be specified relative to a particular base directory. `ScriptCommand` supports the special case of a script name containing unescaped spaces. It guarantees that either the returned script file exists, or its `error()` string is non-empty. Replace `LLLeap::create()` logic, from which `ScriptCommand` was partly derived, with a `ScriptCommand` instance. Make `LLLUAmanager::runScriptFile()` use a `ScriptCommand` instance to parse the passed command line. Subsume `LLAppViewer::init()` script-path-searching logic for `--luafile` into `ScriptCommand`. In fact that lambda now simply calls `LLLUAmanager::runScriptFile()`. Make `lluau::dostring()` accept an optional vector of script argument strings. Following PUC-Rio Lua convention, pass these arguments into a Lua script as the predefined global `arg`, and also as the script's `...` argument. `LuaState::expr()` also accepts and passes through script argument strings. Change the log tag for the Lua script interruption message: if we want it, we can still enable it, but we don't necessarily want it along with all other "Lua" DEBUG messages. Remove `LuaState::script_finished_fn`, which isn't used any more. Also remove the corresponding `LLLUAmanager::script_finished_fn`. This allows us to simplify `~LuaState()` slightly, as well as the parameter signatures for `LLLUAmanager::runScriptFile()` and `runScriptLine()`. --- indra/newview/llluamanager.cpp | 83 ++++++++++++------------------------------ 1 file changed, 23 insertions(+), 60 deletions(-) (limited to 'indra/newview/llluamanager.cpp') diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index c4ce07e492..046526ac3c 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -186,75 +186,41 @@ LLLUAmanager::script_result LLLUAmanager::waitScriptFile(const std::string& file } void LLLUAmanager::runScriptFile(const std::string &filename, bool autorun, - script_result_fn result_cb, script_finished_fn finished_cb) + script_result_fn result_cb) { // A script_result_fn will be called when LuaState::expr() completes. - LLCoros::instance().launch(filename, [filename, autorun, result_cb, finished_cb]() + LLCoros::instance().launch(filename, [filename, autorun, result_cb]() { ScriptObserver observer(LLCoros::getName(), filename); - // Use LLStringUtil::getTokens() to parse the script command line - auto tokens = LLStringUtil::getTokens(filename, - " \t\r\n", // drop_delims - "", // no keep_delims - "\"'", // either kind of quotes - "\\"); // backslash escape - -#error Under construction - // TODO: - // - Move this either/or file existence logic to LuaCommand() - // - Accept LuaCommand in all LLLUAmanager::mumbleScriptFile() methods - // - Update all existing mumbleScriptFile() callers - - llifstream in_file; - in_file.open(tokens[0]); - if (in_file.is_open()) - { - // The first token is in fact the script filename. Now that the - // script file is open, we've consumed that token. The rest are - // command-line arguments. - tokens.erase(tokens.begin()); - } - else + LLSD paths(gSavedSettings.getLLSD("LuaCommandPath")); + LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL; + // allow LuaCommandPath to be specified relative to install dir + ScriptCommand command(filename, paths, gDirUtilp->getAppRODataDir()); + auto error = command.error(); + if (! error.empty()) { - // Parsing the command line produced a script file path we can't - // open. Maybe that's because there are spaces in the original - // pathname that were neither quoted nor escaped? See if we can - // open the whole original command line string. - in_file.open(filename); - if (! in_file.is_open()) + if (result_cb) { - std::ostringstream msgstream; - msgstream << "Can't open script file " << std::quoted(tokens[0]); - if (filename != tokens[0]) - { - msgstream << " or " << std::quoted(filename); - } - auto msg = msgstream.str(); - LL_WARNS("Lua") << msg << LL_ENDL; - if (result_cb) - { - result_cb(-1, msg); - } - return; + result_cb(-1, error); } - // Here we do have in_file open, using the whole input command - // line as its pathname. Discard any parts of it we mistook for - // command-line arguments. - tokens.clear(); - } + return; + } - // Here in_file is open. + llifstream in_file; + in_file.open(command.script); + // At this point, since ScriptCommand did not report an error, we + // should be able to assume that 'script' exists. If we can't open it, + // something else is wrong?! + llassert(in_file.is_open()); if (autorun) { sAutorunScriptCount++; } sScriptCount++; - // A script_finished_fn is used to initialize the LuaState. - // It will be called when the LuaState is destroyed. - LuaState L(finished_cb); + LuaState L; std::string text{std::istreambuf_iterator(in_file), {}}; - auto [count, result] = L.expr(filename, text, tokens); + auto [count, result] = L.expr(command.script, text, command.args); if (result_cb) { result_cb(count, result); @@ -279,8 +245,7 @@ LLLUAmanager::script_result LLLUAmanager::waitScriptLine(const std::string& chun return startScriptLine(chunk).get(); } -void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb, - script_finished_fn finished_cb) +void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb) { // find a suitable abbreviation for the chunk string std::string shortchunk{ chunk }; @@ -292,11 +257,9 @@ void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn resu shortchunk = stringize(shortchunk.substr(0, shortlen), "..."); std::string desc{ "lua: " + shortchunk }; - LLCoros::instance().launch(desc, [desc, chunk, result_cb, finished_cb]() + LLCoros::instance().launch(desc, [desc, chunk, result_cb]() { - // A script_finished_fn is used to initialize the LuaState. - // It will be called when the LuaState is destroyed. - LuaState L(finished_cb); + LuaState L; auto [count, result] = L.expr(desc, chunk); if (result_cb) { -- cgit v1.2.3