From 846d1edc74d14ae92e776bb96bd8371421a8a09a Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Thu, 8 Dec 2011 14:38:24 -0600 Subject: [PATCH] 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. --- libpit/project.properties | 6 +- .../src/main/com/jdbernard/pit/Filter.groovy | 17 +++-- .../src/main/com/jdbernard/pit/Issue.groovy | 11 ++- .../com/jdbernard/pit/file/FileIssue.groovy | 6 +- .../com/jdbernard/pit/file/FileProject.groovy | 67 ++++++++++--------- 5 files changed, 62 insertions(+), 45 deletions(-) diff --git a/libpit/project.properties b/libpit/project.properties index 8e11113..5e665e9 100755 --- a/libpit/project.properties +++ b/libpit/project.properties @@ -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 build.dir=build src.dir=src lib.shared.dir=../shared-libs test.dir=test -build.number=8 -version=3.1.0 +build.number=10 +version=3.2.0 name=libpit lib.dir=lib lib.local=true diff --git a/libpit/src/main/com/jdbernard/pit/Filter.groovy b/libpit/src/main/com/jdbernard/pit/Filter.groovy index 2bbcfaa..3a2025e 100755 --- a/libpit/src/main/com/jdbernard/pit/Filter.groovy +++ b/libpit/src/main/com/jdbernard/pit/Filter.groovy @@ -6,6 +6,7 @@ class Filter { List status = null List projects = null List ids = null + Map extendedProperties = null int priority = 9 boolean acceptProjects = true def issueSorter = defaultIssueSorter @@ -15,10 +16,18 @@ class Filter { public static Closure defaultProjectSorter = { it.name } public boolean accept(Issue i) { - return (i.priority <= priority && - (!categories || categories.contains(i.category)) && - (!status || status.contains(i.status)) && - (!ids || ids.contains(i.id))) + return ( + // Needs to meet the priority threshold. + i.priority <= priority && + // 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) { diff --git a/libpit/src/main/com/jdbernard/pit/Issue.groovy b/libpit/src/main/com/jdbernard/pit/Issue.groovy index 9ab0e2d..d699ceb 100755 --- a/libpit/src/main/com/jdbernard/pit/Issue.groovy +++ b/libpit/src/main/com/jdbernard/pit/Issue.groovy @@ -2,7 +2,7 @@ package com.jdbernard.pit import java.lang.IllegalArgumentException as IAE -public abstract class Issue { +public class Issue { protected String id protected Category category @@ -17,16 +17,15 @@ public abstract class Issue { this.id = props.id this.category = props.category ?: Category.TASK this.status = props.status ?: Status.NEW - this.priority = props.priority ?: 9 + this.priority = props.priority ?: 5 this.title = props.title ?: '' this.text = props.text ?: '' + // Put all the non-native properties into our extendedProperties map. def nativeProps = ["id", "category", "status", "priority", "title", "text"] - - props.each { key, val -> - if (nativeProps.contains(key)) { return } - this.extendedProperties[key] = val }} + extendedProperties.putAll(props.findAll { + !nativeProps.contains(it.key) })} public String getId() { return id; } diff --git a/libpit/src/main/com/jdbernard/pit/file/FileIssue.groovy b/libpit/src/main/com/jdbernard/pit/file/FileIssue.groovy index 6f515d1..435198c 100755 --- a/libpit/src/main/com/jdbernard/pit/file/FileIssue.groovy +++ b/libpit/src/main/com/jdbernard/pit/file/FileIssue.groovy @@ -138,10 +138,10 @@ public class FileIssue extends Issue { result.append("=".multiply(issue.title.length())) result.append("\n\n") result.append(issue.text) - result.append("\n----\n\n") // If there are any extended properties, let's write those. if (issue.extendedProperties.size() > 0) { + result.append("\n----\n\n") def extOutput = [:] def maxKeyLen = 0 def maxValLen = 0 @@ -169,7 +169,9 @@ public class FileIssue extends Issue { result.append("=".multiply(maxKeyLen + 1)) result.append(" ") result.append("=".multiply(maxValLen)) - result.append("\n") }} + result.append("\n") } + + return result.toString()} protected void writeFile() { try { source.write(formatIssue(this)) } diff --git a/libpit/src/main/com/jdbernard/pit/file/FileProject.groovy b/libpit/src/main/com/jdbernard/pit/file/FileProject.groovy index de4ce66..b8b481a 100755 --- a/libpit/src/main/com/jdbernard/pit/file/FileProject.groovy +++ b/libpit/src/main/com/jdbernard/pit/file/FileProject.groovy @@ -23,56 +23,65 @@ class FileProject extends Project { child.isHidden()) return // just an issue folder // otherwise build and add to list - projects[(child.name)] = new FileProject(child) - } else if (child.isFile() && + projects[(child.name)] = new FileProject(child) } + else if (child.isFile() && FileIssue.isValidFilename(child.name)) { def issue // if exception, then not an issue try { issue = new FileIssue(child) } catch (all) { return } - issues[(issue.id)] = issue - } - } - } + issues[(issue.id)] = issue } }} public void setName(String 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) { + Issue issue + File issueFile + if (!options) options = [:] - if (!options.category) options.category = Category.TASK - if (!options.status) options.status = Status.NEW - if (!options.priority) options.priority = 5 - if (!options.text) options.text = "Default issue title.\n" + - "====================\n" - String id - if (issues.size() == 0) id = '0000' + + // We want some different defaults for issues due to the parser being + // unable to handle empty title or text. + if (!options.title) options.title = "Default issue title." + if (!options.text) options.text = "Describe the issue here." + + // 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 { - id = (issues.values().max { it.id.toInteger() }).id - id = (id.toInteger() + 1).toString().padLeft(id.length(), '0') - } + def lastId = (issues.values().max { it.id.toInteger() }).id + options.id = (lastId.toInteger() + 1).toString().padLeft( + lastId.length(), '0') } - def issueFile = new File(source, FileIssue.makeFilename(id, - options.category, options.status, options.priority)) + // Create an Issue object from the options (we will discard it later). + 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.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 - return issue - } + return issue } public FileProject createNewProject(String name) { def newDir = new File(source, name) newDir.mkdirs() - return new FileProject(newDir) - } + return new FileProject(newDir) } public boolean deleteIssue(Issue issue) { if (!issues[(issue.id)]) return false @@ -81,8 +90,7 @@ class FileProject extends Project { if (issue instanceof FileIssue) return issue.deleteFile() - else return true - } + else return true } public boolean deleteProject(Project project) { if (!projects[(project.name)]) return false @@ -91,8 +99,7 @@ class FileProject extends Project { if (project instanceof FileProject) return project.source.delete() - return true - } + return true } @Override public String toString() { return name }