Marty | photo by Rodney Ludwig with FotoSketcher |
replace nasty batch files with simple mscripts
mscript is line-based and uses symbols instead of keywords.
In this document a 'statement prefix' is the same as a 'symbol':
if (verbose_flag) { printf("verbose_flag is turned on\n"); } in mscript becomes ? verbose_flag > "verbose_flag is turned on" }
mscript is developed and maintained by Michael Balloni and Rodney Ludwig
CodeProject articles document mscript versions: 1.0 - 2.0 - 2.0.2 - 3.0 - 4.0
mscript is open source, and contributions are welcome,
especially dll integrations
See GitHub for source code and a description of project layout
questions or comments? contact@mscript.io
installer: mscript4.msi - standalone EXE (< 1 MB): mscript4.exe (TERMS OF USE)
mscript's own build script is a good example:
? arguments.length() != 1 > "Usage: mscript-builder.exe build.ms version" > 'version should be like "4.0.0"' * exit(0) } $ version = arguments.get(0) > "Version " + version $ version_parts = version.split('.') ? version_parts.length() != 3 > "ERROR: Invalid version, not three parts!" * exit(1) } * setenv("ms_version", version) > "Binaries..." >! rmdir /S /Q binaries mkdir binaries xcopy /Y ..\mscript\Win32\Release\*.dll binaries\. xcopy /Y ..\mscript\Win32\Release\*.exe binaries\. > "Samples..." >! rmdir /S /Q samples mkdir samples xcopy /Y ..\mscript\mscript-examples\*.ms samples\. > "Licenses..." >! rmdir /S /Q licenses mkdir licenses xcopy /Y ..\mscript\mscript-licenses\*.* licenses\. / Collect our list of filenames we care about $ filenames = list("mscript4.exe", "mscript-db.dll", "mscript-http.dll", "mscript-log.dll", "mscript-registry.dll", "mscript-sample.dll", "mscript-timestamp.dll") / Update our filenames to be in the relative binaries folder ++ f : 0 -> filenames.length() - 1 * filenames.set(f, "binaries\" + filenames.get(f)) } > "Resource hacking..." $ resource_contents = readFile("resources.template", "utf8") * resource_contents = \ resource_contents.replaced \ ( \ "%MAJOR%", version_parts.get(0), \ "%MINOR%", version_parts.get(1), \ "%BUILD%", version_parts.get(2) \ ) * writeFile("resources.rc", resource_contents, "utf8") >! del resources.res ResourceHacker.exe -open resources.rc -save resources.res -action compile @ filename : filenames > "..." + filename * setenv("ms_filepath", filename) ResourceHacker.exe -open %ms_filepath% -save %ms_filepath% -action addoverwrite -resource resources.res } > "Signing...all at once..." >! * setenv("filenames_combined", filenames.join(" ")) signtool sign /n "Michael Balloni" %filenames_combined% > "Building installer..." AdvancedInstaller.com /edit mscript.aip /SetProperty ProductVersion="%ms_version%" AdvancedInstaller.com /rebuild mscript.aip > "Building release site..." >! rmdir /S /Q site\releases\%ms_version% mkdir site\releases\%ms_version% > "Collecting samples..." >! rmdir /S /Q site\samples mkdir site\samples xcopy /Y ..\mscript\mscript-examples\*.ms site\samples\. > "Assembling release..." @ filename : filenames * setenv("ms_filepath", filename) xcopy /Y %ms_filepath% site\releases\%ms_version%\. } > "Finalizing installer..." * setenv("installer_filename", "mscript4.msi") xcopy /Y /-I mscript-SetupFiles\mscript.msi site\releases\%ms_version%\%installer_filename% signtool sign /n "Michael Balloni" site\releases\%ms_version%\%installer_filename% > "Clean up..." rmdir /S /Q binaries rmdir /S /Q samples rmdir /S /Q licenses rmdir /S /Q mscript-cache rmdir /S /Q mscript-SetupFiles > "All done."
For more samples, check out...
For those programmers familiar with the wonderful text processing program awk, not only is mscript fully capable as an awk replacement, it can, during processing, categorize to a database, which awk could only dream of
For those programmers that have tried to distribute their python code and failed, mscript is a stand-alone executable file requiring no setup, no ugly setup required
mscript is an open source project hosted by GitHub under the Apache 2.0 license
mscript relies on other open source libraries, each of which has its own license:
This includes...
[] some_variable_or_expression = some_value_expression code } = some_other_value_expression code } <> fall through code } }
Break statements (V) work as in other languages, exiting the switch statement.
In previous versions, you could chain if's and else-if's by putting by having ? statements immediately follow each other. This is no longer allowed, just if / else now with ? / <>. If you want to do multiple if / if-else's, you can use a switch statement like so:
$ x = 12 [] true = x < 0 > "negative" } = x > 0 > "positive" } <> > "zero" } }
mscript evaluates the switch expression (true), then walks the case statements evaluating them, in this case for true or false, and executes the code for the first case that matches (in this case true), or the default case (<>). It's kind of a while (true) for if / else-if type coding.
new built-in functions htmlEncoded(), htmlDecoded(), urlEncoded(), and urlDecoded(),
useful for working with new mscript-http extension
this extension has one function: mshttp_process_request()
that takes an index with a full HTTP request interface,
and returns an index with the response status and headers
EQU | equal to NEQ | not equal to LSS | less than LEQ | less than or equal to GTR | greater than GEQ | greater than or equal to
remember that mscript is now case-insensitive, so you don't have to use Caps Lock
To support debugging larger scripts, you can now add trace statements.
The statements conditionally output messages that help you debug.
A global function is used to control which trace statements are active.
More details
In previous versions, extension DLLs had to be signed by the same authority that the mscript EXE was.
This restriction is lifted, which should encourage growth of an mscript extension ecosystem.
To use an extension DLL, the requirement remains that it be in the same directory as the mscript EXE.
NOTE: The language is mostly case-insensitive. Only index lookups and string comparisons are case-sensitive. /* a block comment */ / a single-line comment, on its own line, can't be at the end of a line NOTE: You used to use ! for single-line comments; ! is now used for error handling // another single-line comment, can appear anywhere in a line > "print the value of an expression, like this string, including pi: " + round(pi, 4) Any statement without a prefix is considered a command line to execute, making an mscript an adorned batch file of sorts, like Classic ASP for batch files Unadorned lines (normal commands) are executed and the local variables ms_LastCommand and ms_ErrorLevel are set >> evaluates the rest of the line as a string expression and runs it like a normal command The statement >! causes the next command line that runs to not raise errors if it fails; without this line preceding a command, a command failure raises an error If you put a command after >! on the same line then that command runs with errors suppressed, it's more concise Errors raised by command failures are indexes with entries "Error", "Command", and "ErrorLevel" This way you can have error handling defined far from the source of the error and still have the context of the error / Declare a variable with an initial value $ new_variable = "initial value" / A variable assignment & new_variable = "some other value" / Once a variable has be assigned a non-null value / the variable cannot be assigned to a value of another type, including null / So mscript has some type safety. / A general statement type, the * symbol, is usable for assignments or for side-effects like adding elements to a list $ my_list = list() * my_list.add("foo") * my_other_list = my_list.clone() / The O signifies an unbounded loop, a while(true) or for (;;) / All loops end in a closing curly brace, but do not start with an opening one O ... / the V statement is break > "gotta get out!" V } / If / Else / Curly braces are required at the ends of if and else clauses ? some_number = 12 some_number = 13 } <> some_number = -1 } / Switch [] some_expression_to_look_for = some_value_expression code } = some_other_value_expression code } <> fall through code } } IMPORTANT: Subsequent = clauses cannot have anything between them. So if you have... $ str = "" [] str.length() = 0 ...handle blank string } ...something = 5 / this command fails because it does not immediately follow the = 0 clause ...handle 5 } } With ?'s and ='s and <>'s, there can be no code or even comments or blank lines in the no man's land between the clauses. / A foreach loop / list(1, 2, 3) creates a new list with the given items / This statements processes each item in the list, printing them out / Note the string promotion in the print line @ item : list(1, 2, 3) > "Item: " + item } / Indexing loops / Notice the pseudo-OOP of the my_list.length() and my_list.get() calls $ my_list = list(1, 2, 3) / Use a ++ loop to go up from a starting index to an ending index ++ idx : 0 -> my_list.length() - 1 > "Item: " + my_list.get(idx) } / Use a -- loop to go down from a starting index to an ending index -- idx : my_list.length() - 1 -> 0 > "Item: " + my_list.get(idx) } / Use a # loop to go in either direction between start and end # i : 1 -> 10 > "i: " + i / NOTE: If you use a # loop to iterate an index number over the size of a list / and the list is empty: # i : 0 -> length(list()) - 1 / i will be -1 in here } / Hence the ++ and -- statements. / # is kept for simplicity and backwards compatibility { / Just a little block statement for keeping variable scopes separate / Useful for freeing resources like large strings as soon as they've been used / Variables declared in here... } / ...are not visible out here / Functions are declared like other statements ~ my_function (param1, param2) / do something with param1 and param2 / Function return values... /...with a value <- 15 / ...or without a value <- } / A little loop example ~ counter(low_value, high_value) $ cur_value = low_value $ counted = list() O / Use the * statement to evaluate an expression and ignore its return value / Used for adding to lists or indexes or executing functions with no return values * counted.add(cur_value) * cur_value = cur_value + 1 ? cur_value > high_value / Use the V statement to leave the loop, a break statement V <> / Use the ^ statement to go back up to the start of the loop, a continue statement ^ } } <- counted } / Load and run another script here, an import statement of sorts + "some_other_script.ms" / The script path is an expression, so you can dynamically load different things / Scripts are loaded relative to the script they are imported from / Imported scripts are processed just like top-level scripts; / they can declare global variables, define functions, and execute script / If you want to decide what function to call with a string variable, use the ** statement which unlocks this possibility $ my_counter = "counter" $ ret_val ** ret_val = my_counter(1, 3) / ret_val is now list(1, 2, 3) / The ** statement allows for this dynamic programming which would otherwise result in unexpected behavior
To support debugging larger scripts, you can now add trace statements.
The statements conditionally output messages that help you.
A global function is used to control which trace statements are active.
Tracing is often directed towards one particular section of code, with changing levels of importance over time.
The levels attached to sections are: trace_debug, trace_info, trace_warning, trace_error, and trace_critical.
1. setTracing defines a list of section names with a tracing level, often created by command line paramenters
new function: setTracing(listOfSectionLabelsToTrace, traceLevel)
2. >>> statements are embedded in the code
>>> section-label : level#-expression : output-msg-expression
Example setup:
* setTracing(list("function1","function2"), trace_debug)
/ Turn tracing on for sections function1 and function2,
/ and output all statements including debug tracing
Example tracing statment:
>>> "function1" : trace_debug : "Function1 entered"
Logic for executing >>> "section1" : trace_info : "Trace message"
"Is section1 in the list of sections to output set by setTracing()?"
"If so, is the configured trace level <= trace_info? (this is consistent with cx_Logger / mslog)"
"If so, output Trace message"
A value in mscript can be one of six types:
Binary operators, from least to highest precedence: or || and && <> != neq <= leq >= geq < lss > gtr == = equ % - + / * ^ Unary operators: - ! not An expression can be: null true false number string dquote squote tab lf cr crlf pi e TRACE_NONE TRACE_CRITICAL TRACE_ERROR TRACE_WARNING TRACE_INFO TRACE_DEBUG variable as defined by a $ statement Strings can be double- or single-quoted, both 'foo ("bar")' and "foo ('bar')" are valid This is handy for building command lines that involve double-quotes Just use single quotes around them String promotion: If either side of binary expression evaluates to a string, the expression promotes both sides to string Bool short-circuiting: The left expression is evaluated first If && and left is false, expression is false If || and left is true, expression is true
Error handling revolves around the error() function, and ! error handling statements Note that ! was used for single line comments in mscript 1.0 This is a breaking change to the meaning of ! Use / for single line comments going forward; you can use // if that makes you happy Say we have... ~ verifyLow(value) ? value >= 10 * error("Value is not low: " + value) } } Then we call it... * verifyLow(11) As written, the error() function call will cause mscript to output the error message and exit Not very useful Now we have error handling! Simply add a ! statement after code that you want to handle the errors of: * verifyLow(23) ! err > "verifyLow fails: " + err } In this case the call to verifyLow causes an error, then mscript looks for the first ! statement in the verifyLow function. Not found there, the error bubbles up to the top-level, where mscript finds the ! statement following the call to verifyLow You can pass any kind of object to the error() function, not just strings You can handle any type of object in your ! statements Using indexes for error objects could provide just the sophistication you need With the ! statement, you can name the error handler's variable whatever you like It doesn't have to be err You can have any number of statements before a ! statement, it's not just one ! per prior statement This is sort of like error handling in other languages, just with a lot less code organization and finger wear, which is mscript's mantra
exec(cmd_line, setttings) - Prior to v4, this was the function that gave mscript meaning in life - Build your command line, call exec, and get back an index with all you need to know: - success, exit_code, and output - pass in an index of settings: "ignore_errors" defaults to false any command not returning a zero exit code will raise an error set to true to tolerate errors "method" can be "popen" or "system" to get output from the command the default "popen" works to just get an exit code you can use "system" - Write all the script you want around calls to exec, and get a lot done - You can now do much of what exec() does using normal commands and >> statements - exec() gives you control over what method is used, and you can manage the results of the command - beyond the lifetimes of the local variables that normal commands and >> statements leave behind system(cmd_line[, suppress_errors) - execute the command line and return the exit code - raises errors on non-zero exist codes by default - pass true to the optional second parameter to suppress these errors popen(cmd_line[, suppress_errors) - execute the command line and return the command's output - raises errors on non-zero exist codes by default - pass true to the optional second parameter to suppress these errors input() - Read one line from the standard input and return it as a string getEnv(name) - get the value of an environment variable putEnv(name, value) - Set the value of an environment value - This affects the environment inside mscript and in programs run by exec() cd(newDirectory) - change the current working directory for all programs run with exec() curDir(optional_drive_letter) - get the current working directory, with an optional drive letter obj.toJson() - convert any value into JSON fromJson(json) - take JSON and return any kind of object error(error_msg) - raise an error, see the previous discussion on error processing exit(exit_code) - exit the script with an exit code parseArgs(arguments_list, argument_specs_list) - pass the global string list variable arguments as the first parameter - pass a list of indexes defining the arguments your mscript takes - index fields are: flag: -i long-flag: --input description: Specify the input to this script takes: bool, if true means that a value for the flag is expected required: bool, if no value for the flag then an error is raised default: a default value for the flag if no value given for the flag numeric: bool, if true tries to treat the taken value as a number See example usage obj.getType() - the type of an object obj as a string number(val) - convert a string or bool into a number string(val) - convert anything into a string list(item1, item2...) - create a list with the elements passed in index(key1, value1, key2, value2...) - create an index with the pairs of keys and values passed in obj.clone() - deeply clone an object, including indexes containing list values, etc. obj.length() - string or list length, or index pair count obj.add(to_add1, to_add2...) - append to a string, add to a list, or add pairs to an index obj.set(key, value) - set a character in a string, change the value at a key in a list or index obj.get(key) - return character of string, element in list, or value for key in index obj.has(value) - returns whether a string has a substring, a list has an item, or an index has key obj.keys() obj.values() - index collection access obj.reversed() - returns copy of obj with elements reversed, including keys of an index obj.sorted() - returns a copy of obj with elements sorted, including index keys list.join(separator) - join list items together into a string - the separator can be a string of any length str.split(separator) - split a string into a list of substrings - the separator can be a string of any length str.splitLines() - split a string into a list of line strings str.trimmed() - return a copy of a string with any leading or trailing whitespace removed str.toUpper(), str.toLower() - return a copy of a string in upper or lower case str.replaced(from, to) - return a copy of a string with a substring replaced with another substring - multiple from / to pairs can be provided for easy template-filling functionality random(min, max) - return a random value in the range min -> max str.fmt(parameter0, ...) - replace "{0}" with parameter0, "{1}" with parameter1, etc. obj.firstLocation(toFind), obj.lastLocation(toFind) - find the first or last location of an a substring in a string or item in a list obj.subset(startIndex[, length]) - get a substring of a string or a slice of a list, with an optional length str.isMatch(regex) - return true if a string is a match for a regular expression - by default matches are looked for anywhere in the string - pass true as a second parameter to require a match against the full string str.getMatchLength(regex) - returns the length of the first match for a regular expression, or -1 if no match - by default matches are looked for anywhere in the string - pass true as a second parameter to require a match against the full string str.getMatches(regex) - return a list of matches from a regular expression applied to a string - by default matches are looked for anywhere in the string - pass true as a second parameter to require a match against the full string setEnv(name, value) - set a process environment variable, usable in executed commands getEnv(name) - get the value of an environment variable num.round(places) - return the number rounded to the given number of decimal places str.expandedEnvVars() - return a copy of a string with embedded environment variables expanded getExeFilePath() - return the path to the mscript EXE getBinaryVersion(binary_file_path) - return the four-part version number of any EXE or DLL sleep(seconds) - pause script execution for a number of seconds cd(directory) - change the current directory curDir(driveLetter) - get the current directory for an option drive letter getIniString(file_path, section_name, setting_name, default_value) getIniNumber(file_path, section_name, setting_name, default_value) - provide INI file support readFile(file_path, encoding) - read a text file into a string, using the specified encoding, either "ascii", "utf8", or "utf16" readFileLines(file_path, encoding) - read the lines in a text file into a list strings writeFile(file_path, file_contents, encoding) - write a string to a text file with an encoding getLastError() - get the last Win32 error number getLastErrorMsg() - get the string description and number of the last Win32 error like, "Access denied. (5)" - you can pass in an error number to get its error message htmlEncoded(), htmlDecoded() / urlEncoded(), urlDecoded() - encode and decode strings for use in HTML / URLs - useful for working with the mscript-http extension setTracing(sectionsList, traceLevel) - this function controls tracing done by the >>> statement - the section names provided in the first parameter are enabled, - but the statements only execute if they are at or more serious - than the trace level parameter eval(expression) - this function takes a string, and then evaluates it as an expression - it works with expressions like "2 * 5", not statements like "> 'foobar'" - the expression could be a function call, and it can refer to local and global variables - it returns the result of evaluating the expression, so it can be the right hand side of a $ or &, otherwise call it with * for a side effect - it’s not as robust as JavaScript's eval(), but it’s enough to get you in plenty of trouble, so use with caution, not abandon Standard math functions, for your math homework: abs asin acos atan ceil cos cosh exp floor log log2 log10 round sin sinh sqrt tan tanh / How to add a command line argument / 1) Each command line argument is defined with an index in the following manner $ verbose_index = \ index( \ "flag", "-v", \ "long-flag", "--verbose", \ "description", "verbose output", \ "takes", false, \ "default", false \ ) / 2) A list of all of the arguments is created to be used by parseArgs() $ arg_info = list(verbose_index) / 3) the arguments variable is created from the command line arguments. Note: default is false for -v flag $ args = parseArgs(arguments, arg_info) / 4) now define a variable that is to be used in the program $ verbose_flag = args.get("-v") / here is an example of using the global variable we have turned on with the command line arguments ? verbose_flag > "verbose_flag has been turned on" } / If you want to know the arguments for a program, use the -? argument to get a printout
mscript has a useful set of built-in functions, but adding significant new functionality to the built-in functions is undesirable. Instead, most new functionality will be developed in separate dll integrations. mscript ships with these useful DLLs:
This DLL let's you work with timestamps, in particular last modified times of files. It exports a number of functions, notice the unique prefix of the function names: msts_build(year, month, day) or msts_build(year, month, day, hour, minute, second) - takes three date parameters or six date-time parameters, returning a string usable by the rest of the functions msts_add(timestamp string, part to add to string, and amount to add number) - adds a number of date units to a timestamp, returning the new timestamp - part to add can be "day", "hour", "minute", or "second" msts_diff(date1, data2, part) - returns the number of date units, date1 - date2 - part can be "day", "hour", "minute", or "second" msts_format(timestamp, format_string) - format_string using put_time syntax - you can use this to get parts of the timestamp msts_now() - get the current date-time, you can optionally pass in a bool to use UTC or local time - uses UTC by default msts_to_utc(timestamp) - convert a local date-time to UTC msts_to_local(timestamp) - convert a UTC date-time to local msts_last_modified(file_path) - when was a file last modified? msts_created(file_path) - when was a file created? msts_last_accessed(file_path) - when was a file last accessed? msts_touch(file_path, optional_timestamp) - touch a file, marking its last modified timestamp to be now or optionally a given timestamp To call these functions from your scripts, use a + statement, like so + "mscript-timestamp.dll" > "Now: " + msts_now()
This simple DLL makes working with the registry a breeze... msreg_create_key(key) - ensure that a registry key exists msreg_delete_key(key) - ensure that a registry key no longer exists - deltes the keys, its values, and all sub-keys and their values msreg_get_sub_keys(key) - get a list of the names of the sub-keys of a key - just the names of the sub-keys, not full keys msreg_put_settings(key, settings_index) - add settings to a key with the name-values in an index - you can send in number and string settings - to remove a setting, pass null as the index value msreg_get_settings(key) - get the settings on a key in a name-values index - you only get back REG_DWORD and REG_SZ values no, multi-strings or expanded-strings
This DLL is a simplified logging library based on cx_Logging: http://cx-logging.readthedocs.io/en/latest/ There is one global logging facility You call mslog_start() with the filename to use and the log level to start with, and with an optional index of logging parameters, such as the prefix for each log line You can call mslog_setlevel(log_level) to set what kinds of logging to write to the file. Log levels from least to most are "NONE", "ERROR", "INFO", "DEBUG" With logging set up, you call mslog_error(), mslog_info(), and mslog_debug() to write messages to the log All routines return bool success; errors are only raised for invalid parameters mslog_start(filename, log_level, options_index) - specify the log file path to use, and the initial log level - you can call mslog_setlevel() to set the level later on - options_index can contain the followings options: prefix - what to start each log line with maxFiles - how many numbered files to preserve maxFileSizeBytes - file size reached to start a new log file - see: online documentation for more information about these settings mslog_stop() - turn off logging to the file entirely mslog_setlevel(log_level) - set the logging level as a string, "NONE", "INFO", "ERROR", "DEBUG" mslog_getlevel() - get the log level back out as a string mslog_error(message) mslog_info(message) mslog_debug(message) - write a string message to the log
SQLite is a file-based database engine that allows powerful database querying in an easy-to-use package. mscript-db.dll includes SQLite functionality, making it possible to do SQL in a very clean and simple fashion:
msdb_sql_init(db_name, db_file_path) - specify a name for the database, db_name, and connect to the database at db_file_path, creating the database if it does not already exist - you use the database name in all other API functions msdb_sql_close(db_name) - close the database connection associated with the given db_name msdb_sql_exec(db_name, sql_query, optional_query_parameters_index) - issue any sort of SQL statement, including things like CREATE TABLE - returns a list of lists, with the first list containing the column names msdb_sql_rows_affected(db_name) - get the number of rows affected by the most recent SQL query, such as rows UPDATE'd or DELETE'd msdb_sql_last_inserted_id() - get the autonumber row ID of the most recent SQL INSERT query
4db is a file-based NoSQL database engine. You develop the database schema based on the data you add
There are no CREATE TABLE statements. Instead you call the msdb_4db_define() function specifying a table name, a primary key value, and an index of name-value columns to associate with the primary key. It is an UPSERT that defines schema as you go, adding new and updating existing data
Once you have loaded data into your database, you access the data using a simplified SQL SELECT statement, msdb_4db_query().
$ query_results = \ msdb_4db_query \ ( \ "SELECT value, column1, column2 \ FROM my_table \ WHERE column1 MATCHES @match AND column2 <= @max_val \ ORDER BY column2 DESC \ LIMIT 10", \ index("@match", "some thing not other", @max_val, 10) \ )
Extra built-in columns you can include in your query:
When you want to remove data from your database, you use msdb_4db_delete(). Just pass in the table name and primary key value, and it's gone
msdb_4db_init(ctxt_name, db_file_path) - associate a ctxt_name with a new 4db database, creating the database file if it does not exist msdb_4db_close(ctxt_name) - free the 4db context associated with the given ctxt_name msdb_4db_define(ctxt_name, table_name, primary_key_value, metadata_index) - create a row in the schema in table_name, with primary_key_value, and the columns and values in metadata_index - 4db only supports strings and numbers, so only pass those types of data in if you expect to get those types of data back out msdb_4db_undefine(ctxt_name, table_name, primary_key_value, metadata_name) - erase from table_name with primary_key_value the metadata value for the given metadata_name msdb_4db_query(ctxt_name, sql_query, parameters_index) - issue a simple subset of SQL using sql_query using parameters from parameters_index - hands back a list of lists, with the first list having the column names msdb_4db_delete(ctxt_name, table_name, primary_key_value) - erase a row from table_name with primary_key_value msdb_4db_drop(ctxt_name, table_name) - drop table_name from the schema msdb_4db_get_schema(ctxt_name) - get an index mapping the name of each table in the schema to a list of the table's column names
This simple DLL makes working with HTTP a breeze... Example basic GET request: + "mscript-http.dll" $ in_idx = \ index \ ( "server", "mscript.io", \ "path", "/", \ "outputfile", "mscript.html" \ ) $ out_idx = mshttp_process_request(in_idx) $ html = readFile("mscript.html", "utf-8") > out_idx.get("statuscode") The input index can have the following fields: server: string usetls: bool = true, set to true if port is set to 443 port: number = usetls ? 443 : 80 verb: string = GET path: string headers: index, optional inputfile: string, optional outputfile: string, optional The output index has the following fields: statuscode: number headers: index
You can write DLLs that export functions that you call from mscripts, just like built-in functions or your own mscript functions To develop an mscript DLL... Use Visual Studio 2022 Clone the mscript solution Clone nlohmann's JSON library and put it alongside the mscript solution's directory; not inside it, next to it Do the same for 4db library on GitHub Get the contents of the SQLite ZIP file and place the contents of the folder inside the ZIP, some two .c and two .h files, and put those four files in a folder called sqlite alongside the mscript solution's directory, just like 4db and the JSON library So your directory structure should look like: 4db JSON mscript sqlite Get the mscript solution to build and get the unit tests to pass To understand dll integration, it's best to look at the mscript-dll-sample project // pch.h #pragma once #include "../mscript-core/module.h" #pragma comment(lib, "mscript-core") That alone brings in everything you need for doing mscript DLL work Then you write a dllinterface.cpp file to implement your DLL // dllinterface.cpp #include "pch.h" using namespace mscript; // You implement mscript_GetExports to specify which functions you will be exporting // Your function names have to be globally unique, and can't have dots, so use underscores and make it unique...and lowercase! wchar_t* __cdecl mscript_GetExports() { std::vector<std::wstring> exports { L"ms_sample_sum", L"ms_sample_cat" }; return module_utils::getExports(exports); } // You need to provide a memory freeing function for strings that your DLL allocates void mscript_FreeString(wchar_t* str) { delete[] str; } // Here's the big one. You get a function name, and JSON for a list of parameters, // and you return JSON of an object wchar_t* mscript_ExecuteFunction(const wchar_t* functionName, const wchar_t* parametersJson) { try { std::wstring funcName = functionName; if (funcName == L"ms_sample_sum") { double retVal = 0.0; for (double numVal : module_utils::getNumberParams(parametersJson)) retVal += numVal; return module_utils::jsonStr(retVal); } else if (funcName == L"ms_sample_cat") { std::wstring retVal; for (double numVal : module_utils::getNumberParams(parametersJson)) retVal += num2wstr(numVal); return module_utils::jsonStr(retVal); } else raiseWError(L"Unknown mscript-dll-sample function: " + funcName); } catch (const user_exception& exp) { return module_utils::errorStr(functionName, exp); } catch (const std::exception& exp) { return module_utils::errorStr(functionName, exp); } catch (...) { return nullptr; } } // module_utils implements useful routines for command object / JSON operations // module_utils::getNumberParams() keeps this code pristine, returning vector<double> // Use module_utils::getParams() instead for more general parameter handling // module_utils::jsonStr(retVal) turns any object into a JSON wchar_t* to return to mscript // module_utils::errorStr(functionName, exp) gives you consolidated error handling, // returning an error message JSON wchar_t* that mscript expects You can tread far off this beaten path Process the parameter list JSON and return JSON that maps to an mscript value That's all that's assumed Once you've created your own DLL, in mscript code you import it with the same + statement as importing mscripts DLLs are searched for in the folder the mscript EXE resides in and not from anywhere else
installer: mscript3.msi - standalone EXE: mscript3.exe (TERMS OF USE)
Write to contact@mscript.io with questions and comments - copyright © 2022-2024 - Michael Balloni & Rodney Ludwig