summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoris Roovers <joris.roovers@gmail.com>2020-10-31 12:04:37 +0100
committerJoris Roovers <joris.roovers@gmail.com>2020-11-01 10:58:58 +0100
commitec1453728770dc8b8877f7aef888c7ccca5a3d05 (patch)
tree6c412a444cb59dc2696ad1ab3a1ce5f3919fec03
parent484117b6261a2a519a572a0a7376bbab4fe15aa7 (diff)
Replace format() with f-strings
Replaces most occurences for format() with f-strings (supported since Python 3.6) - this makes for more readable code in most cases. Relates to #151
-rw-r--r--docs/user_defined_rules.md5
-rw-r--r--examples/my_commit_rules.py4
-rw-r--r--examples/my_line_rules.py2
-rw-r--r--gitlint/cli.py16
-rw-r--r--gitlint/config.py56
-rw-r--r--gitlint/git.py8
-rw-r--r--gitlint/hooks.py10
-rw-r--r--gitlint/lint.py13
-rw-r--r--gitlint/options.py21
-rw-r--r--gitlint/rule_finder.py48
-rw-r--r--gitlint/rules.py26
-rw-r--r--gitlint/tests/base.py8
-rw-r--r--gitlint/tests/cli/test_cli.py20
-rw-r--r--gitlint/tests/cli/test_cli_hooks.py7
-rw-r--r--gitlint/tests/config/test_config.py6
-rw-r--r--gitlint/tests/config/test_config_builder.py8
-rw-r--r--gitlint/tests/contrib/test_contrib_rules.py3
-rw-r--r--gitlint/tests/git/test_git.py2
-rw-r--r--gitlint/tests/git/test_git_commit.py6
-rw-r--r--gitlint/tests/rules/test_title_rules.py2
-rw-r--r--gitlint/tests/rules/test_user_rules.py5
-rw-r--r--gitlint/tests/test_hooks.py10
-rw-r--r--gitlint/tests/test_lint.py4
-rw-r--r--gitlint/tests/test_options.py9
-rw-r--r--qa/base.py3
-rw-r--r--qa/samples/user_rules/extra/extra_rules.py16
-rw-r--r--qa/test_commits.py2
-rw-r--r--qa/test_gitlint.py7
-rw-r--r--qa/test_hooks.py6
29 files changed, 160 insertions, 173 deletions
diff --git a/docs/user_defined_rules.md b/docs/user_defined_rules.md
index 88d1dd8..fd944d1 100644
--- a/docs/user_defined_rules.md
+++ b/docs/user_defined_rules.md
@@ -152,7 +152,7 @@ class SpecialChars(LineRule):
# options can be accessed by looking them up by their name in self.options
for char in self.options['special-chars'].value:
if char in line:
- msg = "Title contains the special character '{0}'".format(char)
+ msg = f"Title contains the special character '{char}'"
violation = RuleViolation(self.id, msg, line)
violations.append(violation)
@@ -262,8 +262,7 @@ class BodyMaxLineCount(CommitRule):
line_count = len(commit.message.body)
max_line_count = self.options['max-line-count'].value
if line_count > max_line_count:
- message = "Body contains too many lines ({0} > {1})".format(line_count,
- max_line_count)
+ message = f"Body contains too many lines ({line_count} > {max_line_count})"
return [RuleViolation(self.id, message, line_nr=1)]
```
diff --git a/examples/my_commit_rules.py b/examples/my_commit_rules.py
index 5e8ce08..5a66c94 100644
--- a/examples/my_commit_rules.py
+++ b/examples/my_commit_rules.py
@@ -35,7 +35,7 @@ class BodyMaxLineCount(CommitRule):
line_count = len(commit.message.body)
max_line_count = self.options['max-line-count'].value
if line_count > max_line_count:
- message = "Body contains too many lines ({0} > {1})".format(line_count, max_line_count)
+ message = f"Body contains too many lines ({line_count} > {max_line_count})"
return [RuleViolation(self.id, message, line_nr=1)]
@@ -88,7 +88,7 @@ class BranchNamingConventions(CommitRule):
break
if not valid_branch_name:
- msg = "Branch name '{0}' does not start with one of {1}".format(branch, allowed_branch_prefixes)
+ msg = f"Branch name '{branch}' does not start with one of {allowed_branch_prefixes}"
violations.append(RuleViolation(self.id, msg, line_nr=1))
return violations
diff --git a/examples/my_line_rules.py b/examples/my_line_rules.py
index 820024a..3a1ef36 100644
--- a/examples/my_line_rules.py
+++ b/examples/my_line_rules.py
@@ -45,7 +45,7 @@ class SpecialChars(LineRule):
# options can be accessed by looking them up by their name in self.options
for char in self.options['special-chars'].value:
if char in line:
- msg = "Title contains the special character '{0}'".format(char)
+ msg = f"Title contains the special character '{char}'"
violation = RuleViolation(self.id, msg, line)
violations.append(violation)
diff --git a/gitlint/cli.py b/gitlint/cli.py
index a42d4c6..76a9734 100644
--- a/gitlint/cli.py
+++ b/gitlint/cli.py
@@ -187,7 +187,7 @@ class ContextObj:
type=click.Path(exists=True, resolve_path=True, file_okay=False, readable=True),
help="Path of the target git repository. [default: current working directory]")
@click.option('-C', '--config', type=click.Path(exists=True, dir_okay=False, readable=True, resolve_path=True),
- help="Config file location [default: {0}]".format(DEFAULT_CONFIG_FILE))
+ help=f"Config file location [default: {DEFAULT_CONFIG_FILE}]")
@click.option('-c', multiple=True,
help="Config flags in format <rule>.<option>=<value> (e.g.: -c T1.line-length=80). " +
"Flag can be used multiple times to set multiple config values.") # pylint: disable=bad-continuation
@@ -242,10 +242,10 @@ def cli( # pylint: disable=too-many-arguments
click.echo(e)
ctx.exit(GIT_CONTEXT_ERROR_CODE)
except GitLintUsageError as e:
- click.echo("Error: {0}".format(e))
+ click.echo(f"Error: {e}")
ctx.exit(USAGE_ERROR_CODE)
except LintConfigError as e:
- click.echo("Config Error: {0}".format(e))
+ click.echo(f"Config Error: {e}")
ctx.exit(CONFIG_ERROR_CODE)
@@ -315,7 +315,7 @@ def install_hook(ctx):
try:
hooks.GitHookInstaller.install_commit_msg_hook(ctx.obj.config)
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
- click.echo("Successfully installed gitlint commit-msg hook in {0}".format(hook_path))
+ click.echo(f"Successfully installed gitlint commit-msg hook in {hook_path}")
ctx.exit(0)
except hooks.GitHookInstallerError as e:
click.echo(e, err=True)
@@ -329,7 +329,7 @@ def uninstall_hook(ctx):
try:
hooks.GitHookInstaller.uninstall_commit_msg_hook(ctx.obj.config)
hook_path = hooks.GitHookInstaller.commit_msg_hook_path(ctx.obj.config)
- click.echo("Successfully uninstalled gitlint commit-msg hook from {0}".format(hook_path))
+ click.echo(f"Successfully uninstalled gitlint commit-msg hook from {hook_path}")
ctx.exit(0)
except hooks.GitHookInstallerError as e:
click.echo(e, err=True)
@@ -411,14 +411,14 @@ def generate_config(ctx):
path = os.path.realpath(path)
dir_name = os.path.dirname(path)
if not os.path.exists(dir_name):
- click.echo("Error: Directory '{0}' does not exist.".format(dir_name), err=True)
+ click.echo(f"Error: Directory '{dir_name}' does not exist.", err=True)
ctx.exit(USAGE_ERROR_CODE)
elif os.path.exists(path):
- click.echo("Error: File \"{0}\" already exists.".format(path), err=True)
+ click.echo(f"Error: File \"{path}\" already exists.", err=True)
ctx.exit(USAGE_ERROR_CODE)
LintConfigGenerator.generate_config(path)
- click.echo("Successfully generated {0}".format(path))
+ click.echo(f"Successfully generated {path}")
ctx.exit(0)
diff --git a/gitlint/config.py b/gitlint/config.py
index 2e50046..91179c9 100644
--- a/gitlint/config.py
+++ b/gitlint/config.py
@@ -220,7 +220,7 @@ class LintConfig:
if rule_class:
self.rules.add_rule(rule_class, rule_class.id, {'is_contrib': True})
else:
- raise LintConfigError("No contrib rule with id or name '{0}' found.".format(rule_id_or_name))
+ raise LintConfigError(f"No contrib rule with id or name '{rule_id_or_name}' found.")
except (options.RuleOptionError, rules.UserRuleError) as e:
raise LintConfigError(str(e))
@@ -228,11 +228,11 @@ class LintConfig:
def _get_option(self, rule_name_or_id, option_name):
rule = self.rules.find_rule(rule_name_or_id)
if not rule:
- raise LintConfigError("No such rule '{0}'".format(rule_name_or_id))
+ raise LintConfigError(f"No such rule '{rule_name_or_id}'")
option = rule.options.get(option_name)
if not option:
- raise LintConfigError("Rule '{0}' has no option '{1}'".format(rule_name_or_id, option_name))
+ raise LintConfigError(f"Rule '{rule_name_or_id}' has no option '{option_name}'")
return option
@@ -249,14 +249,14 @@ class LintConfig:
try:
option.set(option_value)
except options.RuleOptionError as e:
- msg = "'{0}' is not a valid value for option '{1}.{2}'. {3}."
- raise LintConfigError(msg.format(option_value, rule_name_or_id, option_name, e))
+ msg = f"'{option_value}' is not a valid value for option '{rule_name_or_id}.{option_name}'. {e}."
+ raise LintConfigError(msg)
def set_general_option(self, option_name, option_value):
attr_name = option_name.replace("-", "_")
# only allow setting general options that exist and don't start with an underscore
if not hasattr(self, attr_name) or attr_name[0] == "_":
- raise LintConfigError("'{0}' is not a valid gitlint option".format(option_name))
+ raise LintConfigError(f"'{option_name}' is not a valid gitlint option")
# else:
setattr(self, attr_name, option_value)
@@ -280,21 +280,21 @@ class LintConfig:
def __str__(self):
# config-path is not a user exposed variable, so don't print it under the general section
- return_str = "config-path: {0}\n".format(self._config_path)
- return_str += "[GENERAL]\n"
- return_str += "extra-path: {0}\n".format(self.extra_path)
- return_str += "contrib: {0}\n".format(self.contrib)
+ return_str = f"config-path: {self._config_path}\n"
+ return_str += f"[GENERAL]\n"
+ return_str += f"extra-path: {self.extra_path}\n"
+ return_str += f"contrib: {self.contrib}\n"
return_str += "ignore: {0}\n".format(",".join(self.ignore))
- return_str += "ignore-merge-commits: {0}\n".format(self.ignore_merge_commits)
- return_str += "ignore-fixup-commits: {0}\n".format(self.ignore_fixup_commits)
- return_str += "ignore-squash-commits: {0}\n".format(self.ignore_squash_commits)
- return_str += "ignore-revert-commits: {0}\n".format(self.ignore_revert_commits)
- return_str += "ignore-stdin: {0}\n".format(self.ignore_stdin)
- return_str += "staged: {0}\n".format(self.staged)
- return_str += "verbosity: {0}\n".format(self.verbosity)
- return_str += "debug: {0}\n".format(self.debug)
- return_str += "target: {0}\n".format(self.target)
- return_str += "[RULES]\n{0}".format(self.rules)
+ return_str += f"ignore-merge-commits: {self.ignore_merge_commits}\n"
+ return_str += f"ignore-fixup-commits: {self.ignore_fixup_commits}\n"
+ return_str += f"ignore-squash-commits: {self.ignore_squash_commits}\n"
+ return_str += f"ignore-revert-commits: {self.ignore_revert_commits}\n"
+ return_str += f"ignore-stdin: {self.ignore_stdin}\n"
+ return_str += f"staged: {self.staged}\n"
+ return_str += f"verbosity: {self.verbosity}\n"
+ return_str += f"debug: {self.debug}\n"
+ return_str += f"target: {self.target}\n"
+ return_str += f"[RULES]\n{self.rules}"
return return_str
@@ -359,7 +359,7 @@ class RuleCollection:
def __unicode__(self):
return_str = ""
for rule in self._rules.values():
- return_str += " {0}: {1}\n".format(rule.id, rule.name)
+ return_str += f" {rule.id}: {rule.name}\n"
for option_name, option_value in sorted(rule.options.items()):
if option_value.value is None:
option_val_repr = None
@@ -369,7 +369,7 @@ class RuleCollection:
option_val_repr = option_value.value.pattern
else:
option_val_repr = option_value.value
- return_str += " {0}={1}\n".format(option_name, option_val_repr)
+ return_str += f" {option_name}={option_val_repr}\n"
return return_str
@@ -414,12 +414,12 @@ class LintConfigBuilder:
self.set_option(rule_name, option_name, option_value)
except ValueError: # raised if the config string is invalid
raise LintConfigError(
- "'{0}' is an invalid configuration option. Use '<rule>.<option>=<value>'".format(config_option))
+ f"'{config_option}' is an invalid configuration option. Use '<rule>.<option>=<value>'")
def set_from_config_file(self, filename):
""" Loads lint config from a ini-style config file """
if not os.path.exists(filename):
- raise LintConfigError("Invalid file path: {0}".format(filename))
+ raise LintConfigError(f"Invalid file path: {filename}")
self._config_path = os.path.realpath(filename)
try:
parser = ConfigParser()
@@ -449,14 +449,14 @@ class LintConfigBuilder:
# - not empty
# - no whitespace or colons
if rule_name == "" or bool(re.search("\\s|:", rule_name, re.UNICODE)):
- msg = "The rule-name part in '{0}' cannot contain whitespace, colons or be empty"
- raise LintConfigError(msg.format(qualified_rule_name))
+ msg = f"The rule-name part in '{qualified_rule_name}' cannot contain whitespace, colons or be empty"
+ raise LintConfigError(msg)
# find parent rule
parent_rule = config.rules.find_rule(parent_rule_specifier)
if not parent_rule:
- msg = "No such rule '{0}' (named rule: '{1}')"
- raise LintConfigError(msg.format(parent_rule_specifier, qualified_rule_name))
+ msg = f"No such rule '{parent_rule_specifier}' (named rule: '{qualified_rule_name}')"
+ raise LintConfigError(msg)
# Determine canonical id and name by recombining the parent id/name and instance name parts.
canonical_id = parent_rule.__class__.id + self.RULE_QUALIFIER_SYMBOL + rule_name
diff --git a/gitlint/git.py b/gitlint/git.py
index 59f1af0..8f239bf 100644
--- a/gitlint/git.py
+++ b/gitlint/git.py
@@ -32,8 +32,7 @@ class GitExitCodeError(GitContextError):
def __init__(self, command, stderr):
self.command = command
self.stderr = stderr
- super(GitExitCodeError, self).__init__(
- "An error occurred while executing '{0}': {1}".format(command, stderr))
+ super(GitExitCodeError, self).__init__(f"An error occurred while executing '{command}': {stderr}")
def _git(*command_parts, **kwargs):
@@ -55,8 +54,7 @@ def _git(*command_parts, **kwargs):
error_msg = e.stderr.strip()
error_msg_lower = error_msg.lower()
if '_cwd' in git_kwargs and b"not a git repository" in error_msg_lower:
- error_msg = "{0} is not a git repository.".format(git_kwargs['_cwd'])
- raise GitContextError(error_msg)
+ raise GitContextError(f"{git_kwargs['_cwd']} is not a git repository.")
if (b"does not have any commits yet" in error_msg_lower or
b"ambiguous argument 'head': unknown revision" in error_msg_lower):
@@ -105,7 +103,7 @@ class GitCommitMessage:
def from_full_message(context, commit_msg_str):
""" Parses a full git commit message by parsing a given string into the different parts of a commit message """
all_lines = commit_msg_str.splitlines()
- cutline = "{0} ------------------------ >8 ------------------------".format(context.commentchar)
+ cutline = f"{context.commentchar} ------------------------ >8 ------------------------"
try:
cutline_index = all_lines.index(cutline)
except ValueError:
diff --git a/gitlint/hooks.py b/gitlint/hooks.py
index f13acf6..aa3766a 100644
--- a/gitlint/hooks.py
+++ b/gitlint/hooks.py
@@ -27,7 +27,7 @@ class GitHookInstaller:
""" Asserts that a given target directory is a git repository """
hooks_dir = git_hooks_dir(target)
if not os.path.isdir(hooks_dir):
- raise GitHookInstallerError("{0} is not a git repository.".format(target))
+ raise GitHookInstallerError(f"{target} is not a git repository.")
@staticmethod
def install_commit_msg_hook(lint_config):
@@ -35,7 +35,7 @@ class GitHookInstaller:
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
if os.path.exists(dest_path):
raise GitHookInstallerError(
- "There is already a commit-msg hook file present in {0}.\n".format(dest_path) +
+ f"There is already a commit-msg hook file present in {dest_path}.\n" +
"gitlint currently does not support appending to an existing commit-msg file.")
# copy hook file
@@ -49,14 +49,14 @@ class GitHookInstaller:
GitHookInstaller._assert_git_repo(lint_config.target)
dest_path = GitHookInstaller.commit_msg_hook_path(lint_config)
if not os.path.exists(dest_path):
- raise GitHookInstallerError("There is no commit-msg hook present in {0}.".format(dest_path))
+ raise GitHookInstallerError(f"There is no commit-msg hook present in {dest_path}.")
with io.open(dest_path, encoding=DEFAULT_ENCODING) as fp:
lines = fp.readlines()
if len(lines) < 2 or lines[1] != GITLINT_HOOK_IDENTIFIER:
- msg = "The commit-msg hook in {0} was not installed by gitlint (or it was modified).\n" + \
+ msg = f"The commit-msg hook in {dest_path} was not installed by gitlint (or it was modified).\n" + \
"Uninstallation of 3th party or modified gitlint hooks is not supported."
- raise GitHookInstallerError(msg.format(dest_path))
+ raise GitHookInstallerError(msg)
# If we are sure it's a gitlint hook, go ahead and remove it
os.remove(dest_path)
diff --git a/gitlint/lint.py b/gitlint/lint.py
index f9e1e1a..4b6c8a3 100644
--- a/gitlint/lint.py
+++ b/gitlint/lint.py
@@ -78,8 +78,8 @@ class GitLinter:
# Skip linting if this is a special commit type that is configured to be ignored
ignore_commit_types = ["merge", "squash", "fixup", "revert"]
for commit_type in ignore_commit_types:
- if getattr(commit, "is_{0}_commit".format(commit_type)) and \
- getattr(self.config, "ignore_{0}_commits".format(commit_type)):
+ if getattr(commit, f"is_{commit_type}_commit") and \
+ getattr(self.config, f"ignore_{commit_type}_commits"):
return []
violations = []
@@ -98,10 +98,9 @@ class GitLinter:
""" Print a given set of violations to the standard error output """
for v in violations:
line_nr = v.line_nr if v.line_nr else "-"
- self.display.e("{0}: {1}".format(line_nr, v.rule_id), exact=True)
- self.display.ee("{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
+ self.display.e(f"{line_nr}: {v.rule_id}", exact=True)
+ self.display.ee(f"{line_nr}: {v.rule_id} {v.message}", exact=True)
if v.content:
- self.display.eee("{0}: {1} {2}: \"{3}\"".format(line_nr, v.rule_id, v.message, v.content),
- exact=True)
+ self.display.eee(f"{line_nr}: {v.rule_id} {v.message}: \"{v.content}\"", exact=True)
else:
- self.display.eee("{0}: {1} {2}".format(line_nr, v.rule_id, v.message), exact=True)
+ self.display.eee(f"{line_nr}: {v.rule_id} {v.message}", exact=True)
diff --git a/gitlint/options.py b/gitlint/options.py
index a4981bc..f9e1071 100644
--- a/gitlint/options.py
+++ b/gitlint/options.py
@@ -41,7 +41,7 @@ class RuleOption:
return self # pragma: no cover
def __unicode__(self):
- return "({0}: {1} ({2}))".format(self.name, self.value, self.description) # pragma: no cover
+ return f"({self.name}: {self.value} ({self.description}))" # pragma: no cover
def __repr__(self):
return self.__str__() # pragma: no cover
@@ -63,9 +63,9 @@ class IntOption(RuleOption):
def _raise_exception(self, value):
if self.allow_negative:
- error_msg = "Option '{0}' must be an integer (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option '{self.name}' must be an integer (current value: '{value}')"
else:
- error_msg = "Option '{0}' must be a positive integer (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option '{self.name}' must be a positive integer (current value: '{value}')"
raise RuleOptionError(error_msg)
@allow_none
@@ -85,7 +85,7 @@ class BoolOption(RuleOption):
def set(self, value):
value = str(value).strip().lower()
if value not in ['true', 'false']:
- raise RuleOptionError("Option '{0}' must be either 'true' or 'false'".format(self.name))
+ raise RuleOptionError(f"Option '{self.name}' must be either 'true' or 'false'")
self.value = value == 'true'
@@ -118,17 +118,16 @@ class PathOption(RuleOption):
if self.type == 'dir':
if not os.path.isdir(value):
- error_msg = "Option {0} must be an existing directory (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option {self.name} must be an existing directory (current value: '{value}')"
elif self.type == 'file':
if not os.path.isfile(value):
- error_msg = "Option {0} must be an existing file (current value: '{1}')".format(self.name, value)
+ error_msg = f"Option {self.name} must be an existing file (current value: '{value}')"
elif self.type == 'both':
if not os.path.isdir(value) and not os.path.isfile(value):
- error_msg = ("Option {0} must be either an existing directory or file "
- "(current value: '{1}')").format(self.name, value)
+ error_msg = (f"Option {self.name} must be either an existing directory or file "
+ f"(current value: '{value}')")
else:
- error_msg = "Option {0} type must be one of: 'file', 'dir', 'both' (current: '{1}')".format(self.name,
- self.type)
+ error_msg = f"Option {self.name} type must be one of: 'file', 'dir', 'both' (current: '{self.type}')"
if error_msg:
raise RuleOptionError(error_msg)
@@ -143,7 +142,7 @@ class RegexOption(RuleOption):
try:
self.value = re.compile(value, re.UNICODE)
except (re.error, TypeError) as exc:
- raise RuleOptionError("Invalid regular expression: '{0}'".format(exc))
+ raise RuleOptionError(f"Invalid regular expression: '{exc}'")
def __deepcopy__(self, _):
# copy.deepcopy() - used in rules.py - doesn't support copying regex objects prior to Python 3.7
diff --git a/gitlint/rule_finder.py b/gitlint/rule_finder.py
index c1528df..e1c5e77 100644
--- a/gitlint/rule_finder.py
+++ b/gitlint/rule_finder.py
@@ -27,7 +27,7 @@ def find_rule_classes(extra_path):
files = os.listdir(extra_path)
directory = extra_path
else:
- raise rules.UserRuleError("Invalid extra-path: {0}".format(extra_path))
+ raise rules.UserRuleError(f"Invalid extra-path: {extra_path}")
# Filter out files that are not python modules
for filename in files:
@@ -55,7 +55,7 @@ def find_rule_classes(extra_path):
importlib.import_module(module)
except Exception as e:
- raise rules.UserRuleError("Error while importing extra-path module '{0}': {1}".format(module, e))
+ raise rules.UserRuleError(f"Error while importing extra-path module '{module}': {e}")
# Find all rule classes in the module. We do this my inspecting all members of the module and checking
# 1) is it a class, if not, skip
@@ -93,55 +93,53 @@ def assert_valid_rule_class(clazz, rule_type="User-defined"): # pylint: disable
# Rules must extend from LineRule, CommitRule or ConfigurationRule
if not (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)
or issubclass(clazz, rules.ConfigurationRule)):
- msg = "{0} rule class '{1}' must extend from {2}.{3}, {2}.{4} or {2}.{5}"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__, rules.CommitRule.__module__,
- rules.LineRule.__name__, rules.CommitRule.__name__,
- rules.ConfigurationRule.__name__))
+ msg = f"{rule_type} rule class '{clazz.__name__}' " + \
+ f"must extend from {rules.CommitRule.__module__}.{rules.LineRule.__name__}, " + \
+ f"{rules.CommitRule.__module__}.{rules.CommitRule.__name__} or " + \
+ f"{rules.CommitRule.__module__}.{rules.ConfigurationRule.__name__}"
+ raise rules.UserRuleError(msg)
# Rules must have an id attribute
if not hasattr(clazz, 'id') or clazz.id is None or not clazz.id:
- msg = "{0} rule class '{1}' must have an 'id' attribute"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have an 'id' attribute")
# Rule id's cannot start with gitlint reserved letters
if clazz.id[0].upper() in ['R', 'T', 'B', 'M', 'I']:
- msg = "The id '{1}' of '{0}' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
- raise rules.UserRuleError(msg.format(clazz.__name__, clazz.id[0]))
+ msg = f"The id '{clazz.id[0]}' of '{clazz.__name__}' is invalid. Gitlint reserves ids starting with R,T,B,M,I"
+ raise rules.UserRuleError(msg)
# Rules must have a name attribute
if not hasattr(clazz, 'name') or clazz.name is None or not clazz.name:
- msg = "{0} rule class '{1}' must have a 'name' attribute"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'name' attribute")
# if set, options_spec must be a list of RuleOption
if not isinstance(clazz.options_spec, list):
- msg = "The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}"
- raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__,
- options.RuleOption.__module__, options.RuleOption.__name__))
+ msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \
+ f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}"
+ raise rules.UserRuleError(msg)
# check that all items in options_spec are actual gitlint options
for option in clazz.options_spec:
if not isinstance(option, options.RuleOption):
- msg = "The options_spec attribute of {0} rule class '{1}' must be a list of {2}.{3}"
- raise rules.UserRuleError(msg.format(rule_type.lower(), clazz.__name__,
- options.RuleOption.__module__, options.RuleOption.__name__))
+ msg = f"The options_spec attribute of {rule_type.lower()} rule class '{clazz.__name__}' " + \
+ f"must be a list of {options.RuleOption.__module__}.{options.RuleOption.__name__}"
+ raise rules.UserRuleError(msg)
# Line/Commit rules must have a `validate` method
# We use isroutine() as it's both python 2 and 3 compatible. Details: http://stackoverflow.com/a/17019998/381010
if (issubclass(clazz, rules.LineRule) or issubclass(clazz, rules.CommitRule)):
if not hasattr(clazz, 'validate') or not inspect.isroutine(clazz.validate):
- msg = "{0} rule class '{1}' must have a 'validate' method"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ raise rules.UserRuleError(f"{rule_type} rule class '{clazz.__name__}' must have a 'validate' method")
# Configuration rules must have an `apply` method
elif issubclass(clazz, rules.ConfigurationRule):
if not hasattr(clazz, 'apply') or not inspect.isroutine(clazz.apply):
- msg = "{0} Configuration rule class '{1}' must have an 'apply' method"
- raise rules.UserRuleError(msg.format(rule_type, clazz.__name__))
+ msg = f"{rule_type} Configuration rule class '{clazz.__name__}' must have an 'apply' method"
+ raise rules.UserRuleError(msg)
# LineRules must have a valid target: rules.CommitMessageTitle or rules.CommitMessageBody
if issubclass(clazz, rules.LineRule):
if clazz.target not in [rules.CommitMessageTitle, rules.CommitMessageBody]:
- msg = "The target attribute of the {0} LineRule class '{1}' must be either {2}.{3} or {2}.{4}"
- msg = msg.format(rule_type.lower(), clazz.__name__, rules.CommitMessageTitle.__module__,
- rules.CommitMessageTitle.__name__, rules.CommitMessageBody.__name__)
+ msg = f"The target attribute of the {rule_type.lower()} LineRule class '{clazz.__name__}' " + \
+ f"must be either {rules.CommitMessageTitle.__module__}.{rules.CommitMessageTitle.__name__} " + \
+ f"or {rules.CommitMessageTitle.__module__}.{rules.CommitMessageBody.__name__}"
raise rules.UserRuleError(msg)
diff --git a/gitlint/rules.py b/gitlint/rules.py
index e944ce0..32e0327 100644
--- a/gitlint/rules.py
+++ b/gitlint/rules.py
@@ -39,7 +39,7 @@ class Rule:
return self # pragma: no cover
def __unicode__(self):
- return "{0} {1}".format(self.id, self.name) # pragma: no cover
+ return f"{self.id} {self.name}" # pragma: no cover
def __repr__(self):
return self.__str__() # pragma: no cover
@@ -96,8 +96,7 @@ class RuleViolation:
return self # pragma: no cover
def __unicode__(self):
- return f"{0}: {1} {2}: \"{3}\"".format(self.line_nr, self.rule_id, self.message,
- self.content) # pragma: no cover
+ return f"{self.line_nr}: {self.rule_id} {self.message}: \"{self.content}\"" # pragma: no cover
def __repr__(self):
return self.__unicode__() # pragma: no cover
@@ -195,7 +194,7 @@ class TitleTrailingPunctuation(LineRule):
punctuation_marks = '?:!.,;'
for punctuation_mark in punctuation_marks:
if title.endswith(punctuation_mark):
- return [RuleViolation(self.id, "Title has trailing punctuation ({0})".format(punctuation_mark), title)]
+ return [RuleViolation(self.id, f"Title has trailing punctuation ({punctuation_mark})", title)]
class TitleHardTab(HardTab):
@@ -232,7 +231,7 @@ class TitleRegexMatches(LineRule):
return
if not self.options['regex'].value.search(title):
- violation_msg = "Title does not match regex ({0})".format(self.options['regex'].value.pattern)
+ violation_msg = f"Title does not match regex ({self.options['regex'].value.pattern})"
return [RuleViolation(self.id, violation_msg, title)]
@@ -246,7 +245,7 @@ class TitleMinLength(LineRule):
min_length = self.options['min-length'].value
actual_length = len(title)
if actual_length < min_length:
- violation_message = "Title is too short ({0}<{1})".format(actual_length, min_length)
+ violation_message = f"Title is too short ({actual_length}<{min_length})"
return [RuleViolation(self.id, violation_message, title, 1)]
@@ -289,7 +288,7 @@ class BodyMinLength(CommitRule):
body_message_no_newline = "".join([line for line in commit.message.body if line is not None])
actual_length = len(body_message_no_newline)
if 0 < actual_length < min_length:
- violation_message = "Body message is too short ({0}<{1})".format(actual_length, min_length)
+ violation_message = f"Body message is too short ({actual_length}<{min_length})"
return [RuleViolation(self.id, violation_message, body_message_no_newline, 3)]
@@ -318,7 +317,7 @@ class BodyChangedFileMention(CommitRule):
# in the commit msg body
if needs_mentioned_file in commit.changed_files:
if needs_mentioned_file not in " ".join(commit.message.body):
- violation_message = "Body does not mention changed file '{0}'".format(needs_mentioned_file)
+ violation_message = f"Body does not m