mirror of
https://github.com/deadc0de6/dotdrop.git
synced 2026-02-05 12:03:49 +00:00
refactor, comment and clean code in config parser
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user