Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
f3c8f575b7 | |||
cabbdf7450 | |||
7f39ab7de1 | |||
daf6bea67b |
@ -1,7 +1,7 @@
|
|||||||
#Mon, 29 Apr 2013 02:07:45 -0500
|
#Wed, 01 May 2013 09:43:41 -0500
|
||||||
lib.local=true
|
lib.local=true
|
||||||
name=jdb-gtd
|
name=jdb-gtd
|
||||||
version=0.2
|
version=0.5
|
||||||
nailgun.classpath.dir=/home/jdbernard/programs/nailgun/classpath
|
nailgun.classpath.dir=/home/jdbernard/programs/nailgun/classpath
|
||||||
|
|
||||||
build.number=15
|
build.number=1
|
||||||
|
@ -10,7 +10,7 @@ import org.joda.time.DateTime
|
|||||||
|
|
||||||
public class GTDCLI {
|
public class GTDCLI {
|
||||||
|
|
||||||
public static final String VERSION = "0.2"
|
public static final String VERSION = "0.5"
|
||||||
private static String EOL = System.getProperty("line.separator")
|
private static String EOL = System.getProperty("line.separator")
|
||||||
private static GTDCLI nailgunInst
|
private static GTDCLI nailgunInst
|
||||||
|
|
||||||
@ -93,18 +93,19 @@ public class GTDCLI {
|
|||||||
switch (command.toLowerCase()) {
|
switch (command.toLowerCase()) {
|
||||||
case ~/help/: printUsage(parsedArgs); break
|
case ~/help/: printUsage(parsedArgs); break
|
||||||
case ~/done/: done(parsedArgs); break
|
case ~/done/: done(parsedArgs); break
|
||||||
|
case ~/cal|calendar/: calendar(parsedArgs); break
|
||||||
case ~/process/: process(parsedArgs); break
|
case ~/process/: process(parsedArgs); break
|
||||||
|
case ~/list-copies/: listCopies(parsedArgs); break
|
||||||
default:
|
default:
|
||||||
parsedArgs.addFirst(command)
|
println "Unrecognized command: ${command}"
|
||||||
process(parsedArgs)
|
|
||||||
break } } }
|
break } } }
|
||||||
|
|
||||||
protected void process(LinkedList args) {
|
protected void process(LinkedList args) {
|
||||||
|
|
||||||
def path = args.poll()
|
def path = args.poll()
|
||||||
if (path) {
|
if (path) {
|
||||||
givenDir = new File(path)
|
def givenDir = new File(path)
|
||||||
if (!(gtdDirs = findGtdRootDir(givenPath))) {
|
if (!(gtdDirs = findGtdRootDir(givenDir))) {
|
||||||
println "'$path' is not a valid directory."; return }}
|
println "'$path' is not a valid directory."; return }}
|
||||||
|
|
||||||
// Start processing items
|
// Start processing items
|
||||||
@ -243,13 +244,17 @@ public class GTDCLI {
|
|||||||
|
|
||||||
protected void done(LinkedList args) {
|
protected void done(LinkedList args) {
|
||||||
|
|
||||||
def selectedFile = args.poll()
|
def selectedFilePath = args.poll()
|
||||||
|
def selectedFile = new File(selectedFilePath)
|
||||||
|
|
||||||
if (!selectedFile) {
|
if (!selectedFile) {
|
||||||
println "gtd done command requires a <action-file> parameter."
|
println "gtd done command requires a <action-file> parameter."
|
||||||
return }
|
return }
|
||||||
|
|
||||||
def item = new Item(new File(workingDir, selectedFile))
|
def item
|
||||||
|
if (selectedFile.isAbsolute()) item = new Item(selectedFile)
|
||||||
|
else item = new Item(new File(workingDir, selectedFilePath))
|
||||||
|
|
||||||
def itemMd5 = md5.digest(item.file.bytes)
|
def itemMd5 = md5.digest(item.file.bytes)
|
||||||
|
|
||||||
// Move to the done folder.
|
// Move to the done folder.
|
||||||
@ -262,27 +267,83 @@ public class GTDCLI {
|
|||||||
if (inPath(gtdDirs.projects, oldFile)) {
|
if (inPath(gtdDirs.projects, oldFile)) {
|
||||||
|
|
||||||
// Delete any copies of this item in the next actions folder.
|
// Delete any copies of this item in the next actions folder.
|
||||||
gtdDirs["next-actions"].eachFileRecurse({ file ->
|
findAllCopies(oldFile, gtdDrs."next-actions").each { file ->
|
||||||
if (file.isFile() && md5.digest(file.bytes) == itemMd5) {
|
println "Deleting duplicate entry from the " +
|
||||||
println "Deleting duplicate entry from the " +
|
|
||||||
"${file.parentFile.name} context."
|
"${file.parentFile.name} context."
|
||||||
file.delete() }})}
|
file.delete() }
|
||||||
|
|
||||||
// Check if this item was in the next-action folder.
|
// Delete any copies of this item in the waiting folder.
|
||||||
if (inPath(gtdDirs["next-actions"], oldFile)) {
|
findAllCopies(oldFile, gtdDirs.waiting).each { file ->
|
||||||
|
println "Deleting duplicate entry from the " +
|
||||||
|
"${file.parentFile.name} waiting context."
|
||||||
|
file.delete() }}
|
||||||
|
|
||||||
// Delete any copies of this item in the next actions folder.
|
// Check if this item was in the next-action or waiting folder.
|
||||||
gtdDirs.projects.eachFileRecurse({ file ->
|
if (inPath(gtdDirs["next-actions"], oldFile) ||
|
||||||
if (file.isFile() && md5.digest(file.bytes) == itemMd5) {
|
inPath(gtdDirs.waiting, oldFile)) {
|
||||||
println "Deleting duplicate entry from the " +
|
|
||||||
"${file.parentFile.name} project."
|
// Delete any copies of this item in the projects folder.
|
||||||
file.delete() }})}
|
findAllCopies(oldFile, gtdDirs.projects).each { file ->
|
||||||
|
println "Deleting duplicate entry from the " +
|
||||||
|
"${file.parentFile.name} project."
|
||||||
|
file.delete() }}
|
||||||
|
|
||||||
// Delete the original
|
// Delete the original
|
||||||
oldFile.delete()
|
oldFile.delete()
|
||||||
|
|
||||||
println "'$item' marked as done." }
|
println "'$item' marked as done." }
|
||||||
|
|
||||||
|
protected void calendar(LinkedList args) {
|
||||||
|
def itemsOnCalendar = []
|
||||||
|
|
||||||
|
def addCalendarItems = { file ->
|
||||||
|
if (!file.isFile()) return
|
||||||
|
def item = new Item(file)
|
||||||
|
if (item.date) itemsOnCalendar << item }
|
||||||
|
|
||||||
|
gtdDirs."next-actions".eachFileRecurse(addCalendarItems)
|
||||||
|
gtdDirs.waiting.eachFileRecurse(addCalendarItems)
|
||||||
|
gtdDirs.projects.eachFileRecurse(addCalendarItems)
|
||||||
|
|
||||||
|
itemsOnCalendar = itemsOnCalendar.unique { md5.digest(it.file.bytes) }.
|
||||||
|
sort { it.date }
|
||||||
|
|
||||||
|
if (!itemsOnCalendar) println "No items on the calendar."
|
||||||
|
|
||||||
|
def currentDate = null
|
||||||
|
|
||||||
|
itemsOnCalendar.each { item ->
|
||||||
|
def itemDay = new DateMidnight(item.date)
|
||||||
|
if (itemDay != currentDate) {
|
||||||
|
if (currentDate != null) println ""
|
||||||
|
println itemDay.toString("EEE, MM/dd")
|
||||||
|
println "----------"
|
||||||
|
currentDate = itemDay }
|
||||||
|
|
||||||
|
println " $item" } }
|
||||||
|
|
||||||
|
protected void listCopies(LinkedList args) {
|
||||||
|
|
||||||
|
args.each { filePath ->
|
||||||
|
def file = new File(filePath)
|
||||||
|
|
||||||
|
if (!file.isAbsolute()) file = new File(workingDir, filePath)
|
||||||
|
|
||||||
|
if (!file.isFile()) {
|
||||||
|
println "${file.canonicalPath} is not a regular file."
|
||||||
|
return }
|
||||||
|
|
||||||
|
String originalRelativePath = getRelativePath(gtdDirs.root, file)
|
||||||
|
println "Copies of $originalRelativePath:"
|
||||||
|
println ""
|
||||||
|
|
||||||
|
findAllCopies(file, gtdDirs.root).each { copy ->
|
||||||
|
if (copy.canonicalPath != file.canonicalPath) {
|
||||||
|
String relativePath = getRelativePath(gtdDirs.root, copy)
|
||||||
|
println " $relativePath" }} }
|
||||||
|
|
||||||
|
args.clear() }
|
||||||
|
|
||||||
protected void printUsage(LinkedList args) {
|
protected void printUsage(LinkedList args) {
|
||||||
|
|
||||||
if (!args) {
|
if (!args) {
|
||||||
@ -297,17 +358,80 @@ public class GTDCLI {
|
|||||||
println ""
|
println ""
|
||||||
println "top-leve commands:"
|
println "top-leve commands:"
|
||||||
println ""
|
println ""
|
||||||
println " process Process inbox items systematically."
|
|
||||||
println " help <command> Print detailed help about a command."
|
println " help <command> Print detailed help about a command."
|
||||||
|
println " process Process inbox items systematically."
|
||||||
|
println " done <action-file> Mark an action as done. This will automatically "
|
||||||
|
println " take care of duplicates of the action in project "
|
||||||
|
println " or next-actions sub-folders."
|
||||||
} else {
|
} else {
|
||||||
def command = args.poll()
|
def command = args.poll()
|
||||||
|
|
||||||
// TODO
|
switch(command.toLowerCase()) {
|
||||||
//switch(command.toLowerCase())
|
case ~/process/: println """\
|
||||||
// case ~/process/:
|
usage: gtd process
|
||||||
|
|
||||||
|
This is an interactive command.
|
||||||
|
|
||||||
|
GTD CLI goes through all the items in the "in" folder for this GTD repository
|
||||||
|
and guides you through the *process* step of the GTD method as follows:
|
||||||
|
|
||||||
|
Is the item actionable?
|
||||||
|
V
|
||||||
|
+---------------------------> No
|
||||||
|
| / \\
|
||||||
|
Yes Incubate Trash
|
||||||
|
| (Someday/Maybe)
|
||||||
|
V
|
||||||
|
Yes <--Too big for one action? --> No
|
||||||
|
| |
|
||||||
|
V |
|
||||||
|
Move to projects V
|
||||||
|
(still needs organization) What is the next action?
|
||||||
|
/
|
||||||
|
/
|
||||||
|
Defer, delegate, or tickler?
|
||||||
|
/ | \\
|
||||||
|
/ Move to the Set a date for this
|
||||||
|
Move to the waiting to become active again.
|
||||||
|
next-actions directory Move to the tickler
|
||||||
|
directory directory."""
|
||||||
|
break
|
||||||
|
|
||||||
|
case ~/done/: println """\
|
||||||
|
usage: gtd done <action-file>
|
||||||
|
|
||||||
|
Where <action-file> is expected to be the path (absolute or relative) to an
|
||||||
|
action item file. The action item file is expected to be in the *projects*
|
||||||
|
folder, the *next-actions* folder, the *waiting* folder, or a subfolder of one of
|
||||||
|
the aforementioned folders. The item is prepended with the current date and
|
||||||
|
moved to the *done* folder. If the item was in a project folder, the
|
||||||
|
*next-actions* and *waiting* folders are scanned recursively for duplicates of
|
||||||
|
the item, which are removed if found. Similarly, if the action was in a
|
||||||
|
*next-actions* or *waiting* folder the *projects* folder is scanned recursively
|
||||||
|
for duplicates.
|
||||||
|
|
||||||
|
The intention of the duplicate removal is to allow you to copy actions from
|
||||||
|
project folders into next action or waiting contexts, so you can keep a view of
|
||||||
|
the item organized by the project or in your next actions list. The GTD CLI tool
|
||||||
|
is smart enough to recognize that these are the same items filed in more than
|
||||||
|
one place and deal with them all in one fell swoop. Duplicates are determined by
|
||||||
|
exact file contents (MD5 has of the file contents)."""
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<File> findAllCopies(File original, File inDir) {
|
||||||
|
def copies = []
|
||||||
|
def originalMD5 = md5.digest(original.bytes)
|
||||||
|
|
||||||
|
inDir.eachFileRecurse { file ->
|
||||||
|
if (file.isFile() && md5.digest(file.bytes) == originalMD5)
|
||||||
|
copies << file }
|
||||||
|
|
||||||
|
return copies }
|
||||||
|
|
||||||
protected boolean inPath(File parent, File child) {
|
protected boolean inPath(File parent, File child) {
|
||||||
def parentPath = parent.canonicalPath.split("/")
|
def parentPath = parent.canonicalPath.split("/")
|
||||||
def childPath = child.canonicalPath.split("/")
|
def childPath = child.canonicalPath.split("/")
|
||||||
@ -328,6 +452,18 @@ public class GTDCLI {
|
|||||||
// parent path.
|
// parent path.
|
||||||
return true }
|
return true }
|
||||||
|
|
||||||
|
protected String getRelativePath(File parent, File child) {
|
||||||
|
def parentPath = parent.canonicalPath.split("/")
|
||||||
|
def childPath = child.canonicalPath.split("/")
|
||||||
|
|
||||||
|
if (parentPath.length > childPath.length) return ""
|
||||||
|
|
||||||
|
int b = 0
|
||||||
|
while (b < parentPath.length && parentPath[b] == childPath[b] ) b++;
|
||||||
|
|
||||||
|
if (b != parentPath.length) return ""
|
||||||
|
return (['.'] + childPath[b..<childPath.length]).join('/') }
|
||||||
|
|
||||||
protected Map findGtdRootDir(File givenDir) {
|
protected Map findGtdRootDir(File givenDir) {
|
||||||
|
|
||||||
def gtdDirs = [:]
|
def gtdDirs = [:]
|
||||||
@ -351,7 +487,7 @@ public class GTDCLI {
|
|||||||
|
|
||||||
static String stringToFilename(String s) {
|
static String stringToFilename(String s) {
|
||||||
return s.replaceAll(/\s/, '-').
|
return s.replaceAll(/\s/, '-').
|
||||||
replaceAll(/[';:]/, '').
|
replaceAll(/[';:(\.$)]/, '').
|
||||||
toLowerCase() }
|
toLowerCase() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,28 +3,28 @@ package com.jdblabs.gtd.cli
|
|||||||
public class Item {
|
public class Item {
|
||||||
|
|
||||||
public File file
|
public File file
|
||||||
public Map properties = [:]
|
public Map gtdProperties = [:]
|
||||||
|
|
||||||
public Item(File f) {
|
public Item(File f) {
|
||||||
this.file = f
|
this.file = f
|
||||||
|
|
||||||
def javaProps = new Properties()
|
def javaProps = new Properties()
|
||||||
f.withReader { reader -> javaProps.load(reader) }
|
f.withReader { reader -> javaProps.load(reader) }
|
||||||
javaProps.each { k, v -> properties[k] = PropertyHelp.parse(v) } }
|
javaProps.each { k, v -> gtdProperties[k] = PropertyHelp.parse(v) } }
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
def javaProps = new Properties()
|
def javaProps = new Properties()
|
||||||
properties.each { k, v -> javaProps[k] = PropertyHelp.format(v) }
|
gtdProperties.each { k, v -> javaProps[k] = PropertyHelp.format(v) }
|
||||||
file.withOutputStream { os -> javaProps.store(os, "") } }
|
file.withOutputStream { os -> javaProps.store(os, "") } }
|
||||||
|
|
||||||
public def propertyMissing(String name, def value) {
|
public def propertyMissing(String name, def value) {
|
||||||
properties[name] = value }
|
gtdProperties[name] = value }
|
||||||
|
|
||||||
public def propertyMissing(String name) { return properties[name] }
|
public def propertyMissing(String name) { return gtdProperties[name] }
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (properties.action) return properties.action
|
if (gtdProperties.action) return gtdProperties.action
|
||||||
if (properties.outcome) return properties.outcome
|
if (gtdProperties.outcome) return gtdProperties.outcome
|
||||||
if (properties.title) return properties.title
|
if (gtdProperties.title) return gtdProperties.title
|
||||||
return file.name.replaceAll(/[-_]/, " ").capitalize() }
|
return file.name.replaceAll(/[-_]/, " ").capitalize() }
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user