Filter and FileProject support for extended properties.

* Added support for extended properties to `Filter`. Unlike the basic
  properties, which allow the caller to specify a list of acceptable values, the
  extended properties only supports a map of properties, all of which must be
  available on the issue and match the given value.
* Made `Issue` a concrete class. There was no reason it could not be a concrete
  object. The main difference is that the extending classes all add some sort of
  persistence to the issue, but having an in-memory `Issue` in its most basic
  form can be useful and should be allowed.
* Changed the default priority for new issues from 9 to 5.
* Bug fix on `FileIssue.formatIssue()`. If was not properly returning its
  result leading to failure if no extended properties were given.
* Reworked `FileIssue.formatIssue()` to only print the horizontal rule
  seperating the issue text from the extended properties if there are extended
  properties to print as well.
* Changed the `FileProject.createNewIssue()` to handle extended properties and
  handle the basic properties in a manner more consistent with `Issue`. It is
  now creating an `Issue` object and using the `FileIssue.formatIssue()`
  function to write that to a new issue file, then loading that file back in as
  a `FileIssue`. This cuts down on code duplication and makes it so that the
  options map that `FileProject.createNewIssue()` expects is the same as the
  options to the `Issue` constructor.
This commit is contained in:
Jonathan Bernard 2011-12-08 14:38:24 -06:00
parent fd94f0e41a
commit 846d1edc74
5 changed files with 62 additions and 45 deletions

View File

@ -1,11 +1,11 @@
#Wed, 07 Dec 2011 17:53:14 -0600 #Thu, 08 Dec 2011 14:35:45 -0600
#Sat Apr 24 17:08:00 CDT 2010 #Sat Apr 24 17:08:00 CDT 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=8 build.number=10
version=3.1.0 version=3.2.0
name=libpit name=libpit
lib.dir=lib lib.dir=lib
lib.local=true lib.local=true

View File

@ -6,6 +6,7 @@ class Filter {
List<Status> status = null List<Status> status = null
List<String> projects = null List<String> projects = null
List<String> ids = null List<String> ids = null
Map<String, Object> extendedProperties = null
int priority = 9 int priority = 9
boolean acceptProjects = true boolean acceptProjects = true
def issueSorter = defaultIssueSorter def issueSorter = defaultIssueSorter
@ -15,10 +16,18 @@ class Filter {
public static Closure defaultProjectSorter = { it.name } public static Closure defaultProjectSorter = { it.name }
public boolean accept(Issue i) { public boolean accept(Issue i) {
return (i.priority <= priority && return (
(!categories || categories.contains(i.category)) && // Needs to meet the priority threshold.
(!status || status.contains(i.status)) && i.priority <= priority &&
(!ids || ids.contains(i.id))) // Needs to be in one of the filtered categories (if given)
(!categories || categories.contains(i.category)) &&
// Needs to have one of the filtered statuses (if given)
(!status || status.contains(i.status)) &&
// Needs to be one of the filtered ids (if given)
(!ids || ids.contains(i.id)) &&
// Needs to have all of the extended properties (if given)
(!extendedProperties ||
extendedProperties.every { name, value -> i[name] == value }))
} }
public boolean accept(Project p) { public boolean accept(Project p) {

View File

@ -2,7 +2,7 @@ package com.jdbernard.pit
import java.lang.IllegalArgumentException as IAE import java.lang.IllegalArgumentException as IAE
public abstract class Issue { public class Issue {
protected String id protected String id
protected Category category protected Category category
@ -17,16 +17,15 @@ public abstract class Issue {
this.id = props.id this.id = props.id
this.category = props.category ?: Category.TASK this.category = props.category ?: Category.TASK
this.status = props.status ?: Status.NEW this.status = props.status ?: Status.NEW
this.priority = props.priority ?: 9 this.priority = props.priority ?: 5
this.title = props.title ?: '' this.title = props.title ?: ''
this.text = props.text ?: '' this.text = props.text ?: ''
// Put all the non-native properties into our extendedProperties map.
def nativeProps = def nativeProps =
["id", "category", "status", "priority", "title", "text"] ["id", "category", "status", "priority", "title", "text"]
extendedProperties.putAll(props.findAll {
props.each { key, val -> !nativeProps.contains(it.key) })}
if (nativeProps.contains(key)) { return }
this.extendedProperties[key] = val }}
public String getId() { return id; } public String getId() { return id; }

View File

@ -138,10 +138,10 @@ public class FileIssue extends Issue {
result.append("=".multiply(issue.title.length())) result.append("=".multiply(issue.title.length()))
result.append("\n\n") result.append("\n\n")
result.append(issue.text) result.append(issue.text)
result.append("\n----\n\n")
// If there are any extended properties, let's write those. // If there are any extended properties, let's write those.
if (issue.extendedProperties.size() > 0) { if (issue.extendedProperties.size() > 0) {
result.append("\n----\n\n")
def extOutput = [:] def extOutput = [:]
def maxKeyLen = 0 def maxKeyLen = 0
def maxValLen = 0 def maxValLen = 0
@ -169,7 +169,9 @@ public class FileIssue extends Issue {
result.append("=".multiply(maxKeyLen + 1)) result.append("=".multiply(maxKeyLen + 1))
result.append(" ") result.append(" ")
result.append("=".multiply(maxValLen)) result.append("=".multiply(maxValLen))
result.append("\n") }} result.append("\n") }
return result.toString()}
protected void writeFile() { protected void writeFile() {
try { source.write(formatIssue(this)) } try { source.write(formatIssue(this)) }

View File

@ -23,56 +23,65 @@ class FileProject extends Project {
child.isHidden()) return // just an issue folder child.isHidden()) return // just an issue folder
// otherwise build and add to list // otherwise build and add to list
projects[(child.name)] = new FileProject(child) projects[(child.name)] = new FileProject(child) }
} else if (child.isFile() && else if (child.isFile() &&
FileIssue.isValidFilename(child.name)) { FileIssue.isValidFilename(child.name)) {
def issue def issue
// if exception, then not an issue // if exception, then not an issue
try { issue = new FileIssue(child) } catch (all) { return } try { issue = new FileIssue(child) } catch (all) { return }
issues[(issue.id)] = issue issues[(issue.id)] = issue } }}
}
}
}
public void setName(String name) { public void setName(String name) {
super.setName(name) super.setName(name)
source.renameTo(new File(source.canonicalFile.parentFile, name)) source.renameTo(new File(source.canonicalFile.parentFile, name)) }
}
public FileIssue createNewIssue(Map options) { public FileIssue createNewIssue(Map options) {
Issue issue
File issueFile
if (!options) options = [:] if (!options) options = [:]
if (!options.category) options.category = Category.TASK
if (!options.status) options.status = Status.NEW // We want some different defaults for issues due to the parser being
if (!options.priority) options.priority = 5 // unable to handle empty title or text.
if (!options.text) options.text = "Default issue title.\n" + if (!options.title) options.title = "Default issue title."
"====================\n" if (!options.text) options.text = "Describe the issue here."
String id
if (issues.size() == 0) id = '0000' // We are also going to find the next id based on the issues already in the
// project.
if (issues.size() == 0) options.id = '0000'
else { else {
id = (issues.values().max { it.id.toInteger() }).id def lastId = (issues.values().max { it.id.toInteger() }).id
id = (id.toInteger() + 1).toString().padLeft(id.length(), '0') options.id = (lastId.toInteger() + 1).toString().padLeft(
} lastId.length(), '0') }
def issueFile = new File(source, FileIssue.makeFilename(id, // Create an Issue object from the options (we will discard it later).
options.category, options.status, options.priority)) issue = new Issue(options)
// Create the filename and File object based on the options given.
issueFile = new File(source, FileIssue.makeFilename(
issue.id, issue.category, issue.status, issue.priority))
// Create the actual file on the system
issueFile.createNewFile() issueFile.createNewFile()
issueFile.write(options.text)
def issue = new FileIssue(issueFile) // Write the issue to the file created.
issueFile.write(FileIssue.formatIssue(issue))
// Read that new file back in as a FileIssue
issue = new FileIssue(issueFile)
// Add the issue to our collection.
issues[(issue.id)] = issue issues[(issue.id)] = issue
return issue return issue }
}
public FileProject createNewProject(String name) { public FileProject createNewProject(String name) {
def newDir = new File(source, name) def newDir = new File(source, name)
newDir.mkdirs() newDir.mkdirs()
return new FileProject(newDir) return new FileProject(newDir) }
}
public boolean deleteIssue(Issue issue) { public boolean deleteIssue(Issue issue) {
if (!issues[(issue.id)]) return false if (!issues[(issue.id)]) return false
@ -81,8 +90,7 @@ class FileProject extends Project {
if (issue instanceof FileIssue) if (issue instanceof FileIssue)
return issue.deleteFile() return issue.deleteFile()
else return true else return true }
}
public boolean deleteProject(Project project) { public boolean deleteProject(Project project) {
if (!projects[(project.name)]) return false if (!projects[(project.name)]) return false
@ -91,8 +99,7 @@ class FileProject extends Project {
if (project instanceof FileProject) if (project instanceof FileProject)
return project.source.delete() return project.source.delete()
return true return true }
}
@Override @Override
public String toString() { return name } public String toString() { return name }