from abc import abstractmethod import os from gitlint.utils import ustr, sstr class RuleOptionError(Exception): pass class RuleOption(object): """ Base class representing a configurable part (i.e. option) of a rule (e.g. the max-length of the title-max-line rule). This class should not be used directly. Instead, use on the derived classes like StrOption, IntOption to set options of a particular type like int, str, etc. """ def __init__(self, name, value, description): self.name = ustr(name) self.description = ustr(description) self.value = None self.set(value) @abstractmethod def set(self, value): """ Validates and sets the option's value """ pass # pragma: no cover def __str__(self): return sstr(self) # pragma: no cover def __unicode__(self): return u"({0}: {1} ({2}))".format(self.name, self.value, self.description) # pragma: no cover def __repr__(self): return self.__str__() # pragma: no cover def __eq__(self, other): return self.name == other.name and self.description == other.description and self.value == other.value def __ne__(self, other): return not self.__eq__(other) # required for py2 class StrOption(RuleOption): def set(self, value): self.value = ustr(value) class IntOption(RuleOption): def __init__(self, name, value, description, allow_negative=False): self.allow_negative = allow_negative super(IntOption, self).__init__(name, value, description) def _raise_exception(self, value): if self.allow_negative: error_msg = u"Option '{0}' must be an integer (current value: '{1}')".format(self.name, value) else: error_msg = u"Option '{0}' must be a positive integer (current value: '{1}')".format(self.name, value) raise RuleOptionError(error_msg) def set(self, value): try: self.value = int(value) except ValueError: self._raise_exception(value) if not self.allow_negative and self.value < 0: self._raise_exception(value) class BoolOption(RuleOption): def set(self, value): value = ustr(value).strip().lower() if value not in ['true', 'false']: raise RuleOptionError(u"Option '{0}' must be either 'true' or 'false'".format(self.name)) self.value = value == 'true' class ListOption(RuleOption): """ Option that is either a given list or a comma-separated string that can be splitted into a list when being set. """ def set(self, value): if isinstance(value, list): the_list = value else: the_list = ustr(value).split(",") self.value = [ustr(item.strip()) for item in the_list if item.strip() != ""] class PathOption(RuleOption): """ Option that accepts either a directory or both a directory and a file. """ def __init__(self, name, value, description, type=u"dir"): self.type = type super(PathOption, self).__init__(name, value, description) def set(self, value): value = ustr(value) error_msg = u"" if self.type == 'dir': if not os.path.isdir(value): error_msg = u"Option {0} must be an existing directory (current value: '{1}')".format(self.name, value) elif self.type == 'file': if not os.path.isfile(value): error_msg = u"Option {0} must be an existing file (current value: '{1}')".format(self.name, value) elif self.type == 'both': if not os.path.isdir(value) and not os.path.isfile(value): error_msg = (u"Option {0} must be either an existing directory or file " u"(current value: '{1}')").format(self.name, value) else: error_msg = u"Option {0} type must be one of: 'file', 'dir', 'both' (current: '{1}')".format(self.name, self.type) if error_msg: raise RuleOptionError(error_msg) self.value = os.path.realpath(value)