Is there a way without resorting to global variables?
var dopus = DOpus.Create();
var cmd = dopus.Command();
cmd.vars.Set("myValue", 123);
cmd.SetType("script");
cmd.AddLine("@script JScript");
cmd.AddLine('DOpus.Output(/*How do I access `myValue` here???*/);');
cmd.Run();
I think the question was without resorting to global variables.
@osgo: What are you trying to achieve here ?
I'm interested in the use case that leads you to create a script from a script.
Usually, script are triggered by some event (button click, filesystem change, clipboard change, ...) and the goal is to perform some actions.
The script will let you define the algorthmic part of these actions (or display a dialog to interact with user) and in the end, to perform "base opus actions" (Go to a certain location, set attributes on files, open a Tab Group, ...) you'll often need to make a Command object to perform these actions.
A few remarks:
It will probably lead to some complexity (as your question demonstrates)
What will the script in the command do in the end, that is not possible to do in the first script that creates the command? I don't think it's possible to just invoke a script like that (apart from CLI), you'll need an entry point (OnClick for a button, the commands/events you declare in a global script).
All that leads to my first question (and it is intriguing ): What's the goal you're trying to achieve?
It's for a script add-in that adds a custom command file runner command. At least currently, when the preference is checked that different listers can be of different type (source, dest, off, dual), dopusrt.exe can only run .dcf files in the source lister. But the control I seek always aims at the active lister, only accessible with dopusrt.exe using its /cmd:active switch. So, a custom command is needed that lets me run code files (without the XML skeleton) using that switch. And that command implementation encapsulates usage of Command.SetType(), Command.AddLine() etc. So, there's no script complexity of having code from both layers in the same source code file.
As I said in another post of mine, I use IPC to communicate (through a JSON file) between the DOpus script side and the external program that launches the scripts using dopusrt.exe.
In such script files, I can avoid the boilerplate of the IPC communication and usage of DOpusFactory.Command()/Command.SetSourceTab()/Command.SetDestTab() in favor of what's passed in the context of function OnClick(clickData), which does then refer to the active and not just the source lister, because the custom command implementation in the script add-in does these things.
Maybe more detail on what you're trying to do at a higher level would be useful here, as it doesn't make much sense to me either so far. It sounds very complicated to do something like open a file that normally wouldn't be complicated.
The custom command file runner is for use with Talon that allows you to control your computer with voice commands. I'm working on support for Directory Opus, and it involves very many aspects of controlling DOpus, so not just a limited use case. Ideally, the whole of DOpus would be controllable by voice.
The code for the different voice commands has different requirements: It may simply cause an action. It may need to cause an action based on arguments (script input data). It may also need to query information, and, based on it, have different branches of behavior that can't necessarily be done in the DOpus script (script output data with continuation on the external Python side).
So, the command file runner that handles the IPC using the quasi-memory-only communication file (because of FILE_ATTRIBUTE_TEMPORARY attribute) must be able to send data into the scripts and retrieve data from the scripts. Since the communication file uses JSON serialization, my first approach would be to set a global variable to the JSON string before the script runs, and to read a global variable that is expected to contain a JSON string after the script ended. However:
I'd like to avoid a global namespace. Direct communication with the innards of the script file without the need to make name collisions improbable would be more professional.
In the spirit of avoiding boilerplate code in the individual scripts (especially for the IPC) and of the separation-of-concerns principle, I'd like to be able to do the JSON (de)serialization in the script add-in and not in every voice command script. At some point, when I tried transferring a JScript object containing DOpus Tab objects, typeof in the inner script reported the type unknown for the JScript object (you never see that in browsers). It would be nice to be able to directly pass objects, if that's possible. Maybe have a general means of extending the JScript API from outside the script using Command or so. Even if objects couldn't be passed from a script add-in to a script without serialization, using some kind of per-Command API extension, you could easily just call a function getInput(), e.g., that'd know the JSON input string and do everything for you.
What I still don't get is why you're building a script from a script ...
If I take your first example (which is obviously a simplification of what you're trying to do), wouldn't it be easier to directly launch the opus commands from this script, e.g.:
var dopus = DOpus.Create();
var cmd = dopus.Command();
// Maybe do something here to retrieve the active lister (enumerating DOpus.GetListers(),
// and check if lastactive is true,
// and if so bind the activetab to the command (cmd.SetSourceTab)
DOpus.Output(/*Do access `myValue` here*/ 123);
Or, if a reason I don't get your only issue is to insert the value, since you're running the command just after building it, you can also go for:
var dopus = DOpus.Create();
var cmd = dopus.Command();
// cmd.vars.Set("myValue", 123);
var myValue = 123; // Replace this by whatever way you get the value
cmd.SetType("script");
cmd.AddLine("@script JScript");
cmd.AddLine('DOpus.Output(' + myValue + ');');
cmd.Run();
But I can't even say if that way of invoking scripts works.
But I can't even say if that way of invoking scripts works.
Running scripts like this works. Using SetType("script") even ensures function OnClick(clickData) from the inner script is called. I also tested this with a .dcf file, and <function> instead of <function type="script"> means the top-level JScript code (initiated with @script JScript) is executed, but OnClick() isn't called.
What I still don't get is why you're building a script from a script ...
[...]
wouldn't it be easier to directly launch the opus commands from this script
From Talon's Python side, I initiate running of DOpus scripts via dopusrt.exe. I do this with its /cmd:active,thisdesktop switch and run a command that my script add-in added (RunBareCommandFile, because such files don't have the XML skeleton). The implementation of this command file runner reads the code from a file. Which bare command file should be run is decided on the Python side, depending on which voice command was uttered. The command file runner is a fancier version of using dopusrt.exe without any switch to run .dcf files, tailored more to my needs with regard to the IPC.
cmd.AddLine('DOpus.Output(' + myValue + ');');
In a defined context or with an escape function available, you could work with string concatenation and make use of JScript's function hoisting and call something like cmd.AddLine('function getInput() { return JSON.parse("' + inputJSON + '");'); at the end of the source script. If there's currently nothing better, I'd use this workaround for the time being. But what if you want to make the input directly available as a variable? The variable definition isn't hoisted (only it's declaration). And if you add it at the beginning, you make error messages less useful, because the line numbers are shifted.
So, being able to inject DOpus variables into Command scripts and reading them after they ran would be useful. Being able to extend what's available inside a script (like an input or args variable, just like DOpus also makes DOpus available) would make the script input part of that even better. Without that, the output part would force you to resort to global or otherwise inappropriately scoped variables, as far as I can see.
Thanks for the explanations, I might now be starting to understand
Just an idea: If that logic was done on the Opus add-in script you might be able to go directly with building the commands in the script rather than in a script built in the script. Since I have no idea what kind of input & logic you have on that side, it might be difficult to achieve though.
Or maybe build some kind of command list description object on Python side and provide it to be interpreted on the add-in script side. But that second option might be tricky (especially building some kind of abstract Opus command description language/pivot format).