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.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 _redefine_templater(self): """create templater based on current variables""" fufile = self.settings[Settings.key_func_file] fifile = self.settings[Settings.key_filter_file] self._tmpl = Templategen(variables=self.variables, func_file=fufile, filter_file=fifile)
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 _resolve_dotfile_paths(self): """resolve dotfile paths""" t = Templategen(variables=self.variables, func_file=self.settings[Settings.key_func_file], filter_file=self.settings[Settings.key_filter_file]) for dotfile in self.dotfiles.values(): # src src = dotfile[self.key_dotfile_src] if not src: dotfile[self.key_dotfile_src] = '' else: new = t.generate_string(src) if new != src and self.debug: msg = 'dotfile src: \"{}\" -> \"{}\"'.format(src, new) self.log.dbg(msg) src = new src = os.path.join(self.settings[self.key_settings_dotpath], src) dotfile[self.key_dotfile_src] = self._norm_path(src) # dst dst = dotfile[self.key_dotfile_dst] if not dst: dotfile[self.key_dotfile_dst] = '' else: new = t.generate_string(dst) if new != dst and self.debug: msg = 'dotfile dst: \"{}\" -> \"{}\"'.format(dst, new) self.log.dbg(msg) dst = new dotfile[self.key_dotfile_dst] = self._norm_path(dst)
def _load_ext_variables(self, paths, profile=None): """load external variables""" variables = {} dvariables = {} cur_vars = self.get_variables(profile, debug=self.debug) t = Templategen(variables=cur_vars) for path in paths: path = self._abs_path(path) path = t.generate_string(path) if self.debug: self.log.dbg('loading variables from {}'.format(path)) content = self._load_yaml(path) if not content: self.log.warn('\"{}\" does not exist'.format(path)) continue # variables if self.key_variables in content: variables.update(content[self.key_variables]) # dynamic variables if self.key_dynvariables in content: dvariables.update(content[self.key_dynvariables]) self.ext_variables = variables if self.debug: self.log.dbg('loaded ext variables: {}'.format(variables)) self.ext_dynvariables = dvariables if self.debug: self.log.dbg('loaded ext dynvariables: {}'.format(dvariables))
def _get_imported_dotfiles_keys(self, profile): """import dotfiles from external file""" keys = [] if self.key_profiles_imp not in self.lnk_profiles[profile]: return keys variables = self.get_variables(profile, debug=self.debug) t = Templategen(variables=variables) paths = self.lnk_profiles[profile][self.key_profiles_imp] for path in paths: path = self._abs_path(path) path = t.generate_string(path) if self.debug: self.log.dbg('loading dotfiles from {}'.format(path)) content = self._load_yaml(path) if not content: self.log.warn('\"{}\" does not exist'.format(path)) continue if self.key_profiles_dots not in content: self.log.warn('not dotfiles in \"{}\"'.format(path)) continue df = content[self.key_profiles_dots] if self.debug: self.log.dbg('imported dotfiles keys: {}'.format(df)) keys.extend(df) return keys
def eval_dotfiles(self, profile, debug=False): """resolve dotfiles src/dst templates""" t = Templategen(profile=profile, variables=self.get_variables(), debug=debug) for d in self.get_dotfiles(profile): d.src = t.generate_string(d.src) d.dst = t.generate_string(d.dst)
def _merge_variables(self): """ resolve all variables across the config apply them to any needed entries and return the full list of variables """ if self.debug: self.log.dbg('get local variables') # get all variables from local and resolve var = self._get_variables_dict(self.profile) # get all dynvariables from local and resolve dvar = self._get_dvariables_dict() # temporarly resolve all variables for "include" merged = self._merge_dict(dvar, var) merged = self._rec_resolve_vars(merged) self._debug_vars(merged) # exec dynvariables self._shell_exec_dvars(dvar.keys(), merged) if self.debug: self.log.dbg('local variables resolved') self._debug_vars(merged) # resolve profile includes t = Templategen(variables=merged, func_file=self.settings[Settings.key_func_file], filter_file=self.settings[Settings.key_filter_file]) for k, v in self.profiles.items(): if self.key_profile_include in v: new = [] for k in v[self.key_profile_include]: new.append(t.generate_string(k)) v[self.key_profile_include] = new # now get the included ones pro_var = self._get_included_variables(self.profile, seen=[self.profile]) pro_dvar = self._get_included_dvariables(self.profile, seen=[self.profile]) # exec incl dynvariables self._shell_exec_dvars(pro_dvar.keys(), pro_dvar) # merge all and resolve merged = self._merge_dict(pro_var, merged) merged = self._merge_dict(pro_dvar, merged) merged = self._rec_resolve_vars(merged) if self.debug: self.log.dbg('resolve all uses of variables in config') self._debug_vars(merged) prokeys = list(pro_var.keys()) + list(pro_dvar.keys()) return merged, prokeys
def eval_dotfiles(self, profile, variables, debug=False): """resolve dotfiles src/dst/actions templating for this profile""" t = Templategen(variables=variables) for d in self.get_dotfiles(profile): # src and dst path d.src = t.generate_string(d.src) d.dst = t.generate_string(d.dst) # pre actions if self.key_actions_pre in d.actions: for action in d.actions[self.key_actions_pre]: action.action = t.generate_string(action.action) # post actions if self.key_actions_post in d.actions: for action in d.actions[self.key_actions_post]: action.action = t.generate_string(action.action)
def _template_item(self, item, exc_if_fail=True): """ template an item using the templategen will raise an exception if template failed and exc_if_fail """ if not Templategen.var_is_template(item): return item try: val = item while Templategen.var_is_template(val): val = self._tmpl.generate_string(val) except UndefinedException as e: if exc_if_fail: raise e return val
def _is_template(self, path): if not Templategen.is_template(path): if self.debug: self.log.dbg('{} is NO template'.format(path)) return False self.log.warn('{} uses template, update manually'.format(path)) return True
def compare(self, opts, conf, tmp, nbdotfiles): dotfiles = conf.get_dotfiles(opts['profile']) self.assertTrue(len(dotfiles) == nbdotfiles) t = Templategen(base=opts['dotpath'], debug=True) inst = Installer(create=opts['create'], backup=opts['backup'], dry=opts['dry'], base=opts['dotpath'], debug=True) comp = Comparator() results = {} for dotfile in dotfiles: ret, insttmp = inst.install_to_temp(t, tmp, dotfile.src, dotfile.dst) if not ret: results[path] = False continue diff = comp.compare(insttmp, dotfile.dst, ignore=['whatever', 'whatelse']) print('XXXX diff for {} and {}:\n{}'.format( dotfile.src, dotfile.dst, diff)) path = os.path.expanduser(dotfile.dst) results[path] = diff == '' return results
def cmd_files(o): """list all dotfiles for a specific profile""" if o.profile not in [p.key for p in o.profiles]: LOG.warn('unknown profile \"{}\"'.format(o.profile)) return what = 'Dotfile(s)' if o.files_templateonly: what = 'Template(s)' LOG.emph('{} for profile \"{}\":\n'.format(what, o.profile)) for dotfile in o.dotfiles: if o.files_templateonly: src = os.path.join(o.dotpath, dotfile.src) if not Templategen.is_template(src): continue if o.files_grepable: fmt = '{},dst:{},src:{},link:{}' fmt = fmt.format(dotfile.key, dotfile.dst, dotfile.src, dotfile.link.name.lower()) if dotfile.chmod: fmt += ',chmod:{:o}' else: fmt += ',chmod:None' LOG.raw(fmt) else: LOG.log('{}'.format(dotfile.key), bold=True) LOG.sub('dst: {}'.format(dotfile.dst)) LOG.sub('src: {}'.format(dotfile.src)) LOG.sub('link: {}'.format(dotfile.link.name.lower())) if dotfile.chmod: LOG.sub('chmod: {:o}'.format(dotfile.chmod)) LOG.log('')
def _apply_variables(self): """template any needed parts of the config""" t = Templategen(variables=self.variables) # import_actions new = [] entries = self.settings.get(self.key_import_actions, []) new = self._template_list(t, entries) if new: self.settings[self.key_import_actions] = new # import_configs entries = self.settings.get(self.key_import_configs, []) new = self._template_list(t, entries) if new: self.settings[self.key_import_configs] = new # import_variables entries = self.settings.get(self.key_import_variables, []) new = self._template_list(t, entries) if new: self.settings[self.key_import_variables] = new # profile's import for k, v in self.profiles.items(): entries = v.get(self.key_import_profile_dfs, []) new = self._template_list(t, entries) if new: v[self.key_import_profile_dfs] = new
def link(self, templater, src, dst, actions=[]): """set src as the link target of dst""" if self.debug: self.log.dbg('link {} to {}'.format(src, dst)) self.action_executed = False src = os.path.normpath(os.path.join(self.base, os.path.expanduser(src))) if not os.path.exists(src): self.log.err('source dotfile does not exist: {}'.format(src)) return [] dst = os.path.normpath(os.path.expanduser(dst)) if self.totemp: # ignore actions return self.install(templater, src, dst, actions=[]) if Templategen.is_template(src): if self.debug: self.log.dbg('dotfile is a template') self.log.dbg('install to {} and symlink'.format(self.workdir)) tmp = self._pivot_path(dst, self.workdir, striphome=True) i = self.install(templater, src, tmp, actions=actions) if not i and not os.path.exists(tmp): return [] src = tmp return self._link(src, dst, actions=actions)
def _get_templater(o): """get an templater instance""" t = Templategen(base=o.dotpath, variables=o.variables, func_file=o.func_file, filter_file=o.filter_file, debug=o.debug) return t
def _detail(dotpath, dotfile): """print details on all files under a dotfile entry""" LOG.log('{} (dst: \"{}\", link: {})'.format(dotfile.key, dotfile.dst, dotfile.link.name.lower())) path = os.path.join(dotpath, os.path.expanduser(dotfile.src)) if not os.path.isdir(path): template = 'no' if Templategen.is_template(path): template = 'yes' LOG.sub('{} (template:{})'.format(path, template)) else: for root, dir, files in os.walk(path): for f in files: p = os.path.join(root, f) template = 'no' if Templategen.is_template(p): template = 'yes' LOG.sub('{} (template:{})'.format(p, template))
def install(opts, conf): """install all dotfiles for this profile""" dotfiles = conf.get_dotfiles(opts['profile']) if dotfiles == []: msg = 'no dotfiles defined for this profile (\"{}\")' LOG.err(msg.format(opts['profile'])) return False t = Templategen(base=opts['dotpath'], debug=opts['debug']) inst = Installer(create=opts['create'], backup=opts['backup'], dry=opts['dry'], safe=opts['safe'], base=opts['dotpath'], diff=opts['installdiff'], debug=opts['debug']) installed = [] for dotfile in dotfiles: if dotfile.actions and Cfg.key_actions_pre in dotfile.actions: for action in dotfile.actions[Cfg.key_actions_pre]: if opts['dry']: LOG.dry('would execute action: {}'.format(action)) else: if opts['debug']: LOG.dbg('executing pre action {}'.format(action)) action.execute() if opts['debug']: LOG.dbg('installing {}'.format(dotfile)) if hasattr(dotfile, 'link') and dotfile.link: r = inst.link(dotfile.src, dotfile.dst) else: src = dotfile.src tmp = None if dotfile.trans: tmp = apply_trans(opts, dotfile) if not tmp: continue src = tmp r = inst.install(t, opts['profile'], src, dotfile.dst) if tmp: tmp = os.path.join(opts['dotpath'], tmp) if os.path.exists(tmp): remove(tmp) if len(r) > 0: if Cfg.key_actions_post in dotfile.actions: actions = dotfile.actions[Cfg.key_actions_post] # execute action for action in actions: if opts['dry']: LOG.dry('would execute action: {}'.format(action)) else: if opts['debug']: LOG.dbg('executing post action {}'.format(action)) action.execute() installed.extend(r) LOG.log('\n{} dotfile(s) installed.'.format(len(installed))) return True
def _resolve_dotfile_paths(self): """resolve dotfile paths""" t = Templategen(variables=self.variables) for dotfile in self.dotfiles.values(): # src src = dotfile[self.key_dotfile_src] new = t.generate_string(src) if new != src and self.debug: self.log.dbg('dotfile: {} -> {}'.format(src, new)) src = new src = os.path.join(self.settings[self.key_settings_dotpath], src) dotfile[self.key_dotfile_src] = self._norm_path(src) # dst dst = dotfile[self.key_dotfile_dst] new = t.generate_string(dst) if new != dst and self.debug: self.log.dbg('dotfile: {} -> {}'.format(dst, new)) dst = new dotfile[self.key_dotfile_dst] = self._norm_path(dst)
def _detail(dotpath, dotfile): """display details on all files under a dotfile entry""" entry = '{}'.format(dotfile.key) attribs = [] attribs.append('dst: \"{}\"'.format(dotfile.dst)) attribs.append('link: \"{}\"'.format(dotfile.link.name.lower())) attribs.append('chmod: \"{}\"'.format(dotfile.chmod)) LOG.log('{} ({})'.format(entry, ', '.join(attribs))) path = os.path.join(dotpath, os.path.expanduser(dotfile.src)) if not os.path.isdir(path): template = 'no' if dotfile.template and Templategen.is_template(path): template = 'yes' LOG.sub('{} (template:{})'.format(path, template)) else: for root, _, files in os.walk(path): for f in files: p = os.path.join(root, f) template = 'no' if dotfile.template and Templategen.is_template(p): template = 'yes' LOG.sub('{} (template:{})'.format(p, template))
def compare(self, opts, conf, tmp, nbdotfiles): dotfiles = conf.get_dotfiles(opts['profile']) self.assertTrue(len(dotfiles) == nbdotfiles) t = Templategen(base=opts['dotpath']) inst = Installer(create=opts['create'], backup=opts['backup'], dry=opts['dry'], base=opts['dotpath'], quiet=True) results = {} for dotfile in dotfiles: same, _ = inst.compare(t, tmp, opts['profile'], dotfile.src, dotfile.dst) path = os.path.expanduser(dotfile.dst) results[path] = same return results
def link(self, templater, src, dst, actionexec=None, template=True): """ set src as the link target of dst @templater: the templater @src: dotfile source path in dotpath @dst: dotfile destination path in the FS @actionexec: action executor callback @template: template this dotfile return - True, None : success - False, error_msg : error - False, None : ignored """ if self.debug: self.log.dbg('link \"{}\" to \"{}\"'.format(src, dst)) if not dst or not src: if self.debug: self.log.dbg('empty dst for {}'.format(src)) return self._log_install(True, None) self.action_executed = False src = os.path.normpath(os.path.join(self.base, os.path.expanduser(src))) if not os.path.exists(src): err = 'source dotfile does not exist: {}'.format(src) return self._log_install(False, err) dst = os.path.normpath(os.path.expanduser(dst)) if self.totemp: # ignore actions b, e = self.install(templater, src, dst, actionexec=None, template=template) return self._log_install(b, e) if template and Templategen.is_template(src): if self.debug: self.log.dbg('dotfile is a template') self.log.dbg('install to {} and symlink'.format(self.workdir)) tmp = self._pivot_path(dst, self.workdir, striphome=True) i, err = self.install(templater, src, tmp, actionexec=actionexec, template=template) if not i and not os.path.exists(tmp): return self._log_install(i, err) src = tmp b, e = self._link(src, dst, actionexec=actionexec) return self._log_install(b, e)
def _apply_variables(self): """template any needed parts of the config""" t = Templategen(variables=self.variables) # dotfiles src/dst/actions keys for k, v in self.dotfiles.items(): # src src = v.get(self.key_dotfile_src) v[self.key_dotfile_src] = t.generate_string(src) # dst dst = v.get(self.key_dotfile_dst) v[self.key_dotfile_dst] = t.generate_string(dst) # import_actions new = [] entries = self.settings.get(self.key_import_actions, []) new = self._template_list(t, entries) if new: self.settings[self.key_import_actions] = new # import_configs entries = self.settings.get(self.key_import_configs, []) new = self._template_list(t, entries) if new: self.settings[self.key_import_configs] = new # import_variables entries = self.settings.get(self.key_import_variables, []) new = self._template_list(t, entries) if new: self.settings[self.key_import_variables] = new # profile's import for k, v in self.profiles.items(): entries = v.get(self.key_import_profile_dfs, []) new = self._template_list(t, entries) if new: v[self.key_import_profile_dfs] = new
def _get_included_dotfiles(self, profile, seen=[]): """find all dotfiles for a specific profile when using the include keyword""" if profile in seen: self.log.err('cyclic include in profile \"{}\"'.format(profile)) return False, [] if not self.lnk_profiles[profile]: return True, [] dotfiles = self.prodots[profile] if self.key_profiles_incl not in self.lnk_profiles[profile]: # no include found return True, dotfiles if not self.lnk_profiles[profile][self.key_profiles_incl]: # empty include found return True, dotfiles variables = self.get_variables(profile, debug=self.debug) t = Templategen(variables=variables) if self.debug: self.log.dbg('handle includes for profile \"{}\"'.format(profile)) for other in self.lnk_profiles[profile][self.key_profiles_incl]: # resolve include value other = t.generate_string(other) if other not in self.prodots: # no such profile self.log.warn('unknown included profile \"{}\"'.format(other)) continue if self.debug: msg = 'include dotfiles from \"{}\" into \"{}\"' self.log.dbg(msg.format(other, profile)) lseen = seen.copy() lseen.append(profile) ret, recincludes = self._get_included_dotfiles(other, seen=lseen) if not ret: return False, [] dotfiles.extend(recincludes) dotfiles.extend(self.prodots[other]) return True, dotfiles
def _get_profile_included_vars(self, tvars): """resolve profile included variables/dynvariables""" t = Templategen(variables=tvars, func_file=self.settings[Settings.key_func_file], filter_file=self.settings[Settings.key_filter_file]) for k, v in self.profiles.items(): if self.key_profile_include in v: new = [] for x in v[self.key_profile_include]: new.append(t.generate_string(x)) v[self.key_profile_include] = new # now get the included ones pro_var = self._get_profile_included_item(self.profile, self.key_profile_variables, seen=[self.profile]) pro_dvar = self._get_profile_included_item(self.profile, self.key_profile_dvariables, seen=[self.profile]) # exec incl dynvariables self._shell_exec_dvars(pro_dvar.keys(), pro_dvar) return pro_var, pro_dvar
def _resolve_dotfile_paths(self): """resolve dotfiles paths""" t = Templategen(variables=self.variables, func_file=self.settings[Settings.key_func_file], filter_file=self.settings[Settings.key_filter_file]) for dotfile in self.dotfiles.values(): # src src = dotfile[self.key_dotfile_src] newsrc = self.resolve_dotfile_src(src, templater=t) dotfile[self.key_dotfile_src] = newsrc # dst dst = dotfile[self.key_dotfile_dst] newdst = self.resolve_dotfile_dst(dst, templater=t) dotfile[self.key_dotfile_dst] = newdst
def _rec_resolve_vars(self, variables): """recursive resolve variables""" t = Templategen(variables=variables) for k in variables.keys(): val = variables[k] while Templategen.var_is_template(val): val = t.generate_string(val) variables[k] = val t.update_variables(variables) return variables
def _rec_resolve_vars(self, variables): """recursive resolve variables""" default = self._get_variables_dict(self.profile) t = Templategen(variables=self._merge_dict(default, variables)) for k in variables.keys(): val = variables[k] while Templategen.var_is_template(val): val = t.generate_string(val) variables[k] = val t.update_variables(variables) return variables
def cmd_list_files(o): """list all dotfiles for a specific profile""" if o.profile not in o.profiles: LOG.warn('unknown profile \"{}\"'.format(o.profile)) return what = 'Dotfile(s)' if o.listfiles_templateonly: what = 'Template(s)' LOG.emph('{} for profile \"{}\"\n'.format(what, o.profile)) for dotfile in o.dotfiles: if o.listfiles_templateonly: src = os.path.join(o.dotpath, dotfile.src) if not Templategen.is_template(src): continue LOG.log('{} (src: \"{}\", link: {})'.format(dotfile.key, dotfile.src, dotfile.link.name.lower())) LOG.sub('{}'.format(dotfile.dst)) LOG.log('')
def cmd_list_files(opts, conf, templateonly=False): """list all dotfiles for a specific profile""" if not opts['profile'] in conf.get_profiles(): LOG.warn('unknown profile \"{}\"'.format(opts['profile'])) return what = 'Dotfile(s)' if templateonly: what = 'Template(s)' LOG.emph('{} for profile \"{}\"\n'.format(what, opts['profile'])) for dotfile in conf.get_dotfiles(opts['profile']): if templateonly: src = os.path.join(opts['dotpath'], dotfile.src) if not Templategen.is_template(src): continue LOG.log('{} (src: \"{}\", link: {})'.format(dotfile.key, dotfile.src, dotfile.link)) LOG.sub('{}'.format(dotfile.dst)) LOG.log('')