Example #1
0
 def __init__(self,
              dotpath,
              variables,
              conf,
              dry=False,
              safe=True,
              debug=False,
              ignore=[],
              showpatch=False):
     """constructor
     @dotpath: path where dotfiles are stored
     @variables: dictionary of variables for the templates
     @conf: configuration manager
     @dry: simulate
     @safe: ask for overwrite if True
     @debug: enable debug
     @ignore: pattern to ignore when updating
     @showpatch: show patch if dotfile to update is a template
     """
     self.dotpath = dotpath
     self.variables = variables
     self.conf = conf
     self.dry = dry
     self.safe = safe
     self.debug = debug
     self.ignore = ignore
     self.showpatch = showpatch
     self.templater = Templategen(variables=self.variables,
                                  base=self.dotpath,
                                  debug=self.debug)
     # save template vars
     self.tvars = self.templater.add_tmp_vars()
     self.log = Logger()
Example #2
0
    def __init__(self, cfgpath):
        if not os.path.exists(cfgpath):
            raise ValueError('config file does not exist: {}'.format(cfgpath))
        # make sure to have an absolute path to config file
        self.cfgpath = os.path.abspath(cfgpath)

        # init the logger
        self.log = Logger()

        # represents all entries under "config"
        # linked inside the yaml dict (self.content)
        self.lnk_settings = {}

        # represents all entries under "profiles"
        # linked inside the yaml dict (self.content)
        self.lnk_profiles = {}

        # represents all dotfiles
        # NOT linked inside the yaml dict (self.content)
        self.dotfiles = {}

        # dict of all action objects by action key
        # NOT linked inside the yaml dict (self.content)
        self.actions = {}

        # dict of all transformation objects by trans key
        # NOT linked inside the yaml dict (self.content)
        self.trans = {}

        # represents all dotfiles per profile by profile key
        # NOT linked inside the yaml dict (self.content)
        self.prodots = {}
        if not self._load_file():
            raise ValueError('config is not valid')
Example #3
0
    def __init__(self, args=None):
        """constructor
        @args: argument dictionary (if None use sys)
        """
        self.args = args
        if not args:
            self.args = docopt(USAGE, version=VERSION)
        self.log = Logger()
        self.debug = self.args['--verbose']
        if not self.debug and ENV_DEBUG in os.environ:
            self.debug = True
        if ENV_NODEBUG in os.environ:
            self.debug = False
        self.profile = self.args['--profile']
        self.confpath = os.path.expanduser(self.args['--cfg'])
        if self.debug:
            self.log.dbg('config file: {}'.format(self.confpath))

        self._read_config(self.profile)
        self._apply_args()
        self._fill_attr()
        if ENV_NOBANNER not in os.environ \
           and self.banner \
           and not self.args['--no-banner']:
            self._header()
        self._print_attr()
        # start monitoring for bad attribute
        self._set_attr_err = True
Example #4
0
    def __init__(self, args=None):
        """constructor
        @args: argument dictionary (if None use sys)
        """
        self.args = {}
        if not args:
            self.args = docopt(USAGE, version=VERSION)
        if args:
            self.args = args.copy()
        self.log = Logger()
        self.debug = self.args['--verbose'] or ENV_DEBUG in os.environ
        self.dry = self.args['--dry']
        if ENV_NODEBUG in os.environ:
            # force disabling debugs
            self.debug = False
        self.profile = self.args['--profile']
        self.confpath = self._get_config_path()
        if self.debug:
            self.log.dbg('version: {}'.format(VERSION))
            self.log.dbg('command: {}'.format(' '.join(sys.argv)))
            self.log.dbg('config file: {}'.format(self.confpath))

        self._read_config()
        self._apply_args()
        self._fill_attr()
        if ENV_NOBANNER not in os.environ \
           and self.banner \
           and not self.args['--no-banner']:
            self._header()
        self._debug_attr()
        # start monitoring for bad attribute
        self._set_attr_err = True
Example #5
0
 def __init__(self, base='.', variables={}, debug=False):
     """constructor
     @base: directory path where to search for templates
     @variables: dictionary of variables for templates
     @debug: enable debug
     """
     self.base = base.rstrip(os.sep)
     self.debug = debug
     self.log = Logger()
     loader = FileSystemLoader(self.base)
     self.env = Environment(loader=loader,
                            trim_blocks=True,
                            lstrip_blocks=True,
                            keep_trailing_newline=True,
                            block_start_string=BLOCK_START,
                            block_end_string=BLOCK_END,
                            variable_start_string=VAR_START,
                            variable_end_string=VAR_END,
                            comment_start_string=COMMENT_START,
                            comment_end_string=COMMENT_END)
     # adding variables
     self.env.globals['env'] = os.environ
     if variables:
         self.env.globals.update(variables)
     # adding header method
     self.env.globals['header'] = self._header
     # adding helper methods
     self.env.globals['exists'] = jhelpers.exists
     self.env.globals['exists_in_path'] = jhelpers.exists_in_path
     self.env.globals['basename'] = jhelpers.basename
     self.env.globals['dirname'] = jhelpers.dirname
     if self.debug:
         self.log.dbg('template additional variables: {}'.format(variables))
Example #6
0
 def __init__(self, dotpath, variables,
              dotfile_key_getter, dotfile_dst_getter,
              dotfile_path_normalizer,
              dry=False, safe=True,
              debug=False, ignore=[], showpatch=False):
     """constructor
     @dotpath: path where dotfiles are stored
     @variables: dictionary of variables for the templates
     @dotfile_key_getter: func to get a dotfile by key
     @dotfile_dst_getter: func to get a dotfile by dst
     @dotfile_path_normalizer: func to normalize dotfile dst
     @dry: simulate
     @safe: ask for overwrite if True
     @debug: enable debug
     @ignore: pattern to ignore when updating
     @showpatch: show patch if dotfile to update is a template
     """
     self.dotpath = dotpath
     self.variables = variables
     self.dotfile_key_getter = dotfile_key_getter
     self.dotfile_dst_getter = dotfile_dst_getter
     self.dotfile_path_normalizer = dotfile_path_normalizer
     self.dry = dry
     self.safe = safe
     self.debug = debug
     self.ignore = ignore
     self.showpatch = showpatch
     self.log = Logger()
Example #7
0
 def __init__(self, base='.', create=True, backup=True,
              dry=False, safe=False, workdir='~/.config/dotdrop',
              debug=False, diff=True, totemp=None, showdiff=False,
              backup_suffix='.dotdropbak', diff_cmd=''):
     """constructor
     @base: directory path where to search for templates
     @create: create directory hierarchy if missing when installing
     @backup: backup existing dotfile when installing
     @dry: just simulate
     @safe: ask for any overwrite
     @workdir: where to install template before symlinking
     @debug: enable debug
     @diff: diff when installing if True
     @totemp: deploy to this path instead of dotfile dst if not None
     @showdiff: show the diff before overwriting (or asking for)
     @backup_suffix: suffix for dotfile backup file
     @diff_cmd: diff command to use
     """
     self.create = create
     self.backup = backup
     self.dry = dry
     self.safe = safe
     self.workdir = os.path.expanduser(workdir)
     self.base = base
     self.debug = debug
     self.diff = diff
     self.totemp = totemp
     self.showdiff = showdiff
     self.backup_suffix = backup_suffix
     self.diff_cmd = diff_cmd
     self.comparing = False
     self.action_executed = False
     self.log = Logger()
Example #8
0
 def __init__(self,
              conf,
              dotpath,
              profile,
              variables,
              dry,
              safe,
              iskey=False,
              debug=False,
              ignore=[],
              showpatch=False):
     """constructor
     @conf: configuration
     @dotpath: path where dotfiles are stored
     @profile: profile selected
     @variables: dictionary of variables for the templates
     @dry: simulate
     @safe: ask for overwrite if True
     @iskey: will the update be called on keys or path
     @debug: enable debug
     @ignore: pattern to ignore when updating
     @showpatch: show patch if dotfile to update is a template
     """
     self.conf = conf
     self.dotpath = dotpath
     self.profile = profile
     self.variables = variables
     self.dry = dry
     self.safe = safe
     self.iskey = iskey
     self.debug = debug
     self.ignore = ignore
     self.showpatch = showpatch
     self.log = Logger()
Example #9
0
    def __init__(self,
                 profile,
                 conf,
                 dotpath,
                 diff_cmd,
                 dry=False,
                 safe=True,
                 debug=False,
                 keepdot=True,
                 ignore=[]):
        """constructor
        @profile: the selected profile
        @conf: configuration manager
        @dotpath: dotfiles dotpath
        @diff_cmd: diff command to use
        @dry: simulate
        @safe: ask for overwrite if True
        @debug: enable debug
        @keepdot: keep dot prefix
        @ignore: patterns to ignore when importing
        """
        self.profile = profile
        self.conf = conf
        self.dotpath = dotpath
        self.diff_cmd = diff_cmd
        self.dry = dry
        self.safe = safe
        self.debug = debug
        self.keepdot = keepdot
        self.ignore = ignore

        self.umask = get_umask()
        self.log = Logger()
Example #10
0
 def __init__(self,
              dotpath,
              dotfiles,
              variables,
              dry=False,
              safe=True,
              debug=False,
              ignore=[],
              showpatch=False):
     """constructor
     @dotpath: path where dotfiles are stored
     @dotfiles: dotfiles for this profile
     @variables: dictionary of variables for the templates
     @dry: simulate
     @safe: ask for overwrite if True
     @debug: enable debug
     @ignore: pattern to ignore when updating
     @showpatch: show patch if dotfile to update is a template
     """
     self.dotpath = dotpath
     self.dotfiles = dotfiles
     self.variables = variables
     self.dry = dry
     self.safe = safe
     self.debug = debug
     self.ignore = ignore
     self.showpatch = showpatch
     self.log = Logger()
Example #11
0
 def __init__(self, diffopts='', debug=False):
     """constructor
     @diffopts: cli switches to pass to unix diff
     @debug: enable debug
     """
     self.diffopts = diffopts
     self.debug = debug
     self.log = Logger()
Example #12
0
 def __init__(self, diff_cmd='', debug=False):
     """constructor
     @diff_cmd: diff command to use
     @debug: enable debug
     """
     self.diff_cmd = diff_cmd
     self.debug = debug
     self.log = Logger()
Example #13
0
 def __init__(self, conf, dotpath, dry, safe, debug):
     self.home = os.path.expanduser(TILD)
     self.conf = conf
     self.dotpath = dotpath
     self.dry = dry
     self.safe = safe
     self.debug = debug
     self.log = Logger()
Example #14
0
    def __init__(self,
                 base='.',
                 variables={},
                 func_file=[],
                 filter_file=[],
                 debug=False):
        """constructor
        @base: directory path where to search for templates
        @variables: dictionary of variables for templates
        @func_file: file path to load functions from
        @filter_file: file path to load filters from
        @debug: enable debug
        """
        self.base = base.rstrip(os.sep)
        self.debug = debug
        self.log = Logger()
        self.variables = {}
        loader1 = FileSystemLoader(self.base)
        loader2 = FunctionLoader(self._template_loader)
        loader = ChoiceLoader([loader1, loader2])
        self.env = Environment(loader=loader,
                               trim_blocks=True,
                               lstrip_blocks=True,
                               keep_trailing_newline=True,
                               block_start_string=BLOCK_START,
                               block_end_string=BLOCK_END,
                               variable_start_string=VAR_START,
                               variable_end_string=VAR_END,
                               comment_start_string=COMMENT_START,
                               comment_end_string=COMMENT_END,
                               undefined=StrictUndefined)

        # adding variables
        self.variables['env'] = os.environ
        if variables:
            self.variables.update(variables)

        # adding header method
        self.env.globals['header'] = self._header
        # adding helper methods
        if self.debug:
            self.log.dbg('load global functions:')
        self._load_funcs_to_dic(jhelpers, self.env.globals)
        if func_file:
            for f in func_file:
                if self.debug:
                    self.log.dbg('load custom functions from {}'.format(f))
                self._load_path_to_dic(f, self.env.globals)
        if filter_file:
            for f in filter_file:
                if self.debug:
                    self.log.dbg('load custom filters from {}'.format(f))
                self._load_path_to_dic(f, self.env.filters)
        if self.debug:
            self._debug_dict('template additional variables', variables)
Example #15
0
 def __init__(self, base='.', create=True, backup=True,
              dry=False, safe=False, debug=False, diff=True):
     self.create = create
     self.backup = backup
     self.dry = dry
     self.safe = safe
     self.base = base
     self.debug = debug
     self.diff = diff
     self.comparing = False
     self.log = Logger()
Example #16
0
    def __init__(self, path, profile=None, debug=False):
        """
        config parser
        @path: config file path
        @profile: the selected profile
        @debug: debug flag
        """
        self.path = os.path.abspath(path)
        self.profile = profile
        self.debug = debug
        self.log = Logger()
        # config needs to be written
        self.dirty = False
        # indicates the config has been updated
        self.dirty_deprecated = False

        if not os.path.exists(path):
            err = 'invalid config path: \"{}\"'.format(path)
            if self.debug:
                self.log.dbg(err)
            raise YamlException(err)

        self.yaml_dict = self._load_yaml(self.path)
        # live patch deprecated entries
        self._fix_deprecated(self.yaml_dict)
        # parse to self variables
        self._parse_main_yaml(self.yaml_dict)
        if self.debug:
            self.log.dbg('before normalization: {}'.format(self.yaml_dict))

        # resolve variables
        self.variables, self.prokeys = self._merge_variables()

        # apply variables
        self._apply_variables()

        # process imported variables (import_variables)
        self._import_variables()
        # process imported actions (import_actions)
        self._import_actions()
        # process imported profile dotfiles (import)
        self._import_profiles_dotfiles()
        # process imported configs (import_configs)
        self._import_configs()

        # process profile include
        self._resolve_profile_includes()
        # process profile ALL
        self._resolve_profile_all()
        # patch dotfiles paths
        self._resolve_dotfile_paths()

        if self.debug:
            self.log.dbg('after normalization: {}'.format(self.yaml_dict))
Example #17
0
    def __init__(self, args=None):
        """constructor
        @args: argument dictionary (if None use sys)
        """
        # attributes gotten from self.conf.get_settings()
        self.banner = None
        self.showdiff = None
        self.default_actions = []
        self.instignore = None
        self.force_chmod = None
        self.cmpignore = None
        self.impignore = None
        self.upignore = None
        self.link_on_import = None
        self.chmod_on_import = None
        self.check_version = None
        self.clear_workdir = None
        self.key_prefix = None
        self.key_separator = None

        # args parsing
        self.args = {}
        if not args:
            self.args = docopt(USAGE, version=VERSION)
        if args:
            self.args = args.copy()
        self.debug = self.args['--verbose'] or ENV_DEBUG in os.environ
        self.log = Logger(debug=self.debug)
        self.dry = self.args['--dry']
        if ENV_NODEBUG in os.environ:
            # force disabling debugs
            self.debug = False
        self.profile = self.args['--profile']
        self.confpath = self._get_config_path()
        if not self.confpath:
            raise YamlException('no config file found')
        self.log.dbg('#################################################')
        self.log.dbg('#################### DOTDROP ####################')
        self.log.dbg('#################################################')
        self.log.dbg('version: {}'.format(VERSION))
        self.log.dbg('command: {}'.format(' '.join(sys.argv)))
        self.log.dbg('config file: {}'.format(self.confpath))

        self._read_config()
        self._apply_args()
        self._fill_attr()
        if ENV_NOBANNER not in os.environ \
           and self.banner \
           and not self.args['--no-banner']:
            self._header()
        self._debug_attr()
        # start monitoring for bad attribute
        self._set_attr_err = True
Example #18
0
 def __init__(self,
              diff_cmd='',
              debug=False,
              ignore_missing_in_dotdrop=False):
     """constructor
     @diff_cmd: diff command to use
     @debug: enable debug
     """
     self.diff_cmd = diff_cmd
     self.debug = debug
     self.log = Logger()
     self.ignore_missing_in_dotdrop = ignore_missing_in_dotdrop
Example #19
0
 def __init__(self, path, profile_key, debug=False):
     """
     high level config parser
     @path: path to the config file
     @profile_key: profile key
     @debug: debug flag
     """
     self.path = path
     self.profile_key = profile_key
     self.debug = debug
     self.log = Logger()
     self._load()
Example #20
0
 def __init__(self, path, profile=None, debug=False):
     """
     high level config parser
     @path: path to the config file
     @profile: selected profile
     @debug: debug flag
     """
     self.path = path
     self.profile = profile
     self.debug = debug
     self.log = Logger()
     self._load()
Example #21
0
 def __init__(self, base='.', debug=False):
     self.base = base.rstrip(os.sep)
     loader = FileSystemLoader(self.base)
     self.env = Environment(loader=loader,
                            trim_blocks=True,
                            lstrip_blocks=True,
                            keep_trailing_newline=True,
                            block_start_string=BLOCK_START,
                            block_end_string=BLOCK_END,
                            variable_start_string=VAR_START,
                            variable_end_string=VAR_END,
                            comment_start_string=COMMENT_START,
                            comment_end_string=COMMENT_END)
     self.log = Logger(debug=debug)
Example #22
0
    def __init__(self, cfgpath, profile=None, debug=False):
        """constructor
        @cfgpath: path to the config file
        @profile: chosen profile
        @debug: enable debug
        """
        if not os.path.exists(cfgpath):
            raise ValueError('config file does not exist: {}'.format(cfgpath))
        # make sure to have an absolute path to config file
        self.cfgpath = os.path.abspath(cfgpath)
        self.debug = debug

        # init the logger
        self.log = Logger()

        # represents all entries under "config"
        # linked inside the yaml dict (self.content)
        self.lnk_settings = {}

        # represents all entries under "profiles"
        # linked inside the yaml dict (self.content)
        self.lnk_profiles = {}

        # represents all dotfiles
        # NOT linked inside the yaml dict (self.content)
        self.dotfiles = {}

        # dict of all action objects by action key
        # NOT linked inside the yaml dict (self.content)
        self.actions = {}

        # dict of all read transformation objects by trans key
        # NOT linked inside the yaml dict (self.content)
        self.trans_r = {}

        # dict of all write transformation objects by trans key
        # NOT linked inside the yaml dict (self.content)
        self.trans_w = {}

        # represents all dotfiles per profile by profile key
        # NOT linked inside the yaml dict (self.content)
        self.prodots = {}

        # represents all variables from external files
        self.ext_variables = {}
        self.ext_dynvariables = {}

        if not self._load_config(profile=profile):
            raise ValueError('config is not valid')
Example #23
0
class DictParser:

    log = Logger()

    @classmethod
    def _adjust_yaml_keys(cls, value):
        """adjust value for object 'cls'"""
        return value

    @classmethod
    def parse(cls, key, value):
        """parse (key,value) and construct object 'cls'"""
        tmp = value
        try:
            tmp = value.copy()
        except AttributeError:
            pass
        newv = cls._adjust_yaml_keys(tmp)
        if not key:
            return cls(**newv)
        return cls(key=key, **newv)

    @classmethod
    def parse_dict(cls, items):
        """parse a dictionary and construct object 'cls'"""
        if not items:
            return []
        return [cls.parse(k, v) for k, v in items.items()]
Example #24
0
 def __init__(self, cfgpath):
     if not os.path.exists(cfgpath):
         raise ValueError('config file does not exist')
     self.cfgpath = cfgpath
     self.log = Logger()
     # link inside content
     self.configs = {}
     # link inside content
     self.profiles = {}
     # not linked to content
     self.dotfiles = {}
     # not linked to content
     self.actions = {}
     # not linked to content
     self.prodots = {}
     if not self._load_file():
         raise ValueError('config is not valid')
Example #25
0
 def __init__(self, key, action):
     """constructor
     @key: action key
     @action: action string
     """
     self.key = key
     self.action = action
     self.log = Logger()
Example #26
0
 def __init__(self, base='.', create=True, backup=True,
              dry=False, safe=False, quiet=False, diff=True):
     self.create = create
     self.backup = backup
     self.dry = dry
     self.safe = safe
     self.base = base
     self.quiet = quiet
     self.diff = diff
     self.comparing = False
     self.log = Logger()
Example #27
0
class Action:
    def __init__(self, key, action):
        self.key = key
        self.action = action
        self.log = Logger()

    def execute(self):
        self.log.sub('executing \"%s\"' % (self.action))
        try:
            subprocess.call(self.action, shell=True)
        except KeyboardInterrupt:
            self.log.warn('action interrupted')

    def __str__(self):
        return 'key:%s -> \"%s\"' % (self.key, self.action)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __hash__(self):
        return hash(self.key) ^ hash(self.action)
Example #28
0
class Action:
    def __init__(self, key, action):
        self.key = key
        self.action = action
        self.log = Logger()

    def execute(self):
        ret = 1
        self.log.sub('executing \"%s\"' % (self.action))
        try:
            ret = subprocess.call(self.action, shell=True)
        except KeyboardInterrupt:
            self.log.warn('action interrupted')
        return ret == 0

    def transform(self, arg0, arg1):
        '''execute transformation with {0} and {1}
        where {0} is the file to transform and
        {1} is the result file'''
        if os.path.exists(arg1):
            msg = 'transformation destination exists: %s'
            self.log.warn(msg % (arg1))
            return False
        ret = 1
        cmd = self.action.format(arg0, arg1)
        self.log.sub('transforming with \"%s\"' % (cmd))
        try:
            ret = subprocess.call(cmd, shell=True)
        except KeyboardInterrupt:
            self.log.warn('action interrupted')
        return ret == 0

    def __str__(self):
        return 'key:%s -> \"%s\"' % (self.key, self.action)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __hash__(self):
        return hash(self.key) ^ hash(self.action)
Example #29
0
class Action:

    def __init__(self, key, action):
        self.key = key
        self.action = action
        self.log = Logger()

    def execute(self):
        self.log.sub('executing \"%s\"' % (self.action))
        try:
            subprocess.call(self.action, shell=True)
        except KeyboardInterrupt:
            self.log.warn('action interrupted')

    def __str__(self):
        return 'key:%s -> \"%s\"' % (self.key, self.action)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __hash__(self):
        return hash(self.key) ^ hash(self.action)
Example #30
0
 def __init__(self,
              base='.',
              create=True,
              backup=True,
              dry=False,
              safe=False,
              workdir='~/.config/dotdrop',
              debug=False,
              diff=True,
              totemp=None,
              showdiff=False):
     self.create = create
     self.backup = backup
     self.dry = dry
     self.safe = safe
     self.workdir = os.path.expanduser(workdir)
     self.base = base
     self.debug = debug
     self.diff = diff
     self.totemp = totemp
     self.showdiff = showdiff
     self.comparing = False
     self.action_executed = False
     self.log = Logger()
Example #31
0
 def __init__(self, profile='', base='.', variables={}, debug=False):
     self.base = base.rstrip(os.sep)
     self.debug = debug
     self.log = Logger()
     loader = FileSystemLoader(self.base)
     self.env = Environment(loader=loader,
                            trim_blocks=True,
                            lstrip_blocks=True,
                            keep_trailing_newline=True,
                            block_start_string=BLOCK_START,
                            block_end_string=BLOCK_END,
                            variable_start_string=VAR_START,
                            variable_end_string=VAR_END,
                            comment_start_string=COMMENT_START,
                            comment_end_string=COMMENT_END)
     # adding variables
     self.env.globals['env'] = os.environ
     if profile:
         self.env.globals['profile'] = profile
     self.env.globals.update(variables)
     # adding header method
     self.env.globals['header'] = self._header
     # adding helper methods
     self.env.globals['exists'] = jhelpers.exists
Example #32
0
 def __init__(self, cfgpath):
     if not os.path.exists(cfgpath):
         raise ValueError('config file does not exist')
     self.cfgpath = cfgpath
     self.log = Logger()
     # link inside content
     self.configs = {}
     # link inside content
     self.profiles = {}
     # not linked to content
     self.dotfiles = {}
     # not linked to content
     self.actions = {}
     # not linked to content
     self.prodots = {}
     if not self._load_file():
         raise ValueError('config is not valid')
Example #33
0
class Templategen:
    def __init__(self,
                 base='.',
                 variables={},
                 func_file=[],
                 filter_file=[],
                 debug=False):
        """constructor
        @base: directory path where to search for templates
        @variables: dictionary of variables for templates
        @func_file: file path to load functions from
        @filter_file: file path to load filters from
        @debug: enable debug
        """
        self.base = base.rstrip(os.sep)
        self.debug = debug
        self.log = Logger()
        self.variables = {}
        loader1 = FileSystemLoader(self.base)
        loader2 = FunctionLoader(self._template_loader)
        loader = ChoiceLoader([loader1, loader2])
        self.env = Environment(loader=loader,
                               trim_blocks=True,
                               lstrip_blocks=True,
                               keep_trailing_newline=True,
                               block_start_string=BLOCK_START,
                               block_end_string=BLOCK_END,
                               variable_start_string=VAR_START,
                               variable_end_string=VAR_END,
                               comment_start_string=COMMENT_START,
                               comment_end_string=COMMENT_END,
                               undefined=StrictUndefined)

        # adding variables
        self.variables['env'] = os.environ
        if variables:
            self.variables.update(variables)

        # adding header method
        self.env.globals['header'] = self._header
        # adding helper methods
        if self.debug:
            self.log.dbg('load global functions:')
        self._load_funcs_to_dic(jhelpers, self.env.globals)
        if func_file:
            for f in func_file:
                if self.debug:
                    self.log.dbg('load custom functions from {}'.format(f))
                self._load_path_to_dic(f, self.env.globals)
        if filter_file:
            for f in filter_file:
                if self.debug:
                    self.log.dbg('load custom filters from {}'.format(f))
                self._load_path_to_dic(f, self.env.filters)
        if self.debug:
            self._debug_dict('template additional variables', variables)

    def generate(self, src):
        """
        render template from path
        may raise a UndefinedException
        in case a variable is undefined
        """
        if not os.path.exists(src):
            return ''
        try:
            return self._handle_file(src)
        except UndefinedError as e:
            err = 'undefined variable: {}'.format(e.message)
            raise UndefinedException(err)

    def generate_string(self, string):
        """
        render template from string
        may raise a UndefinedException
        in case a variable is undefined
        """
        if not string:
            return ''
        try:
            return self.env.from_string(string).render(self.variables)
        except UndefinedError as e:
            err = 'undefined variable: {}'.format(e.message)
            raise UndefinedException(err)

    def add_tmp_vars(self, newvars={}):
        """add vars to the globals, make sure to call restore_vars"""
        saved_variables = self.variables.copy()
        if not newvars:
            return saved_variables
        self.variables.update(newvars)
        return saved_variables

    def restore_vars(self, saved_globals):
        """restore globals from add_tmp_vars"""
        self.variables = saved_globals.copy()

    def update_variables(self, variables):
        """update variables"""
        self.variables.update(variables)

    def _load_path_to_dic(self, path, dic):
        mod = utils.get_module_from_path(path)
        if not mod:
            self.log.warn('cannot load module \"{}\"'.format(path))
            return
        self._load_funcs_to_dic(mod, dic)

    def _load_funcs_to_dic(self, mod, dic):
        """dynamically load functions from module to dic"""
        if not mod or not dic:
            return
        funcs = utils.get_module_functions(mod)
        for name, func in funcs:
            if self.debug:
                self.log.dbg('load function \"{}\"'.format(name))
            dic[name] = func

    def _header(self, prepend=''):
        """add a comment usually in the header of a dotfile"""
        return '{}{}'.format(prepend, utils.header())

    def _handle_file(self, src):
        """generate the file content from template"""
        try:
            import magic
            filetype = magic.from_file(src, mime=True)
            if self.debug:
                self.log.dbg('using \"magic\" for filetype identification')
        except ImportError:
            # fallback
            _, filetype = utils.run(['file', '-b', '--mime-type', src],
                                    debug=self.debug)
            if self.debug:
                self.log.dbg('using \"file\" for filetype identification')
            filetype = filetype.strip()
        istext = self._is_text(filetype)
        if self.debug:
            self.log.dbg('filetype \"{}\": {}'.format(src, filetype))
        if self.debug:
            self.log.dbg('is text \"{}\": {}'.format(src, istext))
        if not istext:
            return self._handle_bin_file(src)
        return self._handle_text_file(src)

    def _is_text(self, fileoutput):
        """return if `file -b` output is ascii text"""
        out = fileoutput.lower()
        if out.startswith('text'):
            return True
        if 'empty' in out:
            return True
        if 'json' in out:
            return True
        return False

    def _template_loader(self, relpath):
        """manually load template when outside of base"""
        path = os.path.join(self.base, relpath)
        path = os.path.normpath(path)
        if not os.path.exists(path):
            raise TemplateNotFound(path)
        with open(path, 'r') as f:
            content = f.read()
        return content

    def _handle_text_file(self, src):
        """write text to file"""
        template_rel_path = os.path.relpath(src, self.base)
        try:
            template = self.env.get_template(template_rel_path)
            content = template.render(self.variables)
        except UnicodeDecodeError:
            data = self._read_bad_encoded_text(src)
            content = self.generate_string(data)
        return content.encode('utf-8')

    def _handle_bin_file(self, src):
        """write binary to file"""
        # this is dirty
        if not src.startswith(self.base):
            src = os.path.join(self.base, src)
        with open(src, 'rb') as f:
            content = f.read()
        return content

    def _read_bad_encoded_text(self, path):
        """decode non utf-8 data"""
        with open(path, 'rb') as f:
            data = f.read()
        return data.decode('utf-8', 'replace')

    @staticmethod
    def is_template(path, ignore=[]):
        """recursively check if any file is a template within path"""
        path = os.path.expanduser(path)

        if utils.must_ignore([path], ignore, debug=False):
            return False
        if not os.path.exists(path):
            return False
        if os.path.isfile(path):
            # is file
            return Templategen._is_template(path, ignore=ignore)
        for entry in os.listdir(path):
            fpath = os.path.join(path, entry)
            if not os.path.isfile(fpath):
                # recursively explore directory
                if Templategen.is_template(fpath, ignore=ignore):
                    return True
            else:
                # check if file is a template
                if Templategen._is_template(fpath, ignore=ignore):
                    return True
        return False

    @staticmethod
    def var_is_template(string):
        """check if variable contains template(s)"""
        return VAR_START in str(string)

    @staticmethod
    def _is_template(path, ignore):
        """test if file pointed by path is a template"""
        if utils.must_ignore([path], ignore, debug=False):
            return False
        if not os.path.isfile(path):
            return False
        if os.stat(path).st_size == 0:
            return False
        markers = [BLOCK_START, VAR_START, COMMENT_START]
        patterns = [re.compile(marker.encode()) for marker in markers]
        try:
            with io.open(path, "r", encoding="utf-8") as f:
                m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
                for pattern in patterns:
                    if pattern.search(m):
                        return True
        except UnicodeDecodeError:
            # is binary so surely no template
            return False
        return False

    def _debug_dict(self, title, elems):
        """pretty print dict"""
        if not self.debug:
            return
        self.log.dbg('{}:'.format(title))
        if not elems:
            return
        for k, v in elems.items():
            self.log.dbg('  - \"{}\": {}'.format(k, v))
Example #34
0
class Cfg:
    key_all = 'ALL'
    key_config = 'config'
    key_dotfiles = 'dotfiles'
    key_actions = 'actions'
    key_dotpath = 'dotpath'
    key_profiles = 'profiles'
    key_profiles_dots = 'dotfiles'
    key_profiles_incl = 'include'
    key_dotfiles_src = 'src'
    key_dotfiles_dst = 'dst'
    key_dotfiles_link = 'link'
    key_dotfiles_actions = 'actions'

    def __init__(self, cfgpath):
        if not os.path.exists(cfgpath):
            raise ValueError('config file does not exist')
        self.cfgpath = cfgpath
        self.log = Logger()
        # link inside content
        self.configs = {}
        # link inside content
        self.profiles = {}
        # not linked to content
        self.dotfiles = {}
        # not linked to content
        self.actions = {}
        # not linked to content
        self.prodots = {}
        if not self._load_file():
            raise ValueError('config is not valid')

    def _load_file(self):
        with open(self.cfgpath, 'r') as f:
            self.content = yaml.load(f)
        if not self._is_valid():
            return False
        return self._parse()

    def _is_valid(self):
        if self.key_profiles not in self.content:
            self.log.err('missing \"%s\" in config' % (self.key_profiles))
            return False
        if self.key_config not in self.content:
            self.log.err('missing \"%s\" in config' % (self.key_config))
            return False
        if self.key_dotfiles not in self.content:
            self.log.err('missing \"%s\" in config' % (self.key_dotfiles))
            return False
        if self.content[self.key_profiles]:
            # make sure dotfiles are in a sub called "dotfiles"
            pro = self.content[self.key_profiles]
            tosave = False
            for k in pro.keys():
                if self.key_profiles_dots not in pro[k]:
                    pro[k] = {self.key_profiles_dots: pro[k]}
                    tosave = True
            if tosave:
                # save the new config file
                self._save(self.content, self.cfgpath)

        return True

    def _parse_actions(self, actions, entries):
        """ parse actions specified for an element """
        res = []
        for entry in entries:
            if entry not in actions.keys():
                self.log.warn('unknown action \"%s\"' % (entry))
                continue
            res.append(actions[entry])
        return res

    def _parse(self):
        """ parse config file """
        # parse all actions
        if self.key_actions in self.content:
            if self.content[self.key_actions] is not None:
                for k, v in self.content[self.key_actions].items():
                    self.actions[k] = Action(k, v)

        # parse the profiles
        self.profiles = self.content[self.key_profiles]
        if self.profiles is None:
            self.content[self.key_profiles] = {}
            self.profiles = self.content[self.key_profiles]
        for k, v in self.profiles.items():
            if v[self.key_profiles_dots] is None:
                v[self.key_profiles_dots] = []

        # parse the configs
        self.configs = self.content[self.key_config]

        # parse the dotfiles
        if not self.content[self.key_dotfiles]:
            self.content[self.key_dotfiles] = {}
        for k, v in self.content[self.key_dotfiles].items():
            src = v[self.key_dotfiles_src]
            dst = v[self.key_dotfiles_dst]
            link = v[self.key_dotfiles_link] if self.key_dotfiles_link \
                in v else False
            entries = v[self.key_dotfiles_actions] if \
                self.key_dotfiles_actions in v else []
            actions = self._parse_actions(self.actions, entries)
            self.dotfiles[k] = Dotfile(k, dst, src,
                                       link=link, actions=actions)

        # assign dotfiles to each profile
        for k, v in self.profiles.items():
            self.prodots[k] = []
            if self.key_profiles_dots not in v:
                v[self.key_profiles_dots] = []
            if not v[self.key_profiles_dots]:
                continue
            dots = v[self.key_profiles_dots]
            if self.key_all in dots:
                self.prodots[k] = list(self.dotfiles.values())
            else:
                self.prodots[k].extend([self.dotfiles[d] for d in dots])

        # handle "include" for each profile
        for k in self.profiles.keys():
            dots = self._get_included_dotfiles(k)
            self.prodots[k].extend(dots)
            # no duplicates
            self.prodots[k] = list(set(self.prodots[k]))

        # make sure we have an absolute dotpath
        self.curdotpath = self.configs[self.key_dotpath]
        self.configs[self.key_dotpath] = self.get_abs_dotpath(self.curdotpath)
        return True

    def _get_included_dotfiles(self, profile):
        included = []
        if self.key_profiles_incl not in self.profiles[profile]:
            return included
        if not self.profiles[profile][self.key_profiles_incl]:
            return included
        for other in self.profiles[profile][self.key_profiles_incl]:
            if other not in self.prodots:
                self.log.warn('unknown included profile \"%s\"' % (other))
                continue
            included.extend(self.prodots[other])
        return included

    def get_abs_dotpath(self, dotpath):
        """ transform dotpath to an absolute path """
        if not dotpath.startswith(os.sep):
            absconf = os.path.join(os.path.dirname(
                self.cfgpath), dotpath)
            return absconf
        return dotpath

    def _save(self, content, path):
        ret = False
        with open(path, 'w') as f:
            ret = yaml.dump(content, f,
                            default_flow_style=False, indent=2)
        return ret

    def new(self, dotfile, profile, link=False):
        """ import new dotfile """
        # keep it short
        home = os.path.expanduser('~')
        dotfile.dst = dotfile.dst.replace(home, '~')

        # ensure content is valid
        if profile not in self.profiles:
            self.profiles[profile] = {self.key_profiles_dots: []}

        # when dotfile already there
        if dotfile.key in self.dotfiles.keys():
            # already in it
            if profile in self.prodots and dotfile in self.prodots[profile]:
                self.log.err('\"%s\" already present' % (dotfile.key))
                return False

            # add for this profile
            if profile not in self.prodots:
                self.prodots[profile] = []
            self.prodots[profile].append(dotfile)

            ent = self.content[self.key_profiles][profile]
            if self.key_all not in ent[self.key_profiles_dots]:
                ent[self.key_profiles_dots].append(dotfile.key)
            return True

        # adding the dotfile
        dots = self.content[self.key_dotfiles]
        dots[dotfile.key] = {
            self.key_dotfiles_dst: dotfile.dst,
            self.key_dotfiles_src: dotfile.src,
        }
        if link:
            # avoid putting it everywhere
            dots[dotfile.key][self.key_dotfiles_link] = True

        # link it to this profile
        pro = self.content[self.key_profiles][profile]
        if self.key_all not in pro[self.key_profiles_dots]:
            pro[self.key_profiles_dots].append(dotfile.key)

        return True

    def get_dotfiles(self, profile):
        """ returns a list of dotfiles for a specific profile """
        if profile not in self.prodots:
            return []
        return sorted(self.prodots[profile],
                      key=lambda x: str(x.key),
                      reverse=True)

    def get_profiles(self):
        """ returns all defined profiles """
        return self.profiles.keys()

    def get_configs(self):
        """ returns all defined configs """
        return self.configs.copy()

    def dump(self):
        """ dump config file """
        # temporary reset dotpath
        tmp = self.configs[self.key_dotpath]
        self.configs[self.key_dotpath] = self.curdotpath
        ret = yaml.dump(self.content, default_flow_style=False, indent=2)
        # restore dotpath
        self.configs[self.key_dotpath] = tmp
        return ret

    def save(self):
        """ save config file to path """
        # temporary reset dotpath
        tmp = self.configs[self.key_dotpath]
        self.configs[self.key_dotpath] = self.curdotpath
        ret = self._save(self.content, self.cfgpath)
        # restore dotpath
        self.configs[self.key_dotpath] = tmp
        return ret
Example #35
0
class Installer:

    BACKUP_SUFFIX = '.dotdropbak'

    def __init__(self, base='.', create=True, backup=True,
                 dry=False, safe=False, quiet=False, diff=True):
        self.create = create
        self.backup = backup
        self.dry = dry
        self.safe = safe
        self.base = base
        self.quiet = quiet
        self.diff = diff
        self.comparing = False
        self.log = Logger()

    def install(self, templater, profile, src, dst):
        '''Install the dotfile for profile "profile"'''
        src = os.path.join(self.base, os.path.expanduser(src))
        dst = os.path.join(self.base, os.path.expanduser(dst))
        if os.path.isdir(src):
            return self._handle_dir(templater, profile, src, dst)
        return self._handle_file(templater, profile, src, dst)

    def link(self, src, dst):
        '''Sets src as the link target of dst'''
        src = os.path.join(self.base, os.path.expanduser(src))
        dst = os.path.join(self.base, os.path.expanduser(dst))
        if os.path.exists(dst):
            if os.path.realpath(dst) == os.path.realpath(src):
                self.log.sub('ignoring "%s", link exists' % dst)
                return []
            if self.dry:
                self.log.dry('would remove %s and link it to %s'
                             % (dst, src))
                return []
            if self.safe and \
                    not self.log.ask('Remove "%s" for link creation?' % dst):
                self.log.warn('ignoring "%s", link was not created' % dst)
                return []
            try:
                utils.remove(dst)
            except OSError:
                self.log.err('something went wrong with %s' % src)
                return []
        if self.dry:
            self.log.dry('would link %s to %s' % (dst, src))
            return []
        os.symlink(src, dst)
        self.log.sub('linked %s to %s' % (dst, src))
        # Follows original developer's behavior
        return [(src, dst)]

    def _handle_file(self, templater, profile, src, dst):
        '''Install a file using templater for "profile"'''
        content = templater.generate(src, profile)
        if content is None:
            self.log.err('generate from template \"%s\"' % (src))
            return []
        if not os.path.exists(src):
            self.log.err('installing %s to %s' % (src, dst))
            return []
        st = os.stat(src)
        ret = self._write(dst, content, st.st_mode)
        if ret < 0:
            self.log.err('installing %s to %s' % (src, dst))
            return []
        if ret > 0:
            if not self.quiet:
                self.log.sub('ignoring \"%s\", same content' % (dst))
            return []
        if ret == 0:
            if not self.dry and not self.comparing:
                self.log.sub('copied %s to %s' % (src, dst))
            return [(src, dst)]
        return []

    def _handle_dir(self, templater, profile, src, dst):
        '''Install a folder using templater for "profile"'''
        ret = []
        for entry in os.listdir(src):
            f = os.path.join(src, entry)
            if not os.path.isdir(f):
                res = self._handle_file(
                    templater, profile, f, os.path.join(dst, entry))
                ret.extend(res)
            else:
                res = self._handle_dir(
                    templater, profile, f, os.path.join(dst, entry))
                ret.extend(res)
        return ret

    def _fake_diff(self, dst, content):
        '''Fake diff by comparing file content with "content"'''
        cur = ''
        with open(dst, 'br') as f:
            cur = f.read()
        return cur == content

    def _write(self, dst, content, rights):
        '''Write file'''
        if self.dry:
            self.log.dry('would install %s' % (dst))
            return 0
        if os.path.exists(dst):
            if self.diff and self._fake_diff(dst, content):
                return 1
            if self.safe and not self.log.ask('Overwrite \"%s\"' % (dst)):
                self.log.warn('ignoring \"%s\", already present' % (dst))
                return 1
        if self.backup and os.path.exists(dst):
            self._backup(dst)
        base = os.path.dirname(dst)
        if not self._create_dirs(base):
            self.log.err('creating directory for %s' % (dst))
            return -1
        with open(dst, 'wb') as f:
            f.write(content)
        os.chmod(dst, rights)
        return 0

    def _create_dirs(self, folder):
        '''mkdir -p "folder"'''
        if not self.create and not os.path.exists(folder):
            return False
        if os.path.exists(folder):
            return True
        os.makedirs(folder)
        return os.path.exists(folder)

    def _backup(self, path):
        '''Backup the file'''
        if self.dry:
            return
        dst = path.rstrip(os.sep) + self.BACKUP_SUFFIX
        self.log.log('backup %s to %s' % (path, dst))
        os.rename(path, dst)

    def _install_to_temp(self, templater, profile, src, dst, tmpfolder):
        '''Install a dotfile to a tempfolder for comparing'''
        sub = dst
        if dst[0] == os.sep:
            sub = dst[1:]
        tmpdst = os.path.join(tmpfolder, sub)
        return self.install(templater, profile, src, tmpdst), tmpdst

    def compare(self, templater, tmpfolder, profile, src, dst, opts=''):
        '''Compare temporary generated dotfile with local one'''
        self.comparing = True
        retval = False, ''
        drysaved = self.dry
        self.dry = False
        diffsaved = self.diff
        self.diff = False
        src = os.path.expanduser(src)
        dst = os.path.expanduser(dst)
        if not os.path.exists(dst):
            retval = False, '\"%s\" does not exist on local\n' % (dst)
        else:
            ret, tmpdst = self._install_to_temp(templater,
                                                profile,
                                                src, dst,
                                                tmpfolder)
            if ret:
                diff = utils.diff(tmpdst, dst, log=False,
                                  raw=False, opts=opts)
                if diff == '':
                    retval = True, ''
                else:
                    retval = False, diff
        self.dry = drysaved
        self.diff = diffsaved
        self.comparing = False
        return retval
Example #36
0
 def __init__(self, key, action):
     self.key = key
     self.action = action
     self.log = Logger()