]> SALOME platform Git repositories - tools/sat.git/commitdiff
Salome HOME
add src/versionMinorMajorPatch.py, not used
authorChristian Van Wambeke <christian.van-wambeke@cea.fr>
Fri, 19 Oct 2018 14:46:41 +0000 (16:46 +0200)
committerChristian Van Wambeke <christian.van-wambeke@cea.fr>
Fri, 19 Oct 2018 14:46:41 +0000 (16:46 +0200)
commands/config.py
commands/log.py
doc/src/conf.py
src/__init__.py
src/debug.py
src/environment.py
src/fileEnviron.py
src/logger.py
src/product.py
src/versionMinorMajorPatch.py [new file with mode: 0755]

index 4a216d2d6cc7d93f342442d818b654a8cf9d32ef..6cb7f9dbeecd323b8296f811fda8956b4e90279e 100644 (file)
@@ -43,7 +43,9 @@ parser.add_option('i', 'info', 'string', 'info',
 parser.add_option('l', 'list', 'boolean', 'list',
     _("Optional: list all available applications."))
 parser.add_option('', 'show_patchs', 'boolean', 'show_patchs',
-    _("Optional: synthetic view of all patches used in the application"))
+    _("Optional: synthetic list of all patches used in the application"))
+parser.add_option('', 'show_properties', 'boolean', 'show_properties',
+    _("Optional: synthetic list of all properties used in the application"))
 parser.add_option('c', 'copy', 'boolean', 'copy',
     _("""Optional: copy a config file to the personal config files directory.
 \tWARNING the included files are not copied.
@@ -721,27 +723,53 @@ def show_product_info(config, name, logger):
                                        False)
     zz.set_python_libdirs()
     zz.set_a_product(name, logger)
-        
+    logger.write("\n", 2)
+
+
 def show_patchs(config, logger):
-    '''Prints all the used patchs in the application.
-    
-    :param config Config: the global configuration.
-    :param logger Logger: The logger instance to use for the display
-    '''
-    len_max = max([len(p) for p in config.APPLICATION.products]) + 2
-    for product in config.APPLICATION.products:
-        product_info = src.product.get_product_config(config, product)
-        if src.product.product_has_patches(product_info):
-            logger.write("%s: " % product, 1)
-            logger.write(src.printcolors.printcInfo(
-                                            " " * (len_max - len(product) -2) +
-                                            "%s\n" % product_info.patches[0]),
-                         1)
-            if len(product_info.patches) > 1:
-                for patch in product_info.patches[1:]:
-                    logger.write(src.printcolors.printcInfo(len_max*" " +
-                                                            "%s\n" % patch), 1)
-            logger.write("\n", 1)
+  '''Prints all the used patchs in the application.
+
+  :param config Config: the global configuration.
+  :param logger Logger: The logger instance to use for the display
+  '''
+  oneOrMore = False
+  for product in sorted(config.APPLICATION.products):
+    product_info = src.product.get_product_config(config, product)
+    if src.product.product_has_patches(product_info):
+      oneOrMore = True
+      logger.write("%s:\n" % product, 1)
+      for i in product_info.patches:
+        logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
+  if oneOrMore:
+    logger.write("\n", 1)
+  else:
+    logger.write("No patchs found\n", 1)
+
+
+def show_properties(config, logger):
+  '''Prints all the used properties in the application.
+
+  :param config Config: the global configuration.
+  :param logger Logger: The logger instance to use for the display
+  '''
+  oneOrMore = False
+  for product in sorted(config.APPLICATION.products):
+    product_info = src.product.get_product_config(config, product)
+    done = False
+    try:
+      for i in product_info.properties:
+        if not done:
+          logger.write("%s:\n" % product, 1)
+          done = True
+        oneOrMore = True
+        logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
+    except:
+      # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
+      pass
+  if oneOrMore:
+    logger.write("\n", 1)
+  else:
+    logger.write("No properties found\n", 1)
 
 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
     '''Prints a value from the configuration. Prints recursively the values 
@@ -858,7 +886,7 @@ def run(args, runner, logger):
     if options.schema:
         get_config_children(runner.cfg, args)
         return
-    
+
     # case : print a value of the config
     if options.value:
         if options.value == ".":
@@ -882,7 +910,7 @@ def run(args, runner, logger):
 
     
     # case : edit user pyconf file or application file
-    elif options.edit:
+    if options.edit:
         editor = runner.cfg.USER.editor
         if ('APPLICATION' not in runner.cfg and
                        'open_application' not in runner.cfg): # edit user pyconf
@@ -901,20 +929,19 @@ def run(args, runner, logger):
                     break
     
     # case : give information about the product in parameter
-    elif options.info:
+    if options.info:
         src.check_config_has_application(runner.cfg)
         if options.info in runner.cfg.APPLICATION.products:
             show_product_info(runner.cfg, options.info, logger)
-            return
-        raise src.SatException(_("%(product_name)s is not a product "
-                                 "of %(application_name)s.") % 
-                               {'product_name' : options.info,
-                                'application_name' : 
-                                runner.cfg.VARS.application})
+            # return
+        else:
+          msg = _("%s is not a product of %s.") % \
+                (options.info, runner.cfg.VARS.application)
+          raise Exception(msg)
     
     # case : copy an existing <application>.pyconf 
     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
-    elif options.copy:
+    if options.copy:
         # product is required
         src.check_config_has_application( runner.cfg )
 
@@ -958,7 +985,7 @@ def run(args, runner, logger):
             logger.write(_("%s has been created.\n") % dest_file)
     
     # case : display all the available pyconf applications
-    elif options.list:
+    if options.list:
         lproduct = list()
         # search in all directories that can have pyconf applications
         for path in runner.cfg.PATHS.APPLICATIONPATH:
@@ -985,18 +1012,27 @@ def run(args, runner, logger):
                             logger.write("%s\n" % appliname)
                             
             logger.write("\n")
+
+    # case: print all the products name of the application (internal use for completion)
+    if options.completion:
+        for product_name in runner.cfg.APPLICATION.products.keys():
+            logger.write("%s\n" % product_name)
+        
     # case : give a synthetic view of all patches used in the application
-    elif options.show_patchs:
+    if options.show_patchs:
         src.check_config_has_application(runner.cfg)
         # Print some informations
-        logger.write(_('Show the patchs of application %s\n') % 
+        logger.write(_('Patchs of application %s\n') %
                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
         logger.write("\n", 2, False)
         show_patchs(runner.cfg, logger)
-    
-    # case: print all the products name of the application (internal use for completion)
-    elif options.completion:
-        for product_name in runner.cfg.APPLICATION.products.keys():
-            logger.write("%s\n" % product_name)
-        
-    
+
+    # case : give a synthetic view of all patches used in the application
+    if options.show_properties:
+        src.check_config_has_application(runner.cfg)
+        # Print some informations
+        logger.write(_('Properties of application %s\n') %
+                    src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
+        logger.write("\n", 2, False)
+        show_properties(runner.cfg, logger)
+
index d8def14303da1197ca3abf5f33a2a00c8714a310..8d2aa1c73224df9634af6b3a1233be3386308b42 100644 (file)
@@ -206,9 +206,9 @@ def run(args, runner, logger):
     # Parse the options
     (options, args) = parser.parse_args(args)
 
-    # get the log directory. 
+    # get the log directory.
     logDir = src.get_log_path(runner.cfg)
-    
+
     # Print a header
     nb_files_log_dir = len(glob.glob(os.path.join(logDir, "*")))
     info = [("log directory", logDir), 
@@ -263,24 +263,29 @@ def run(args, runner, logger):
     # copy the stylesheets in the log directory
     # OP We use copy instead of copy2 to update the creation date
     #    So we can clean the LOGS directories easily
-    shutil.copy(xslCommand, logDir)
-    shutil.copy(xslHat, logDir)
-    src.ensure_path_exists(os.path.join(logDir, "TEST"))
-    shutil.copy(xsltest, os.path.join(logDir, "TEST"))
-    shutil.copy(imgLogo, logDir)
+    try:
+      src.ensure_path_exists(logDir)
+      shutil.copy(xslCommand, logDir)
+      shutil.copy(xslHat, logDir)
+      src.ensure_path_exists(os.path.join(logDir, "TEST"))
+      shutil.copy(xsltest, os.path.join(logDir, "TEST"))
+      shutil.copy(imgLogo, logDir)
+    except:
+      # we are here  if an user make sat log in jenkins LOGS without write rights
+      # Make a warning and do nothing
+      logger.warning("problem for writing in directory '%s', may be not owner." % logDir)
 
     # If the last option is invoked, just, show the last log file
     if options.last_terminal:
         src.check_config_has_application(runner.cfg)
-        log_dirs = os.listdir(os.path.join(runner.cfg.APPLICATION.workdir,
-                                           'LOGS'))
+        log_dirs = os.listdir(os.path.join(runner.cfg.APPLICATION.workdir, 'LOGS'))
         show_last_logs(logger, runner.cfg, log_dirs)
         return 0
 
     # If the last option is invoked, just, show the last log file
     if options.last:
         lastLogFilePath = get_last_log_file(logDir,
-                                            notShownCommands + ["config"])        
+                                            notShownCommands + ["config"])
         if options.terminal:
             # Show the log corresponding to the selected command call
             print_log_command_in_terminal(lastLogFilePath, logger)
@@ -328,14 +333,22 @@ def run(args, runner, logger):
     # Create or update the hat xml that gives access to all the commands log files
     logger.write(_("Generating the hat log file (can be long) ... "), 3)
     xmlHatFilePath = os.path.join(logDir, 'hat.xml')
-    src.logger.update_hat_xml(logDir, 
+    try:
+      src.logger.update_hat_xml(logDir,
                               application = runner.cfg.VARS.application, 
                               notShownCommands = notShownCommands)
-    logger.write(src.printcolors.printc("OK"), 3)
+
+      logger.write(src.printcolors.printc("OK"), 3)
+    except:
+      logger.write(src.printcolors.printc("KO"), 3)
+      logger.write(" problem update hat.xml", 3)
+
     logger.write("\n", 3)
     
     # open the hat xml in the user editor
     if not options.no_browser:
-        logger.write(_("\nOpening the log file\n"), 3)
+        logger.write(_("\nOpening the hat log file %s\n" % xmlHatFilePath), 3)
         src.system.show_in_editor(runner.cfg.USER.browser, xmlHatFilePath, logger)
+    else:
+        logger.write("\nHat log File is %s\n" % xmlHatFilePath, 3)
     return 0
index 3016b642e4589cb2ac7eb743cdde88c19ba848ef..7888880a4c21bee2622f5f3bc8ce532581d55b7c 100644 (file)
@@ -16,9 +16,9 @@ import os
 
 # Append source folder to path in order to enable autodoc
 currentPath = os.path.dirname(__file__)
-print "sphinx on file", __file__
+print("sphinx on file %s" % __file__=
 dirAutodoc = os.path.realpath(os.path.join(currentPath, '..', '..'))
-print "autodoc on dir", dirAutodoc 
+print("autodoc on dir %s" % dirAutodoc)
 sys.path.append(dirAutodoc)
 sys.path.append(dirAutodoc + "/commands")
 
index c5535d573c4d9f5346dd111c43f6b600f12182d6..91dfc6617dfd971c509d29b6b89748e63c059197 100644 (file)
@@ -180,27 +180,26 @@ def get_log_path(config):
     return log_dir_path
 
 def get_salome_version(config):
+    import versionMinorMajorPatch as VMMP
+
     if hasattr(config.APPLICATION, 'version_salome'):
-        Version = config.APPLICATION.version_salome
+        version = VMMP.MinorMajorPatch(config.APPLICATION.version_salome)
     else:
-        KERNEL_info = product.get_product_config(config, "KERNEL")
-        VERSION = os.path.join(
-                            KERNEL_info.install_dir,
+        kernel_info = product.get_product_config(config, "KERNEL")
+        aFile = os.path.join(
+                            kernel_info.install_dir,
                             "bin",
                             "salome",
                             "VERSION")
-        if not os.path.isfile(VERSION):
+        if not os.path.isfile(aFile):
             return None
-            
-        fVERSION = open(VERSION)
-        Version = fVERSION.readline()
-        fVERSION.close()
-        
-    VersionSalome = int(only_numbers(Version))    
-    return VersionSalome
+        with open(aFile) as f:
+          line = f.readline()  # example: '[SALOME KERNEL] : 8.4.0'
+        version = VMMP.MinorMajorPatch(line.split(":")[1])
 
-def only_numbers(str_num):
-    return ''.join([nb for nb in str_num if nb in '0123456789'] or '0')
+    res = version.strCompact()
+    # print("get_salome_version %s -> %s" % (version, res))
+    return res
 
 def read_config_from_a_file(filePath):
         try:
index 13d7612b22c0f62e9b7026262d2d714dcba89614..8c24d5d90ee066f12be58ba6a89469c6f414b574 100755 (executable)
@@ -234,7 +234,7 @@ def _saveConfigRecursiveDbg(config, aStream, indent, path, nb):
     
     indstr = indent * ' ' # '':no indent, ' ':indent
     strType = str(type(config))
-    if debug: print "saveDbg Type", path, strType
+    if debug: print("saveDbg Type %s %s" % (path, strType))
     
     if "Sequence" in strType:
       for i in range(len(config)):
@@ -259,7 +259,7 @@ def _saveConfigRecursiveDbg(config, aStream, indent, path, nb):
     for key in sorted(data): #order): # data as sort alphabetical, order as initial order
       value = data[key]
       strType = str(type(value))
-      if debug: print 'strType', path, key, strType
+      if debug: print('strType %s %s %s' % (path, key, strType))
       if "Config" in strType:
         _saveConfigRecursiveDbg(value, aStream, indentp, path+"."+key, nbp)
         continue
index 1c89d260235cb65ff866657e31eec350577ad558..88a59590118c89c000f74b482292502a3c68ed5a 100644 (file)
@@ -25,6 +25,7 @@ import src
 import src.debug as DBG
 import pprint as PP
 
+
 class Environ:
     """\
     Class to manage the environment context
@@ -65,12 +66,16 @@ class Environ:
 
     def append_value(self, key, value, sep=os.pathsep):
         """\
-        append value to key using sep
-        
+        append value to key using sep,
+        if value contains ":" or ";" then raise error
+
         :param key str: the environment variable to append
         :param value str: the value to append to key
         :param sep str: the separator string
         """
+        for c in [";", ":"]: # windows or linux path separators
+          if c in value:
+            raise Exception("Environ append key '%s' value '%s' contains forbidden character '%s'" % (key, value, c))
         # check if the key is already in the environment
         if key in self.environ:
             value_list = self.environ[key].split(sep)
@@ -99,12 +104,17 @@ class Environ:
 
     def prepend_value(self, key, value, sep=os.pathsep):
         """\
-        prepend value to key using sep
+        prepend value to key using sep,
+        if value contains ":" or ";" then raise error
         
         :param key str: the environment variable to prepend
         :param value str: the value to prepend to key
         :param sep str: the separator string
         """
+        for c in [";", ":"]: # windows or linux path separators
+          if c in value:
+            raise Exception("Environ prepend key '%s' value '%s' contains forbidden character '%s'" % (key, value, c))
+        # check if the key is already in the environment
         if key in self.environ:
             value_list = self.environ[key].split(sep)
             if not value in value_list:
@@ -124,7 +134,7 @@ class Environ:
         :param sep str: the separator string
         """
         if isinstance(value, list):
-            for v in value:
+            for v in reversed(value): # prepend list, first item at last to stay first
                 self.prepend_value(key, v, sep)
         else:
             self.prepend_value(key, value, sep)
index dfcd1e5656c62f7ecd8993b6e3a3ba9b37c5b9e5..2fafeaf0e94bad8c981f65605c32e9d4e50537ca 100644 (file)
@@ -178,12 +178,16 @@ class FileEnviron(object):
 
     def append_value(self, key, value, sep=os.pathsep):
         """\
-        append value to key using sep
-        
+        append value to key using sep,
+        if value contains ":" or ";" then raise error
+
         :param key str: the environment variable to append
         :param value str: the value to append to key
         :param sep str: the separator string
         """
+        for c in [";", ":"]: # windows or linux path separators
+          if c in value:
+            raise Exception("FileEnviron append key '%s' value '%s' contains forbidden character '%s'" % (key, value, c))
         self.set(key, self.get(key) + sep + value)
         if (key, sep) not in self.toclean:
             self.toclean.append((key, sep))
@@ -197,18 +201,23 @@ class FileEnviron(object):
         :param sep str: the separator string
         """
         if isinstance(value, list):
-            self.append_value(key, sep.join(value), sep)
+            for v in value:
+                self.append_value(key, v, sep)
         else:
             self.append_value(key, value, sep)
 
     def prepend_value(self, key, value, sep=os.pathsep):
         """\
-        prepend value to key using sep
+        prepend value to key using sep,
+        if value contains ":" or ";" then raise error
         
         :param key str: the environment variable to prepend
         :param value str: the value to prepend to key
         :param sep str: the separator string
         """
+        for c in [";", ":"]: # windows or linux path separators
+          if c in value:
+            raise Exception("FileEnviron prepend key '%s' value '%s' contains forbidden character '%s'" % (key, value, c))
         self.set(key, value + sep + self.get(key))
         if (key, sep) not in self.toclean:
             self.toclean.append((key, sep))
@@ -222,7 +231,8 @@ class FileEnviron(object):
         :param sep str: the separator string
         """
         if isinstance(value, list):
-            self.prepend_value(key, sep.join(value), sep)
+            for v in reversed(value): # prepend list, first item at last to stay first
+                self.prepend_value(key, v, sep)
         else:
             self.prepend_value(key, value, sep)
 
@@ -536,12 +546,16 @@ class LauncherFileEnviron:
         self.output.write('# "WARNING %s"\n' % warning)
 
     def append_value(self, key, value, sep=":"):
-        """append value to key using sep
+        """append value to key using sep,
+        if value contains ":" or ";" then raise error
         
         :param key str: the environment variable to append
         :param value str: the value to append to key
         :param sep str: the separator string
         """
+        for c in [";", ":"]: # windows or linux path separators
+          if c in value:
+            raise Exception("LauncherFileEnviron append key '%s' value '%s' contains forbidden character '%s'" % (key, value, c))
         if self.is_defined(key) :
             self.add(key, value)
         else :
@@ -555,17 +569,22 @@ class LauncherFileEnviron:
         :param sep str: the separator string
         """
         if isinstance(value, list):
-            self.append_value(key, sep.join(value), sep)
+            for v in value:
+                self.append_value(key, v, sep)
         else:
             self.append_value(key, value, sep)
 
     def prepend_value(self, key, value, sep=":"):
-        """prepend value to key using sep
+        """prepend value to key using sep,
+        if value contains ":" or ";" then raise error
         
         :param key str: the environment variable to prepend
         :param value str: the value to prepend to key
         :param sep str: the separator string
         """
+        for c in [";", ":"]: # windows or linux path separators
+          if c in value:
+            raise Exception("LauncherFileEnviron prepend key '%s' value '%s' contains forbidden character '%s'" % (key, value, c))
         if self.is_defined(key) :
             self.add(key, value)
         else :
@@ -579,7 +598,8 @@ class LauncherFileEnviron:
         :param sep str: the separator string
         """
         if isinstance(value, list):
-            self.prepend_value(key, sep.join(value), sep)
+            for v in value:
+                self.prepend_value(key, v, sep)
         else:
             self.prepend_value(key, value, sep)
 
@@ -764,8 +784,8 @@ def __initialize():
   try:
     from salomeContextUtils import setOmniOrbUserPath
     setOmniOrbUserPath()
-  except Exception, e:
-    print e
+  except Exception as e:
+    print(e)
     sys.exit(1)
 # End of preliminary work
 
index 94f81d609b093e952caa07538e245de283f8400e..49a8502c6ba735612736dfb811c7737c4a996013 100755 (executable)
@@ -22,6 +22,7 @@ Implements the classes and method relative to the logging
 
 import sys
 import os
+import stat
 import datetime
 import re
 import tempfile
@@ -70,8 +71,22 @@ class Logger(object):
         # the external commands calls (cmake, make, git clone, etc...)
         txtFileName = prefix + hour_command_host + ".txt"
         txtFilePath = os.path.join(log_dir, "OUT", txtFileName)
-        
-        src.ensure_path_exists(os.path.dirname(logFilePath))
+
+        aDirLog = os.path.dirname(logFilePath)
+        if not os.path.exists(aDirLog):
+          print("create log dir %s" % aDirLog)
+          src.ensure_path_exists(aDirLog)
+          # sometimes other users make 'sat log' and create hat.xml file...
+          os.chmod(aDirLog,
+                   stat.S_IRUSR |
+                   stat.S_IRGRP |
+                   stat.S_IROTH |
+                   stat.S_IWUSR |
+                   stat.S_IWGRP |
+                   stat.S_IWOTH |
+                   stat.S_IXUSR |
+                   stat.S_IXGRP |
+                   stat.S_IXOTH)
         src.ensure_path_exists(os.path.dirname(txtFilePath))
         
         # The path of the log files (one for sat traces, and the other for 
@@ -450,8 +465,7 @@ def update_hat_xml(logDir, application=None, notShownCommands = []):
     """
     # Create an instance of XmlLogFile class to create hat.xml file
     xmlHatFilePath = os.path.join(logDir, 'hat.xml')
-    xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath,
-                                    "LOGlist", {"application" : application})
+    xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath, "LOGlist", {"application" : application})
     # parse the log directory to find all the command logs, 
     # then add it to the xml file
     lLogFile = list_log_file(logDir, log_macro_command_file_expression)
@@ -471,6 +485,15 @@ def update_hat_xml(logDir, application=None, notShownCommands = []):
     
     # Write the file on the hard drive
     xmlHat.write_tree('hat.xsl')
+    # Sometimes other users will make 'sat log' and update this file
+    os.chmod(xmlHatFilePath,
+             stat.S_IRUSR |
+             stat.S_IRGRP |
+             stat.S_IROTH |
+             stat.S_IWUSR |
+             stat.S_IWGRP |
+             stat.S_IWOTH )
+
 
 
 # TODO for future
index 77fd3c6432be066867874f4d60d74564709875f2..47bd73a112d715d2dd24db5b639fc6734287692d 100644 (file)
@@ -346,6 +346,8 @@ def get_product_section(config, product_name, version, section=None):
             prod_info.section = section_range
             prod_info.from_file = config.PRODUCTS[product_name].from_file
             return prod_info
+
+
     
     # Else, get the standard informations
     if "default" in config.PRODUCTS[product_name]:
diff --git a/src/versionMinorMajorPatch.py b/src/versionMinorMajorPatch.py
new file mode 100755 (executable)
index 0000000..d79831b
--- /dev/null
@@ -0,0 +1,296 @@
+#!/usr/bin/env python
+#-*- coding:utf-8 -*-
+
+#  Copyright (C) 2010-2018  CEA/DEN
+#
+#  This library is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2.1 of the License.
+#
+#  This library is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+
+
+"""\
+class and utilities to define a version as MAJOR.MINOR.PATCH
+
+| Given a version number MAJOR.MINOR.PATCH separator "_" or "."
+| increment the
+| MAJOR version when you make incompatible API changes,
+| MINOR version when you add functionality in a backwards-compatible manner,
+| PATCH version when you make backwards-compatible bug fixes.
+"""
+
+import os
+import sys
+
+verbose = False # True
+
+#############################################
+def only_numbers(aStr):
+  """
+  Remove non numericals characters from string,
+  returns None if no numbers
+  """
+  res = ''.join([nb for nb in aStr if nb in '0123456789'])
+  if res == "":
+    return None
+  else:
+    return res
+
+#############################################
+def toList_majorMinorPatch(aStr):
+  """
+  Returns list of integer as  [major, minor, patch] from a string,
+  raise exception if problem
+  """
+  if verbose: print("toList_majorMinorPatch('%s')" % aStr)
+  res = aStr.replace(" ", "").replace(".", "_").split("_")
+  if len(res) > 3:
+    msg = "Not a major_minor_patch correct syntax: '%s'" % aStr
+    raise Exception(msg)
+  if len(res) == 0:
+    msg = "An empty string is not a major_minor_patch syntax"
+    raise Exception(msg)
+  # complete MINOR.PATCH if not existing
+  if len(res) == 1:
+    res.append("0")
+  if len(res) == 2:
+    res.append("0")
+
+  try:
+    ii = int(res[0])
+  except:
+    msg = "major in major_minor_patch is not integer: '%s'" % aStr
+    raise Exception(msg)
+  if ii < 0:
+    msg = "major in major_minor_patch is negative integer: '%s'" % aStr
+    raise Exception(msg)
+
+  try:
+    ii = int(res[1])
+  except:
+    msg = "minor in major_minor_patch is not integer: '%s'" % aStr
+    raise Exception(msg)
+  if ii < 0:
+    msg = "minor in major_minor_patch is negative integer: '%s'" % aStr
+    raise Exception(msg)
+
+  try:
+    ii = int(res[2])
+  except:
+    msg = "patch in major_minor_patch is not integer: '%s'" % aStr
+    raise Exception(msg)
+  if ii < 0:
+    msg = "patch in major_minor_patch is negative integer: '%s'" % aStr
+    raise Exception(msg)
+
+  return [int(i) for i in res]
+
+#############################################
+def toCompactStr_majorMinorPatch(version):
+  """
+  parameter version is list of integer as  [major, minor, patch]
+
+  | returns "789" for [7, 8, 9]
+  | warning:
+  |   minor, pach have to be integer less than 10
+  |   raise exception for [7, 10, 11]
+  |   (which returns "71011" as ambigous 710.1.1 for example)
+  """
+  if len(version) != 3:
+    msg = "version major_minor_patch is incorrect: '%s'" % version
+    raise Exception(msg)
+
+  aStr = '_'.join([str(i) for i in version])
+  toList_majorMinorPatch(aStr) # will raise error if problem (as too much or negative values)
+
+  res = "".join([str(i) for i in version])
+  if version[1] > 9 or version[2] > 9:
+     raise Exception("ambigous major_minor_patch compact representation '%s' from '%s'" % (res, version))
+
+  return res
+
+
+#############################################
+class MinorMajorPatch(object):
+  """\
+  class to define a version as MAJOR.MINOR.PATCH
+
+  | Given a version number MAJOR.MINOR.PATCH separator "_" or "."
+  | increment the
+  | MAJOR version when you make incompatible API changes,
+  | MINOR version when you add functionality in a backwards-compatible manner,
+  | PATCH version when you make backwards-compatible bug fixes.
+  """
+
+  def __init__(self, version):
+    if type(version) == list:
+      aStr = '_'.join([str(i) for i in version])
+      v = toList_majorMinorPatch(aStr)
+    else:
+      v = toList_majorMinorPatch(version)
+    self.major = v[0]
+    self.minor = v[1]
+    self.patch = v[2]
+
+  def __repr__(self, sep="_"):
+    """example is 'version_1_2_3' """
+    res = "version_%i%s%i%s%i" % (self.major, sep, self.minor, sep, self.patch)
+    return res
+
+  def __str__(self, sep="."):
+    """example is '1.2.3' """
+    res = "%i%s%i%s%i" % (self.major, sep, self.minor, sep, self.patch)
+    return res
+
+  def strSalome(self):
+    """example is '1_2_3' """
+    return self.__str__(sep="_")
+
+  def strClassic(self):
+    """example is '1.2.3' """
+    return self.__str__(sep=".")
+
+  def strCompact(self):
+    """example is '123' from '1.2.3' """
+    return toCompactStr_majorMinorPatch(self.toList())
+
+  def toList(self):
+    """example is list of integer [1, 2, 3] from '1.2.3' """
+    return [self.major, self.minor, self.patch]
+
+  def __lt__(self, other):
+    res = (self.toList() < other.toList())
+    return res
+
+  def __le__(self, other):
+    res = (self.toList() <= other.toList())
+    return res
+
+  def __gt__(self, other):
+    res = (self.toList() > other.toList())
+    return res
+
+  def __ge__(self, other):
+    res = (self.toList() >= other.toList())
+    return res
+
+  def __eq__(self, other):
+    res = (self.toList() == other.toList())
+    return res
+
+  def __ne__(self, other):
+    res = (self.toList() != other.toList())
+    return res
+
+#############################################
+import unittest
+import pprint as PP
+
+
+class TestCase(unittest.TestCase):
+  "Test the versionMajorMinorPatch.py"""
+
+  def test_010(self):
+    if verbose: print(PP.pformat(dir(self)))
+    self.assertTrue(only_numbers("") is None)
+    self.assertEqual(only_numbers("1.2.3"), "123")
+    self.assertEqual(only_numbers("\n11.12.13\n"), "111213")
+    self.assertEqual(only_numbers(" \n 11.\t\n\t..12.13-rc2\n"), "1112132")
+
+  def test_020(self):
+    res = [11, 222, 3333]
+    self.assertEqual(toList_majorMinorPatch("11.222.3333"), res)
+    self.assertEqual(toList_majorMinorPatch("11_222_3333"), res)
+    self.assertEqual(toList_majorMinorPatch("11.222_3333"), res)
+    self.assertEqual(toList_majorMinorPatch("  11.  222 . 3333  "), res)
+    self.assertEqual(toList_majorMinorPatch("\n  11  .    222 .   3333   \n"), res)
+    self.assertEqual(toList_majorMinorPatch(" \n11.\t222.\r3333\n "), res) # could be tricky
+
+    self.assertEqual(toList_majorMinorPatch("11"), [11, 0, 0])
+    self.assertEqual(toList_majorMinorPatch("11.0"), [11, 0, 0])
+    self.assertEqual(toList_majorMinorPatch("11.2"), [11, 2, 0])
+    self.assertEqual(toList_majorMinorPatch("\n1 .    2  \n"), [1, 2, 0])
+
+    with self.assertRaises(Exception): toList_majorMinorPatch("")
+    with self.assertRaises(Exception): toList_majorMinorPatch("11.")
+    with self.assertRaises(Exception): toList_majorMinorPatch("11.2.")
+    with self.assertRaises(Exception): toList_majorMinorPatch("11.2.3.")
+    with self.assertRaises(Exception): toList_majorMinorPatch(".11")
+    with self.assertRaises(Exception): toList_majorMinorPatch("1_2_3_4")
+    with self.assertRaises(Exception): toList_majorMinorPatch("_1_2_3_")
+    with self.assertRaises(Exception): toList_majorMinorPatch(" \n 11...22.333-rc2\n")
+    with self.assertRaises(Exception): toList_majorMinorPatch(" \n 11...22.333-rc2\n")
+    with self.assertRaises(Exception): toList_majorMinorPatch(" \n 11...22.333-rc2\n")
+
+
+  def test_030(self):
+    self.assertEqual(toCompactStr_majorMinorPatch([1, 2, 3]), "123")
+    self.assertEqual(toCompactStr_majorMinorPatch([11, 2, 3]), "1123")
+    self.assertEqual(toCompactStr_majorMinorPatch([1, 9, 9]), "199")
+
+    with self.assertRaises(Exception): toCompactStr_majorMinorPatch([1, 2, 10])
+    with self.assertRaises(Exception): toCompactStr_majorMinorPatch([1, 10, 3])
+    with self.assertRaises(Exception): toCompactStr_majorMinorPatch([10, 10, 10])
+
+  def test_040(self):
+    MMP = MinorMajorPatch
+    v = [1, 2, 3]
+    self.assertEqual(MMP(v).__str__(), "1.2.3")
+    self.assertEqual(MMP(v).__str__(sep="_"), "1_2_3")
+    self.assertEqual(str(MMP(v)), "1.2.3")
+
+    self.assertEqual(MMP(v).__repr__(), "version_1_2_3")
+    self.assertEqual(MMP(v).__repr__(sep="."), "version_1.2.3")
+
+    self.assertEqual(MMP(v).strSalome(), "1_2_3")
+    self.assertEqual(MMP(v).strClassic(), "1.2.3")
+
+    self.assertEqual(MMP(['  123 \n', 2, 10]).strClassic(), "123.2.10")
+    self.assertEqual(MMP(['  123 \n', 2, 10]).strSalome(), "123_2_10")
+    self.assertEqual(MMP(['  123 \n', 2, 9]).strCompact(), "12329") # no ambigous
+
+    with self.assertRaises(Exception): MMP([-5, 2, 10])
+    with self.assertRaises(Exception): MMP([5, -2, 10])
+    with self.assertRaises(Exception): MMP([5, 2, -10])
+    with self.assertRaises(Exception): MMP(['-123', 2, 10])
+    with self.assertRaises(Exception): MMP([123, 2, 10].strCompact()) # ambigous
+
+  def test_040(self):
+    MMP = MinorMajorPatch
+    v000 = MMP("0.0.0")
+    v010 = MMP("0.1.0")
+    v100 = MMP("1.0.0")
+    v101 = MMP("1.0.1")
+
+    va = v000
+    vb = MMP("0.0.0")
+    self.assertTrue(va == vb)
+    self.assertTrue(va >= vb)
+    self.assertTrue(va <= vb)
+    self.assertFalse(va != vb)
+    self.assertFalse(va > vb)
+    self.assertFalse(va < vb)
+
+    va = v000
+    vb = v010
+    self.assertFalse(va == vb)
+    self.assertFalse(va >= vb)
+    self.assertTrue(va <= vb)
+    self.assertTrue(va != vb)
+    self.assertFalse(va > vb)
+    self.assertTrue(va < vb)
+
+
+if __name__ == '__main__':
+  unittest.main(exit=False)
+  pass
+