diff --git a/.hgignore b/.hgignore
index 6f3cc06..b0b9510 100644
--- a/.hgignore
+++ b/.hgignore
@@ -1 +1,2 @@
+build/
\..*sw[op]
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..a6041da
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pit.groovy b/pit.groovy
deleted file mode 100755
index 58b3c90..0000000
--- a/pit.groovy
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/groovy
-
-def cli = new CliBuilder(usage: '')
-cli.h(longOpt: 'help', 'Show help information.')
-cli.v(longOpt: 'verbose', 'Show verbose task information')
-cli.l(longOpt: 'list', 'List issues. Unless otherwise specified it lists all '
- + 'sub projects and all unclosed issue categories.')
-cli.i(argName: 'id', longOpt: 'id', args: 1,
- 'Filter issues by id. Accepts a comma-delimited list.')
-cli.c(argName: 'category', longOpt: 'category', args: 1,
- 'Filter issues by category (bug, feature, task, closed). Accepts a '
- + 'comma-delimited list.')
-cli.p(argName: 'priority', longOpt: 'priority', args: 1,
- 'Filter issues by priority. This acts as a threshhold, listing all issues '
- + 'greater than or equal to the given priority.')
-cli.r(argName: 'project', longOpt: 'project', args: 1,
- 'Filter issues by project (relative to the current directory). Accepts a '
- + 'comma-delimited list.')
-cli.s(longOpt: 'show-subprojects',
- 'Include sup projects in listing (default behaviour)')
-cli.S(longOpt: 'no-subprojects', 'Do not list subprojects.')
-
-def opts = cli.parse(args)
-def issuedb = [:]
-
-if (!opts) System.exit(1) // better solution?
-
-if (opts.h) cli.usage()
-
-def categories = ['bug','feature','task']
-if (opts.c) categories = opts.c.split(/[,\s]/)
-categories = categories.collect { Category.toCategory(it) }
-
-// build issue list
-issuedb = new Project(new File('.'),
- new Filter('categories': categories,
- 'priority': (opts.p ? opts.p.toInteger() : 9),
- 'projects': (opts.r ? opts.r.toLowerCase().split(/[,\s]/).asType(List.class) : []),
- 'ids': (opts.i ? opts.i.split(/[,\s]/).asType(List.class) : []),
- 'acceptProjects': (opts.s || !opts.S)))
-
-// list first
-if (opts.l) issuedb.list('verbose': opts.v)
-
-// change priority second
-//else if (opts.cp)
-
-// change category third
-//else if (opts.cc)
-
-// new entry last
-
-enum Category {
- BUG,
- FEATURE,
- TASK,
- CLOSED
-
- public static Category toCategory(String s) {
- for(c in Category.values())
- if (c.toString().startsWith(s.toUpperCase())) return c
- throw new IllegalArgumentException("No category matches ${s}.")
- }
-}
-
-class Project {
-
- String name
- Map issues = [:]
- Map projects = [:]
-
- Project(File dir, Filter filter = null) {
- dir.eachFile { child ->
-
- // add sub projects
- if (child.isDirectory()) {
- if ( child.name ==~ /\d{4}/ || // just an issue folder
- (filter && !filter.accept(child.name)))
- return
-
- // otherwise build and add to list
- projects[(child.name)] = new Project(child, filter)
- } else if (child.isFile()) {
- def issue
-
- // if exception, then not an issue
- try { issue = new Issue(child) } catch (all) { return }
-
- if (filter && !filter.accept(issue)) return
-
- issues[(issue.id)] = issue
- }
- }
- }
-
- public void eachIssue(Closure c) {
- for (i in issues.values()) c.call(i)
- for (p in projects.values()) p.eachIssue(c)
- }
-
- public void each(Filter filter = null, Closure c) {
- def is = filter?.issueSorter ?: { it.id.toInteger() }
- def ps = filter?.projectSorter ?: { it.name }
-
- for (issue in issues.values().sort(is)) {
- if (filter && !filter.accept(issue))
- return
-
- c.call(issue)
- }
-
- for (project in projects.values().sort(ps)) {
- if (filter && !filter.accept(project))
- return
-
- c.call(project)
- project.each(c)
- }
- }
-
- public void list(Map options = [:]) {
- if (!options.offset) options.offset = ""
- if (!options.verbose) options.verbose = false
-
- each(options.filter) {
- if (it instanceof Project) {
- println "\n${options.offset}${it.name}"
- println "${options.offset}${'-'.multiply(p.name.length())}"
- } else {
- println "${options.offset}${it.id}(${it.priority}): " +
- "${it.category} ${it.title}"
- if (options.verbose) println "\n${it.text}"
- }
- }
- }
-}
-
-class Issue {
-
- String id
- Category category
- int priority
- String title
- String text
-
- Issue(File file) {
-
- def matcher = file.name =~ /(\d{4})([bftc])(\d).*/
- if (!matcher) return null
-
- id = matcher[0][1]
- category = Category.toCategory(matcher[0][2])
- priority = matcher[0][3].toInteger()
-
- file.withReader { title = it.readLine() }
- text = file.text
- }
-}
-
-class Filter {
-
- List categories = null
- List projects = null
- List ids = null
- int priority = 9
- boolean acceptProjects = true
- Closure projectSorter
- Closure issueSorter
-
- public boolean accept(Issue i) {
- return (i.priority <= priority &&
- (!categories || categories.contains(i.category)) &&
- (!ids || ids.contains(i.id)))
- }
-
- public boolean accept(Project p) {
- return (acceptProjects &&
- (!projects || projects.contains(p.name)))
- }
-
- public boolean accept(String name) {
- return (acceptProjects &&
- (!projects || projects.contains(name)))
- }
-}
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..7acec89
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,8 @@
+app.version=1.0
+build.dir=build
+build.jar=pit-${app.version}.jar
+build.lib.jar=lib${build.jar}
+groovy.home=/usr/share/groovy
+lib.dir=lib
+main.class=com.jdbernard.pit.PersonalIssueTracker
+src.dir=src
diff --git a/src/com/jdbernard/pit/Category.groovy b/src/com/jdbernard/pit/Category.groovy
new file mode 100644
index 0000000..f6edd51
--- /dev/null
+++ b/src/com/jdbernard/pit/Category.groovy
@@ -0,0 +1,14 @@
+package com.jdbernard.pit
+
+public enum Category {
+ BUG,
+ FEATURE,
+ TASK,
+ CLOSED
+
+ public static Category toCategory(String s) {
+ for(c in Category.values())
+ if (c.toString().startsWith(s.toUpperCase())) return c
+ throw new IllegalArgumentException("No category matches ${s}.")
+ }
+}
diff --git a/src/com/jdbernard/pit/Filter.groovy b/src/com/jdbernard/pit/Filter.groovy
new file mode 100644
index 0000000..70eadd9
--- /dev/null
+++ b/src/com/jdbernard/pit/Filter.groovy
@@ -0,0 +1,28 @@
+package com.jdbernard.pit
+
+class Filter {
+
+ List categories = null
+ List projects = null
+ List ids = null
+ int priority = 9
+ boolean acceptProjects = true
+ Closure projectSorter
+ Closure issueSorter
+
+ public boolean accept(Issue i) {
+ return (i.priority <= priority &&
+ (!categories || categories.contains(i.category)) &&
+ (!ids || ids.contains(i.id)))
+ }
+
+ public boolean accept(Project p) {
+ return (acceptProjects &&
+ (!projects || projects.contains(p.name)))
+ }
+
+ public boolean accept(String name) {
+ return (acceptProjects &&
+ (!projects || projects.contains(name)))
+ }
+}
diff --git a/src/com/jdbernard/pit/Issue.groovy b/src/com/jdbernard/pit/Issue.groovy
new file mode 100644
index 0000000..f4d104b
--- /dev/null
+++ b/src/com/jdbernard/pit/Issue.groovy
@@ -0,0 +1,24 @@
+package com.jdbernard.pit
+
+public class Issue {
+
+ String id
+ Category category
+ int priority
+ String title
+ String text
+
+ Issue(File file) {
+
+ def matcher = file.name =~ /(\d{4})([bftc])(\d).*/
+ if (!matcher) return null
+
+ id = matcher[0][1]
+ category = Category.toCategory(matcher[0][2])
+ priority = matcher[0][3].toInteger()
+
+ file.withReader { title = it.readLine() }
+ text = file.text
+ }
+
+}
diff --git a/src/com/jdbernard/pit/PersonalIssueTracker.groovy b/src/com/jdbernard/pit/PersonalIssueTracker.groovy
new file mode 100644
index 0000000..8dce79b
--- /dev/null
+++ b/src/com/jdbernard/pit/PersonalIssueTracker.groovy
@@ -0,0 +1,51 @@
+package com.jdbernard.pit
+
+def cli = new CliBuilder(usage: '')
+cli.h(longOpt: 'help', 'Show help information.')
+cli.v(longOpt: 'verbose', 'Show verbose task information')
+cli.l(longOpt: 'list', 'List issues. Unless otherwise specified it lists all '
+ + 'sub projects and all unclosed issue categories.')
+cli.i(argName: 'id', longOpt: 'id', args: 1,
+ 'Filter issues by id. Accepts a comma-delimited list.')
+cli.c(argName: 'category', longOpt: 'category', args: 1,
+ 'Filter issues by category (bug, feature, task, closed). Accepts a '
+ + 'comma-delimited list.')
+cli.p(argName: 'priority', longOpt: 'priority', args: 1,
+ 'Filter issues by priority. This acts as a threshhold, listing all issues '
+ + 'greater than or equal to the given priority.')
+cli.r(argName: 'project', longOpt: 'project', args: 1,
+ 'Filter issues by project (relative to the current directory). Accepts a '
+ + 'comma-delimited list.')
+cli.s(longOpt: 'show-subprojects',
+ 'Include sup projects in listing (default behaviour)')
+cli.S(longOpt: 'no-subprojects', 'Do not list subprojects.')
+
+def opts = cli.parse(args)
+def issuedb = [:]
+
+if (!opts) System.exit(1) // better solution?
+
+if (opts.h) cli.usage()
+
+def categories = ['bug','feature','task']
+if (opts.c) categories = opts.c.split(/[,\s]/)
+categories = categories.collect { Category.toCategory(it) }
+
+// build issue list
+issuedb = new Project(new File('.'),
+ new Filter('categories': categories,
+ 'priority': (opts.p ? opts.p.toInteger() : 9),
+ 'projects': (opts.r ? opts.r.toLowerCase().split(/[,\s]/).asType(List.class) : []),
+ 'ids': (opts.i ? opts.i.split(/[,\s]/).asType(List.class) : []),
+ 'acceptProjects': (opts.s || !opts.S)))
+
+// list first
+if (opts.l) issuedb.list('verbose': opts.v)
+
+// change priority second
+//else if (opts.cp)
+
+// change category third
+//else if (opts.cc)
+
+// new entry last
diff --git a/src/com/jdbernard/pit/Project.groovy b/src/com/jdbernard/pit/Project.groovy
new file mode 100644
index 0000000..717669f
--- /dev/null
+++ b/src/com/jdbernard/pit/Project.groovy
@@ -0,0 +1,73 @@
+package com.jdbernard.pit
+
+class Project {
+
+ String name
+ Map issues = [:]
+ Map projects = [:]
+
+ Project(File dir, Filter filter = null) {
+ dir.eachFile { child ->
+
+ // add sub projects
+ if (child.isDirectory()) {
+ if ( child.name ==~ /\d{4}/ || // just an issue folder
+ (filter && !filter.accept(child.name)))
+ return
+
+ // otherwise build and add to list
+ projects[(child.name)] = new Project(child, filter)
+ } else if (child.isFile()) {
+ def issue
+
+ // if exception, then not an issue
+ try { issue = new Issue(child) } catch (all) { return }
+
+ if (filter && !filter.accept(issue)) return
+
+ issues[(issue.id)] = issue
+ }
+ }
+ }
+
+ public void eachIssue(Closure c) {
+ for (i in issues.values()) c.call(i)
+ for (p in projects.values()) p.eachIssue(c)
+ }
+
+ public void each(Filter filter = null, Closure c) {
+ def is = filter?.issueSorter ?: { it.id.toInteger() }
+ def ps = filter?.projectSorter ?: { it.name }
+
+ for (issue in issues.values().sort(is)) {
+ if (filter && !filter.accept(issue))
+ return
+
+ c.call(issue)
+ }
+
+ for (project in projects.values().sort(ps)) {
+ if (filter && !filter.accept(project))
+ return
+
+ c.call(project)
+ project.each(c)
+ }
+ }
+
+ public void list(Map options = [:]) {
+ if (!options.offset) options.offset = ""
+ if (!options.verbose) options.verbose = false
+
+ each(options.filter) {
+ if (it instanceof Project) {
+ println "\n${options.offset}${it.name}"
+ println "${options.offset}${'-'.multiply(p.name.length())}"
+ } else {
+ println "${options.offset}${it.id}(${it.priority}): " +
+ "${it.category} ${it.title}"
+ if (options.verbose) println "\n${it.text}"
+ }
+ }
+ }
+}