diff options
author | André Erdmann <dywi@mailerd.de> | 2013-09-18 15:09:31 +0200 |
---|---|---|
committer | André Erdmann <dywi@mailerd.de> | 2013-09-18 15:11:13 +0200 |
commit | f044064074f2f632f980233f604c362c42253de5 (patch) | |
tree | d0050151ebd6b2a35fee4c8bd1353fc9e98c7153 /roverlay/setupscript | |
parent | argparser: subparse_parsed() (diff) | |
download | R_overlay-f044064074f2f632f980233f604c362c42253de5.tar.gz R_overlay-f044064074f2f632f980233f604c362c42253de5.tar.bz2 R_overlay-f044064074f2f632f980233f604c362c42253de5.zip |
roverlay-setup: "hooks" command
The "hooks" can now be used to add/show/remove hook links.
Proper hook priority assignment ([0-9][0-9]-<hook name>.sh) is TODO.
Diffstat (limited to 'roverlay/setupscript')
-rw-r--r-- | roverlay/setupscript/hookenv.py | 276 | ||||
-rw-r--r-- | roverlay/setupscript/runtime.py | 53 |
2 files changed, 293 insertions, 36 deletions
diff --git a/roverlay/setupscript/hookenv.py b/roverlay/setupscript/hookenv.py index e942356..8d70f02 100644 --- a/roverlay/setupscript/hookenv.py +++ b/roverlay/setupscript/hookenv.py @@ -143,6 +143,10 @@ class HookScriptBase ( roverlay.util.objects.Referenceable ): ) # --- end of __str__ (...) --- + def has_priority ( self ): + return self.priority is not None and self.priority >= 0 + # --- end of has_priority (...) --- + def get_static_info ( self ): return roverlay.static.hookinfo.get ( self.name, None ) # --- end of get_static_info (...) --- @@ -246,6 +250,8 @@ class HookScript ( HookScriptBase ): def add_user_script ( self, user_script ): self.user_script_refs.add ( user_script.get_ref() ) + if self.priority is None and user_script.has_priority(): + self.priority = user_script.priority # --- end of add_user_script (...) --- def iter_user_scripts ( self, ignore_missing=True ): @@ -296,7 +302,21 @@ class HookScript ( HookScriptBase ): class HookScriptDirBase ( roverlay.util.objects.Referenceable ): - HOOK_SCRIPT_CLS = None + HOOK_SCRIPT_CLS = None + DIRNAMES_IGNORE = frozenset({ '.*', }) + FILENAMES_IGNORE = frozenset({ '.*', }) + + def dirname_filter ( self, dirname, _fnmatch=fnmatch.fnmatch ): + return all ( + not _fnmatch ( dirname, pat ) for pat in self.DIRNAMES_IGNORE + ) + # --- end of dirname_filter (...) --- + + def filename_filter ( self, filename, _fnmatch=fnmatch.fnmatch ): + return all ( + not _fnmatch ( filename, pat ) for pat in self.FILENAMES_IGNORE + ) + # --- end of filename_filter (...) --- def __init__ ( self, root ): super ( HookScriptDirBase, self ).__init__() @@ -328,6 +348,31 @@ class HookScriptDirBase ( roverlay.util.objects.Referenceable ): yield script # --- end of iter_scripts (...) --- + def find_all ( self, condition, c_args=(), c_kwargs={} ): + for script in self.iter_scripts(): + if condition ( script, *c_args, **c_kwargs ): + yield script + # --- end of find_all (...) --- + + def find ( self, condition, c_args=(), c_kwargs={}, **kw ): + try: + return next ( self.find_all ( condition, c_args, c_kwargs, **kw ) ) + except StopIteration: + return None + # --- end of find (...) --- + + def find_all_by_name ( self, name, **kw ): + return self.find_all ( + lambda s, n: s.name == n, c_args=( name, ), **kw + ) + # --- end of find_by_name (...) --- + + def find_all_by_name_begin ( self, prefix, **kw ): + return self.find_all ( + lambda s, pre: s.name.startswith ( pre ), c_args=( prefix, ), **kw + ) + # --- end of find_all_by_name_begin (...) --- + def scan ( self ): root = self.root try: @@ -338,26 +383,18 @@ class HookScriptDirBase ( roverlay.util.objects.Referenceable ): else: HOOK_CLS = self.HOOK_SCRIPT_CLS for fname in filenames: - fspath = root + os.sep + fname - if os.path.isfile ( fspath ): - script_obj = HOOK_CLS ( fspath, filename=fname ) - self.scripts [script_obj.name] = script_obj + if self.filename_filter ( fname ): + fspath = root + os.sep + fname + if os.path.isfile ( fspath ): + script_obj = HOOK_CLS ( fspath, filename=fname ) + self.scripts [script_obj.name] = script_obj # --- end of scan (...) --- # --- end of HookScriptDirBase --- class NestedHookScriptDirBase ( HookScriptDirBase ): - SUBDIR_CLS = collections.OrderedDict - DIRNAMES_IGNORE = frozenset({ '.*', }) - FILENAMES_IGNORE = frozenset({ '.*', }) - - def dirname_filter ( self, dirname, _fnmatch=fnmatch.fnmatch ): - for pattern in self.DIRNAMES_IGNORE: - if _fnmatch ( dirname, pattern ): - return False - return True - # --- end of dirname_filter (...) --- + SUBDIR_CLS = collections.OrderedDict def get_script ( self, name ): return [ @@ -365,13 +402,6 @@ class NestedHookScriptDirBase ( HookScriptDirBase ): ] # --- end of get_script (...) --- - def filename_filter ( self, filename, _fnmatch=fnmatch.fnmatch ): - for pattern in self.FILENAMES_IGNORE: - if _fnmatch ( filename, pattern ): - return False - return True - # --- end of filename_filter (...) --- - def create_hookscript ( self, fspath, filename, root ): return self.HOOK_SCRIPT_CLS ( fspath, filename=filename ) # --- end of create_hookscript (...) --- @@ -389,17 +419,40 @@ class NestedHookScriptDirBase ( HookScriptDirBase ): hook.event = event # --- end of scan (...) --- - def iter_scripts ( self ): + def iter_scripts ( self, event=None, ignore_missing=False ): # roverlay uses per-event subdirs containing hook files SUBDIR_CLS = self.SUBDIR_CLS - for event, subdir in self.scripts.items(): - if isinstance ( subdir, SUBDIR_CLS ): - for hook in subdir.values(): - if isinstance ( hook, HookScriptBase ) and hook.is_visible(): - #if not isinstance ( hook, SUBDIR_CLS ): - yield ( event, hook ) + + if event is None: + for event_name, subdir in self.scripts.items(): + if isinstance ( subdir, SUBDIR_CLS ): + for hook in subdir.values(): + if isinstance ( hook, HookScriptBase ) and hook.is_visible(): + #if not isinstance ( hook, SUBDIR_CLS ): + yield ( event_name, hook ) + else: + try: + subdir = self.scripts [event] + except KeyError: + pass + else: + assert isinstance ( subdir, SUBDIR_CLS ) + for script in subdir.values(): + yield script + # -- end if # --- end of iter_scripts (...) --- + def find_all ( self, condition, c_args=(), c_kwargs={}, event=None ): + if event is None: + for event_name, script in self.iter_scripts(): + if condition ( script, *c_args, **c_kwargs ): + yield script + else: + for script in self.iter_scripts ( event=event, ignore_missing=True ): + if condition ( script, *c_args, **c_kwargs ): + yield script + # --- end of find_all_by_name (...) --- + # --- end of NestedHookScriptDirBase --- @@ -512,15 +565,13 @@ class SetupHookEnvironment ( for name, ev_prio in info: event_names.update ( item[0] for item in ev_prio ) + # len(...) + 4 == len(...) + len("(__)") event_words = [ ( ev, (4+len(ev)) * ' ' ) for ev in sorted ( event_names ) ] if sort_info: - my_info = sorted ( - info, - key=lambda k: ( not k[1], k[0] ) - ) + my_info = sorted ( info, key=lambda k: ( not k[1], k[0] ) ) else: my_info = info @@ -581,6 +632,7 @@ class SetupHookEnvironment ( # --- end of gen_hook_info_lines (...) --- def setup ( self ): + self.hook_overwrite_control = self.setup_env.hook_overwrite additions_dir = self.config.get ( 'OVERLAY.additions_dir', None ) @@ -589,8 +641,21 @@ class SetupHookEnvironment ( os.path.join ( self.setup_env.data_root, 'hooks' ) ) self.hook_root.scan() - self._prio_gen = roverlay.util.counter.UnsafeCounter ( 30 ) + # TODO: + # prio_gen should be bound to user hook dirs (per event dir) + # (priority assignment needs to be changed to realize that) + # + self._prio_gen = roverlay.util.counter.SkippingPriorityGenerator ( + 30, skip=( + h.priority for h in self.hook_root.iter_scripts() + if h.has_priority() + ) + ) + # not strictly necessary + self._prio_gen.add_generated ( + roverlay.static.hookinfo.get_priorities() + ) if additions_dir: self.user_hooks = UserHookScriptDir ( @@ -604,6 +669,11 @@ class SetupHookEnvironment ( self.user_hooks.scan() if self.hook_root: self.user_hooks.make_hookdir_refs ( self.hook_root ) + + self._prio_gen.add_generated ( + h.priority for ev, h in self.user_hooks.iter_scripts() + if h.has_priority() + ) else: self.user_hooks = None # --- end of setup (...) --- @@ -700,6 +770,37 @@ class SetupHookEnvironment ( return success # --- end of link_hooks_v (...) --- + def link_hooks_to_events ( self, hooks, events ): + success = True + for event_name in events: + if not self.link_hooks_v ( event_name, hooks ): + success = False + return success + # --- end of link_hooks_to_events (...) --- + + def unlink_hooks ( self, hooks, symlinks_only=True ): + unlink = self.setup_env.fs_private.unlink + + if not symlinks_only: + for hook in hooks: + fspath = hook.fspath + if os.path.isdir ( fspath ): + self.error ( + "skipping {!r} - is a directory.\n".format ( fspath ) + ) + else: + unlink ( fspath ) + else: + for hook in hooks: + fspath = hook.fspath + if os.path.islink ( fspath ): + unlink ( fspath ) + else: + self.error ( + "skipping {!r} - not a symlink.\n".format ( fspath ) + ) + # --- end of unlink_hooks (...) --- + def enable_defaults ( self ): # not strict: missing hooks are ignored success = False @@ -715,8 +816,111 @@ class SetupHookEnvironment ( # --- end of enable_defaults (...) --- def run ( self ): - # TODO - self.info ( '\n'.join ( self.gen_hook_info_lines() ) ) + setup_env = self.setup_env + options = setup_env.options + command = options ['hook.action'] + hook_name = options ['hook.name'] + hook_events = options ['hook.events'] + + if command in { 'show', }: + self.info ( '\n'.join ( self.gen_hook_info_lines() ) ) + + elif command in { 'add', }: + hooks = list ( self.hook_root.find_all_by_name_begin ( hook_name ) ) + if not hooks: + self.error ( + "no hooks found matching {!r}\n".format ( hook_name ) + ) + # FIXME: exit code? + + elif len ( hooks ) == 1: + # good + self.link_hooks_to_events ( hooks, hook_events ) + + else: + exact_matches = [ k for k in hooks if k.name == hook_name ] + + if not exact_matches or len ( exact_matches ) != 1: + self.error ( + "ambiguous hook name: {!r} could match {}\n".format ( + hook_name, ', '.join ( hook.name for hook in hooks ) + ) + ) + else: + self.link_hooks_to_events ( exact_matches, hook_events ) + + elif command in { 'del', }: + hooks_to_unlink = [] + + if hook_events and not "all" in hook_events: + for event in hook_events: + hooks = list ( + self.user_hooks.find_all_by_name_begin ( + hook_name, event=event + ) + ) + if not hooks: + self.error ( + "no hooks found for event {!r} matching {!r}\n".format ( + event, hook_name + ) + ) + + elif len ( hooks ) == 1: + hooks_to_unlink.append ( hooks[0] ) + + else: + exact_matches = [ k for k in hooks if k.name == hook_name ] + + if not exact_matches or len ( exact_matches ) != 1: + self.error ( + 'ambiguous hook name {!r} for event {!r}: ' + 'could match {}\n'.format ( + hook_name, event, + ', '.join ( hook.name for hook in hooks ) + ) + ) + else: + hooks_to_unlink.append ( exact_matches[0] ) + # -- end for + else: + hooks = list ( + self.user_hooks.find_all_by_name_begin ( + hook_name, event=None + ) + ) + + if not hooks: + self.error ( + "no hooks found matching {!r}\n".format ( hook_name ) + ) + elif len ( hooks ) == 1: + #hooks_to_unlink = hooks + hooks_to_unlink.append ( hooks[0] ) + else: + # COULDFIX: it would be better to check if the hooks' + # realpaths (link dest) are identical + exact_matches = [ k for k in hooks if k.name == hook_name ] + num_event_matches = ( + collections.Counter ( k.event for k in exact_matches ) + ) + if exact_matches and all ( + k < 2 for k in num_event_matches.values() + ): + #hooks_to_unlink = exact_matches + hooks_to_unlink.extend ( exact_matches ) + else: + self.error ( + 'ambiguous hook name {!r}: could match {}\n'.format ( + hook_name, + ', '.join ( set ( k.name for k in hooks ) ) + ) + ) + # -- end if + + self.unlink_hooks ( hooks_to_unlink ) + else: + raise NotImplementedError ( command ) # --- end of run (...) --- # --- end of SetupHookEnvironment --- diff --git a/roverlay/setupscript/runtime.py b/roverlay/setupscript/runtime.py index 75bfff6..56b2a8b 100644 --- a/roverlay/setupscript/runtime.py +++ b/roverlay/setupscript/runtime.py @@ -47,8 +47,56 @@ def arg_stdout_or_fs ( value ): # --- end of arg_stdout_or_fs (...) --- +class HookManageParser ( roverlay.argparser.RoverlayArgumentParserBase ): + + SETUP_TARGETS = ( 'actions', ) + PARSE_TARGETS = ( 'actions', ) + + def setup_actions ( self ): + arg = self.arg + + arg ( + "hook.action", nargs="?", default="show", defkey="hook.action", + flags=self.ARG_WITH_DEFAULT, metavar='<action>', + choices=( "show", "add", "del", ), + help="action to perform (%(choices)s)", + ) + arg ( + "hook.name", nargs="?", default=None, defkey="hook.name", + flags=self.ARG_WITH_DEFAULT, metavar="<hook>", + help="hook name for add/del", + ) + + arg ( + "hook.events", nargs="*", + default=[ 'overlay_success', ], defkey="hook.events", + flags=self.ARG_WITH_DEFAULT, metavar="<event>", + help="event(s) to/from which <hook> should be added/removed", + ) + + return arg + # --- end of setup_actions (...) --- + + def parse_actions ( self ): + parsed = self.parsed + if not ( + parsed ['hook.name'] or parsed ['hook.action'] in { 'show', } + ): + self.parser.exit ( + "action {!r} needs <hook>.".format ( parsed ['hook.action'] ) + ) + # --- end of parse_actions (...) --- + +# --- end of HookManageParser --- + + class SetupArgumentParser ( roverlay.argparser.RoverlayArgumentParser ): MULTIPLE_COMMANDS = False + COMMAND_SUBPARSERS = { + 'init' : None, + 'mkconfig' : None, + 'hooks' : HookManageParser, + } COMMAND_DESCRIPTION = { 'init' : 'initialize roverlay\'s config and filesystem layout', 'mkconfig' : 'generate a config file', @@ -262,6 +310,11 @@ class SetupArgumentParser ( roverlay.argparser.RoverlayArgumentParser ): return arg # --- end of setup_hooks (...) --- + def setup_subparser_hooks ( self ): + subparser = self.add_subparser ( "hooks" ) + subparser.arg ( "--fuck" ) + # --- end of setup_subparser_hooks (...) --- + # --- end of SetupArgumentParser --- |