2016-11-02 15:35:22 +01:00
|
|
|
#!/usr/bin/env python3
|
2019-10-04 17:47:11 +02:00
|
|
|
# Copyright (c) 2016-2019 The Bitcoin Core developers
|
2022-06-08 01:36:46 +02:00
|
|
|
# Copyright (c) 2019-2021 The Dash Core developers
|
2016-11-02 15:35:22 +01:00
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
import re
|
|
|
|
import fnmatch
|
|
|
|
import sys
|
|
|
|
import subprocess
|
|
|
|
import datetime
|
|
|
|
import os
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# file filtering
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
EXCLUDE = [
|
2018-01-03 16:02:55 +01:00
|
|
|
# auto generated:
|
2016-11-02 15:35:22 +01:00
|
|
|
'src/qt/bitcoinstrings.cpp',
|
|
|
|
'src/chainparamsseeds.h',
|
|
|
|
# other external copyrights:
|
2020-01-16 21:57:24 +01:00
|
|
|
'src/reverse_iterator.h',
|
|
|
|
'src/test/fuzz/FuzzedDataProvider.h',
|
2016-11-02 15:35:22 +01:00
|
|
|
'src/tinyformat.h',
|
2021-06-26 12:03:16 +02:00
|
|
|
'src/bench/nanobench.h',
|
2019-05-19 22:20:34 +02:00
|
|
|
'test/functional/test_framework/bignum.py',
|
2016-11-02 15:35:22 +01:00
|
|
|
# python init:
|
|
|
|
'*__init__.py',
|
|
|
|
]
|
|
|
|
EXCLUDE_COMPILED = re.compile('|'.join([fnmatch.translate(m) for m in EXCLUDE]))
|
|
|
|
|
2018-11-22 14:45:47 +01:00
|
|
|
EXCLUDE_DIRS = [
|
|
|
|
# git subtrees
|
|
|
|
"src/crypto/ctaes/",
|
|
|
|
"src/leveldb/",
|
|
|
|
"src/secp256k1/",
|
|
|
|
"src/univalue/",
|
2021-07-15 22:42:55 +02:00
|
|
|
"src/crc32c/",
|
2018-11-22 14:45:47 +01:00
|
|
|
]
|
|
|
|
|
2019-10-04 17:47:11 +02:00
|
|
|
INCLUDE = ['*.h', '*.cpp', '*.cc', '*.c', '*.mm', '*.py', '*.sh', '*.bash-completion']
|
2016-11-02 15:35:22 +01:00
|
|
|
INCLUDE_COMPILED = re.compile('|'.join([fnmatch.translate(m) for m in INCLUDE]))
|
|
|
|
|
|
|
|
def applies_to_file(filename):
|
2018-11-22 14:45:47 +01:00
|
|
|
for excluded_dir in EXCLUDE_DIRS:
|
|
|
|
if filename.startswith(excluded_dir):
|
|
|
|
return False
|
2016-11-02 15:35:22 +01:00
|
|
|
return ((EXCLUDE_COMPILED.match(filename) is None) and
|
|
|
|
(INCLUDE_COMPILED.match(filename) is not None))
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# obtain list of files in repo according to INCLUDE and EXCLUDE
|
|
|
|
################################################################################
|
|
|
|
|
2019-01-26 19:57:28 +01:00
|
|
|
GIT_LS_CMD = 'git ls-files --full-name'.split(' ')
|
|
|
|
GIT_TOPLEVEL_CMD = 'git rev-parse --show-toplevel'.split(' ')
|
2016-11-02 15:35:22 +01:00
|
|
|
|
2019-01-26 19:57:28 +01:00
|
|
|
def call_git_ls(base_directory):
|
|
|
|
out = subprocess.check_output([*GIT_LS_CMD, base_directory])
|
2016-11-02 15:35:22 +01:00
|
|
|
return [f for f in out.decode("utf-8").split('\n') if f != '']
|
|
|
|
|
2019-01-26 19:57:28 +01:00
|
|
|
def call_git_toplevel():
|
|
|
|
"Returns the absolute path to the project root"
|
|
|
|
return subprocess.check_output(GIT_TOPLEVEL_CMD).strip().decode("utf-8")
|
|
|
|
|
|
|
|
def get_filenames_to_examine(base_directory):
|
|
|
|
"Returns an array of absolute paths to any project files in the base_directory that pass the include/exclude filters"
|
|
|
|
root = call_git_toplevel()
|
|
|
|
filenames = call_git_ls(base_directory)
|
|
|
|
return sorted([os.path.join(root, filename) for filename in filenames if
|
2016-11-02 15:35:22 +01:00
|
|
|
applies_to_file(filename)])
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# define and compile regexes for the patterns we are looking for
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
|
2021-09-10 02:45:16 +02:00
|
|
|
COPYRIGHT_WITH_C = r'Copyright \(c\)'
|
2016-11-02 15:35:22 +01:00
|
|
|
COPYRIGHT_WITHOUT_C = 'Copyright'
|
|
|
|
ANY_COPYRIGHT_STYLE = '(%s|%s)' % (COPYRIGHT_WITH_C, COPYRIGHT_WITHOUT_C)
|
|
|
|
|
|
|
|
YEAR = "20[0-9][0-9]"
|
|
|
|
YEAR_RANGE = '(%s)(-%s)?' % (YEAR, YEAR)
|
|
|
|
YEAR_LIST = '(%s)(, %s)+' % (YEAR, YEAR)
|
|
|
|
ANY_YEAR_STYLE = '(%s|%s)' % (YEAR_RANGE, YEAR_LIST)
|
|
|
|
ANY_COPYRIGHT_STYLE_OR_YEAR_STYLE = ("%s %s" % (ANY_COPYRIGHT_STYLE,
|
|
|
|
ANY_YEAR_STYLE))
|
|
|
|
|
|
|
|
ANY_COPYRIGHT_COMPILED = re.compile(ANY_COPYRIGHT_STYLE_OR_YEAR_STYLE)
|
|
|
|
|
|
|
|
def compile_copyright_regex(copyright_style, year_style, name):
|
2021-09-10 02:45:16 +02:00
|
|
|
return re.compile(r'%s %s,? %s( +\*)?\n' % (copyright_style, year_style, name))
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
EXPECTED_HOLDER_NAMES = [
|
2021-09-10 02:45:16 +02:00
|
|
|
r"Satoshi Nakamoto",
|
|
|
|
r"The Bitcoin Core developers",
|
|
|
|
r"The Dash Core developers",
|
|
|
|
r"BitPay Inc\.",
|
|
|
|
r"University of Illinois at Urbana-Champaign\.",
|
|
|
|
r"Pieter Wuille",
|
|
|
|
r"Wladimir J\. van der Laan",
|
|
|
|
r"Jeff Garzik",
|
|
|
|
r"Jan-Klaas Kollhof",
|
|
|
|
r"ArtForz -- public domain half-a-node",
|
|
|
|
r"Intel Corporation ?",
|
|
|
|
r"The Zcash developers",
|
|
|
|
r"Jeremy Rubin",
|
2016-11-02 15:35:22 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
DOMINANT_STYLE_COMPILED = {}
|
|
|
|
YEAR_LIST_STYLE_COMPILED = {}
|
|
|
|
WITHOUT_C_STYLE_COMPILED = {}
|
|
|
|
|
|
|
|
for holder_name in EXPECTED_HOLDER_NAMES:
|
|
|
|
DOMINANT_STYLE_COMPILED[holder_name] = (
|
|
|
|
compile_copyright_regex(COPYRIGHT_WITH_C, YEAR_RANGE, holder_name))
|
|
|
|
YEAR_LIST_STYLE_COMPILED[holder_name] = (
|
|
|
|
compile_copyright_regex(COPYRIGHT_WITH_C, YEAR_LIST, holder_name))
|
|
|
|
WITHOUT_C_STYLE_COMPILED[holder_name] = (
|
|
|
|
compile_copyright_regex(COPYRIGHT_WITHOUT_C, ANY_YEAR_STYLE,
|
|
|
|
holder_name))
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# search file contents for copyright message of particular category
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
def get_count_of_copyrights_of_any_style_any_holder(contents):
|
|
|
|
return len(ANY_COPYRIGHT_COMPILED.findall(contents))
|
|
|
|
|
|
|
|
def file_has_dominant_style_copyright_for_holder(contents, holder_name):
|
|
|
|
match = DOMINANT_STYLE_COMPILED[holder_name].search(contents)
|
|
|
|
return match is not None
|
|
|
|
|
|
|
|
def file_has_year_list_style_copyright_for_holder(contents, holder_name):
|
|
|
|
match = YEAR_LIST_STYLE_COMPILED[holder_name].search(contents)
|
|
|
|
return match is not None
|
|
|
|
|
|
|
|
def file_has_without_c_style_copyright_for_holder(contents, holder_name):
|
|
|
|
match = WITHOUT_C_STYLE_COMPILED[holder_name].search(contents)
|
|
|
|
return match is not None
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# get file info
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
def read_file(filename):
|
2019-01-26 19:57:28 +01:00
|
|
|
return open(filename, 'r', encoding="utf8").read()
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
def gather_file_info(filename):
|
|
|
|
info = {}
|
|
|
|
info['filename'] = filename
|
|
|
|
c = read_file(filename)
|
|
|
|
info['contents'] = c
|
|
|
|
|
|
|
|
info['all_copyrights'] = get_count_of_copyrights_of_any_style_any_holder(c)
|
|
|
|
|
|
|
|
info['classified_copyrights'] = 0
|
|
|
|
info['dominant_style'] = {}
|
|
|
|
info['year_list_style'] = {}
|
|
|
|
info['without_c_style'] = {}
|
|
|
|
for holder_name in EXPECTED_HOLDER_NAMES:
|
|
|
|
has_dominant_style = (
|
|
|
|
file_has_dominant_style_copyright_for_holder(c, holder_name))
|
|
|
|
has_year_list_style = (
|
|
|
|
file_has_year_list_style_copyright_for_holder(c, holder_name))
|
|
|
|
has_without_c_style = (
|
|
|
|
file_has_without_c_style_copyright_for_holder(c, holder_name))
|
|
|
|
info['dominant_style'][holder_name] = has_dominant_style
|
|
|
|
info['year_list_style'][holder_name] = has_year_list_style
|
|
|
|
info['without_c_style'][holder_name] = has_without_c_style
|
|
|
|
if has_dominant_style or has_year_list_style or has_without_c_style:
|
|
|
|
info['classified_copyrights'] = info['classified_copyrights'] + 1
|
|
|
|
return info
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# report execution
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
SEPARATOR = '-'.join(['' for _ in range(80)])
|
|
|
|
|
|
|
|
def print_filenames(filenames, verbose):
|
|
|
|
if not verbose:
|
|
|
|
return
|
|
|
|
for filename in filenames:
|
|
|
|
print("\t%s" % filename)
|
|
|
|
|
|
|
|
def print_report(file_infos, verbose):
|
|
|
|
print(SEPARATOR)
|
|
|
|
examined = [i['filename'] for i in file_infos]
|
|
|
|
print("%d files examined according to INCLUDE and EXCLUDE fnmatch rules" %
|
|
|
|
len(examined))
|
|
|
|
print_filenames(examined, verbose)
|
|
|
|
|
|
|
|
print(SEPARATOR)
|
|
|
|
print('')
|
|
|
|
zero_copyrights = [i['filename'] for i in file_infos if
|
|
|
|
i['all_copyrights'] == 0]
|
|
|
|
print("%4d with zero copyrights" % len(zero_copyrights))
|
|
|
|
print_filenames(zero_copyrights, verbose)
|
|
|
|
one_copyright = [i['filename'] for i in file_infos if
|
|
|
|
i['all_copyrights'] == 1]
|
|
|
|
print("%4d with one copyright" % len(one_copyright))
|
|
|
|
print_filenames(one_copyright, verbose)
|
|
|
|
two_copyrights = [i['filename'] for i in file_infos if
|
|
|
|
i['all_copyrights'] == 2]
|
|
|
|
print("%4d with two copyrights" % len(two_copyrights))
|
|
|
|
print_filenames(two_copyrights, verbose)
|
|
|
|
three_copyrights = [i['filename'] for i in file_infos if
|
|
|
|
i['all_copyrights'] == 3]
|
|
|
|
print("%4d with three copyrights" % len(three_copyrights))
|
|
|
|
print_filenames(three_copyrights, verbose)
|
|
|
|
four_or_more_copyrights = [i['filename'] for i in file_infos if
|
|
|
|
i['all_copyrights'] >= 4]
|
|
|
|
print("%4d with four or more copyrights" % len(four_or_more_copyrights))
|
|
|
|
print_filenames(four_or_more_copyrights, verbose)
|
|
|
|
print('')
|
|
|
|
print(SEPARATOR)
|
|
|
|
print('Copyrights with dominant style:\ne.g. "Copyright (c)" and '
|
|
|
|
'"<year>" or "<startYear>-<endYear>":\n')
|
|
|
|
for holder_name in EXPECTED_HOLDER_NAMES:
|
|
|
|
dominant_style = [i['filename'] for i in file_infos if
|
|
|
|
i['dominant_style'][holder_name]]
|
|
|
|
if len(dominant_style) > 0:
|
|
|
|
print("%4d with '%s'" % (len(dominant_style),
|
|
|
|
holder_name.replace('\n', '\\n')))
|
|
|
|
print_filenames(dominant_style, verbose)
|
|
|
|
print('')
|
|
|
|
print(SEPARATOR)
|
|
|
|
print('Copyrights with year list style:\ne.g. "Copyright (c)" and '
|
|
|
|
'"<year1>, <year2>, ...":\n')
|
|
|
|
for holder_name in EXPECTED_HOLDER_NAMES:
|
|
|
|
year_list_style = [i['filename'] for i in file_infos if
|
|
|
|
i['year_list_style'][holder_name]]
|
|
|
|
if len(year_list_style) > 0:
|
|
|
|
print("%4d with '%s'" % (len(year_list_style),
|
|
|
|
holder_name.replace('\n', '\\n')))
|
|
|
|
print_filenames(year_list_style, verbose)
|
|
|
|
print('')
|
|
|
|
print(SEPARATOR)
|
|
|
|
print('Copyrights with no "(c)" style:\ne.g. "Copyright" and "<year>" or '
|
|
|
|
'"<startYear>-<endYear>":\n')
|
|
|
|
for holder_name in EXPECTED_HOLDER_NAMES:
|
|
|
|
without_c_style = [i['filename'] for i in file_infos if
|
|
|
|
i['without_c_style'][holder_name]]
|
|
|
|
if len(without_c_style) > 0:
|
|
|
|
print("%4d with '%s'" % (len(without_c_style),
|
|
|
|
holder_name.replace('\n', '\\n')))
|
|
|
|
print_filenames(without_c_style, verbose)
|
|
|
|
|
|
|
|
print('')
|
|
|
|
print(SEPARATOR)
|
|
|
|
|
|
|
|
unclassified_copyrights = [i['filename'] for i in file_infos if
|
|
|
|
i['classified_copyrights'] < i['all_copyrights']]
|
|
|
|
print("%d with unexpected copyright holder names" %
|
|
|
|
len(unclassified_copyrights))
|
|
|
|
print_filenames(unclassified_copyrights, verbose)
|
|
|
|
print(SEPARATOR)
|
|
|
|
|
|
|
|
def exec_report(base_directory, verbose):
|
2019-01-26 19:57:28 +01:00
|
|
|
filenames = get_filenames_to_examine(base_directory)
|
2016-11-02 15:35:22 +01:00
|
|
|
file_infos = [gather_file_info(f) for f in filenames]
|
|
|
|
print_report(file_infos, verbose)
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# report cmd
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
REPORT_USAGE = """
|
|
|
|
Produces a report of all copyright header notices found inside the source files
|
|
|
|
of a repository.
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
$ ./copyright_header.py report <base_directory> [verbose]
|
|
|
|
|
|
|
|
Arguments:
|
2019-01-03 10:20:22 +01:00
|
|
|
<base_directory> - The base directory of a Dash Core source code repository.
|
2016-11-02 15:35:22 +01:00
|
|
|
[verbose] - Includes a list of every file of each subcategory in the report.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def report_cmd(argv):
|
|
|
|
if len(argv) == 2:
|
|
|
|
sys.exit(REPORT_USAGE)
|
Merge #12987: tests/tools: Enable additional Python flake8 rules for automatic linting via Travis
643aad17fa Enable additional flake8 rules (practicalswift)
f020aca297 Minor Python cleanups to make flake8 pass with the new rules enabled (practicalswift)
Pull request description:
Enabled rules:
```
* E242: tab after ','
* E266: too many leading '#' for block comment
* E401: multiple imports on one line
* E402: module level import not at top of file
* E701: multiple statements on one line (colon)
* E901: SyntaxError: invalid syntax
* E902: TokenError: EOF in multi-line string
* F821: undefined name 'Foo'
* W293: blank line contains whitespace
* W606: 'async' and 'await' are reserved keywords starting with Python 3.7
```
Note to reviewers:
* In general we don't allow whitespace cleanups to existing code, but in order to allow for enabling Travis checking for these rules a few smaller whitespace cleanups had to made as part of this PR.
* Use [this `?w=1` link](https://github.com/bitcoin/bitcoin/pull/12987/files?w=1) to show a diff without whitespace changes.
Before this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
5 E266 too many leading '#' for block comment
4 E401 multiple imports on one line
6 E402 module level import not at top of file
5 E701 multiple statements on one line (colon)
1 F812 list comprehension redefines 'n' from line 159
4 F821 undefined name 'ConnectionRefusedError'
28 W293 blank line contains whitespace
```
After this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
$
```
Tree-SHA512: fc7d5e752298a50d4248afc620ee2c173135b4ca008e48e02913ac968e5a24a5fd5396926047ec62f1d580d537434ccae01f249bb2f3338fa59dc630bf97ca7a
Signed-off-by: pasta <pasta@dashboost.org>
2018-04-16 17:49:49 +02:00
|
|
|
|
2016-11-02 15:35:22 +01:00
|
|
|
base_directory = argv[2]
|
|
|
|
if not os.path.exists(base_directory):
|
|
|
|
sys.exit("*** bad <base_directory>: %s" % base_directory)
|
|
|
|
|
|
|
|
if len(argv) == 3:
|
|
|
|
verbose = False
|
|
|
|
elif argv[3] == 'verbose':
|
|
|
|
verbose = True
|
|
|
|
else:
|
|
|
|
sys.exit("*** unknown argument: %s" % argv[2])
|
|
|
|
|
|
|
|
exec_report(base_directory, verbose)
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# query git for year of last change
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
GIT_LOG_CMD = "git log --pretty=format:%%ai %s"
|
|
|
|
|
|
|
|
def call_git_log(filename):
|
|
|
|
out = subprocess.check_output((GIT_LOG_CMD % filename).split(' '))
|
|
|
|
return out.decode("utf-8").split('\n')
|
|
|
|
|
|
|
|
def get_git_change_years(filename):
|
|
|
|
git_log_lines = call_git_log(filename)
|
|
|
|
if len(git_log_lines) == 0:
|
|
|
|
return [datetime.date.today().year]
|
|
|
|
# timestamp is in ISO 8601 format. e.g. "2016-09-05 14:25:32 -0600"
|
|
|
|
return [line.split(' ')[0].split('-')[0] for line in git_log_lines]
|
|
|
|
|
|
|
|
def get_most_recent_git_change_year(filename):
|
|
|
|
return max(get_git_change_years(filename))
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# read and write to file
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
def read_file_lines(filename):
|
2019-01-26 19:57:28 +01:00
|
|
|
f = open(filename, 'r', encoding="utf8")
|
2016-11-02 15:35:22 +01:00
|
|
|
file_lines = f.readlines()
|
|
|
|
f.close()
|
|
|
|
return file_lines
|
|
|
|
|
|
|
|
def write_file_lines(filename, file_lines):
|
2019-01-26 19:57:28 +01:00
|
|
|
f = open(filename, 'w', encoding="utf8")
|
2016-11-02 15:35:22 +01:00
|
|
|
f.write(''.join(file_lines))
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# update header years execution
|
|
|
|
################################################################################
|
|
|
|
|
2021-09-10 02:45:16 +02:00
|
|
|
COPYRIGHT = r'Copyright \(c\)'
|
2016-11-02 15:35:22 +01:00
|
|
|
YEAR = "20[0-9][0-9]"
|
|
|
|
YEAR_RANGE = '(%s)(-%s)?' % (YEAR, YEAR)
|
2019-01-03 10:20:22 +01:00
|
|
|
HOLDER = 'The Dash Core developers'
|
2016-11-02 15:35:22 +01:00
|
|
|
UPDATEABLE_LINE_COMPILED = re.compile(' '.join([COPYRIGHT, YEAR_RANGE, HOLDER]))
|
|
|
|
|
|
|
|
def get_updatable_copyright_line(file_lines):
|
|
|
|
index = 0
|
|
|
|
for line in file_lines:
|
|
|
|
if UPDATEABLE_LINE_COMPILED.search(line) is not None:
|
|
|
|
return index, line
|
|
|
|
index = index + 1
|
|
|
|
return None, None
|
|
|
|
|
|
|
|
def parse_year_range(year_range):
|
|
|
|
year_split = year_range.split('-')
|
|
|
|
start_year = year_split[0]
|
|
|
|
if len(year_split) == 1:
|
|
|
|
return start_year, start_year
|
|
|
|
return start_year, year_split[1]
|
|
|
|
|
|
|
|
def year_range_to_str(start_year, end_year):
|
|
|
|
if start_year == end_year:
|
|
|
|
return start_year
|
|
|
|
return "%s-%s" % (start_year, end_year)
|
|
|
|
|
|
|
|
def create_updated_copyright_line(line, last_git_change_year):
|
|
|
|
copyright_splitter = 'Copyright (c) '
|
|
|
|
copyright_split = line.split(copyright_splitter)
|
|
|
|
# Preserve characters on line that are ahead of the start of the copyright
|
|
|
|
# notice - they are part of the comment block and vary from file-to-file.
|
|
|
|
before_copyright = copyright_split[0]
|
|
|
|
after_copyright = copyright_split[1]
|
|
|
|
|
|
|
|
space_split = after_copyright.split(' ')
|
|
|
|
year_range = space_split[0]
|
|
|
|
start_year, end_year = parse_year_range(year_range)
|
|
|
|
if end_year == last_git_change_year:
|
|
|
|
return line
|
|
|
|
return (before_copyright + copyright_splitter +
|
|
|
|
year_range_to_str(start_year, last_git_change_year) + ' ' +
|
|
|
|
' '.join(space_split[1:]))
|
|
|
|
|
|
|
|
def update_updatable_copyright(filename):
|
|
|
|
file_lines = read_file_lines(filename)
|
|
|
|
index, line = get_updatable_copyright_line(file_lines)
|
|
|
|
if not line:
|
|
|
|
print_file_action_message(filename, "No updatable copyright.")
|
|
|
|
return
|
|
|
|
last_git_change_year = get_most_recent_git_change_year(filename)
|
|
|
|
new_line = create_updated_copyright_line(line, last_git_change_year)
|
|
|
|
if line == new_line:
|
|
|
|
print_file_action_message(filename, "Copyright up-to-date.")
|
|
|
|
return
|
|
|
|
file_lines[index] = new_line
|
|
|
|
write_file_lines(filename, file_lines)
|
|
|
|
print_file_action_message(filename,
|
|
|
|
"Copyright updated! -> %s" % last_git_change_year)
|
|
|
|
|
|
|
|
def exec_update_header_year(base_directory):
|
2019-01-26 19:57:28 +01:00
|
|
|
for filename in get_filenames_to_examine(base_directory):
|
2016-11-02 15:35:22 +01:00
|
|
|
update_updatable_copyright(filename)
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# update cmd
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
UPDATE_USAGE = """
|
2019-01-03 10:20:22 +01:00
|
|
|
Updates all the copyright headers of "The Dash Core developers" which were
|
2016-11-02 15:35:22 +01:00
|
|
|
changed in a year more recent than is listed. For example:
|
|
|
|
|
2019-01-03 10:20:22 +01:00
|
|
|
// Copyright (c) <firstYear>-<lastYear> The Dash Core developers
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
will be updated to:
|
|
|
|
|
2019-01-03 10:20:22 +01:00
|
|
|
// Copyright (c) <firstYear>-<lastModifiedYear> The Dash Core developers
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
where <lastModifiedYear> is obtained from the 'git log' history.
|
|
|
|
|
|
|
|
This subcommand also handles copyright headers that have only a single year. In those cases:
|
|
|
|
|
2019-01-03 10:20:22 +01:00
|
|
|
// Copyright (c) <year> The Dash Core developers
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
will be updated to:
|
|
|
|
|
2019-01-03 10:20:22 +01:00
|
|
|
// Copyright (c) <year>-<lastModifiedYear> The Dash Core developers
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
where the update is appropriate.
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
$ ./copyright_header.py update <base_directory>
|
|
|
|
|
|
|
|
Arguments:
|
2019-01-03 10:20:22 +01:00
|
|
|
<base_directory> - The base directory of Dash Core source code repository.
|
2016-11-02 15:35:22 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
def print_file_action_message(filename, action):
|
|
|
|
print("%-52s %s" % (filename, action))
|
|
|
|
|
|
|
|
def update_cmd(argv):
|
|
|
|
if len(argv) != 3:
|
|
|
|
sys.exit(UPDATE_USAGE)
|
Merge #12987: tests/tools: Enable additional Python flake8 rules for automatic linting via Travis
643aad17fa Enable additional flake8 rules (practicalswift)
f020aca297 Minor Python cleanups to make flake8 pass with the new rules enabled (practicalswift)
Pull request description:
Enabled rules:
```
* E242: tab after ','
* E266: too many leading '#' for block comment
* E401: multiple imports on one line
* E402: module level import not at top of file
* E701: multiple statements on one line (colon)
* E901: SyntaxError: invalid syntax
* E902: TokenError: EOF in multi-line string
* F821: undefined name 'Foo'
* W293: blank line contains whitespace
* W606: 'async' and 'await' are reserved keywords starting with Python 3.7
```
Note to reviewers:
* In general we don't allow whitespace cleanups to existing code, but in order to allow for enabling Travis checking for these rules a few smaller whitespace cleanups had to made as part of this PR.
* Use [this `?w=1` link](https://github.com/bitcoin/bitcoin/pull/12987/files?w=1) to show a diff without whitespace changes.
Before this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
5 E266 too many leading '#' for block comment
4 E401 multiple imports on one line
6 E402 module level import not at top of file
5 E701 multiple statements on one line (colon)
1 F812 list comprehension redefines 'n' from line 159
4 F821 undefined name 'ConnectionRefusedError'
28 W293 blank line contains whitespace
```
After this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
$
```
Tree-SHA512: fc7d5e752298a50d4248afc620ee2c173135b4ca008e48e02913ac968e5a24a5fd5396926047ec62f1d580d537434ccae01f249bb2f3338fa59dc630bf97ca7a
Signed-off-by: pasta <pasta@dashboost.org>
2018-04-16 17:49:49 +02:00
|
|
|
|
2016-11-02 15:35:22 +01:00
|
|
|
base_directory = argv[2]
|
|
|
|
if not os.path.exists(base_directory):
|
|
|
|
sys.exit("*** bad base_directory: %s" % base_directory)
|
|
|
|
exec_update_header_year(base_directory)
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# inserted copyright header format
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
def get_header_lines(header, start_year, end_year):
|
|
|
|
lines = header.split('\n')[1:-1]
|
|
|
|
lines[0] = lines[0] % year_range_to_str(start_year, end_year)
|
|
|
|
return [line + '\n' for line in lines]
|
|
|
|
|
|
|
|
CPP_HEADER = '''
|
2019-01-03 10:20:22 +01:00
|
|
|
// Copyright (c) %s The Dash Core developers
|
2016-11-02 15:35:22 +01:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
'''
|
|
|
|
|
|
|
|
def get_cpp_header_lines_to_insert(start_year, end_year):
|
|
|
|
return reversed(get_header_lines(CPP_HEADER, start_year, end_year))
|
|
|
|
|
2020-01-16 21:57:24 +01:00
|
|
|
SCRIPT_HEADER = '''
|
2019-01-03 10:20:22 +01:00
|
|
|
# Copyright (c) %s The Dash Core developers
|
2016-11-02 15:35:22 +01:00
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
'''
|
|
|
|
|
2020-01-16 21:57:24 +01:00
|
|
|
def get_script_header_lines_to_insert(start_year, end_year):
|
|
|
|
return reversed(get_header_lines(SCRIPT_HEADER, start_year, end_year))
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# query git for year of last change
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
def get_git_change_year_range(filename):
|
|
|
|
years = get_git_change_years(filename)
|
|
|
|
return min(years), max(years)
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# check for existing core copyright
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
def file_already_has_core_copyright(file_lines):
|
|
|
|
index, _ = get_updatable_copyright_line(file_lines)
|
2018-12-13 12:57:41 +01:00
|
|
|
return index is not None
|
2016-11-02 15:35:22 +01:00
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# insert header execution
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
def file_has_hashbang(file_lines):
|
|
|
|
if len(file_lines) < 1:
|
|
|
|
return False
|
|
|
|
if len(file_lines[0]) <= 2:
|
|
|
|
return False
|
|
|
|
return file_lines[0][:2] == '#!'
|
|
|
|
|
2020-01-16 21:57:24 +01:00
|
|
|
def insert_script_header(filename, file_lines, start_year, end_year):
|
2016-11-02 15:35:22 +01:00
|
|
|
if file_has_hashbang(file_lines):
|
2018-05-11 02:59:36 +02:00
|
|
|
insert_idx = 1
|
2016-11-02 15:35:22 +01:00
|
|
|
else:
|
|
|
|
insert_idx = 0
|
2020-01-16 21:57:24 +01:00
|
|
|
header_lines = get_script_header_lines_to_insert(start_year, end_year)
|
2016-11-02 15:35:22 +01:00
|
|
|
for line in header_lines:
|
|
|
|
file_lines.insert(insert_idx, line)
|
|
|
|
write_file_lines(filename, file_lines)
|
|
|
|
|
|
|
|
def insert_cpp_header(filename, file_lines, start_year, end_year):
|
2020-01-16 21:57:24 +01:00
|
|
|
file_lines.insert(0, '\n')
|
2016-11-02 15:35:22 +01:00
|
|
|
header_lines = get_cpp_header_lines_to_insert(start_year, end_year)
|
|
|
|
for line in header_lines:
|
|
|
|
file_lines.insert(0, line)
|
|
|
|
write_file_lines(filename, file_lines)
|
|
|
|
|
|
|
|
def exec_insert_header(filename, style):
|
|
|
|
file_lines = read_file_lines(filename)
|
|
|
|
if file_already_has_core_copyright(file_lines):
|
2019-01-03 10:20:22 +01:00
|
|
|
sys.exit('*** %s already has a copyright by The Dash Core developers'
|
2016-11-02 15:35:22 +01:00
|
|
|
% (filename))
|
|
|
|
start_year, end_year = get_git_change_year_range(filename)
|
2020-01-16 21:57:24 +01:00
|
|
|
if style in ['python', 'shell']:
|
|
|
|
insert_script_header(filename, file_lines, start_year, end_year)
|
2016-11-02 15:35:22 +01:00
|
|
|
else:
|
|
|
|
insert_cpp_header(filename, file_lines, start_year, end_year)
|
|
|
|
|
|
|
|
################################################################################
|
|
|
|
# insert cmd
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
INSERT_USAGE = """
|
2019-01-03 10:20:22 +01:00
|
|
|
Inserts a copyright header for "The Dash Core developers" at the top of the
|
2016-11-02 15:35:22 +01:00
|
|
|
file in either Python or C++ style as determined by the file extension. If the
|
|
|
|
file is a Python file and it has a '#!' starting the first line, the header is
|
|
|
|
inserted in the line below it.
|
|
|
|
|
|
|
|
The copyright dates will be set to be:
|
|
|
|
|
|
|
|
"<year_introduced>-<current_year>"
|
|
|
|
|
|
|
|
where <year_introduced> is according to the 'git log' history. If
|
|
|
|
<year_introduced> is equal to <current_year>, the date will be set to be:
|
|
|
|
|
|
|
|
"<current_year>"
|
|
|
|
|
2019-01-03 10:20:22 +01:00
|
|
|
If the file already has a copyright for "The Dash Core developers", the
|
2016-11-02 15:35:22 +01:00
|
|
|
script will exit.
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
$ ./copyright_header.py insert <file>
|
|
|
|
|
|
|
|
Arguments:
|
2019-01-03 10:20:22 +01:00
|
|
|
<file> - A source file in the Dash Core repository.
|
2016-11-02 15:35:22 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
def insert_cmd(argv):
|
|
|
|
if len(argv) != 3:
|
|
|
|
sys.exit(INSERT_USAGE)
|
|
|
|
|
|
|
|
filename = argv[2]
|
|
|
|
if not os.path.isfile(filename):
|
|
|
|
sys.exit("*** bad filename: %s" % filename)
|
|
|
|
_, extension = os.path.splitext(filename)
|
2020-01-16 21:57:24 +01:00
|
|
|
if extension not in ['.h', '.cpp', '.cc', '.c', '.py', '.sh']:
|
2016-11-02 15:35:22 +01:00
|
|
|
sys.exit("*** cannot insert for file extension %s" % extension)
|
Merge #12987: tests/tools: Enable additional Python flake8 rules for automatic linting via Travis
643aad17fa Enable additional flake8 rules (practicalswift)
f020aca297 Minor Python cleanups to make flake8 pass with the new rules enabled (practicalswift)
Pull request description:
Enabled rules:
```
* E242: tab after ','
* E266: too many leading '#' for block comment
* E401: multiple imports on one line
* E402: module level import not at top of file
* E701: multiple statements on one line (colon)
* E901: SyntaxError: invalid syntax
* E902: TokenError: EOF in multi-line string
* F821: undefined name 'Foo'
* W293: blank line contains whitespace
* W606: 'async' and 'await' are reserved keywords starting with Python 3.7
```
Note to reviewers:
* In general we don't allow whitespace cleanups to existing code, but in order to allow for enabling Travis checking for these rules a few smaller whitespace cleanups had to made as part of this PR.
* Use [this `?w=1` link](https://github.com/bitcoin/bitcoin/pull/12987/files?w=1) to show a diff without whitespace changes.
Before this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
5 E266 too many leading '#' for block comment
4 E401 multiple imports on one line
6 E402 module level import not at top of file
5 E701 multiple statements on one line (colon)
1 F812 list comprehension redefines 'n' from line 159
4 F821 undefined name 'ConnectionRefusedError'
28 W293 blank line contains whitespace
```
After this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
$
```
Tree-SHA512: fc7d5e752298a50d4248afc620ee2c173135b4ca008e48e02913ac968e5a24a5fd5396926047ec62f1d580d537434ccae01f249bb2f3338fa59dc630bf97ca7a
Signed-off-by: pasta <pasta@dashboost.org>
2018-04-16 17:49:49 +02:00
|
|
|
|
2018-05-11 02:59:36 +02:00
|
|
|
if extension == '.py':
|
2016-11-02 15:35:22 +01:00
|
|
|
style = 'python'
|
2020-01-16 21:57:24 +01:00
|
|
|
elif extension == '.sh':
|
|
|
|
style = 'shell'
|
2016-11-02 15:35:22 +01:00
|
|
|
else:
|
|
|
|
style = 'cpp'
|
|
|
|
exec_insert_header(filename, style)
|
Merge #12987: tests/tools: Enable additional Python flake8 rules for automatic linting via Travis
643aad17fa Enable additional flake8 rules (practicalswift)
f020aca297 Minor Python cleanups to make flake8 pass with the new rules enabled (practicalswift)
Pull request description:
Enabled rules:
```
* E242: tab after ','
* E266: too many leading '#' for block comment
* E401: multiple imports on one line
* E402: module level import not at top of file
* E701: multiple statements on one line (colon)
* E901: SyntaxError: invalid syntax
* E902: TokenError: EOF in multi-line string
* F821: undefined name 'Foo'
* W293: blank line contains whitespace
* W606: 'async' and 'await' are reserved keywords starting with Python 3.7
```
Note to reviewers:
* In general we don't allow whitespace cleanups to existing code, but in order to allow for enabling Travis checking for these rules a few smaller whitespace cleanups had to made as part of this PR.
* Use [this `?w=1` link](https://github.com/bitcoin/bitcoin/pull/12987/files?w=1) to show a diff without whitespace changes.
Before this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
5 E266 too many leading '#' for block comment
4 E401 multiple imports on one line
6 E402 module level import not at top of file
5 E701 multiple statements on one line (colon)
1 F812 list comprehension redefines 'n' from line 159
4 F821 undefined name 'ConnectionRefusedError'
28 W293 blank line contains whitespace
```
After this commit:
```
$ flake8 -qq --statistics --ignore=B,C,E,F,I,N,W --select=E112,E113,E115,E116,E125,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,F401,E901,E902,F402,F404,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W292,W293,W504,W601,W602,W603,W604,W605,W606 .
$
```
Tree-SHA512: fc7d5e752298a50d4248afc620ee2c173135b4ca008e48e02913ac968e5a24a5fd5396926047ec62f1d580d537434ccae01f249bb2f3338fa59dc630bf97ca7a
Signed-off-by: pasta <pasta@dashboost.org>
2018-04-16 17:49:49 +02:00
|
|
|
|
2016-11-02 15:35:22 +01:00
|
|
|
################################################################################
|
|
|
|
# UI
|
|
|
|
################################################################################
|
|
|
|
|
|
|
|
USAGE = """
|
2019-01-03 10:20:22 +01:00
|
|
|
copyright_header.py - utilities for managing copyright headers of 'The Dash
|
2016-11-02 15:35:22 +01:00
|
|
|
Core developers' in repository source files.
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
$ ./copyright_header <subcommand>
|
|
|
|
|
|
|
|
Subcommands:
|
|
|
|
report
|
|
|
|
update
|
|
|
|
insert
|
|
|
|
|
|
|
|
To see subcommand usage, run them without arguments.
|
|
|
|
"""
|
|
|
|
|
|
|
|
SUBCOMMANDS = ['report', 'update', 'insert']
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
if len(sys.argv) == 1:
|
|
|
|
sys.exit(USAGE)
|
|
|
|
subcommand = sys.argv[1]
|
|
|
|
if subcommand not in SUBCOMMANDS:
|
|
|
|
sys.exit(USAGE)
|
|
|
|
if subcommand == 'report':
|
|
|
|
report_cmd(sys.argv)
|
|
|
|
elif subcommand == 'update':
|
|
|
|
update_cmd(sys.argv)
|
|
|
|
elif subcommand == 'insert':
|
|
|
|
insert_cmd(sys.argv)
|