예제 #1
0
    def parse(self, argv):
        # Prepend with 'default' if necessary
        self.renv.lifecycle.mark(Lifecycle.CMD_PARSE, Lifecycle.ORDER_BEFORE,
                                 argv)
        feature = argv[0]
        if feature not in self.features:
            raise StopException(StopException.EFTR,
                                "Feature '{}' is not enabled".format(feature))

        argv = argv[1:]
        if argv:
            actions = self.features[feature].actions
            action = argv[0]
            if not actions or action in actions.get_names():
                argv = [feature] + argv
            else:
                argv = [feature, 'default'] + argv
        else:
            argv = [feature, 'default']

        self.renv.lifecycle.mark(Lifecycle.CMD_PARSE, Lifecycle.ORDER_AFTER)

        try:
            opts = vars(self.parser.parse_args(argv))
        except SystemExit:
            raise StopException(StopException.EPAR, "Parsing failed")

        # Normalize project_directory
        opts['project_directory'] = os.path.abspath(opts['project_directory'])
        self.renv.update_cli_properties(opts)
        return opts
예제 #2
0
파일: test.py 프로젝트: m4yers/crutch
    def action_remove(self):
        renv = self.renv

        test = Test(renv.get_prop(OPT_TEST))

        if test not in self.get_tests():
            raise StopException(StopException.EFS,
                                "'{}' does not exist".format(test.name))

        if not prompter.yesno("Do you really want remove this test?"):
            raise StopException("Nothing was removed")

        # Remove test folder
        shutil.rmtree(
            os.path.join(self.get_test_src_dir(), os.path.sep.join(test.path)))

        # Remove empty folders if any
        path = test.path[:-1]
        while path:
            fullpath = os.path.join(self.get_test_src_dir(),
                                    os.path.sep.join(path))
            if len(os.listdir(fullpath)) == 1:
                shutil.rmtree(fullpath)
            path.pop()

        print("Test '{}' was removed".format(test.name))
예제 #3
0
파일: ctrl.py 프로젝트: m4yers/crutch
    def assert_dependency(self, name, dependency):
        if name == dependency:
            raise StopException(
                StopException.EPERM,
                "You cannot add self-dependency for '{}'".format(name))

        if self.is_category(dependency):
            self.assert_category_has_defaults(dependency)
        elif not self.is_feature(dependency):
            raise StopException(
                StopException.EFTR,
                "Unknown dependency name '{}'".format(dependency))
예제 #4
0
파일: ctrl.py 프로젝트: m4yers/crutch
 def assert_category_has_defaults(self, name):
     cat_desc = self.categories[name]
     if not cat_desc.defaults:
         raise StopException(
             StopException.EFTR,
             str("You cannot add dependency on category '{}' because it " +
                 "does not have default features defined").format(name))
예제 #5
0
파일: ctrl.py 프로젝트: m4yers/crutch
    def get_activation_order(self, request):
        """
    Derive feature activation order from the dependency graph.

    :param request: `iterable` of feature and categories names
    :returns: total activation order.
    """

        if request == 'default':
            request = sum([d.defaults for d in self.categories.values()], [])

        errors = list(self.get_name_errors(request))
        if errors:
            raise StopException(
                StopException.EFTR,
                "Names {} are not features nor categories".format(errors))

        flatten = self.flatten_with_defaults(request)
        closure = self.get_features_dependency_closure(flatten)

        total_order = self.get_topological_order(closure)
        total_order = self.clean_up_activation_order(total_order)
        flatten_order = [f for f in total_order if f in flatten]

        return total_order, flatten_order
예제 #6
0
파일: ctrl.py 프로젝트: m4yers/crutch
    def register_feature_category_class(
        self, cat_name, cat_class=FeatureCategory, features=None, defaults=None, \
        requires=None, mono=True):
        """
    Register feature category. Every name mentioned in its configuration must
    be previously registered to prevent potential circular dependencies.

    :param cat_name: unique category name
    :param cat_class: category's constructor
    :param features: `list` of feature names this category contains. Feature
      must be previous registered. These feature names must be previously
      registered.
    :param defaults: `list` of default features this category provides if its
      name mentioned as a dependency if none of its features is already active.
    :param requires: `list` of features and/or categories this category depends
      upon. These names must be previously registered.
    :param mono: If `True` this category can have only one active feature at a
      time, otherwise it can have many.
    """
        if not features:
            raise StopException(
                StopException.EFTR,
                "You need to define features list for '{}'".format(cat_name))

        self.categories[cat_name] = CategoryDesc(cat_name, cat_class, features,
                                                 defaults, requires, mono)

        self.dep_graph.add_node(cat_name)

        for ftr_name in features:
            self.assert_dependency(cat_name, ftr_name)

            if self.feature_to_category[ftr_name] != self.sink_category_name:
                raise StopException(
                    StopException.EPERM,
                    "A feature '{}' cannot be attached to multiple categoires".
                    format(ftr_name))

            if requires:
                for dep_name in requires:
                    self.assert_dependency(cat_name, dep_name)
                    self.assert_dependency(ftr_name, dep_name)
                    if not self.dep_graph.has_edge(dep_name, ftr_name):
                        self.dep_graph.add_edge(dep_name, ftr_name)

            self.feature_to_category[ftr_name] = cat_name
            self.sink_category.features.remove(ftr_name)
예제 #7
0
 def config_flush(self):
     self.lifecycle.mark(Lifecycle.CONFIG_FLUSH, Lifecycle.ORDER_BEFORE)
     crutch_config = self.get_crutch_config()
     if not crutch_config:
         raise StopException(StopException.ECFG, 'Crutch config is not set')
     self.props.update_config(crutch_config)
     self.props.config_flush()
     self.lifecycle.mark(Lifecycle.CONFIG_FLUSH, Lifecycle.ORDER_AFTER)
예제 #8
0
    def check_version(self):
        config_version = self.renv.props.config.get('crutch_version')
        config_version_parts = config_version.split('.')
        this_version = self.renv.props.defaults.get('crutch_version')
        this_version_parts = this_version.split('.')

        # Major version mismatch is a no go
        if config_version_parts[0] != this_version_parts[0]:
            raise StopException(
                StopException.EVER,
                'Major versions are not compatible: project({}) vs crutch({})'\
                .format(config_version, this_version))

        # Minor version of the project cannot be bigger than CRUTCH's
        if config_version_parts[1] > this_version_parts[1]:
            raise StopException(
                StopException.EVER,
                'Minor versions are not compatible: project({}) vs crutch({})'\
                .format(config_version, this_version))
예제 #9
0
파일: file.py 프로젝트: m4yers/crutch
  def action_remove(self):
    renv = self.renv

    project_name = renv.get_project_name()
    project_directory = renv.get_project_directory()

    group = FileGroup(renv.get_prop(OPT_GROUP), project_directory, project_name)

    if not group.exists():
      raise StopException(
          StopException.EFS,
          "File group '{}' does not exist".format(group.name))

    if not prompter.yesno("Are you sure?"):
      raise StopException("Nothing was removed")

    print("Removed: ")
    for deleted in group.delete():
      print("{}".format(deleted))
예제 #10
0
파일: feature.py 프로젝트: m4yers/crutch
  def action_remove(self):
    project_features = self.renv.get_project_features()
    names = self.renv.get_prop(OPT_FEATURES)

    _, flatten_order = self.renv.feature_ctrl.get_deactivation_order(names)
    if not flatten_order:
      raise StopException("There is nothing to remove")

    if not prompter.yesno("Do you really want to remove {}".format(names)):
      raise StopException("Nothing was removed")

    _, flatten_order = self.renv.feature_ctrl.deactivate_features(
        names,
        tear_down=True,
        skip=set(project_features) - set(flatten_order))

    self.renv.set_prop('project_features', flatten_order, mirror_to_config=True)

    print("Removed {}".format(flatten_order))
예제 #11
0
파일: test.py 프로젝트: m4yers/crutch
    def action_add(self):
        renv = self.renv

        test = Test(renv.get_prop(OPT_TEST))

        for tst in self.get_tests():
            if test.name == tst.name:
                raise StopException(StopException.EFS,
                                    "'{}' already exists".format(test.name))
            if test.name in tst.name:
                raise StopException(
                    StopException.EFS,
                    "'{}' is a group of tests".format(test.name))

        renv.set_prop(OPT_TEST, test.target, mirror_to_repl=True)

        jdir = os.path.join(renv.get_project_type(), 'other', self.name)
        jdir_test_group = os.path.join(jdir, 'group')
        jdir_test = os.path.join(jdir, 'test')

        psub = {'ProjectNameRepl': renv.get_project_name()}

        # Init all folders along test path
        fullpath = self.get_test_src_dir()
        path = list(reversed(test.path))
        final = path[0]
        path = path[1:]
        while path:
            fullpath = os.path.join(fullpath, path.pop())
            # If this folder already exists we must change nothing
            if os.path.exists(fullpath):
                continue
            self.jinja_ftr.copy_folder(jdir_test_group, fullpath, psub)

        fullpath = os.path.join(fullpath, final)
        self.jinja_ftr.copy_folder(jdir_test, fullpath, psub)
예제 #12
0
파일: ctrl.py 프로젝트: m4yers/crutch
    def get_deactivation_order(self, request, skip=None):
        """
    Derive feature deactivation order from the dependency graph.

    :param request: `iterable` of feature and categories names
    :returns: total deactivation order.
    """
        if request == 'all':
            request = self.categories.keys()

        errors = list(self.get_name_errors(request))
        if errors:
            raise StopException(
                StopException.EFTR,
                "Names {} are not features nor categories".format(errors))

        flatten = self.flatten_with_active(request)

        closure = set(flatten)
        while True:
            new = closure | set(self.get_features_dependency_closure(closure))
            if closure == new:
                break
            closure = new

        if skip:
            for name in skip:
                if name in closure:
                    closure.remove(name)

        # Remove implicit dependencies if some other feature that we won't remove
        # depends on it
        for implicit in [f for f in closure if f not in flatten]:
            for ftr_dep in self.dep_graph.successors(implicit):
                if ftr_dep not in closure:
                    cat_name = self.feature_to_category[ftr_dep]
                    cat_inst = self.active_categories.get(cat_name, None)
                    if cat_inst and cat_inst.is_active_feature(ftr_dep):
                        closure.remove(implicit)
                        break

        total_order = self.get_reversed_topological_order(closure)
        total_order = self.clean_up_deactivation_order(total_order)
        flatten_order = [f for f in total_order if f in flatten]

        return total_order, flatten_order
예제 #13
0
    def handle_new(self):
        self.renv.set_prop('project_features', ['new'])

        runner = self.renv.create_runner('new')
        runner.activate_features()

        self.renv.del_prop('project_features')

        self.renv.menu.parse(self.renv.get_prop('crutch_argv'))
        self.set_default_props()

        if os.path.exists(self.renv.get_crutch_config()):
            raise StopException(
                StopException.EPERM,
                'You cannot invoke `new` on already existing CRUTCH directory {}'
                .format(self.renv.get_project_directory()))

        return runner
예제 #14
0
파일: file.py 프로젝트: m4yers/crutch
  def action_add(self):
    renv = self.renv

    project_name = renv.get_project_name()
    project_directory = renv.get_project_directory()

    group = FileGroup(renv.get_prop(OPT_GROUP), project_directory, project_name)

    if group.exists():
      raise StopException(
          StopException.EFS,
          "File group '{}' already exists".format(group.name))

    jdir = os.path.join(renv.get_project_type(), 'other', NAME)
    psub = {'FileGroupRepl': os.path.join(project_name, *group.path)}

    renv.set_prop(OPT_GROUP, group.name, mirror_to_repl=True)

    self.jinja_ftr.copy_folder(jdir, project_directory, psub)
예제 #15
0
 def check_crutch_config(self):
     if not os.path.exists(self.renv.get_prop('crutch_config')):
         raise StopException(StopException.EPERM,
                             'CRUTCH config does not exist')
예제 #16
0
    def handle_prompt(self):
        self.set_default_props(os.path.abspath('.'))

        crutch_config = self.renv.get_prop('crutch_config')

        runner = None

        # Since prompt syntax highlight depends on active features we need to read
        # the config first and activate all the features
        if os.path.exists(crutch_config):
            self.renv.config_load()
            self.check_version()
            self.set_default_props()
            runner = self.renv.create_runner(self.renv.get_project_type())
            runner.activate_features()

        # Otherwise we allow only new to run, and it will update runner upon success
        else:
            self.renv.create_runner('new').activate_features()

        print("CRUTCH {}".format(self.get_version()))
        print("Home: https://github.com/m4yers/crutch")

        self.renv.prompt.initialize()
        reinitialize_prompt = False

        while True:

            try:
                argv = self.renv.prompt.activate(reinitialize_prompt)
                reinitialize_prompt = False

                if not argv:
                    continue

                elif argv[0] == 'new':
                    if os.path.exists(crutch_config):
                        raise StopException(
                            StopException.EPERM,
                            'You cannot invoke `new` on already existing CRUTCH directory'
                        )

                    self.renv.menu.parse(argv)
                    self.set_default_props()
                    runner = self.renv.feature_ctrl.get_active_feature(
                        'new').create()
                    reinitialize_prompt = True

                else:
                    self.renv.menu.parse(argv)
                    runner.run()

                self.renv.config_flush()
            except StopException as stop:
                if stop.terminate:
                    raise
                if stop.message:
                    print(stop.message)
                continue
            except KeyboardInterrupt:
                break
            except EOFError:
                break

        raise StopException()
예제 #17
0
파일: ctrl.py 프로젝트: m4yers/crutch
    def activate_features(self, request, set_up=False):
        self.renv.lifecycle.mark_before(Lifecycle.FEATURE_CREATION, request)

        total_order, flatten_order = self.get_activation_order(request)

        LOGGER.info("Request: '%s'", request)
        LOGGER.info("Total order: '%s'", total_order)
        LOGGER.info("Flatten order: '%s'", flatten_order)

        # Check for conflicts within flatten(user requested) order
        conflicts = list()
        for category, features in self.get_mono_conflicts(flatten_order):
            conflicts.append(
                str("Mono category '{}' cannot have all of '{}' features " +
                    "activated at the same time").format(category, features))
        if conflicts:
            raise StopException(
                StopException.EFTR,
                'There were some conflicting dependencies:\n' +
                '\n'.join(conflicts))

        # Check for total order conflicts relative to already active categories
        conflicts = list()
        for category, feature in self.get_activation_conflicts(total_order):
            # In case the category was requested explicitly we can ignore this
            # conflict, since in this case user requested any feature(default or
            # already enabled) of that category
            if category in request:
                conflicts.append(
                    "Category '{}' is already active".format(category))

            # Inability to instantiate an explicit request is an error
            if feature in request:
                conflicts.append(
                    str("Cannot activate '{}' feature because its category '{}' is "
                        + "mono and already contains active features").format(
                            feature, category))

        if conflicts:
            raise StopException(
                StopException.EPERM,
                'There were some conflicting dependencies:\n' +
                '\n'.join(conflicts))

        self.features_in_activation_process = list(total_order)

        # Finally instantiate, activate and set up if needed all the features
        for ftr_name in total_order:
            cat_name = self.feature_to_category[ftr_name]
            cat_desc = self.categories[cat_name]
            cat_inst = self.active_categories.get(cat_name, None)

            if not cat_inst:
                cat_inst = cat_desc.init(
                    self.renv,
                    {n: self.features[n].init
                     for n in cat_desc.features})

                if set_up:
                    self.renv.lifecycle.mark_before(Lifecycle.CATEGORY_SET_UP,
                                                    cat_name)
                    cat_inst.set_up()
                    self.renv.lifecycle.mark_after(Lifecycle.CATEGORY_SET_UP,
                                                   cat_name)

                self.renv.lifecycle.mark_before(Lifecycle.CATEGORY_ACTIVATE,
                                                cat_name)
                cat_inst.activate()
                self.renv.lifecycle.mark_after(Lifecycle.CATEGORY_ACTIVATE,
                                               cat_name)

                self.active_categories[cat_name] = cat_inst

            if not cat_inst.is_active_feature(ftr_name):
                cat_inst.activate_feature(ftr_name, set_up=set_up)

        self.features_in_activation_process = None

        self.renv.lifecycle.mark_after(Lifecycle.FEATURE_CREATION, total_order)

        return total_order, flatten_order
예제 #18
0
파일: ctrl.py 프로젝트: m4yers/crutch
    def deactivate_features(self, request, tear_down=False, skip=None):
        """
    Deactivate features and/or categories.

    :param request: `list` of categories and/or features to deactivate; or a
      `str` equal to `all` which means deactivate all active features and
      categories.
    :param tear_down: If `True` every deactivated feature/category will be torn
      down, meaning it will be completely removed from the project.
    :param skip: `list` of feature names to skip during deactivation. Since
      this control has no knowledge of actual project features(it only manages
      dependencies, explicit and implicit), to remove a feature with its
      dependencies you want to skip those features that are explicitly enabled
      by user; otherwise the whole dependency tree will be removed unless some
      other feature depends on some of its sub-trees.
    """
        self.renv.lifecycle.mark_before(Lifecycle.FEATURE_DESTRUCTION, request)

        total_order, flatten_order = self.get_deactivation_order(request, skip)

        LOGGER.info("Request: '%s'", request)
        LOGGER.info("Total order: '%s'", total_order)
        LOGGER.info("Flatten order: '%s'", flatten_order)
        LOGGER.info("Skip: '%s'", skip)

        # Before we remove anything we verify if we can do that without breaking
        # any dependencies
        conflicts = list()
        for ftr_name, ftr_dep in self.get_deactivation_conflicts(total_order):
            conflicts.append(
                str("Cannot deactivate feature '{}' because it is a direct " +
                    "dependency of '{}'").format(ftr_name, ftr_dep))
        if conflicts:
            raise StopException(
                StopException.EPERM,
                'There were some conflicting dependencies:\n' +
                '\n'.join(conflicts))

        for ftr_name in total_order:
            cat_name = self.feature_to_category[ftr_name]
            cat_inst = self.active_categories.get(cat_name, None)

            if cat_inst and cat_inst.is_active_feature(ftr_name):
                cat_inst.deactivate_feature(ftr_name, tear_down)

                if cat_inst.get_active_features():
                    continue

                self.renv.lifecycle.mark_before(Lifecycle.CATEGORY_DEACTIVATE,
                                                cat_name)
                cat_inst.deactivate()
                self.renv.lifecycle.mark_after(Lifecycle.CATEGORY_DEACTIVATE,
                                               cat_name)

                if tear_down:
                    self.renv.lifecycle.mark_before(
                        Lifecycle.CATEGORY_TEAR_DOWN, cat_name)
                    cat_inst.tear_down()
                    self.renv.lifecycle.mark_after(
                        Lifecycle.CATEGORY_TEAR_DOWN, cat_name)

                del self.active_categories[cat_name]

        self.renv.lifecycle.mark_after(Lifecycle.FEATURE_DESTRUCTION,
                                       total_order)

        return total_order, flatten_order