├── .gitignore ├── README.rst ├── broc ├── __init__.py ├── cli.py ├── db.py └── git-hooks │ ├── __init__.py │ └── post_commit_hook.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/broc.db 55 | */broc.db 56 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Broc the Brownie Coder 3 | ====================== 4 | 5 | **Code for Brownie points** 6 | 7 | A program for getting Brownie points for making more commits. 8 | 9 | 10 | =========== 11 | How it work 12 | =========== 13 | * It sets git commit hooks in git repos (post-commit hook for now) 14 | * Git hooks are used for collecting stats every time you make a commit, or pull new code (not implemented yet) 15 | 16 | =============================== 17 | How it calculate brownie points 18 | =============================== 19 | * Each commit is worth 1 brownie points 20 | * Each brownie point for every file changed 21 | * 50 additions are worth 1 brownie points 22 | * 100 deletions are worth 1 brownie points 23 | * Every 20 chars in a commit message are worth 1 brownie point. I think it should be 50 24 | 25 | ========================= 26 | Why this scoring criteria 27 | ========================= 28 | 29 | * I think more and small commit messages are good 30 | * We can't always make small frequent commits, so no. of files/additions/deletions are also awarded 31 | * I think commit messages should tell a story, the story of how the software is evolving. Commit messages are permanent documentation, so more points for more verbose commit messages. Longer commit messages are the easiest way to earn more brownie points, so write as long as you can about what changes you have made, what's up your mind right now, etc etc. 32 | 33 | ============ 34 | Installation 35 | ============ 36 | :: 37 | pip install broc 38 | 39 | ===== 40 | Usage 41 | ===== 42 | 43 | :: 44 | Usage: broc [OPTIONS] COMMAND [ARGS]... 45 | 46 | the brownie coder. Code for brownie points 47 | 48 | Options: 49 | --help Show this message and exit. 50 | 51 | Commands: 52 | init Start calculating brownie points for present git repo 53 | stats Show today's stats (income and expenditure) 54 | spend Spend for because 55 | 56 | --------- 57 | broc init 58 | --------- 59 | Run this from within a git repository to start milking it for brownie points. broc adds local `post-commit` git hooks, so you can choose which of your git repos should earn you brownie points. 60 | 61 | --------------------- 62 | broc stats -e 63 | --------------------- 64 | email defaults to `git config --global --get user.email` 65 | 66 | Show today's stats. At the moment, they look something like this: Most commands accept `-e` for email address. `broc` keeps track of brownie points with the email address of the author of commit, so it's configurable and defaults to global git user's email. 67 | 68 | 69 | .. image:: https://i.imgur.com/5FNcsIS.png 70 | 71 | ------------------------------------------------------------------------------------------------ 72 | broc spend -p -m -e (defaults to `git config --global --get user.email`) 73 | ------------------------------------------------------------------------------------------------ 74 | :: 75 | broc spend -p 50 -m "playing Coll Of Duty 4. Because I don't have GTA V" 76 | 77 | 78 | Spend points for . 79 | 80 | 81 | ------------------------------------------------------------------------------- 82 | Why spend? Shouldn't it be like earn some badge after a threshold or something? 83 | ------------------------------------------------------------------------------- 84 | 85 | Read the inspiration section. 86 | 87 | =========== 88 | Inspiration 89 | =========== 90 | 91 | I work as a freelance software (mostly web) developer, but I often keep getting calls from startups/companies etc. A couple days back a rather cool startup contacted me with an offer to stay with them in a villa and code with a team while living with a team. It's a sort of fantasy actually. If I didn't have all these commitments I have, I would have said yes in a heartbeat. But my imagination flew. I was thinking how awesome it would be to have all the gadgets etc. Specially gaming consoles. I have a constant battle going for self-discipline, so it struck my head that 92 | :: 93 | "how I'll manage to write enough code while being able to play enough video games (without feeling bad about it)?" 94 | "Hmm..I would track my productivity" 95 | "How will I do that and utilize it for playing games?" 96 | "I'll convert my code into currency, and spend it on playing games" 97 | "Cool! But I wonder if there is some tool for doing that, I mean their are things for tracking time and all but..." 98 | "Wtf dude? I don't need people to write software for me. I write software for people" 99 | 100 | "But I am not joining the startup, am still freelancing, shall I build it?" 101 | "Let's build it so it would help us write good code too." 102 | 103 | Then I thought of what problems I face when writing code, improvements I wanna make in my coding life. More often than not, I forget to commit my code. Although this is very rare, what happens pretty often are commit messages like "Some change", "Bug fix", "Change in this file" etc etc. I saw a good opportunity here to encourage improving my committing habits, so after few hours of work, here I am. 104 | 105 | ============ 106 | What's next? 107 | ============ 108 | I don't know. 'm gonna use this system daily, and if it proves to be worth the effort, I'll improve and enhance it. I have some ideas about having a pretty interface with a built-in web server (I wanted to try react for a long time for something more than dumb tutorials, this could be it), may be a cloud app too where everybody push showcase their brownie points, earn some badges may be. Oh wait! https://coderwall.com/ _Coderwall! 109 | 110 | -------------------------------------------------------------------------------- /broc/__init__.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from shell import shell 4 | 5 | from cli import cli 6 | 7 | BROC_ROOT_DIR = path.dirname(path.realpath(__file__)) 8 | BROC_HOOKS_ROOT_DIR = BROC_ROOT_DIR + '/git-hooks' 9 | 10 | BROC_HOOKS_PATHS = { 11 | 'post-commit': BROC_HOOKS_ROOT_DIR + '/post_commit_hook.py' 12 | } 13 | 14 | GIT_CONFIG = { 15 | 'email': (lambda: shell('git config --global --get user.email').output()[0])(), 16 | 'author': (lambda: shell('git config --global --get user.name').output()[0])() 17 | } 18 | 19 | get_global_git_email = lambda: shell('git config --global --get user.email').output()[0] 20 | 21 | is_in_git_repo = lambda : len(shell('git rev-parse').errors()) == 0 22 | get_git_root = lambda : shell('git rev-parse --show-toplevel').output()[0] if is_in_git_repo() else False 23 | get_git_hooks_dir = lambda : get_git_root() + '/.git/hooks' if get_git_root() else False 24 | 25 | def link_file_in_dir_as(hook_name, dir_path, link_name): 26 | hook_path = BROC_HOOKS_PATHS[hook_name] 27 | link_path = dir_path + '/' + link_name 28 | 29 | ln = shell('ln -s {0} {1}'.format(hook_path, link_path)) 30 | chmod = shell('chmod +x {0}'.format(hook_path)) 31 | return ln.code 32 | 33 | def calculate_brownie_points(commit_msg_length, num_files_changed, additions, deletions): 34 | commit_score = 1 + int(commit_msg_length/20) 35 | additions_score = int(additions/50) 36 | deletions_score = int(deletions/100) 37 | 38 | return commit_score + additions_score + deletions_score + int(num_files_changed) 39 | -------------------------------------------------------------------------------- /broc/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | import broc 3 | import db 4 | 5 | @click.group() 6 | def cli(): 7 | """the brownie coder. Code for brownie points""" 8 | pass 9 | 10 | @cli.command() 11 | def init(): 12 | """Start calculating brownie points for present git repo""" 13 | git_hooks_dir = broc.get_git_hooks_dir() 14 | if git_hooks_dir: 15 | broc.link_file_in_dir_as('post-commit', git_hooks_dir, 'post-commit') 16 | return click.echo(click.style("Created git hook in present git repo", fg='green')) 17 | 18 | return click.echo(click.style("Not a git repo", fg='red')) 19 | 20 | @cli.command() 21 | @click.option('-p', default=0, help="Number of points you spent") 22 | @click.option('-m', default="because I wanted to. Biatch!", help="Reason for which you spent those poor brownie points. You spent the brownie points :'(") 23 | @click.option('-e', help="Email address of the user who want to spend brownie points. Defaults to global git user", default='') 24 | def spend(p, m, e): 25 | """Spend for because """ 26 | points = p 27 | msg = m 28 | email = broc.GIT_CONFIG['email'] if not e else e 29 | 30 | if points > 0: 31 | db.add_spend_entry(points, msg, email) 32 | 33 | points = click.style(str(points), fg='red') 34 | msg = click.style(msg, fg='red') 35 | email = click.style(email, fg='cyan') 36 | click.echo("{2} spent {0} points {1}".format(points, msg, email)) 37 | 38 | 39 | @cli.command() 40 | @click.option('-e', help="Email address of the user who want to spend brownie points. Defaults to global git user", default='') 41 | def stats(e): 42 | """Show today's stats (income and expenditure)""" 43 | email = e if e else broc.GIT_CONFIG['email'] 44 | 45 | balance = db.get_total_brownie_points(email) 46 | today_income, today_expenditure = db.get_todays_stats(email) 47 | 48 | earned = click.style('Today Earned: ' + str(today_income), fg='green') 49 | spent = click.style('Today Spent: ' + str(today_expenditure), fg='red') 50 | total = click.style('Brownie Balance: ', fg='cyan') 51 | balance = click.style(str(balance), fg=(lambda b: 'green' if b > 0 else 'red')(balance)) 52 | 53 | click.echo(earned) 54 | click.echo(spent) 55 | click.echo(total + balance) 56 | -------------------------------------------------------------------------------- /broc/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from datetime import datetime 3 | from calendar import timegm 4 | 5 | import broc 6 | 7 | def create_db_if_not_exists(cursor): 8 | cursor.execute("""CREATE TABLE IF NOT EXISTS income 9 | (id INTEGER PRIMARY KEY, 10 | repository TEXT, 11 | commit_hash TEXT, 12 | commit_message TEXT, 13 | points_earned INTEGER, 14 | created_at TEXT, 15 | author_email TEXT, 16 | author_name TEXT 17 | ) 18 | """) 19 | 20 | cursor.execute("""CREATE TABLE IF NOT EXISTS expense 21 | (id INTEGER PRIMARY KEY, 22 | reason TEXT, 23 | points_spent Integer, 24 | created_at TEXT, 25 | author_email TEXT 26 | ) 27 | """) 28 | 29 | 30 | def db_operation(func): 31 | """Decorator for functions who need a cursor and connection and close them after work""" 32 | def wrapper(*args): 33 | conn = sqlite3.connect(broc.BROC_ROOT_DIR + "/broc.db") 34 | cursor = conn.cursor() 35 | create_db_if_not_exists(cursor) 36 | 37 | return func(*args, conn=conn, cursor=cursor) 38 | return wrapper 39 | 40 | @db_operation 41 | def add_commit_entry(repo, hash, msg, points_earned, created_at, author_name, author_email, conn, cursor): 42 | query = "INSERT INTO income (repository, commit_hash, commit_message, points_earned, created_at, author_email, author_name) VALUES (?, ?, ?, ?, ?, ?, ?)" 43 | 44 | cursor.execute(query, (repo, hash, msg, points_earned, created_at, author_email, author_name, )) 45 | conn.commit() 46 | conn.close() 47 | 48 | @db_operation 49 | def add_spend_entry(points_spent, message, email, conn, cursor): 50 | created_at = datetime.now() 51 | 52 | query = "INSERT INTO expense (points_spent, reason, created_at, author_email) VALUES (?, ?, ?, ?)" 53 | 54 | cursor.execute(query, (points_spent, message, created_at, email)) 55 | conn.commit() 56 | conn.close() 57 | 58 | @db_operation 59 | def get_total_brownie_points(email, conn, cursor): 60 | """Get total brownie points remaining in the account at the moment""" 61 | total_income = cursor.execute('SELECT sum(points_earned) FROM income WHERE author_email = ?', (email,)).fetchall()[0][0] or 0 62 | total_expenditure = cursor.execute('SELECT sum(points_spent) FROM expense WHERE author_email = ?', (email,)).fetchall()[0][0] or 0 63 | 64 | balance = total_income - total_expenditure 65 | 66 | conn.commit() 67 | conn.close() 68 | 69 | return balance 70 | 71 | @db_operation 72 | def get_todays_stats(email, conn, cursor): 73 | income = cursor.execute("SELECT sum(points_earned) FROM income WHERE created_at >= date('now') and author_email = ?", (email, )).fetchall()[0][0] or 0 74 | expenditure = cursor.execute("SELECT sum(points_spent) FROM expense WHERE created_at >= date('now') and author_email = ?", (email, )).fetchall()[0][0] or 0 75 | 76 | conn.commit() 77 | conn.close() 78 | 79 | return (income, expenditure, ) 80 | -------------------------------------------------------------------------------- /broc/git-hooks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitspook/broc/c7be357bf5532cbe8b52a57cce550ffa7392cae1/broc/git-hooks/__init__.py -------------------------------------------------------------------------------- /broc/git-hooks/post_commit_hook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import broc 3 | from click import echo, style 4 | from shell import shell 5 | from broc import db 6 | from datetime import datetime 7 | 8 | 9 | repo = broc.get_git_root() 10 | 11 | (hash, msg, email, author, timestamp) = shell('git log -1 --pretty=format:%H|||%s|||%ae|||%an|||%at').output()[0].split('|||') 12 | 13 | created_at = datetime.fromtimestamp(float(timestamp)) 14 | 15 | stats_pairs = map( 16 | lambda s: s.split('\t')[:2], 17 | shell('git diff HEAD~ --numstat').output() 18 | ) 19 | 20 | 21 | if not stats_pairs: 22 | (additions, deletions, num_files_changed) = 0, 0, 0 23 | else: 24 | num_files_changed = len(stats_pairs) 25 | # stats_pairs = [['11', '13'], ['22', '0'], ['22', '0'], ['22', '0']] 26 | # additions = [int(i[0]) for i in stats_pairs] 27 | # total additions = reduce(lambda x,y: x+y, [int(i[0]) for i in stats_pairs]) 28 | (additions, deletions) = (reduce(lambda x,y: x+y, [int(i[0]) for i in stats_pairs]), #sum all the additions 29 | reduce(lambda x,y: x+y, [int(i[1]) for i in stats_pairs])) #sum all the deletions 30 | 31 | brownie_points = broc.calculate_brownie_points(len(msg), num_files_changed, additions, deletions) 32 | 33 | db.add_commit_entry(repo, hash, msg, brownie_points, created_at, author, email) 34 | 35 | echo("Brownie points earned: " + style(str(brownie_points), fg='green')) 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | try: 4 | long_description = open("README.rst").read() 5 | except IOError: 6 | long_description = "" 7 | 8 | setup( 9 | name="broc", 10 | version="0.1.3", 11 | description="Broc: the brownie coder. Code for brownie points", 12 | license="MIT", 13 | author="Charanjit Singh", 14 | author_email="ckhabra@gmail.com", 15 | packages=['broc', 'broc.git-hooks'], 16 | install_requires=[ 17 | 'Click', 18 | 'shell' 19 | ], 20 | entry_points=''' 21 | [console_scripts] 22 | broc=broc:cli 23 | ''', 24 | long_description=long_description, 25 | classifiers=[ 26 | "Programming Language :: Python", 27 | "Programming Language :: Python :: 2.7", 28 | ] 29 | ) 30 | --------------------------------------------------------------------------------