1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-11 07:54:15 +00:00

refactor, comment and clean code in config parser

This commit is contained in:
deadc0de6
2018-06-02 15:40:09 +02:00
parent ea3cc6663b
commit 2af2a6132a
2 changed files with 152 additions and 121 deletions

View File

@@ -56,17 +56,29 @@ class Cfg:
raise ValueError('config file does not exist') raise ValueError('config file does not exist')
self.cfgpath = cfgpath self.cfgpath = cfgpath
self.log = Logger() self.log = Logger()
# link inside content
self.settings = {} # represents all entries under "config"
# link inside content # linked inside the yaml dict (self.content)
self.profiles = {} self.lnk_settings = {}
# not linked to content
# 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 = {} self.dotfiles = {}
# not linked to content
# dict of all action objects by action key
# NOT linked inside the yaml dict (self.content)
self.actions = {} self.actions = {}
# not linked to content
# dict of all transformation objects by trans key
# NOT linked inside the yaml dict (self.content)
self.trans = {} self.trans = {}
# not linked to content
# represents all dotfiles per profile by profile key
# NOT linked inside the yaml dict (self.content)
self.prodots = {} self.prodots = {}
if not self._load_file(): if not self._load_file():
raise ValueError('config is not valid') raise ValueError('config is not valid')
@@ -80,7 +92,7 @@ class Cfg:
return self._parse() return self._parse()
def _is_valid(self): 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: if self.key_profiles not in self.content:
self.log.err('missing \"{}\" in config'.format(self.key_profiles)) self.log.err('missing \"{}\" in config'.format(self.key_profiles))
return False return False
@@ -92,67 +104,19 @@ class Cfg:
return False return False
if self.content[self.key_profiles]: if self.content[self.key_profiles]:
# make sure dotfiles are in a sub called "dotfiles" # make sure dotfiles are in a sub called "dotfiles"
pro = self.content[self.key_profiles] # and adapt if there are not
tosave = False profiles = self.content[self.key_profiles]
for k in pro.keys(): changed = False
if self.key_profiles_dots not in pro[k] and \ for k in profiles.keys():
self.key_profiles_incl not in pro[k]: if self.key_profiles_dots not in profiles[k] and \
pro[k] = {self.key_profiles_dots: pro[k]} self.key_profiles_incl not in profiles[k]:
tosave = True profiles[k] = {self.key_profiles_dots: profiles[k]}
if tosave: changed = True
if changed:
# save the new config file # save the new config file
self._save(self.content, self.cfgpath) self._save(self.content, self.cfgpath)
return True 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): def _parse(self):
"""parse config file""" """parse config file"""
# parse all actions # parse all actions
@@ -160,12 +124,14 @@ class Cfg:
if self.content[self.key_actions] is not None: if self.content[self.key_actions] is not None:
for k, v in self.content[self.key_actions].items(): for k, v in self.content[self.key_actions].items():
if k in [self.key_actions_pre, self.key_actions_post]: if k in [self.key_actions_pre, self.key_actions_post]:
# parse pre/post actions
items = self.content[self.key_actions][k].items() items = self.content[self.key_actions][k].items()
for k2, v2 in items: for k2, v2 in items:
if k not in self.actions: if k not in self.actions:
self.actions[k] = {} self.actions[k] = {}
self.actions[k][k2] = Action(k2, v2) self.actions[k][k2] = Action(k2, v2)
else: else:
# parse naked actions as post actions
self.actions[k] = Action(k, v) self.actions[k] = Action(k, v)
# parse all transformations # parse all transformations
@@ -175,88 +141,147 @@ class Cfg:
self.trans[k] = Transform(k, v) self.trans[k] = Transform(k, v)
# parse the profiles # parse the profiles
self.profiles = self.content[self.key_profiles] self.lnk_profiles = self.content[self.key_profiles]
if self.profiles is None: if self.lnk_profiles is None:
# ensures self.lnk_profiles is a dict
self.content[self.key_profiles] = {} self.content[self.key_profiles] = {}
self.profiles = self.content[self.key_profiles] self.lnk_profiles = self.content[self.key_profiles]
for k, v in self.profiles.items(): for k, v in self.lnk_profiles.items():
if self.key_profiles_dots in v and \ if self.key_profiles_dots in v and \
v[self.key_profiles_dots] is None: 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] = [] v[self.key_profiles_dots] = []
# parse the settings # parse the settings
self.settings = self.content[self.key_settings] self.lnk_settings = self.content[self.key_settings]
self._complete_settings() self._complete_settings()
# parse the dotfiles # parse the dotfiles
# and construct the dict of objects per dotfile key
if not self.content[self.key_dotfiles]: if not self.content[self.key_dotfiles]:
# ensures the dotfiles entry is a dict
self.content[self.key_dotfiles] = {} self.content[self.key_dotfiles] = {}
for k, v in self.content[self.key_dotfiles].items(): for k, v in self.content[self.key_dotfiles].items():
src = v[self.key_dotfiles_src] src = v[self.key_dotfiles_src]
dst = v[self.key_dotfiles_dst] dst = v[self.key_dotfiles_dst]
link = v[self.key_dotfiles_link] if self.key_dotfiles_link \ link = v[self.key_dotfiles_link] if self.key_dotfiles_link \
in v else self.default_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 [] self.key_dotfiles_actions in v else []
actions = self._parse_actions(self.actions, entries) actions = self._parse_actions(itsactions)
entries = v[self.key_dotfiles_trans] if \ itstrans = v[self.key_dotfiles_trans] if \
self.key_dotfiles_trans in v else [] 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: if len(trans) > 0 and link:
msg = 'transformations disabled for \"{}\"'.format(dst) msg = 'transformations disabled for \"{}\"'.format(dst)
msg += ' because link is True' msg += ' because link is True'
self.log.warn(msg) self.log.warn(msg)
trans = [] trans = []
self.dotfiles[k] = Dotfile(k, dst, src, link=link, self.dotfiles[k] = Dotfile(k, dst, src,
link=link,
actions=actions, actions=actions,
trans=trans) trans=trans)
# assign dotfiles to each profile # assign dotfiles to each profile
for k, v in self.profiles.items(): for k, v in self.lnk_profiles.items():
self.prodots[k] = [] self.prodots[k] = []
if self.key_profiles_dots not in v: if self.key_profiles_dots not in v:
# ensures is a list
v[self.key_profiles_dots] = [] v[self.key_profiles_dots] = []
if not v[self.key_profiles_dots]: if not v[self.key_profiles_dots]:
continue continue
dots = v[self.key_profiles_dots] dots = v[self.key_profiles_dots]
if self.key_all in dots: if self.key_all in dots:
# add all if key ALL is used
self.prodots[k] = list(self.dotfiles.values()) self.prodots[k] = list(self.dotfiles.values())
else: else:
# add the dotfiles
self.prodots[k].extend([self.dotfiles[d] for d in dots]) self.prodots[k].extend([self.dotfiles[d] for d in dots])
# handle "include" for each profile # handle "include" for each profile
for k in self.profiles.keys(): for k in self.lnk_profiles.keys():
dots = self._get_included_dotfiles(k) dots = self._get_included_dotfiles(k)
self.prodots[k].extend(dots) self.prodots[k].extend(dots)
# no duplicates # remove duplicates if any
self.prodots[k] = list(set(self.prodots[k])) self.prodots[k] = list(set(self.prodots[k]))
# make sure we have an absolute dotpath # make sure we have an absolute dotpath
self.curdotpath = self.settings[self.key_dotpath] self.curdotpath = self.lnk_settings[self.key_dotpath]
self.settings[self.key_dotpath] = self.get_abs_dotpath(self.curdotpath) self.lnk_settings[self.key_dotpath] = self.get_abs(self.curdotpath)
return True return True
def _get_included_dotfiles(self, profile): 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 = [] 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 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 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: if other not in self.prodots:
# no such profile
self.log.warn('unknown included profile \"{}\"'.format(other)) self.log.warn('unknown included profile \"{}\"'.format(other))
continue continue
included.extend(self.prodots[other]) included.extend(self.prodots[other])
return included return included
def get_abs_dotpath(self, dotpath): def _parse_actions(self, entries):
"""transform dotpath to an absolute path""" """parse actions specified for an element
if not dotpath.startswith(os.sep): 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( absconf = os.path.join(os.path.dirname(
self.cfgpath), dotpath) self.cfgpath), path)
return absconf return absconf
return dotpath return path
def _save(self, content, path): def _save(self, content, path):
"""writes the config to file""" """writes the config to file"""
@@ -267,7 +292,8 @@ class Cfg:
return ret return ret
def _get_unique_key(self, dst): 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() allkeys = self.dotfiles.keys()
idx = -1 idx = -1
while True: while True:
@@ -292,21 +318,23 @@ class Cfg:
return False, self._get_unique_key(dotfile.dst) return False, self._get_unique_key(dotfile.dst)
def new(self, dotfile, profile, link=False): 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 # keep it short
home = os.path.expanduser('~') home = os.path.expanduser('~')
dotfile.dst = dotfile.dst.replace(home, '~') dotfile.dst = dotfile.dst.replace(home, '~')
# adding new profile if doesn't exist # adding new profile if doesn't exist
if profile not in self.profiles: if profile not in self.lnk_profiles:
self.profiles[profile] = {self.key_profiles_dots: []} # in the yaml
self.lnk_profiles[profile] = {self.key_profiles_dots: []}
# in the global list of dotfiles per profile
self.prodots[profile] = [] self.prodots[profile] = []
# when dotfile already there
exists, key = self._dotfile_exists(dotfile) exists, key = self._dotfile_exists(dotfile)
if exists: if exists:
# when dotfile already there somewhere
dotfile = self.dotfiles[key] dotfile = self.dotfiles[key]
# already in it
if dotfile in self.prodots[profile]: if dotfile in self.prodots[profile]:
self.log.err('\"{}\" already present'.format(dotfile.key)) self.log.err('\"{}\" already present'.format(dotfile.key))
return False, dotfile return False, dotfile
@@ -314,30 +342,33 @@ class Cfg:
# add for this profile # add for this profile
self.prodots[profile].append(dotfile) self.prodots[profile].append(dotfile)
ent = self.content[self.key_profiles][profile] # get a pointer in the yaml profiles->this_profile
if self.key_all not in ent[self.key_profiles_dots]: # and complete it with the new entry
ent[self.key_profiles_dots].append(dotfile.key) 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 return True, dotfile
# adding the dotfile # adding the new dotfile
dotfile.key = key dotfile.key = key
# add the entry in the yaml file
dots = self.content[self.key_dotfiles] dots = self.content[self.key_dotfiles]
dots[dotfile.key] = { dots[dotfile.key] = {
self.key_dotfiles_dst: dotfile.dst, self.key_dotfiles_dst: dotfile.dst,
self.key_dotfiles_src: dotfile.src, self.key_dotfiles_src: dotfile.src,
} }
if link: if link:
# avoid putting it everywhere # set the link flag
dots[dotfile.key][self.key_dotfiles_link] = True 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] pro = self.content[self.key_profiles][profile]
if self.key_all not in pro[self.key_profiles_dots]: if self.key_all not in pro[self.key_profiles_dots]:
pro[self.key_profiles_dots].append(dotfile.key) 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 self.dotfiles[dotfile.key] = dotfile
# adding to the profile # add it to this profile
self.prodots[profile].append(dotfile) self.prodots[profile].append(dotfile)
return True, dotfile return True, dotfile
@@ -351,42 +382,42 @@ class Cfg:
def get_profiles(self): def get_profiles(self):
"""return all defined profiles""" """return all defined profiles"""
return self.profiles.keys() return self.lnk_profiles.keys()
def get_settings(self): def get_settings(self):
"""return all defined settings""" """return all defined settings"""
return self.settings.copy() return self.lnk_settings.copy()
def dump(self): def dump(self):
"""return a dump of the config""" """return a dump of the config"""
# temporary reset dotpath # temporary reset dotpath
dotpath = self.settings[self.key_dotpath] dotpath = self.lnk_settings[self.key_dotpath]
self.settings[self.key_dotpath] = self.curdotpath self.lnk_settings[self.key_dotpath] = self.curdotpath
# reset banner # reset banner
if self.settings[self.key_banner]: if self.lnk_settings[self.key_banner]:
del self.settings[self.key_banner] del self.lnk_settings[self.key_banner]
# dump # dump
ret = yaml.dump(self.content, default_flow_style=False, indent=2) ret = yaml.dump(self.content, default_flow_style=False, indent=2)
# restore dotpath # restore dotpath
self.settings[self.key_dotpath] = dotpath self.lnk_settings[self.key_dotpath] = dotpath
# restore banner # restore banner
if self.key_banner not in self.settings: if self.key_banner not in self.lnk_settings:
self.settings[self.key_banner] = self.default_banner self.lnk_settings[self.key_banner] = self.default_banner
return ret return ret
def save(self): def save(self):
"""save the config to file""" """save the config to file"""
# temporary reset dotpath # temporary reset dotpath
dotpath = self.settings[self.key_dotpath] dotpath = self.lnk_settings[self.key_dotpath]
self.settings[self.key_dotpath] = self.curdotpath self.lnk_settings[self.key_dotpath] = self.curdotpath
# reset banner # reset banner
if self.settings[self.key_banner]: if self.lnk_settings[self.key_banner]:
del self.settings[self.key_banner] del self.lnk_settings[self.key_banner]
# save # save
ret = self._save(self.content, self.cfgpath) ret = self._save(self.content, self.cfgpath)
# restore dotpath # restore dotpath
self.settings[self.key_dotpath] = dotpath self.lnk_settings[self.key_dotpath] = dotpath
# restore banner # restore banner
if self.key_banner not in self.settings: if self.key_banner not in self.lnk_settings:
self.settings[self.key_banner] = self.default_banner self.lnk_settings[self.key_banner] = self.default_banner
return ret return ret

View File

@@ -205,7 +205,7 @@ def update(opts, conf, path):
LOG.err('multiple dotfiles found: {}'.format(found)) LOG.err('multiple dotfiles found: {}'.format(found))
return False return False
dotfile = subs[0] 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 \ if os.path.isfile(src) and \
Templategen.get_marker() in open(src, 'r').read(): Templategen.get_marker() in open(src, 'r').read():
LOG.warn('\"{}\" uses template, please update manually'.format(src)) LOG.warn('\"{}\" uses template, please update manually'.format(src))