Refactored Issues/Projects in to abstract classes with further implementations.

ALl tests passing. Ran into some frustrationg aspects of Groovy
This commit is contained in:
Jonathan Bernard 2010-02-20 22:31:33 -06:00
parent fe6ce85a73
commit 7ff8806544
11 changed files with 272 additions and 166 deletions

View File

@ -88,7 +88,7 @@
<formatter type="brief" usefile="false" /> <formatter type="brief" usefile="false" />
<batchtest> <batchtest>
<fileset dir="${build.dir}/tests"> <fileset dir="${build.dir}/tests">
<include name="**/*.class"/> <include name="**/*Test.class"/>
</fileset> </fileset>
</batchtest> </batchtest>
</junit> </junit>

View File

@ -1,9 +1,9 @@
#Thu Feb 18 12:01:49 CST 2010 #Sat Feb 20 22:25:08 CST 2010
build.dir=build build.dir=build
src.dir=src src.dir=src
lib.shared.dir=../shared-libs lib.shared.dir=../shared-libs
test.dir=test test.dir=test
build.number=4 build.number=53
expected.application.version=1.1.2 expected.application.version=1.1.2
lib.dir=lib lib.dir=lib
release.dir=release release.dir=release

View File

@ -0,0 +1,68 @@
package com.jdbernard.pit
import java.lang.IllegalArgumentException as IAE
public class FileIssue extends Issue {
File source
FileIssue(File file) {
/* I do not like this construction, but groovy automatically
* calls obj.setProperty(...) when you type obj.property = ...
* There is an exception for fields accessed withing the class
* that defines them, it does not catt eh setter/getter, but
* this exception does not extend to subclasses accessing member
* variables of their parent class. So instead of using Issue's
* default constructor and setting the id, category, and priority
* fields here, we have to let Issue's constructor initialize
* those values.*/
super((file.name =~ /(\d+)([bcft])(\d).*/)[0][1],
Category.toCategory((file.name =~ /(\d+)([bcft])(\d).*/)[0][2]),
(file.name =~ /(\d+)([bcft])(\d).*/)[0][3].toInteger())
//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()*/
this.source = file
file.withReader { title = it.readLine() }
text = file.text
}
void setCategory(Category c) {
super.setCategory(c)
source.renameTo(new File(source.canonicalFile.parentFile, getFilename()))
}
void setPriority(int p) {
super.setPriority(p)
source.renameTo(new File(source.canonicalFile.parentFile, getFilename()))
}
String getFilename() { return makeFilename(id, category, priority) }
static boolean isValidFilename(String name) {
return name ==~ /(\d+)([bcft])(\d).*/
}
static String makeFilename(String id, Category category, int priority) {
// bounds check priority
priority = Math.min(9, Math.max(0, priority))
//check for valid values of cateogry and id
if (category == null)
throw new IAE("Category must be non-null.")
if (!(id ==~ /\d+/))
throw new IAE( "'${id}' is not a legal value for id.")
return id + category.symbol + priority + ".rst";
}
}

View File

@ -0,0 +1,68 @@
package com.jdbernard.pit
class FileProject extends Project {
File source
FileProject(File dir) {
super(dir.name)
if (!dir.isDirectory())
throw new IllegalArgumentException(
"${dir.name} is not a directory.")
this.source = dir
dir.eachFile { child ->
// add sub projects
if (child.isDirectory()) {
if ( child.name ==~ /\d{4}/) return // just an issue folder
// otherwise build and add to list
projects[(child.name)] = new FileProject(child)
} else if (child.isFile()) {
def issue
// if exception, then not an issue
try { issue = new FileIssue(child) } catch (all) { return }
issues[(issue.id)] = issue
}
}
}
public void rename(String newName) {
this.name = newName
}
public void setName(String name) {
super.setName(name)
source.renameTo(new File(source.canonicalFile.parentFile, name))
}
public FileIssue createNewIssue(Map options) {
if (!options) 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
if (issues.size() == 0) id = '0000'
else {
id = (issues.values().max { it.id.toInteger() }).id
id = (id.toInteger() + 1).toString().padLeft(id.length(), '0')
}
def issueFile = new File(source, FileIssue.makeFilename(id, options.category, options.priority))
assert !issueFile.exists()
issueFile.createNewFile()
issueFile.write(options.text)
return new FileIssue(issueFile)
}
@Override
String toString() { return name }
}

View File

@ -4,62 +4,26 @@ import java.lang.IllegalArgumentException as IAE
public class Issue { public class Issue {
final String id String id
Category category Category category
int priority int priority
String title String title
String text String text
File source
Issue(File file) { Issue(String id, Category c = Category.TASK, int p = 9) {
this.id = id
def matcher = file.name =~ /(\d{4})([bftc])(\d).*/ this.category = c
if (!matcher) return null this.priority = p
this.source = file
id = matcher[0][1]
category = Category.toCategory(matcher[0][2])
priority = matcher[0][3].toInteger()
file.withReader { title = it.readLine() }
text = file.text
} }
/**
*/
void setCategory(Category c) { void setCategory(Category c) {
if (c == null)
if (category == null)
throw new IAE("Category cannot be null.") throw new IAE("Category cannot be null.")
this.category = c this.category = c
source.renameTo(new File(source.canonicalFile.parentFile, getFilename()))
} }
void setPriority(int p) { void setPriority(int p) { priority = Math.min(9, Math.max(0, p)) }
// bounds check priority
priority = Math.min(9, Math.max(0, priority))
source.renameTo(new File(source.canonicalFile.parentFile, getFilename()))
}
String getFilename() { return makeFilename(id, category, priority) }
static String makeFilename(String id, Category category, int priority) {
// bounds check priority
priority = Math.min(9, Math.max(0, priority))
//check for valid values of cateogry and id
if (category == null)
throw new IAE("Category must be non-null.")
if (!(/\d+/ ==~ id))
throw new IAE( "'${id}' is not a legal value for id.")
return id + category.symbol + priority + ".rst";
}
@Override @Override
String toString() { return "${id}(${priority}): ${category} ${title}" } String toString() { return "${id}(${priority}): ${category} ${title}" }

View File

@ -1,46 +1,13 @@
package com.jdbernard.pit package com.jdbernard.pit
class Project { public abstract class Project {
String name String name
Map<String, Issue> issues = [:] Map<String, Issue> issues = [:]
Map<String, Project> projects = [:] Map<String, Project> projects = [:]
File source
Project(File dir) { Project(String name) { this.name = name }
if (!dir.isDirectory())
throw new IllegalArgumentException(
"${dir.name} is not a directory.")
this.source = dir
this.name = dir.name
dir.eachFile { child ->
// add sub projects
if (child.isDirectory()) {
if ( child.name ==~ /\d{4}/) return // just an issue folder
// otherwise build and add to list
projects[(child.name)] = new Project(child)
} else if (child.isFile()) {
def issue
// if exception, then not an issue
try { issue = new Issue(child) } catch (all) { return }
issues[(issue.id)] = issue
}
}
}
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(Filter filter = null, Closure c) { public void eachIssue(Filter filter = null, Closure c) {
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
for (i in issues.values().sort(sorter)) for (i in issues.values().sort(sorter))
@ -55,46 +22,8 @@ class Project {
c.call(p) c.call(p)
} }
/*public void each(Filter filter = null, Closure c) {
def is = filter?.issueSorter ?: Filter.defaultIssueSorter
def ps = filter?.projectSorter ?: Filter.defaultProjectSorter
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)
}
}*/
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
if (issues.size() == 0) id = '0000'
else {
id = (issues.values().max { it.id.toInteger() }).id
id = (id.toInteger() + 1).toString().padLeft(id.length(), '0')
}
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 @Override
String toString() { return name } String toString() { return name }
public abstract Issue createNewIssue(Map options)
} }

View File

@ -1,4 +1,4 @@
public package com.jdbernard.pit package com.jdbernard.pit
import org.junit.Test import org.junit.Test
import static org.junit.Assert.assertEquals import static org.junit.Assert.assertEquals
@ -7,7 +7,7 @@ import static com.jdbernard.pit.Category.toCategory
class CategoryTest { class CategoryTest {
@Test testToCategory() { @Test void testToCategory() {
assertEquals toCategory("BUG"), Category.BUG assertEquals toCategory("BUG"), Category.BUG
assertEquals toCategory("FEATURE"), Category.FEATURE assertEquals toCategory("FEATURE"), Category.FEATURE
@ -16,21 +16,21 @@ class CategoryTest {
assertEquals toCategory("bug"), Category.BUG assertEquals toCategory("bug"), Category.BUG
assertEquals toCategory("feature"), Category.FEATURE assertEquals toCategory("feature"), Category.FEATURE
assertEquals toCategory("task"), Category.TASk assertEquals toCategory("task"), Category.TASK
assertEquals toCategory("closed"), Category.CLOSED assertEquals toCategory("closed"), Category.CLOSED
assertEquals toCategory("b"), Category.BUG assertEquals toCategory("b"), Category.BUG
assertEquals toCategory("f"), Category.FEATURE assertEquals toCategory("f"), Category.FEATURE
assertEquals toCategory("t"), Category.TASk assertEquals toCategory("t"), Category.TASK
assertEquals toCategory("c"), Category.CLOSED assertEquals toCategory("c"), Category.CLOSED
} }
@Test testGetSymbol() { @Test void testGetSymbol() {
assertEquals Category.BUG.symbol, "b" assertEquals Category.BUG.symbol, "b"
assertEquals Category.CLOSED.symbol "c" assertEquals Category.CLOSED.symbol, "c"
assertEquals Category.FEATURE.symbol "f" assertEquals Category.FEATURE.symbol, "f"
assertEquals Category.TASK.symbol "t" assertEquals Category.TASK.symbol, "t"
} }
} }

View File

@ -5,7 +5,7 @@ import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertFalse import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertEquals import static org.junit.Assert.assertEquals
class IssueTest { class FileIssueTest {
def issues def issues
File testDir File testDir
@ -22,7 +22,7 @@ class IssueTest {
"Add the killer feature to the killer app.\n" + "Add the killer feature to the killer app.\n" +
"=========================================\n\n" + "=========================================\n\n" +
"Make our killer app shine!.") "Make our killer app shine!.")
issues << new Issue(issueFile) issues << new FileIssue(issueFile)
issueFile = new File(testDir, '0002t5.rst') issueFile = new File(testDir, '0002t5.rst')
issueFile.write( issueFile.write(
@ -30,7 +30,7 @@ class IssueTest {
"==============\n\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!") "We must rectify this at once!")
issues << new Issue(issueFile) issues << new FileIssue(issueFile)
} }
@After void deleteIssueFiles() { @After void deleteIssueFiles() {
@ -73,7 +73,7 @@ class IssueTest {
@Test void testConstruction() { @Test void testConstruction() {
File issueFile = new File(testDir, '0001f1.rst') File issueFile = new File(testDir, '0001f1.rst')
Issue issue = new Issue(issueFile) Issue issue = new FileIssue(issueFile)
assertEquals issue.id , "0001" assertEquals issue.id , "0001"
assertEquals issue.category , Category.FEATURE assertEquals issue.category , Category.FEATURE
@ -86,20 +86,20 @@ class IssueTest {
} }
@Test void testMakeFilename() { @Test void testMakeFilename() {
assertEquals Issue.makeFilename('0001', Category.BUG, 5) , '0001b5.rst' assertEquals FileIssue.makeFilename('0001', Category.BUG, 5) , '0001b5.rst'
assertEquals Issue.makeFilename('0010', Category.FEATURE, 1), '0010f1.rst' assertEquals FileIssue.makeFilename('0010', Category.FEATURE, 1), '0010f1.rst'
assertEquals Issue.makeFilename('0002', Category.CLOSED, 3) , '0002c3.rst' assertEquals FileIssue.makeFilename('0002', Category.CLOSED, 3) , '0002c3.rst'
assertEquals Issue.makeFilename('0001', Category.BUG, -2) , '0001b0.rst' assertEquals FileIssue.makeFilename('0001', Category.BUG, -2) , '0001b0.rst'
assertEquals Issue.makeFilename('0001', Category.TASK, 10) , '0001t9.rst' assertEquals FileIssue.makeFilename('0001', Category.TASK, 10) , '0001t9.rst'
assertEquals Issue.makeFilename('00101', Category.BUG, 5) , '00101b5.rst' assertEquals FileIssue.makeFilename('00101', Category.BUG, 5) , '00101b5.rst'
try { try {
Issue.makeFilename('badid', Category.BUG, 5) FileIssue.makeFilename('badid', Category.BUG, 5)
assertTrue 'Issue.makeFilename() succeeded with bad id input.', false assertTrue 'Issue.makeFilename() succeeded with bad id input.', false
} catch (IllegalArgumentException iae) {} } catch (IllegalArgumentException iae) {}
try { try {
Issue.makeFilename('0002', null, 5) FileIssue.makeFilename('0002', null, 5)
assertTrue 'Issue.makeFilename() succeeded given no Category.', false assertTrue 'Issue.makeFilename() succeeded given no Category.', false
} catch (IllegalArgumentException iae) {} } catch (IllegalArgumentException iae) {}
} }

View File

@ -8,7 +8,7 @@ import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertNotNull import static org.junit.Assert.assertNotNull
import static org.junit.Assert.assertTrue import static org.junit.Assert.assertTrue
class ProjectTest { class FileProjectTest {
File testDir File testDir
Project rootProj Project rootProj
@ -69,7 +69,7 @@ class ProjectTest {
subDir = new File(testDir, 'emptyproj') subDir = new File(testDir, 'emptyproj')
subDir.mkdirs() subDir.mkdirs()
rootProj = new Project(testDir) rootProj = new FileProject(testDir)
} }
@After void deleteTestProjects() { @After void deleteTestProjects() {
@ -80,11 +80,11 @@ class ProjectTest {
} }
@Test void testConstruction() { @Test void testConstruction() {
Project proj = new Project(testDir) Project proj = new FileProject(testDir)
assertEquals proj.name, 'testdir' assertEquals proj.name, 'testdir'
assertEquals proj.issues.size(), 3 assertEquals proj.issues.size(), 3
assertEquals proj.projects.size(), 1 assertEquals proj.projects.size(), 2
// Issue construction in general is under test in IssueTest // Issue construction in general is under test in IssueTest
// just check that the issues actually exists // just check that the issues actually exists
@ -110,13 +110,14 @@ class ProjectTest {
'Zippners are not zippning.' 'Zippners are not zippning.'
assertNotNull proj.projects.emptyproj assertNotNull proj.projects.emptyproj
assertEquals proj.projects.emptyproj.size(), 0 assertEquals proj.projects.emptyproj.issues.size(), 0
assertEquals proj.projects.emptyproj.projects.size(), 0
} }
@Test void testRename() { @Test void testRename() {
assert rootProj.name == 'testdir' assert rootProj.name == 'testdir'
rootProj.rename('renamedTestDir') rootProj.name = 'renamedTestDir'
assertEquals rootProj.name, 'renamedTestDir' assertEquals rootProj.name, 'renamedTestDir'
assertTrue new File('renamedTestDir').exists() assertTrue new File('renamedTestDir').exists()
@ -146,23 +147,4 @@ class ProjectTest {
} }
/*@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 }
}*/
} }

View File

@ -1,11 +1,96 @@
package com.jdbernard.pit package com.jdbernard.pit
import org.junit.Test import org.junit.Test
import org.junit.Before
import org.junit.After
import static org.junit.Assert.assertTrue import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertFalse
class FilterTest { class FilterTest {
@Test void emptyTest() { Project proj
@Before void setUpIssues() {
proj = new MockProject('proj1')
def issue = new Issue( '0000', Category.TASK, 5)
proj.issues['0000'] = issue
issue = new Issue('0001', Category.BUG, 3)
proj.issues['0001'] = issue
issue = new Issue('0002', Category.CLOSED, 9)
proj.issues['0002'] = issue
issue = new Issue('0003', Category.FEATURE, 0)
proj.issues['0003'] = issue
def subProj = new MockProject('subproj1')
proj.projects['subproj1'] = subProj
subProj = new MockProject('subproj2')
proj.projects['subproj2'] = subProj
}
@Test void testDefaultFilter() {
Filter f = new Filter()
proj.issues.values().each { assertTrue f.accept(it) }
proj.projects.values().each { assertTrue f.accept(it) }
}
@Test void testPriorityIssueFilter() {
Filter f = new Filter(priority: 9)
proj.eachIssue { assertTrue f.accept(it) }
f.priority = 6
assertTrue f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
f.priority = 5
assertTrue f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
f.priority = 0
assertFalse f.accept(proj.issues['0000'])
assertFalse f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
}
@Test void testCategoryFilter() {
Filter f = new Filter(categories:
[Category.BUG, Category.FEATURE, Category.TASK])
assertTrue f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
f.categories = [ Category.CLOSED ]
assertFalse f.accept(proj.issues['0000'])
assertFalse f.accept(proj.issues['0001'])
assertTrue f.accept(proj.issues['0002'])
assertFalse f.accept(proj.issues['0003'])
f.categories = [ Category.BUG, Category.FEATURE ]
assertFalse f.accept(proj.issues['0000'])
assertTrue f.accept(proj.issues['0001'])
assertFalse f.accept(proj.issues['0002'])
assertTrue f.accept(proj.issues['0003'])
}
@Test void testProjectFilter() {
} }

View File

@ -0,0 +1,10 @@
package com.jdbernard.pit
class MockProject extends Project {
public MockProject(String name) { super(name) }
public Issue createNewIssue(Map options) {
throw new UnsupportedOperationException()
}
}