Salome HOME
gestion de path multiples dans la configuration projet
[tools/sat.git] / src / options.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 """
20 The Options class that manages the access to all options passed as 
21 parameters in salomeTools command lines
22 """
23
24 import getopt
25 import sys
26 import re
27 import pprint as PP
28
29 from . import printcolors
30
31 import src
32 import src.debug as DBG # Easy print stderr (for DEBUG only)
33
34 class OptResult(object):
35     """
36     An instance of this class will be the object manipulated
37     in code of all salomeTools commands
38     The aim of this class is to have an elegant syntax to manipulate the options.
39     
40     | Example:        
41     | >> options, remainderArgs = command.parseArguments(args)
42     | >> print(options.output_verbose_level)
43     | >> 'INFO'
44     """
45     def __init__(self):
46         """Initialization
47         """
48         self.__dict__ = dict()
49
50     def __getattr__(self, name):
51         """
52         Overwrite of the __getattr__ function 
53         to customize it for option usage
54         
55         :param name: (str) The attribute to get the value.
56         :return: (str int list boolean level)
57           the value corresponding to the attribute.
58         """
59         if name in self.__dict__:
60             return self.__dict__[name]
61         else:
62             raise AttributeError("--" + name + _(u" is not a valid option"))
63
64     def __setattr__(self, name, value):
65         """
66         Overwrite of the __setattr__ function 
67         to customize it for option usage
68         
69         :param name: (str) The attribute to set.
70         :param value: (str) The value  corresponding to the attribute.
71         :return: None
72         """
73         object.__setattr__(self, name, value)
74
75     def __repr__(self):
76         aStr = PP.pformat(self.__dict__)
77         res = "%s(\n %s\n)" % (self.__class__.__name__, aStr[1:-1])
78         return res
79
80 class Options(object):
81     """
82     Class to manage all salomeTools options
83     """
84     def __init__(self):
85         """Initialization
86         """
87         # The options field stocks all options of a command 
88         # in a list that contains dicts
89         self.PROPERTY_EXPRESSION = "^.+:.+$"
90         self.options = []
91         # The list of available option type
92         self.availableOptions = "noboolean boolean string int float long list list2 level properties".split()
93         self.noArgOptions = "noboolean boolean".split()
94         self.default = None
95         self.results = {}
96
97     def add_option(self, shortName, longName, optionType, destName, helpString="", default=None):
98         """
99         Add an option to a command. It gets all attributes
100         of an option and append it in the options field
101         
102         :param shortName: (str) 
103           The short name of the option (as '-l' for level option).
104         :param longName: (str) 
105           The long name of the option (as '--level' for level option).
106         :param optionType: (str) The type of the option (ex "int").
107         :param destName: (str) The name that will be used in the code.
108         :param helpString: (str) 
109           The text to display when user ask for help on a command.     
110         :return: None
111         """
112         tmp = [o['shortName'] for o in self.options if o['shortName'] != '']
113         if shortName in tmp: 
114           raise Exception("option '-%s' existing yet" % shortName)
115         tmp = [o['longName'] for o in self.options if o['longName'] != '']
116         if longName in tmp: 
117           raise Exception("option '--%s' existing yet" % longName)
118
119         option = dict()
120         option['shortName'] = shortName
121         option['longName'] = longName
122
123         if optionType not in self.availableOptions:
124           raise Exception("error optionType '%s' not available." % optionType)
125
126         option['optionType'] = optionType
127         option['destName'] = destName
128         option['helpString'] = helpString
129         option['result'] = default
130         
131         self.options.append(option)
132
133         # add option properties unconditionaly if 'products' option added
134         if [shortName, longName] == ["p", "products"]:
135           self.add_option('', 'properties', 'properties', 'properties',
136                           _('Optional: Filter the products by their properties.\n\tSyntax: '
137                           '--properties <property>:<value>'))
138
139
140         
141     def getDetailOption(self, option):
142         """
143         for convenience 
144         
145         :return: (tuple) 4-elements (shortName, longName, optionType, helpString)
146         """
147         oos = option['shortName']
148         ool = option['longName']
149         oot = option['optionType']
150         ooh = option['helpString']
151         return (oos, ool, oot, ooh)
152
153     def get_help(self):
154         """
155         Returns all options stored in self.options 
156         as help message colored string
157         
158         :return: (str) colored string
159         """
160         msg = ""
161         # Do nothing if there are no options
162
163         #there is -h option, always
164         #if len(self.options) == 0:
165         #    return _("No available options.")
166
167         # for all options, gets its values. 
168         # "shortname" is an mandatory field of the options, could be '' 
169         msg += printcolors.printcHeader(_("Available options are:"))
170         for option in self.options:
171             oos, ool, oot, ooh = self.getDetailOption(option)
172             if len(oos) > 0:
173                 msg += "\n -%1s, --%s (%s)\n" % (oos, ool, oot)
174             else:
175                 msg += "\n --%s (%s)\n" % (ool, oot)
176                 
177             msg += "%s\n" % self.indent(ooh, 10)
178         return msg
179
180     def indent(self, text, amount, car=" "):
181         """indent multi lines message"""
182         padding = amount * car
183         return ''.join(padding + line for line in text.splitlines(True))
184                
185     def parse_args(self, argList=None):
186         """
187         Instantiates the class OptResult 
188         that gives access to all options in the code
189         
190         :param argList: (list) the raw list of arguments that were passed
191         :return: (OptResult, list) as (optResult, args) 
192           optResult is the option instance to manipulate in the code. 
193           args is the full raw list of passed options 
194         """
195         # see https://pymotw.com/2/getopt/
196         if argList is None:
197             argList = sys.argv[1:]
198         
199         DBG.write("parse_args", argList)
200         # DBG.write("options", self.options)
201         # format shortNameOption and longNameOption 
202         # to make right arguments to getopt.getopt function
203         shortNameOption = ""
204         longNameOption = []
205         for option in self.options:
206             shortNameOption = shortNameOption + option['shortName']
207             if option['shortName'] != "" and option['optionType'] not in self.noArgOptions:
208                 shortNameOption = shortNameOption + ":"
209
210             if option['longName'] != "":
211                 if option['optionType'] not in self.noArgOptions:
212                     longNameOption.append(option['longName'] + "=")
213                 else:
214                     longNameOption.append(option['longName'])
215
216         # call to getopt.getopt function to get the option 
217         # passed in the command regarding the available options
218         try:
219           optlist, args = getopt.getopt(argList, shortNameOption, longNameOption)
220         except Exception as e:
221           msg = str(e) + " on '%s'\n\n" % " ".join(argList) + self.get_help()
222           raise Exception(msg)
223
224         # instantiate and completing the optResult that will be returned
225         optResult = OptResult()
226         for option in self.options:
227             shortOption = "-" + option['shortName']
228             longOption = "--" + option['longName']
229             optionType = option['optionType']
230             for opt in optlist:
231                 if opt[0] in [shortOption, longOption]:
232                     if optionType == "string":
233                         option['result'] = opt[1]
234                     elif optionType == "boolean":
235                         option['result'] = True
236                     elif optionType == "noboolean":
237                         option['result'] = False
238                     elif optionType == "int":
239                         option['result'] = int(opt[1])
240                     elif optionType == "float":
241                         option['result'] = float(opt[1])
242                     elif optionType == "long":
243                         option['result'] = long(opt[1])
244                     elif optionType == "list":
245                         if option['result'] is None:
246                             option['result'] = list()
247                         option['result'].append(opt[1])
248                     elif optionType == "level": #logger logging levels
249                         option['result'] = self.filterLevel(opt[1])
250                     elif optionType == "list2":
251                         if option['result'] is None:
252                             option['result'] = list()
253                         option['result'] = self.filterList2(opt[1])
254                     elif optionType == "properties":
255                         option['result'] = self.filterProperties(opt[1])
256
257             optResult.__setattr__(option['destName'], option['result'])
258             # free the option in order to be able to make 
259             # a new free call of options (API case)
260             option['result'] = None
261
262         self.results = {"optlist": optlist, "optResult": optResult, "args": args, "argList": argList}
263         DBG.write("results", self.results)
264         return optResult, args
265         
266     def filterLevel(self, aLevel):
267       """filter level logging values"""
268       import src.loggingSimple as LOG
269       aLev = aLevel.upper()
270       knownLevels = LOG._knownLevels
271       maxLen = max([len(i) for i in knownLevels])
272       for i in range(maxLen):
273         for lev in knownLevels:
274           if aLev == lev[:i]:
275             DBG.write("filterLevel", "%s -> %s" % (aLevel, lev)) 
276             return lev
277       msg = "Unknown level '%s', accepted are:\n%s" % (aLev, ", ".join(knownLevels))
278       raise Exception(msg)
279       
280     def filterList2(self, aStr):
281       """filter a list as 'KERNEL,YACS,etc.'"""
282       aList = aStr.strip().split(",")
283       # fix list leading ',' as ',KERNEL,...'
284       aList = [i for i in aList if i != ""] # split old list leadin "," as ",KERNEL,ETC..."
285       return aList
286       
287     def filterProperties(self, aStr):
288       """
289       filter properties values
290
291       example:
292       >> sat -v 9 prepare $TRG -p KERNEL --properties is_SALOME_module:yes
293       """
294       msg = _('The "--properties" option must have the following syntax:\n--properties <property>:<value>')
295       oExpr = re.compile(self.PROPERTY_EXPRESSION)
296       if not oExpr.search(aStr):
297         raise Exception(msg)
298       res = aStr.split(":")
299       if len(res) != 2:
300         raise Exception(msg)
301       return res
302
303     def __repr__(self): 
304       """
305       repr for only self.options and self.results (if present)
306       """
307       aDict = {'options': self.options, 'results': self.results}
308       aStr = PP.pformat(aDict)
309       res = "%s(\n %s\n)" % (self.__class__.__name__, aStr[1:-1])
310       return res
311         
312     def __str__(self): 
313       """
314       str for only resume expected self.options
315       """
316       #aDict = [(k["longName"], k["shortName", k["helpString"]) for k in self.options}
317       #aList = [(k, self.options[k]) for k in sorted(self.options.keys())]
318       aDict = {}
319       for o in self.options:
320         aDict[o["longName"]] = (o["shortName"], o["helpString"])
321       aStr = PP.pformat(aDict)
322       res = "%s(\n %s)" % (self.__class__.__name__, aStr[1:-1])
323       return res
324         
325     def debug_write(self):
326       DBG.write("options and results", self, True)
327
328