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()
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')
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
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
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))
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()
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()
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()
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()
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()
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()
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()
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()
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 __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()
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))
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
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
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()
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()
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)
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')
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()]
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 __init__(self, key, action): """constructor @key: action key @action: action string """ self.key = key self.action = action self.log = Logger()
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()
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)
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)
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()
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
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))
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
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
def __init__(self, key, action): self.key = key self.action = action self.log = Logger()