diff --git a/ictmpl/MANIFEST.in b/MANIFEST.in similarity index 100% rename from ictmpl/MANIFEST.in rename to MANIFEST.in diff --git a/ictmpl/__init__.py b/ictmpl/__init__.py index de73617..fdbd9ea 100644 --- a/ictmpl/__init__.py +++ b/ictmpl/__init__.py @@ -1,6 +1,7 @@ from .param import Param +from .dependency import SysDep -__all__ = ('Param', ) +__all__ = ('Param', 'SysDep') if __name__ == '__main__': from .config import Config diff --git a/ictmpl/__version__.py b/ictmpl/__version__.py index f5f41d4..716f5c5 100644 --- a/ictmpl/__version__.py +++ b/ictmpl/__version__.py @@ -3,8 +3,8 @@ __title__ = 'ictmpl' __description__ = 'Generate projects from templates' __url__ = 'https://github.com/ideascup/ictmpl/' -__version__ = '1.1.2' -__build__ = 0x010102 +__version__ = '1.2.0' +__build__ = 0x010200 __author__ = 'Dmitriy Pleshevskiy' __author_email__ = 'dmitriy@ideascup.me' __license__ = 'MIT' diff --git a/ictmpl/dependency.py b/ictmpl/dependency.py new file mode 100644 index 0000000..2760617 --- /dev/null +++ b/ictmpl/dependency.py @@ -0,0 +1,25 @@ +from subprocess import Popen, PIPE + + +class SysDep: + def __init__(self, packages, checkcmd=None, checkparams=None): + self.packages = packages + self.checkcmd = checkcmd + self.checkparams = checkparams + + def check(self, app): + if self.checkcmd: + proc = Popen('command -v {}'.format(self.checkcmd), shell=True, stdout=PIPE) + stdout = proc.communicate()[0] + if not stdout: + return True + elif self.checkparams: + if callable(self.checkparams): + return bool(self.checkparams(app.params)) + elif isinstance(self.checkparams, str): + return bool(app.params.get(self.checkparams)) + else: + return True + + return False + \ No newline at end of file diff --git a/ictmpl/helpers/fsutil.py b/ictmpl/helpers/fsutil.py index 5008cf9..5b4c5f4 100644 --- a/ictmpl/helpers/fsutil.py +++ b/ictmpl/helpers/fsutil.py @@ -1,16 +1,22 @@ +import re import os import json -from re import compile, sub, _pattern_type from os.path import abspath, isdir, join from shutil import copytree, rmtree __all__ = ('parse_ignore_file', 'copytree_with_ignore', 'treewalk') -RE_DOUBLESTAR = compile(r'\*\*') -RE_STAR = compile(r'\*') +RE_DOUBLESTAR = re.compile(r'\*\*') +RE_STAR = re.compile(r'\*') -RE_IF_CONDITION = compile(r'\s*%\s*(endif|if\s+(.+))\s*%') +RE_IF_CONDITION = re.compile(r""" + (?: + ^\s*%\s*(else|(?:el(?:se )?)?if\s+(.+))\s*% + | + \s*%\s*(endif)\s*% + ) + """, re.VERBOSE|re.MULTILINE) def parse_ignore_file(filepath): @@ -30,7 +36,7 @@ def parse_ignore_file(filepath): if line.startswith('/'): line = '^'+line - regexes.append(compile(line)) + regexes.append(re.compile(line)) return regexes @@ -108,34 +114,62 @@ def replace_template_file(filepath, app): break start, end = [start_index+v for v in match.span()] - statement = match.group(1) + statement = match.group(1) or match.group(3) if statement.startswith('if '): cond_deep += 1 + if cut_info is not None: + continue + local = params.copy() exec('result__=bool({})'.format(match.group(2)), local) if local['result__']: filedata = filedata[:start] + filedata[end:] start_index = start - elif cut_info is None: + else: cut_info = (start, cond_deep) start_index = end - else: + elif statement.startswith('elif ') \ + or statement.startswith('else if '): + if cut_info is not None: + start = cut_info[0] + cut_info = None + + local = params.copy() + exec('result__=bool({})'.format(match.group(2)), local) + if local['result__']: + filedata = filedata[:start] + filedata[end:] + start_index = start + continue + + cut_info = (start, cond_deep) + start_index = end + elif statement.startswith('else') \ + and not match.group(2): + if cut_info is not None: + start = cut_info[0] + cut_info = None + + filedata = filedata[:start] + filedata[end:] + start_index = start + + elif statement.startswith('endif'): if cut_info is not None \ and cut_info[1] is cond_deep: start = cut_info[0] cut_info = None filedata = filedata[:start] + filedata[end:] start_index = start + cond_deep -= 1 # replace params for key, value in params.items(): - if isinstance(value, _pattern_type): + if isinstance(value, re._pattern_type): continue elif value is None or isinstance(value, bool): value = str(value) else: value = json.dumps(value) - filedata = sub(r'%%{}%%'.format(key), value, filedata) + filedata = re.sub(r'%%{}%%'.format(key), value, filedata) with open(filepath, 'w') as file: file.write(filedata) diff --git a/ictmpl/methods/create.py b/ictmpl/methods/create.py index d1cd4f7..02b9088 100644 --- a/ictmpl/methods/create.py +++ b/ictmpl/methods/create.py @@ -7,7 +7,7 @@ from uuid import uuid4 from textwrap import dedent from shutil import rmtree -from ictmpl import Param +from ictmpl import Param, SysDep from ictmpl.helpers.git import Git from ictmpl.helpers.fsutil import ( copytree_with_ignore, rmtree_without_ignore, replace_template_file) @@ -74,29 +74,19 @@ def create_project(args, app): print(e) return - dependencies = rc.get('sysDependencies', None) - if dependencies and isinstance(dependencies, dict): - commands = [] - for key, packages in dependencies.items(): - pipes = Popen('command -v %s' % key, shell=True, stdout=PIPE) - stdout, _ = pipes.communicate() - if not stdout: - commands.append(packages) - - if commands: - # TODO: Need check platform - command = 'sudo apt-get install %s' % ' '.join(commands) - Popen(command, shell=True) for key in ('name', 'version', 'description', 'author', 'author_email', 'author_website', 'licence'): app.params['__{}__'.format(key.upper())] = rc.get(key, '') params = rc.get('params', []) - if params: + if params and isinstance(params, (list, tuple)): print('Configure template:') - ask_params(app, params) - print(app.params) + try: + ask_params(app, params) + except KeyboardInterrupt: + rmtree(project_path) + exit() print('------------------\n') print('Walking files and replace params') @@ -111,6 +101,29 @@ def create_project(args, app): for filename in files: replace_template_file(join(root, filename), app) + try: + dependencies = rc.get('sys_dependencies', []) + if dependencies and isinstance(dependencies, (list, tuple)): + packages = ' '.join( + sysdep.packages + for sysdep in dependencies + if isinstance(sysdep, SysDep) and sysdep.check(app) + ) + + if packages: + # TODO: Need check platform + try: + proc = Popen('sudo apt-get install {}'.format(packages), + shell=True) + proc.communicate() + except KeyboardInterrupt: + proc.kill() + print(os.linesep) + exit() + except Exception as e: + import traceback + traceback.print_exc() + setup_command = rc.get('commands', {}).get('setup', None) if setup_command: if isinstance(setup_command, (list, tuple)): diff --git a/ictmpl/param.py b/ictmpl/param.py index ecc27a0..40798de 100644 --- a/ictmpl/param.py +++ b/ictmpl/param.py @@ -1,4 +1,6 @@ +class auto(str): pass + class Param: def __init__(self, name, default, vtype=None, question=None, children=None): @@ -13,20 +15,30 @@ class Param: except: pass - self.question = question + ' ' + self.question = question.rstrip() + ' ' self.children = children def strparse(self, value): - if self.vtype is not str and type(value) is self.vtype: + if not isinstance(self.vtype, type) and callable(self.vtype): + vtype = self.vtype(value) + else: + vtype = self.vtype + + if value is 'None': + return None + + if type(value) is vtype: return value - if self.vtype in (list, tuple) \ - or (self.vtype is str and ',' in value): + lvalue = value.lower() + if vtype in (list, tuple) \ + or (vtype is auto and ',' in value): return [v.strip() for v in value.split(',') if v.strip()] - elif self.vtype is bool: - return value[:1].lower() == 'y' + elif vtype is bool \ + or (vtype is auto and lvalue in ('true', 'false')): + return lvalue == 'y' or lvalue == 'true' - return self.vtype(value) + return vtype(value) def format(self, value): type_ = type(value)