def __init__(self, path, config=None, step=None, step_done=None, id=None): self._logger = logging.getLogger('Workflow') self._logger.debug("Initializing workflow {0}".format(path)) self.step = step self.step_done = step_done if not isinstance(path, Path): path = Path(path) self.path = path if not self.path.exists(): self.path.mkdir() self.id = id if self.images: self.pages_shot = len(self.images) else: self.pages_shot = 0 # See if supplied `config` is already a valid ConfigView object if isinstance(config, confit.ConfigView): self.config = config elif isinstance(config, Configuration): self.config = config.as_view() else: self.config = self._load_config(config) self._capture_lock = threading.RLock() self.active = False self._devices = None self._pluginmanager = None # Instantiate plugins self.plugins = [cls(self.config) for cls in plugin.get_plugins(*self.config["plugins"].get()) .values()]
def __init__(self, path, config=None, step=None, step_done=None, id=None): self._logger = logging.getLogger('Workflow') self._logger.debug("Initializing workflow {0}".format(path)) self.step = step self.step_done = step_done if not isinstance(path, Path): path = Path(path) self.path = path if not self.path.exists(): self.path.mkdir() self.id = id if self.images: self.pages_shot = len(self.images) else: self.pages_shot = 0 # See if supplied `config` is already a valid ConfigView object if isinstance(config, confit.ConfigView): self.config = config elif isinstance(config, Configuration): self.config = config.as_view() else: self.config = self._load_config(config) self._capture_lock = threading.RLock() self.active = False self._devices = None self._pluginmanager = None # Instantiate plugins self.plugins = [ cls(self.config) for cls in plugin.get_plugins( *self.config["plugins"].get()).values() ]
def on_update_plugin_selection(self, event): selection = self.plugin_select.selection() self.selected_plugins = list(selection) try: exts = [name for name, cls in plugin.get_plugins(*selection) .iteritems() if issubclass(cls, plugin.ProcessHookMixin)] except plugin.ExtensionException as e: exts = [] failed_ext = e.extension messagebox.showerror(message=e.message) #self.plugin_select.selection_remove(failed_ext) ext_id = self.plugin_select.index(failed_ext) self.plugin_select.delete(failed_ext) self.plugin_select.insert('', ext_id, failed_ext, text=failed_ext, tags=["missingdep"]) selection = tuple(x for x in selection if x != failed_ext) for item in selection: if item in exts: if not self.processorder_tree.exists(item): self.processorder_tree.insert('', 'end', item, text=item) else: continue for item in self.processorder_tree.get_children(): if item not in selection: self.processorder_tree.delete(item) self.update_plugin_config(selection)
def _setup_processing_pipeline(config): """ Display dialog to configure order of postprocessing plugins and update the configuration accordingly. :param config: Currently active global configuration :type config: :py:class:`spreads.config.Configuration` """ # Only get names of postprocessing plugins. For this we have to load all # enabled plugins first and check if they implement the correct hook. exts = [name for name, cls in plugin.get_plugins(*config["plugins"].get()) .iteritems() if issubclass(cls, plugin.ProcessHooksMixin)] if not exts: return print("The following postprocessing plugins were detected:") print("\n".join(" - {0}".format(ext) for ext in exts)) while True: answer = raw_input("Please enter the extensions in the order that they" " should be invoked, separated by commas or hit" " enter to keep the current order:\n") if not answer: plugins = exts else: plugins = [x.strip() for x in answer.split(',')] if any(x not in exts for x in plugins): print(colorize("At least one of the entered extensions was not" "found, please try again!", colorama.Fore.RED)) else: break # Append other plugins after the postprocessing plugins config["plugins"] = plugins + [x for x in config["plugins"].get() if x not in plugins]
def _setup_processing_pipeline(config): exts = [ name for name, cls in plugin.get_plugins( *config["plugins"].get()).iteritems() if issubclass(cls, plugin.ProcessHookMixin) ] if not exts: return print("The following postprocessing plugins were detected:") print("\n".join(" - {0}".format(ext) for ext in exts)) while True: answer = raw_input("Please enter the extensions in the order that they" " should be invoked, separated by commas or hit" " enter to keep the current order:\n") if not answer: plugins = exts else: plugins = [x.strip() for x in answer.split(',')] if any(x not in exts for x in plugins): print( colorize( "At least one of the entered extensions was not" "found, please try again!", colorama.Fore.RED)) else: break config["plugins"] = plugins + [ x for x in config["plugins"].get() if x not in plugins ]
def _list_plugins(): """ Get a the names of all activated plugins grouped by type. :rtype: dict (key: unicode, value: list of unicode) """ config = app.config['default_config'] plugins = plugin.get_plugins(*config['plugins'].get()) return { 'capture': [ name for name, cls in plugins.iteritems() if issubclass(cls, plugin.CaptureHooksMixin) ], 'trigger': [ name for name, cls in plugins.iteritems() if issubclass(cls, plugin.TriggerHooksMixin) ], 'postprocessing': [ name for name, cls in plugins.iteritems() if issubclass(cls, plugin.ProcessHooksMixin) ], 'output': [ name for name, cls in plugins.iteritems() if issubclass(cls, plugin.OutputHooksMixin) ] }
def __init__(self, path, config=None, metadata=None): self._logger = logging.getLogger('Workflow') self._logger.debug("Initializing workflow {0}".format(path)) self.status = { 'step': None, 'step_progress': None, 'prepared': False } if not isinstance(path, Path): path = Path(path) self.path = path is_new = not self.path.exists() try: self.bag = bagit.Bag(unicode(self.path)) except bagit.BagError: # Convert non-bagit directories from older versions self.bag = bagit.Bag.convert_directory(unicode(self.path)) if not self.slug: self.slug = util.slugify(unicode(self.path.name)) if not self.id: self.id = unicode(uuid.uuid4()) # See if supplied `config` is already a valid ConfigView object if isinstance(config, confit.ConfigView): self.config = config elif isinstance(config, Configuration): self.config = config.as_view() else: self.config = self._load_config(config) self._metadata = Metadata(self.path) if metadata: self.metadata = metadata self._capture_lock = threading.RLock() self._devices = None self._pluginmanager = None self._threadpool = concfut.ThreadPoolExecutor(max_workers=1) self._pending_tasks = [] # Filter out subcommand plugins, since these are not workflow-specific plugin_classes = [ (name, cls) for name, cls in plugin.get_plugins(*self.config["plugins"] .get()).iteritems() if not cls.__bases__ == (plugin.SubcommandHookMixin,)] self.plugins = [cls(self.config) for name, cls in plugin_classes] self.config['plugins'] = [name for name, cls in plugin_classes] # Save configuration self._save_config() self.pages = self._load_pages() self.table_of_contents = self.load_toc() if is_new: on_created.send(self, workflow=self)
def list_plugins(): config = app.config['default_config'] plugins = plugin.get_plugins(*config['plugins'].get()) return { 'capture': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.CaptureHooksMixin)], 'trigger': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.TriggerHooksMixin)], 'postprocessing': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.ProcessHookMixin)], 'output': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.OutputHookMixin)] }
def list_plugins(): config = app.config['default_config'] plugins = plugin.get_plugins(*config['plugins'].get()) return { 'capture': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.CaptureHooksMixin)], 'trigger': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.TriggerHooksMixin)], 'postprocessing': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.ProcessHookMixin)], 'output': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.OutputHookMixin)] }
def get_plugin_templates(): """ Return the names of all globally activated plugins and their configuration templates. """ config = app.config['default_config'] plugins = plugin.get_plugins(*config['plugins'].get()) scanner_exts = [ name for name, cls in plugins.iteritems() if any( issubclass(cls, mixin) for mixin in (plugin.CaptureHooksMixin, plugin.TriggerHooksMixin)) ] processor_exts = [ name for name, cls in plugins.iteritems() if any( issubclass(cls, mixin) for mixin in (plugin.ProcessHookMixin, plugin.OutputHookMixin)) ] if app.config['mode'] == 'scanner': templates = { section: config.templates[section] for section in config.templates if section in scanner_exts or section == 'device' } elif app.config['mode'] == 'processor': templates = { section: config.templates[section] for section in config.templates if section in processor_exts } elif app.config['mode'] == 'full': templates = { section: config.templates[section] for section in config.templates if section != 'core' } rv = dict() for plugname, options in templates.iteritems(): if options is None: continue for key, option in options.iteritems(): if option.selectable: value = [config[plugname][key].get()] value += [x for x in option.value if x not in value] else: value = config[plugname][key].get() if not plugname in rv: rv[plugname] = dict() rv[plugname][key] = dict(value=value, docstring=option.docstring, selectable=option.selectable) return rv
def __init__(self, path, config=None, metadata=None): self._logger = logging.getLogger('Workflow') self._logger.debug("Initializing workflow {0}".format(path)) self.status = {'step': None, 'step_progress': None, 'prepared': False} if not isinstance(path, Path): path = Path(path) self.path = path is_new = not self.path.exists() try: self.bag = bagit.Bag(unicode(self.path)) except bagit.BagError: # Convert non-bagit directories from older versions self.bag = bagit.Bag.convert_directory(unicode(self.path)) if not self.slug: self.slug = util.slugify(unicode(self.path.name)) if not self.id: self.id = unicode(uuid.uuid4()) # See if supplied `config` is already a valid ConfigView object if isinstance(config, confit.ConfigView): self.config = config elif isinstance(config, Configuration): self.config = config.as_view() else: self.config = self._load_config(config) if metadata: self.metadata = metadata self._capture_lock = threading.RLock() self._devices = None self._pluginmanager = None self._pool_executor = None # Filter out subcommand plugins, since these are not workflow-specific plugin_classes = [ (name, cls) for name, cls in plugin.get_plugins( *self.config["plugins"].get()).iteritems() if not cls.__bases__ == (plugin.SubcommandHookMixin, ) ] self.plugins = [cls(self.config) for name, cls in plugin_classes] self.config['plugins'] = [name for name, cls in plugin_classes] # Save configuration self._save_config() self.pages = self._load_pages() self.table_of_contents = self.load_toc() if is_new: on_created.send(self, workflow=self)
def _list_plugins(): """ Get a the names of all activated plugins grouped by type. :rtype: dict (key: unicode, value: list of unicode) """ config = app.config['default_config'] plugins = plugin.get_plugins(*config['plugins'].get()) return { 'capture': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.CaptureHooksMixin)], 'trigger': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.TriggerHooksMixin)], 'postprocessing': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.ProcessHooksMixin)], 'output': [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.OutputHooksMixin)] }
def _setup_processing_pipeline(config): exts = [name for name, cls in plugin.get_plugins(*config["plugins"].get()) .iteritems() if issubclass(cls, plugin.ProcessHookMixin)] if not exts: return print("The following postprocessing plugins were detected:") print("\n".join(" - {0}".format(ext) for ext in exts)) while True: answer = raw_input("Please enter the extensions in the order that they" " should be invoked, separated by commas:\n") plugins = [x.strip() for x in answer.split(',')] if any(x not in exts for x in plugins): print(colorize("At least one of the entered extensions was not" "found, please try again!", colorama.Fore.RED)) else: break config["plugins"] = plugins + [x for x in config["plugins"].get() if x not in plugins]
def on_update_plugin_selection(self, event): """ Callback for when the user toggles a plugin. Tries to load the newly selected plugins. If loading fails, a dialog with the cause of failure will be displayed and the plugin will be highlighted in the list and made inactive. If successful, the plugin will be added to the 'postprocessing order' widget (if it implements :py:class:`spreads.plugin.ProcessHooksMixin`) and the configuration will be updated. :param event: Event from Tkinter :type event: :py:class:`Tkinter.Event` """ selection = self.plugin_select.selection() self.selected_plugins = list(selection) try: exts = [ name for name, cls in plugin.get_plugins(*selection).iteritems() if issubclass(cls, plugin.ProcessHooksMixin) ] except plugin.ExtensionException as e: exts = [] failed_ext = e.extension messagebox.showerror(message=e.message) ext_id = self.plugin_select.index(failed_ext) self.plugin_select.delete(failed_ext) self.plugin_select.insert('', ext_id, failed_ext, text=failed_ext, tags=["missingdep"]) selection = tuple(x for x in selection if x != failed_ext) for item in selection: if item in exts: if not self.processorder_tree.exists(item): self.processorder_tree.insert('', 'end', item, text=item) else: continue for item in self.processorder_tree.get_children(): if item not in selection: self.processorder_tree.delete(item) self.update_plugin_config(selection)
def get_plugin_templates(): """ Return the names of all globally activated plugins and their configuration templates. """ config = app.config['default_config'] plugins = plugin.get_plugins(*config['plugins'].get()) scanner_exts = [name for name, cls in plugins.iteritems() if any(issubclass(cls, mixin) for mixin in (plugin.CaptureHooksMixin, plugin.TriggerHooksMixin))] processor_exts = [name for name, cls in plugins.iteritems() if any(issubclass(cls, mixin) for mixin in (plugin.ProcessHookMixin, plugin.OutputHookMixin))] if app.config['mode'] == 'scanner': templates = {section: config.templates[section] for section in config.templates if section in scanner_exts or section == 'device'} elif app.config['mode'] == 'processor': templates = {section: config.templates[section] for section in config.templates if section in processor_exts} elif app.config['mode'] == 'full': templates = {section: config.templates[section] for section in config.templates if section != 'core'} rv = dict() for plugname, options in templates.iteritems(): if options is None: continue for key, option in options.iteritems(): if option.selectable: value = [config[plugname][key].get()] value += [x for x in option.value if x not in value] else: value = config[plugname][key].get() if not plugname in rv: rv[plugname] = dict() rv[plugname][key] = dict(value=value, docstring=option.docstring, selectable=option.selectable) return rv
def on_update_plugin_selection(self, event): """ Callback for when the user toggles a plugin. Tries to load the newly selected plugins. If loading fails, a dialog with the cause of failure will be displayed and the plugin will be highlighted in the list and made inactive. If successful, the plugin will be added to the 'postprocessing order' widget (if it implements :py:class:`spreads.plugin.ProcessHooksMixin`) and the configuration will be updated. :param event: Event from Tkinter :type event: :py:class:`Tkinter.Event` """ selection = self.plugin_select.selection() self.selected_plugins = list(selection) try: exts = [name for name, cls in plugin.get_plugins(*selection) .iteritems() if issubclass(cls, plugin.ProcessHooksMixin)] except plugin.ExtensionException as e: exts = [] failed_ext = e.extension messagebox.showerror(message=e.message) ext_id = self.plugin_select.index(failed_ext) self.plugin_select.delete(failed_ext) self.plugin_select.insert('', ext_id, failed_ext, text=failed_ext, tags=["missingdep"]) selection = tuple(x for x in selection if x != failed_ext) for item in selection: if item in exts: if not self.processorder_tree.exists(item): self.processorder_tree.insert('', 'end', item, text=item) else: continue for item in self.processorder_tree.get_children(): if item not in selection: self.processorder_tree.delete(item) self.update_plugin_config(selection)
def _setup_processing_pipeline(config): """ Display dialog to configure order of postprocessing plugins and update the configuration accordingly. :param config: Currently active global configuration :type config: :py:class:`spreads.config.Configuration` """ # Only get names of postprocessing plugins. For this we have to load all # enabled plugins first and check if they implement the correct hook. exts = [ name for name, cls in plugin.get_plugins( *config["plugins"].get()).iteritems() if issubclass(cls, plugin.ProcessHooksMixin) ] if not exts: return print("The following postprocessing plugins were detected:") print("\n".join(" - {0}".format(ext) for ext in exts)) while True: answer = raw_input("Please enter the extensions in the order that they" " should be invoked, separated by commas or hit" " enter to keep the current order:\n") if not answer: plugins = exts else: plugins = [x.strip() for x in answer.split(',')] if any(x not in exts for x in plugins): print( colorize( "At least one of the entered extensions was not" "found, please try again!", colorama.Fore.RED)) else: break # Append other plugins after the postprocessing plugins config["plugins"] = plugins + [ x for x in config["plugins"].get() if x not in plugins ]
def setup_parser(config): plugins = plugin.get_plugins(*config["plugins"].get()) rootparser = argparse.ArgumentParser( description="Scanning Tool for DIY Book Scanner", formatter_class=argparse.RawDescriptionHelpFormatter) rootparser.add_argument( '-V', '--version', action='version', version=( "spreads {0}\n\n" "Licensed under the terms of the GNU Affero General Public " "License 3.0.\n" "(C) 2013-2014 Johannes Baiter <*****@*****.**>\n" "For a complete list of contributors see:\n" "https://github.com/DIYBookScanner/spreads/graphs/contributors\n\n" .format(util.get_version()))) for key, option in config.templates['core'].iteritems(): try: add_argument_from_template('core', key, option, rootparser, config['core'][key].get()) except TypeError: continue subparsers = rootparser.add_subparsers() wizard_parser = subparsers.add_parser( 'wizard', help="Interactive mode") wizard_parser.add_argument( "path", type=unicode, help="Project path") wizard_parser.set_defaults(subcommand=cli.wizard) config_parser = subparsers.add_parser( 'configure', help="Perform initial configuration") config_parser.set_defaults(subcommand=cli.configure) try: import spreads.tkconfigure as tkconfigure guiconfig_parser = subparsers.add_parser( 'guiconfigure', help="Perform initial configuration with a GUI") guiconfig_parser.set_defaults(subcommand=tkconfigure.configure) except ImportError: print "Could not load _tkinter module, disabling guiconfigure command" capture_parser = subparsers.add_parser( 'capture', help="Start the capturing workflow") capture_parser.add_argument( "path", type=unicode, help="Project path") capture_parser.set_defaults(subcommand=cli.capture) # Add arguments from plugins for parser in (capture_parser, wizard_parser): ext_names = [ name for name, cls in plugins.iteritems() if any(issubclass(cls, mixin) for mixin in (plugin.CaptureHooksMixin, plugin.TriggerHooksMixin))] ext_names.append('device') for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue postprocess_parser = subparsers.add_parser( 'postprocess', help="Postprocess scanned images.") postprocess_parser.add_argument( "path", type=unicode, help="Project path") postprocess_parser.add_argument( "--jobs", "-j", dest="jobs", type=int, default=None, metavar="<int>", help="Number of concurrent processes") postprocess_parser.set_defaults(subcommand=cli.postprocess) # Add arguments from plugins for parser in (postprocess_parser, wizard_parser): ext_names = [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.ProcessHookMixin)] for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue output_parser = subparsers.add_parser( 'output', help="Generate output files.") output_parser.add_argument( "path", type=unicode, help="Project path") output_parser.set_defaults(subcommand=cli.output) # Add arguments from plugins for parser in (output_parser, wizard_parser): ext_names = [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.OutputHookMixin)] for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue # Add custom subcommands from plugins if config["plugins"].get(): classes = (cls for cls in plugins.values() if issubclass(cls, plugin.SubcommandHookMixin)) for cls in classes: cls.add_command_parser(subparsers, config) return rootparser
def setup_parser(config): plugins = plugin.get_plugins(*config["plugins"].get()) rootparser = argparse.ArgumentParser( description="Scanning Tool for DIY Book Scanner") subparsers = rootparser.add_subparsers() for key, option in config.templates['core'].iteritems(): try: add_argument_from_template('core', key, option, rootparser, config['core'][key].get()) except TypeError: continue wizard_parser = subparsers.add_parser( 'wizard', help="Interactive mode") wizard_parser.add_argument( "path", type=unicode, help="Project path") wizard_parser.set_defaults(subcommand=cli.wizard) config_parser = subparsers.add_parser( 'configure', help="Perform initial configuration") config_parser.set_defaults(subcommand=cli.configure) try: import spreads.tkconfigure as tkconfigure guiconfig_parser = subparsers.add_parser( 'guiconfigure', help="Perform initial configuration with a GUI") guiconfig_parser.set_defaults(subcommand=tkconfigure.configure) except ImportError: print "Could not load _tkinter module, disabling guiconfigure command" capture_parser = subparsers.add_parser( 'capture', help="Start the capturing workflow") capture_parser.add_argument( "path", type=unicode, help="Project path") capture_parser.set_defaults(subcommand=cli.capture) # Add arguments from plugins for parser in (capture_parser, wizard_parser): ext_names = [ name for name, cls in plugins.iteritems() if any(issubclass(cls, mixin) for mixin in (plugin.CaptureHooksMixin, plugin.TriggerHooksMixin))] ext_names.append('driver') for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue postprocess_parser = subparsers.add_parser( 'postprocess', help="Postprocess scanned images.") postprocess_parser.add_argument( "path", type=unicode, help="Project path") postprocess_parser.add_argument( "--jobs", "-j", dest="jobs", type=int, default=None, metavar="<int>", help="Number of concurrent processes") postprocess_parser.set_defaults(subcommand=cli.postprocess) # Add arguments from plugins for parser in (postprocess_parser, wizard_parser): ext_names = [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.ProcessHookMixin)] for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue output_parser = subparsers.add_parser( 'output', help="Generate output files.") output_parser.add_argument( "path", type=unicode, help="Project path") output_parser.set_defaults(subcommand=cli.output) # Add arguments from plugins for parser in (output_parser, wizard_parser): ext_names = [name for name, cls in plugins.iteritems() if issubclass(cls, plugin.OutputHookMixin)] for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue # Add custom subcommands from plugins if config["plugins"].get(): classes = (cls for cls in plugins.values() if issubclass(cls, plugin.SubcommandHookMixin)) for cls in classes: cls.add_command_parser(subparsers, config) return rootparser
def setup_parser(config): """ Sets up an :py:class:`argparse.ArgumentParser` instance with all options and subcommands that are available in the core and activated plugins. :param config: Current application configuration :type config: :py:class:`spreads.config.Configuration` :returns: Fully initialized argument parser :rtype: :py:class:`argparse.ArgumentParser` """ plugins = plugin.get_plugins(*config["plugins"].get()) def _add_arguments(parsers, mixins, extra_names=None): if extra_names is None: extra_names = [] for parser in parsers: # Only plugins that implement the capture or trigger hook mixins # and the currently active device configuration are relevant for # this subcommand. ext_names = [name for name, cls in plugins.iteritems() if any(issubclass(cls, mixin) for mixin in mixins)] ext_names.extend(extra_names) for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): if not should_show_argument(option, config['plugins'].get()): continue try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue rootparser = argparse.ArgumentParser( description="Scanning Tool for DIY Book Scanner", formatter_class=argparse.RawDescriptionHelpFormatter) rootparser.add_argument( '-V', '--version', action='version', version=( "spreads {0}\n\n" "Licensed under the terms of the GNU Affero General Public " "License 3.0.\n" "(C) 2013-2014 Johannes Baiter <*****@*****.**>\n" "For a complete list of contributors see:\n" "https://github.com/DIYBookScanner/spreads/graphs/contributors\n\n" .format(util.get_version()))) for key, option in config.templates['core'].iteritems(): if not should_show_argument(option, config['plugins'].get()): continue try: add_argument_from_template('core', key, option, rootparser, config['core'][key].get()) except TypeError: continue subparsers = rootparser.add_subparsers() wizard_parser = subparsers.add_parser( 'wizard', help="Interactive mode") wizard_parser.add_argument( "path", type=unicode, help="Project path") wizard_parser.set_defaults(subcommand=cli.wizard) config_parser = subparsers.add_parser( 'configure', help="Perform initial configuration") config_parser.set_defaults(subcommand=cli.configure) try: import spreads.tkconfigure as tkconfigure guiconfig_parser = subparsers.add_parser( 'guiconfigure', help="Perform initial configuration with a GUI") guiconfig_parser.set_defaults(subcommand=tkconfigure.configure) except ImportError: pass capture_parser = subparsers.add_parser( 'capture', help="Start the capturing workflow") capture_parser.add_argument( "path", type=unicode, help="Project path") capture_parser.set_defaults(subcommand=cli.capture) # Add arguments from plugins _add_arguments(parsers=(capture_parser, wizard_parser), mixins=(plugin.CaptureHooksMixin, plugin.TriggerHooksMixin), extra_names=('device',)) postprocess_parser = subparsers.add_parser( 'postprocess', help="Postprocess scanned images.") postprocess_parser.add_argument( "path", type=unicode, help="Project path") postprocess_parser.add_argument( "--jobs", "-j", dest="jobs", type=int, default=None, metavar="<int>", help="Number of concurrent processes") postprocess_parser.set_defaults(subcommand=cli.postprocess) _add_arguments(parsers=(postprocess_parser, wizard_parser), mixins=(plugin.ProcessHooksMixin,)) output_parser = subparsers.add_parser( 'output', help="Generate output files.") output_parser.add_argument( "path", type=unicode, help="Project path") output_parser.set_defaults(subcommand=cli.output) _add_arguments(parsers=(output_parser, wizard_parser), mixins=(plugin.OutputHooksMixin,)) # Add custom subcommands from plugins if config["plugins"].get(): classes = (cls for cls in plugins.values() if issubclass(cls, plugin.SubcommandHooksMixin)) for cls in classes: cls.add_command_parser(subparsers, config) return rootparser
def __init__(self, path, config=None, metadata=None): self._logger = logging.getLogger('Workflow') self._logger.debug("Initializing workflow {0}".format(path)) self.status = { 'step': None, 'step_progress': None, 'prepared': False } if not isinstance(path, Path): path = Path(path) self.path = path is_new = not self.path.exists() try: self.bag = bagit.Bag(unicode(self.path)) except bagit.BagError: # Convert non-bagit directories from older versions self.bag = bagit.Bag.convert_directory(unicode(self.path)) if not self.slug: self.slug = util.slugify(unicode(self.path.name)) if not self.id: self.id = unicode(uuid.uuid4()) # See if supplied `config` is already a valid ConfigView object if isinstance(config, confit.ConfigView): self.config = config elif isinstance(config, Configuration): self.config = config.as_view() else: self.config = self._load_config(config) #: :py:class:`spreads.metadata.Metadata` instance that backs the #: corresponding getter and setter self._metadata = Metadata(self.path) # This will invoke the setter if metadata: self.metadata = metadata #: Lock that is held when a shot is being executed during the capture #: phase self._capture_lock = threading.RLock() #: List of :py:class:`spreads.plugin.DeviceDriver` instances that #: backs the corresponding getters and setters self._devices = None # Thread pool for background tasks self._threadpool = concfut.ThreadPoolExecutor(max_workers=1) # List of unfinished :py:class:`concurrent.futures.Future` instances self._pending_tasks = [] # Filter out subcommand plugins, since these are not workflow-specific plugin_classes = [ (name, cls) for name, cls in plugin.get_plugins(*self.config["plugins"] .get()).iteritems() if not cls.__bases__ == (plugin.SubcommandHooksMixin,)] self._plugins = [cls(self.config) for name, cls in plugin_classes] self.config['plugins'] = [name for name, cls in plugin_classes] self._save_config() self.pages = self._load_pages() self.table_of_contents = self._load_toc() if is_new: on_created.send(self, workflow=self)
def setup_parser(config): plugins = plugin.get_plugins(*config["plugins"].get()) rootparser = argparse.ArgumentParser( description="Scanning Tool for DIY Book Scanner") subparsers = rootparser.add_subparsers() for key, option in config.templates['core'].iteritems(): try: add_argument_from_template('core', key, option, rootparser) except TypeError: continue wizard_parser = subparsers.add_parser('wizard', help="Interactive mode") wizard_parser.add_argument("path", type=unicode, help="Project path") wizard_parser.set_defaults(subcommand=wizard) config_parser = subparsers.add_parser('configure', help="Perform initial configuration") config_parser.set_defaults(subcommand=configure) capture_parser = subparsers.add_parser('capture', help="Start the capturing workflow") capture_parser.add_argument("path", type=unicode, help="Project path") capture_parser.set_defaults(subcommand=capture) # Add arguments from plugins for parser in (capture_parser, wizard_parser): ext_names = [ name for name, cls in plugins.iteritems() if any( issubclass(cls, mixin) for mixin in (plugin.CaptureHooksMixin, plugin.TriggerHooksMixin)) ] ext_names.append('driver') for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser) except TypeError: continue postprocess_parser = subparsers.add_parser( 'postprocess', help="Postprocess scanned images.") postprocess_parser.add_argument("path", type=unicode, help="Project path") postprocess_parser.add_argument("--jobs", "-j", dest="jobs", type=int, default=None, metavar="<int>", help="Number of concurrent processes") postprocess_parser.set_defaults(subcommand=postprocess) # Add arguments from plugins for parser in (postprocess_parser, wizard_parser): ext_names = [ name for name, cls in plugins.iteritems() if issubclass(cls, plugin.ProcessHookMixin) ] for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser) except TypeError: continue output_parser = subparsers.add_parser('output', help="Generate output files.") output_parser.add_argument("path", type=unicode, help="Project path") output_parser.set_defaults(subcommand=output) # Add arguments from plugins for parser in (output_parser, wizard_parser): ext_names = [ name for name, cls in plugins.iteritems() if issubclass(cls, plugin.OutputHookMixin) ] for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): try: add_argument_from_template(ext, key, tmpl, parser) except TypeError: continue # Add custom subcommands from plugins if config["plugins"].get(): classes = (cls for cls in plugins.values() if issubclass(cls, plugin.SubcommandHookMixin)) for cls in classes: cls.add_command_parser(subparsers) return rootparser
def __init__(self, path, config=None, metadata=None): self._logger = logging.getLogger('Workflow') self._logger.debug("Initializing workflow {0}".format(path)) self.status = { 'step': None, 'step_progress': None, 'prepared': False } if not isinstance(path, Path): path = Path(path) self.path = path is_new = not self.path.exists() # See if supplied `config` is already a valid ConfigView object if isinstance(config, confit.ConfigView): self.config = config elif isinstance(config, Configuration): self.config = config.as_view() else: self.config = self._load_config(config) try: self.bag = bagit.Bag(unicode(self.path)) except bagit.BagError: if self.config['core']['convert_old'].get(bool): # Convert non-bagit directories from older versions self.bag = bagit.Bag.convert_directory(unicode(self.path)) self.pages = [Page(img) for img in (self.path/'data'/'raw').iterdir()] self._save_pages() else: raise bagit.BagError( "Specified workflow directory is not structured according " "to BagIt convertions and automatic conversion has been " "disabled (check `convert_old` setting)") if not self.slug: self.slug = util.slugify(unicode(self.path.name)) if not self.id: self.id = unicode(uuid.uuid4()) #: :py:class:`spreads.metadata.Metadata` instance that backs the #: corresponding getter and setter self._metadata = Metadata(self.path) # This will invoke the setter if metadata: self.metadata = metadata #: Lock that is held when a shot is being executed during the capture #: phase self._capture_lock = threading.RLock() #: List of :py:class:`spreads.plugin.DeviceDriver` instances that #: backs the corresponding getters and setters self._devices = None # Thread pool for background tasks self._threadpool = concfut.ThreadPoolExecutor(max_workers=1) # List of unfinished :py:class:`concurrent.futures.Future` instances self._pending_tasks = [] # Filter out subcommand plugins, since these are not workflow-specific plugin_classes = [ (name, cls) for name, cls in plugin.get_plugins(*self.config["plugins"] .get()).iteritems() if not cls.__bases__ == (plugin.SubcommandHooksMixin,)] self._plugins = [cls(self.config) for name, cls in plugin_classes] self.config['plugins'] = [name for name, cls in plugin_classes] self._save_config() self.pages = self._load_pages() self.table_of_contents = self._load_toc() if is_new: on_created.send(self, workflow=self)
def initializePage(self): # noqa wizard = self.wizard() self.setTitle("Welcome!") intro_label = QtGui.QLabel( "This wizard will guide you through the digitization workflow. " ) intro_label.setWordWrap(True) dirpick_layout = QtGui.QHBoxLayout() self.line_edit = QtGui.QLineEdit() self.line_edit.textChanged.connect(self.completeChanged) browse_btn = QtGui.QPushButton("Browse") dirpick_layout.addWidget(self.line_edit) dirpick_layout.addWidget(browse_btn) browse_btn.clicked.connect(self.show_filepicker) self.tab_widget = QtGui.QTabWidget() # Add configuration widgets from plugins self.plugin_widgets = {} # Filter out subcommand plugins available = [ name for name, cls in plugin.get_plugins(*wizard.config['plugins'].get()).items() if not issubclass(cls, plugin.SubcommandHooksMixin) ] available.append('device') for name, tmpl in wizard.config.templates.iteritems(): if not tmpl or name not in available: continue page = QtGui.QGroupBox() layout = QtGui.QFormLayout() widgets = self._get_plugin_config_widgets(tmpl, name) self.plugin_widgets[name] = widgets for label, widget in widgets.values(): # We don't need a label for QCheckBoxes if isinstance(widget, QtGui.QCheckBox): layout.addRow(widget) else: layout.addRow(label, widget) page.setLayout(layout) self.tab_widget.addTab(page, name.title()) self.save_btn = QtGui.QPushButton("&Save as defaults") self.save_btn.clicked.connect(self.saveSettings) save_layout = QtGui.QHBoxLayout() save_layout.addStretch(1) save_layout.addWidget(self.save_btn) main_layout = QtGui.QVBoxLayout() main_layout.addWidget(intro_label) main_layout.addSpacing(30) main_layout.addWidget( QtGui.QLabel("Please select a project directory.") ) main_layout.addLayout(dirpick_layout) main_layout.addSpacing(30) main_layout.addWidget(self.tab_widget) main_layout.addLayout(save_layout) main_layout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) self.setLayout(main_layout) self.adjustSize()
def setup_parser(config): """ Sets up an :py:class:`argparse.ArgumentParser` instance with all options and subcommands that are available in the core and activated plugins. :param config: Current application configuration :type config: :py:class:`spreads.config.Configuration` :returns: Fully initialized argument parser :rtype: :py:class:`argparse.ArgumentParser` """ plugins = plugin.get_plugins(*config["plugins"].get()) def _add_arguments(parsers, mixins, extra_names=None): if extra_names is None: extra_names = [] for parser in parsers: # Only plugins that implement the capture or trigger hook mixins # and the currently active device configuration are relevant for # this subcommand. ext_names = [ name for name, cls in plugins.iteritems() if any( issubclass(cls, mixin) for mixin in mixins) ] ext_names.extend(extra_names) for ext in ext_names: for key, tmpl in config.templates.get(ext, {}).iteritems(): if not should_show_argument(option, config['plugins'].get()): continue try: add_argument_from_template(ext, key, tmpl, parser, config[ext][key].get()) except TypeError: continue rootparser = argparse.ArgumentParser( description="Scanning Tool for DIY Book Scanner", formatter_class=argparse.RawDescriptionHelpFormatter) rootparser.add_argument( '-V', '--version', action='version', version=( "spreads {0}\n\n" "Licensed under the terms of the GNU Affero General Public " "License 3.0.\n" "(C) 2013-2014 Johannes Baiter <*****@*****.**>\n" "For a complete list of contributors see:\n" "https://github.com/DIYBookScanner/spreads/graphs/contributors\n\n" .format(util.get_version()))) for key, option in config.templates['core'].iteritems(): if not should_show_argument(option, config['plugins'].get()): continue try: add_argument_from_template('core', key, option, rootparser, config['core'][key].get()) except TypeError: continue subparsers = rootparser.add_subparsers() wizard_parser = subparsers.add_parser('wizard', help="Interactive mode") wizard_parser.add_argument("path", type=unicode, help="Project path") wizard_parser.set_defaults(subcommand=cli.wizard) config_parser = subparsers.add_parser('configure', help="Perform initial configuration") config_parser.set_defaults(subcommand=cli.configure) try: import spreads.tkconfigure as tkconfigure guiconfig_parser = subparsers.add_parser( 'guiconfigure', help="Perform initial configuration with a GUI") guiconfig_parser.set_defaults(subcommand=tkconfigure.configure) except ImportError: pass capture_parser = subparsers.add_parser('capture', help="Start the capturing workflow") capture_parser.add_argument("path", type=unicode, help="Project path") capture_parser.set_defaults(subcommand=cli.capture) # Add arguments from plugins _add_arguments(parsers=(capture_parser, wizard_parser), mixins=(plugin.CaptureHooksMixin, plugin.TriggerHooksMixin), extra_names=('device', )) postprocess_parser = subparsers.add_parser( 'postprocess', help="Postprocess scanned images.") postprocess_parser.add_argument("path", type=unicode, help="Project path") postprocess_parser.add_argument("--jobs", "-j", dest="jobs", type=int, default=None, metavar="<int>", help="Number of concurrent processes") postprocess_parser.set_defaults(subcommand=cli.postprocess) _add_arguments(parsers=(postprocess_parser, wizard_parser), mixins=(plugin.ProcessHooksMixin, )) output_parser = subparsers.add_parser('output', help="Generate output files.") output_parser.add_argument("path", type=unicode, help="Project path") output_parser.set_defaults(subcommand=cli.output) _add_arguments(parsers=(output_parser, wizard_parser), mixins=(plugin.OutputHooksMixin, )) # Add custom subcommands from plugins if config["plugins"].get(): classes = (cls for cls in plugins.values() if issubclass(cls, plugin.SubcommandHooksMixin)) for cls in classes: cls.add_command_parser(subparsers, config) return rootparser
def initializePage(self): wizard = self.wizard() self.setTitle("Welcome!") intro_label = QtGui.QLabel( "This wizard will guide you through the digitization workflow. " ) intro_label.setWordWrap(True) dirpick_layout = QtGui.QHBoxLayout() self.line_edit = QtGui.QLineEdit() self.line_edit.textChanged.connect(self.completeChanged) browse_btn = QtGui.QPushButton("Browse") dirpick_layout.addWidget(self.line_edit) dirpick_layout.addWidget(browse_btn) browse_btn.clicked.connect(self.show_filepicker) self.tab_widget = QtGui.QTabWidget() # Add configuration widgets from plugins self.plugin_widgets = {} # Filter out subcommand plugins available = [ name for name, cls in plugin.get_plugins(*wizard.config['plugins'].get()).items() if not issubclass(cls, plugin.SubcommandHookMixin) ] available.append('device') for name, tmpl in wizard.config.templates.iteritems(): if not tmpl or name not in available: continue page = QtGui.QGroupBox() layout = QtGui.QFormLayout() widgets = self._get_plugin_config_widgets(tmpl, name) self.plugin_widgets[name] = widgets for label, widget in widgets.values(): # We don't need a label for QCheckBoxes if isinstance(widget, QtGui.QCheckBox): layout.addRow(widget) else: layout.addRow(label, widget) page.setLayout(layout) self.tab_widget.addTab(page, name.title()) self.save_btn = QtGui.QPushButton("&Save as defaults") self.save_btn.clicked.connect(self.saveSettings) save_layout = QtGui.QHBoxLayout() save_layout.addStretch(1) save_layout.addWidget(self.save_btn) main_layout = QtGui.QVBoxLayout() main_layout.addWidget(intro_label) main_layout.addSpacing(30) main_layout.addWidget( QtGui.QLabel("Please select a project directory.") ) main_layout.addLayout(dirpick_layout) main_layout.addSpacing(30) main_layout.addWidget(self.tab_widget) main_layout.addLayout(save_layout) main_layout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) self.setLayout(main_layout) self.adjustSize()