Skip to content

Instantly share code, notes, and snippets.

@ScreamingHawk
Created May 27, 2025 21:15
Show Gist options
  • Save ScreamingHawk/94bf200e19d890f2cb04b33812595231 to your computer and use it in GitHub Desktop.
Save ScreamingHawk/94bf200e19d890f2cb04b33812595231 to your computer and use it in GitHub Desktop.
Update solidity natspec author by commit
#!/usr/bin/env python3
import os
import re
import subprocess
import sys
def get_author_display(author_name):
if author_name == 'ScreamingHawk':
return 'Michael Standen'
else:
return author_name
def get_git_authors(file_path):
"""
Returns a list of unique author display-names for `file_path`, ordered
by the timestamp of their first commit (oldest → newest).
"""
try:
# Get lines like "Alice|1610000000"
raw = subprocess.check_output(
['git', 'log', '--format=%aN|%at', '--', file_path],
stderr=subprocess.DEVNULL
).decode('utf-8')
except subprocess.CalledProcessError:
return []
first_commit_ts = {}
for line in raw.splitlines():
if '|' not in line:
continue
name, ts_str = line.split('|', 1)
name = name.strip()
try:
ts = int(ts_str.strip())
except ValueError:
continue
display = get_author_display(name)
# record the earliest (min) timestamp per display-name
if display not in first_commit_ts or ts < first_commit_ts[display]:
first_commit_ts[display] = ts
# sort by that timestamp
ordered = sorted(first_commit_ts.items(), key=lambda kv: kv[1])
return [get_author_display(author) for author, _ in ordered]
def process_file(file_path, pattern):
"""
Reads `file_path`, replaces any `/// @author xxx` line
with all unique committers, and writes back if changed.
"""
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
updated = False
new_lines = []
for line in lines:
m = pattern.match(line)
if m:
authors = get_git_authors(file_path)
if authors:
author_list = ', '.join(authors)
new_line = f'/// @author {author_list}\n'
if new_line != line:
line = new_line
updated = True
new_lines.append(line)
if updated:
with open(file_path, 'w', encoding='utf-8') as f:
f.writelines(new_lines)
print(f'Updated authors in: {file_path}')
def main(root_dir):
# Regex to match lines like: /// @author xxx
author_pattern = re.compile(r'^\s*/// @author xxx$')
for dirpath, dirnames, filenames in os.walk(root_dir):
# skip .git folder
if '.git' in dirnames:
dirnames.remove('.git')
for fname in filenames:
full_path = os.path.join(dirpath, fname)
# Only process text/code files; you can adjust the extensions if you like
if full_path.lower().endswith(('.sol')):
# Quick check to see if file contains the tag at all
with open(full_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
if '/// @author' in content:
process_file(full_path, author_pattern)
if __name__ == '__main__':
root = sys.argv[1] if len(sys.argv) > 1 else '.'
main(root)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment