Add todo.txt addons

This commit is contained in:
Ethan Lane 2025-04-29 17:53:01 +01:00
parent 3e0905a8dd
commit 317555179f
13 changed files with 1567 additions and 0 deletions

View 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)