From 2af2a6132a3ceba1cfc657a3722d10a371d02523 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Sat, 2 Jun 2018 15:40:09 +0200 Subject: [PATCH] refactor, comment and clean code in config parser --- dotdrop/config.py | 271 +++++++++++++++++++++++++-------------------- dotdrop/dotdrop.py | 2 +- 2 files changed, 152 insertions(+), 121 deletions(-) diff --git a/dotdrop/config.py b/dotdrop/config.py index f9ee030..bbefbc0 100644 --- a/dotdrop/config.py +++ b/dotdrop/config.py @@ -56,17 +56,29 @@ class Cfg: raise ValueError('config file does not exist') self.cfgpath = cfgpath self.log = Logger() - # link inside content - self.settings = {} - # link inside content - self.profiles = {} - # not linked to content + + # 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 = {} - # not linked to content + + # dict of all action objects by action key + # NOT linked inside the yaml dict (self.content) self.actions = {} - # not linked to content + + # dict of all transformation objects by trans key + # NOT linked inside the yaml dict (self.content) self.trans = {} - # not linked to content + + # 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') @@ -80,7 +92,7 @@ class Cfg: return self._parse() def _is_valid(self): - """test the yaml file (self.content) is valid""" + """test the yaml dict (self.content) is valid""" if self.key_profiles not in self.content: self.log.err('missing \"{}\" in config'.format(self.key_profiles)) return False @@ -92,67 +104,19 @@ class Cfg: 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] and \ - self.key_profiles_incl not in pro[k]: - pro[k] = {self.key_profiles_dots: pro[k]} - tosave = True - if tosave: + # and adapt if there are not + profiles = self.content[self.key_profiles] + changed = False + for k in profiles.keys(): + if self.key_profiles_dots not in profiles[k] and \ + self.key_profiles_incl not in profiles[k]: + profiles[k] = {self.key_profiles_dots: profiles[k]} + changed = True + if changed: # 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 - where actions are all known actions and - entries are the ones defined for this dotfile""" - res = { - self.key_actions_pre: [], - self.key_actions_post: [], - } - for entry in entries: - action = None - if self.key_actions_pre in actions and \ - entry in actions[self.key_actions_pre]: - key = self.key_actions_pre - action = actions[self.key_actions_pre][entry] - elif self.key_actions_post in actions and \ - entry in actions[self.key_actions_post]: - key = self.key_actions_post - action = actions[self.key_actions_post][entry] - elif entry not in actions.keys(): - self.log.warn('unknown action \"{}\"'.format(entry)) - continue - else: - key = self.key_actions_post - action = actions[entry] - res[key].append(action) - return res - - def _parse_trans(self, trans, entries): - """parse transformations specified for an element - where trans are all known transformation and - entries are the ones defined for this dotfile""" - res = [] - for entry in entries: - if entry not in trans.keys(): - self.log.warn('unknown trans \"{}\"'.format(entry)) - continue - res.append(trans[entry]) - return res - - def _complete_settings(self): - """set settings defaults if not present""" - if self.key_backup not in self.settings: - self.settings[self.key_backup] = self.default_backup - if self.key_create not in self.settings: - self.settings[self.key_create] = self.default_create - if self.key_banner not in self.settings: - self.settings[self.key_banner] = self.default_banner - def _parse(self): """parse config file""" # parse all actions @@ -160,12 +124,14 @@ class Cfg: if self.content[self.key_actions] is not None: for k, v in self.content[self.key_actions].items(): if k in [self.key_actions_pre, self.key_actions_post]: + # parse pre/post actions items = self.content[self.key_actions][k].items() for k2, v2 in items: if k not in self.actions: self.actions[k] = {} self.actions[k][k2] = Action(k2, v2) else: + # parse naked actions as post actions self.actions[k] = Action(k, v) # parse all transformations @@ -175,88 +141,147 @@ class Cfg: self.trans[k] = Transform(k, v) # parse the profiles - self.profiles = self.content[self.key_profiles] - if self.profiles is None: + self.lnk_profiles = self.content[self.key_profiles] + if self.lnk_profiles is None: + # ensures self.lnk_profiles is a dict self.content[self.key_profiles] = {} - self.profiles = self.content[self.key_profiles] - for k, v in self.profiles.items(): + self.lnk_profiles = self.content[self.key_profiles] + for k, v in self.lnk_profiles.items(): if self.key_profiles_dots in v and \ v[self.key_profiles_dots] is None: + # if has the dotfiles entry but is empty + # ensures it's an empty list v[self.key_profiles_dots] = [] # parse the settings - self.settings = self.content[self.key_settings] + self.lnk_settings = self.content[self.key_settings] self._complete_settings() # parse the dotfiles + # and construct the dict of objects per dotfile key if not self.content[self.key_dotfiles]: + # ensures the dotfiles entry is a dict 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 self.default_link - entries = v[self.key_dotfiles_actions] if \ + itsactions = v[self.key_dotfiles_actions] if \ self.key_dotfiles_actions in v else [] - actions = self._parse_actions(self.actions, entries) - entries = v[self.key_dotfiles_trans] if \ + actions = self._parse_actions(itsactions) + itstrans = v[self.key_dotfiles_trans] if \ self.key_dotfiles_trans in v else [] - trans = self._parse_trans(self.trans, entries) + trans = self._parse_trans(itstrans) if len(trans) > 0 and link: msg = 'transformations disabled for \"{}\"'.format(dst) msg += ' because link is True' self.log.warn(msg) trans = [] - self.dotfiles[k] = Dotfile(k, dst, src, link=link, + self.dotfiles[k] = Dotfile(k, dst, src, + link=link, actions=actions, trans=trans) # assign dotfiles to each profile - for k, v in self.profiles.items(): + for k, v in self.lnk_profiles.items(): self.prodots[k] = [] if self.key_profiles_dots not in v: + # ensures is a list 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: + # add all if key ALL is used self.prodots[k] = list(self.dotfiles.values()) else: + # add the dotfiles self.prodots[k].extend([self.dotfiles[d] for d in dots]) # handle "include" for each profile - for k in self.profiles.keys(): + for k in self.lnk_profiles.keys(): dots = self._get_included_dotfiles(k) self.prodots[k].extend(dots) - # no duplicates + # remove duplicates if any self.prodots[k] = list(set(self.prodots[k])) # make sure we have an absolute dotpath - self.curdotpath = self.settings[self.key_dotpath] - self.settings[self.key_dotpath] = self.get_abs_dotpath(self.curdotpath) + self.curdotpath = self.lnk_settings[self.key_dotpath] + self.lnk_settings[self.key_dotpath] = self.get_abs(self.curdotpath) return True def _get_included_dotfiles(self, profile): - """find all dotfiles for a specific include keyword""" + """find all dotfiles for a specific profile + when using the include keyword""" included = [] - if self.key_profiles_incl not in self.profiles[profile]: + if self.key_profiles_incl not in self.lnk_profiles[profile]: + # no include found return included - if not self.profiles[profile][self.key_profiles_incl]: + if not self.lnk_profiles[profile][self.key_profiles_incl]: + # empty include found return included - for other in self.profiles[profile][self.key_profiles_incl]: + for other in self.lnk_profiles[profile][self.key_profiles_incl]: if other not in self.prodots: + # no such profile self.log.warn('unknown included profile \"{}\"'.format(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): + def _parse_actions(self, entries): + """parse actions specified for an element + where entries are the ones defined for this dotfile""" + res = { + self.key_actions_pre: [], + self.key_actions_post: [], + } + for entry in entries: + action = None + if self.key_actions_pre in self.actions and \ + entry in self.actions[self.key_actions_pre]: + key = self.key_actions_pre + action = self.actions[self.key_actions_pre][entry] + elif self.key_actions_post in self.actions and \ + entry in self.actions[self.key_actions_post]: + key = self.key_actions_post + action = self.actions[self.key_actions_post][entry] + elif entry not in self.actions.keys(): + self.log.warn('unknown action \"{}\"'.format(entry)) + continue + else: + key = self.key_actions_post + action = self.actions[entry] + res[key].append(action) + return res + + def _parse_trans(self, entries): + """parse transformations specified for an element + where entries are the ones defined for this dotfile""" + res = [] + for entry in entries: + if entry not in self.trans.keys(): + self.log.warn('unknown trans \"{}\"'.format(entry)) + continue + res.append(self.trans[entry]) + return res + + def _complete_settings(self): + """set settings defaults if not present""" + if self.key_backup not in self.lnk_settings: + self.lnk_settings[self.key_backup] = self.default_backup + if self.key_create not in self.lnk_settings: + self.lnk_settings[self.key_create] = self.default_create + if self.key_banner not in self.lnk_settings: + self.lnk_settings[self.key_banner] = self.default_banner + + def get_abs(self, path): + """transform path to an absolute path based on config path""" + if not path.startswith(os.sep): absconf = os.path.join(os.path.dirname( - self.cfgpath), dotpath) + self.cfgpath), path) return absconf - return dotpath + return path def _save(self, content, path): """writes the config to file""" @@ -267,7 +292,8 @@ class Cfg: return ret def _get_unique_key(self, dst): - """return a unique key for an inexistent dotfile""" + """return a unique key where dst + is known not to be an already existing dotfile""" allkeys = self.dotfiles.keys() idx = -1 while True: @@ -292,21 +318,23 @@ class Cfg: return False, self._get_unique_key(dotfile.dst) def new(self, dotfile, profile, link=False): - """import new dotfile (key will change)""" + """import new dotfile + dotfile key will change and can be empty""" # keep it short home = os.path.expanduser('~') dotfile.dst = dotfile.dst.replace(home, '~') # adding new profile if doesn't exist - if profile not in self.profiles: - self.profiles[profile] = {self.key_profiles_dots: []} + if profile not in self.lnk_profiles: + # in the yaml + self.lnk_profiles[profile] = {self.key_profiles_dots: []} + # in the global list of dotfiles per profile self.prodots[profile] = [] - # when dotfile already there exists, key = self._dotfile_exists(dotfile) if exists: + # when dotfile already there somewhere dotfile = self.dotfiles[key] - # already in it if dotfile in self.prodots[profile]: self.log.err('\"{}\" already present'.format(dotfile.key)) return False, dotfile @@ -314,30 +342,33 @@ class Cfg: # add for this 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) + # get a pointer in the yaml profiles->this_profile + # and complete it with the new entry + 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, dotfile - # adding the dotfile + # adding the new dotfile dotfile.key = key + # add the entry in the yaml file 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 + # set the link flag dots[dotfile.key][self.key_dotfiles_link] = True - # link it to this profile + # link it to this profile in the yaml file 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) - # adding to global list + # add it to the global list of dotfiles self.dotfiles[dotfile.key] = dotfile - # adding to the profile + # add it to this profile self.prodots[profile].append(dotfile) return True, dotfile @@ -351,42 +382,42 @@ class Cfg: def get_profiles(self): """return all defined profiles""" - return self.profiles.keys() + return self.lnk_profiles.keys() def get_settings(self): """return all defined settings""" - return self.settings.copy() + return self.lnk_settings.copy() def dump(self): """return a dump of the config""" # temporary reset dotpath - dotpath = self.settings[self.key_dotpath] - self.settings[self.key_dotpath] = self.curdotpath + dotpath = self.lnk_settings[self.key_dotpath] + self.lnk_settings[self.key_dotpath] = self.curdotpath # reset banner - if self.settings[self.key_banner]: - del self.settings[self.key_banner] + if self.lnk_settings[self.key_banner]: + del self.lnk_settings[self.key_banner] # dump ret = yaml.dump(self.content, default_flow_style=False, indent=2) # restore dotpath - self.settings[self.key_dotpath] = dotpath + self.lnk_settings[self.key_dotpath] = dotpath # restore banner - if self.key_banner not in self.settings: - self.settings[self.key_banner] = self.default_banner + if self.key_banner not in self.lnk_settings: + self.lnk_settings[self.key_banner] = self.default_banner return ret def save(self): """save the config to file""" # temporary reset dotpath - dotpath = self.settings[self.key_dotpath] - self.settings[self.key_dotpath] = self.curdotpath + dotpath = self.lnk_settings[self.key_dotpath] + self.lnk_settings[self.key_dotpath] = self.curdotpath # reset banner - if self.settings[self.key_banner]: - del self.settings[self.key_banner] + if self.lnk_settings[self.key_banner]: + del self.lnk_settings[self.key_banner] # save ret = self._save(self.content, self.cfgpath) # restore dotpath - self.settings[self.key_dotpath] = dotpath + self.lnk_settings[self.key_dotpath] = dotpath # restore banner - if self.key_banner not in self.settings: - self.settings[self.key_banner] = self.default_banner + if self.key_banner not in self.lnk_settings: + self.lnk_settings[self.key_banner] = self.default_banner return ret diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 21c3394..4998b35 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -205,7 +205,7 @@ def update(opts, conf, path): LOG.err('multiple dotfiles found: {}'.format(found)) return False dotfile = subs[0] - src = os.path.join(conf.get_abs_dotpath(opts['dotpath']), dotfile.src) + src = os.path.join(conf.get_abs(opts['dotpath']), dotfile.src) if os.path.isfile(src) and \ Templategen.get_marker() in open(src, 'r').read(): LOG.warn('\"{}\" uses template, please update manually'.format(src))