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 line in entries: fields = shlex.split(line) entry = fields[0] args = [] if len(fields) > 1: args = fields[1:] action = None if self.key_actions_pre in self.actions and \ entry in self.actions[self.key_actions_pre]: kind = self.key_actions_pre if not args: action = self.actions[self.key_actions_pre][entry] else: a = self.actions[self.key_actions_pre][entry].action action = Action(entry, kind, a, *args) elif self.key_actions_post in self.actions and \ entry in self.actions[self.key_actions_post]: kind = self.key_actions_post if not args: action = self.actions[self.key_actions_post][entry] else: a = self.actions[self.key_actions_post][entry].action action = Action(entry, kind, a, *args) else: self.log.warn('unknown action \"{}\"'.format(entry)) continue res[kind].append(action) 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 test_install(self): """Test the install function""" # dotpath location tmp = get_tempdir() self.assertTrue(os.path.exists(tmp)) self.addCleanup(clean, tmp) # where dotfiles will be installed dst = get_tempdir() self.assertTrue(os.path.exists(dst)) self.addCleanup(clean, dst) # create the dotfile in dotdrop f1, c1 = create_random_file(tmp) dst1 = os.path.join(dst, get_string(6)) d1 = Dotfile(get_string(5), dst1, os.path.basename(f1)) # fake a print self.assertTrue(str(d1) != '') f2, c2 = create_random_file(tmp) dst2 = os.path.join(dst, get_string(6)) d2 = Dotfile(get_string(5), dst2, os.path.basename(f2)) with open(f2, 'w') as f: f.write(self.TEMPLATE) f3, _ = create_random_file(tmp, binary=True) dst3 = os.path.join(dst, get_string(6)) d3 = Dotfile(get_string(5), dst3, os.path.basename(f3)) # create a directory dotfile dir1 = os.path.join(tmp, 'somedir') create_dir(dir1) fd, _ = create_random_file(dir1) dstd = os.path.join(dst, get_string(6)) ddot = Dotfile(get_string(5), dstd, os.path.basename(dir1)) # to test backup f4, c4 = create_random_file(tmp) dst4 = os.path.join(dst, get_string(6)) d4 = Dotfile(key=get_string(6), dst=dst4, src=os.path.basename(f4)) with open(dst4, 'w') as f: f.write(get_string(16)) # to test link f5, c5 = create_random_file(tmp) dst5 = os.path.join(dst, get_string(6)) self.addCleanup(clean, dst5) d5 = Dotfile(get_string(6), dst5, os.path.basename(f5), link=True) # create the dotfile directories in dotdrop dir1 = create_dir(os.path.join(tmp, get_string(6))) self.assertTrue(os.path.exists(dir1)) self.addCleanup(clean, dir1) dst6 = os.path.join(dst, get_string(6)) # fill with files sub1, _ = create_random_file(dir1, template=True) self.assertTrue(os.path.exists(sub1)) sub2, _ = create_random_file(dir1) self.assertTrue(os.path.exists(sub2)) # make up the dotfile d6 = Dotfile(get_string(6), dst6, os.path.basename(dir1)) # to test symlink directories dir2 = create_dir(os.path.join(tmp, get_string(6))) self.assertTrue(os.path.exists(dir2)) self.addCleanup(clean, dir2) dst7 = os.path.join(dst, get_string(6)) # fill with files sub3, _ = create_random_file(dir2) self.assertTrue(os.path.exists(sub3)) sub4, _ = create_random_file(dir2) self.assertTrue(os.path.exists(sub4)) # make up the dotfile d7 = Dotfile(get_string(6), dst7, os.path.basename(dir2), link=True) # to test actions value = get_string(12) fact = '/tmp/action' self.addCleanup(clean, fact) act1 = Action('testaction', 'post', 'echo "{}" > {}'.format(value, fact)) f8, c8 = create_random_file(tmp) dst8 = os.path.join(dst, get_string(6)) d8 = Dotfile(get_string(6), dst8, os.path.basename(f8), actions=[act1]) # to test transformations trans1 = 'trans1' trans2 = 'trans2' cmd = 'cat {0} | sed \'s/%s/%s/g\' > {1}' % (trans1, trans2) tr = Action('testtrans', 'post', cmd) f9, c9 = create_random_file(tmp, content=trans1) dst9 = os.path.join(dst, get_string(6)) d9 = Dotfile(get_string(6), dst9, os.path.basename(f9), trans_r=tr) # to test template f10, _ = create_random_file(tmp, content='{{@@ header() @@}}') dst10 = os.path.join(dst, get_string(6)) d10 = Dotfile(get_string(6), dst10, os.path.basename(f10)) # generate the config and stuff profile = get_string(5) confpath = os.path.join(tmp, self.CONFIG_NAME) dotfiles = [d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, ddot] self.fake_config(confpath, dotfiles, profile, tmp, [act1], [tr]) conf = Cfg(confpath) self.assertTrue(conf is not None) # install them conf, opts = load_config(confpath, profile) opts['safe'] = False opts['debug'] = True opts['showdiff'] = True opts['variables'] = {} cmd_install(opts, conf) # now compare the generated files self.assertTrue(os.path.exists(dst1)) self.assertTrue(os.path.exists(dst2)) self.assertTrue(os.path.exists(dst3)) self.assertTrue(os.path.exists(dst5)) self.assertTrue(os.path.exists(dst6)) self.assertTrue(os.path.exists(dst7)) self.assertTrue(os.path.exists(dst8)) self.assertTrue(os.path.exists(dst10)) self.assertTrue(os.path.exists(fd)) # check if 'dst5' is a link whose target is 'f5' self.assertTrue(os.path.islink(dst5)) self.assertTrue(os.path.realpath(dst5) == os.path.realpath(f5)) # check if 'dst7' is a link whose target is 'dir2' self.assertTrue(os.path.islink(dst7)) self.assertTrue(os.path.realpath(dst7) == os.path.realpath(dir2)) # make sure backup is there b = dst4 + Installer.BACKUP_SUFFIX self.assertTrue(os.path.exists(b)) self.assertTrue(filecmp.cmp(f1, dst1, shallow=True)) f2content = open(dst2, 'r').read() self.assertTrue(f2content == self.RESULT) self.assertTrue(filecmp.cmp(f3, dst3, shallow=True)) # test action has been executed self.assertTrue(os.path.exists(fact)) self.assertTrue(str(act1) != '') actcontent = open(fact, 'r').read().rstrip() self.assertTrue(actcontent == value) # test transformation has been done self.assertTrue(os.path.exists(dst9)) transcontent = open(dst9, 'r').read().rstrip() self.assertTrue(transcontent == trans2) # test template has been remplaced self.assertTrue(os.path.exists(dst10)) tempcontent = open(dst10, 'r').read().rstrip() self.assertTrue(tempcontent == header())
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(): # loop through all actions 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, k, v2) else: # parse naked actions as post actions if self.key_actions_post not in self.actions: self.actions[self.key_actions_post] = {} self.actions[self.key_actions_post][k] = Action( k, '', v) # parse read transformations if self.key_trans_r in self.content: if self.content[self.key_trans_r] is not None: for k, v in self.content[self.key_trans_r].items(): self.trans_r[k] = Transform(k, v) # parse write transformations if self.key_trans_w in self.content: if self.content[self.key_trans_w] is not None: for k, v in self.content[self.key_trans_w].items(): self.trans_w[k] = Transform(k, v) # parse the profiles 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.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.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 = os.path.normpath(v[self.key_dotfiles_src]) dst = os.path.normpath(v[self.key_dotfiles_dst]) # Fail if both `link` and `link_children` present if self.key_dotfiles_link in v \ and self.key_dotfiles_link_children in v: msg = 'only one of `link` or `link_children` allowed per' msg += ' dotfile, error on dotfile "{}".' self.log.err(msg.format(k)) # Otherwise, get link type link = LinkTypes.NOLINK if self.key_dotfiles_link in v and v[self.key_dotfiles_link]: link = LinkTypes.PARENTS if self.key_dotfiles_link_children in v \ and v[self.key_dotfiles_link_children]: link = LinkTypes.CHILDREN noempty = v[self.key_dotfiles_noempty] if \ self.key_dotfiles_noempty \ in v else self.lnk_settings[self.key_ignoreempty] itsactions = v[self.key_dotfiles_actions] if \ self.key_dotfiles_actions in v else [] actions = self._parse_actions(itsactions) # parse read transformation itstrans_r = v[self.key_dotfiles_trans_r] if \ self.key_dotfiles_trans_r in v else None trans_r = None if itstrans_r: if type(itstrans_r) is list: msg = 'One transformation allowed per dotfile' msg += ', error on dotfile \"{}\"' self.log.err(msg.format(k)) msg = 'Please modify your config file to: \"trans: {}\"' self.log.err(msg.format(itstrans_r[0])) msg = 'see https://github.com/deadc0de6/dotdrop/wiki/transformations#config-error-with-transformation-list' # noqa self.log.err(msg) return False trans_r = self._parse_trans(itstrans_r, read=True) if not trans_r: msg = 'unknown trans \"{}\" for \"{}\"' self.log.err(msg.format(itstrans_r, k)) return False # parse write transformation itstrans_w = v[self.key_dotfiles_trans_w] if \ self.key_dotfiles_trans_w in v else None trans_w = None if itstrans_w: if type(itstrans_w) is list: msg = 'One write transformation allowed per dotfile' msg += ', error on dotfile \"{}\"' self.log.err(msg.format(k)) msg = 'Please modify your config file: \"trans_write: {}\"' self.log.err(msg.format(itstrans_w[0])) msg = 'see https://github.com/deadc0de6/dotdrop/wiki/transformations#config-error-with-transformation-list' # noqa self.log.err(msg) return False trans_w = self._parse_trans(itstrans_w, read=False) if not trans_w: msg = 'unknown trans_write \"{}\" for \"{}\"' self.log.err(msg.format(itstrans_w, k)) return False # disable transformation when link is true if link != LinkTypes.NOLINK and (trans_r or trans_w): msg = 'transformations disabled for \"{}\"'.format(dst) msg += ' because link is True' self.log.warn(msg) trans_r = None trans_w = None # parse cmpignore pattern cmpignores = v[self.key_dotfiles_cmpignore] if \ self.key_dotfiles_cmpignore in v else [] # parse upignore pattern upignores = v[self.key_dotfiles_upignore] if \ self.key_dotfiles_upignore in v else [] # create new dotfile self.dotfiles[k] = Dotfile(k, dst, src, link=link, actions=actions, trans_r=trans_r, trans_w=trans_w, cmpignore=cmpignores, noempty=noempty, upignore=upignores) # assign dotfiles to each profile 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 for d in dots: if d not in self.dotfiles: msg = 'unknown dotfile \"{}\" for {}'.format(d, k) self.log.err(msg) continue self.prodots[k].append(self.dotfiles[d]) # handle "include" for each profile for k in self.lnk_profiles.keys(): dots = self._get_included_dotfiles(k) self.prodots[k].extend(dots) # remove duplicates if any self.prodots[k] = list(set(self.prodots[k])) # make sure we have an absolute dotpath self.curdotpath = self.lnk_settings[self.key_dotpath] self.lnk_settings[self.key_dotpath] = \ self._abs_path(self.curdotpath) # make sure we have an absolute workdir self.curworkdir = self.lnk_settings[self.key_workdir] self.lnk_settings[self.key_workdir] = \ self._abs_path(self.curworkdir) return True
def _load(self): """load lower level config""" self.cfgyaml = CfgYaml(self.path, self.profile_key, debug=self.debug) # settings self.settings = Settings.parse(None, self.cfgyaml.settings) # dotfiles self.dotfiles = Dotfile.parse_dict(self.cfgyaml.dotfiles) if self.debug: self._debug_list('dotfiles', self.dotfiles) # profiles self.profiles = Profile.parse_dict(self.cfgyaml.profiles) if self.debug: self._debug_list('profiles', self.profiles) # actions self.actions = Action.parse_dict(self.cfgyaml.actions) if self.debug: self._debug_list('actions', self.actions) # trans_r self.trans_r = Transform.parse_dict(self.cfgyaml.trans_r) if self.debug: self._debug_list('trans_r', self.trans_r) # trans_w self.trans_w = Transform.parse_dict(self.cfgyaml.trans_w) if self.debug: self._debug_list('trans_w', self.trans_w) # variables self.variables = self.cfgyaml.variables if self.debug: self._debug_dict('variables', self.variables) # patch dotfiles in profiles self._patch_keys_to_objs(self.profiles, "dotfiles", self.get_dotfile) # patch action in dotfiles actions self._patch_keys_to_objs(self.dotfiles, "actions", self._get_action_w_args) # patch action in profiles actions self._patch_keys_to_objs(self.profiles, "actions", self._get_action_w_args) # patch actions in settings default_actions self._patch_keys_to_objs([self.settings], "default_actions", self._get_action_w_args) if self.debug: msg = 'default actions: {}'.format(self.settings.default_actions) self.log.dbg(msg) # patch trans_w/trans_r in dotfiles self._patch_keys_to_objs(self.dotfiles, "trans_r", self._get_trans_w_args(self._get_trans_r), islist=False) self._patch_keys_to_objs(self.dotfiles, "trans_w", self._get_trans_w_args(self._get_trans_w), islist=False)
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(): # loop through all actions 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 if self.key_actions_post not in self.actions: self.actions[self.key_actions_post] = {} self.actions[self.key_actions_post][k] = Action(k, v) # parse all transformations if self.key_trans in self.content: if self.content[self.key_trans] is not None: for k, v in self.content[self.key_trans].items(): self.trans[k] = Transform(k, v) # parse the profiles 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.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.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 itsactions = v[self.key_dotfiles_actions] if \ self.key_dotfiles_actions in v else [] actions = self._parse_actions(itsactions) itstrans = v[self.key_dotfiles_trans] if \ self.key_dotfiles_trans in v else [] 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, actions=actions, trans=trans) # assign dotfiles to each profile 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.lnk_profiles.keys(): dots = self._get_included_dotfiles(k) self.prodots[k].extend(dots) # remove duplicates if any self.prodots[k] = list(set(self.prodots[k])) # make sure we have an absolute dotpath self.curdotpath = self.lnk_settings[self.key_dotpath] self.lnk_settings[self.key_dotpath] = self.abs_dotpath(self.curdotpath) return True
def test_install(self): '''Test the install function''' # dotpath location tmp = get_tempfolder() self.assertTrue(os.path.exists(tmp)) self.addCleanup(clean, tmp) # where dotfiles will be installed dst = get_tempfolder() self.assertTrue(os.path.exists(dst)) self.addCleanup(clean, dst) # create the dotfile in dotdrop f1, c1 = create_random_file(tmp) dst1 = os.path.join(dst, get_string(6)) d1 = Dotfile(get_string(5), dst1, os.path.basename(f1)) # fake a print self.assertTrue(str(d1) != '') f2, c2 = create_random_file(tmp) dst2 = os.path.join(dst, get_string(6)) d2 = Dotfile(get_string(5), dst2, os.path.basename(f2)) with open(f2, 'w') as f: f.write(self.TEMPLATE) f3, _ = create_random_file(tmp, binary=True) dst3 = os.path.join(dst, get_string(6)) d3 = Dotfile(get_string(5), dst3, os.path.basename(f3)) # to test backup f4, c4 = create_random_file(tmp) dst4 = os.path.join(dst, get_string(6)) d4 = Dotfile(get_string(6), dst4, os.path.basename(f4)) with open(dst4, 'w') as f: f.write(get_string(16)) # to test link f5, c5 = create_random_file(tmp) dst5 = os.path.join(dst, get_string(6)) self.addCleanup(clean, dst5) d5 = Dotfile(get_string(6), dst5, os.path.basename(f5), link=True) # create the dotfile folders in dotdrop dir1 = create_dir(os.path.join(tmp, get_string(6))) self.assertTrue(os.path.exists(dir1)) self.addCleanup(clean, dir1) dst6 = os.path.join(dst, get_string(6)) # fill with files sub1, _ = create_random_file(dir1) self.assertTrue(os.path.exists(sub1)) sub2, _ = create_random_file(dir1) self.assertTrue(os.path.exists(sub2)) # make up the dotfile d6 = Dotfile(get_string(6), dst6, os.path.basename(dir1)) # to test symlink folders dir2 = create_dir(os.path.join(tmp, get_string(6))) self.assertTrue(os.path.exists(dir2)) self.addCleanup(clean, dir2) dst7 = os.path.join(dst, get_string(6)) # fill with files sub3, _ = create_random_file(dir2) self.assertTrue(os.path.exists(sub3)) sub4, _ = create_random_file(dir2) self.assertTrue(os.path.exists(sub4)) # make up the dotfile d7 = Dotfile(get_string(6), dst7, os.path.basename(dir2), link=True) # to test actions value = get_string(12) fact = '/tmp/action' act1 = Action('testaction', 'echo "%s" > %s' % (value, fact)) f8, c8 = create_random_file(tmp) dst8 = os.path.join(dst, get_string(6)) d8 = Dotfile(get_string(6), dst8, os.path.basename(f8), actions=[act1]) # generate the config and stuff profile = get_string(5) confpath = os.path.join(tmp, self.CONFIG_NAME) self.fake_config(confpath, [d1, d2, d3, d4, d5, d6, d7, d8], profile, tmp, [act1]) conf = Cfg(confpath) self.assertTrue(conf is not None) # install them conf, opts = load_config(confpath, profile) opts['safe'] = False opts['quiet'] = True install(opts, conf) # now compare the generated files self.assertTrue(os.path.exists(dst1)) self.assertTrue(os.path.exists(dst2)) self.assertTrue(os.path.exists(dst3)) self.assertTrue(os.path.exists(dst5)) self.assertTrue(os.path.exists(dst6)) self.assertTrue(os.path.exists(dst7)) self.assertTrue(os.path.exists(dst8)) # check if 'dst5' is a link whose target is 'f5' self.assertTrue(os.path.islink(dst5)) self.assertTrue(os.path.realpath(dst5) == os.path.realpath(f5)) # check if 'dst7' is a link whose target is 'dir2' self.assertTrue(os.path.islink(dst7)) self.assertTrue(os.path.realpath(dst7) == os.path.realpath(dir2)) # make sure backup is there b = dst4 + Installer.BACKUP_SUFFIX self.assertTrue(os.path.exists(b)) self.assertTrue(filecmp.cmp(f1, dst1, shallow=True)) f2content = open(dst2, 'r').read() self.assertTrue(f2content == self.RESULT) self.assertTrue(filecmp.cmp(f3, dst3, shallow=True)) # test action has been executed self.assertTrue(os.path.exists(fact)) self.assertTrue(str(act1) != '') actcontent = open(fact, 'r').read().rstrip() self.assertTrue(actcontent == value)