Skip to content

Commit

Permalink
Format python with Ruff styling
Browse files Browse the repository at this point in the history
  • Loading branch information
jwnrt committed Jun 9, 2024
1 parent 334e010 commit 67cc6f1
Showing 1 changed file with 78 additions and 84 deletions.
162 changes: 78 additions & 84 deletions licence-checker/licence-checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,26 @@ def numbered_lines(self, skip=0):

@property
def first_word(self):
(first_word, _) = self._lines[0].split(' ', 1)
(first_word, _) = self._lines[0].split(" ", 1)
return first_word


class CommentStyle:
'''Base class for comment style objects'''
"""Base class for comment style objects"""

def __init__(self, first_line_prefix, comment_prefix):
self.first_line_prefix = first_line_prefix
self.comment_prefix = comment_prefix

def search_line_pattern(self, licence_first_word):
return re.compile(
re.escape(self.comment_prefix + ' ' + licence_first_word))
return re.compile(re.escape(self.comment_prefix + " " + licence_first_word))

def full_line_parts(self, licence_line):
return [re.escape(self.comment_prefix), licence_line]

def full_line_pattern(self, licence_line):
'''Returns a regex pattern which matches one line of licence text.'''
return re.compile(' '.join(self.full_line_parts(licence_line)))
"""Returns a regex pattern which matches one line of licence text."""
return re.compile(" ".join(self.full_line_parts(licence_line)))


class LineCommentStyle(CommentStyle):
Expand All @@ -84,26 +83,27 @@ def __init__(self, prefix, suffix):

def full_line_parts(self, licence_line):
return [
re.escape(self.comment_prefix), licence_line,
re.escape(self.comment_suffix)
re.escape(self.comment_prefix),
licence_line,
re.escape(self.comment_suffix),
]


SLASH_SLASH = '//'
HASH = '#'
SLASH_STAR = '/*'
SLASH_SLASH = "//"
HASH = "#"
SLASH_STAR = "/*"

COMMENT_STYLES = {
SLASH_SLASH: LineCommentStyle("//"),
HASH: LineCommentStyle("#"),
SLASH_STAR: BlockCommentStyle("/*", "*/"),
'corefile': DifferentFirstLineCommentStyle("CAPI=2", "#")
"corefile": DifferentFirstLineCommentStyle("CAPI=2", "#"),
}

# Suffixes that can be added to those in the list below. For example, we've got
# ".c" as one of the possible suffixes for a C file below. If ".tpl" is in
# TEMPLATE_SUFFIXES then "foo.c.tpl" will be treated as a C file.
TEMPLATE_SUFFIXES = ['.tpl']
TEMPLATE_SUFFIXES = [".tpl"]

# (Prioritised) Mapping of file name suffixes to comment style. If the suffix
# of your file does not match one of these, it will not be checked.
Expand All @@ -122,10 +122,9 @@ def full_line_parts(self, licence_line):
COMMENT_CHARS = [
# Hardware Files
([".svh", ".sv"], SLASH_SLASH), # SystemVerilog

# Hardware Build Systems
([".tcl", ".sdc"], HASH), # tcl
([".core"], 'corefile'), # FuseSoC Core Files
([".core"], "corefile"), # FuseSoC Core Files
(["Makefile", ".mk"], HASH), # Makefiles
([".ys"], HASH), # Yosys script
([".waiver"], HASH), # AscentLint waiver files
Expand All @@ -134,11 +133,9 @@ def full_line_parts(self, licence_line):
([".el"], SLASH_SLASH), # Exclusion list
([".cfg"], [SLASH_SLASH, HASH]), # Kinds of configuration files
([".f"], []), # File lists (not checked)

# The following two rules will inevitably bite us.
(["riviera_run.do"], HASH), # Riviera dofile
([".do"], SLASH_SLASH), # Cadence LEC dofile

# Software Files
([".c", ".h", ".inc", ".cc", ".cpp"], SLASH_SLASH), # C, C++
([".def"], SLASH_SLASH), # C, C++ X-Include List Declaration Files
Expand All @@ -148,35 +145,30 @@ def full_line_parts(self, licence_line):
([".rs"], SLASH_SLASH), # Rust
([".go"], SLASH_SLASH), # Golang
([".proto"], SLASH_SLASH), # Protobuf

# Software Build Systems
(["meson.build", "toolchain.txt", "meson_options.txt"], HASH), # Meson
(["WORKSPACE", "BUILD", "BUILD.bazel", ".bzl"], HASH), # Bazel

# General Tooling
([".py"], HASH), # Python
([".sh"], HASH), # Shell Scripts
(["Dockerfile"], HASH), # Dockerfiles

# Configuration
([".hjson"], SLASH_SLASH), # hjson
([".yml", ".yaml"], HASH), # YAML
([".toml"], HASH), # TOML
(["-requirements.txt"], HASH), # Apt and Python requirements files
(["redirector.conf"], HASH), # nginx config

# Documentation
([".md", ".html"], []), # Markdown and HTML (not checked)
([".css"], SLASH_STAR), # CSS
([".scss"], SLASH_SLASH), # SCSS

# Templates (Last because there are overlaps with extensions above)
([".tpl"], HASH), # Mako templates
]


class LicenceMatcher:
'''An object to match a given licence at the start of a file'''
"""An object to match a given licence at the start of a file"""

def __init__(self, comment_style, licence, match_regex):
self.style = comment_style
Expand All @@ -185,24 +177,22 @@ def __init__(self, comment_style, licence, match_regex):
if match_regex:
for i, ll in enumerate(licence):
try:
self.expected_lines.append(
comment_style.full_line_pattern(ll))
self.expected_lines.append(comment_style.full_line_pattern(ll))
# Catch any regex error here and raise a runtime error.
except re.error as e:
raise RuntimeError(
f"Can't compile line {i} of the licence as a regular "
f"expression. Saw `{e.pattern[e.pos]}`: {e.msg}")
f"expression. Saw `{e.pattern[e.pos]}`: {e.msg}"
)
# use the "first line" as a licence marker
self.search_marker = self.expected_lines[0]
# For non-regex matching we need to escape everything.
# This can never throw an exception as everything has been escaped and
# therefore is always a legal regex.
else:
self.search_marker = comment_style.search_line_pattern(
licence.first_word)
self.search_marker = comment_style.search_line_pattern(licence.first_word)
self.expected_lines = [
comment_style.full_line_pattern(re.escape(ll))
for ll in licence
comment_style.full_line_pattern(re.escape(ll)) for ll in licence
]

self.lines_left = []
Expand All @@ -217,18 +207,18 @@ def looks_like_first_line(self, line):
return self.search_marker.match(line) is not None

def start(self):
'''Reset lines_left, to match at the start of the licence'''
"""Reset lines_left, to match at the start of the licence"""
self.lines_left = self.expected_lines

def take_line(self, line):
'''Check whether line matches the next line of the licence.
"""Check whether line matches the next line of the licence.
Returns a pair (matched, done). matched is true if the line matched. If
this was the last line of the licence, done is true. On a match, this
increments an internal counter, so the next call to take_line will
match against the next line of the licence.
'''
"""
# If we have no more lines to match, claim a match and that we're done.
# This shouldn't happen in practice, except if the configuration has an
# empty licence.
Expand All @@ -247,9 +237,9 @@ def take_line(self, line):


def comment_styles_for_filename(filename):
'''Return zero or more comment style strings based on filename'''
all_tpl_suffixes = [''] + TEMPLATE_SUFFIXES
for (suffixes, keys) in COMMENT_CHARS:
"""Return zero or more comment style strings based on filename"""
all_tpl_suffixes = [""] + TEMPLATE_SUFFIXES
for suffixes, keys in COMMENT_CHARS:
for suffix in suffixes:
for tpl_suffix in all_tpl_suffixes:
full_suffix = suffix + tpl_suffix
Expand All @@ -266,25 +256,24 @@ def comment_styles_for_filename(filename):


def detect_comment_char(all_matchers, filename):
'''Find zero or more LicenceMatcher objects for filename
"""Find zero or more LicenceMatcher objects for filename
all_matchers should be a dict like COMMENT_STYLES, but where the values are
the corresponding LicenceMatcher objects.
'''
"""
return [all_matchers[key] for key in comment_styles_for_filename(filename)]


def git_find_repo_toplevel():
git_output = subprocess.check_output(
['git', 'rev-parse', '--show-toplevel'])
git_output = subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
return Path(git_output.decode().strip()).resolve()


def git_find_all_file_paths(top_level, search_paths):
git_output = subprocess.check_output(
["git", "-C",
str(top_level), "ls-files", "-z", "--", *search_paths])
["git", "-C", str(top_level), "ls-files", "-z", "--", *search_paths]
)
for path in git_output.rstrip(b"\0").split(b"\0"):
yield Path(top_level, path.decode())

Expand All @@ -304,8 +293,12 @@ def __init__(self, base_dir):

@property
def total_count(self):
return (self.passed_count + self.failed_count + self.skipped_count +
self.excluded_count)
return (
self.passed_count
+ self.failed_count
+ self.skipped_count
+ self.excluded_count
)

def passed(self, path, line_no, reason):
rel_path = path.relative_to(self.base_dir)
Expand Down Expand Up @@ -333,11 +326,13 @@ def any_failed(self):

def display_nicely(self):
headers = ["Results:", "Files"]
results = [["Passed", self.passed_count],
["Failed", self.failed_count],
["Skipped", self.skipped_count],
["Excluded", self.excluded_count],
["Total", self.total_count]]
results = [
["Passed", self.passed_count],
["Failed", self.failed_count],
["Skipped", self.skipped_count],
["Excluded", self.excluded_count],
["Total", self.total_count],
]

return tabulate(results, headers, tablefmt="simple")

Expand Down Expand Up @@ -406,14 +401,14 @@ def check_file_for_licence(all_matchers, results, filepath):


def check_file_with_matcher(matcher, filepath):
'''Check the file at filepath against matcher.
"""Check the file at filepath against matcher.
Returns a tuple (is_good, line_number, msg). is_good is True on success;
False on failure. line_number is the position where the licence was found
(on success) or where we gave up searching for it (on failure). msg is the
associated success or error message.
'''
"""

def next_line(file, line_no):
return (next(file).rstrip(), line_no + 1)
Expand All @@ -436,20 +431,17 @@ def next_line(file, line_no):
try:
line, line_no = next_line(f, line_no)
except StopIteration:
return (False, line_no,
"Reached end of file before finding licence")
return (False, line_no, "Reached end of file before finding licence")

# Skip lines that don't seem to be the first line of the licence
while not matcher.looks_like_first_line(line):
try:
line, line_no = next_line(f, line_no)
except StopIteration:
return (False, line_no,
"Reached end of file before finding licence")
return (False, line_no, "Reached end of file before finding licence")

if not matcher.looks_like_comment(line):
return (False, line_no,
"First comment ended before licence notice")
return (False, line_no, "First comment ended before licence notice")

# We found the marker, so we found the first line of the licence. The
# current line is in the first comment, so check the line matches the
Expand All @@ -464,8 +456,7 @@ def next_line(file, line_no):
try:
line, line_no = next_line(f, line_no)
except StopIteration:
return (False, line_no,
"Reached end of file before finding licence")
return (False, line_no, "Reached end of file before finding licence")

# Check against full expected line.
matched, done = matcher.take_line(line)
Expand All @@ -478,27 +469,28 @@ def next_line(file, line_no):
def main():
desc = "A tool to check the lowRISC licence header is in each source file"
parser = argparse.ArgumentParser(description=desc)
parser.add_argument("--config",
metavar="config.hjson",
type=argparse.FileType('r', encoding='UTF-8'),
required=True,
help="HJSON file to read for licence configuration.")
parser.add_argument("paths",
metavar="path",
nargs='*',
default=["."],
help="Paths to check for licence headers.")
parser.add_argument('-v',
"--verbose",
action='store_true',
dest='verbose',
help="Verbose output")
parser.add_argument(
"--config",
metavar="config.hjson",
type=argparse.FileType("r", encoding="UTF-8"),
required=True,
help="HJSON file to read for licence configuration.",
)
parser.add_argument(
"paths",
metavar="path",
nargs="*",
default=["."],
help="Paths to check for licence headers.",
)
parser.add_argument(
"-v", "--verbose", action="store_true", dest="verbose", help="Verbose output"
)

options = parser.parse_args()

if options.verbose:
logging.basicConfig(format="%(levelname)s: %(message)s",
level=logging.INFO)
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO)
else:
logging.basicConfig(format="%(levelname)s: %(message)s")

Expand All @@ -507,15 +499,17 @@ def main():

parsed_config = hjson.load(options.config)

config.licence = LicenceHeader(parsed_config['licence'])
config.exclude_paths = set(parsed_config['exclude_paths'])
config.licence = LicenceHeader(parsed_config["licence"])
config.exclude_paths = set(parsed_config["exclude_paths"])
# Check whether we should use regex matching or full string matching.
match_regex = parsed_config.get('match_regex', 'false')
if match_regex not in ['true', 'false']:
print('Invalid value for match_regex: {!r}. '
'Should be "true" or "false".'.format(match_regex))
match_regex = parsed_config.get("match_regex", "false")
if match_regex not in ["true", "false"]:
print(
"Invalid value for match_regex: {!r}. "
'Should be "true" or "false".'.format(match_regex)
)
exit(1)
config.match_regex = match_regex == 'true'
config.match_regex = match_regex == "true"

results = check_paths(config, options.paths)

Expand All @@ -531,5 +525,5 @@ def main():
exit(0)


if __name__ == '__main__':
if __name__ == "__main__":
main()

0 comments on commit 67cc6f1

Please sign in to comment.