3 # Copyright (C) 2010-2013 CEA/DEN
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.
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.
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
28 # Compatibility python 2/3 for input function
29 # input stays input for python 3 and input = raw_input for python 2
35 parser = src.options.Options()
36 parser.add_option('n', 'name', 'string', 'name',
37 _("""REQUIRED: the name of the module to create.
38 \tThe name must be a single word in upper case with only alphanumeric characters.
39 \tWhen generating a c++ component the module's """
40 """name must be suffixed with 'CPP'."""))
41 parser.add_option('t', 'template', 'string', 'template',
42 _('REQUIRED: the template to use.'))
43 parser.add_option('', 'target', 'string', 'target',
44 _('REQUIRED: where to create the module.'))
45 parser.add_option('', 'param', 'string', 'param',
46 _('''Optional: dictionary to generate the configuration for salomeTools.
47 \tFormat is: --param param1=value1,param2=value2... without spaces
48 \tNote that when using this option you must supply all the '''
49 '''values otherwise an error will be raised.'''))
50 parser.add_option('', 'info', 'boolean', 'info',
51 _('Optional: Get information on the template.'), False)
54 def __init__(self, param_def, compo_name, dico=None):
57 self.check_method = None
59 if isinstance(param_def, str):
61 elif isinstance(param_def, tuple):
62 self.name = param_def[0]
63 if len(param_def) > 1:
64 if dico is not None: self.default = param_def[1] % dico
65 else: self.default = param_def[1]
66 if len(param_def) > 2: self.prompt = param_def[2]
67 if len(param_def) > 3: self.check_method = param_def[3]
69 raise src.SatException(_("ERROR in template parameter definition"))
71 self.raw_prompt = self.prompt
72 if len(self.prompt) == 0:
73 self.prompt = _("value for '%s'") % self.name
75 if len(self.default) > 0:
76 self.prompt += "[%s] " % self.default
78 def check_value(self, val):
79 if self.check_method is None:
81 return len(val) > 0 and self.check_method(val)
83 def get_dico_param(dico, key, default):
88 class TemplateSettings:
89 def __init__(self, compo_name, settings_file, target):
90 self.compo_name = compo_name
96 execfile(settings_file, gdic, ldic)
98 # check required parameters in template.info
100 for pp in ["file_subst", "parameters"]:
101 if not ldic.has_key(pp): missing.append("'%s'" % pp)
103 raise src.SatException(_(
104 "Bad format in settings file! %s not defined.") % ", ".join(
107 self.file_subst = ldic["file_subst"]
108 self.parameters = ldic['parameters']
109 self.info = get_dico_param(ldic, "info", "").strip()
110 self.pyconf = get_dico_param(ldic, "pyconf", "")
111 self.post_command = get_dico_param(ldic, "post_command", "")
113 # get the delimiter for the template
114 self.delimiter_char = get_dico_param(ldic, "delimiter", ":sat:")
116 # get the ignore filter
117 self.ignore_filters = map(lambda l: l.strip(),
118 ldic["ignore_filters"].split(','))
120 def has_pyconf(self):
121 return len(self.pyconf) > 0
123 def get_pyconf_parameters(self):
124 if len(self.pyconf) == 0:
126 return re.findall("%\((?P<name>\S[^\)]*)", self.pyconf)
129 # Check if the file needs to be parsed.
130 def check_file_for_substitution(self, file_):
131 for filter_ in self.ignore_filters:
132 if fnmatch.fnmatchcase(file_, filter_):
136 def check_user_values(self, values):
140 # create a list of all parameters (pyconf + list))
141 pnames = self.get_pyconf_parameters()
142 for p in self.parameters:
143 tp = TParam(p, self.compo_name)
144 pnames.append(tp.name)
147 pnames = list(set(pnames)) # remove duplicates
149 known_values = ["name", "Name", "NAME", "target", self.file_subst]
150 known_values.extend(values.keys())
153 if p not in known_values:
157 raise src.SatException(_(
158 "Missing parameters: %s") % ", ".join(missing))
160 def get_parameters(self, conf_values=None):
161 if self.dico is not None:
164 self.check_user_values(conf_values)
166 # create dictionary with default values
168 dico["name"] = self.compo_name.lower()
169 dico["Name"] = self.compo_name.capitalize()
170 dico["NAME"] = self.compo_name
171 dico["target"] = self.target
172 dico[self.file_subst] = self.compo_name
173 # add user values if any
174 if conf_values is not None:
175 for p in conf_values.keys():
176 dico[p] = conf_values[p]
178 # ask user for values
179 for p in self.parameters:
180 tp = TParam(p, self.compo_name, dico)
181 if dico.has_key(tp.name):
185 while not tp.check_value(val):
186 val = raw_input(tp.prompt)
187 if len(val) == 0 and len(tp.default) > 0:
191 # ask for missing value for pyconf
192 pyconfparam = self.get_pyconf_parameters()
193 for p in filter(lambda l: not dico.has_key(l), pyconfparam):
196 rep = raw_input("%s? " % p)
202 def search_template(config, template):
204 template_src_dir = ""
205 if os.path.isabs(template):
206 if os.path.exists(template):
207 template_src_dir = template
209 # look in template directory
210 for td in [os.path.join(config.VARS.datadir, "templates")]:
211 zz = os.path.join(td, template)
212 if os.path.exists(zz):
213 template_src_dir = zz
216 if len(template_src_dir) == 0:
217 raise src.SatException(_("Template not found: %s") % template)
219 return template_src_dir
221 # Prepares a module from a template.
222 def prepare_from_template(config,
228 template_src_dir = search_template(config, template)
232 if os.path.isfile(template_src_dir):
233 logger.write(" " + _(
234 "Extract template %s\n") % src.printcolors.printcInfo(
236 src.system.archive_extract(template_src_dir, target_dir)
238 logger.write(" " + _(
239 "Copy template %s\n") % src.printcolors.printcInfo(
241 shutil.copytree(template_src_dir, target_dir)
242 logger.write("\n", 5)
245 if name.endswith("CPP"):
246 compo_name = name[:-3]
249 settings_file = os.path.join(target_dir, "template.info")
250 if not os.path.exists(settings_file):
251 raise src.SatException(_("Settings file not found"))
252 tsettings = TemplateSettings(compo_name, settings_file, target_dir)
254 # first rename the files
255 logger.write(" " + src.printcolors.printcLabel(_("Rename files\n")), 4)
256 for root, dirs, files in os.walk(target_dir):
258 ff = fic.replace(tsettings.file_subst, compo_name)
260 if os.path.exists(os.path.join(root, ff)):
261 raise src.SatException(_(
262 "Destination file already exists: %s") % os.path.join(
264 logger.write(" %s -> %s\n" % (fic, ff), 5)
265 os.rename(os.path.join(root, fic), os.path.join(root, ff))
267 # rename the directories
268 logger.write("\n", 5)
269 logger.write(" " + src.printcolors.printcLabel(_("Rename directories\n")),
271 for root, dirs, files in os.walk(target_dir, topdown=False):
273 dd = rep.replace(tsettings.file_subst, compo_name)
275 if os.path.exists(os.path.join(root, dd)):
276 raise src.SatException(_(
277 "Destination directory "
278 "already exists: %s") % os.path.join(root, dd))
279 logger.write(" %s -> %s\n" % (rep, dd), 5)
280 os.rename(os.path.join(root, rep), os.path.join(root, dd))
282 # ask for missing parameters
283 logger.write("\n", 5)
284 logger.write(" " + src.printcolors.printcLabel(
285 _("Make substitution in files\n")), 4)
286 logger.write(" " + _("Delimiter =") + " %s\n" % tsettings.delimiter_char,
288 logger.write(" " + _("Ignore Filters =") + " %s\n" % ', '.join(
289 tsettings.ignore_filters), 5)
290 dico = tsettings.get_parameters(conf_values)
291 logger.write("\n", 3)
293 # override standard string.Template class to use the desire delimiter
294 class CompoTemplate(string.Template):
295 delimiter = tsettings.delimiter_char
298 logger.write("\n", 5, True)
299 pathlen = len(target_dir) + 1
300 for root, dirs, files in os.walk(target_dir):
302 fpath = os.path.join(root, fic)
303 if not tsettings.check_file_for_substitution(fpath[pathlen:]):
304 logger.write(" - %s\n" % fpath[pathlen:], 5)
307 m = file(fpath, 'r').read()
308 # make the substitution
309 template = CompoTemplate(m)
310 d = template.safe_substitute(dico)
311 # overwrite the file with substituted content
315 file(fpath, 'w').write(d)
316 logger.write(" %s %s\n" % (changed, fpath[pathlen:]), 5)
318 if not tsettings.has_pyconf:
319 logger.write(src.printcolors.printcWarning(_(
320 "Definition for sat not found in settings file.")) + "\n", 2)
322 definition = tsettings.pyconf % dico
323 pyconf_file = os.path.join(target_dir, name + '.pyconf')
324 f = open(pyconf_file, 'w')
328 "Create configuration file: ") + src.printcolors.printcInfo(
329 pyconf_file) + "\n", 2)
331 if len(tsettings.post_command) > 0:
332 cmd = tsettings.post_command % dico
333 logger.write("\n", 5, True)
335 "Run post command: ") + src.printcolors.printcInfo(cmd) + "\n", 3)
337 p = subprocess.Popen(cmd, shell=True, cwd=target_dir)
343 def get_template_info(config, template_name, logger):
344 sources = search_template(config, template_name)
345 src.printcolors.print_value(logger, _("Template"), sources)
348 tmpdir = os.path.join(config.VARS.tmp_root, "tmp_template")
349 settings_file = os.path.join(tmpdir, "template.info")
350 if os.path.exists(tmpdir):
351 shutil.rmtree(tmpdir)
352 if os.path.isdir(sources):
353 shutil.copytree(sources, tmpdir)
355 src.system.archive_extract(sources, tmpdir)
356 settings_file = os.path.join(tmpdir, "template.info")
358 if not os.path.exists(settings_file):
359 raise src.SatException(_("Settings file not found"))
360 tsettings = TemplateSettings("NAME", settings_file, "target")
362 logger.write("\n", 3)
363 if len(tsettings.info) == 0:
364 logger.write(src.printcolors.printcWarning(_(
365 "No information for this template.")), 3)
367 logger.write(tsettings.info, 3)
369 logger.write("\n", 3)
370 logger.write("= Configuration", 3)
371 src.printcolors.print_value(logger,
372 "file substitution key",
373 tsettings.file_subst)
374 src.printcolors.print_value(logger,
376 tsettings.delimiter_char)
377 if len(tsettings.ignore_filters) > 0:
378 src.printcolors.print_value(logger,
380 ', '.join(tsettings.ignore_filters))
382 logger.write("\n", 3)
383 logger.write("= Parameters", 3)
385 for pp in tsettings.parameters:
386 tt = TParam(pp, "NAME")
387 pnames.append(tt.name)
388 src.printcolors.print_value(logger, "Name", tt.name)
389 src.printcolors.print_value(logger, "Prompt", tt.raw_prompt)
390 src.printcolors.print_value(logger, "Default value", tt.default)
391 logger.write("\n", 3)
394 logger.write("= Verification\n", 3)
395 if tsettings.file_subst not in pnames:
397 "file substitution key not defined as a "
398 "parameter: %s" % tsettings.file_subst, 3)
401 reexp = tsettings.delimiter_char.replace("$", "\$") + "{(?P<name>\S[^}]*)"
402 pathlen = len(tmpdir) + 1
403 for root, __, files in os.walk(tmpdir):
405 fpath = os.path.join(root, fic)
406 if not tsettings.check_file_for_substitution(fpath[pathlen:]):
409 m = file(fpath, 'r').read()
410 zz = re.findall(reexp, m)
411 zz = list(set(zz)) # reduce
412 zz = filter(lambda l: l not in pnames, zz)
414 logger.write("Missing definition in %s: %s" % (
415 src.printcolors.printcLabel(
416 fpath[pathlen:]), ", ".join(zz)), 3)
420 logger.write(src.printcolors.printc("OK"), 3)
422 logger.write(src.printcolors.printc("KO"), 3)
424 logger.write("\n", 3)
427 shutil.rmtree(tmpdir)
432 # Describes the command
434 return _("The template command creates the sources for a SALOME "
435 "module from a template.\n\nexample\nsat template "
436 "--name my_product_name --template PythonComponent --target /tmp")
438 def run(args, runner, logger):
439 '''method that is called when salomeTools is called with template parameter.
441 (options, args) = parser.parse_args(args)
443 if options.template is None:
444 msg = _("Error: the --%s argument is required\n") % "template"
445 logger.write(src.printcolors.printcError(msg), 1)
446 logger.write("\n", 1)
449 if options.target is None and options.info is None:
450 msg = _("Error: the --%s argument is required\n") % "target"
451 logger.write(src.printcolors.printcError(msg), 1)
452 logger.write("\n", 1)
455 if "APPLICATION" in runner.cfg:
456 msg = _("Error: this command does not use a product.")
457 logger.write(src.printcolors.printcError(msg), 1)
458 logger.write("\n", 1)
462 return get_template_info(runner.cfg, options.template, logger)
464 if options.name is None:
465 msg = _("Error: the --%s argument is required\n") % "name"
466 logger.write(src.printcolors.printcError(msg), 1)
467 logger.write("\n", 1)
470 if not options.name.replace('_', '').isalnum():
471 msg = _("Error: component name must contains only alphanumeric "
472 "characters and no spaces\n")
473 logger.write(src.printcolors.printcError(msg), 1)
474 logger.write("\n", 1)
477 # Ask user confirmation if a module of the same name already exists
478 if options.name in runner.cfg.PRODUCTS and not runner.options.batch:
479 logger.write(src.printcolors.printcWarning(
480 _("A module named '%s' already exists." % options.name)), 1)
481 logger.write("\n", 1)
482 rep = input(_("Are you sure you want to continue? [Yes/No] "))
483 if rep.upper() != _("YES"):
486 if options.target is None:
487 msg = _("Error: the --%s argument is required\n") % "target"
488 logger.write(src.printcolors.printcError(msg), 1)
489 logger.write("\n", 1)
492 target_dir = os.path.join(options.target, options.name)
493 if os.path.exists(target_dir):
494 msg = _("Error: the target already exists: %s") % target_dir
495 logger.write(src.printcolors.printcError(msg), 1)
496 logger.write("\n", 1)
499 if options.template == "Application":
500 if "_APPLI" not in options.name and not runner.options.batch:
501 msg = _("An Application module named '..._APPLI' "
502 "is usually recommended.")
503 logger.write(src.printcolors.printcWarning(msg), 1)
504 logger.write("\n", 1)
505 rep = input(_("Are you sure you want to continue? [Yes/No] "))
506 if rep.upper() != _("YES"):
509 logger.write(_('Create sources from template\n'), 1)
510 src.printcolors.print_value(logger, 'destination', target_dir, 2)
511 src.printcolors.print_value(logger, 'name', options.name, 2)
512 src.printcolors.print_value(logger, 'template', options.template, 2)
513 logger.write("\n", 3, False)
516 if options.param is not None:
518 for elt in options.param.split(","):
519 param_def = elt.strip().split('=')
520 if len(param_def) != 2:
521 msg = _("Error: bad parameter definition")
522 logger.write(src.printcolors.printcError(msg), 1)
523 logger.write("\n", 1)
525 conf_values[param_def[0].strip()] = param_def[1].strip()
527 retcode = prepare_from_template(runner.cfg, options.name, options.template,
528 target_dir, conf_values, logger)
532 "The sources were created in %s") % src.printcolors.printcInfo(
534 logger.write(src.printcolors.printcWarning(_("\nDo not forget to put "
535 "them in your version control system.")), 3)
537 logger.write("\n", 3)