Skip to content

Instantly share code, notes, and snippets.

@Miffyli
Last active September 16, 2022 17:28
Show Gist options
  • Save Miffyli/a02a9f851124feb730fadb37456554ff to your computer and use it in GitHub Desktop.
Save Miffyli/a02a9f851124feb730fadb37456554ff to your computer and use it in GitHub Desktop.
A very useful script for making sure everyone is credited accordingly
# A very useful script for giving credit where it is due: by replacing (most) variable names by the authors who wrote them.
# Requirements: pip install gitpython
# Usage: python3 assign-credits.py <input_file> <output_file>
# Example: python3 assign-credits.py ./src/main.py ./src/main-credited.py
# NOTE that this is an awful idea with an awful implementation. The "generated" code likely does not work
# (e.g. typing stuff is skipped, class attribute names are replaced). The "author" is decided by the current HEAD of git repo, and whoever
# defines the variable first will get the credits (hahaa dunno if even this is right).
# It is getting late and I am tired of typing so I will let Github Copilot write something for me.
# I am not sure if this is the best way to do it, but it works.
# I am not sure if this is the best way to do it, but it works.
# I am not sure if this is the best way to do it, but it works.
import ast
from argparse import ArgumentParser
import builtins
import re
import git
# Just to avoid messing with names we really do not want to mess with
SKIP_NAMES = set(dir(builtins) + ["_"])
def main(input_file, output_file):
# Get all variable names
# Höhöö we just pick the first time we see the variable, and treat it as "first time it is defined"
# Might be right, probably not.
source = open(input_file, "r").read()
root = ast.parse(source)
definition_names_to_linenos = {}
for node in ast.walk(root):
name = None
lineno = None
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
name = node.name
lineno = node.lineno
elif isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store):
name = node.id
lineno = node.lineno
if name and name not in SKIP_NAMES and name not in definition_names_to_linenos:
definition_names_to_linenos[name] = lineno
# Add arguments
if isinstance(node, ast.FunctionDef):
for arg in node.args.args:
name = arg.arg
lineno = arg.lineno
if name not in SKIP_NAMES and name not in definition_names_to_linenos:
definition_names_to_linenos[name] = lineno
# Find whoever last updated the lines that created the variables
repo = git.Repo(input_file, search_parent_directories=True)
blames = repo.blame("HEAD", input_file)
authors_to_id = {}
author_id_to_name_and_count = {}
line_to_author_id = []
for blame in blames:
commit, lines = blame
author = commit.author.name
if author not in authors_to_id:
author_id = len(authors_to_id)
authors_to_id[author] = author_id
# Come up with author name
clean_name = "".join(filter(lambda c: str.isalpha(c) or c == " ", author)).replace(" ", "_")
clean_name = clean_name.lower()
author_id_to_name_and_count[author_id] = [clean_name, 0]
author_id = authors_to_id[author]
for _ in range(len(lines)):
line_to_author_id.append(author_id)
# Rewrite source
new_source = source
for definition_name, definition_lineno in definition_names_to_linenos.items():
author_id = line_to_author_id[definition_lineno]
author_name, name_counter = author_id_to_name_and_count[author_id]
# Super clever camel casing
upper_mask = bin(name_counter)[2:].zfill(len(author_name))
new_variable_name = "".join((author_name[i].upper() if upper_mask[i] == "1" else author_name[i]) for i in range(len(author_name)))
new_source = re.sub("(\W){}(\W)".format(definition_name), r"\1{}\2".format(new_variable_name), new_source)
author_id_to_name_and_count[author_id][1] += 1
with open(output_file, "w") as f:
f.write(new_source)
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("input_file", help="Path to the file to update")
parser.add_argument("output_file", help="Path where to write the file")
args = parser.parse_args()
main(args.input_file, args.output_file)
@rnilva
Copy link

rnilva commented Dec 14, 2021

The best practical python obfuscator I've ever seen. 4.978/5

@alex-petrenko
Copy link

Love it :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment