Salome HOME
updated copyright message
[modules/kernel.git] / bin / salomeContextUtils.py.in
index 3f517dcf73b3c154d03c8d0dc99170cce3e16ac0..fee74ff042e5eb5999d50c619463afb27154e2e7 100644 (file)
@@ -1,6 +1,4 @@
-#! /usr/bin/env python
-
-# Copyright (C) 2013-2014  CEA/DEN, EDF R&D, OPEN CASCADE
+# Copyright (C) 2013-2023  CEA, EDF, OPEN CASCADE
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -24,6 +22,8 @@ import sys
 import glob
 import subprocess
 import re
+import socket
+import json
 
 """
 Define a specific exception class to manage exceptions related to SalomeContext
@@ -38,12 +38,6 @@ def __listDirectory(path):
     cfgFiles = glob.glob(os.path.join(root,'*.cfg'))
     allFiles += cfgFiles
 
-    shFiles = glob.glob(os.path.join(root,'*.sh'))
-    for f in shFiles:
-      no_ext = os.path.splitext(f)[0]
-      if not os.path.isfile(no_ext+".cfg"):
-        allFiles.append(f)
-
   return allFiles
 #
 
@@ -59,18 +53,14 @@ def __getConfigFileNamesDefault():
   return __listDirectory(envdDir)
 #
 
-def getConfigFileNames(args, checkExistence=False):
-  # special case: configuration files are provided by user
-  # Search for command-line argument(s) --config=file1,file2,..., filen
-  # Search for command-line argument(s) --config=dir1,dir2,..., dirn
-  configOptionPrefix = "--config="
-  configArgs = [ str(x) for x in args if str(x).startswith(configOptionPrefix) ]
+def __getEnvironmentFileNames(args, optionPrefix, checkExistence):
+  # special case: extra configuration/environment files are provided by user
+  # Search for command-line argument(s) <optionPrefix>=file1,file2,..., filen
+  # Search for command-line argument(s) <optionPrefix>=dir1,dir2,..., dirn
+  configArgs = [ str(x) for x in args if str(x).startswith(optionPrefix) ]
 
-  if len(configArgs) == 0:
-    return __getConfigFileNamesDefault(), args, []
-
-  args = [ x for x in args if not x.startswith(configOptionPrefix) ]
-  allLists = [ x.replace(configOptionPrefix, '') for x in configArgs ]
+  args = [ x for x in args if not x.startswith(optionPrefix) ]
+  allLists = [ x.replace(optionPrefix, '') for x in configArgs ]
 
   configFileNames = []
   unexisting = []
@@ -89,7 +79,23 @@ def getConfigFileNames(args, checkExistence=False):
   return configFileNames, args, unexisting
 #
 
+def getConfigFileNames(args, checkExistence=False):
+  configOptionPrefix = "--config="
+  configArgs = [ str(x) for x in args if str(x).startswith(configOptionPrefix) ]
+  if len(configArgs) == 0:
+    configFileNames, unexist = __getConfigFileNamesDefault(), []
+  else:
+    # get configuration filenames
+    configFileNames, args, unexist = __getEnvironmentFileNames(args, configOptionPrefix, checkExistence)
+
+  return configFileNames, args, unexist
+#
+
 def __getScriptPath(scriptName, searchPathList):
+  scriptName = os.path.expanduser(scriptName)
+  if os.path.isabs(scriptName):
+    return scriptName
+
   if searchPathList is None or len(searchPathList) == 0:
     return None
 
@@ -101,8 +107,54 @@ def __getScriptPath(scriptName, searchPathList):
   return None
 #
 
-# Return an array of dictionaries {script_name: [list_of_its_args]}
-def getScriptsAndArgs(args=[], searchPathList=None):
+class ScriptAndArgs:
+  # script: the command to be run, e.g. python <script.py>
+  # args: its input parameters
+  # out: its output parameters
+  def __init__(self, script=None, args=None, out=None):
+    self.script = script
+    self.args = args
+    self.out = out
+  #
+  def __repr__(self):
+    msg = "\n# Script: %s\n"%self.script
+    msg += "     * Input: %s\n"%self.args
+    msg += "     * Output: %s\n"%self.out
+    return msg
+  #
+#
+class ScriptAndArgsObjectEncoder(json.JSONEncoder):
+  def default(self, obj):
+    if isinstance(obj, ScriptAndArgs):
+      # to be easily parsed in GUI module (SalomeApp_Application)
+      # Do not export output arguments
+      return {obj.script:obj.args or []}
+    else:
+      return json.JSONEncoder.default(self, obj)
+#
+
+def getShortAndExtraArgs(args=None):
+  if args is None:
+    args = []
+  try:
+    pos = args.index("--") # raise a ValueError if not found
+    short_args = args[:pos]
+    extra_args = args[pos:] # include "--"
+  except ValueError:
+    short_args = args
+    extra_args = []
+    pass
+
+  return short_args, extra_args
+#
+
+# Return an array of ScriptAndArgs objects
+def getScriptsAndArgs(args=None, searchPathList=None):
+  if args is None:
+    args = []
+  short_args, extra_args = getShortAndExtraArgs(args)
+  args = short_args
+
   if searchPathList is None:
     searchPathList = sys.path
 
@@ -110,94 +162,162 @@ def getScriptsAndArgs(args=[], searchPathList=None):
   scriptArgs = []
   currentKey = None
   argsPrefix = "args:"
+  outPrefix = "out:"
   callPython = False
+  afterArgs = False
   currentScript = None
 
   for i in range(len(args)):
-    elt = args[i]
+    elt = os.path.expanduser(args[i])
+    isDriver = (elt == "driver") # special case for YACS scheme execution
 
     if elt.startswith(argsPrefix):
       if not currentKey or callPython:
         raise SalomeContextException("args list must follow corresponding script file in command line.")
       elt = elt.replace(argsPrefix, '')
-      scriptArgs[len(scriptArgs)-1][currentKey] = elt.split(",")
+      # Special process if some items of 'args:' list are themselves lists
+      # Note that an item can be a list, but not a list of lists...
+      # So we can have something like this:
+      # myscript.py args:['file1','file2'],val1,"done",[1,2,3],[True,False],"ok"
+      # With such a call, an elt variable contains the string representing ['file1','file2'],val1,"done",[1,2,3],[True,False],"ok" that is '[file1,file2],val1,done,[1,2,3],[True,False],ok'
+      # We have to split elt to obtain: ['[file1,file2]','val1','done','[1,2,3]','[True,False]','ok']
+      contains_list = re.findall('(\[[^\]]*\])', elt)
+      if contains_list:
+        extracted_args = []
+        x = elt.split(",")
+        # x is ['[file1', 'file2]', 'val1', 'done', '[1', '2', '3]', '[True', 'False]', 'ok']
+        list_begin_indices = [i for i in range(len(x)) if x[i].startswith('[')] # [0, 4, 7]
+        list_end_indices = [i for i in range(len(x)) if x[i].endswith(']')] # [1, 6, 8]
+        start = 0
+        for lbeg, lend in zip(list_begin_indices,list_end_indices): # [(0, 1), (4, 6), (7, 8)]
+          if lbeg > start:
+            extracted_args += x[start:lbeg]
+            pass
+          extracted_args += [','.join(x[lbeg:lend+1])]
+          start = lend+1
+          pass
+        if start < len(x):
+          extracted_args += x[start:len(x)]
+          pass
+        scriptArgs[len(scriptArgs)-1].args = extracted_args
+        pass
+      else: # a single split is enough
+        scriptArgs[len(scriptArgs)-1].args = [os.path.expanduser(x) for x in elt.split(",")]
+        pass
       currentKey = None
       callPython = False
+      afterArgs = True
+    elif elt.startswith(outPrefix):
+      if (not currentKey and not afterArgs) or callPython:
+        raise SalomeContextException("out list must follow both corresponding script file and its args in command line.")
+      elt = elt.replace(outPrefix, '')
+      scriptArgs[len(scriptArgs)-1].out = [os.path.expanduser(x) for x in elt.split(",")]
+      currentKey = None
+      callPython = False
+      afterArgs = False
     elif elt.startswith("python"):
       callPython = True
+      afterArgs = False
     else:
+      file_extension = os.path.splitext(elt)[-1]
       if not os.path.isfile(elt) and not os.path.isfile(elt+".py"):
         eltInSearchPath = __getScriptPath(elt, searchPathList)
         if eltInSearchPath is None or (not os.path.isfile(eltInSearchPath) and not os.path.isfile(eltInSearchPath+".py")):
-          if elt[-3:] == ".py":
+          if file_extension == ".py":
             raise SalomeContextException("Script not found: %s"%elt)
+          scriptArgs.append(ScriptAndArgs(script=elt))
           continue
         elt = eltInSearchPath
 
-      if elt[-4:] != ".hdf":
-        if elt[-3:] == ".py":
+      if file_extension != ".hdf":
+        if file_extension == ".py" or isDriver:
           currentScript = os.path.abspath(elt)
         elif os.path.isfile(elt+".py"):
           currentScript = os.path.abspath(elt+".py")
         else:
           currentScript = os.path.abspath(elt) # python script not necessary has .py extension
         pass
+
       if currentScript and callPython:
         currentKey = "@PYTHONBIN@ "+currentScript
-        scriptArgs.append({currentKey:[]})
+        scriptArgs.append(ScriptAndArgs(script=currentKey))
         callPython = False
       elif currentScript:
-        if not os.access(currentScript, os.X_OK):
+        script_extension = os.path.splitext(currentScript)[-1]
+        if isDriver:
+          currentKey = currentScript
+          scriptArgs.append(ScriptAndArgs(script=currentKey))
+          callPython = False
+        elif not os.access(currentScript, os.X_OK):
           currentKey = "@PYTHONBIN@ "+currentScript
-          scriptArgs.append({currentKey:[]})
+          scriptArgs.append(ScriptAndArgs(script=currentKey))
         else:
           ispython = False
           try:
             fn = open(currentScript)
-            for i in xrange(10): # read only 10 first lines
+            for i in range(10): # read only 10 first lines
               ln = fn.readline()
               if re.search("#!.*python"):
                 ispython = True
                 break
               pass
-            fn.close()
-          except:
+          except Exception:
             pass
-          if not ispython and currentScript[-3:] == ".py":
+          finally:
+            fn.close()
+          if not ispython and script_extension == ".py":
             currentKey = "@PYTHONBIN@ "+currentScript
           else:
             currentKey = currentScript
             pass
-          scriptArgs.append({currentKey:[]})
+          scriptArgs.append(ScriptAndArgs(script=currentKey))
+      # CLOSE elif currentScript
+      afterArgs = False
   # end for loop
+
+  if len(extra_args) > 1: # syntax: -- program [options] [arguments]
+    command = extra_args[1]
+    command_args = extra_args[2:]
+    scriptArgs.append(ScriptAndArgs(script=command, args=command_args))
+    pass
+
   return scriptArgs
 #
 
 # Formatting scripts and args as a Bash-like command-line:
 # script1.py [args] ; script2.py [args] ; ...
-def formatScriptsAndArgs(scriptArgs=[]):
+# scriptArgs is a list of ScriptAndArgs objects; their output parameters are omitted
+def formatScriptsAndArgs(scriptArgs=None, escapeSpaces=False):
+    if scriptArgs is None:
+      return ""
     commands = []
-    for sc_dict in scriptArgs:
-      for script, sc_args in sc_dict.items(): # single entry
-        cmd = script
-        if sc_args:
-          cmd = cmd + " " + " ".join(sc_args)
-        commands.append(cmd)
+    for sa_obj in scriptArgs:
+      cmd = sa_obj.script
+      if escapeSpaces and cmd:
+        if sys.platform == "win32":
+          cmd = cmd.replace(' ', ' "', 1)
+          cmd = cmd + '"'
+        else:
+          cmd = cmd.replace(' ', '\ ').replace('\ ', ' ', 1)
+      if sa_obj.args:
+        cmd = " ".join([cmd]+sa_obj.args)
+      commands.append(cmd)
+
     sep = " ; "
     if sys.platform == "win32":
-      sep= " & "
+      sep = " & "
     command = sep.join(["%s"%x for x in commands])
     return command
 #
 
-# Ensure OMNIORB_USER_PATH is defined. This variable refers to a the folder in which
+# Ensure OMNIORB_USER_PATH is defined. This variable refers to a folder in which
 # SALOME will write omniOrb configuration files.
 # If OMNIORB_USER_PATH is already set, only checks write access to associated directory ;
 # an exception is raised if check fails. It allows users for choosing a specific folder.
 # Else the function sets OMNIORB_USER_PATH this way:
-# - If APPLI environment variable is set, OMNIORB_USER_PATH is set to ${APPLI}/USERS.
-#   The function does not check USERS folder existence or write access. This folder
-#   must exist ; this is the case if SALOME virtual application has been created using
+# - If APPLI environment variable is set, and if ${APPLI}/USERS points at an existing 
+#   folder with write access, then OMNIORB_USER_PATH is set to ${APPLI}/USERS.
+#   This is the case if SALOME virtual application has been created using
 #   appli_gen.py script.
 # - Else OMNIORB_USER_PATH is set to user home directory.
 def setOmniOrbUserPath():
@@ -207,11 +327,27 @@ def setOmniOrbUserPath():
       raise Exception("Unable to get write access to directory: %s"%omniorbUserPath)
     pass
   else:
-    homePath = os.path.realpath(os.path.expanduser('~'))
-    #defaultOmniorbUserPath = os.path.join(homePath, ".salomeConfig/USERS")
-    defaultOmniorbUserPath = homePath
-    if os.getenv("APPLI"):
-      defaultOmniorbUserPath = os.path.join(homePath, os.getenv("APPLI"), "USERS")
-      pass
-    os.environ["OMNIORB_USER_PATH"] = defaultOmniorbUserPath
+    # Must be in /tmp (or equivalent) to handle application concurrency
+    try:
+      import tempfile
+      temp = tempfile.NamedTemporaryFile()
+      temp_dir = os.path.dirname(temp.name)
+      temp.close()
+      if not os.access(temp_dir, os.W_OK):
+        raise Exception("Unable to get write access to directory: %s"%temp_dir)
+      os.environ["OMNIORB_USER_PATH"] = temp_dir
+    except Exception:
+      homePath = os.path.realpath(os.path.expanduser('~'))
+      #defaultOmniorbUserPath = os.path.join(homePath, ".salomeConfig/USERS")
+      defaultOmniorbUserPath = homePath
+      if os.getenv("APPLI"):
+        appli_users_path=os.path.join(homePath, os.getenv("APPLI"), "USERS")
+        if os.access(appli_users_path, os.W_OK):
+          defaultOmniorbUserPath = appli_users_path
+        pass
+      os.environ["OMNIORB_USER_PATH"] = defaultOmniorbUserPath
+#
+
+def getHostname():
+  return socket.gethostname().split('.')[0]
 #