1
0
Fork 0
mirror of synced 2024-06-01 15:01:10 -04:00
This commit is contained in:
Joshua Blum 2015-04-24 23:19:24 +00:00
commit f671b4954a
12 changed files with 80 additions and 27 deletions

8
Makefile Normal file
View file

@ -0,0 +1,8 @@
.PHONY: clean lint
clean:
find . -type f -name '*.py[cod]' -delete
find . -type f -name '*.*~' -delete
lint: clean
-flake8 .

View file

@ -4,24 +4,30 @@ from .dispatcher import Dispatcher, DispatchError
from .messenger import Messenger from .messenger import Messenger
from .messenger import Level from .messenger import Level
def add_options(parser): def add_options(parser):
parser.add_argument('-Q', '--super-quiet', dest='super_quiet', action='store_true', parser.add_argument('-Q', '--super-quiet', dest='super_quiet',
help='suppress almost all output') action='store_true',
help='suppress almost all output')
parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', parser.add_argument('-q', '--quiet', dest='quiet', action='store_true',
help='suppress most output') help='suppress most output')
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
help='enable verbose output') help='enable verbose output')
parser.add_argument('-d', '--base-directory', nargs=1, parser.add_argument('-d', '--base-directory', nargs=1,
dest='base_directory', help='execute commands from within BASEDIR', dest='base_directory',
metavar='BASEDIR', required=True) help='execute commands from within BASEDIR',
metavar='BASEDIR', required=True)
parser.add_argument('-c', '--config-file', nargs=1, dest='config_file', parser.add_argument('-c', '--config-file', nargs=1, dest='config_file',
help='run commands given in CONFIGFILE', metavar='CONFIGFILE', help='run commands given in CONFIGFILE',
required=True) metavar='CONFIGFILE',
required=True)
def read_config(config_file): def read_config(config_file):
reader = ConfigReader(config_file) reader = ConfigReader(config_file)
return reader.get_config() return reader.get_config()
def main(): def main():
log = Messenger() log = Messenger()
try: try:
@ -40,7 +46,8 @@ def main():
if success: if success:
log.info('\n==> All tasks executed successfully') log.info('\n==> All tasks executed successfully')
else: else:
raise DispatchError('\n==> Some tasks were not executed successfully') raise DispatchError(
'\n==> Some tasks were not executed successfully')
except (ReadingError, DispatchError) as e: except (ReadingError, DispatchError) as e:
log.error('%s' % e) log.error('%s' % e)
exit(1) exit(1)

View file

@ -1,7 +1,9 @@
import yaml import yaml
from .util import string from .util import string
class ConfigReader(object): class ConfigReader(object):
def __init__(self, config_file_path): def __init__(self, config_file_path):
self._config = self._read(config_file_path) self._config = self._read(config_file_path)
@ -17,5 +19,6 @@ class ConfigReader(object):
def get_config(self): def get_config(self):
return self._config return self._config
class ReadingError(Exception): class ReadingError(Exception):
pass pass

View file

@ -2,7 +2,9 @@ import os
from .executor import Executor from .executor import Executor
from .messenger import Messenger from .messenger import Messenger
class Dispatcher(object): class Dispatcher(object):
def __init__(self, base_directory): def __init__(self, base_directory):
self._log = Messenger() self._log = Messenger()
self._set_base_directory(base_directory) self._set_base_directory(base_directory)
@ -28,7 +30,8 @@ class Dispatcher(object):
handled = True handled = True
except Exception: except Exception:
self._log.error( self._log.error(
'An error was encountered while executing action %s' % 'An error was encountered\
while executing action %s' %
action) action)
if not handled: if not handled:
success = False success = False
@ -37,7 +40,8 @@ class Dispatcher(object):
def _load_plugins(self): def _load_plugins(self):
self._plugins = [plugin(self._base_directory) self._plugins = [plugin(self._base_directory)
for plugin in Executor.__subclasses__()] for plugin in Executor.__subclasses__()]
class DispatchError(Exception): class DispatchError(Exception):
pass pass

View file

@ -1,7 +1,9 @@
import os import os
from . import Executor from . import Executor
class Cleaner(Executor): class Cleaner(Executor):
''' '''
Cleans broken symbolic links. Cleans broken symbolic links.
''' '''
@ -39,7 +41,10 @@ class Cleaner(Executor):
if not os.path.exists(path) and os.path.islink(path): if not os.path.exists(path) and os.path.islink(path):
if self._in_directory(path, self._base_directory): if self._in_directory(path, self._base_directory):
self._log.lowinfo('Removing invalid link %s -> %s' % self._log.lowinfo('Removing invalid link %s -> %s' %
(path, os.path.join(os.path.dirname(path), os.readlink(path)))) (path,
os.path.join(os.path.dirname(path),
os.readlink(path)
)))
os.remove(path) os.remove(path)
return True return True

View file

@ -1,7 +1,10 @@
import os, subprocess import os
import subprocess
from . import Executor from . import Executor
class CommandRunner(Executor): class CommandRunner(Executor):
''' '''
Run arbitrary shell commands. Run arbitrary shell commands.
''' '''
@ -14,7 +17,7 @@ class CommandRunner(Executor):
def handle(self, directive, data): def handle(self, directive, data):
if directive != self._directive: if directive != self._directive:
raise ValueError('CommandRunner cannot handle directive %s' % raise ValueError('CommandRunner cannot handle directive %s' %
directive) directive)
return self._process_commands(data) return self._process_commands(data)
def _process_commands(self, data): def _process_commands(self, data):
@ -41,8 +44,9 @@ class CommandRunner(Executor):
self._log.lowinfo(cmd) self._log.lowinfo(cmd)
else: else:
self._log.lowinfo('%s [%s]' % (msg, cmd)) self._log.lowinfo('%s [%s]' % (msg, cmd))
ret = subprocess.call(cmd, shell=True, stdin=stdin, stdout=stdout, ret = subprocess.call(cmd, shell=True,
stderr=stderr, cwd=self._base_directory) stdin=stdin, stdout=stdout,
stderr=stderr, cwd=self._base_directory)
if ret != 0: if ret != 0:
success = False success = False
self._log.warning('Command [%s] failed' % cmd) self._log.warning('Command [%s] failed' % cmd)

View file

@ -1,6 +1,8 @@
from ..messenger import Messenger from ..messenger import Messenger
class Executor(object): class Executor(object):
''' '''
Abstract base class for commands that process directives. Abstract base class for commands that process directives.
''' '''

View file

@ -1,7 +1,10 @@
import os, shutil import os
import shutil
from . import Executor from . import Executor
class Linker(Executor): class Linker(Executor):
''' '''
Symbolically links dotfiles. Symbolically links dotfiles.
''' '''
@ -60,7 +63,8 @@ class Linker(Executor):
def _create(self, path): def _create(self, path):
success = True success = True
parent = os.path.abspath(os.path.join(os.path.expanduser(path), os.pardir)) parent = os.path.abspath(
os.path.join(os.path.expanduser(path), os.pardir))
if not self._exists(parent): if not self._exists(parent):
try: try:
os.makedirs(parent) os.makedirs(parent)
@ -100,29 +104,32 @@ class Linker(Executor):
if (not self._exists(link_name) and self._is_link(link_name) and if (not self._exists(link_name) and self._is_link(link_name) and
self._link_destination(link_name) != source): self._link_destination(link_name) != source):
self._log.warning('Invalid link %s -> %s' % self._log.warning('Invalid link %s -> %s' %
(link_name, self._link_destination(link_name))) (link_name, self._link_destination(link_name)))
elif not self._exists(link_name) and self._exists(source): elif not self._exists(link_name) and self._exists(source):
try: try:
os.symlink(source, os.path.expanduser(link_name)) os.symlink(source, os.path.expanduser(link_name))
except OSError: except OSError:
self._log.warning('Linking failed %s -> %s' % (link_name, source)) self._log.warning('Linking failed %s -> %s' %
(link_name, source))
else: else:
self._log.lowinfo('Creating link %s -> %s' % (link_name, source)) self._log.lowinfo('Creating link %s -> %s' %
(link_name, source))
success = True success = True
elif self._exists(link_name) and not self._is_link(link_name): elif self._exists(link_name) and not self._is_link(link_name):
self._log.warning( self._log.warning(
'%s already exists but is a regular file or directory' % '%s already exists but is a regular file or directory' %
link_name) link_name)
elif self._is_link(link_name) and self._link_destination(link_name) != source: elif self._is_link(link_name)\
and self._link_destination(link_name) != source:
self._log.warning('Incorrect link %s -> %s' % self._log.warning('Incorrect link %s -> %s' %
(link_name, self._link_destination(link_name))) (link_name, self._link_destination(link_name)))
elif not self._exists(source): elif not self._exists(source):
if self._is_link(link_name): if self._is_link(link_name):
self._log.warning('Nonexistent target %s -> %s' % self._log.warning('Nonexistent target %s -> %s' %
(link_name, source)) (link_name, source))
else: else:
self._log.warning('Nonexistent target for %s : %s' % self._log.warning('Nonexistent target for %s : %s' %
(link_name, source)) (link_name, source))
else: else:
self._log.lowinfo('Link exists %s -> %s' % (link_name, source)) self._log.lowinfo('Link exists %s -> %s' % (link_name, source))
success = True success = True

View file

@ -4,8 +4,10 @@ from ..util.compat import with_metaclass
from .color import Color from .color import Color
from .level import Level from .level import Level
class Messenger(with_metaclass(Singleton, object)): class Messenger(with_metaclass(Singleton, object)):
def __init__(self, level = Level.LOWINFO):
def __init__(self, level=Level.LOWINFO):
self.set_level(level) self.set_level(level)
def set_level(self, level): def set_level(self, level):

View file

@ -1,5 +1,6 @@
def with_metaclass(meta, *bases): def with_metaclass(meta, *bases):
class metaclass(meta): class metaclass(meta):
def __new__(cls, name, this_bases, d): def __new__(cls, name, this_bases, d):
return meta(name, bases, d) return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {}) return type.__new__(metaclass, 'temporary_class', (), {})

View file

@ -1,6 +1,8 @@
class Singleton(type): class Singleton(type):
_instances = {} _instances = {}
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
if cls not in cls._instances: if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) cls._instances[cls] = super(
Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls] return cls._instances[cls]

8
setup.cfg Normal file
View file

@ -0,0 +1,8 @@
[flake8]
exclude = lib/*
# List of errors and warnings:
# http://flake8.readthedocs.org/en/latest/warnings.html
# Things we ignore:
# F401 module imported but unused
ignore = F401