diff --git a/dotdrop/action.py b/dotdrop/action.py new file mode 100644 index 0000000..d5e9538 --- /dev/null +++ b/dotdrop/action.py @@ -0,0 +1,26 @@ +""" +author: deadc0de6 (https://github.com/deadc0de6) +Copyright (c) 2017, deadc0de6 +Represent an action in dotdrop +""" + +import subprocess +from logger import Logger + + +class Action: + + def __init__(self, key, action): + self.key = key + self.action = action + self.log = Logger() + + def execute(self): + self.log.sub('executing \"%s\"' % (self.action)) + try: + subprocess.run(self.action, shell=True) + except KeyboardInterrupt: + self.log.warn('action interrupted') + + def __str__(self): + return 'key:%s -> \"%s\"' % (self.key, self.action) diff --git a/dotdrop/config.py b/dotdrop/config.py index 213463c..a31095c 100644 --- a/dotdrop/config.py +++ b/dotdrop/config.py @@ -8,6 +8,7 @@ import yaml import os from dotfile import Dotfile from logger import Logger +from action import Action class Cfg: @@ -15,10 +16,12 @@ class Cfg: key_config = 'config' key_profiles = 'profiles' key_dotfiles = 'dotfiles' + key_actions = 'actions' key_dotpath = 'dotpath' key_dotfiles_src = 'src' key_dotfiles_dst = 'dst' key_dotfiles_link = 'link' + key_dotfiles_actions = 'actions' def __init__(self, cfgpath): if not os.path.exists(cfgpath): @@ -27,6 +30,7 @@ class Cfg: self.log = Logger() self.configs = {} self.dotfiles = {} + self.actions = {} self.profiles = {} self.prodots = {} if not self._load_file(): @@ -51,21 +55,45 @@ class Cfg: return False return True + def _parse_actions(self, actions, entries): + """ parse actions specified for an element """ + res = [] + for entry in entries: + if entry in actions.keys(): + res.append(actions[entry]) + else: + self.log.err('unknown action \"%s\"' % (entry)) + return False, [] + return True, 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.profiles = {} + # parse the configs self.configs = self.content[self.key_config] - # contains all defined dotfiles + # parse the dotfiles if self.content[self.key_dotfiles] is not None: 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 - self.dotfiles[k] = Dotfile(k, dst, src, link) - # contains a list of dotfiles defined for each profile + entries = v[self.key_dotfiles_actions] if \ + self.key_dotfiles_actions in v else [] + res, actions = self._parse_actions(self.actions, entries) + if not res: + return False + self.dotfiles[k] = Dotfile(k, dst, src, + link=link, actions=actions) + # attribute dotfiles to each profile for k, v in self.profiles.items(): self.prodots[k] = [] if v is None: diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index c6586e4..377cb26 100755 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -73,6 +73,10 @@ def install(opts, conf): r = inst.link(dotfile.src, dotfile.dst) else: r = inst.install(t, opts['profile'], dotfile.src, dotfile.dst) + if len(r) > 0 and len(dotfile.actions) > 0: + # execute action + for action in dotfile.actions: + action.execute() installed.extend(r) LOG.log('\n%u dotfile(s) installed.' % (len(installed))) return True diff --git a/dotdrop/dotfile.py b/dotdrop/dotfile.py index a7e53b0..a151d96 100644 --- a/dotdrop/dotfile.py +++ b/dotdrop/dotfile.py @@ -7,7 +7,7 @@ represents a dotfile in dotdrop class Dotfile: - def __init__(self, key, dst, src, link=False): + def __init__(self, key, dst, src, actions=[], link=False): # key of dotfile in the config self.key = key # where to install this dotfile @@ -16,6 +16,8 @@ class Dotfile: self.src = src # should be a link self.link = link + # list of actions + self.actions = actions def __str__(self): return 'key:%s, src: %s, dst: %s, link: %s' % (self.key, self.src, diff --git a/tests/helpers.py b/tests/helpers.py index db93d72..53fab23 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -102,4 +102,5 @@ def create_fake_config(folder, configname='config.yaml', f.write(' dotpath: %s\n' % (dotpath)) f.write('dotfiles:\n') f.write('profiles:\n') + f.write('actions:\n') return path diff --git a/tests/test_install.py b/tests/test_install.py index 0fffccd..bb199ab 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -11,6 +11,7 @@ from tests.helpers import * from dotdrop.dotfile import Dotfile from dotdrop.dotdrop import install from dotdrop.installer import Installer +from dotdrop.action import Action class TestInstall(unittest.TestCase): @@ -30,9 +31,12 @@ exec bspwm exec bspwm ''' - def fake_config(self, path, dotfiles, profile, dotpath): + def fake_config(self, path, dotfiles, profile, dotpath, actions): '''Create a fake config file''' with open(path, 'w') as f: + f.write('actions:\n') + for action in actions: + f.write(' %s: %s\n' % (action.key, action.action)) f.write('config:\n') f.write(' backup: true\n') f.write(' create: true\n') @@ -43,6 +47,10 @@ exec bspwm f.write(' dst: %s\n' % (d.dst)) f.write(' src: %s\n' % (d.src)) f.write(' link: %s\n' % str(d.link).lower()) + if len(d.actions) > 0: + f.write(' actions:\n') + for action in actions: + f.write(' - %s\n' % (action.key)) f.write('profiles:\n') f.write(' %s:\n' % (profile)) for d in dotfiles: @@ -114,10 +122,19 @@ exec bspwm # 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], profile, tmp) + self.fake_config(confpath, [d1, d2, d3, d4, d5, d6, d7, d8], + profile, tmp, [act1]) conf = Cfg(confpath) self.assertTrue(conf is not None) @@ -134,6 +151,7 @@ exec bspwm 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)) @@ -152,6 +170,11 @@ exec bspwm self.assertTrue(f2content == self.RESULT) self.assertTrue(filecmp.cmp(f3, dst3, shallow=True)) + # test action has been executed + self.assertTrue(os.path.exists(fact)) + actcontent = open(fact, 'r').read().rstrip() + self.assertTrue(actcontent == value) + def main(): unittest.main()