diff --git a/issues/libpit/0002c6.rst b/issues/libpit/0002c6.rst index be412bc..1c87fc8 100644 --- a/issues/libpit/0002c6.rst +++ b/issues/libpit/0002c6.rst @@ -1,5 +1,5 @@ -Add the ability to change an issue's status -=========================================== +Add the ability to change an issue's category +============================================= The -l/--list options lists issues, add a set of options to reclassify an issue from one category to another. The most common is CLOSED, but diff --git a/issues/libpit/0003c6.rst b/issues/libpit/0003c6.rst index c740c7a..155a3a1 100644 --- a/issues/libpit/0003c6.rst +++ b/issues/libpit/0003c6.rst @@ -1,4 +1,2 @@ -Add the ability to re-prioritize a set of issues -================================================ - -Add a mode that changes the priority of a selection of issues. +Add the ability to re-prioritize an issue. +========================================== diff --git a/issues/libpit/0004f7.rst b/issues/libpit/0004c7.rst similarity index 72% rename from issues/libpit/0004f7.rst rename to issues/libpit/0004c7.rst index 21e9f8f..f736655 100644 --- a/issues/libpit/0004f7.rst +++ b/issues/libpit/0004c7.rst @@ -1,2 +1,4 @@ Add the ability to enter new issues =================================== + +Implemented on 2010/02/17 diff --git a/issues/libpit/0005t1.rst b/issues/libpit/0005t5.rst similarity index 100% rename from issues/libpit/0005t1.rst rename to issues/libpit/0005t5.rst diff --git a/issues/libpit/0006t1.rst b/issues/libpit/0006t5.rst similarity index 100% rename from issues/libpit/0006t1.rst rename to issues/libpit/0006t5.rst diff --git a/issues/libpit/0007t1.rst b/issues/libpit/0007t5.rst similarity index 100% rename from issues/libpit/0007t1.rst rename to issues/libpit/0007t5.rst diff --git a/issues/libpit/0008t1.rst b/issues/libpit/0008t5.rst similarity index 100% rename from issues/libpit/0008t1.rst rename to issues/libpit/0008t5.rst diff --git a/issues/libpit/0009t1.rst b/issues/libpit/0009t5.rst similarity index 100% rename from issues/libpit/0009t1.rst rename to issues/libpit/0009t5.rst diff --git a/issues/libpit/0010t1.rst b/issues/libpit/0010t5.rst similarity index 100% rename from issues/libpit/0010t1.rst rename to issues/libpit/0010t5.rst diff --git a/issues/libpit/0011t2.rst b/issues/libpit/0011t6.rst similarity index 100% rename from issues/libpit/0011t2.rst rename to issues/libpit/0011t6.rst diff --git a/issues/libpit/0012t1.rst b/issues/libpit/0012c1.rst similarity index 63% rename from issues/libpit/0012t1.rst rename to issues/libpit/0012c1.rst index 58cf3b7..bc9da41 100644 --- a/issues/libpit/0012t1.rst +++ b/issues/libpit/0012c1.rst @@ -1,2 +1,4 @@ Add unit tests for changing an issue's category. ================================================ + +Test defined in test/com/jdbernard/pit/IssueTest.groovy diff --git a/issues/libpit/0013t1.rst b/issues/libpit/0013c1.rst similarity index 62% rename from issues/libpit/0013t1.rst rename to issues/libpit/0013c1.rst index bdf375c..85cf118 100644 --- a/issues/libpit/0013t1.rst +++ b/issues/libpit/0013c1.rst @@ -1,2 +1,5 @@ Add unit tests for changing an issue's priority. ================================================ + + +Test defined in test/com/jdbernard/pit/IssueTest.groovy diff --git a/issues/libpit/0014t1.rst b/issues/libpit/0014c1.rst similarity index 61% rename from issues/libpit/0014t1.rst rename to issues/libpit/0014c1.rst index d904a7e..1891b0e 100644 --- a/issues/libpit/0014t1.rst +++ b/issues/libpit/0014c1.rst @@ -1,3 +1,4 @@ Add unit tests covering Issue construction. =========================================== +Test defined in test/com/jdbernard/pit/IssueTest.groovy diff --git a/issues/libpit/0015t1.rst b/issues/libpit/0015t5.rst similarity index 100% rename from issues/libpit/0015t1.rst rename to issues/libpit/0015t5.rst diff --git a/issues/pit-cli/0003c5.rst b/issues/pit-cli/0003c5.rst new file mode 100644 index 0000000..5bdd8d9 --- /dev/null +++ b/issues/pit-cli/0003c5.rst @@ -0,0 +1,2 @@ +Add the ability to change an Issue's priority. +============================================== diff --git a/issues/pit-cli/0004c5.rst b/issues/pit-cli/0004c5.rst new file mode 100644 index 0000000..91c265e --- /dev/null +++ b/issues/pit-cli/0004c5.rst @@ -0,0 +1,2 @@ +Add the ability to change an Issue's category. +============================================== diff --git a/issues/pit-cli/0005f4.rst b/issues/pit-cli/0005f4.rst new file mode 100644 index 0000000..564c4e7 --- /dev/null +++ b/issues/pit-cli/0005f4.rst @@ -0,0 +1,2 @@ +Add the ability to create a new issue. +====================================== diff --git a/libpit/build.xml b/libpit/build.xml index d6077a0..c40f8a2 100644 --- a/libpit/build.xml +++ b/libpit/build.xml @@ -14,6 +14,12 @@ + + + + + + - + + + + + + + + + + + + + + + + + + ids = null int priority = 9 boolean acceptProjects = true - Closure projectSorter - Closure issueSorter + Closure projectSorter = defaultIssueSorter + Closure issueSorter = defaultProjectSorter + + public static Closure defaultIssueSorter = { it.id.toInteger() } + public static Closure defaultProjectSorter = { it.name } public boolean accept(Issue i) { return (i.priority <= priority && diff --git a/libpit/src/com/jdbernard/pit/Issue.groovy b/libpit/src/com/jdbernard/pit/Issue.groovy index 5c0ef4e..8d21d9f 100644 --- a/libpit/src/com/jdbernard/pit/Issue.groovy +++ b/libpit/src/com/jdbernard/pit/Issue.groovy @@ -26,16 +26,22 @@ public class Issue { void setCategory(Category c) { this.category = c - source.renameTo(getFilename()) + source.renameTo(new File(source.canonicalFile.parentFile, getFilename())) } void setPriority(int p) { if (p < 0) priority = 0 else if (p > 9) priority = 9 else priority = p - source.renameTo(getFilename()) + source.renameTo(new File(source.canonicalFile.parentFile, getFilename())) } - String getFilename() { return id + category.symbol + priority + ".rst"; } + String getFilename() { return makeFilename(id, category, priority) } + static String makeFilename(String id, Category category, int priority) { + return id + category.symbol + priority + ".rst"; + } + + @Override + String toString() { return "${id}(${priority}): ${category} ${title}" } } diff --git a/libpit/src/com/jdbernard/pit/Project.groovy b/libpit/src/com/jdbernard/pit/Project.groovy index 37fb7c1..9565790 100644 --- a/libpit/src/com/jdbernard/pit/Project.groovy +++ b/libpit/src/com/jdbernard/pit/Project.groovy @@ -38,16 +38,26 @@ class Project { } } - public void rename(String newName) { source.renameTo(newName) } + public void rename(String newName) { + this.name = newName + source.renameTo(new File(source.canonicalFile.parentFile, newName)) + } + + public void setName(String name) { rename(name) } - public void eachIssue(Closure c) { - for (i in issues.values()) c.call(i) - for (p in projects.values()) p.eachIssue(c) + public void eachIssue(Filter filter = null, Closure c) { + def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter + for (i in issues.values().sort(sorter)) c.call(i) + } + + public void eachProject(Filter filter = null, Closure c) { + def sorter = filter?.projectSorter ?: Filter.defaultProjectSorter + for (p in projects.values().sort(sorter)) c.call(p) } public void each(Filter filter = null, Closure c) { - def is = filter?.issueSorter ?: { it.id.toInteger() } - def ps = filter?.projectSorter ?: { it.name } + def is = filter?.issueSorter ?: Filter.defaultIssueSorter + def ps = filter?.projectSorter ?: Filter.defaultProjectSorter for (issue in issues.values().sort(is)) { if (filter && !filter.accept(issue)) @@ -61,23 +71,25 @@ class Project { return c.call(project) - project.each(c) } } - public void list(Map options = [:]) { - if (!options.offset) options.offset = "" - if (!options.verbose) options.verbose = false + public Issue createNewIssue(Map options) { + if (!options.category) options.category = Category.TASK + if (!options.priority) options.priority = 5 + if (!options.text) options.text = "Default issue title.\n" + + "====================\n" + String id = (issues.values().max { it.id.toInteger() }).id - each(options.filter) { - if (it instanceof Project) { - println "\n${options.offset}${it.name}" - println "${options.offset}${'-'.multiply(it.name.length())}" - } else { - println "${options.offset}${it.id}(${it.priority}): " + - "${it.category} ${it.title}" - if (options.verbose) println "\n${it.text}" - } - } + def issueFile = new File(source, Issue.makeFilename(id, options.category, options.priority)) + assert !issueFile.exists() + issueFile.createNewFile() + issueFile.write(options.text) + + return new Issue(issueFile) } + + @Override + String toString() { return name } + } diff --git a/libpit/test/com/jdbernard/pit/FilterTest.groovy b/libpit/test/com/jdbernard/pit/FilterTest.groovy new file mode 100644 index 0000000..cf9c89e --- /dev/null +++ b/libpit/test/com/jdbernard/pit/FilterTest.groovy @@ -0,0 +1,12 @@ +package com.jdbernard.pit + +import org.junit.Test +import static org.junit.Assert.assertTrue + +class FilterTest { + + @Test void emptyTest() { + + } + +} diff --git a/libpit/test/com/jdbernard/pit/IssueTest.groovy b/libpit/test/com/jdbernard/pit/IssueTest.groovy index 4a45720..2221e11 100644 --- a/libpit/test/com/jdbernard/pit/IssueTest.groovy +++ b/libpit/test/com/jdbernard/pit/IssueTest.groovy @@ -1,6 +1,10 @@ package com.jdbernard.pit -class IssueTest +import org.junit.* +import static org.junit.Assert.assertTrue +import static org.junit.Assert.assertFalse + +class IssueTest { def issues File testDir @@ -23,43 +27,60 @@ class IssueTest issueFile.write( "Obtain donuts.\n" + "==============\n\n" + - "The office is seriously lacking in sugary donuts.\n\n + "The office is seriously lacking in sugary donuts.\n\n" + "We must rectify this at once!") issues << new Issue(issueFile) } - @AfterClass void deleteIssueFiles() { + @After void deleteIssueFiles() { testDir.deleteDir() } @Test void testSetCategory() { - assert issues[0].category == Category.FEATURE - assert issues[1].category == Category.TASK + assertTrue issues[0].category == Category.FEATURE + assertTrue issues[1].category == Category.TASK - issues[0].category == Category.CLOSED - issues[1].category == Category.TASK + issues[0].category = Category.CLOSED + issues[1].category = Category.BUG - assert issues[0].category == Category.CLOSED - assert issues[1].category == Category.BUG + assertTrue issues[0].category == Category.CLOSED + assertTrue issues[1].category == Category.BUG - assert new File(testDir, '0001c1.rst').exists() - assert new File(testDir, '0002b5.rst').exists() + assertTrue new File(testDir, '0001c1.rst').exists() + assertTrue new File(testDir, '0002b5.rst').exists() assertFalse new File(testDir, '0001f1.rst').exists() assertFalse new File(testDir, '0002t5.rst').exists() } - @Test void testIssueConstructor() { + @Test void testSetPriority() { + + assertTrue issues[0].priority == 1 + assertTrue issues[1].priority == 5 + + issues[0].priority = 2 + issues[1].priority = 9 + + assertTrue issues[0].priority == 2 + assertTrue issues[1].priority == 9 + + assertTrue new File(testDir, '0001f2.rst').exists() + assertTrue new File(testDir, '0002t9.rst').exists() + assertFalse new File(testDir, '0001f1.rst').exists() + assertFalse new File(testDir, '0002t5.rst').exists() + } + + @Test void testConstruction() { File issueFile = new File(testDir, '0001f1.rst') Issue issue = new Issue(issueFile) - assert issue.id == "0001" - assert issue.category == Category.FEATURE - assert issue.priority == 1 - assert issue.title == "Add the killer feature to the killer app." - assert issue.text == "Add the killer feature to the killer app.\n" + + assertTrue issue.id == "0001" + assertTrue issue.category == Category.FEATURE + assertTrue issue.priority == 1 + assertTrue issue.title == "Add the killer feature to the killer app." + assertTrue issue.text == "Add the killer feature to the killer app.\n" + "=========================================\n\n" + "Make our killer app shine!." - assert issue.source == issueFile + assertTrue issue.source == issueFile } } diff --git a/libpit/test/com/jdbernard/pit/ProjectTest.groovy b/libpit/test/com/jdbernard/pit/ProjectTest.groovy new file mode 100644 index 0000000..044c474 --- /dev/null +++ b/libpit/test/com/jdbernard/pit/ProjectTest.groovy @@ -0,0 +1,115 @@ +package com.jdbernard.pit + +import org.junit.After +import org.junit.Before +import org.junit.Test +import static org.junit.Assert.assertTrue +import static org.junit.Assert.assertFalse + +class ProjectTest { + + File testDir + Project rootProj + + @Before void createTestProjects() { + testDir = new File('testdir') + testDir.mkdirs() + + def issueFile = new File(testDir, '0001t5.rst') + issueFile.createNewFile() + issueFile.write('Test Issue 1\n' + + '============\n\n' + + 'This is the first test issue.') + + issueFile = new File(testDir, '0002b5.rst') + issueFile.createNewFile() + issueFile.write('Test Bug\n' + + '========\n\n' + + 'Yeah, it is a test bug.') + + issueFile = new File(testDir, '0003f2.rst') + issueFile.createNewFile() + issueFile.write('Important Feature Request\n' + + '=========================\n\n' + + 'Here is our sweet feature. Please implement it!') + + def subDir = new File(testDir, 'subproj1') + subDir.mkdirs() + + issueFile = new File(subDir, '0001f3.rst') + issueFile.createNewFile() + issueFile.write('First feature in subproject\n' + + '===========================\n\n' + + 'Please make the grubblers grobble.') + + issueFile = new File(subDir, '0002b4.rst') + issueFile.createNewFile() + issueFile.write('Zippners are not zippning.\n' + + '==========================\n\n' + + 'For some reason, the Zippners are bilperring, not zippning.') + + rootProj = new Project(testDir) + } + + @After void deleteTestProjects() { + testDir.delete() + } + + @Test void testConstruction() { + Project proj = new Project(testDir, null) + + assertTrue proj.name == 'testdir' + assertTrue proj.issues.size() == 3 + assertTrue proj.projects.size() == 1 + + // Issue construction in general is under test in IssueTest + // just check that the issues actually exists + assertTrue proj.issues['0001'].id == '0001' + assertTrue proj.issues['0001'].title == 'Test Issue 1' + + assertTrue proj.issues['0002'].id == '0002' + assertTrue proj.issues['0002'].title == 'Test Bug' + + assertTrue proj.issues['0003'].id == '0003' + assertTrue proj.issues['0003'].title == 'Important Feature Request' + + // check sub-project behaviour + assertTrue proj.projects.subproj1 != null + assertTrue proj.projects.subproj1.name == 'subproj1' + assertTrue proj.projects.subproj1.issues.size() == 2 + assertTrue proj.projects.subproj1.projects.size() == 0 + assertTrue proj.projects.subproj1.issues['0001'].id == '0001' + assertTrue proj.projects.subproj1.issues['0001'].title == 'First feature in subproject' + assertTrue proj.projects.subproj1.issues['0002'].id == '0002' + assertTrue proj.projects.subproj1.issues['0002'].title == 'Zippners are not zippning.' + } + + @Test void testRename() { + assert rootProj.name == 'testdir' + + rootProj.rename('renamedTestDir') + + assertTrue rootProj.name == 'renamedTestDir' + assertTrue new File('renamedTestDir').exists() + } + + /*@Test void testEachIssue() { + def expectedList = [rootProj.issues['0001'], + rootProj.issues['0002'], rootProj.issues['0003']] + + // sort using default ordering (ids ascending) + def actualList = [] + rootProj.eachIssue { actualList << it } + + assertArrayEquals expectedList, actualList + + // sort using reverse ordering (ids descending) + expectedList = expectedList.reverse() + actualList = [] + + rootProj.eachIssue( + new Filter(issueSorter: { -(it.id.toInteger()) })) + { actualList << it } + }*/ + +} diff --git a/pit-cli/build.xml b/pit-cli/build.xml index 9f669c6..02aa1de 100644 --- a/pit-cli/build.xml +++ b/pit-cli/build.xml @@ -9,13 +9,29 @@ - - + + + + + + + + + + + + + + + + + + @@ -80,7 +96,7 @@ - + + println "${offset}${issue}" + if (opts.v) { + println "" + issue.text.eachLine { println "${offset} ${it}" } + println "" + } + } + + def projectPrinter + projectPrinter = { project, offset -> + println "\n${offset}${project.name}" + println "${offset}${'-'.multiply(project.name.length())}" + project.eachIssue { issuePrinter.call(it, offset) } + project.eachProject { projectPrinter.call(it, offset + " ") } + } + + issuedb.eachIssue { issuePrinter.call(it, "") } + issuedb.eachProject { projectPrinter.call(it, "") } + +} + +// change priority second +else if (opts.P) { + def priority + try { priority = max(0, min(9, opts.P.toInteger())) } + catch (e) { println "Invalid priority: ${opts.P}"; return 1 } + + walkProject(issuedb) { it.priority = priority } +} +// change category third +else if (opts.C) { + def cat + try { cat = Category.toCategory(opts.C) } + catch (e) { println "Invalid category: ${opts.C}"; return 1 } + + walkProject(issuedb) { it.category = cat } +} +// new entry last +else if (opts.n) { + def cat, priority + String text = "" + Issue ussie + def sin = System.in.newReader() + + while(true) { + try { + print "Category (bug, feature, task, closed): " + cat = Category.toCategory(sin.readLine()) + break + } catch (e) { + println "Invalid category: " + e.getLocalizedMessage() + println "Valid options are: \n${Category.values().join(', ')}\n " + + "(abbreviations are accepted.)" + } + } + + while (true) { + try { + print "Priority (0-9): " + priority = max(0, min(9, sin.readLine().toInteger())) + break + } catch (e) { println "Not a valid value." } + } + + println "Enter issue (use EOF of ^D to end): " + try { + sin.eachLine { line -> + def m = line =~ /(.*)EOF.*/ + if (m) { + text << m[0][1] + sin.close() + } else text << line + } + } catch (e) {} + + issue = issuedb.createNewIssue(category: cat, priority: priority, text: text) + + println "New issue created: " + println issue +} + +def walkProject(Project p, Closure c) { + p.eachIssue(c) + p.eachProject { walkProject(it, c) } +} diff --git a/pit-cli/src/com/jdbernard/pit/PersonalIssurTrackerCLI.groovy b/pit-cli/src/com/jdbernard/pit/PersonalIssurTrackerCLI.groovy deleted file mode 100644 index 8dce79b..0000000 --- a/pit-cli/src/com/jdbernard/pit/PersonalIssurTrackerCLI.groovy +++ /dev/null @@ -1,51 +0,0 @@ -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