Like many programs, the GoFiler application can be run from a command-line or shell command. By learning some basics, you can run and control the application directly from a command prompt or shell execute. Further, by using some script code you can do much more than just open a file.
Friday, February 03. 2017
LDC #20: Running a Script From a Command-Line
Like most applications, GoFiler will accept command-line parameters or options. The source of the command-line parameters is not important. What matters is that they are passed to the application as part of the startup.
It is important to note that while the GoFiler can be run from a command-line console like cmd.exe, it is not a console application. This means it cannot easily communicate with the conventional console, including shell pipes, standard input, output, and errors (stdio or conventional pipes). It also means that depending on how that application is run, the calling shell may not wait for execution to complete. For example, when running the application from the Windows command prompt, the command prompt will run the application asynchronously as a separate process and therefore return the user immediately to the prompt. Certain programming environments such as Perl, Python or even Legato have options to wait for and be signaled when the program executing the shell terminates. These issues become important when a command-line action is used to run scripts as part of a larger system.
Command-line options are delimited by forward slashes (‘/’) or plain dashes (‘-’). There is no difference in their meaning. By default, most command-line options are not case sensitive but that can depend on the section of the application receiving the specified options.
For the GoFiler application, command-line data is considered to be a series of unrelated options. The options not understood by the application are assumed to belong to other scripts and tasks. What this means is well-formed but unknown options will not necessarily generate an error. So be careful typing. The first item, if not preceded by a slash or a dash, is assumed to be a filename specification to open by the application desktop.
To get a list of options for the application, just go to Windows Start, Run cmd.exe (command prompt), and make sure you have the path for GoFiler or are in the GoFiler program folder. Type:
gofiler /?
The result will be a dump to the console of the application’s options:
Command Line Help - GoFiler Complete ----------------- Form: app.exe filename /option:data ..... Where: filename Name to open on the application desktop. /CommandFile Specifies a file which contains parameters to process as a command line. After being processed, the command line parsing will continue from the start of the file. /NewInstance Starts a new instance of the application or starts normally if no application is open. /NoGUI Indicates whether to start the Graphics User Interface (desktop). When used with RunScript, the script is run during the application startup sequence. /OpenFile Opens the specified file(s). /OpenInfoFile Opens the specified file as an Information View log. /OpenProjectFile Opens the specified specifically as a project file. /Parameters Specifies individual parameter "name: value;" pairs. /ParameterFile Specifies a file to use instead of the default parameters. /RunScript Runs the specified script file either as a startup script or as applications desktop script depending on the NoGUI parameter. /ScriptParams Specifies parameters to pass to the script specified with the RunScript Command As and API shell call (see API documentation): /InFile Specifies one or more input files to apply to the API verb. /OutFile Output File /Preferences Specifies a file that will override one or more application preferences. /Project Specifies the project file to use or create during API /Result Specifies destination file for command line API verb processing. This must be provided to have an API verb be processed. /..verb... See the API reference for the application for a list of verbs.
If an option’s value contains spaces, place double quotes around the value data. While it may seem obvious, GoFiler is a ‘window’ application, not a ‘console’ application. In this article, we will be concerning ourselves with those items related to scripts and using the command line to pass a script to GoFiler for execution.
Running a Script
The first option we will discuss is ‘/RunScript’, which runs a specified script. By default, when the application runs the script, it is executed in the first instance of the application found on the computer desktop. If a version of the application is already running and the ‘/NewInstance’ option is not specified, the script location is sent to the currently running application and then executed. Note that this can get a bit ugly since the script will be executed even if dialogs are open in the current application instance. It is up to the script to determine whether it is appropriate to perform designated operations without conflicting with already running tasks. One tool, the IsDialogOpen Legato function, can be used to see if any dialogs are open.
An example of ensuring the script is run in a new instance:
gofiler /runscript:"z:\myscripts\test.ls" /newinstance
When the script is run in the GUI (Graphics User Interface or the application desktop), the desktop will return to its previous state upon completion. The ExitApplication function can be used to force the application to quit.
Keep in mind that files specified in the command-line should have qualified path names. In our examples, the path for the gofiler.exe (or other product name) is omitted for brevity.
Finally, since the application desktop may be opening, initialization tasks, such as RSS notifications and other application startup functions, will run. On a new instance execution, the script you specify is run as the last initialization operation. For an existing instance, the /RunScript is passed as a message to the existing frame window.
To GUI or Not to GUI
Unless directed otherwise, the GUI is displayed or the command-line is directed to another GUI instance. The ‘/NoGUI’ command-line option will stop the application from displaying the desktop and will direct it instead to enter menu/message mode.
When run in NoGUI mode, the application desktop is not available. Files cannot be opened in any edit views and any desktop or window-dependent functions will not operate. This includes running menu functions with the RunMenuFunction function. To test if the desktop is open, use the IsDesktopRunning function.
Dialog and message box functions are fully functional and, in fact, it is not an uncommon programming practice to use a dialog as a graphics user interface to perform functions.
Without a desktop, the application will immediately exit when the script execution has completed. As mentioned earlier, depending on the execution or ‘shell’ method, the caller may not be waiting for execution to complete. As a result, programmers must determine the best method to signal or control the program that called the script via command-line.
The return value of the script is interpreted in three ways:
ERROR_CANCEL will cause the NoGUI operation to be aborted and the application desktop will load. This can be used as an opportunity to perform certain initialization prior to starting the app, such as a log-in.
ERROR_SOFT or any dword with the top bit set (0x80000000) will cause the application to exit with EXIT_FAILURE (1).
Any other code will cause the application to exit with EXIT_SUCCESS (0).
The failure or success code is returned to Windows and will be available to the calling program.
Getting Command-Line Data
There are essentially two methods of getting command-line data along with a variation. The GetCommandLine SDK function will return the entire command-line, less the name of the application executable. After retrieving the string, a script can then parse it as appropriate.
gofiler /runscript:d:\data\commandline.ls /a /b /c:"My thing" /NoGUI
The content of ‘commandline.ls’ (location is your choice):
MessageBox('i', GetCommandLine());
results in:
Another method is to use the GetCommandLineParameter function. This function is a little more sophisticated in that it uses the internal command-line class and allows parameters to be directly addressed. Changing the content of ‘commandline.ls’:
MessageBox('i', GetCommandLineParameter("c"));
results in:
The GetCommandLine function takes no parameters while the GetCommandLineParameter function has the following structure:
string = GetCommandLineParameter ( string name, [int index] );
The name parameter is the name of the parameter to retrieve. Some predefined parameters, such as ‘/InFile’, allow for multiple entries, so the index parameter can be used to retrieve each sub item.
The variation to the above is to use the predefined ‘/Parameters’ or ‘/ParameterFile’ options to send script specific options. The GetCommandLineParameter function can be used to get the data, and the script can then parse them as needed.
Finally, Windows limits the amount of data that can be placed on a command line to about 4,000 bytes. The ‘/CommandFile’ option can be used with large amounts of data stored in command-line format in a file of up to 65K bytes. The command-line parser for the application will automatically load the data, which will only be available to the GetCommandLineParameter function.
Example #1 — Retrieving Company Information
Using the command-line and the ‘/NoGUI’ option, let us make a script to retrieve information from the public side EDGAR Company database:
string list[]; string cik, output, s1; int rc; output = GetCommandLineParameter("output"); if (output == "") { return ERROR_CANCEL; } cik = GetCommandLineParameter("cik"); if (cik == "") { StringToFile("CIK Required", output); return ERROR_RANGE; } list = EDGARLookupCIK(cik); if (IsError()) { rc = GetLastError(); s1 = FormatString("Error %08X looking up CIK %s", rc, cik); StringToFile(s1, output); return rc; } s1 = ArrayToParameters(list); StringToFile(s1, output); return ERROR_NONE;
This is a simple, unstructured script, that when run retrieves two options from the command-line: ‘/cik’ and ‘/output’. Assuming we get a valid filename from the /output option, errors can be reported back in the contents of the output file. Prior to that, if the script is called from a shell operation, the return value can be used to determine if an error occurred.
The first task is to get the ‘/output’ name and ‘/cik’ options as strings. Once we have those, the EDGARLookupCIK function can be used to get the data. The data is then converted from a keyed list to a parameter list and written to a file.
An example command-line (location is set as ‘d:\data\’):
gofiler /runscript:d:\data\getcompanydata.ls /nogui /cik:320193 /output:d:\data\result.txt
Would result in the output file:
CompanyName: APPLE INC CIK: 0000320193 IRSNumber: 942404110 FileNumber: 001-36743 EntityType: SIC: 3571 Address: ONE INFINITE LOOP CUPERTINO, CALIFORNIA 95014 PhoneNumber: (408) 996-1010 SOI: CALIFORNIA FYE: 0930 LastUpdate: 12/15/16
Note that if we open the result in Notepad, address lines one and two will not be split. A simple return (0x0D) is added to delimit the line in the ‘Address’ field (Notepad does not understand simple return codes). To fix this for our simple example, add the following code before the array is converted to a parameter string:
list["Address"] = ReplaceInString(list["Address"], "\r", ",");
Example #2 — Fetch all Registrant EDGAR Filings
Here is another example:
string list[]; string cik, output, s1; int rc; output = GetCommandLineParameter("output"); if (output == "") { return ERROR_CANCEL; } cik = GetCommandLineParameter("cik"); if (cik == "") { StringToFile("CIK Required", output); return ERROR_RANGE; } list = EDGARFetchArchiveList(cik); if (IsError()) { rc = GetLastError(); s1 = FormatString("Error %08X looking up CIK %s", rc, cik); StringToFile(s1, output); return rc; } s1 = ImplodeArray(list); StringToFile(s1, output); return ERROR_NONE;
The example command-line would be:
gofiler /runscript:d:\data\getfilings.ls /nogui /cik:320193 /output:d:\data\result.txt
And this is the result placed into the output file (truncated):
http://www.sec.gov/Archives/edgar/data/320193/000162828017000312/0001628280-17-000312.txt http://www.sec.gov/Archives/edgar/data/320193/000162828017000311/0001628280-17-000311.txt http://www.sec.gov/Archives/edgar/data/320193/000021545717001275/0000215457-17-001275.txt http://www.sec.gov/Archives/edgar/data/320193/000162828017000187/0001628280-17-000187.txt http://www.sec.gov/Archives/edgar/data/320193/000119312517003764/0001193125-17-003764.txt http://www.sec.gov/Archives/edgar/data/320193/000119312517003753/0001193125-17-003753.txt http://www.sec.gov/Archives/edgar/data/320193/000162828016022242/0001628280-16-022242.txt http://www.sec.gov/Archives/edgar/data/320193/000162828016022104/0001628280-16-022104.txt http://www.sec.gov/Archives/edgar/data/320193/000162828016022047/0001628280-16-022047.txt . . . http://www.sec.gov/Archives/edgar/data/320193/000095015094000392/0000950150-94-000392.txt http://www.sec.gov/Archives/edgar/data/320193/000091898394000005/0000918983-94-000005.txt http://www.sec.gov/Archives/edgar/data/320193/000091898394000004/0000918983-94-000004.txt http://www.sec.gov/Archives/edgar/data/320193/000095015094000252/0000950150-94-000252.txt http://www.sec.gov/Archives/edgar/data/320193/000032019394000002/0000320193-94-000002.txt http://www.sec.gov/Archives/edgar/data/320193/000089161894000021/0000891618-94-000021.txt
Exmaple #3 — Get Series and Class Information
This example is a little more complex since we will be retrieving a table of information. This could be written out as a CSV table or perhaps as a cluster. For this example, we will convert it to plain text:
string list[][]; string cik, output, s1, s2; int ix, size; int rc; output = GetCommandLineParameter("output"); if (output == "") { return ERROR_CANCEL; } cik = GetCommandLineParameter("cik"); if (cik == "") { StringToFile("CIK Required", output); return ERROR_RANGE; } list = EDGARGetClassTable(cik); if (IsError()) { rc = GetLastError(); s1 = FormatString("Error %08X looking up CIK %s", rc, cik); StringToFile(s1, output); return rc; } s1 = "IX ------Key Name-------- : -SeriesID- -ClassID- --Ticker-- --Status--\r\n"; size = ArrayGetAxisDepth(list, AXIS_ROW); while (ix < size) { s2 = ArrayGetKeyName(list, ix, AXIS_ROW); s1 += FormatString("%3d %-22s : %10s %10s %-10s %-8s %s\r\n", ix, s2, list[ix]["SeriesID"], list[ix]["ClassID"], list[ix]["Ticker"], list[ix]["Status"], list[ix]["Name"]); ix++; } StringToFile(s1, output); return ERROR_NONE;
This would be the command-line for this example:
gofiler /runscript:d:\data\getseries.ls /nogui /cik:930667 /output:d:\data\result.txt
Note that our list is now a two-dimension table and we are iterating through the data to build the text output. The result in the output file would be like (truncated to fit):
IX ------Key Name-------- : -SeriesID- -ClassID- -Ticker- -Status- 0 S000004246 : S000004246 Active ... MSCI Australia ETF 1 S000004246_C000011950 : C000011950 EWA Active ... MSCI Australia ETF 2 S000004247 : S000004247 Active ... MSCI Hong Kong ETF 3 S000004247_C000011951 : C000011951 EWH Active ... MSCI Hong Kong ETF 4 S000004248 : S000004248 Active ... MSCI Italy Capped ETF 5 S000004248_C000011952 : C000011952 EWI Active ... MSCI Italy Capped ETF . . . 139 S000049021_C000154544 : C000154544 EMGF Active ... Edge MSCI Multifac... 140 S000051285 : S000051285 Active ... Edge MSCI Min Vol ... 141 S000051285_C000161697 : C000161697 HEMV Active ... Edge MSCI Min Vol ... 142 S000054183 : S000054183 Active ... MSCI EM ESG Optimi... 143 S000054183_C000170244 : C000170244 ESGE Active ... MSCI EM ESG Optimi...
Output like this could also easily be written to a CSV file using the CSVWriteTable function.
Putting it Together
To develop scripts that run from a command-line, it is a good idea to write and debug the code in the GoFiler IDE. Code can actually be made multi-purpose by using the GetScriptParent function to determine how to handle the input and output. The return value can be checked in the Legato IDE or with various command-line states. Working in the IDE also allows program stepping and variable inspection during debugging.
string list[]; string cik, output, s1; int rc; if (GetScriptParent() == "LegatoIDE") { output = "D:\\Data\\MyOutput.txt"; cik = "320193"; } else { output = GetCommandLineParameter("output"); if (output == "") { return ERROR_CANCEL; } cik = GetCommandLineParameter("cik"); if (cik == "") { StringToFile("CIK Required", output); return ERROR_RANGE; } } list = EDGARLookupCIK(cik); if (IsError()) { rc = GetLastError(); s1 = FormatString("Error %08X looking up CIK %s", rc, cik); StringToFile(s1, output); return rc; } s1 = ArrayToParameters(list); StringToFile(s1, output); if (GetScriptParent() == "LegatoIDE") { ConsolePrint(s1); } return ERROR_NONE;
Using example #1, we can add the IDE test to the start of the script and load our variable manually. We can even dump the output to a console window. This make debugging much easier. The debug code can be later removed or left in place for future development.
In addition, since debugging scripts for command-line operation is essentially performed in the blind, the DebugTrace pragma can be used to place execution and debug data in a log. For example, adding the following to the start of the program:
#pragma DebugTrace mylog.log
will place trace data into ‘mylog.log’ in the user temporary file area (‘%temp%’). The application log (located in ‘%AppData%\Novaworks\’) can also be used to check the overall operation of the application.
Learning how to use the command-line can open a lot of alternatives to access EDGAR and other functions embedded in GoFiler.
Note: The CIK and associated companies used in the examples are not related to Novaworks and are intended only as a source of data for demonstration purposes.
Scott Theis is the President of Novaworks and the principal developer of the Legato scripting language. He has extensive expertise with EDGAR, HTML, XBRL, and other programming languages. |
Additional Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato