Compare commits
44 Commits
v2.3
...
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 | ||
|
|
5491e458a2 | ||
|
|
5789f5b4c2 | ||
|
|
b17cb11ec6 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +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:])
|
||||
24
Makefile
24
Makefile
@@ -29,6 +29,24 @@ dist: $(DISTFILES) todo.sh
|
||||
clean:
|
||||
rm -f $(DISTNAME).tar.gz $(DISTNAME).zip
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@echo "TBD!"
|
||||
|
||||
#
|
||||
# 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)
|
||||
|
||||
8
README
8
README
@@ -1,8 +0,0 @@
|
||||
TODO.TXT Command Line Interface
|
||||
Latest version: http://github.com/ginatrapani/todo.txt-cli/tree/master
|
||||
First release: 5/11/2006
|
||||
Conceived and released by: Gina Trapani (http://ginatrapani.org)
|
||||
Contributors: http://github.com/ginatrapani/todo.txt-cli/network
|
||||
License: GPL, http://www.gnu.org/copyleft/gpl.html
|
||||
More information and mailing list at http://todotxt.com
|
||||
To submit patches, fork the repository at http://github.com/ginatrapani/todo.txt-cli/tree/master
|
||||
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
|
||||
58
todo.cfg
58
todo.cfg
@@ -10,32 +10,46 @@ export DONE_FILE="$TODO_DIR/done.txt"
|
||||
export REPORT_FILE="$TODO_DIR/report.txt"
|
||||
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 ===
|
||||
|
||||
# === 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'
|
||||
## If you have re-mapped your color codes, you may need to
|
||||
## over-ride by uncommenting and editing these defaults.
|
||||
|
||||
# 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'
|
||||
|
||||
# === PRIORITY COLORS ===
|
||||
|
||||
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
|
||||
## Priorities can be any upper-case letter.
|
||||
## Colors are supported for the first three.
|
||||
## Uncomment and edit to override these defaults.
|
||||
|
||||
# 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'
|
||||
|
||||
133
todo.sh
133
todo.sh
@@ -102,7 +102,7 @@ help()
|
||||
Marks item on line NUMBER as done in todo.txt.
|
||||
|
||||
help
|
||||
Display this help message.
|
||||
Display this help message.
|
||||
|
||||
list [TERM...]
|
||||
ls [TERM...]
|
||||
@@ -203,16 +203,17 @@ help()
|
||||
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
|
||||
|
||||
if [ -d "$HOME/.todo.actions.d" ]
|
||||
if [ -d "$TODO_ACTIONS_DIR" ]
|
||||
then
|
||||
echo ""
|
||||
for action in $HOME/.todo.actions.d/*
|
||||
for action in "$TODO_ACTIONS_DIR"/*
|
||||
do
|
||||
if [ -x $action ]
|
||||
if [ -x "$action" ]
|
||||
then
|
||||
$action usage
|
||||
"$action" usage
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
@@ -244,7 +245,7 @@ archive()
|
||||
cp "$TODO_FILE" "$TMP_FILE"
|
||||
sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P' "$TMP_FILE" > "$TODO_FILE"
|
||||
#[[ $TODOTXT_VERBOSE -gt 0 ]] && echo "TODO: Duplicate tasks have been removed."
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $TODO_FILE archived."
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $TODO_FILE archived."
|
||||
cleanup
|
||||
}
|
||||
|
||||
@@ -341,6 +342,35 @@ 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"
|
||||
@@ -351,13 +381,17 @@ TODOTXT_DEFAULT_ACTION=${TODOTXT_DEFAULT_ACTION:-}
|
||||
fi
|
||||
}
|
||||
|
||||
export TODOTXT_VERBOSE TODOTXT_PLAIN TODOTXT_CFG_FILE TODOTXT_FORCE TODOTXT_PRESERVE_LINE_NUMBERS TODOTXT_AUTO_ARCHIVE TODOTXT_DATE_ON_ADD
|
||||
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!) ===
|
||||
[ -r "$TODOTXT_CFG_FILE" ] || die "Fatal error: Cannot read configuration file $TODOTXT_CFG_FILE"
|
||||
[ -r "$TODOTXT_CFG_FILE" ] || die "Fatal error: Cannot read configuration file $TODOTXT_CFG_FILE"
|
||||
|
||||
. "$TODOTXT_CFG_FILE"
|
||||
|
||||
@@ -367,7 +401,7 @@ ACTION=${1:-$TODOTXT_DEFAULT_ACTION}
|
||||
[ -d "$TODO_DIR" ] || die "Fatal Error: $TODO_DIR is not a directory"
|
||||
( cd "$TODO_DIR" ) || die "Fatal Error: Unable to cd to $TODO_DIR"
|
||||
|
||||
[ -w "$TMP_FILE" ] || echo -n > "$TMP_FILE" || die "Fatal Error: Unable to write to $TMP_FILE"
|
||||
[ -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 "$DONE_FILE" ] || cp /dev/null "$DONE_FILE"
|
||||
[ -f "$REPORT_FILE" ] || cp /dev/null "$REPORT_FILE"
|
||||
@@ -444,7 +478,7 @@ _list() {
|
||||
items=$(
|
||||
sed = "$src" \
|
||||
| sed "N; s/^/ /; s/ *\(.\{$PADDING,\}\)\n/\1 /" \
|
||||
| grep -v "^[0-9]\+ *$"
|
||||
| grep -v "^[0-9]\+ *$"
|
||||
)
|
||||
if [ "${filter_command}" ]; then
|
||||
filtered_items=$(echo -ne "$items" | eval ${filter_command})
|
||||
@@ -460,13 +494,13 @@ _list() {
|
||||
s/^ /00/;
|
||||
s/^ /0/;
|
||||
''' \
|
||||
| sort -f -k2 \
|
||||
| ${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;
|
||||
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 '''
|
||||
@@ -486,7 +520,7 @@ _list() {
|
||||
fi
|
||||
if [ $TODOTXT_VERBOSE -gt 1 ]
|
||||
then
|
||||
echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}"
|
||||
echo "TODO DEBUG: Filter Command was: ${filter_command:-cat}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -505,9 +539,9 @@ then
|
||||
shift
|
||||
## Reset action to new first argument
|
||||
action=$( printf "%s\n" "$1" | tr 'A-Z' 'a-z' )
|
||||
elif [ -d "$HOME/.todo.actions.d" -a -x "$HOME/.todo.actions.d/$action" ]
|
||||
elif [ -d "$TODO_ACTIONS_DIR" -a -x "$TODO_ACTIONS_DIR/$action" ]
|
||||
then
|
||||
"$HOME/.todo.actions.d/$action" "$@"
|
||||
"$TODO_ACTIONS_DIR/$action" "$@"
|
||||
cleanup
|
||||
fi
|
||||
|
||||
@@ -545,7 +579,7 @@ case $action in
|
||||
TASKNUM=$(sed -n '$ =' "$dest")
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$input' added to $dest on line $TASKNUM."
|
||||
else
|
||||
echo "TODO: Destination file $dest does not exist."
|
||||
echo "TODO: Destination file $dest does not exist."
|
||||
fi
|
||||
cleanup;;
|
||||
|
||||
@@ -556,7 +590,7 @@ case $action in
|
||||
[ -z "$item" ] && die "$errmsg"
|
||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||
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
|
||||
echo -n "Append: "
|
||||
read input
|
||||
@@ -567,7 +601,7 @@ case $action in
|
||||
newtodo=$(sed "$item!d" "$TODO_FILE")
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
|
||||
else
|
||||
echo "TODO: Error appending task $item."
|
||||
echo "TODO: Error appending task $item."
|
||||
fi
|
||||
cleanup;;
|
||||
|
||||
@@ -584,7 +618,7 @@ case $action in
|
||||
|
||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||
if sed -ne "$item p" "$TODO_FILE" | grep "^."; then
|
||||
DELETEME=$(sed "$2!d" "$TODO_FILE")
|
||||
DELETEME=$(sed "$item!d" "$TODO_FILE")
|
||||
|
||||
if [ $TODOTXT_FORCE = 0 ]; then
|
||||
echo "Delete '$DELETEME'? (y/n)"
|
||||
@@ -595,22 +629,22 @@ case $action in
|
||||
if [ "$ANSWER" = "y" ]; then
|
||||
if [ $TODOTXT_PRESERVE_LINE_NUMBERS = 0 ]; then
|
||||
# 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
|
||||
# leave blank line behind (preserves line numbers)
|
||||
sed -i.bak -e $2"s/^.*//" "$TODO_FILE"
|
||||
sed -i.bak -e $item"s/^.*//" "$TODO_FILE"
|
||||
fi
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$DELETEME' deleted."
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$DELETEME' deleted."
|
||||
cleanup
|
||||
else
|
||||
echo "TODO: No tasks were deleted."
|
||||
echo "TODO: No tasks were deleted."
|
||||
fi
|
||||
else
|
||||
echo "$item: No such todo."
|
||||
fi
|
||||
else
|
||||
sed -i.bak -e $item"s/$3/ /g" "$TODO_FILE"
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $3 removed from $item."
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $3 removed from $item."
|
||||
fi ;;
|
||||
|
||||
"depri" | "dp" )
|
||||
@@ -618,15 +652,15 @@ case $action in
|
||||
errmsg="usage: $0 depri ITEM#"
|
||||
|
||||
todo=$(sed "$item!d" "$TODO_FILE")
|
||||
[ -z "$todo" ] && die "$item: No such todo."
|
||||
[ -z "$todo" ] && die "$item: No such todo."
|
||||
[[ "$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
|
||||
#it's all good, continue
|
||||
sed -i.bak -e $2"s/^(.*) //" "$TODO_FILE"
|
||||
NEWTODO=$(sed "$2!d" "$TODO_FILE")
|
||||
sed -i.bak -e $item"s/^(.) //" "$TODO_FILE"
|
||||
NEWTODO=$(sed "$item!d" "$TODO_FILE")
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo -e "`echo "$item: $NEWTODO"`"
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item deprioritized."
|
||||
cleanup
|
||||
@@ -641,11 +675,11 @@ case $action in
|
||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||
|
||||
todo=$(sed "$item!d" "$TODO_FILE")
|
||||
[ -z "$todo" ] && die "$item: No such todo."
|
||||
[ -z "$todo" ] && die "$item: No such todo."
|
||||
|
||||
now=`date '+%Y-%m-%d'`
|
||||
# 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"
|
||||
newtodo=$(sed "$item!d" "$TODO_FILE")
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
|
||||
@@ -687,11 +721,11 @@ case $action in
|
||||
;;
|
||||
|
||||
"listcon" | "lsc" )
|
||||
gawk '{for(i = 1; i <= NF; i++) print $i}' "$TODO_FILE" | grep '@' | sort | uniq
|
||||
grep -w -o '@[^ ]\+' "$TODO_FILE" | sort -u
|
||||
cleanup ;;
|
||||
|
||||
"listproj" | "lsprj" )
|
||||
gawk '{for(i = 1; i <= NF; i++) print $i}' "$TODO_FILE" | grep '+' | sort | uniq
|
||||
grep -w -o '+[^ ]\+' "$TODO_FILE" | sort -u
|
||||
cleanup ;;
|
||||
|
||||
|
||||
@@ -702,8 +736,8 @@ case $action in
|
||||
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."
|
||||
die "usage: $0 listpri PRIORITY
|
||||
note: PRIORITY must a single letter from A to Z."
|
||||
}
|
||||
else
|
||||
## No priority specified; show all priority tasks
|
||||
@@ -747,16 +781,16 @@ case $action in
|
||||
fi
|
||||
echo "$MOVEME" >> "$dest"
|
||||
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$MOVEME' moved from '$src' to '$dest'."
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: '$MOVEME' moved from '$src' to '$dest'."
|
||||
cleanup
|
||||
else
|
||||
echo "TODO: No tasks moved."
|
||||
echo "TODO: No tasks moved."
|
||||
fi
|
||||
else
|
||||
echo "$item: No such item in $src."
|
||||
fi
|
||||
else
|
||||
echo "TODO: Destination file $dest does not exist."
|
||||
echo "TODO: Destination file $dest does not exist."
|
||||
fi
|
||||
else
|
||||
echo "TODO: Source file $src does not exist."
|
||||
@@ -771,7 +805,7 @@ case $action in
|
||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||
|
||||
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
|
||||
echo -n "Prepend: "
|
||||
@@ -784,7 +818,7 @@ case $action in
|
||||
newtodo=$(sed "$item!d" "$TODO_FILE")
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $newtodo"
|
||||
else
|
||||
echo "TODO: Error prepending task $item."
|
||||
echo "TODO: Error prepending task $item."
|
||||
fi
|
||||
cleanup;;
|
||||
|
||||
@@ -793,18 +827,18 @@ case $action in
|
||||
newpri=$( printf "%s\n" "$3" | tr 'a-z' 'A-Z' )
|
||||
|
||||
errmsg="usage: $0 pri ITEM# PRIORITY
|
||||
note: PRIORITY must be anywhere from A to Z."
|
||||
note: PRIORITY must be anywhere from A to Z."
|
||||
|
||||
[ "$#" -ne 3 ] && 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
|
||||
#it's all good, continue
|
||||
sed -i.bak -e $2"s/^(.*) //" -e $2"s/^/($newpri) /" "$TODO_FILE"
|
||||
NEWTODO=$(sed "$2!d" "$TODO_FILE")
|
||||
sed -i.bak -e $item"s/^(.) //" -e $item"s/^/($newpri) /" "$TODO_FILE"
|
||||
NEWTODO=$(sed "$item!d" "$TODO_FILE")
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo -e "`echo "$item: $NEWTODO"`"
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: $item prioritized ($newpri)."
|
||||
cleanup
|
||||
@@ -820,7 +854,7 @@ note: PRIORITY must be anywhere from A to Z."
|
||||
[[ "$item" = +([0-9]) ]] || die "$errmsg"
|
||||
|
||||
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
|
||||
echo -n "Replacement: "
|
||||
@@ -831,6 +865,7 @@ note: PRIORITY must be anywhere from A to Z."
|
||||
|
||||
sed -i.bak $item" s|^.*|$input|" "$TODO_FILE"
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && NEWTODO=$(head -$item "$TODO_FILE" | tail -1)
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $todo"
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "replaced with"
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "$item: $NEWTODO"
|
||||
cleanup;;
|
||||
@@ -850,7 +885,7 @@ note: PRIORITY must be anywhere from A to Z."
|
||||
TECHO=$(echo $(date +%Y-%m-%d-%T); echo ' '; echo ${TOTAL:-0}; echo ' ';
|
||||
echo ${TDONE:-0})
|
||||
echo $TECHO >> "$REPORT_FILE"
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file updated."
|
||||
[ $TODOTXT_VERBOSE -gt 0 ] && echo "TODO: Report file updated."
|
||||
cat "$REPORT_FILE"
|
||||
cleanup;;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user