Is there a way to use multiple code files in dm-script to structure the code? Something like:
import "path/to/utility_functions.s";
utility_functions.do_something_general();
Note that I do not want to have the code as a menu item if possible. The code contains only functions that I use in the main script.
I tried the following:
File 1: test.s
void test(){
result("test\n");
}
File 2: require-test.s
AddScriptFileToPackage("path/to/test.s", "test", 3, "test-function", "", "", 1);
ExecuteScriptString("test()"); // works immediately but feels wrong
test(); // works after restart
Now I have the following problems:
I have to restart DigitalMicrograph after executing this script, otherwise test() does not work (ExecuteScriptString("test()"); works but it feels wrong to use strings for invoking code, if possible I'd like to avoid that)
When I restart DigitalMicrograph another time AddScriptFileToPackage() sais 'The script cannot be added because the package exists and is read-only. [...]'. Is there a way around it or do I have to use try blocks?
I feel like I am not doing this wrong at some place.
DM script does not support on-demand-loading of packages, but there are two different ways to achieve what you want:
Using library packages
When you "install" a script, you can choose to either install it as menu-command or as a library. This is true for both installing scripts via the menu command (which get stored in the global preferences file) or via the scripting-command (which can be used to
create .gtk files which one can then add/remove from the plugins
folder as needed).
The "menu" option will install a script such that it is invoked once via the menu-item but does not stay in memory otherwise.
The "library" option will execute a script once on startup and keep the script itself in scope. So you can define methods (or classes) in a library file and have it generally available. And you can put some executable code in a library if you need some startup-action.
Using script libraries as .gtk plugins is possibly the recommended way to achieve what you want. They are, however, always loaded.
Piece of advise: If you make libraries ensure you use very unique class and method names to avoid any conflict. I would recommend pre-fixing all class/method names with some library-name, i.e. use MyLib_MyClass instead of MyClass and the like.
Clarification: Scripts added as library packages are permanently added to the software, i.e. those packages get created once and are then placed in the plugins-folder. They will always load on startup of DM and be available. The Library package method is not suitable for temporarily 'loading' external scripts. Such 'on demand import' is not supported by DM-scripting.
The script commands to create packages are utility commands to help one create packages in an easy and manageable way. Typically, one would create a "Create package XY" script with several such commands adding all scripts from a location into a package. The script would be called once to create the package-file (It is already in the plugins folder afterwards.)
Only when the included scripts change and the package therefore requires to be updated, is the create-package script called again. Note, that in this case it is first required to remove the package-file from the plugins folder and start DigitalMicrograph without loading it, so that a new package is created. Otherwise the script would append to the package, which would not be possible if methods of the same name already exist in the package.
The F1 help documentation has an example script:
A typical examples, using GMS 3.4.0:
Script stored at: C:\Tmp\testLib.s
void TestCall()
{
Result("\nTest")
}
Script stored at: C:\Tmp\menuAction.s
Result("\nPerforming an action here.")
One-time run script to install a package:
// General package parameters
// *********************************************
string pkNa = "myPkg" // Filename of plugin
number pkLe = 3 // level 3 (.gtk) only needed for load order
string pkLo = "user_plugin" // plugin location
string scriptRoot = "C:\\Temp\\"
// List of Scripts to be installed as menu items
// *********************************************
// Each entry needs a (unique) command-name, a menu-name and an optional sub-menu name.
// The "isLibary" flag is set to 0
// It is possible to add the same script multiple times. The script will be executed when the menu item
// is chosen. Methods and Classes of the script are not available otherwise
// A separator can be added by installing and empty script with a (unique) command name starting with "-"
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 1", "MyMenu", "MySubMenu", 0 )
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 2", "MyMenu", "", 0 )
AddScriptToPackage( "", pkNa, pkLe, pkLo, "-sep1", "MyMenu", "", 0 )
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 3", "MyMenu", "", 0 )
// List of Scripts to be installed as library
// *********************************************
// Each entry needs a (unique) command-name. Menu-name and sub-menu name are "".
// The "isLibary" flag is set to 1
// The script will be executed once on startup (if there is executable code). It is also executed once
// here during the install.
// Methods and Classes of the script are permanently available and need unique names.
// Adding a script to the package as libary can be used to create on-load-version info output.
AddScriptFileToPackage( scriptRoot + "testLib.s", pkNa, pkLe, pkLo, "library-1", "", "", 1 )
AddScriptToPackage( "Result(\"Script packages myPkg loaded.\\n\")", pkNa, pkLe, pkLo, "myPkg-versionInfo", "", "", 1 )
After running the install-script there will be:
A menu like this:
Output in the results window like this:
A package file in the folder C:\Users\USERNAME\AppData\Local\Gatan\Plugins\myPkg.gtk
The script command TestCall() generally available in all scripts.
The package will load each time when DM starts as long as the .gtk file remains in the plugins folder.
Calling script code from within scripts
The scripting language supports two commands to call a script from within a script:
Number ExecuteScriptString( String text )
Number ExecuteScriptFile( String file_path )
Using the command to execute scripts form disc can do what you want, but maintaining a useful 'library' that way could be tedious. It also does not allow you to install classes.
Example of calling a script from within a script:
// Direct example
void Demo()
{
ClearResults()
Result( "I am a test call.\n")
number n = 5
Result( "I am working on the number: " + n )
}
Demo()
//Having the script as a string
number otherNumber = 11 // To show how you can modify a script call as an example
string scriptStr
scriptStr += "void Demo()\n{" + "\n"
scriptStr += "ClearResults()" + "\n"
scriptStr += "Result( \"I am a test call.\\n\")" + "\n"
scriptStr += "number n = " + otherNumber + "\n"
scriptStr += "Result( \"I am working on the number: \" + n )"+ "\n"
scriptStr += "}\n"
scriptStr += "Demo()\n"
If ( TwoButtonDialog("Script-call","Show it", "Run it") )
{
ClearResults()
Result( scriptStr )
}
else
ExecuteScriptString( scriptStr )
The following explicit example of build script usage may be closer to what you are looking for. It shows that in the course of a single DM session, one can edit the module source files and repeatedly rebuild the package without having to relaunch DM, contrary to the clarification about package creation provided in the answer from BmyGuest. This example also makes use of the very convenient GetCurrentScriptSourceFilePath function which greatly simplifies file path references when one can locate the build script and module source files in the same folder (this is the approach I take with my own development projects).
Here is the arrangement of my files for this example:
The two source modules are very simple function and class libraries.
Here is Module1:
void Module1SayHello()
{
OKDialog("Hello from module 1");
}
And here is Module2:
class Module2TestClass
{
void Module2SayHello(Object self)
{
OKDialog("Hello from module 2");
}
}
Here is the build script:
void main()
{
// Establish the source code directory relative to the current build script location
String buildScriptSourceFilePath;
GetCurrentScriptSourceFilePath(buildScriptSourceFilePath);
String sourceFileDir = buildScriptSourceFilePath.PathExtractDirectory(0);
// Add the modules
AddScriptFileToPackage(sourceFileDir.PathConcatenate("Module1.s"), "MultiModuleTest", 3, "Module1", "", "", 1);
AddScriptFileToPackage(sourceFileDir.PathConcatenate("Module2.s"), "MultiModuleTest", 3, "Module2", "", "", 1);
}
main();
Contrary to the above-mentioned clarification, this build script can be run multiple times during a DM session and the content of the package file gets replaced each time. So now one has a very nice development environment where one can open the source file for a module, edit it as desired, save it, and then rebuild the package file. One can use the following test script to see that the behavior changes as one edits, saves, and rebuilds the implementation of any function or method in the module source files:
void main()
{
Module1SayHello();
Alloc(Module2TestClass).Module2SayHello();
}
main();
Because of the way the DM script interpreter parses, tokenizes, and executes code, all functions and methods invoked anywhere in a script must have been previously defined before a script is executed. This is why the above test script, or any other script that uses the added modules, cannot simply be appended to the end of the build script (except if embedded in a string passed to the ExecuteScriptString function, as pointed out in the posed question). The concept of imported code modules (e.g. as in Python) is therefore not really possible in DM scripting (as pointed out in a comment to the answer by BmyGuest). In this sense, DM scripting shows its roots in 1990’s coding concepts, which commonly involved separate compilation, linking, and execution phases.
Nevertheless, the build script approach described here allows one to take advantage of the features of a true integrated development environment (IDE). For example, one can add the module source files (and build script) to a project in Visual Studio and get all the benefits of a modern multi-file code editor and revision control (e.g. via Git). This is what I do with the Enabler framework.
The one caveat is that once the DM session is closed, the plug-in (package) file does become finalized in some way so that it can no longer be replaced by the build script in a future DM session. In this case, one does have to remove the package file from the plug-ins folder before resuming another development session in DM (as covered in the clarification from BmyGuest).
For everybody else who needs this, I am using AddScriptFileToPackage() now, inspired by both, #BmyGuest and #MikeKundmann.
The following main.s is always open in my GMS. The real code I'm working on is in program.s. To test your code execute the main.s. This file can be executed multiple times in one session!
For opening GMS I use the (Windows) batch file below. This deleteds registered plugins automatically which makes the main.s usable again. For debugging I created a python script that combines all the files listed in the main.s. This way GMS jumps to the errors. This python program can be downloaded from my github page.
/**
* File: main.s
*/
String __file__;
GetCurrentScriptSourceFilePath(__file__);
String __base__ = __file__.PathExtractDirectory(0);
/**
* Load and add the file `filename`, the name will be the `filename` without
* the extension.
*
* This is dynamic only for the current session. If GMS is restarted, using
* this will create errors except if the plugins folder does not contain the
* required files (delete `%LOCALAPPDATA%\Gatan\Plugins\` before starting).
*
* #param filename The filename (or path) relative to the path of this file
* #param name The internal name to register the script with
*/
void require(String filename, String name){
// AddScriptFileToPackage(
// <file_path>,
// <packageName: filename of .gtk file in plugins>,
// <packageLevel: load order [0..3]>,
// <command_name: id/name of the libary/command>,
// <menu_name: name of the menu, ignored if isLibrary=1>
// <sub_menu_name: name of the submenu, ignored if isLibrary=1>,
// <isLibrary: wheter to add as library (1) or as menu item (0)>
// )
AddScriptFileToPackage(__base__.PathConcatenate(filename), "__require_main_" + name, 3, name, "", "", 1);
}
/**
* Require the file `filename` with the basename of the `filename` as the name.
*
* #see require(String filename, String name);
*
* #param filename The filename (or path) relative to the path of this file
*/
void require(String filename){
require(filename, PathExtractBaseName(filename, 0));
}
void main(){
// add libaries
require("string-lib.s");
// add main file
require("program.s");
}
main();
The (Windows) batch file to start GMS. This deletes the plugins folder automatically. Then the main.s does not cause any problems.
#echo off
rem
rem File: start-gatan.bat
rem ---------------------
echo Deleting GMS cached libaries...
SET plugins_path=%LOCALAPPDATA%\Gatan\Plugins\
SET gms_path=%PROGRAMFILES%\Gatan\DigitalMicrograph.exe
if exist %plugins_path% (
echo Deleting all .gtk files in %plugins_path%...
del %plugins_path%__require_main_*.gtk /F /Q
del %plugins_path%__require_main_*.gt1 /F /Q
del %plugins_path%__require_main_*.gt2 /F /Q
del %plugins_path%__require_main_*.gt3 /F /Q
if exist "%gms_path%" (
echo Starting GMS
start "" "%gms_path%"
) else (
echo GMS path %gms_path% does not exist.
pause
)
) else (
echo Plugins path %plugins_path% does not exist.
pause
)
In a project we have a table with the following fields: FolderPath, FileType, DaysToKeep, ServerIP.
When the utility is executed, I read the rs from the table then want to access the [ServerIP]\[FolderPath] to see of the delta of Now() and the first file's (or a file's) last save date is greater than DaysToKeep.
I have most of the pseudo-code done and am confident in being able to do this on a local server using the FileSystemObject (importing Microsoft Scripting Runtime).
rsDirectoryList.MoveFirst
Do While Not rsDirectoryList.EOF
' Fields: FolderPath, FileType, DaysToKeep, ServerIP
Debug.Print "Values: " & rsDirectory.Fields("FolderPath").Value
' get directory contents from [ServerIP]:[FolderPath]
'fileSpec = rsDirectory.Fields("ServerIP") & rsDirectory.Fields("FolderPath")
Set f = fso.GetFile(fileSpec)
Debug.Print "Last Modified: " & f.DateLastModified & vbNewLine
' Get the date of the first file from [ServerIP]\[FolderPath]
' If Now() - FileDate in days > DaysToKeep, purge directory
'If DateDiff("d", Now(), f.DateLastModified) > rsDirectory.Fields("DaysToKeep").Value Then
' ' Delete files from specified directory
' Kill (serverIP \ FolderPath) ?????
'Else
' Debug.Print "Skipping: " & rsDirectory.Fields(0).Value & vbNewLine
'End If
rs1.MoveNext
Loop
I was thinking that I may have to create a share for each server on the utility's server and access them by drive letter, rather than by IP address.
Is there a way to do this with the given IP addresses?
I am using music21 with MuseScore in an ipython notebook. It works well, the only problem is that every time I create a Stream and then run my_stream.show(), it takes a forever because it waits to open the MuseScore application. This happens even if MuseScore is already open (it opens a second copy of the app, which then closes itself after the image is printed).
How can I prevent music21 from re-opening MuseScore each time and get it to use the already opened app instead?
EDIT: Adding version/OS info
I'm on a mac (OSX 10.10.4) using MuseScore version 2.1.0
I've also tried the method outlined here to print out sheet music in an ipython notebook but the same thing happened.
For the second method at least, the problem seems to be in music21/converter/subConverter.py.
Under
class ConverterMusicXML(SubConverter):
There's this section:
musescoreRun = '"' + musescorePath + '" ' + fp + " -o " + fpOut + " -T 0 "
if 'dpi' in keywords:
musescoreRun += " -r " + str(keywords['dpi'])
if common.runningUnderIPython():
musescoreRun += " -r " + str(defaults.ipythonImageDpi)
storedStrErr = sys.stderr
fileLikeOpen = six.StringIO()
sys.stderr = fileLikeOpen
os.system(musescoreRun)
fileLikeOpen.close()
sys.stderr = storedStrErr
I believe that this line in particular
os.system(musescoreRun)
is opening MuseScore independently each time, but I can't figure out what to replace it with that will allow music21 to find the already running instance of MuseScore.
Same problem errors. Here refers an issue on GitHub:
... changing os.system(musescoreRun) line 891 of subconverters.py by subprocess.run(musescoreRun). You need also to import subprocess at the start of subconverters.py.
Maybe it works for you!
I have nearly a hundred data sources in a WebSphere Application Server (WAS) and due to office relocation, the IP of the database servers have changed and I need to update the datasource IP addresses in my WAS too.
Considering it error-prone to update hundred IPs through admin console.
Is there any way that I can make the change by updating config files or running a script? My version of WAS is 7.0.
You should be able to use the WAS Admin Console's built-in "command assistance" to capture simple code snippets for listing datasources and changing them by just completing those operations in the UI once.
Take those snippets and create a new jython script to list and update all of them.
More info on command assistance:
http://www.ibm.com/developerworks/websphere/library/techarticles/0812_rhodes/0812_rhodes.html
wsadmin scripting library:
https://github.com/wsadminlib/wsadminlib
You can achieve this using wsadmin scripting. Covener has the right idea with using the admin console's command assistance to do the update once manually (to get the code) and then dump that into a script that you can automate.
The basic idea is that a datasource has a set of properties nested under it, one of which is the ip address. So you want to write a script that will query for the datasource, find its nested property set, and iterate over the property set looking for the 'ipAddress' property to update.
Here is a function that will update the value of the "ipAddress" property.
import sys
def updateDataSourceIP(dsName, newIP):
ds = AdminConfig.getid('/Server:myServer/JDBCProvider:myProvider/DataSource:' + dsName + '/')
propertySet = AdminConfig.showAttribute(ds, 'propertySet')
propertyList = AdminConfig.list('J2EEResourceProperty', propertySet).splitlines()
for prop in propertyList:
print AdminConfig.showAttribute(prop, 'name')
if (AdminConfig.showAttribute(prop, 'name') == 'ipAddress'):
AdminConfig.modify(prop, '[[value '" + newIP + "']]')
AdminConfig.save();
# Call the function using command line args
updateDataSourceIP(sys.argv[0], sys.argv[1])
To run this script you would invoke the following from the command line:
$WAS_HOME/bin/wsadmin.sh -lang jython -f /path/to/script.py myDataSource 127.0.0.1
**Disclaimer: untested script. I don't know the name of the "ipAddress" property off the top of my head, but if you run this once it will print out all the properties on your ds, so you can get it there
Some useful links:
Basics about jython scripting
Modifying config objects using wsadmin
As an improvement to aguibert's script, to avoid having to provide all 100 datasource names and to update it to correct the containment path of the configuration id, consider this script which will update all datasources, regardless of the scope at which they're defined. As always, backup your configuration before beginning and once you're satisified the script is working as expected, replace the AdminConfig.reset() with save(). Note, these scripts will likely not work properly if you're using connection URLs in your configuration.
import sys
def updateDataSourceIP(newIP):
datasources = AdminConfig.getid('/DataSource:/').splitlines()
for datasource in datasources:
propertySet = AdminConfig.showAttribute(datasource, 'propertySet')
propertyList = AdminConfig.list('J2EEResourceProperty', propertySet).splitlines()
for prop in propertyList:
if (AdminConfig.showAttribute(prop, 'name') == 'serverName'):
oldip = AdminConfig.showAttribute(prop, 'value')
print "Updating serverName attribute of datasource '" + datasource + "' from " + oldip + " to " + sys.argv[0]
AdminConfig.modify(prop, '[[value ' + newIP + ']]')
AdminConfig.reset();
# Call the function using command line arg
updateDataSourceIP(sys.argv[0])
The script should be invoked similarly to the above, but without the datasource parameter, the only parameter is the new hostname or ip address:
$WAS_HOME/bin/wsadmin.sh -lang jython -f /path/to/script.py 127.0.0.1
I have a database with two agents, well there are really more than two, but two that matter right now. One works, the other does not. Both have Uselsx '*lsxlc' defined in (Options).
I have commented out everything in the failing agent except
Dim s As New NotesSession
Dim db As NotesDatabase
Dim agentLog As NotesLog
Set db = s.CurrentDatabase
'agent log
Set agentLog = New NotesLog("Customers from Aging Report - AKM")
Call agentLog.OpenNotesLog( db.server, "agentinfo.nsf" )
agentLog.LogActions = True 'Set to True/False to turn on/off action logging
agentLog.LogErrors = True 'Set to True/False to turn on/off error logging
Call agentLog.LogAction("Start Agent: GetCustomerDataBasedOnAging")
On Error Goto throwError
Dim lcses As New LCSession
Dim src As New LCConnection(COutConn)
%REM
....
%END REM
Exit Sub
throwError:
'Error code
Dim epos As String
Dim emsg As String
Dim msg As String
Dim result As String
Dim status As Integer
Dim msgcode As Long
If lcses.status <> LCSUCCESS Then
status = lcses.GetStatus (result, msgcode, msg)
Call agentLog.LogError( msgcode,"Status for connector session is: " & Cstr(status) & Chr(10) & "Message: " & msg & " Code # " & Cstr(msgcode) & Chr(10) & "Connector Message: " & result )
emsg = "Customers from Aging Report' Agent: ("+Cstr(Erl)+") "& "[" &Cstr(Err) & "] [" & Error$ & "]"
Call agentLog.LogError( Err, emsg)
Else
emsg = "Customers from Aging Report' Agent: ("+Cstr(Erl)+") "& "[" &Cstr(Err) & "] [" & Error$ & "]"
Call agentLog.LogError( Err, emsg)
End If
Resume Next
COutConn is defined as a constant with value 'DB2'
I get the following error in the agent log:
Status for connector session is: 1544
Message: Code # 0
Connector Message: Error: Cannot find Connector 'DB2'
This happens whether I use the constant COutConn, or "DB2".
The strange thing is that the other agent with the same definitions works properly. I know DB2 exists on the machine, it is i5/OS v5r4. DB2 is built in on this operating system.
What else do I need to look for?
The answer is, be sure you know which machine the agent is running on. When you right click the agent in Domino Designer, and select Run, as I did, the agent is not running on the server that the database resides on, but rather inside the Domino Designer client. that is going to be Windows or Linux depending on your workstation.
So why did the one agent work while the other did not? Well the one that worked was activated from a button in the Notes Client, and the function attached to the button used Run on Server. The server was indeed IBM i. However, in the case of the failing agent, I executed that one from within Domino Designer as mentioned above, thus no DB2 connector.
Here's to hoping someone can learn from my pain!