Compare commits
107 Commits
v2.1.0
...
wip-weekly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c963ba41ae | ||
|
|
b4ef59b637 | ||
|
|
90b80268d3 | ||
|
|
7dde1fbab6 | ||
|
|
b3e0f791a7 | ||
|
|
732e45aeb1 | ||
|
|
a53bb81b4c | ||
|
|
29a470f97d | ||
|
|
6ab0004fe8 | ||
|
|
15acb054f5 | ||
|
|
5f3dadee5c | ||
|
|
20c6f44784 | ||
|
|
9bab224a29 | ||
|
|
4d3b7472ff | ||
|
|
680e93e737 | ||
|
|
477738828f | ||
|
|
b9f95633dc | ||
|
|
df1e2eb7cf | ||
|
|
a4e68f9c3f | ||
|
|
3028de42a8 | ||
|
|
8fceae171d | ||
|
|
9898e7df3f | ||
|
|
42e1a658d6 | ||
|
|
a075adb4ec | ||
|
|
f1caecec4e | ||
|
|
078c69496f | ||
|
|
01a250c702 | ||
|
|
1f17672215 | ||
|
|
40e0da5108 | ||
|
|
56dfae0486 | ||
|
|
8549eef46b | ||
|
|
8567a90e4c | ||
|
|
55f45e8515 | ||
|
|
03ccc73703 | ||
|
|
a822560d44 | ||
|
|
d860c2c36e | ||
|
|
7f954d73ae | ||
|
|
8e864568a9 | ||
|
|
df4f9150cf | ||
|
|
5cc988102d | ||
|
|
3a0fd43270 | ||
|
|
1a6ff81e28 | ||
|
|
5491e458a2 | ||
|
|
5789f5b4c2 | ||
|
|
b17cb11ec6 | ||
|
|
0b3d9109de | ||
|
|
ad40ef0f18 | ||
|
|
31216fe365 | ||
|
|
7f5c8fb3e1 | ||
|
|
3e7b60abcd | ||
|
|
cc3e5f73aa | ||
|
|
52604ebf78 | ||
|
|
bbe153b9bb | ||
|
|
ec54a032cb | ||
|
|
758cdc5551 | ||
|
|
02dc030225 | ||
|
|
7b769b2eea | ||
|
|
39ee9ab045 | ||
|
|
1e5902d0e2 | ||
|
|
3df7497287 | ||
|
|
07bb979d43 | ||
|
|
7e04849a4f | ||
|
|
f8b2646b92 | ||
|
|
02980ae7ee | ||
|
|
12bbf8fe67 | ||
|
|
ab78607506 | ||
|
|
6be78ca5fa | ||
|
|
9ab77253db | ||
|
|
cf3c5312bf | ||
|
|
37a7bb0e8a | ||
|
|
ed8e8e24d9 | ||
|
|
d6f00ca42f | ||
|
|
a03a3bf66b | ||
|
|
448cecb91d | ||
|
|
ee59233c36 | ||
|
|
f55f5e8b5f | ||
|
|
d508ed9dee | ||
|
|
62f3313ff9 | ||
|
|
6bc374c5f2 | ||
|
|
87959a8aa8 | ||
|
|
3e9d40ebd7 | ||
|
|
6bc05000d9 | ||
|
|
717f052f13 | ||
|
|
4ee8c332ed | ||
|
|
88caf44e9e | ||
|
|
db66767170 | ||
|
|
f8f8e83c40 | ||
|
|
2648bb047c | ||
|
|
bbff2d13bb | ||
|
|
e4c7979888 | ||
|
|
47c7ba75b3 | ||
|
|
2bd2e9f7bd | ||
|
|
f37cedc7ca | ||
|
|
7b2c9f080a | ||
|
|
98646a575a | ||
|
|
e6649e6293 | ||
|
|
eb61752708 | ||
|
|
5683490c0e | ||
|
|
20e6892775 | ||
|
|
fd9b002ce1 | ||
|
|
7736e6b4fa | ||
|
|
586abe8282 | ||
|
|
7bd6696540 | ||
|
|
b5a03cfee5 | ||
|
|
d7b9f87994 | ||
|
|
959598416f | ||
|
|
25c6505007 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
VERSION-FILE
|
||||||
|
tests/test-results
|
||||||
|
tests/trash\ directory.*
|
||||||
10
.todo.actions.d/README
Normal file
10
.todo.actions.d/README
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
TODO.TXT CLI Add-ons
|
||||||
|
|
||||||
|
adda (symlink to aa for fewer keystrokes)
|
||||||
|
* Adds a task and prioritizes it A in one shot
|
||||||
|
|
||||||
|
addx (symlink ax for fewer keystrokes)
|
||||||
|
* Adds a task and marks it as complete in one shot
|
||||||
|
|
||||||
|
birdseye (requires Python in path and birdseye.py file)
|
||||||
|
* Generates a textual report of open and complete tasks in all contexts and projects
|
||||||
20
.todo.actions.d/adda
Executable file
20
.todo.actions.d/adda
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
action=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
[ "$action" = "usage" ] && {
|
||||||
|
echo " Add and prioritize A:"
|
||||||
|
curcmd=`basename $0`
|
||||||
|
echo " $curcmd \"THING I NEED TO DO +project @context\""
|
||||||
|
echo " Add an item and prioritize it A in one step"
|
||||||
|
echo ""
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
if "$TODO_SH" command add "$@"; then
|
||||||
|
# figure out the line of what we just added, and prioritize it A
|
||||||
|
line=`sed -n '$ =' "$TODO_FILE"`
|
||||||
|
echo "$line"
|
||||||
|
"$TODO_SH" command pri "$line" A
|
||||||
|
fi
|
||||||
20
.todo.actions.d/addx
Executable file
20
.todo.actions.d/addx
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
action=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
[ "$action" = "usage" ] && {
|
||||||
|
echo " Add and do:"
|
||||||
|
curcmd=`basename $0`
|
||||||
|
echo " $curcmd \"THING I DID +project @context\""
|
||||||
|
echo " Add an item and mark it as done in one step"
|
||||||
|
echo ""
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
if "$TODO_SH" command add "$@"; then
|
||||||
|
# figure out the line of what we just added, and prioritize it A
|
||||||
|
line=`sed -n '$ =' "$TODO_FILE"`
|
||||||
|
echo "$line"
|
||||||
|
"$TODO_SH" command do "$line"
|
||||||
|
fi
|
||||||
16
.todo.actions.d/birdseye
Executable file
16
.todo.actions.d/birdseye
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
action=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
[ "$action" = "usage" ] && {
|
||||||
|
echo " Bird's eye report:"
|
||||||
|
echo " birdseye"
|
||||||
|
echo " generates a textual report of pending and completed tasks in all projects and contexts"
|
||||||
|
echo ""
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
[ "$action" = "birdseye" ] && {
|
||||||
|
python ${TODO_ACTIONS_DIR}/birdseye.py "$TODO_FILE" "$DONE_FILE"
|
||||||
|
}
|
||||||
202
.todo.actions.d/birdseye.py
Executable file
202
.todo.actions.d/birdseye.py
Executable file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
""" TODO.TXT Bird's Eye View Reporter
|
||||||
|
USAGE:
|
||||||
|
birdseye.py [todo.txt] [done.txt]
|
||||||
|
|
||||||
|
USAGE NOTES:
|
||||||
|
Expects two text files as parameters, each of which formatted as follows:
|
||||||
|
- One todo per line, ie, "call Mom"
|
||||||
|
- with an optional project association indicated as such: "+projectname"
|
||||||
|
- with the context in which the tasks should be completed, indicated as such: "@context"
|
||||||
|
- with the task priority optionally listed at the front of the line, in parens, ie, "(A)"
|
||||||
|
|
||||||
|
For example, 4 lines of todo.txt might look like this:
|
||||||
|
|
||||||
|
+garagesale @phone schedule Goodwill pickup
|
||||||
|
(A) @phone Tell Mom I love her
|
||||||
|
+writing draft Great American Novel
|
||||||
|
(B) smell the roses
|
||||||
|
|
||||||
|
The done.txt file is a list of completed todos from todo.txt.
|
||||||
|
|
||||||
|
See more on todo.txt here:
|
||||||
|
http://todotxt.com
|
||||||
|
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
Displays a list of:
|
||||||
|
- working projects and their percentage complete
|
||||||
|
- contexts in which open todos exist
|
||||||
|
- contexts and projects with tasks that have been prioritized
|
||||||
|
- projects which are completely done (don't have any open todos)
|
||||||
|
|
||||||
|
CHANGELOG:
|
||||||
|
2016.03.17 - Update for Python 3. Tx, JonathanReeve!
|
||||||
|
2006.07.29 - Now supports p:, p- and + project notation. Tx, Pedro!
|
||||||
|
2006.05.02 - Released
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
__version__ = "1.2"
|
||||||
|
__date__ = "2006/05/02"
|
||||||
|
__updated__ = "2016/03/17"
|
||||||
|
__author__ = "Gina Trapani (ginatrapani@gmail.com)"
|
||||||
|
__copyright__ = "Copyright 2006 - 2016, Gina Trapani"
|
||||||
|
__license__ = "GPL"
|
||||||
|
__history__ = """
|
||||||
|
1.2 - Update for Python 3. Tx, JonathanReeve!
|
||||||
|
1.1 - Now supports p:, p- and + project notation. Tx, Pedro!
|
||||||
|
1.0 - Released.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print("USAGE: %s [todo.txt] [done.txt]" % (sys.argv[0], ))
|
||||||
|
|
||||||
|
def printTaskGroups(title, taskDict, priorityList, percentages):
|
||||||
|
print("")
|
||||||
|
print("%s"% (title,))
|
||||||
|
separator("-")
|
||||||
|
if not taskDict:
|
||||||
|
print("No items to list.")
|
||||||
|
else:
|
||||||
|
# sort the dictionary by value
|
||||||
|
# http://python.fyxm.net/peps/pep-0265.html
|
||||||
|
items = [(v, k) for k, v in list(taskDict.items())]
|
||||||
|
items.sort()
|
||||||
|
items.reverse() # so largest is first
|
||||||
|
items = [(k, v) for v, k in items]
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if item[0] in priorityList:
|
||||||
|
if item[0] not in percentages:
|
||||||
|
printTaskGroup(item, -1, "*")
|
||||||
|
else:
|
||||||
|
printTaskGroup(item, percentages[item[0]], "*")
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if item[0] not in priorityList:
|
||||||
|
if item[0] not in percentages:
|
||||||
|
printTaskGroup(item, -1, " ")
|
||||||
|
else:
|
||||||
|
printTaskGroup(item, percentages[item[0]], " ")
|
||||||
|
|
||||||
|
def printTaskGroup(p, pctage, star):
|
||||||
|
if pctage > -1:
|
||||||
|
progressBar = ""
|
||||||
|
numStars = int(pctage//10)
|
||||||
|
progressBar = "=" * numStars
|
||||||
|
numSpaces = 10 - numStars
|
||||||
|
for n in range(numSpaces):
|
||||||
|
progressBar += " "
|
||||||
|
|
||||||
|
if pctage > 9:
|
||||||
|
displayTotal = " %d%%"% (pctage, );
|
||||||
|
else:
|
||||||
|
displayTotal = " %d%%"% (pctage, );
|
||||||
|
print("%s %s [%s] %s (%d todos)"% (star, displayTotal, progressBar, p[0], p[1],))
|
||||||
|
else:
|
||||||
|
print("%s %s (%d todos)"% (star, p[0], p[1], ))
|
||||||
|
|
||||||
|
def separator(c):
|
||||||
|
sep = ""
|
||||||
|
sep = c * 42
|
||||||
|
print(sep)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
# make sure you have all your args
|
||||||
|
if len(argv) < 2:
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
# process todo.txt
|
||||||
|
try:
|
||||||
|
f = open (argv[0], "r")
|
||||||
|
projects = {}
|
||||||
|
contexts = {}
|
||||||
|
projectPriority = []
|
||||||
|
contextPriority = []
|
||||||
|
for line in f:
|
||||||
|
prioritized = False
|
||||||
|
words = line.split()
|
||||||
|
if words and words[0].startswith("("):
|
||||||
|
prioritized = True
|
||||||
|
for word in words:
|
||||||
|
if word[0:2] == "p:" or word[0:2] == "p-" or word[0:1] == "+":
|
||||||
|
if word not in projects:
|
||||||
|
projects[word] = 1
|
||||||
|
else:
|
||||||
|
projects[word] = projects.setdefault(word,0) + 1
|
||||||
|
if prioritized:
|
||||||
|
projectPriority.append(word)
|
||||||
|
if word[0:1] == "@":
|
||||||
|
if word not in contexts:
|
||||||
|
contexts[word] = 1
|
||||||
|
else:
|
||||||
|
contexts[word] = contexts.setdefault(word, 0) + 1
|
||||||
|
if prioritized:
|
||||||
|
contextPriority.append(word)
|
||||||
|
f.close()
|
||||||
|
except IOError:
|
||||||
|
print("ERROR: The file named %s could not be read."% (argv[0], ))
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
# process done.txt
|
||||||
|
try:
|
||||||
|
completedTasks = {}
|
||||||
|
f = open (argv[1], "r")
|
||||||
|
for line in f:
|
||||||
|
words = line.split()
|
||||||
|
for word in words:
|
||||||
|
if word[0:2] == "p:" or word[0:2] == "p-" or word[0:1] == "+":
|
||||||
|
if word not in completedTasks:
|
||||||
|
completedTasks[word] = 1
|
||||||
|
else:
|
||||||
|
completedTasks[word] = completedTasks.setdefault(word, 0) + 1
|
||||||
|
f.close()
|
||||||
|
except IOError:
|
||||||
|
print("ERROR: The file named %s could not be read."% (argv[1], ))
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
# calculate percentages
|
||||||
|
projectPercentages = {}
|
||||||
|
for project in projects:
|
||||||
|
openTasks = projects[project]
|
||||||
|
if project in completedTasks:
|
||||||
|
closedTasks = completedTasks[project]
|
||||||
|
else:
|
||||||
|
closedTasks = 0
|
||||||
|
totalTasks = openTasks + closedTasks
|
||||||
|
projectPercentages[project] = (closedTasks*100) / totalTasks
|
||||||
|
|
||||||
|
# get projects all done
|
||||||
|
projectsWithNoIncompletes = {}
|
||||||
|
for task in completedTasks:
|
||||||
|
if task not in projects:
|
||||||
|
projectsWithNoIncompletes[task] = 0
|
||||||
|
|
||||||
|
# print out useful info
|
||||||
|
#print "TODO.TXT Bird's Eye View Report %s"% ( datetime.date.today().isoformat(), )
|
||||||
|
print("")
|
||||||
|
print("TODO.TXT Bird's Eye View Report")
|
||||||
|
|
||||||
|
separator("=")
|
||||||
|
|
||||||
|
printTaskGroups("Projects with Open TODOs", projects, projectPriority, projectPercentages)
|
||||||
|
printTaskGroups("Contexts with Open TODOs", contexts, contextPriority, projectPercentages)
|
||||||
|
printTaskGroups("Completed Projects (No open TODOs)", projectsWithNoIncompletes, projectPriority, projectPercentages)
|
||||||
|
print("")
|
||||||
|
print("* Projects and contexts with an asterisk next to them denote prioritized tasks.")
|
||||||
|
print("Project with prioritized tasks are listed first, then sorted by number of open todos.")
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
||||||
199
.todo.actions.d/weeklyreview.py
Normal file
199
.todo.actions.d/weeklyreview.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
""" TODO.TXT Weekly Review
|
||||||
|
USAGE:
|
||||||
|
weeklyreview.py [todo.txt] [done.txt] [projects.txt]
|
||||||
|
|
||||||
|
USAGE NOTES:
|
||||||
|
Expects three text files as parameters:
|
||||||
|
1 & 2. Properly-formatted todo.txt and done.txt files.
|
||||||
|
3. A projects.txt file which lists one project per line, and any number of #goals associated with it.
|
||||||
|
|
||||||
|
See more on todo.txt here:
|
||||||
|
http://todotxt.com
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
Displays a count of how many tasks were completed associated with a goal.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
__version__ = "1.2"
|
||||||
|
__date__ = "2016/03/17"
|
||||||
|
__updated__ = "2016/03/17"
|
||||||
|
__author__ = "Gina Trapani (ginatrapani@gmail.com)"
|
||||||
|
__copyright__ = "Copyright 2016, Gina Trapani"
|
||||||
|
__license__ = "GPL"
|
||||||
|
__history__ = """
|
||||||
|
0.1 - WIP
|
||||||
|
"""
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print("USAGE: %s [todo.txt] [done.txt] [projects.txt]" % (sys.argv[0], ))
|
||||||
|
|
||||||
|
def separator(c, r=42):
|
||||||
|
sep = ""
|
||||||
|
sep = c * r
|
||||||
|
print(sep)
|
||||||
|
|
||||||
|
def printTitle(text):
|
||||||
|
print("")
|
||||||
|
r = len(text)
|
||||||
|
print(text)
|
||||||
|
separator("=", r)
|
||||||
|
|
||||||
|
def printHeader(text):
|
||||||
|
r = len(text)
|
||||||
|
print("")
|
||||||
|
print(text)
|
||||||
|
separator("-", r)
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
# make sure you have all your args
|
||||||
|
if len(argv) < 3:
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
goal_projects = getGoalProjects(argv)
|
||||||
|
#print(goal_projects)
|
||||||
|
|
||||||
|
last_7_days = getLast7Days()
|
||||||
|
#print(last_7_days)
|
||||||
|
|
||||||
|
last_7_days_of_completions = getLast7DaysOfCompletions(argv, last_7_days)
|
||||||
|
#print(last_7_days_of_completions)
|
||||||
|
|
||||||
|
project_completions = getProjectCompletions(argv, last_7_days_of_completions)
|
||||||
|
#print(project_completions)
|
||||||
|
|
||||||
|
goal_completions = getGoalCompletions(goal_projects, project_completions)
|
||||||
|
# print(goal_completions)
|
||||||
|
|
||||||
|
# Print report: For each item in goal_projects, print the goal, the number of tasks completed,
|
||||||
|
# then each project and the number of tasks completed
|
||||||
|
printTitle("Weekly Review for the past 7 days")
|
||||||
|
|
||||||
|
goals_not_moved = []
|
||||||
|
goals_moved = []
|
||||||
|
for goal in goal_projects:
|
||||||
|
total_done = 0
|
||||||
|
if goal in goal_completions:
|
||||||
|
total_done = len(goal_completions[goal])
|
||||||
|
goal_header = goal + " - " + str(total_done) + " done"
|
||||||
|
if total_done > 0:
|
||||||
|
printHeader(goal_header)
|
||||||
|
for project in goal_projects[goal]:
|
||||||
|
if project in project_completions:
|
||||||
|
print(project + " - " + str(len(project_completions[project])) + " done" )
|
||||||
|
for task in project_completions[project]:
|
||||||
|
print(" " + task.strip())
|
||||||
|
goals_moved.append(goal)
|
||||||
|
else:
|
||||||
|
goals_not_moved.append(goal)
|
||||||
|
|
||||||
|
# Print a list of goals that had no movement
|
||||||
|
if len(goals_not_moved) > 0:
|
||||||
|
printTitle("Goals with no progress")
|
||||||
|
for goal in goals_not_moved:
|
||||||
|
print(goal)
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
print("")
|
||||||
|
summary = str(len(last_7_days_of_completions)) + " completed tasks moved " + str(len(goals_moved)) + " out of " + str(len(goal_projects)) + " goals forward."
|
||||||
|
separator("-", len(summary))
|
||||||
|
print(summary)
|
||||||
|
separator("-", len(summary))
|
||||||
|
|
||||||
|
# Warnings
|
||||||
|
crossCheckCompletedProjects(project_completions, goal_projects)
|
||||||
|
|
||||||
|
|
||||||
|
# Return an array of goals with total tasks completed.
|
||||||
|
def getGoalCompletions(goal_projects, project_completions):
|
||||||
|
goal_completions = {}
|
||||||
|
goals = goal_projects.keys()
|
||||||
|
for goal in goal_projects:
|
||||||
|
for project in project_completions:
|
||||||
|
if project in goal_projects[goal]:
|
||||||
|
if goal not in goal_completions:
|
||||||
|
goal_completions[goal] = project_completions[project]
|
||||||
|
else:
|
||||||
|
goal_completions[goal] = goal_completions[goal] + project_completions[project]
|
||||||
|
return goal_completions
|
||||||
|
|
||||||
|
# Return the goal/project list as an array of arrays goalProjects[goal] = projects[]
|
||||||
|
def getGoalProjects(argv):
|
||||||
|
try:
|
||||||
|
goal_projects = {}
|
||||||
|
f = open (argv[2], "r")
|
||||||
|
for line in f:
|
||||||
|
words = line.split()
|
||||||
|
for word in words:
|
||||||
|
# Project
|
||||||
|
if word[0:1] == "+":
|
||||||
|
current_project = word
|
||||||
|
# Goal
|
||||||
|
if word[0:1] == "#":
|
||||||
|
if word not in goal_projects:
|
||||||
|
goal_projects[word] = [current_project];
|
||||||
|
else:
|
||||||
|
goal_projects[word].append(current_project)
|
||||||
|
f.close()
|
||||||
|
return goal_projects
|
||||||
|
except IOError:
|
||||||
|
print("ERROR: The file named %s could not be read."% (argv[1], ))
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
# Get the last 7 days as an array of todo.txt-formatted dates.
|
||||||
|
def getLast7Days():
|
||||||
|
today = datetime.date.today()
|
||||||
|
last7Days = []
|
||||||
|
for d in range(8):
|
||||||
|
day_this_week = today - datetime.timedelta(days=d)
|
||||||
|
last7Days.append(day_this_week.strftime('%Y-%m-%d'))
|
||||||
|
return last7Days
|
||||||
|
|
||||||
|
# Return last 7 days of completed tasks from done.txt
|
||||||
|
def getLast7DaysOfCompletions(argv, last_7_days):
|
||||||
|
try:
|
||||||
|
last_7_days_of_completions = []
|
||||||
|
f = open (argv[1], "r")
|
||||||
|
for line in f:
|
||||||
|
words = line.split()
|
||||||
|
if len(words) > 2 and words[1] in last_7_days:
|
||||||
|
last_7_days_of_completions.append(line)
|
||||||
|
f.close()
|
||||||
|
return last_7_days_of_completions
|
||||||
|
except IOError:
|
||||||
|
print("ERROR: The file named %s could not be read."% (argv[1], ))
|
||||||
|
usage()
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
# Return an array of projects with the total tasks completed.
|
||||||
|
def getProjectCompletions(argv, last_7_days_of_completions):
|
||||||
|
project_completions = {}
|
||||||
|
for task in last_7_days_of_completions:
|
||||||
|
words = task.split()
|
||||||
|
for word in words:
|
||||||
|
if word[0:2] == "p:" or word[0:2] == "p-" or word[0:1] == "+":
|
||||||
|
if word not in project_completions:
|
||||||
|
project_completions[word] = [task]
|
||||||
|
else:
|
||||||
|
project_completions[word].append(task)
|
||||||
|
return project_completions
|
||||||
|
|
||||||
|
def crossCheckCompletedProjects(project_completions, goal_projects):
|
||||||
|
for project in project_completions:
|
||||||
|
goal_in_project = False
|
||||||
|
for goal in goal_projects:
|
||||||
|
if project in goal_projects[goal]:
|
||||||
|
goal_in_project = True
|
||||||
|
if goal_in_project == False:
|
||||||
|
print("WARNING: Project " + project + " not in goal.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
||||||
38
GEN-VERSION-FILE
Executable file
38
GEN-VERSION-FILE
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Based on git's GIT-VERSION-GEN.
|
||||||
|
|
||||||
|
VF=VERSION-FILE
|
||||||
|
DEF_VER=v2.2
|
||||||
|
|
||||||
|
LF='
|
||||||
|
'
|
||||||
|
|
||||||
|
if test -d .git -o -f .git &&
|
||||||
|
VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
|
||||||
|
case "$VN" in
|
||||||
|
*$LF*) (exit 1) ;;
|
||||||
|
v[0-9]*)
|
||||||
|
git update-index -q --refresh
|
||||||
|
test -z "$(git diff-index --name-only HEAD --)" ||
|
||||||
|
VN="$VN-dirty" ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
VN=$(echo "$VN" | sed -e 's/-/./g');
|
||||||
|
else
|
||||||
|
VN="$DEF_VER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
VN=$(expr "$VN" : v*'\(.*\)')
|
||||||
|
|
||||||
|
if test -r $VF
|
||||||
|
then
|
||||||
|
VC=$(sed -e 's/^VERSION=//' <$VF)
|
||||||
|
else
|
||||||
|
VC=unset
|
||||||
|
fi
|
||||||
|
test "$VN" = "$VC" || {
|
||||||
|
echo >&2 "VERSION=$VN"
|
||||||
|
echo "VERSION=$VN" >$VF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
52
Makefile
Normal file
52
Makefile
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#
|
||||||
|
# Makefile for todo.txt
|
||||||
|
#
|
||||||
|
|
||||||
|
# Dynamically detect/generate version file as necessary
|
||||||
|
# This file will define a variable called VERSION.
|
||||||
|
.PHONY: .FORCE-VERSION-FILE
|
||||||
|
VERSION-FILE: .FORCE-VERSION-FILE
|
||||||
|
@./GEN-VERSION-FILE
|
||||||
|
-include VERSION-FILE
|
||||||
|
|
||||||
|
# Maybe this will include the version in it.
|
||||||
|
todo.sh: VERSION-FILE
|
||||||
|
|
||||||
|
# For packaging
|
||||||
|
DISTFILES := todo.cfg
|
||||||
|
|
||||||
|
DISTNAME=todo.txt_cli-$(VERSION)
|
||||||
|
dist: $(DISTFILES) todo.sh
|
||||||
|
mkdir -p $(DISTNAME)
|
||||||
|
cp -f $(DISTFILES) $(DISTNAME)/
|
||||||
|
sed -e 's/@DEV_VERSION@/'$(VERSION)'/' todo.sh > $(DISTNAME)/todo.sh
|
||||||
|
tar cf $(DISTNAME).tar $(DISTNAME)/
|
||||||
|
gzip -f -9 $(DISTNAME).tar
|
||||||
|
zip -9r $(DISTNAME).zip $(DISTNAME)/
|
||||||
|
rm -r $(DISTNAME)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(DISTNAME).tar.gz $(DISTNAME).zip
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Testing
|
||||||
|
#
|
||||||
|
TESTS = $(wildcard tests/t[0-9][0-9][0-9][0-9]-*.sh)
|
||||||
|
#TEST_OPTIONS=--verbose
|
||||||
|
|
||||||
|
test-pre-clean:
|
||||||
|
rm -rf tests/test-results "tests/trash directory"*
|
||||||
|
|
||||||
|
aggregate-results: $(TESTS)
|
||||||
|
|
||||||
|
$(TESTS): test-pre-clean
|
||||||
|
-cd tests && sh $(notdir $@) $(TEST_OPTIONS)
|
||||||
|
|
||||||
|
test: aggregate-results
|
||||||
|
tests/aggregate-results.sh tests/test-results/t*-*
|
||||||
|
rm -rf tests/test-results
|
||||||
|
|
||||||
|
# Force tests to get run every time
|
||||||
|
.PHONY: test test-pre-clean aggregate-results $(TESTS)
|
||||||
111
README
111
README
@@ -1,111 +0,0 @@
|
|||||||
Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
|
|
||||||
|
|
||||||
Actions:
|
|
||||||
add "THING I NEED TO DO +project @context"
|
|
||||||
a "THING I NEED TO DO +project @context"
|
|
||||||
Adds THING I NEED TO DO to your todo.txt file on its own line.
|
|
||||||
Project and context notation optional.
|
|
||||||
Quotes optional.
|
|
||||||
|
|
||||||
addto DEST "TEXT TO ADD"
|
|
||||||
Adds a line of text to any file located in the todo.txt directory.
|
|
||||||
For example, addto inbox.txt "decide about vacation"
|
|
||||||
|
|
||||||
append NUMBER "TEXT TO APPEND"
|
|
||||||
app NUMBER "TEXT TO APPEND"
|
|
||||||
Adds TEXT TO APPEND to the end of the todo on line NUMBER.
|
|
||||||
Quotes optional.
|
|
||||||
|
|
||||||
archive
|
|
||||||
Moves done items from todo.txt to done.txt and removes blank lines.
|
|
||||||
|
|
||||||
del NUMBER [TERM]
|
|
||||||
rm NUMBER [TERM]
|
|
||||||
Deletes the item on line NUMBER in todo.txt.
|
|
||||||
If term specified, deletes only the term from the line.
|
|
||||||
|
|
||||||
depri NUMBER
|
|
||||||
dp NUMBER
|
|
||||||
Deprioritizes (removes the priority) from the item
|
|
||||||
on line NUMBER in todo.txt.
|
|
||||||
|
|
||||||
do NUMBER
|
|
||||||
Marks item on line NUMBER as done in todo.txt.
|
|
||||||
|
|
||||||
list [TERM...]
|
|
||||||
ls [TERM...]
|
|
||||||
Displays all todo's that contain TERM(s) sorted by priority with line
|
|
||||||
numbers. If no TERM specified, lists entire todo.txt.
|
|
||||||
|
|
||||||
listall [TERM...]
|
|
||||||
lsa [TERM...]
|
|
||||||
Displays all the lines in todo.txt AND done.txt that contain TERM(s)
|
|
||||||
sorted by priority with line numbers. If no TERM specified, lists
|
|
||||||
entire todo.txt AND done.txt concatenated and sorted.
|
|
||||||
|
|
||||||
listcon
|
|
||||||
lsc
|
|
||||||
Lists all the task contexts that start with the @ sign in todo.txt.
|
|
||||||
|
|
||||||
listfile SRC [TERM...]
|
|
||||||
lf SRC [TERM...]
|
|
||||||
Displays all the lines in SRC file located in the todo.txt directory,
|
|
||||||
sorted by priority with line numbers. If TERM specified, lists
|
|
||||||
all lines that contain TERM in SRC file.
|
|
||||||
|
|
||||||
listpri [PRIORITY]
|
|
||||||
lsp [PRIORITY]
|
|
||||||
Displays all items prioritized PRIORITY.
|
|
||||||
If no PRIORITY specified, lists all prioritized items.
|
|
||||||
|
|
||||||
listproj
|
|
||||||
lsprj
|
|
||||||
Lists all the projects that start with the + sign in todo.txt.
|
|
||||||
|
|
||||||
move NUMBER DEST [SRC]
|
|
||||||
mv NUMBER DEST [SRC]
|
|
||||||
Moves a line from source text file (SRC) to destination text file (DEST).
|
|
||||||
Both source and destination file must be located in the directory defined
|
|
||||||
in the configuration directory. When SRC is not defined
|
|
||||||
it's by default todo.txt.
|
|
||||||
|
|
||||||
prepend NUMBER "TEXT TO PREPEND"
|
|
||||||
prep NUMBER "TEXT TO PREPEND"
|
|
||||||
Adds TEXT TO PREPEND to the beginning of the todo on line NUMBER.
|
|
||||||
Quotes optional.
|
|
||||||
|
|
||||||
pri NUMBER PRIORITY
|
|
||||||
p NUMBER PRIORITY
|
|
||||||
Adds PRIORITY to todo on line NUMBER. If the item is already
|
|
||||||
prioritized, replaces current priority with new PRIORITY.
|
|
||||||
PRIORITY must be an uppercase letter between A and Z.
|
|
||||||
|
|
||||||
replace NUMBER "UPDATED TODO"
|
|
||||||
Replaces todo on line NUMBER with UPDATED TODO.
|
|
||||||
|
|
||||||
report
|
|
||||||
Adds the number of open todo's and closed done's to report.txt.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-d CONFIG_FILE
|
|
||||||
Use a configuration file other than the default ~/todo.cfg
|
|
||||||
-f
|
|
||||||
Forces actions without confirmation or interactive input
|
|
||||||
-h
|
|
||||||
Display this help message
|
|
||||||
-p
|
|
||||||
Plain mode turns off colors
|
|
||||||
-a
|
|
||||||
Don't auto-archive tasks automatically on completion
|
|
||||||
-n
|
|
||||||
Don't preserve line numbers; automatically remove blank lines
|
|
||||||
on task deletion
|
|
||||||
-t
|
|
||||||
Prepend the current date to a task automatically
|
|
||||||
when it's added.
|
|
||||||
-v
|
|
||||||
Verbose mode turns on confirmation messages
|
|
||||||
-V
|
|
||||||
Displays version, license and credits
|
|
||||||
26
README.textile
Normal file
26
README.textile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
h1. TODO.TXT Command Line Interface
|
||||||
|
|
||||||
|
A simple and extensible shell script for managing your todo.txt file.
|
||||||
|
|
||||||
|
h2. "Downloads":http://github.com/ginatrapani/todo.txt-cli/downloads
|
||||||
|
|
||||||
|
"Download the latest stable release":http://github.com/ginatrapani/todo.txt-cli/downloads for use on your desktop or server.
|
||||||
|
|
||||||
|
h2. "Documentation":http://wiki.github.com/ginatrapani/todo.txt-cli
|
||||||
|
|
||||||
|
* "User Documentation":http://wiki.github.com/ginatrapani/todo.txt-cli/user-documentation - Find out "how to install and use Todo.txt CLI":http://wiki.github.com/ginatrapani/todo.txt-cli/user-documentation, and get tips and tricks.
|
||||||
|
|
||||||
|
* "Developer Documentation":http://wiki.github.com/ginatrapani/todo.txt-cli/developer-documentation - "Contribute to Todo.txt CLI":http://wiki.github.com/ginatrapani/todo.txt-cli/developer-documentation and build your own custom add-ons.
|
||||||
|
|
||||||
|
h2. "Mailing List":http://groups.yahoo.com/group/todotxt/
|
||||||
|
|
||||||
|
Get support from users and developers on the "mailing list":http://groups.yahoo.com/group/todotxt/.
|
||||||
|
|
||||||
|
h2. Quick Links
|
||||||
|
|
||||||
|
* Original anemic release by "Gina Trapani":http://ginatrapani.org on 5/11/2006.
|
||||||
|
* Raised to great heights by "brainy and dedicated volunteers":http://github.com/ginatrapani/todo.txt-cli/network.
|
||||||
|
* Licensed under the "GPL":http://www.gnu.org/copyleft/gpl.html
|
||||||
|
* "Add-on Directory":http://wiki.github.com/ginatrapani/todo.txt-cli/todosh-add-on-directory
|
||||||
|
* "Changelog":http://wiki.github.com/ginatrapani/todo.txt-cli/todosh-changelog
|
||||||
|
* "Known Bugs":http://wiki.github.com/ginatrapani/todo.txt-cli/known-bugs
|
||||||
2
tests/Makefile
Normal file
2
tests/Makefile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
test:
|
||||||
|
$(MAKE) -C .. test
|
||||||
219
tests/README
Normal file
219
tests/README
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
todo.sh tests
|
||||||
|
=============
|
||||||
|
|
||||||
|
This directory holds test scripts for todo.sh. The
|
||||||
|
first part of this short document describes how to run the tests
|
||||||
|
and read their output.
|
||||||
|
|
||||||
|
When fixing the tools or adding enhancements, you are strongly
|
||||||
|
encouraged to add tests in this directory to cover what you are
|
||||||
|
trying to fix or enhance. The later part of this short document
|
||||||
|
describes how your test scripts should be organized.
|
||||||
|
|
||||||
|
|
||||||
|
Running Tests
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The easiest way to run tests is to say "make test" from the top-level.
|
||||||
|
This runs all the tests.
|
||||||
|
|
||||||
|
rm -rf tests/test-results "tests/trash directory"*
|
||||||
|
cd tests && sh t0000-config.sh
|
||||||
|
* ok 1: no config file
|
||||||
|
* ok 2: config file (default location 1)
|
||||||
|
* ok 3: config file (default location 2)
|
||||||
|
* ok 4: config file (command line)
|
||||||
|
* ok 5: config file (env variable)
|
||||||
|
* passed all 5 test(s)
|
||||||
|
cd tests && sh t0001-null.sh
|
||||||
|
* ok 1: null ls
|
||||||
|
* passed all 1 test(s)
|
||||||
|
rm -rf tests/test-results
|
||||||
|
|
||||||
|
Or you can run each test individually from command line, like
|
||||||
|
this:
|
||||||
|
|
||||||
|
$ ./t0001-null.sh
|
||||||
|
* ok 1: null ls
|
||||||
|
* passed all 1 test(s)
|
||||||
|
|
||||||
|
You can pass --verbose (or -v), --debug (or -d), and --immediate
|
||||||
|
(or -i) command line argument to the test, or by setting GIT_TEST_OPTS
|
||||||
|
appropriately before running "make".
|
||||||
|
|
||||||
|
--verbose::
|
||||||
|
This makes the test more verbose. Specifically, the
|
||||||
|
command being run and their output if any are also
|
||||||
|
output.
|
||||||
|
|
||||||
|
--debug::
|
||||||
|
This may help the person who is developing a new test.
|
||||||
|
It causes the command defined with test_debug to run.
|
||||||
|
|
||||||
|
--immediate::
|
||||||
|
This causes the test to immediately exit upon the first
|
||||||
|
failed test.
|
||||||
|
|
||||||
|
--long-tests::
|
||||||
|
This causes additional long-running tests to be run (where
|
||||||
|
available), for more exhaustive testing.
|
||||||
|
|
||||||
|
--tee::
|
||||||
|
In addition to printing the test output to the terminal,
|
||||||
|
write it to files named 't/test-results/$TEST_NAME.out'.
|
||||||
|
As the names depend on the tests' file names, it is safe to
|
||||||
|
run the tests with this option in parallel.
|
||||||
|
|
||||||
|
Skipping Tests
|
||||||
|
--------------
|
||||||
|
|
||||||
|
In some environments, certain tests have no way of succeeding
|
||||||
|
due to platform limitation, such as lack of 'unzip' program, or
|
||||||
|
filesystem that do not allow arbitrary sequence of non-NUL bytes
|
||||||
|
as pathnames.
|
||||||
|
|
||||||
|
You should be able to say something like
|
||||||
|
|
||||||
|
$ SKIP_TESTS=t0000.2 sh ./t0000-config.sh
|
||||||
|
|
||||||
|
and even:
|
||||||
|
|
||||||
|
$ SKIP_TESTS='t[0-4]??? t91?? t9200.8' make
|
||||||
|
|
||||||
|
to omit such tests. The value of the environment variable is a
|
||||||
|
SP separated list of patterns that tells which tests to skip,
|
||||||
|
and either can match the "t[0-9]{4}" part to skip the whole
|
||||||
|
test, or t[0-9]{4} followed by ".$number" to say which
|
||||||
|
particular test to skip.
|
||||||
|
|
||||||
|
Note that some tests in the existing test suite rely on previous
|
||||||
|
test item, so you cannot arbitrarily disable one and expect the
|
||||||
|
remainder of test to check what the test originally was intended
|
||||||
|
to check.
|
||||||
|
|
||||||
|
|
||||||
|
Naming Tests
|
||||||
|
------------
|
||||||
|
|
||||||
|
The test files are named as:
|
||||||
|
|
||||||
|
tNNNN-commandname-details.sh
|
||||||
|
|
||||||
|
where N is a decimal digit.
|
||||||
|
|
||||||
|
First digit tells the family:
|
||||||
|
|
||||||
|
0 - the absolute basics and global stuff
|
||||||
|
1 - basic every-day usage
|
||||||
|
2 - add ins
|
||||||
|
|
||||||
|
Second digit tells the particular command we are testing.
|
||||||
|
|
||||||
|
Third digit (optionally) tells the particular switch or group of switches
|
||||||
|
we are testing.
|
||||||
|
|
||||||
|
If you create files under tests/ directory (i.e. here) that is not
|
||||||
|
the top-level test script, never name the file to match the above
|
||||||
|
pattern. The Makefile here considers all such files as the
|
||||||
|
top-level test script and tries to run all of them. A care is
|
||||||
|
especially needed if you are creating a common test library
|
||||||
|
file, similar to test-lib.sh, because such a library file may
|
||||||
|
not be suitable for standalone execution.
|
||||||
|
|
||||||
|
|
||||||
|
Writing Tests
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The test script is written as a shell script. It should start
|
||||||
|
with the standard "#!/bin/sh" with copyright notices, and an
|
||||||
|
assignment to variable 'test_description', like this:
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 Junio C Hamano
|
||||||
|
#
|
||||||
|
|
||||||
|
test_description='xxx test (option --frotz)
|
||||||
|
|
||||||
|
This test registers the following structure in the cache
|
||||||
|
and tries to run git-ls-files with option --frotz.'
|
||||||
|
|
||||||
|
|
||||||
|
Source 'test-lib.sh'
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
After assigning test_description, the test script should source
|
||||||
|
test-lib.sh like this:
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
This test harness library does the following things:
|
||||||
|
|
||||||
|
- If the script is invoked with command line argument --help
|
||||||
|
(or -h), it shows the test_description and exits.
|
||||||
|
|
||||||
|
- Creates an empty test directory with an empty todo file
|
||||||
|
database and chdir(2) into it. This directory is 't/trash directory'
|
||||||
|
if you must know, but I do not think you care.
|
||||||
|
|
||||||
|
- Defines standard test helper functions for your scripts to
|
||||||
|
use. These functions are designed to make all scripts behave
|
||||||
|
consistently when command line arguments --verbose (or -v),
|
||||||
|
--debug (or -d), and --immediate (or -i) is given.
|
||||||
|
|
||||||
|
|
||||||
|
End with test_done
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Your script will be a sequence of tests, using helper functions
|
||||||
|
from the test harness library. At the end of the script, call
|
||||||
|
'test_done'.
|
||||||
|
|
||||||
|
|
||||||
|
Test harness library
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
There are a handful helper functions defined in the test harness
|
||||||
|
library for your script to use.
|
||||||
|
|
||||||
|
- test_expect_success <message> <script>
|
||||||
|
|
||||||
|
This takes two strings as parameter, and evaluates the
|
||||||
|
<script>. If it yields success, test is considered
|
||||||
|
successful. <message> should state what it is testing.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
test_expect_success \
|
||||||
|
'git-write-tree should be able to write an empty tree.' \
|
||||||
|
'tree=$(git-write-tree)'
|
||||||
|
|
||||||
|
- test_expect_failure <message> <script>
|
||||||
|
|
||||||
|
This is NOT the opposite of test_expect_success, but is used
|
||||||
|
to mark a test that demonstrates a known breakage. Unlike
|
||||||
|
the usual test_expect_success tests, which say "ok" on
|
||||||
|
success and "FAIL" on failure, this will say "FIXED" on
|
||||||
|
success and "still broken" on failure. Failures from these
|
||||||
|
tests won't cause -i (immediate) to stop.
|
||||||
|
|
||||||
|
- test_debug <script>
|
||||||
|
|
||||||
|
This takes a single argument, <script>, and evaluates it only
|
||||||
|
when the test script is started with --debug command line
|
||||||
|
argument. This is primarily meant for use during the
|
||||||
|
development of a new test script.
|
||||||
|
|
||||||
|
- test_done
|
||||||
|
|
||||||
|
Your test script must have test_done at the end. Its purpose
|
||||||
|
is to summarize successes and failures in the test script and
|
||||||
|
exit with an appropriate error code.
|
||||||
|
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
This test framework was derived from the framework used by
|
||||||
|
git itself, written originally by Junio Hamano and licensed
|
||||||
|
for use under the GPL.
|
||||||
34
tests/aggregate-results.sh
Executable file
34
tests/aggregate-results.sh
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
fixed=0
|
||||||
|
success=0
|
||||||
|
failed=0
|
||||||
|
broken=0
|
||||||
|
total=0
|
||||||
|
|
||||||
|
for file
|
||||||
|
do
|
||||||
|
while read type value
|
||||||
|
do
|
||||||
|
case $type in
|
||||||
|
'')
|
||||||
|
continue ;;
|
||||||
|
fixed)
|
||||||
|
fixed=$(($fixed + $value)) ;;
|
||||||
|
success)
|
||||||
|
success=$(($success + $value)) ;;
|
||||||
|
failed)
|
||||||
|
failed=$(($failed + $value)) ;;
|
||||||
|
broken)
|
||||||
|
broken=$(($broken + $value)) ;;
|
||||||
|
total)
|
||||||
|
total=$(($total + $value)) ;;
|
||||||
|
esac
|
||||||
|
done <"$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%-8s%d\n" fixed $fixed
|
||||||
|
printf "%-8s%d\n" success $success
|
||||||
|
printf "%-8s%d\n" failed $failed
|
||||||
|
printf "%-8s%d\n" broken $broken
|
||||||
|
printf "%-8s%d\n" total $total
|
||||||
61
tests/t0000-config.sh
Executable file
61
tests/t0000-config.sh
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='todo.sh configuration file location
|
||||||
|
|
||||||
|
This test just makes sure that todo.sh can find its
|
||||||
|
config files in the default locations and take arguments
|
||||||
|
to find it somewhere else.
|
||||||
|
'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
# Remove the pre-created todo.cfg to test behavior in its absence
|
||||||
|
rm -f todo.cfg
|
||||||
|
echo "Fatal error: Cannot read configuration file $HOME/todo.cfg" > expect
|
||||||
|
test_expect_success 'no config file' '
|
||||||
|
todo.sh > output 2>&1 || test_cmp expect output
|
||||||
|
'
|
||||||
|
|
||||||
|
# All the below tests will output the usage message.
|
||||||
|
cat > expect << EOF
|
||||||
|
Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
|
||||||
|
Try 'todo.sh -h' for more information.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > test.cfg << EOF
|
||||||
|
export TODO_DIR=.
|
||||||
|
export TODO_FILE="$TODO_DIR/todo.txt"
|
||||||
|
export DONE_FILE="$TODO_DIR/done.txt"
|
||||||
|
export REPORT_FILE="$TODO_DIR/report.txt"
|
||||||
|
export TMP_FILE="$TODO_DIR/todo.tmp"
|
||||||
|
touch used_config
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rm -f used_config
|
||||||
|
test_expect_success 'config file (default location 1)' '
|
||||||
|
cp test.cfg todo.cfg
|
||||||
|
todo.sh > output;
|
||||||
|
test_cmp expect output && test -f used_config &&
|
||||||
|
rm -f todo.cfg
|
||||||
|
'
|
||||||
|
|
||||||
|
rm -f used_config
|
||||||
|
test_expect_success 'config file (default location 2)' '
|
||||||
|
cp test.cfg .todo.cfg
|
||||||
|
todo.sh > output;
|
||||||
|
test_cmp expect output && test -f used_config &&
|
||||||
|
rm -f .todo.cfg
|
||||||
|
'
|
||||||
|
|
||||||
|
rm -f used_config
|
||||||
|
test_expect_success 'config file (command line)' '
|
||||||
|
todo.sh -d test.cfg > output;
|
||||||
|
test_cmp expect output && test -f used_config
|
||||||
|
'
|
||||||
|
|
||||||
|
rm -f used_config
|
||||||
|
test_expect_success 'config file (env variable)' '
|
||||||
|
TODOTXT_CFG_FILE=test.cfg todo.sh > output;
|
||||||
|
test_cmp expect output && test -f used_config
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
||||||
101
tests/t0001-null.sh
Executable file
101
tests/t0001-null.sh
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='todo.sh basic null functionality test.
|
||||||
|
|
||||||
|
This test just makes sure the basic commands work,
|
||||||
|
when there are no todos.
|
||||||
|
'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# ls|list
|
||||||
|
#
|
||||||
|
cat > expect <<EOF
|
||||||
|
--
|
||||||
|
TODO: 0 of 0 tasks shown from $HOME/todo.txt
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'null ls' '
|
||||||
|
todo.sh ls > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
test_expect_success 'null list' '
|
||||||
|
todo.sh list > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
test_expect_success 'null list filter' '
|
||||||
|
todo.sh list filter > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
|
||||||
|
#
|
||||||
|
# lsp|listpri
|
||||||
|
#
|
||||||
|
# Re-use expect from ls.
|
||||||
|
test_expect_success 'null lsp' '
|
||||||
|
todo.sh lsp > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
test_expect_success 'null listpri' '
|
||||||
|
todo.sh listpri > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
test_expect_success 'null listpri a' '
|
||||||
|
todo.sh listpri a > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
|
||||||
|
#
|
||||||
|
# lsa|listall
|
||||||
|
#
|
||||||
|
cat > expect <<EOF
|
||||||
|
--
|
||||||
|
TODO: 0 of 0 tasks shown from $HOME/todo.tmp
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_expect_success 'null lsa' '
|
||||||
|
todo.sh lsa > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
test_expect_success 'null list' '
|
||||||
|
todo.sh listall > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
test_expect_success 'null list filter' '
|
||||||
|
todo.sh listall filter > output && test_cmp expect output
|
||||||
|
'
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# lsc|listcon
|
||||||
|
#
|
||||||
|
test_expect_success 'null lsc' '
|
||||||
|
todo.sh lsc > output && ! test -s output
|
||||||
|
'
|
||||||
|
test_expect_success 'null listcon' '
|
||||||
|
todo.sh listcon > output && ! test -s output
|
||||||
|
'
|
||||||
|
|
||||||
|
#
|
||||||
|
# lsprj|listproj
|
||||||
|
#
|
||||||
|
test_expect_success 'null lsprj' '
|
||||||
|
todo.sh lsprj > output && ! test -s output
|
||||||
|
'
|
||||||
|
test_expect_success 'null listproj' '
|
||||||
|
todo.sh listproj > output && ! test -s output
|
||||||
|
'
|
||||||
|
|
||||||
|
#
|
||||||
|
# lf|listfile
|
||||||
|
#
|
||||||
|
cat > expect <<EOF
|
||||||
|
TODO: File does not exist.
|
||||||
|
EOF
|
||||||
|
# XXX really should give a better usage error message here.
|
||||||
|
test_expect_success 'null lf' '
|
||||||
|
todo.sh lf > output || test_cmp expect output
|
||||||
|
'
|
||||||
|
test_expect_success 'null listfile' '
|
||||||
|
todo.sh listfile > output || test_cmp expect output
|
||||||
|
'
|
||||||
|
cat > expect <<EOF
|
||||||
|
TODO: File foo.txt does not exist.
|
||||||
|
EOF
|
||||||
|
test_expect_success 'null listfile foo.txt' '
|
||||||
|
todo.sh listfile foo.txt > output || test_cmp expect output
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
||||||
467
tests/test-lib.sh
Normal file
467
tests/test-lib.sh
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005 Junio C Hamano
|
||||||
|
#
|
||||||
|
|
||||||
|
# if --tee was passed, write the output not only to the terminal, but
|
||||||
|
# additionally to the file test-results/$BASENAME.out, too.
|
||||||
|
case "$TEST_TEE_STARTED, $* " in
|
||||||
|
done,*)
|
||||||
|
# do not redirect again
|
||||||
|
;;
|
||||||
|
*' --tee '*|*' --va'*)
|
||||||
|
mkdir -p test-results
|
||||||
|
BASE=test-results/$(basename "$0" .sh)
|
||||||
|
(TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1;
|
||||||
|
echo $? > $BASE.exit) | tee $BASE.out
|
||||||
|
test "$(cat $BASE.exit)" = 0
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Keep the original TERM for say_color
|
||||||
|
ORIGINAL_TERM=$TERM
|
||||||
|
|
||||||
|
# For repeatability, reset the environment to known value.
|
||||||
|
LANG=C
|
||||||
|
LC_ALL=C
|
||||||
|
PAGER=cat
|
||||||
|
TZ=UTC
|
||||||
|
TERM=dumb
|
||||||
|
export LANG LC_ALL PAGER TERM TZ
|
||||||
|
EDITOR=:
|
||||||
|
VISUAL=:
|
||||||
|
|
||||||
|
# Protect ourselves from common misconfiguration to export
|
||||||
|
# CDPATH into the environment
|
||||||
|
unset CDPATH
|
||||||
|
|
||||||
|
# Protect ourselves from using predefined TODOTXT_CFG_FILE
|
||||||
|
unset TODOTXT_CFG_FILE
|
||||||
|
|
||||||
|
# Each test should start with something like this, after copyright notices:
|
||||||
|
#
|
||||||
|
# test_description='Description of this test...
|
||||||
|
# This test checks if command xyzzy does the right thing...
|
||||||
|
# '
|
||||||
|
# . ./test-lib.sh
|
||||||
|
[ "x$ORIGINAL_TERM" != "xdumb" ] && (
|
||||||
|
TERM=$ORIGINAL_TERM &&
|
||||||
|
export TERM &&
|
||||||
|
[ -t 1 ] &&
|
||||||
|
tput bold >/dev/null 2>&1 &&
|
||||||
|
tput setaf 1 >/dev/null 2>&1 &&
|
||||||
|
tput sgr0 >/dev/null 2>&1
|
||||||
|
) &&
|
||||||
|
color=t
|
||||||
|
|
||||||
|
while test "$#" -ne 0
|
||||||
|
do
|
||||||
|
case "$1" in
|
||||||
|
-d|--d|--de|--deb|--debu|--debug)
|
||||||
|
debug=t; shift ;;
|
||||||
|
-i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
|
||||||
|
immediate=t; shift ;;
|
||||||
|
-l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
|
||||||
|
TODOTXT_TEST_LONG=t; export TODOTXT_TEST_LONG; shift ;;
|
||||||
|
-h|--h|--he|--hel|--help)
|
||||||
|
help=t; shift ;;
|
||||||
|
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
|
||||||
|
verbose=t; shift ;;
|
||||||
|
-q|--q|--qu|--qui|--quie|--quiet)
|
||||||
|
quiet=t; shift ;;
|
||||||
|
--no-color)
|
||||||
|
color=; shift ;;
|
||||||
|
--no-python)
|
||||||
|
# noop now...
|
||||||
|
shift ;;
|
||||||
|
--tee)
|
||||||
|
shift ;; # was handled already
|
||||||
|
*)
|
||||||
|
break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if test -n "$color"; then
|
||||||
|
say_color () {
|
||||||
|
(
|
||||||
|
TERM=$ORIGINAL_TERM
|
||||||
|
export TERM
|
||||||
|
case "$1" in
|
||||||
|
error) tput bold; tput setaf 1;; # bold red
|
||||||
|
skip) tput bold; tput setaf 2;; # bold green
|
||||||
|
pass) tput setaf 2;; # green
|
||||||
|
info) tput setaf 3;; # brown
|
||||||
|
*) test -n "$quiet" && return;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
printf "* %s" "$*"
|
||||||
|
tput sgr0
|
||||||
|
echo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
say_color() {
|
||||||
|
test -z "$1" && test -n "$quiet" && return
|
||||||
|
shift
|
||||||
|
echo "* $*"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
error () {
|
||||||
|
say_color error "error: $*"
|
||||||
|
trap - EXIT
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
say () {
|
||||||
|
say_color info "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
test "${test_description}" != "" ||
|
||||||
|
error "Test script did not set test_description."
|
||||||
|
|
||||||
|
if test "$help" = "t"
|
||||||
|
then
|
||||||
|
echo "$test_description"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec 5>&1
|
||||||
|
if test "$verbose" = "t"
|
||||||
|
then
|
||||||
|
exec 4>&2 3>&1
|
||||||
|
else
|
||||||
|
exec 4>/dev/null 3>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_failure=0
|
||||||
|
test_count=0
|
||||||
|
test_fixed=0
|
||||||
|
test_broken=0
|
||||||
|
test_success=0
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo >&5 "FATAL: Unexpected exit with code $?"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'die' EXIT
|
||||||
|
|
||||||
|
# The semantics of the editor variables are that of invoking
|
||||||
|
# sh -c "$EDITOR \"$@\"" files ...
|
||||||
|
#
|
||||||
|
# If our trash directory contains shell metacharacters, they will be
|
||||||
|
# interpreted if we just set $EDITOR directly, so do a little dance with
|
||||||
|
# environment variables to work around this.
|
||||||
|
#
|
||||||
|
# In particular, quoting isn't enough, as the path may contain the same quote
|
||||||
|
# that we're using.
|
||||||
|
test_set_editor () {
|
||||||
|
FAKE_EDITOR="$1"
|
||||||
|
export FAKE_EDITOR
|
||||||
|
VISUAL='"$FAKE_EDITOR"'
|
||||||
|
export VISUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
# You are not expected to call test_ok_ and test_failure_ directly, use
|
||||||
|
# the text_expect_* functions instead.
|
||||||
|
|
||||||
|
test_ok_ () {
|
||||||
|
test_success=$(($test_success + 1))
|
||||||
|
say_color "" " ok $test_count: $@"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_failure_ () {
|
||||||
|
test_failure=$(($test_failure + 1))
|
||||||
|
say_color error "FAIL $test_count: $1"
|
||||||
|
shift
|
||||||
|
echo "$@" | sed -e 's/^/ /'
|
||||||
|
test "$immediate" = "" || { trap - EXIT; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
test_known_broken_ok_ () {
|
||||||
|
test_fixed=$(($test_fixed+1))
|
||||||
|
say_color "" " FIXED $test_count: $@"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_known_broken_failure_ () {
|
||||||
|
test_broken=$(($test_broken+1))
|
||||||
|
say_color skip " still broken $test_count: $@"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_debug () {
|
||||||
|
test "$debug" = "" || eval "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_run_ () {
|
||||||
|
eval >&3 2>&4 "$1"
|
||||||
|
eval_ret="$?"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test_skip () {
|
||||||
|
test_count=$(($test_count+1))
|
||||||
|
to_skip=
|
||||||
|
for skp in $SKIP_TESTS
|
||||||
|
do
|
||||||
|
case $this_test.$test_count in
|
||||||
|
$skp)
|
||||||
|
to_skip=t
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
case "$to_skip" in
|
||||||
|
t)
|
||||||
|
say_color skip >&3 "skipping test: $@"
|
||||||
|
say_color skip "skip $test_count: $1"
|
||||||
|
: true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
false
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_failure () {
|
||||||
|
test "$#" = 2 ||
|
||||||
|
error "bug in the test script: not 2 parameters to test-expect-failure"
|
||||||
|
if ! test_skip "$@"
|
||||||
|
then
|
||||||
|
say >&3 "checking known breakage: $2"
|
||||||
|
test_run_ "$2"
|
||||||
|
if [ "$?" = 0 -a "$eval_ret" = 0 ]
|
||||||
|
then
|
||||||
|
test_known_broken_ok_ "$1"
|
||||||
|
else
|
||||||
|
test_known_broken_failure_ "$1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo >&3 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success () {
|
||||||
|
test "$#" = 2 ||
|
||||||
|
error "bug in the test script: not 2 parameters to test-expect-success"
|
||||||
|
if ! test_skip "$@"
|
||||||
|
then
|
||||||
|
say >&3 "expecting success: $2"
|
||||||
|
test_run_ "$2"
|
||||||
|
if [ "$?" = 0 -a "$eval_ret" = 0 ]
|
||||||
|
then
|
||||||
|
test_ok_ "$1"
|
||||||
|
else
|
||||||
|
test_failure_ "$@"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo >&3 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_code () {
|
||||||
|
test "$#" = 3 ||
|
||||||
|
error "bug in the test script: not 3 parameters to test-expect-code"
|
||||||
|
if ! test_skip "$@"
|
||||||
|
then
|
||||||
|
say >&3 "expecting exit code $1: $3"
|
||||||
|
test_run_ "$3"
|
||||||
|
if [ "$?" = 0 -a "$eval_ret" = "$1" ]
|
||||||
|
then
|
||||||
|
test_ok_ "$2"
|
||||||
|
else
|
||||||
|
test_failure_ "$@"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo >&3 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# test_external runs external test scripts that provide continuous
|
||||||
|
# test output about their progress, and succeeds/fails on
|
||||||
|
# zero/non-zero exit code. It outputs the test output on stdout even
|
||||||
|
# in non-verbose mode, and announces the external script with "* run
|
||||||
|
# <n>: ..." before running it. When providing relative paths, keep in
|
||||||
|
# mind that all scripts run in "trash directory".
|
||||||
|
# Usage: test_external description command arguments...
|
||||||
|
# Example: test_external 'Perl API' perl ../path/to/test.pl
|
||||||
|
test_external () {
|
||||||
|
test "$#" -eq 3 ||
|
||||||
|
error >&5 "bug in the test script: not 3 parameters to test_external"
|
||||||
|
descr="$1"
|
||||||
|
shift
|
||||||
|
if ! test_skip "$descr" "$@"
|
||||||
|
then
|
||||||
|
# Announce the script to reduce confusion about the
|
||||||
|
# test output that follows.
|
||||||
|
say_color "" " run $test_count: $descr ($*)"
|
||||||
|
# Run command; redirect its stderr to &4 as in
|
||||||
|
# test_run_, but keep its stdout on our stdout even in
|
||||||
|
# non-verbose mode.
|
||||||
|
"$@" 2>&4
|
||||||
|
if [ "$?" = 0 ]
|
||||||
|
then
|
||||||
|
test_ok_ "$descr"
|
||||||
|
else
|
||||||
|
test_failure_ "$descr" "$@"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Like test_external, but in addition tests that the command generated
|
||||||
|
# no output on stderr.
|
||||||
|
test_external_without_stderr () {
|
||||||
|
# The temporary file has no (and must have no) security
|
||||||
|
# implications.
|
||||||
|
tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi
|
||||||
|
stderr="$tmp/todotxt-external-stderr.$$.tmp"
|
||||||
|
test_external "$@" 4> "$stderr"
|
||||||
|
[ -f "$stderr" ] || error "Internal error: $stderr disappeared."
|
||||||
|
descr="no stderr: $1"
|
||||||
|
shift
|
||||||
|
say >&3 "expecting no stderr from previous command"
|
||||||
|
if [ ! -s "$stderr" ]; then
|
||||||
|
rm "$stderr"
|
||||||
|
test_ok_ "$descr"
|
||||||
|
else
|
||||||
|
if [ "$verbose" = t ]; then
|
||||||
|
output=`echo; echo Stderr is:; cat "$stderr"`
|
||||||
|
else
|
||||||
|
output=
|
||||||
|
fi
|
||||||
|
# rm first in case test_failure exits.
|
||||||
|
rm "$stderr"
|
||||||
|
test_failure_ "$descr" "$@" "$output"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# This is not among top-level (test_expect_success | test_expect_failure)
|
||||||
|
# but is a prefix that can be used in the test script, like:
|
||||||
|
#
|
||||||
|
# test_expect_success 'complain and die' '
|
||||||
|
# do something &&
|
||||||
|
# do something else &&
|
||||||
|
# test_must_fail git checkout ../outerspace
|
||||||
|
# '
|
||||||
|
#
|
||||||
|
# Writing this as "! git checkout ../outerspace" is wrong, because
|
||||||
|
# the failure could be due to a segv. We want a controlled failure.
|
||||||
|
|
||||||
|
test_must_fail () {
|
||||||
|
"$@"
|
||||||
|
test $? -gt 0 -a $? -le 129 -o $? -gt 192
|
||||||
|
}
|
||||||
|
|
||||||
|
# test_cmp is a helper function to compare actual and expected output.
|
||||||
|
# You can use it like:
|
||||||
|
#
|
||||||
|
# test_expect_success 'foo works' '
|
||||||
|
# echo expected >expected &&
|
||||||
|
# foo >actual &&
|
||||||
|
# test_cmp expected actual
|
||||||
|
# '
|
||||||
|
#
|
||||||
|
# This could be written as either "cmp" or "diff -u", but:
|
||||||
|
# - cmp's output is not nearly as easy to read as diff -u
|
||||||
|
# - not all diff versions understand "-u"
|
||||||
|
|
||||||
|
test_cmp() {
|
||||||
|
diff -u "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_done () {
|
||||||
|
trap - EXIT
|
||||||
|
test_results_dir="$TEST_DIRECTORY/test-results"
|
||||||
|
mkdir -p "$test_results_dir"
|
||||||
|
test_results_path="$test_results_dir/${0%.sh}-$$"
|
||||||
|
|
||||||
|
echo "total $test_count" >> $test_results_path
|
||||||
|
echo "success $test_success" >> $test_results_path
|
||||||
|
echo "fixed $test_fixed" >> $test_results_path
|
||||||
|
echo "broken $test_broken" >> $test_results_path
|
||||||
|
echo "failed $test_failure" >> $test_results_path
|
||||||
|
echo "" >> $test_results_path
|
||||||
|
|
||||||
|
if test "$test_fixed" != 0
|
||||||
|
then
|
||||||
|
say_color pass "fixed $test_fixed known breakage(s)"
|
||||||
|
fi
|
||||||
|
if test "$test_broken" != 0
|
||||||
|
then
|
||||||
|
say_color error "still have $test_broken known breakage(s)"
|
||||||
|
msg="remaining $(($test_count-$test_broken)) test(s)"
|
||||||
|
else
|
||||||
|
msg="$test_count test(s)"
|
||||||
|
fi
|
||||||
|
case "$test_failure" in
|
||||||
|
0)
|
||||||
|
say_color pass "passed all $msg"
|
||||||
|
|
||||||
|
# Clean up this test.
|
||||||
|
test -d "$remove_trash" &&
|
||||||
|
cd "$(dirname "$remove_trash")" &&
|
||||||
|
rm -rf "$(basename "$remove_trash")"
|
||||||
|
|
||||||
|
exit 0 ;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
say_color error "failed $test_failure among $msg"
|
||||||
|
exit 1 ;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure we are testing the latest version.
|
||||||
|
TEST_DIRECTORY=$(pwd)
|
||||||
|
PATH=$TEST_DIRECTORY/..:$PATH
|
||||||
|
|
||||||
|
# Test repository
|
||||||
|
test="trash directory.$(basename "$0" .sh)"
|
||||||
|
test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test"
|
||||||
|
rm -fr "$test" || {
|
||||||
|
trap - EXIT
|
||||||
|
echo >&5 "FATAL: Cannot prepare test area"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Most tests can use the created repository, but some may need to create more.
|
||||||
|
# Usage: test_init_todo <directory>
|
||||||
|
test_init_todo () {
|
||||||
|
test "$#" = 1 ||
|
||||||
|
error "bug in the test script: not 1 parameter to test_init_todo"
|
||||||
|
owd=`pwd`
|
||||||
|
root="$1"
|
||||||
|
mkdir -p "$root"
|
||||||
|
cd "$root" || error "Cannot setup todo dir in $root"
|
||||||
|
# Initialize the configuration file. Carefully quoted.
|
||||||
|
sed -e 's|TODO_DIR=.*$|TODO_DIR="'"$TEST_DIRECTORY/$test"'"|' $TEST_DIRECTORY/../todo.cfg > todo.cfg
|
||||||
|
cd "$owd"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test_init_todo "$test"
|
||||||
|
# Use -P to resolve symlinks in our working directory so that the cwd
|
||||||
|
# in subprocesses equals our $PWD (for pathname comparisons).
|
||||||
|
cd -P "$test" || exit 1
|
||||||
|
|
||||||
|
# Since todo.sh refers to the home directory often,
|
||||||
|
# make sure we don't accidentally grab the tester's config
|
||||||
|
# but use something specified by the framework.
|
||||||
|
HOME=$(pwd)
|
||||||
|
export HOME
|
||||||
|
|
||||||
|
this_test=${0##*/}
|
||||||
|
this_test=${this_test%%-*}
|
||||||
|
for skp in $SKIP_TESTS
|
||||||
|
do
|
||||||
|
to_skip=
|
||||||
|
for skp in $SKIP_TESTS
|
||||||
|
do
|
||||||
|
case "$this_test" in
|
||||||
|
$skp)
|
||||||
|
to_skip=t
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
case "$to_skip" in
|
||||||
|
t)
|
||||||
|
say_color skip >&3 "skipping test $this_test altogether"
|
||||||
|
say_color skip "skip all tests in $this_test"
|
||||||
|
test_done
|
||||||
|
esac
|
||||||
|
done
|
||||||
70
todo.cfg
70
todo.cfg
@@ -1,41 +1,55 @@
|
|||||||
# === EDIT FILE LOCATIONS BELOW ===
|
# === EDIT FILE LOCATIONS BELOW ===
|
||||||
|
|
||||||
# Your todo.txt directory
|
# Your todo.txt directory
|
||||||
#TODO_DIR="/Users/gina/Documents/todo"
|
#export TODO_DIR="/Users/gina/Documents/todo"
|
||||||
TODO_DIR="C:/Documents and Settings/gina/My Documents"
|
export TODO_DIR="C:/Documents and Settings/gina/My Documents"
|
||||||
|
|
||||||
# Your todo/done/report.txt locations
|
# Your todo/done/report.txt locations
|
||||||
TODO_FILE="$TODO_DIR/todo.txt"
|
export TODO_FILE="$TODO_DIR/todo.txt"
|
||||||
DONE_FILE="$TODO_DIR/done.txt"
|
export DONE_FILE="$TODO_DIR/done.txt"
|
||||||
REPORT_FILE="$TODO_DIR/report.txt"
|
export REPORT_FILE="$TODO_DIR/report.txt"
|
||||||
TMP_FILE="$TODO_DIR/todo.tmp"
|
export TMP_FILE="$TODO_DIR/todo.tmp"
|
||||||
|
|
||||||
|
# You can customize your actions directory location
|
||||||
|
#export TODO_ACTIONS_DIR="$HOME/.todo.actions.d"
|
||||||
|
|
||||||
# == EDIT FILE LOCATIONS ABOVE ===
|
# == EDIT FILE LOCATIONS ABOVE ===
|
||||||
|
|
||||||
# === COLOR MAP ===
|
# === COLOR MAP ===
|
||||||
|
|
||||||
NONE=''
|
## If you have re-mapped your color codes, you may need to
|
||||||
BLACK='\\033[0;30m'
|
## over-ride by uncommenting and editing these defaults.
|
||||||
RED='\\033[0;31m'
|
|
||||||
GREEN='\\033[0;32m'
|
# export BLACK='\\033[0;30m'
|
||||||
BROWN='\\033[0;33m'
|
# export RED='\\033[0;31m'
|
||||||
BLUE='\\033[0;34m'
|
# export GREEN='\\033[0;32m'
|
||||||
PURPLE='\\033[0;35m'
|
# export BROWN='\\033[0;33m'
|
||||||
CYAN='\\033[0;36m'
|
# export BLUE='\\033[0;34m'
|
||||||
LIGHT_GREY='\\033[0;37m'
|
# export PURPLE='\\033[0;35m'
|
||||||
DARK_GREY='\\033[1;30m'
|
# export CYAN='\\033[0;36m'
|
||||||
LIGHT_RED='\\033[1;31m'
|
# export LIGHT_GREY='\\033[0;37m'
|
||||||
LIGHT_GREEN='\\033[1;32m'
|
# export DARK_GREY='\\033[1;30m'
|
||||||
YELLOW='\\033[1;33m'
|
# export LIGHT_RED='\\033[1;31m'
|
||||||
LIGHT_BLUE='\\033[1;34m'
|
# export LIGHT_GREEN='\\033[1;32m'
|
||||||
LIGHT_PURPLE='\\033[1;35m'
|
# export YELLOW='\\033[1;33m'
|
||||||
LIGHT_CYAN='\\033[1;36m'
|
# export LIGHT_BLUE='\\033[1;34m'
|
||||||
WHITE='\\033[1;37m'
|
# export LIGHT_PURPLE='\\033[1;35m'
|
||||||
DEFAULT='\\033[0m'
|
# export LIGHT_CYAN='\\033[1;36m'
|
||||||
|
# export WHITE='\\033[1;37m'
|
||||||
|
# export DEFAULT='\\033[0m'
|
||||||
|
|
||||||
# === PRIORITY COLORS ===
|
# === PRIORITY COLORS ===
|
||||||
|
|
||||||
PRI_A=$YELLOW # color for A priority
|
## Priorities can be any upper-case letter.
|
||||||
PRI_B=$GREEN # color for B priority
|
## Colors are supported for the first three.
|
||||||
PRI_C=$LIGHT_BLUE # color for C priority
|
## Uncomment and edit to override these defaults.
|
||||||
PRI_X=$WHITE # color for rest of them
|
|
||||||
|
# export PRI_A=$YELLOW # color for A priority
|
||||||
|
# export PRI_B=$GREEN # color for B priority
|
||||||
|
# export PRI_C=$LIGHT_BLUE # color for C priority
|
||||||
|
# export PRI_X=$WHITE # color for rest of them
|
||||||
|
|
||||||
|
# === BEHAVIOR ===
|
||||||
|
|
||||||
|
## customize list output
|
||||||
|
# export TODOTXT_SORT_COMMAND='env LC_COLLATE=C sort -f -k2'
|
||||||
|
|||||||
623
todo.sh
Normal file → Executable file
623
todo.sh
Normal file → Executable file
@@ -3,32 +3,67 @@
|
|||||||
# NOTE: Todo.sh requires the todo.cfg configuration file to run.
|
# NOTE: Todo.sh requires the todo.cfg configuration file to run.
|
||||||
# Place the todo.cfg file in your home directory or use the -d option for a custom location.
|
# Place the todo.cfg file in your home directory or use the -d option for a custom location.
|
||||||
|
|
||||||
|
[ -f VERSION-FILE ] && . VERSION-FILE || VERSION="@DEV_VERSION@"
|
||||||
version() { sed -e 's/^ //' <<EndVersion
|
version() { sed -e 's/^ //' <<EndVersion
|
||||||
TODO.TXT Manager
|
TODO.TXT Command Line Interface v$VERSION
|
||||||
Version 2.1
|
|
||||||
Author: Gina Trapani (ginatrapani@gmail.com)
|
First release: 5/11/2006
|
||||||
Last updated: 2/23/2009
|
Original conception by: Gina Trapani (http://ginatrapani.org)
|
||||||
Release date: 5/11/2006
|
Contributors: http://github.com/ginatrapani/todo.txt-cli/network
|
||||||
License: GPL, http://www.gnu.org/copyleft/gpl.html
|
License: GPL, http://www.gnu.org/copyleft/gpl.html
|
||||||
More information and mailing list at http://todotxt.com
|
More information and mailing list at http://todotxt.com
|
||||||
|
Code repository: http://github.com/ginatrapani/todo.txt-cli/tree/master
|
||||||
EndVersion
|
EndVersion
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oneline_usage="todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]"
|
||||||
|
|
||||||
usage()
|
usage()
|
||||||
{
|
{
|
||||||
sed -e 's/^ //' <<EndUsage
|
sed -e 's/^ //' <<EndUsage
|
||||||
Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
|
Usage: $oneline_usage
|
||||||
Try 'todo.sh -h' for more information.
|
Try 'todo.sh -h' for more information.
|
||||||
EndUsage
|
EndUsage
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shorthelp()
|
||||||
|
{
|
||||||
|
sed -e 's/^ //' <<EndHelp
|
||||||
|
Usage: $oneline_usage
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
add|a "THING I NEED TO DO +project @context"
|
||||||
|
addto DEST "TEXT TO ADD"
|
||||||
|
append|app NUMBER "TEXT TO APPEND"
|
||||||
|
archive
|
||||||
|
command [ACTIONS]
|
||||||
|
del|rm NUMBER [TERM]
|
||||||
|
dp|depri NUMBER
|
||||||
|
do NUMBER
|
||||||
|
help
|
||||||
|
list|ls [TERM...]
|
||||||
|
listall|lsa [TERM...]
|
||||||
|
listcon|lsc
|
||||||
|
listfile|lf SRC [TERM...]
|
||||||
|
listpri|lsp [PRIORITY]
|
||||||
|
listproj|lsprj
|
||||||
|
move|mv NUMBER DEST [SRC]
|
||||||
|
prepend|prep NUMBER "TEXT TO PREPEND"
|
||||||
|
pri|p NUMBER PRIORITY
|
||||||
|
replace NUMBER "UPDATED TODO"
|
||||||
|
report
|
||||||
|
|
||||||
|
See "help" for more details.
|
||||||
|
EndHelp
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
help()
|
help()
|
||||||
{
|
{
|
||||||
sed -e 's/^ //' <<EndHelp
|
sed -e 's/^ //' <<EndHelp
|
||||||
Usage: todo.sh [-fhpantvV] [-d todo_config] action [task_number] [task_description]
|
Usage: $oneline_usage
|
||||||
|
|
||||||
Actions:
|
Actions:
|
||||||
add "THING I NEED TO DO +project @context"
|
add "THING I NEED TO DO +project @context"
|
||||||
@@ -49,6 +84,10 @@ help()
|
|||||||
archive
|
archive
|
||||||
Moves done items from todo.txt to done.txt and removes blank lines.
|
Moves done items from todo.txt to done.txt and removes blank lines.
|
||||||
|
|
||||||
|
command [ACTIONS]
|
||||||
|
Runs the remaining arguments using only todo.sh builtins.
|
||||||
|
Will not call any .todo.actions.d scripts.
|
||||||
|
|
||||||
del NUMBER [TERM]
|
del NUMBER [TERM]
|
||||||
rm NUMBER [TERM]
|
rm NUMBER [TERM]
|
||||||
Deletes the item on line NUMBER in todo.txt.
|
Deletes the item on line NUMBER in todo.txt.
|
||||||
@@ -62,6 +101,9 @@ help()
|
|||||||
do NUMBER
|
do NUMBER
|
||||||
Marks item on line NUMBER as done in todo.txt.
|
Marks item on line NUMBER as done in todo.txt.
|
||||||
|
|
||||||
|
help
|
||||||
|
Display this help message.
|
||||||
|
|
||||||
list [TERM...]
|
list [TERM...]
|
||||||
ls [TERM...]
|
ls [TERM...]
|
||||||
Displays all todo's that contain TERM(s) sorted by priority with line
|
Displays all todo's that contain TERM(s) sorted by priority with line
|
||||||
@@ -119,14 +161,23 @@ help()
|
|||||||
|
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
-@
|
||||||
|
Hide context names in list output. Use twice to show context
|
||||||
|
names (default).
|
||||||
|
-+
|
||||||
|
Hide project names in list output. Use twice to show project
|
||||||
|
names (default).
|
||||||
-d CONFIG_FILE
|
-d CONFIG_FILE
|
||||||
Use a configuration file other than the default ~/todo.cfg
|
Use a configuration file other than the default ~/todo.cfg
|
||||||
-f
|
-f
|
||||||
Forces actions without confirmation or interactive input
|
Forces actions without confirmation or interactive input
|
||||||
-h
|
-h
|
||||||
Display this help message
|
Display a short help message
|
||||||
-p
|
-p
|
||||||
Plain mode turns off colors
|
Plain mode turns off colors
|
||||||
|
-P
|
||||||
|
Hide priority labels in list output. Use twice to show
|
||||||
|
priority labels (default).
|
||||||
-a
|
-a
|
||||||
Don't auto-archive tasks automatically on completion
|
Don't auto-archive tasks automatically on completion
|
||||||
-n
|
-n
|
||||||
@@ -137,18 +188,32 @@ help()
|
|||||||
when it's added.
|
when it's added.
|
||||||
-v
|
-v
|
||||||
Verbose mode turns on confirmation messages
|
Verbose mode turns on confirmation messages
|
||||||
|
-vv
|
||||||
|
Extra verbose mode prints some debugging information
|
||||||
-V
|
-V
|
||||||
Displays version, license and credits
|
Displays version, license and credits
|
||||||
|
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
TODOTXT_AUTO_ARCHIVE=0 is same as option -a
|
||||||
|
TODOTXT_CFG_FILE=CONFIG_FILE is same as option -d CONFIG_FILE
|
||||||
|
TODOTXT_FORCE=1 is same as option -f
|
||||||
|
TODOTXT_PRESERVE_LINE_NUMBERS=0 is same as option -n
|
||||||
|
TODOTXT_PLAIN=1 is same as option -p
|
||||||
|
TODOTXT_DATE_ON_ADD=1 is same as option -t
|
||||||
|
TODOTXT_VERBOSE=1 is same as option -v
|
||||||
|
TODOTXT_DEFAULT_ACTION="" run this when called with no arguments
|
||||||
|
TODOTXT_SORT_COMMAND="sort ..." customize list output
|
||||||
EndHelp
|
EndHelp
|
||||||
|
|
||||||
if [ -d "$HOME/.todo.actions.d" ]
|
if [ -d "$TODO_ACTIONS_DIR" ]
|
||||||
then
|
then
|
||||||
echo ""
|
echo ""
|
||||||
for action in $HOME/.todo.actions.d/*
|
for action in "$TODO_ACTIONS_DIR"/*
|
||||||
do
|
do
|
||||||
if [ -x $action ]
|
if [ -x "$action" ]
|
||||||
then
|
then
|
||||||
$action usage
|
"$action" usage
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo ""
|
echo ""
|
||||||
@@ -174,53 +239,92 @@ archive()
|
|||||||
{
|
{
|
||||||
#defragment blank lines
|
#defragment blank lines
|
||||||
sed -i.bak -e '/./!d' "$TODO_FILE"
|
sed -i.bak -e '/./!d' "$TODO_FILE"
|
||||||
[[ $VERBOSE = 1 ]] && grep "^x " "$TODO_FILE"
|
[ $TODOTXT_VERBOSE -gt 0 ] && grep "^x " "$TODO_FILE"
|
||||||
grep "^x " "$TODO_FILE" >> "$DONE_FILE"
|
grep "^x " "$TODO_FILE" >> "$DONE_FILE"
|
||||||
sed -i.bak '/^x /d' "$TODO_FILE"
|
sed -i.bak '/^x /d' "$TODO_FILE"
|
||||||
cp "$TODO_FILE" "$TMP_FILE"
|
cp "$TODO_FILE" "$TMP_FILE"
|
||||||
sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P' "$TMP_FILE" > "$TODO_FILE"
|
sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P' "$TMP_FILE" > "$TODO_FILE"
|
||||||
#[[ $VERBOSE = 1 ]] && echo "TODO: Duplicate tasks have been removed."
|
#[[ $TODOTXT_VERBOSE -gt 0 ]] && echo "TODO: Duplicate tasks have been removed."
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: $TODO_FILE archived."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $TODO_FILE archived."
|
||||||
cleanup
|
cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# == PROCESS OPTIONS ==
|
# == PROCESS OPTIONS ==
|
||||||
# defaults
|
while getopts ":fhpnatvV+@Pd:" Option
|
||||||
VERBOSE=1
|
|
||||||
PLAIN=0
|
|
||||||
CFG_FILE=$HOME/todo.cfg
|
|
||||||
FORCE=0
|
|
||||||
PRESERVE_LINE_NUMBERS=1
|
|
||||||
AUTO_ARCHIVE=1
|
|
||||||
DATE_ON_ADD=0
|
|
||||||
|
|
||||||
while getopts ":fhpnatvVd:" Option
|
|
||||||
do
|
do
|
||||||
case $Option in
|
case $Option in
|
||||||
|
'@' )
|
||||||
|
## HIDE_CONTEXT_NAMES starts at zero (false); increment it to one
|
||||||
|
## (true) the first time this flag is seen. Each time the flag
|
||||||
|
## is seen after that, increment it again so that an even
|
||||||
|
## number hides project names and an odd number shows project
|
||||||
|
## names.
|
||||||
|
: $(( HIDE_CONTEXT_NAMES++ ))
|
||||||
|
if [ $(( $HIDE_CONTEXT_NAMES % 2 )) -eq 0 ]
|
||||||
|
then
|
||||||
|
## Zero or even value -- show context names
|
||||||
|
unset HIDE_CONTEXTS_SUBSTITUTION
|
||||||
|
else
|
||||||
|
## One or odd value -- hide context names
|
||||||
|
export HIDE_CONTEXTS_SUBSTITUTION='[[:space:]]@[^[:space:]]\{1,\}'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
'+' )
|
||||||
|
## HIDE_PROJECT_NAMES starts at zero (false); increment it to one
|
||||||
|
## (true) the first time this flag is seen. Each time the flag
|
||||||
|
## is seen after that, increment it again so that an even
|
||||||
|
## number hides project names and an odd number shows project
|
||||||
|
## names.
|
||||||
|
: $(( HIDE_PROJECT_NAMES++ ))
|
||||||
|
if [ $(( $HIDE_PROJECT_NAMES % 2 )) -eq 0 ]
|
||||||
|
then
|
||||||
|
## Zero or even value -- show project names
|
||||||
|
unset HIDE_PROJECTS_SUBSTITUTION
|
||||||
|
else
|
||||||
|
## One or odd value -- hide project names
|
||||||
|
export HIDE_PROJECTS_SUBSTITUTION='[[:space:]][+][^[:space:]]\{1,\}'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
a )
|
a )
|
||||||
AUTO_ARCHIVE=0
|
TODOTXT_AUTO_ARCHIVE=0
|
||||||
;;
|
;;
|
||||||
d )
|
d )
|
||||||
CFG_FILE=$OPTARG
|
TODOTXT_CFG_FILE=$OPTARG
|
||||||
;;
|
;;
|
||||||
f )
|
f )
|
||||||
FORCE=1
|
TODOTXT_FORCE=1
|
||||||
;;
|
;;
|
||||||
h )
|
h )
|
||||||
help
|
shorthelp
|
||||||
;;
|
;;
|
||||||
n )
|
n )
|
||||||
PRESERVE_LINE_NUMBERS=0
|
TODOTXT_PRESERVE_LINE_NUMBERS=0
|
||||||
;;
|
;;
|
||||||
p )
|
p )
|
||||||
PLAIN=1
|
TODOTXT_PLAIN=1
|
||||||
|
;;
|
||||||
|
P )
|
||||||
|
## HIDE_PRIORITY_LABELS starts at zero (false); increment it to one
|
||||||
|
## (true) the first time this flag is seen. Each time the flag
|
||||||
|
## is seen after that, increment it again so that an even
|
||||||
|
## number hides project names and an odd number shows project
|
||||||
|
## names.
|
||||||
|
: $(( HIDE_PRIORITY_LABELS++ ))
|
||||||
|
if [ $(( $HIDE_PRIORITY_LABELS % 2 )) -eq 0 ]
|
||||||
|
then
|
||||||
|
## Zero or even value -- show priority labels
|
||||||
|
unset HIDE_PRIORITY_SUBSTITUTION
|
||||||
|
else
|
||||||
|
## One or odd value -- hide priority labels
|
||||||
|
export HIDE_PRIORITY_SUBSTITUTION="([A-Z])[[:space:]]"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
t )
|
t )
|
||||||
DATE_ON_ADD=1
|
TODOTXT_DATE_ON_ADD=1
|
||||||
;;
|
;;
|
||||||
v )
|
v )
|
||||||
VERBOSE=1
|
: $(( TODOTXT_VERBOSE++ ))
|
||||||
;;
|
;;
|
||||||
V )
|
V )
|
||||||
version
|
version
|
||||||
@@ -229,31 +333,80 @@ do
|
|||||||
done
|
done
|
||||||
shift $(($OPTIND - 1))
|
shift $(($OPTIND - 1))
|
||||||
|
|
||||||
[ -e "$CFG_FILE" ] || {
|
# defaults if not yet defined
|
||||||
|
TODOTXT_VERBOSE=${TODOTXT_VERBOSE:-1}
|
||||||
|
TODOTXT_PLAIN=${TODOTXT_PLAIN:-0}
|
||||||
|
TODOTXT_CFG_FILE=${TODOTXT_CFG_FILE:-$HOME/todo.cfg}
|
||||||
|
TODOTXT_FORCE=${TODOTXT_FORCE:-0}
|
||||||
|
TODOTXT_PRESERVE_LINE_NUMBERS=${TODOTXT_PRESERVE_LINE_NUMBERS:-1}
|
||||||
|
TODOTXT_AUTO_ARCHIVE=${TODOTXT_AUTO_ARCHIVE:-1}
|
||||||
|
TODOTXT_DATE_ON_ADD=${TODOTXT_DATE_ON_ADD:-0}
|
||||||
|
TODOTXT_DEFAULT_ACTION=${TODOTXT_DEFAULT_ACTION:-}
|
||||||
|
TODOTXT_SORT_COMMAND=${TODOTXT_SORT_COMMAND:-env LC_COLLATE=C sort -f -k2}
|
||||||
|
|
||||||
|
export TODOTXT_VERBOSE TODOTXT_PLAIN TODOTXT_CFG_FILE TODOTXT_FORCE TODOTXT_PRESERVE_LINE_NUMBERS TODOTXT_AUTO_ARCHIVE TODOTXT_DATE_ON_ADD TODOTXT_SORT_COMMAND
|
||||||
|
|
||||||
|
# Default color map
|
||||||
|
export NONE=''
|
||||||
|
export BLACK='\\033[0;30m'
|
||||||
|
export RED='\\033[0;31m'
|
||||||
|
export GREEN='\\033[0;32m'
|
||||||
|
export BROWN='\\033[0;33m'
|
||||||
|
export BLUE='\\033[0;34m'
|
||||||
|
export PURPLE='\\033[0;35m'
|
||||||
|
export CYAN='\\033[0;36m'
|
||||||
|
export LIGHT_GREY='\\033[0;37m'
|
||||||
|
export DARK_GREY='\\033[1;30m'
|
||||||
|
export LIGHT_RED='\\033[1;31m'
|
||||||
|
export LIGHT_GREEN='\\033[1;32m'
|
||||||
|
export YELLOW='\\033[1;33m'
|
||||||
|
export LIGHT_BLUE='\\033[1;34m'
|
||||||
|
export LIGHT_PURPLE='\\033[1;35m'
|
||||||
|
export LIGHT_CYAN='\\033[1;36m'
|
||||||
|
export WHITE='\\033[1;37m'
|
||||||
|
export DEFAULT='\\033[0m'
|
||||||
|
|
||||||
|
# Default priority->color map.
|
||||||
|
export PRI_A=$YELLOW # color for A priority
|
||||||
|
export PRI_B=$GREEN # color for B priority
|
||||||
|
export PRI_C=$LIGHT_BLUE # color for C priority
|
||||||
|
export PRI_X=$WHITE # color for rest of them
|
||||||
|
|
||||||
|
[ -e "$TODOTXT_CFG_FILE" ] || {
|
||||||
CFG_FILE_ALT="$HOME/.todo.cfg"
|
CFG_FILE_ALT="$HOME/.todo.cfg"
|
||||||
|
|
||||||
if [ -e "$CFG_FILE_ALT" ]
|
if [ -e "$CFG_FILE_ALT" ]
|
||||||
then
|
then
|
||||||
CFG_FILE="$CFG_FILE_ALT"
|
TODOTXT_CFG_FILE="$CFG_FILE_ALT"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if [ -z "$TODO_ACTIONS_DIR" -o ! -d "$TODO_ACTIONS_DIR" ]
|
||||||
|
then
|
||||||
|
TODO_ACTIONS_DIR="$HOME/.todo.actions.d"
|
||||||
|
export TODO_ACTIONS_DIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
TODO_SH="$0"
|
||||||
|
export TODO_SH
|
||||||
|
|
||||||
# === SANITY CHECKS (thanks Karl!) ===
|
# === SANITY CHECKS (thanks Karl!) ===
|
||||||
[ -r "$CFG_FILE" ] || die "Fatal error: Cannot read configuration file $CFG_FILE"
|
[ -r "$TODOTXT_CFG_FILE" ] || die "Fatal error: Cannot read configuration file $TODOTXT_CFG_FILE"
|
||||||
|
|
||||||
. "$CFG_FILE"
|
. "$TODOTXT_CFG_FILE"
|
||||||
|
|
||||||
[ -z "$1" ] && usage
|
ACTION=${1:-$TODOTXT_DEFAULT_ACTION}
|
||||||
|
|
||||||
|
[ -z "$ACTION" ] && usage
|
||||||
[ -d "$TODO_DIR" ] || die "Fatal Error: $TODO_DIR is not a directory"
|
[ -d "$TODO_DIR" ] || die "Fatal Error: $TODO_DIR is not a directory"
|
||||||
cd "$TODO_DIR" || die "Fatal Error: Unable to cd to $TODO_DIR"
|
( cd "$TODO_DIR" ) || die "Fatal Error: Unable to cd to $TODO_DIR"
|
||||||
|
|
||||||
echo '' > "$TMP_FILE" || die "Fatal Error: Unable to write in $TODO_DIR"
|
[ -w "$TMP_FILE" ] || echo -n > "$TMP_FILE" || die "Fatal Error: Unable to write to $TMP_FILE"
|
||||||
[ -f "$TODO_FILE" ] || cp /dev/null "$TODO_FILE"
|
[ -f "$TODO_FILE" ] || cp /dev/null "$TODO_FILE"
|
||||||
[ -f "$DONE_FILE" ] || cp /dev/null "$DONE_FILE"
|
[ -f "$DONE_FILE" ] || cp /dev/null "$DONE_FILE"
|
||||||
[ -f "$REPORT_FILE" ] || cp /dev/null "$REPORT_FILE"
|
[ -f "$REPORT_FILE" ] || cp /dev/null "$REPORT_FILE"
|
||||||
|
|
||||||
|
if [ $TODOTXT_PLAIN = 1 ]; then
|
||||||
if [ $PLAIN = 1 ]; then
|
|
||||||
PRI_A=$NONE
|
PRI_A=$NONE
|
||||||
PRI_B=$NONE
|
PRI_B=$NONE
|
||||||
PRI_C=$NONE
|
PRI_C=$NONE
|
||||||
@@ -264,12 +417,138 @@ fi
|
|||||||
# === HEAVY LIFTING ===
|
# === HEAVY LIFTING ===
|
||||||
shopt -s extglob
|
shopt -s extglob
|
||||||
|
|
||||||
# == HANDLE ACTION ==
|
_list() {
|
||||||
action=$( printf "%s\n" "$1" | tr 'A-Z' 'a-z' )
|
local FILE="$1"
|
||||||
|
## If the file starts with a "/" use absolute path. Otherwise,
|
||||||
|
## try to find it in either $TODO_DIR or using a relative path
|
||||||
|
if [ "${1:0:1}" == / ]
|
||||||
|
then
|
||||||
|
## Absolute path
|
||||||
|
src="$FILE"
|
||||||
|
elif [ -f "$TODO_DIR/$FILE" ]
|
||||||
|
then
|
||||||
|
## Path relative to todo.sh directory
|
||||||
|
src="$TODO_DIR/$1"
|
||||||
|
elif [ -f "$FILE" ]
|
||||||
|
then
|
||||||
|
## Path relative to current working directory
|
||||||
|
src="$FILE"
|
||||||
|
else
|
||||||
|
echo "TODO: File $FILE does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Get our search arguments, if any
|
||||||
|
shift ## was file name, new $1 is first search term
|
||||||
|
|
||||||
|
## Prefix the filter_command with the pre_filter_command
|
||||||
|
filter_command="${pre_filter_command:-}"
|
||||||
|
|
||||||
|
for search_term in "$@"
|
||||||
|
do
|
||||||
|
## See if the first character of $search_term is a dash
|
||||||
|
if [ ${search_term:0:1} != '-' ]
|
||||||
|
then
|
||||||
|
## First character isn't a dash: hide lines that don't match
|
||||||
|
## this $search_term
|
||||||
|
filter_command="${filter_command:-} ${filter_command:+|} \
|
||||||
|
grep -i \"$search_term\" "
|
||||||
|
else
|
||||||
|
## First character is a dash: hide lines that match this
|
||||||
|
## $search_term
|
||||||
|
#
|
||||||
|
## Remove the first character (-) before adding to our filter command
|
||||||
|
filter_command="${filter_command:-} ${filter_command:+|} \
|
||||||
|
grep -v -i \"${search_term:1}\" "
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
## If post_filter_command is set, append it to the filter_command
|
||||||
|
[ -n "$post_filter_command" ] && {
|
||||||
|
filter_command="${filter_command:-}${filter_command:+ | }${post_filter_command:-}"
|
||||||
|
}
|
||||||
|
|
||||||
|
## Figure out how much padding we need to use
|
||||||
|
## We need one level of padding for each power of 10 $LINES uses
|
||||||
|
LINES=$( sed -n '$ =' "$src" )
|
||||||
|
PADDING=${#LINES}
|
||||||
|
|
||||||
|
## Number the file, then run the filter command,
|
||||||
|
## then sort and mangle output some more
|
||||||
|
items=$(
|
||||||
|
sed = "$src" \
|
||||||
|
| sed "N; s/^/ /; s/ *\(.\{$PADDING,\}\)\n/\1 /" \
|
||||||
|
| grep -v "^[0-9]\+ *$"
|
||||||
|
)
|
||||||
|
if [ "${filter_command}" ]; then
|
||||||
|
filtered_items=$(echo -ne "$items" | eval ${filter_command})
|
||||||
|
else
|
||||||
|
filtered_items=$items
|
||||||
|
fi
|
||||||
|
filtered_items=$(
|
||||||
|
echo -ne "$filtered_items" \
|
||||||
|
| sed '''
|
||||||
|
s/^ /00000/;
|
||||||
|
s/^ /0000/;
|
||||||
|
s/^ /000/;
|
||||||
|
s/^ /00/;
|
||||||
|
s/^ /0/;
|
||||||
|
''' \
|
||||||
|
| ${TODOTXT_SORT_COMMAND} \
|
||||||
|
| sed '''
|
||||||
|
/^[0-9]\{'$PADDING'\} x /! {
|
||||||
|
s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g;
|
||||||
|
s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g;
|
||||||
|
s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g;
|
||||||
|
s/\(.*([D-Z]).*\)/'$PRI_X'\1'$DEFAULT'/g;
|
||||||
|
}
|
||||||
|
''' \
|
||||||
|
| sed '''
|
||||||
|
s/'${HIDE_PRIORITY_SUBSTITUTION:-^}'//g
|
||||||
|
s/'${HIDE_PROJECTS_SUBSTITUTION:-^}'//g
|
||||||
|
s/'${HIDE_CONTEXTS_SUBSTITUTION:-^}'//g
|
||||||
|
''' \
|
||||||
|
)
|
||||||
|
echo -ne "$filtered_items${filtered_items:+\n}"
|
||||||
|
|
||||||
|
if [ $TODOTXT_VERBOSE -gt 0 ]; then
|
||||||
|
NUMTASKS=$( echo -ne "$filtered_items" | sed -n '$ =' )
|
||||||
|
TOTALTASKS=$( echo -ne "$items" | sed -n '$ =' )
|
||||||
|
|
||||||
|
echo "--"
|
||||||
|
echo "TODO: ${NUMTASKS:-0} of ${TOTALTASKS:-0} tasks shown from $FILE"
|
||||||
|
fi
|
||||||
|
if [ $TODOTXT_VERBOSE -gt 1 ]
|
||||||
|
then
|
||||||
|
echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
export -f _list
|
||||||
|
|
||||||
|
# == HANDLE ACTION ==
|
||||||
|
action=$( printf "%s\n" "$ACTION" | tr 'A-Z' 'a-z' )
|
||||||
|
|
||||||
|
## If the first argument is "command", run the rest of the arguments
|
||||||
|
## using todo.sh builtins.
|
||||||
|
## Else, run a actions script with the name of the command if it exists
|
||||||
|
## or fallback to using a builtin
|
||||||
|
if [ "$action" == command ]
|
||||||
|
then
|
||||||
|
## Get rid of "command" from arguments list
|
||||||
|
shift
|
||||||
|
## Reset action to new first argument
|
||||||
|
action=$( printf "%s\n" "$1" | tr 'A-Z' 'a-z' )
|
||||||
|
elif [ -d "$TODO_ACTIONS_DIR" -a -x "$TODO_ACTIONS_DIR/$action" ]
|
||||||
|
then
|
||||||
|
"$TODO_ACTIONS_DIR/$action" "$@"
|
||||||
|
cleanup
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Only run if $action isn't found in .todo.actions.d
|
||||||
case $action in
|
case $action in
|
||||||
"add" | "a")
|
"add" | "a")
|
||||||
if [[ -z "$2" && $FORCE = 0 ]]; then
|
if [[ -z "$2" && $TODOTXT_FORCE = 0 ]]; then
|
||||||
echo -n "Add: "
|
echo -n "Add: "
|
||||||
read input
|
read input
|
||||||
else
|
else
|
||||||
@@ -278,35 +557,27 @@ case $action in
|
|||||||
input=$*
|
input=$*
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ $TODOTXT_DATE_ON_ADD = 1 ]]; then
|
||||||
if [[ $DATE_ON_ADD = 1 ]]; then
|
|
||||||
now=`date '+%Y-%m-%d'`
|
now=`date '+%Y-%m-%d'`
|
||||||
input="$now $input"
|
input="$now $input"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$input" >> "$TODO_FILE"
|
echo "$input" >> "$TODO_FILE"
|
||||||
|
TASKNUM=$(sed -n '$ =' "$TODO_FILE")
|
||||||
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$input' added on line $TASKNUM."
|
||||||
|
|
||||||
TASKNUM=$(wc -l "$TODO_FILE" | sed 's/^[[:space:]]*\([0-9]*\).*/\1/')
|
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: '$input' added on line $TASKNUM."
|
|
||||||
cleanup;;
|
cleanup;;
|
||||||
|
|
||||||
"addto" )
|
"addto" )
|
||||||
[ -z "$2" ] && die "usage: $0 addto DEST \"TODO ITEM\""
|
[ -z "$2" ] && die "usage: $0 addto DEST \"TODO ITEM\""
|
||||||
dest="$TODO_DIR/$2"
|
dest="$TODO_DIR/$2"
|
||||||
|
|
||||||
[ -z "$3" ] && die "usage: $0 addto DEST \"TODO ITEM\""
|
[ -z "$3" ] && die "usage: $0 addto DEST \"TODO ITEM\""
|
||||||
shift
|
shift
|
||||||
shift
|
shift
|
||||||
input=$*
|
input=$*
|
||||||
|
|
||||||
if [ -f "$dest" ]; then
|
if [ -f "$dest" ]; then
|
||||||
|
|
||||||
echo "$input" >> "$dest"
|
echo "$input" >> "$dest"
|
||||||
|
TASKNUM=$(sed -n '$ =' "$dest")
|
||||||
TASKNUM=$(wc -l "$dest" | sed 's/^[[:space:]]*\([0-9]*\).*/\1/')
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$input' added to $dest on line $TASKNUM."
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: '$input' added to $dest on line $TASKNUM."
|
|
||||||
else
|
else
|
||||||
echo "TODO: Destination file $dest does not exist."
|
echo "TODO: Destination file $dest does not exist."
|
||||||
fi
|
fi
|
||||||
@@ -318,21 +589,17 @@ case $action in
|
|||||||
|
|
||||||
[ -z "$item" ] && die "$errmsg"
|
[ -z "$item" ] && die "$errmsg"
|
||||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||||
|
|
||||||
todo=$(sed "$item!d" "$TODO_FILE")
|
todo=$(sed "$item!d" "$TODO_FILE")
|
||||||
[ -z "$todo" ] && die "$item: No such todo."
|
[ -z "$todo" ] && die "$item: No such todo."
|
||||||
|
if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
|
||||||
|
|
||||||
if [[ -z "$1" && $FORCE = 0 ]]; then
|
|
||||||
echo -n "Append: "
|
echo -n "Append: "
|
||||||
read input
|
read input
|
||||||
else
|
else
|
||||||
input=$*
|
input=$*
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if sed -i.bak $item" s|^.*|& $input|" "$TODO_FILE"; then
|
if sed -i.bak $item" s|^.*|& $input|" "$TODO_FILE"; then
|
||||||
newtodo=$(sed "$item!d" "$TODO_FILE")
|
newtodo=$(sed "$item!d" "$TODO_FILE")
|
||||||
[[ $VERBOSE = 1 ]] && echo "$item: $newtodo"
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
|
||||||
else
|
else
|
||||||
echo "TODO: Error appending task $item."
|
echo "TODO: Error appending task $item."
|
||||||
fi
|
fi
|
||||||
@@ -342,7 +609,7 @@ case $action in
|
|||||||
archive;;
|
archive;;
|
||||||
|
|
||||||
"del" | "rm" )
|
"del" | "rm" )
|
||||||
# replace deleted line with a blank line when PRESERVE_LINE_NUMBERS is 1
|
# replace deleted line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
|
||||||
errmsg="usage: $0 del ITEM#"
|
errmsg="usage: $0 del ITEM#"
|
||||||
item=$2
|
item=$2
|
||||||
[ -z "$item" ] && die "$errmsg"
|
[ -z "$item" ] && die "$errmsg"
|
||||||
@@ -351,26 +618,24 @@ case $action in
|
|||||||
|
|
||||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||||
if sed -ne "$item p" "$TODO_FILE" | grep "^."; then
|
if sed -ne "$item p" "$TODO_FILE" | grep "^."; then
|
||||||
DELETEME=$(sed "$2!d" "$TODO_FILE")
|
DELETEME=$(sed "$item!d" "$TODO_FILE")
|
||||||
|
|
||||||
if [ $FORCE = 0 ]; then
|
if [ $TODOTXT_FORCE = 0 ]; then
|
||||||
echo "Delete '$DELETEME'? (y/n)"
|
echo "Delete '$DELETEME'? (y/n)"
|
||||||
read ANSWER
|
read ANSWER
|
||||||
else
|
else
|
||||||
ANSWER="y"
|
ANSWER="y"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ANSWER" = "y" ]; then
|
if [ "$ANSWER" = "y" ]; then
|
||||||
if [ $PRESERVE_LINE_NUMBERS = 0 ]; then
|
if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
|
||||||
# delete line (changes line numbers)
|
# delete line (changes line numbers)
|
||||||
sed -i.bak -e $2"s/^.*//" -e '/./!d' "$TODO_FILE"
|
sed -i.bak -e $item"s/^.*//" -e '/./!d' "$TODO_FILE"
|
||||||
else
|
else
|
||||||
# leave blank line behind (preserves line numbers)
|
# leave blank line behind (preserves line numbers)
|
||||||
sed -i.bak -e $2"s/^.*//" "$TODO_FILE"
|
sed -i.bak -e $item"s/^.*//" "$TODO_FILE"
|
||||||
fi
|
fi
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: '$DELETEME' deleted."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$DELETEME' deleted."
|
||||||
cleanup
|
cleanup
|
||||||
|
|
||||||
else
|
else
|
||||||
echo "TODO: No tasks were deleted."
|
echo "TODO: No tasks were deleted."
|
||||||
fi
|
fi
|
||||||
@@ -379,7 +644,7 @@ case $action in
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
sed -i.bak -e $item"s/$3/ /g" "$TODO_FILE"
|
sed -i.bak -e $item"s/$3/ /g" "$TODO_FILE"
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: $3 removed from $item."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $3 removed from $item."
|
||||||
fi ;;
|
fi ;;
|
||||||
|
|
||||||
"depri" | "dp" )
|
"depri" | "dp" )
|
||||||
@@ -390,14 +655,14 @@ case $action in
|
|||||||
[ -z "$todo" ] && die "$item: No such todo."
|
[ -z "$todo" ] && die "$item: No such todo."
|
||||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||||
|
|
||||||
sed -e $item"s/^(.*) //" "$TODO_FILE" > /dev/null 2>&1
|
sed -e $item"s/^(.) //" "$TODO_FILE" > /dev/null 2>&1
|
||||||
|
|
||||||
if [ "$?" -eq 0 ]; then
|
if [ "$?" -eq 0 ]; then
|
||||||
#it's all good, continue
|
#it's all good, continue
|
||||||
sed -i.bak -e $2"s/^(.*) //" "$TODO_FILE"
|
sed -i.bak -e $item"s/^(.) //" "$TODO_FILE"
|
||||||
NEWTODO=$(sed "$2!d" "$TODO_FILE")
|
NEWTODO=$(sed "$item!d" "$TODO_FILE")
|
||||||
[[ $VERBOSE = 1 ]] && echo -e "`echo "$item: $NEWTODO"`"
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo -e "`echo "$item: $NEWTODO"`"
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: $item deprioritized."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item deprioritized."
|
||||||
cleanup
|
cleanup
|
||||||
else
|
else
|
||||||
die "$errmsg"
|
die "$errmsg"
|
||||||
@@ -414,144 +679,83 @@ case $action in
|
|||||||
|
|
||||||
now=`date '+%Y-%m-%d'`
|
now=`date '+%Y-%m-%d'`
|
||||||
# remove priority once item is done
|
# remove priority once item is done
|
||||||
sed -i.bak $item"s/^(.*) //" "$TODO_FILE"
|
sed -i.bak $item"s/^(.) //" "$TODO_FILE"
|
||||||
sed -i.bak $item"s|^|&x $now |" "$TODO_FILE"
|
sed -i.bak $item"s|^|&x $now |" "$TODO_FILE"
|
||||||
newtodo=$(sed "$item!d" "$TODO_FILE")
|
newtodo=$(sed "$item!d" "$TODO_FILE")
|
||||||
[[ $VERBOSE = 1 ]] && echo "$item: $newtodo"
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: $item marked as done."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item marked as done."
|
||||||
|
|
||||||
if [ $AUTO_ARCHIVE = 1 ]; then
|
if [ $TODOTXT_AUTO_ARCHIVE = 1 ]; then
|
||||||
archive
|
archive
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cleanup ;;
|
cleanup ;;
|
||||||
|
|
||||||
|
"help" )
|
||||||
|
help
|
||||||
|
;;
|
||||||
|
|
||||||
"list" | "ls" )
|
"list" | "ls" )
|
||||||
item=$2
|
shift ## Was ls; new $1 is first search term
|
||||||
if [ -z "$item" ]; then
|
_list "$TODO_FILE" "$@"
|
||||||
echo -e "`sed = "$TODO_FILE" | sed 'N; s/^/ /; s/ *\(.\{2,\}\)\n/\1 /' | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/'`"
|
|
||||||
echo "--"
|
|
||||||
NUMTASKS=$(wc -l "$TODO_FILE" | sed 's/^[[:space:]]*\([0-9]*\).*/\1/')
|
|
||||||
echo "TODO: $NUMTASKS tasks in $TODO_FILE."
|
|
||||||
else
|
|
||||||
command=`sed = "$TODO_FILE" | sed 'N; s/^/ /; s/ *\(.\{2,\}\)\n/\1 /' | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/' | grep -i $item `
|
|
||||||
shift
|
|
||||||
shift
|
|
||||||
for i in $*
|
|
||||||
do
|
|
||||||
command=`echo "$command" | grep -i $i `
|
|
||||||
done
|
|
||||||
command=`echo "$command" | sort -f -k2`
|
|
||||||
|
|
||||||
echo -e "$command"
|
cleanup
|
||||||
fi
|
;;
|
||||||
|
|
||||||
cleanup ;;
|
|
||||||
|
|
||||||
"listall" | "lsa" )
|
"listall" | "lsa" )
|
||||||
item=$2
|
shift ## Was lsa; new $1 is first search term
|
||||||
|
|
||||||
cat "$TODO_FILE" "$DONE_FILE" > "$TMP_FILE"
|
cat "$TODO_FILE" "$DONE_FILE" > "$TMP_FILE"
|
||||||
|
_list "$TMP_FILE" "$@"
|
||||||
|
|
||||||
if [ -z "$item" ]; then
|
cleanup
|
||||||
echo -e "`sed = "$TMP_FILE" | sed 'N; s/^/ /; s/ *\(.\{3,\}\)\n/\1 /' | sed 's/^ /00/' | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/'`"
|
;;
|
||||||
else
|
|
||||||
command=`sed = "$TMP_FILE" | sed 'N; s/^/ /; s/ *\(.\{3,\}\)\n/\1 /' | sed 's/^ /00/' | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/' | grep -i $item `
|
|
||||||
shift
|
|
||||||
shift
|
|
||||||
for i in $*
|
|
||||||
do
|
|
||||||
command=`echo "$command" | grep -i $i `
|
|
||||||
done
|
|
||||||
command=`echo "$command" | sort -f -k2`
|
|
||||||
|
|
||||||
echo -e "$command"
|
|
||||||
fi
|
|
||||||
cleanup ;;
|
|
||||||
|
|
||||||
|
|
||||||
"listfile" | "lf" )
|
"listfile" | "lf" )
|
||||||
src="$TODO_DIR/$2"
|
shift ## Was listfile, next $1 is file name
|
||||||
|
FILE="$1"
|
||||||
|
shift ## Was filename; next $1 is first search term
|
||||||
|
|
||||||
if [ -z "$3" ]; then
|
_list "$FILE" "$@"
|
||||||
item=""
|
|
||||||
else
|
|
||||||
item=$3
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$src" ]; then
|
cleanup
|
||||||
if [ -z "$item" ]; then
|
;;
|
||||||
echo -e "`sed = "$src" | sed 'N; s/^/ /; s/ *\(.\{2,\}\)\n/\1 /' | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/'`"
|
|
||||||
if [ $VERBOSE = 1 ]; then
|
|
||||||
echo "--"
|
|
||||||
NUMTASKS=$( sed '/./!d' "$src" | wc -l | sed 's/^[[:space:]]*\([0-9]*\).*/\1/')
|
|
||||||
echo "TODO: $NUMTASKS lines in $src."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
command=`sed = "$src" | sed 'N; s/^/ /; s/ *\(.\{2,\}\)\n/\1 /' | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/' | grep -i $item `
|
|
||||||
shift
|
|
||||||
shift
|
|
||||||
for i in $*
|
|
||||||
do
|
|
||||||
command=`echo "$command" | grep -i $i `
|
|
||||||
done
|
|
||||||
command=`echo "$command" | sort -f -k2`
|
|
||||||
|
|
||||||
echo -e "$command"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "TODO: File $src does not exist."
|
|
||||||
|
|
||||||
fi
|
|
||||||
cleanup ;;
|
|
||||||
|
|
||||||
"listcon" | "lsc" )
|
"listcon" | "lsc" )
|
||||||
gawk '{for(i = 1; i <= NF; i++) print $i}' "$TODO_FILE" | grep '@' | sort | uniq
|
grep -w -o '@[^ ]\+' "$TODO_FILE" | sort -u
|
||||||
cleanup ;;
|
cleanup ;;
|
||||||
|
|
||||||
"listproj" | "lsprj" )
|
"listproj" | "lsprj" )
|
||||||
gawk '{for(i = 1; i <= NF; i++) print $i}' "$TODO_FILE" | grep '+' | sort | uniq
|
grep -w -o '+[^ ]\+' "$TODO_FILE" | sort -u
|
||||||
cleanup ;;
|
cleanup ;;
|
||||||
|
|
||||||
|
|
||||||
"listpri" | "lsp" )
|
"listpri" | "lsp" )
|
||||||
pri=$( printf "%s\n" "$2" | tr 'a-z' 'A-Z' )
|
shift ## was "listpri", new $1 is priority to list
|
||||||
|
|
||||||
errmsg="usage: $0 listpri PRIORITY
|
if [ "${1:-}" ]
|
||||||
|
then
|
||||||
|
## A priority was specified
|
||||||
|
pri=$( printf "%s\n" "$1" | tr 'a-z' 'A-Z' | grep '^[A-Z]$' ) || {
|
||||||
|
die "usage: $0 listpri PRIORITY
|
||||||
note: PRIORITY must a single letter from A to Z."
|
note: PRIORITY must a single letter from A to Z."
|
||||||
|
}
|
||||||
if [ -z "$pri" ]; then
|
|
||||||
echo -e "`sed = "$TODO_FILE" | sed 'N; s/^/ /; s/ *\(.\{2,\}\)\n/\1 /' | sed 's/^ /0/' | sort -f -k2 | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/'`" | grep \([A-Z]\)
|
|
||||||
if [ $VERBOSE = 1 ]; then
|
|
||||||
echo "--"
|
|
||||||
NUMTASKS=$(grep \([A-Z]\) "$TODO_FILE" | wc -l | sed 's/^[[:space:]]*\([0-9]*\).*/\1/')
|
|
||||||
echo "TODO: $NUMTASKS prioritized tasks in $TODO_FILE."
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
[[ "$pri" = +([A-Z]) ]] || die "$errmsg"
|
## No priority specified; show all priority tasks
|
||||||
|
pri="[[:upper:]]"
|
||||||
echo -e "`sed = "$TODO_FILE" | sed 'N; s/^/ /; s/ *\(.\{2,\}\)\n/\1 /' | sed 's/^ /0/' | sort -f -k2 | sed 's/^ /0/' | sort -f -k2 | sed '/^[0-9][0-9] x /!s/\(.*(A).*\)/'$PRI_A'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(B).*\)/'$PRI_B'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*(C).*\)/'$PRI_C'\1'$DEFAULT'/g' | sed '/^[0-9][0-9] x /!s/\(.*([A-Z]).*\)/'$PRI_X'\1'$DEFAULT'/'`" | grep \($pri\)
|
|
||||||
if [ $VERBOSE = 1 ]; then
|
|
||||||
echo "--"
|
|
||||||
NUMTASKS=$(grep \($pri\) "$TODO_FILE" | wc -l | sed 's/^[[:space:]]*\([0-9]*\).*/\1/')
|
|
||||||
echo "TODO: $NUMTASKS tasks prioritized $pri in $TODO_FILE."
|
|
||||||
fi
|
fi
|
||||||
|
pri="($pri)"
|
||||||
|
|
||||||
fi
|
_list "$TODO_FILE" "$pri"
|
||||||
cleanup;;
|
;;
|
||||||
|
|
||||||
"move" | "mv" )
|
"move" | "mv" )
|
||||||
|
# replace moved line with a blank line when TODOTXT_PRESERVE_LINE_NUMBERS is 1
|
||||||
# replace moved line with a blank line when PRESERVE_LINE_NUMBERS is 1
|
|
||||||
errmsg="usage: $0 mv ITEM# DEST [SRC]"
|
errmsg="usage: $0 mv ITEM# DEST [SRC]"
|
||||||
|
|
||||||
item=$2
|
item=$2
|
||||||
dest="$TODO_DIR/$3"
|
dest="$TODO_DIR/$3"
|
||||||
|
|
||||||
src="$TODO_DIR/$4"
|
src="$TODO_DIR/$4"
|
||||||
|
|
||||||
[ -z "$item" ] && die "$errmsg"
|
[ -z "$item" ] && die "$errmsg"
|
||||||
|
|
||||||
[ -z "$4" ] && src="$TODO_FILE"
|
[ -z "$4" ] && src="$TODO_FILE"
|
||||||
[ -z "$dest" ] && die "$errmsg"
|
[ -z "$dest" ] && die "$errmsg"
|
||||||
|
|
||||||
@@ -559,19 +763,16 @@ note: PRIORITY must a single letter from A to Z."
|
|||||||
|
|
||||||
if [ -f "$src" ]; then
|
if [ -f "$src" ]; then
|
||||||
if [ -f "$dest" ]; then
|
if [ -f "$dest" ]; then
|
||||||
|
|
||||||
if sed -ne "$item p" "$src" | grep "^."; then
|
if sed -ne "$item p" "$src" | grep "^."; then
|
||||||
MOVEME=$(sed "$item!d" "$src")
|
MOVEME=$(sed "$item!d" "$src")
|
||||||
|
if [ $TODOTXT_FORCE = 0 ]; then
|
||||||
if [ $FORCE = 0 ]; then
|
|
||||||
echo "Move '$MOVEME' from $src to $dest? (y/n)"
|
echo "Move '$MOVEME' from $src to $dest? (y/n)"
|
||||||
read ANSWER
|
read ANSWER
|
||||||
else
|
else
|
||||||
ANSWER="y"
|
ANSWER="y"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ANSWER" = "y" ]; then
|
if [ "$ANSWER" = "y" ]; then
|
||||||
if [ $PRESERVE_LINE_NUMBERS = 0 ]; then
|
if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
|
||||||
# delete line (changes line numbers)
|
# delete line (changes line numbers)
|
||||||
sed -i.bak -e $item"s/^.*//" -e '/./!d' "$src"
|
sed -i.bak -e $item"s/^.*//" -e '/./!d' "$src"
|
||||||
else
|
else
|
||||||
@@ -580,16 +781,14 @@ note: PRIORITY must a single letter from A to Z."
|
|||||||
fi
|
fi
|
||||||
echo "$MOVEME" >> "$dest"
|
echo "$MOVEME" >> "$dest"
|
||||||
|
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: '$MOVEME' moved from '$src' to '$dest'."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$MOVEME' moved from '$src' to '$dest'."
|
||||||
cleanup
|
cleanup
|
||||||
|
|
||||||
else
|
else
|
||||||
echo "TODO: No tasks moved."
|
echo "TODO: No tasks moved."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "$item: No such item in $src."
|
echo "$item: No such item in $src."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
echo "TODO: Destination file $dest does not exist."
|
echo "TODO: Destination file $dest does not exist."
|
||||||
fi
|
fi
|
||||||
@@ -605,11 +804,10 @@ note: PRIORITY must a single letter from A to Z."
|
|||||||
[ -z "$item" ] && die "$errmsg"
|
[ -z "$item" ] && die "$errmsg"
|
||||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||||
|
|
||||||
|
|
||||||
todo=$(sed "$item!d" "$TODO_FILE")
|
todo=$(sed "$item!d" "$TODO_FILE")
|
||||||
[ -z "$todo" ] && die "$item: No such todo."
|
[ -z "$todo" ] && die "$item: No such todo."
|
||||||
|
|
||||||
if [[ -z "$1" && $FORCE = 0 ]]; then
|
if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
|
||||||
echo -n "Prepend: "
|
echo -n "Prepend: "
|
||||||
read input
|
read input
|
||||||
else
|
else
|
||||||
@@ -618,7 +816,7 @@ note: PRIORITY must a single letter from A to Z."
|
|||||||
|
|
||||||
if sed -i.bak $item" s|^.*|$input &|" "$TODO_FILE"; then
|
if sed -i.bak $item" s|^.*|$input &|" "$TODO_FILE"; then
|
||||||
newtodo=$(sed "$item!d" "$TODO_FILE")
|
newtodo=$(sed "$item!d" "$TODO_FILE")
|
||||||
[[ $VERBOSE = 1 ]] && echo "$item: $newtodo"
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
|
||||||
else
|
else
|
||||||
echo "TODO: Error prepending task $item."
|
echo "TODO: Error prepending task $item."
|
||||||
fi
|
fi
|
||||||
@@ -633,16 +831,16 @@ note: PRIORITY must be anywhere from A to Z."
|
|||||||
|
|
||||||
[ "$#" -ne 3 ] && die "$errmsg"
|
[ "$#" -ne 3 ] && die "$errmsg"
|
||||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||||
[[ "$newpri" = +([A-Z]) ]] || die "$errmsg"
|
[[ "$newpri" = @([A-Z]) ]] || die "$errmsg"
|
||||||
|
|
||||||
sed -e $item"s/^(.*) //" -e $item"s/^/($newpri) /" "$TODO_FILE" > /dev/null 2>&1
|
sed -e $item"s/^(.) //" -e $item"s/^/($newpri) /" "$TODO_FILE" > /dev/null 2>&1
|
||||||
|
|
||||||
if [ "$?" -eq 0 ]; then
|
if [ "$?" -eq 0 ]; then
|
||||||
#it's all good, continue
|
#it's all good, continue
|
||||||
sed -i.bak -e $2"s/^(.*) //" -e $2"s/^/($newpri) /" "$TODO_FILE"
|
sed -i.bak -e $item"s/^(.) //" -e $item"s/^/($newpri) /" "$TODO_FILE"
|
||||||
NEWTODO=$(sed "$2!d" "$TODO_FILE")
|
NEWTODO=$(sed "$item!d" "$TODO_FILE")
|
||||||
[[ $VERBOSE = 1 ]] && echo -e "`echo "$item: $NEWTODO"`"
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo -e "`echo "$item: $NEWTODO"`"
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: $item prioritized ($newpri)."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item prioritized ($newpri)."
|
||||||
cleanup
|
cleanup
|
||||||
else
|
else
|
||||||
die "$errmsg"
|
die "$errmsg"
|
||||||
@@ -658,8 +856,7 @@ note: PRIORITY must be anywhere from A to Z."
|
|||||||
todo=$(sed "$item!d" "$TODO_FILE")
|
todo=$(sed "$item!d" "$TODO_FILE")
|
||||||
[ -z "$todo" ] && die "$item: No such todo."
|
[ -z "$todo" ] && die "$item: No such todo."
|
||||||
|
|
||||||
|
if [[ -z "$1" && $TODOTXT_FORCE = 0 ]]; then
|
||||||
if [[ -z "$1" && $FORCE = 0 ]]; then
|
|
||||||
echo -n "Replacement: "
|
echo -n "Replacement: "
|
||||||
read input
|
read input
|
||||||
else
|
else
|
||||||
@@ -667,9 +864,10 @@ note: PRIORITY must be anywhere from A to Z."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
sed -i.bak $item" s|^.*|$input|" "$TODO_FILE"
|
sed -i.bak $item" s|^.*|$input|" "$TODO_FILE"
|
||||||
[[ $VERBOSE = 1 ]] && NEWTODO=$(head -$item "$TODO_FILE" | tail -1)
|
[ $TODOTXT_VERBOSE -gt 0 ] && NEWTODO=$(head -$item "$TODO_FILE" | tail -1)
|
||||||
[[ $VERBOSE = 1 ]] && echo "replaced with"
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $todo"
|
||||||
[[ $VERBOSE = 1 ]] && echo "$item: $NEWTODO"
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "replaced with"
|
||||||
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $NEWTODO"
|
||||||
cleanup;;
|
cleanup;;
|
||||||
|
|
||||||
"report" )
|
"report" )
|
||||||
@@ -677,30 +875,21 @@ note: PRIORITY must be anywhere from A to Z."
|
|||||||
sed '/^x /!d' "$TODO_FILE" >> "$DONE_FILE"
|
sed '/^x /!d' "$TODO_FILE" >> "$DONE_FILE"
|
||||||
sed -i.bak '/^x /d' "$TODO_FILE"
|
sed -i.bak '/^x /d' "$TODO_FILE"
|
||||||
|
|
||||||
NUMLINES=$(wc -l "$TODO_FILE" | sed 's/^[[:space:]]*\([0-9]*\).*/\1/')
|
NUMLINES=$( sed -n '$ =' "$TODO_FILE" )
|
||||||
if [ $NUMLINES = "0" ]; then
|
if [ ${NUMLINES:-0} = "0" ]; then
|
||||||
echo "datetime todos dones" >> "$REPORT_FILE"
|
echo "datetime todos dones" >> "$REPORT_FILE"
|
||||||
fi
|
fi
|
||||||
#now report
|
#now report
|
||||||
TOTAL=$(cat "$TODO_FILE" | wc -l | sed 's/^[ \t]*//')
|
TOTAL=$( sed -n '$ =' "$TODO_FILE" )
|
||||||
TDONE=$(cat "$DONE_FILE" | wc -l | sed 's/^[ \t]*//')
|
TDONE=$( sed -n '$ =' "$DONE_FILE" )
|
||||||
TECHO=$(echo $(date +%Y-%m-%d-%T); echo ' '; echo $TOTAL; echo ' ';
|
TECHO=$(echo $(date +%Y-%m-%d-%T); echo ' '; echo ${TOTAL:-0}; echo ' ';
|
||||||
echo $TDONE)
|
echo ${TDONE:-0})
|
||||||
echo $TECHO >> "$REPORT_FILE"
|
echo $TECHO >> "$REPORT_FILE"
|
||||||
[[ $VERBOSE = 1 ]] && echo "TODO: Report file updated."
|
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file updated."
|
||||||
cat "$REPORT_FILE"
|
cat "$REPORT_FILE"
|
||||||
cleanup;;
|
cleanup;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
usage
|
usage
|
||||||
if [ -d "$HOME/.todo.actions.d" ]
|
;;
|
||||||
then
|
|
||||||
if [ -x "$HOME/.todo.actions.d/$action" ]
|
|
||||||
then
|
|
||||||
CFG_FILE="$CFG_FILE" "$HOME/.todo.actions.d/$action" "$@"
|
|
||||||
else
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
usage
|
|
||||||
fi
|
|
||||||
esac
|
esac
|
||||||
Reference in New Issue
Block a user