Add todo.txt addons
This commit is contained in:
parent
3e0905a8dd
commit
317555179f
13 changed files with 1567 additions and 0 deletions
180
.todo.actions.d/graph/graph.py
Normal file
180
.todo.actions.d/graph/graph.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
#!/bin/sh
|
||||
# encoding: utf-8
|
||||
from __future__ import print_function
|
||||
''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" # '''
|
||||
''''which python >/dev/null 2>&1 && exec python "$0" "$@" # '''
|
||||
''''exec echo "Error: I can't find python anywhere" # '''
|
||||
# Author: Tim Pulver
|
||||
# Date: 2015
|
||||
# https://github.com/timpulver/todo.txt-graph
|
||||
#
|
||||
# Visualizes the completed tasks in todo.txt with horizontal bar graphs
|
||||
# Items are coloured green when there are more than 5 completed tasks per day,
|
||||
# grey otherwise to motivate completing stuff.
|
||||
# Have a look at Jerry Seinfeld's "Don't break the chain" methodoligy:
|
||||
# https://dontbreakthechain.com/what
|
||||
# You can change the threshold by adding this line to your todo.cfg file:
|
||||
# export TODOTXT_GRAPH_THRESHOLD=123
|
||||
#
|
||||
# Params:
|
||||
# - TODO_TXT_PATH: Where the todo.txt / done.txt file is located
|
||||
# - NUMBER_OF_DAYS: How many days should be visualized (optional, default: 7)
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import collections
|
||||
|
||||
TICK_CHAR = '■'
|
||||
DEFAULT_THRESHOLD = 5 # grey bar < DEFAULT_THRESHOLD >= green bar
|
||||
WIDTH = 60 # Graph width
|
||||
DONE = "done.txt"
|
||||
TODO = "todo.txt"
|
||||
|
||||
# try to use xrange, which is faster
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# Bash color codes and color helper class from:
|
||||
# https://github.com/emilerl/emilerl/blob/master/pybash/bash/__init__.py
|
||||
RESET = '\033[0m'
|
||||
CCODES = {
|
||||
'black' :'\033[0;30m',
|
||||
'blue' :'\033[0;34m',
|
||||
'green' :'\033[0;32m',
|
||||
'cyan' :'\033[0;36m',
|
||||
'red' :'\033[0;31m',
|
||||
'purple' :'\033[0;35m',
|
||||
'brown' :'\033[0;33m',
|
||||
'light_gray' :'\033[0;37m',
|
||||
'dark_gray' :'\033[0;30m',
|
||||
'light_blue' :'\033[0;34m',
|
||||
'light_green' :'\033[0;32m',
|
||||
'light_cyan' :'\033[0;36m',
|
||||
'light_red' :'\033[0;31m',
|
||||
'light_purple' :'\033[0;35m',
|
||||
'yellow' :'\033[0;33m',
|
||||
'white' :'\033[0;37m',
|
||||
}
|
||||
|
||||
# A helper class to colorize strings
|
||||
class Colors(object):
|
||||
def __init__(self, state = False):
|
||||
self.disabled = state
|
||||
|
||||
def disable(self):
|
||||
self.disabled = True
|
||||
|
||||
def enable(self):
|
||||
self.disabled = False
|
||||
|
||||
def __getattr__(self,key):
|
||||
if key not in CCODES.keys():
|
||||
raise AttributeError("Colors object has no attribute '%s'" % key)
|
||||
else:
|
||||
if self.disabled:
|
||||
return lambda x: x
|
||||
else:
|
||||
return lambda x: RESET + CCODES[key] + x + RESET
|
||||
|
||||
def __dir__(self):
|
||||
return self.__class__.__dict__.keys() + CCODES.keys()
|
||||
|
||||
|
||||
|
||||
# Graph based on:
|
||||
# https://github.com/mkaz/termgraph/blob/master/termgraph.py
|
||||
def print_blocks(label, count, step):
|
||||
# when nothing has been done, step equals 0 so the number
|
||||
# of blocks/ticks can't be computed but also doesn't
|
||||
# matter, hence set blocks = 0 in that case.
|
||||
blocks = int(count / step) if step > 0 else 0
|
||||
print("{}: ".format(label), end="")
|
||||
threshold = int(os.getenv('TODOTXT_GRAPH_THRESHOLD', DEFAULT_THRESHOLD))
|
||||
|
||||
c = Colors()
|
||||
|
||||
for i in range(blocks):
|
||||
if count >= threshold:
|
||||
sys.stdout.write(c.green(TICK_CHAR))
|
||||
else:
|
||||
sys.stdout.write(c.light_gray(TICK_CHAR))
|
||||
|
||||
# format the text (number of tasks done)
|
||||
if count < 10:
|
||||
print("{:>2.0f}".format(count))
|
||||
elif count < 100:
|
||||
print("{:>3.0f}".format(count))
|
||||
else:
|
||||
print("{:>4.0f}".format(count))
|
||||
|
||||
# Initialize dictionary with all days to also display days
|
||||
# where no tasks were completed
|
||||
def initialize_dic(cutoffDays = 7):
|
||||
base = datetime.datetime.today().date()
|
||||
dic = {(base - datetime.timedelta(days=x)).isoformat() : 0
|
||||
for x in range(0, cutoffDays)}
|
||||
return dic
|
||||
|
||||
# Based on Lately Addon:
|
||||
# https://github.com/emilerl/emilerl/tree/master/todo.actions.d
|
||||
def main(todo_file, done_file, cutoffDays = 7):
|
||||
lines = []
|
||||
files = [todo_file, done_file]
|
||||
for filename in files:
|
||||
with open(filename, 'r') as f:
|
||||
lines.extend(f.readlines())
|
||||
today = datetime.datetime.today()
|
||||
cutoff = today - datetime.timedelta(days=cutoffDays)
|
||||
dic = initialize_dic(cutoffDays)
|
||||
|
||||
for line in lines:
|
||||
m = re.match("x ([\d]{4}-[\d]{2}-[\d]{2}).*", line)
|
||||
if m is not None:
|
||||
done = m.group(1)
|
||||
year, month, day = m.group(1).split("-")
|
||||
completed = datetime.datetime(int(year),int(month),int(day))
|
||||
if completed >= cutoff:
|
||||
#print c.green(m.group(1)) + " " + c.blue(line.replace("x %s" % m.group(1), "").strip())
|
||||
if m.group(1) in dic:
|
||||
oldVal = dic.get(m.group(1), 1)
|
||||
dic.update({m.group(1): oldVal+1})
|
||||
else:
|
||||
dic[m.group(1)] = 1
|
||||
|
||||
# find out max value
|
||||
max = 0
|
||||
for key, value in dic.items():
|
||||
if value > max:
|
||||
max = value
|
||||
|
||||
maxf = float(max)
|
||||
step = maxf / WIDTH
|
||||
|
||||
orderedDic = collections.OrderedDict(sorted(dic.items()))
|
||||
|
||||
# display graph
|
||||
print()
|
||||
for key, value in orderedDic.items():
|
||||
print_blocks(key, value, step)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: graph.py [TODO_FILE] [DONE_FILE] <days back>")
|
||||
sys.exit(1)
|
||||
|
||||
if os.path.isfile(sys.argv[1]) and os.path.isfile(sys.argv[2]):
|
||||
if len(sys.argv) == 4:
|
||||
main(sys.argv[1], sys.argv[2], int(sys.argv[3]))
|
||||
else:
|
||||
main(sys.argv[1], sys.argv[2])
|
||||
else:
|
||||
print("Error: %s or %s doesn't exist" % (sys.argv[1], sys.argv[2]))
|
||||
sys.exit(1)
|
Loading…
Add table
Add a link
Reference in a new issue