Clean slate.
37
build.xml
@ -1,37 +0,0 @@
|
||||
<project name="Personal Issue Tracker" default="package">
|
||||
<property file="version.properties"/>
|
||||
<property environment="env"/>
|
||||
|
||||
<property
|
||||
name="libpit.jar"
|
||||
value="libpit/release/libpit-${application.version}.jar"/>
|
||||
|
||||
<target name="clean">
|
||||
<ant dir="libpit" target="clean" inheritAll="false"/>
|
||||
<ant dir="pit-cli" target="clean" inheritAll="false"/>
|
||||
<!-- <ant dir="pit-swing" target="clean" inheritAll="false"/> -->
|
||||
</target>
|
||||
|
||||
<target name="libpit">
|
||||
<ant dir="libpit" target="release" inheritAll="false"/>
|
||||
</target>
|
||||
|
||||
<target name="pit-cli" depends="libpit">
|
||||
<copy file="${libpit.jar}" todir="pit-cli/lib"/>
|
||||
<ant dir="pit-cli" target="release" inheritAll="false"/>
|
||||
</target>
|
||||
|
||||
<!-- <target name="pit-swing" depends="libpit">
|
||||
<copy file="${libpit.jar}" todir="pit-swing/lib"/>
|
||||
<ant dir="pit-swing" fork="true" target="package" inheritAll="false"/>
|
||||
</target> -->
|
||||
|
||||
<!-- <target name="package" depends="libpit,pit-cli,pit-swing"> -->
|
||||
<target name="package" depends="libpit,pit-cli">
|
||||
<mkdir dir="release/lib"/>
|
||||
<copy file="pit-cli/release/pit-cli-${application.version}.jar" todir="release"/>
|
||||
<!-- <copy file="pit-swing/dist/jar/pit-swing.jar"
|
||||
tofile="release/pit-swing-${application.version}.jar"/> -->
|
||||
<copy file="${libpit.jar}" todir="release/lib"/>
|
||||
</target>
|
||||
</project>
|
@ -1,13 +0,0 @@
|
||||
FileIssue is not formatting output in the same way it parses input.
|
||||
===================================================================
|
||||
|
||||
`FileIssue.formatIssue(Issue)` introduces at least one extra line to the end
|
||||
of the issue body compared to what is parsed in using the PegParser.
|
||||
|
||||
|
||||
----
|
||||
|
||||
========= ===================
|
||||
Created : 2011-12-18T22:53:45
|
||||
Resolved: 2011-12-19T16:09:50
|
||||
========= ===================
|
@ -1,203 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<project name="Jonathan Bernard Build Common">
|
||||
|
||||
<property environment="env"/>
|
||||
|
||||
<!--======== INIT TARGETS ========-->
|
||||
<target name="-init" depends="-common-init,init"/>
|
||||
|
||||
<target name="-common-init">
|
||||
<!-- Set default values for some key properties. Since properties are
|
||||
write once, any value set before this point takes precedence. -->
|
||||
|
||||
<property name="versioning.file" value="project.properties"/>
|
||||
|
||||
<property name="src.dir" value="${basedir}/src"/>
|
||||
<property name="build.dir" value="${basedir}/build"/>
|
||||
<property name="lib.dir" value="${basedir}/lib"/>
|
||||
<property name="resources.dir" value="${basedir}/resources"/>
|
||||
|
||||
<!--======== PATHS ========-->
|
||||
<path id="groovy.classpath">
|
||||
<fileset dir="${env.GROOVY_HOME}/lib">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<path id="groovy.embeddable">
|
||||
<fileset dir="${env.GROOVY_HOME}/embeddable">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<path id="compile-libs">
|
||||
<fileset dir="${build.dir}/lib/compile/jar">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<path id="runtime-libs">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar">
|
||||
<include name="*.jar"/>
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="-init-groovy">
|
||||
<taskdef name="groovyc" classpathref="groovy.classpath"
|
||||
classname="org.codehaus.groovy.ant.Groovyc"/>
|
||||
|
||||
<taskdef name="groovy" classpathref="groovy.classpath"
|
||||
classname="org.codehaus.groovy.ant.Groovy"/>
|
||||
</target>
|
||||
|
||||
<target name="init"/>
|
||||
|
||||
<target name="clean" depends="-init">
|
||||
<delete dir="${build.dir}"/>
|
||||
</target>
|
||||
|
||||
<!--======== LIBRARY TARGETS ========-->
|
||||
<target name="-lib" depends="-lib-local,-lib-ivy,lib"/>
|
||||
|
||||
<target name="lib"/>
|
||||
|
||||
<target name="-lib-ivy" unless="${lib.local}"/>
|
||||
|
||||
<target name="-lib-local" if="${lib.local}">
|
||||
<echo message="Resolving libraries locally."/>
|
||||
<mkdir dir="${build.dir}/lib/compile/jar"/>
|
||||
<mkdir dir="${build.dir}/lib/runtime/jar"/>
|
||||
<copy todir="${build.dir}/lib/compile/jar" failonerror="false">
|
||||
<fileset dir="${lib.dir}/compile/jar"/>
|
||||
</copy>
|
||||
|
||||
<copy todir="${build.dir}/lib/runtime/jar" failonerror="false">
|
||||
<fileset dir="${lib.dir}/runtime/jar"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<!--======== VERSIONING TARGETS ========-->
|
||||
<target name="increment-build-number" depends="-init">
|
||||
<propertyfile file="${versioning.file}">
|
||||
<entry key="build.number" default="0" type="int" value="1"
|
||||
operation="+"/>
|
||||
</propertyfile>
|
||||
</target>
|
||||
|
||||
<target name="set-version" depends="-common-init">
|
||||
<input
|
||||
message="The current version is ${version}. Enter a new version: "
|
||||
addproperty="new-version"/>
|
||||
<propertyfile file="${versioning.file}">
|
||||
<entry key="version" value="${new-version}" operation="="
|
||||
type="string"/>
|
||||
<entry key="build.number" value="0" type="int" operation="="/>
|
||||
</propertyfile>
|
||||
</target>
|
||||
|
||||
<!--======== COMPILATION TARGETS ========-->
|
||||
<target name="-compile-groovy" depends="-init,-init-groovy,-lib">
|
||||
<mkdir dir="${build.dir}/main/classes"/>
|
||||
<groovyc srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
||||
includeAntRuntime="false" fork="yes">
|
||||
|
||||
<classpath>
|
||||
<path refid="groovy.classpath"/>
|
||||
<path refid="compile-libs"/>
|
||||
</classpath>
|
||||
<javac/>
|
||||
</groovyc>
|
||||
</target>
|
||||
|
||||
<target name="-compile-java" depends="-init,-lib">
|
||||
<mkdir dir="${build.dir}/main/classes"/>
|
||||
<javac srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
|
||||
includeAntRuntime="false" classpathref="compile-libs"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="-compile-groovy"/>
|
||||
|
||||
<!--======== JUNIT TARGETS ========-->
|
||||
<target name="-compile-tests-groovy" depends="-init,compile">
|
||||
<mkdir dir="${build.dir}/test/classes"/>
|
||||
<groovyc srcdir="${src.dir}/test" destdir="${build.dir}/test/classes"
|
||||
includeAntRuntime="false" fork="true">
|
||||
|
||||
<classpath>
|
||||
<path refid="groovy.classpath"/>
|
||||
<path refid="compile-libs"/>
|
||||
<path location="${build.dir}/main/classes"/>
|
||||
</classpath>
|
||||
</groovyc>
|
||||
</target>
|
||||
|
||||
<target name="-compile-tests-java" depends="-init,compile">
|
||||
<mkdir dir="${build.dir}/test/classes"/>
|
||||
<javac srcdir="${src.dir}/test" destdir="${build.dir}/test/classes"
|
||||
includeAntRuntime="false">
|
||||
<classpath>
|
||||
<path refid="compile-libs"/>
|
||||
<path location="${build.dir}/main/classes"/>
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="compile-tests" depends="-compile-tests-groovy"/>
|
||||
|
||||
<target name="run-tests" depends="compile-tests,resources-test">
|
||||
<junit printsummary="true">
|
||||
<classpath>
|
||||
<path refid="groovy.classpath"/>
|
||||
<path refid="compile-libs"/>
|
||||
<path location="${build.dir}/main/classes"/>
|
||||
<path location="${build.dir}/test/classes"/>
|
||||
</classpath>
|
||||
<formatter type="plain" usefile="false"/>
|
||||
<batchtest>
|
||||
<fileset dir="${build.dir}/test/classes">
|
||||
<include name="**/*"/>
|
||||
</fileset>
|
||||
</batchtest>
|
||||
</junit>
|
||||
</target>
|
||||
|
||||
<!--======== RESOURCES TARGETS ========-->
|
||||
|
||||
<target name="resources" depends="-init">
|
||||
<mkdir dir="${build.dir}/main/classes"/>
|
||||
<copy todir="${build.dir}/main/classes" failonerror="false">
|
||||
<fileset dir="${resources.dir}/main/"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="resources-test" depends="-init">
|
||||
<mkdir dir="${build.dir}/test/classes"/>
|
||||
<copy todir="${build.dir}/test/classes" failonerror="false">
|
||||
<fileset dir="${resources.dir}/test/"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<!--======== BUILD TARGETS ========-->
|
||||
<target name="-build-modular"
|
||||
depends="compile,increment-build-number,resources">
|
||||
|
||||
<jar destfile="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
basedir="${build.dir}/main/classes"/>
|
||||
</target>
|
||||
|
||||
<target name="-build-packed-libs"
|
||||
depends="compile,increment-build-number,resources">
|
||||
|
||||
<unjar destdir="${build.dir}/main/classes">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar"/>
|
||||
</unjar>
|
||||
|
||||
<jar destfile="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
basedir="${build.dir}/main/classes"/>
|
||||
</target>
|
||||
|
||||
<target name="build" depends="-build-modular"/>
|
||||
|
||||
</project>
|
@ -1,27 +0,0 @@
|
||||
<project name="Personal Issue Tracker" default="release">
|
||||
|
||||
<property file="project.properties"/>
|
||||
|
||||
<import file="../jdb-build-1.6.xml"/>
|
||||
|
||||
<target name="init">
|
||||
<fail
|
||||
unless="env.GROOVY_HOME"
|
||||
message="GROOVY_HOME environment variable is not set."/>
|
||||
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
|
||||
</target>
|
||||
|
||||
<target name="clean-all" depends="clean">
|
||||
<delete dir="${release.dir}"/>
|
||||
</target>
|
||||
|
||||
<target name="release" depends="build">
|
||||
<mkdir dir="${release.dir}/lib"/>
|
||||
<copy file="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
tofile="${release.dir}/${name}-${version}.jar"/>
|
||||
<copy todir="${release.dir}/lib">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar"/>
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
</project>
|
@ -1,7 +0,0 @@
|
||||
IssueFile - Title Body PropertyBlock?
|
||||
Title - ONE_LINE TITLE_SEPARATOR
|
||||
Body - ANY_LINE+
|
||||
Separator - DASH{4} NEW_LINE
|
||||
PropertyBlock - HorizontalRule TableSeparator PropertyDefinition+ TableSeparator
|
||||
TableSeparator -
|
||||
PropertyDefinition - PropertyKey COLON PropertyValue
|
@ -1,13 +0,0 @@
|
||||
#Thu, 26 Mar 2015 19:51:28 -0500
|
||||
#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.3.3
|
||||
name=libpit
|
||||
lib.dir=lib
|
||||
lib.local=true
|
||||
release.dir=release
|
||||
release.jar=pit-${application.version}.jar
|
@ -1,13 +0,0 @@
|
||||
import com.jdbernard.pit.*
|
||||
import com.jdbernard.pit.file.*
|
||||
|
||||
import org.parboiled.Parboiled
|
||||
import org.parboiled.parserunners.ReportingParseRunner
|
||||
import org.parboiled.parserunners.RecoveringParseRunner
|
||||
|
||||
parser = Parboiled.createParser(IssuePegParser.class)
|
||||
parseRunner = new ReportingParseRunner(parser.IssueFile())
|
||||
issueFile = new File('/Volumes/NO NAME/Dropbox/tasks/0015tn3.rst')
|
||||
issueText = issueFile.text
|
||||
result = parseRunner.run(issueText)
|
||||
issueMap = result.valueStack.pop()
|
1835
libpit/session.vim
@ -1,17 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
public enum Category {
|
||||
BUG,
|
||||
FEATURE,
|
||||
TASK
|
||||
|
||||
public static Category toCategory(String s) {
|
||||
for(c in Category.values())
|
||||
if (c.name().startsWith(s.toUpperCase())) return c
|
||||
throw new IllegalArgumentException("No category matches ${s}.")
|
||||
}
|
||||
|
||||
public String getSymbol() { toString()[0].toLowerCase() }
|
||||
|
||||
public String toString() { return "${name()[0]}${name()[1..-1].toLowerCase()}" }
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
import org.joda.time.DateMidnight
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
public enum ExtendedPropertyHelp {
|
||||
|
||||
// Property types should be ordered here in order of decreasing specificity.
|
||||
// That is, subclasses should come before the more general class so that
|
||||
// objects are converted using the most specific class that
|
||||
// ExtendedPropertyHelp knows how to work with.
|
||||
DATE_MIDNIGHT(/^\d{4}-\d{2}-\d{2}$/, DateMidnight,
|
||||
{ v -> DateMidnight.parse(v) },
|
||||
{ d -> d.toString("YYYY-MM-dd") }),
|
||||
DATETIME(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/, DateTime,
|
||||
{ v -> DateTime.parse(v) },
|
||||
{ d -> d.toString("YYYY-MM-dd'T'HH:mm:ss") }),
|
||||
// We never want to parse a value into a java.util.Date or
|
||||
// java.util.Calendar object (we are using Joda Time instead of the
|
||||
// standard Java Date and Calendar objects) but we do want to be able to
|
||||
// handle if someone gives us a Date or Calendar object.
|
||||
DATE(NEVER_MATCH, Date,
|
||||
{ v -> v }, // never called
|
||||
{ d -> dateFormat.format(d) }),
|
||||
CALENDAR(NEVER_MATCH, Calendar,
|
||||
{ v -> v }, // never called
|
||||
{ c ->
|
||||
def df = dateFormat.clone()
|
||||
df.calendar = c
|
||||
df.format(c.time) }),
|
||||
|
||||
INTEGER(NEVER_MATCH, Integer,
|
||||
{ v -> v as Integer }, // never called
|
||||
{ i -> i as String }),
|
||||
LONG(/^\d+$/, Long,
|
||||
{ v -> v as Long },
|
||||
{ l -> l as String }),
|
||||
FLOAT(NEVER_MATCH, Float,
|
||||
{ v -> v as Float}, // never called
|
||||
{ f -> f as String}),
|
||||
DOUBLE(/^\d+\.\d+$/, Double,
|
||||
{ v -> v as Double },
|
||||
{ d -> d as String });
|
||||
|
||||
String pattern;
|
||||
Class klass;
|
||||
def parseFun, formatFun;
|
||||
|
||||
private static SimpleDateFormat dateFormat =
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
|
||||
// This pattern for can never match (is uses negative lookahead to
|
||||
// contradict itself).
|
||||
private static String NEVER_MATCH = /(?!x)x/;
|
||||
|
||||
|
||||
public ExtendedPropertyHelp(String pattern, Class klass, def parseFun,
|
||||
def formatFun) {
|
||||
this.pattern = pattern
|
||||
this.klass = klass
|
||||
this.parseFun = parseFun
|
||||
this.formatFun = formatFun }
|
||||
|
||||
public boolean matches(String prop) { return prop ==~ pattern }
|
||||
|
||||
public boolean matches(Class klass) { return this.klass == klass }
|
||||
|
||||
public static Object parse(String value) {
|
||||
def propertyType = ExtendedPropertyHelp.values().find {
|
||||
it.matches(value) }
|
||||
|
||||
return propertyType ? propertyType.parseFun(value) : value }
|
||||
|
||||
public static String format(def object) {
|
||||
def propertyType = ExtendedPropertyHelp.values().find {
|
||||
it.klass.isInstance(object) }
|
||||
|
||||
return propertyType ? propertyType.formatFun(object) : object.toString() }
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
class Filter {
|
||||
|
||||
List<Category> categories = null
|
||||
List<Status> status = null
|
||||
List<String> projects = null
|
||||
List<String> ids = null
|
||||
Map<String, Object> extendedProperties = null
|
||||
int priority = 9
|
||||
boolean acceptProjects = true
|
||||
def issueSorter = defaultIssueSorter
|
||||
def projectSorter = defaultProjectSorter
|
||||
|
||||
public static Closure defaultIssueSorter = { it.id.toInteger() }
|
||||
public static Closure defaultProjectSorter = { it.name }
|
||||
|
||||
public boolean accept(Issue i) {
|
||||
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) {
|
||||
return (acceptProjects &&
|
||||
(!projects || projects.contains(p.name)))
|
||||
}
|
||||
|
||||
public boolean accept(String name) {
|
||||
return (acceptProjects &&
|
||||
(!projects || projects.contains(name)))
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
public class FlatProjectView extends Project {
|
||||
|
||||
public FlatProjectView(String name) { super(name) }
|
||||
|
||||
public Issue createNewIssue(Map options) {
|
||||
throw new UnsupportedOperationException("The FlatProjectView is " +
|
||||
"read-only.")
|
||||
}
|
||||
|
||||
public Project createNewProject(String name) {
|
||||
throw new UnsupportedOperationException("The FlatProjectView is " +
|
||||
"read-only.")
|
||||
}
|
||||
|
||||
public boolean deleteIssue(Issue issue) { return false }
|
||||
public boolean deleteProject(Project project) { return false }
|
||||
|
||||
public boolean delete() { return true }
|
||||
|
||||
public void eachIssue(Filter filter = null, Closure closure) {
|
||||
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
||||
def gatherIssues
|
||||
def gatheredIssues = []
|
||||
|
||||
gatherIssues = { project, f ->
|
||||
project.eachIssue(f) { gatheredIssues << it }
|
||||
project.eachProject(f) { gatherIssues(it, f) }
|
||||
}
|
||||
for (p in projects.values())
|
||||
if (!filter || filter.accept(p))
|
||||
gatherIssues(p, filter)
|
||||
|
||||
gatheredIssues.sort(sorter).each(closure)
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
import java.lang.IllegalArgumentException as IAE
|
||||
|
||||
public class Issue {
|
||||
|
||||
protected String id
|
||||
protected Category category
|
||||
protected Status status
|
||||
protected int priority
|
||||
protected String text
|
||||
protected String title
|
||||
|
||||
Map extendedProperties = [:]
|
||||
|
||||
Issue(Map props) {
|
||||
this.id = props.id
|
||||
this.category = props.category ?: Category.TASK
|
||||
this.status = props.status ?: Status.NEW
|
||||
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"]
|
||||
extendedProperties.putAll(props.findAll {
|
||||
!nativeProps.contains(it.key) })}
|
||||
|
||||
public String getId() { return id; }
|
||||
|
||||
public Category getCategory() { return category }
|
||||
|
||||
public void setCategory(Category c) throws IOException {
|
||||
if (c == null)
|
||||
throw new IAE("Category cannot be null.")
|
||||
|
||||
this.category = c
|
||||
}
|
||||
|
||||
public Status getStatus() { return status }
|
||||
|
||||
public void setStatus(Status s) throws IOException {
|
||||
if (s == null)
|
||||
throw new IAE("Status cannot be null.")
|
||||
|
||||
this.status = s
|
||||
}
|
||||
|
||||
public int getPriority() { return priority }
|
||||
|
||||
public void setPriority(int p) throws IOException {
|
||||
priority = Math.min(9, Math.max(0, p))
|
||||
}
|
||||
|
||||
public String getTitle() { return title }
|
||||
|
||||
public void setTitle(String t) throws IOException { title = t }
|
||||
|
||||
public String getText() { return text }
|
||||
|
||||
public void setText(String t) throws IOException { text = t }
|
||||
|
||||
public def propertyMissing(String name) { extendedProperties[name] }
|
||||
|
||||
public def propertyMissing(String name, def value) {
|
||||
extendedProperties[name] = value }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "${id}(${priority}-${status}): ${category} ${title}"
|
||||
}
|
||||
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
public abstract class Project {
|
||||
|
||||
protected String name
|
||||
Map<String, Issue> issues = [:]
|
||||
Map<String, Project> projects = [:]
|
||||
|
||||
Project(String name) { this.name = name }
|
||||
|
||||
public void eachIssue(Filter filter = null, Closure c) {
|
||||
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
||||
for (i in sort(issues.values(), sorter))
|
||||
if (!filter || filter.accept(i))
|
||||
c.call(i) }
|
||||
|
||||
public void eachProject(Filter filter = null, Closure c) {
|
||||
def sorter = filter?.projectSorter ?: Filter.defaultProjectSorter
|
||||
for (p in sort(projects.values(), sorter))
|
||||
if (!filter || filter.accept(p))
|
||||
c.call(p) }
|
||||
|
||||
// walk every issue and project in this project recursively and execute the
|
||||
// given closure on each issue that meets the filter criteria
|
||||
public void walkProject(Filter filter, Closure c) {
|
||||
this.eachIssue(filter, c)
|
||||
this.eachProject(filter) { p -> p.walkProject(filter, c) } }
|
||||
|
||||
// This get all issues, including subissues
|
||||
public List getAllIssues(Filter filter = null) {
|
||||
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
||||
|
||||
List allIssues = this.issues.values().findAll {
|
||||
filter ? filter.accept(it) : true }
|
||||
|
||||
this.eachProject(filter) { p -> allIssues += p.getAllIssues(filter) }
|
||||
|
||||
return sort(allIssues, sorter) }
|
||||
|
||||
public void setName(String name) { this.name = name }
|
||||
|
||||
public String getName() { return name }
|
||||
|
||||
@Override
|
||||
String toString() { return name }
|
||||
|
||||
public abstract Issue createNewIssue(Map options)
|
||||
|
||||
public abstract Project createNewProject(String name)
|
||||
|
||||
public abstract boolean deleteIssue(Issue issue)
|
||||
|
||||
public abstract boolean deleteProject(Project project)
|
||||
|
||||
protected List sort(def collection, def sorter) {
|
||||
if (sorter instanceof Closure) {
|
||||
return collection.sort(sorter) }
|
||||
else if (sorter instanceof List) {
|
||||
return sorter.reverse().inject(collection) { c, s -> c.sort(s) }}}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
public abstract class Repository {
|
||||
|
||||
public abstract void persist()
|
||||
public abstract Project[] getRootProjects()
|
||||
public abstract Project createNewProject(String name)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
public enum Status {
|
||||
REASSIGNED('a'),
|
||||
REJECTED('j'),
|
||||
NEW('n'),
|
||||
RESOLVED('s'),
|
||||
VALIDATION_REQUIRED('v')
|
||||
|
||||
String symbol
|
||||
|
||||
protected Status(String s) { symbol = s }
|
||||
|
||||
public static Status toStatus(String str) {
|
||||
// Try to match based on symbol
|
||||
def match = Status.values().find {it.symbol.equalsIgnoreCase(str)}
|
||||
if (match) { return match }
|
||||
|
||||
// No match on the symbol, look for the status name (or abbreviations)
|
||||
match = Status.values().findAll {
|
||||
it.name().startsWith(str.toUpperCase()) }
|
||||
|
||||
// No matching status, oops.
|
||||
if (match.size() == 0) {
|
||||
throw new IllegalArgumentException("No status matches '${str}'") }
|
||||
|
||||
// More than one matching status, oops.
|
||||
else if (match.size() > 1) {
|
||||
throw new IllegalArgumentException("Request string is" +
|
||||
" ambigous, '${str}' could represent any of ${match}.")}
|
||||
|
||||
// Only one matching status, yay!
|
||||
else { return match[0] }}
|
||||
|
||||
public String toString() {
|
||||
def words = name().split("_")
|
||||
String result = ""
|
||||
words.each { result += "${it[0]}${it[1..-1].toLowerCase()} " }
|
||||
return result[0..-2]
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
package com.jdbernard.pit.file
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
|
||||
import java.lang.IllegalArgumentException as IAE
|
||||
|
||||
import org.parboiled.Parboiled
|
||||
import org.parboiled.parserunners.ReportingParseRunner
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
public class FileIssue extends Issue {
|
||||
|
||||
protected File source
|
||||
private Logger log = LoggerFactory.getLogger(getClass())
|
||||
|
||||
public static final String fileExp = /(\d+)([bft])([ajnsv])(\d).*/
|
||||
|
||||
protected static parseRunner
|
||||
|
||||
static {
|
||||
def parser = Parboiled.createParser(IssuePegParser)
|
||||
parseRunner = new ReportingParseRunner(parser.IssueFile()) }
|
||||
|
||||
public FileIssue(File file) {
|
||||
|
||||
super(id: -1, title: 'REPLACE_ME')
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Loading a FileIssue from '{}'", file.canonicalPath) }
|
||||
|
||||
def matcher = file.name =~ fileExp
|
||||
if (!matcher)
|
||||
throw new IllegalArgumentException("${file} " +
|
||||
"is not a valid Issue file.")
|
||||
|
||||
// Read issue attributes from the filename.
|
||||
super.id = matcher[0][1]
|
||||
super.category = Category.toCategory(matcher[0][2])
|
||||
super.status = Status.toStatus(matcher[0][3])
|
||||
super.priority = matcher[0][4].toInteger()
|
||||
|
||||
log.debug("id: {}\tcategory: {}\tstatus: {}\tpriority: {}",
|
||||
super.id, super.category, super.status, super.priority)
|
||||
|
||||
this.source = file
|
||||
|
||||
// Parse the file and extract the title, text, and extended properties
|
||||
// TODO: guard against parsing problems (null/empty value stack, etc.)
|
||||
def parsedIssue = parseRunner.run(file.text).valueStack.pop()
|
||||
|
||||
super.text = parsedIssue.body
|
||||
super.title = parsedIssue.title
|
||||
|
||||
// Add the extended properties
|
||||
parsedIssue.extProperties.each { key, value ->
|
||||
key = key.toLowerCase().replaceAll(/\s/, '_')
|
||||
super.extendedProperties[key] =
|
||||
ExtendedPropertyHelp.parse(value) }
|
||||
}
|
||||
|
||||
public void setCategory(Category c) throws IOException {
|
||||
|
||||
File newSource = new File(source.canonicalFile.parentFile,
|
||||
makeFilename(id, c, status, priority))
|
||||
|
||||
if (source.renameTo(newSource)) {
|
||||
source = newSource
|
||||
super.setCategory(c) }
|
||||
else { throw new IOException("I was unable to set the category. "
|
||||
+ "I need to rename the file for this issue, but something is "
|
||||
+ "preventing me from doing so (maybe the path to the file is "
|
||||
+ "no longer valid, or maybe the file is currently open in "
|
||||
+ "some other program).") }}
|
||||
|
||||
public void setStatus(Status s) throws IOException {
|
||||
File newSource = new File(source.canonicalFile.parentFile,
|
||||
makeFilename(id, category, s, priority))
|
||||
|
||||
if (source.renameTo(newSource)) {
|
||||
source = newSource
|
||||
super.setStatus(s) }
|
||||
else { throw new IOException("I was unable to set the status. "
|
||||
+ "I need to rename the file for this issue, but something is "
|
||||
+ "preventing me from doing so (maybe the path to the file is "
|
||||
+ "no longer valid, or maybe the file is currently open in "
|
||||
+ "some other program).") }}
|
||||
|
||||
public void setPriority(int p) throws IOException {
|
||||
|
||||
File newSource = new File(source.canonicalFile.parentFile,
|
||||
makeFilename(id, category, status, p))
|
||||
|
||||
if (source.renameTo(newSource)) {
|
||||
source = newSource
|
||||
super.setPriority(p) }
|
||||
else { throw new IOException("I was unable to set the priority. "
|
||||
+ "I need to rename the file for this issue, but something is "
|
||||
+ "preventing me from doing so (maybe the path to the file is "
|
||||
+ "no longer valid, or maybe the file is currently open in "
|
||||
+ "some other program).") }}
|
||||
|
||||
public String getFilename() {
|
||||
return makeFilename(id, category, status, priority) }
|
||||
|
||||
public void setTitle(String title) throws IOException {
|
||||
super.setTitle(title)
|
||||
writeFile() }
|
||||
|
||||
public void setText(String text) throws IOException {
|
||||
super.setText(text)
|
||||
writeFile() }
|
||||
|
||||
public def propertyMissing(String name, def value) {
|
||||
super.propertyMissing(name, value)
|
||||
writeFile() }
|
||||
|
||||
boolean deleteFile() { return source.deleteDir() }
|
||||
|
||||
public static boolean isValidFilename(String name) {
|
||||
return name ==~ fileExp }
|
||||
|
||||
public static String makeFilename(String id, Category category,
|
||||
Status status, 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 (status == null)
|
||||
throw new IAE("Status must be non-null.")
|
||||
if (!(id ==~ /\d+/))
|
||||
throw new IAE( "'${id}' is not a legal value for id.")
|
||||
|
||||
return id + category.symbol + status.symbol + priority + ".rst" }
|
||||
|
||||
public static String formatIssue(Issue issue) {
|
||||
def result = new StringBuilder()
|
||||
result.append(issue.title)
|
||||
result.append("\n")
|
||||
result.append("=".multiply(issue.title.length()))
|
||||
result.append("\n\n")
|
||||
result.append(issue.text)
|
||||
|
||||
// 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
|
||||
|
||||
// Find the longest key and value, convert all to strings.
|
||||
issue.extendedProperties.each { key, val ->
|
||||
def ks = key.toString().split('_').collect({it.capitalize()}).join(' ')
|
||||
def vs = ExtendedPropertyHelp.format(val)
|
||||
|
||||
extOutput[ks] = vs
|
||||
if (ks.length() > maxKeyLen) { maxKeyLen = ks.length() }
|
||||
if (vs.length() > maxValLen) { maxValLen = vs.length() } }
|
||||
|
||||
result.append("=".multiply(maxKeyLen + 1))
|
||||
result.append(" ")
|
||||
result.append("=".multiply(maxValLen))
|
||||
result.append("\n")
|
||||
|
||||
extOutput.sort().each { key, val ->
|
||||
result.append(key.padRight(maxKeyLen))
|
||||
result.append(": ")
|
||||
result.append(val.padRight(maxValLen))
|
||||
result.append("\n") }
|
||||
|
||||
result.append("=".multiply(maxKeyLen + 1))
|
||||
result.append(" ")
|
||||
result.append("=".multiply(maxValLen))
|
||||
result.append("\n") }
|
||||
|
||||
return result.toString()}
|
||||
|
||||
protected void writeFile() {
|
||||
try { source.write(formatIssue(this)) }
|
||||
catch (IOException ioe) {
|
||||
throw new IOException("I could not save the new text for this "
|
||||
+ "issue. I can not write to the file for this issue. I do not"
|
||||
+ " know why, I am sorry (maybe the file can not be reached).") } }
|
||||
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package com.jdbernard.pit.file
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
|
||||
class FileProject extends Project {
|
||||
|
||||
protected File source
|
||||
|
||||
public FileProject(File dir) {
|
||||
super(dir.canonicalFile.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+/ ||
|
||||
child.isHidden()) return // just an issue folder
|
||||
|
||||
// otherwise build and add to list
|
||||
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 } }}
|
||||
|
||||
public void setName(String name) {
|
||||
super.setName(name)
|
||||
source.renameTo(new File(source.canonicalFile.parentFile, name)) }
|
||||
|
||||
public FileIssue createNewIssue(Map options) {
|
||||
Issue issue
|
||||
File issueFile
|
||||
|
||||
if (!options) options = [:]
|
||||
|
||||
// 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 {
|
||||
def lastId = (issues.values().max { it.id.toInteger() }).id
|
||||
options.id = (lastId.toInteger() + 1).toString().padLeft(
|
||||
lastId.length(), '0') }
|
||||
|
||||
// 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()
|
||||
|
||||
// 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 }
|
||||
|
||||
public FileProject createNewProject(String name) {
|
||||
def newDir = new File(source, name)
|
||||
newDir.mkdirs()
|
||||
|
||||
return new FileProject(newDir) }
|
||||
|
||||
public boolean deleteIssue(Issue issue) {
|
||||
if (!issues[(issue.id)]) return false
|
||||
|
||||
issues.remove(issue.id)
|
||||
if (issue instanceof FileIssue)
|
||||
return issue.deleteFile()
|
||||
|
||||
else return true }
|
||||
|
||||
public boolean deleteProject(Project project) {
|
||||
if (!projects[(project.name)]) return false
|
||||
|
||||
projects.remove(project.name)
|
||||
if (project instanceof FileProject)
|
||||
return project.source.delete()
|
||||
|
||||
return true }
|
||||
|
||||
@Override
|
||||
public String toString() { return name }
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package com.jdbernard.pit.file
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
|
||||
public class FileRepository extends Repository {
|
||||
|
||||
@Delegate FileProject fileProject
|
||||
|
||||
public FileRepository(File dir) {
|
||||
assert dir.isDirectory()
|
||||
fileProject = new FileProject(dir)
|
||||
}
|
||||
|
||||
public void persist() {} // nothing to do
|
||||
public Project[] getRootProjects() {
|
||||
return [fileProject] as Project[]
|
||||
}
|
||||
|
||||
public FileProject createNewProject(String name) {
|
||||
return fileProject.createNewProject()
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package com.jdbernard.pit.file;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.parboiled.Action;
|
||||
import org.parboiled.BaseParser;
|
||||
import org.parboiled.Context;
|
||||
import org.parboiled.Rule;
|
||||
import org.parboiled.annotations.*;
|
||||
|
||||
@BuildParseTree
|
||||
public class IssuePegParser extends BaseParser<Object> {
|
||||
|
||||
public Rule IssueFile() {
|
||||
return Sequence(push(makeNode()),
|
||||
Title(), Body(), Optional(PropertyBlock())); }
|
||||
|
||||
Rule Title() {
|
||||
return Sequence(
|
||||
OneOrMore(NOT_EOL), addToNode("title", match()), EOL,
|
||||
HorizontalRule(), EOL,
|
||||
ZeroOrMore(SPACE), EOL); }
|
||||
|
||||
Rule Body() { return Sequence(OneOrMore(Sequence(
|
||||
TestNot(PropertyBlock()), ANY)), addToNode("body", match())); }
|
||||
|
||||
Rule PropertyBlock() {
|
||||
return Sequence(push(makeNode()),
|
||||
EOL, HorizontalRule(), OneOrMore(EOL), TableSeparator(),
|
||||
EOL, OneOrMore(PropertyDefinition()), TableSeparator(),
|
||||
addToNode("extProperties", pop())); }
|
||||
|
||||
Rule PropertyDefinition() {
|
||||
return Sequence(
|
||||
PropertyKey(), push(match()), COLON,
|
||||
PropertyValue(), push(match()), EOL,
|
||||
swap(), addToNode(popAsString().trim(), popAsString().trim())); }
|
||||
|
||||
Rule PropertyKey() { return OneOrMore(Sequence(TestNot(COLON), NOT_EOL)); }
|
||||
|
||||
Rule PropertyValue() { return OneOrMore(NOT_EOL); }
|
||||
|
||||
Rule TableSeparator() {
|
||||
return Sequence(OneOrMore(SEPARATOR_CHAR), OneOrMore(SPACE),
|
||||
OneOrMore(SEPARATOR_CHAR)); }
|
||||
|
||||
Rule HorizontalRule() {
|
||||
return Sequence(SEPARATOR_CHAR, SEPARATOR_CHAR, SEPARATOR_CHAR,
|
||||
OneOrMore(SEPARATOR_CHAR)); }
|
||||
|
||||
Rule EOL = Ch('\n');
|
||||
Rule NOT_EOL = Sequence(TestNot(EOL), ANY);
|
||||
Rule SEPARATOR_CHAR = AnyOf("\"'`~:-_=+*^#<>");
|
||||
Rule SPACE = AnyOf(" \t");
|
||||
Rule COLON = Ch(':');
|
||||
|
||||
Map makeNode() { return new HashMap(); }
|
||||
|
||||
boolean addToNode(Object key, Object val) {
|
||||
Map node = (Map) pop();
|
||||
node.put(key, val);
|
||||
push(node);
|
||||
return true; }
|
||||
|
||||
String popAsString() { return (String) pop(); }
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package com.jdbernard.pit.util
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
|
||||
if (args.size() != 1) {
|
||||
println "Usage: Convert1_2 [dir]"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
File rootDir = new File(args[0])
|
||||
Scanner scan = new Scanner(System.in)
|
||||
|
||||
rootDir.eachFileRecurse { file ->
|
||||
def m = file.name =~ /(\d+)([bcft])(\d).*/
|
||||
if (m && file.isFile()) {
|
||||
println m[0][0]
|
||||
def parentFile = file.canonicalFile.parentFile
|
||||
def c
|
||||
def s
|
||||
switch(m[0][2]) {
|
||||
case "c":
|
||||
println file.readLines()[0]
|
||||
print "Issue was closed, was category does it belong in?"
|
||||
c = Category.toCategory(scan.nextLine())
|
||||
s = Status.RESOLVED
|
||||
break
|
||||
default:
|
||||
c = Category.toCategory(m[0][2])
|
||||
s = Status.NEW
|
||||
break
|
||||
}
|
||||
println "${m[0][2]}: ${c}"
|
||||
file.renameTo(new File(parentFile,
|
||||
FileIssue.makeFilename(m[0][1], c, s, m[0][3].toInteger())))
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package com.jdbernard.pit.xml
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
|
||||
public class XmlIssue extends Issue {
|
||||
|
||||
def issueNode
|
||||
XmlProject project
|
||||
XmlRepository repository
|
||||
|
||||
XmlIssue(def issueNode, XmlRepository repository, XmlProject project) {
|
||||
super(issueNode.@id, issueNode.@category ?: Category.TASK,
|
||||
issueNode.@status ?: Status.NEW, issueNode.@priority ?: 9)
|
||||
|
||||
this.issueNode = issueNode
|
||||
this.project = project
|
||||
this.repository = repository
|
||||
}
|
||||
|
||||
XmlIssue(String id, Category c = Category.TASK, Status s = Status.NEW,
|
||||
int p = 9, String title, String text, XmlRepository repository,
|
||||
XmlProject project) {
|
||||
super(id, c, s, p)
|
||||
|
||||
this.project = project
|
||||
this.repository = repository
|
||||
|
||||
// Node constructor adds the node to the parent node
|
||||
issueNode = new Node(project.projectNode, "Issue",
|
||||
[id: id, category: c, status: s, priority: p, title: title])
|
||||
|
||||
super.@title = title
|
||||
super.@text = text
|
||||
issueNode.value = text
|
||||
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
public void setCategory(Category c) {
|
||||
super.setCategory(c)
|
||||
|
||||
issueNode.@category = c.name()
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
public void setStatus(Status s) {
|
||||
super.setStatus(s)
|
||||
|
||||
issueNode.@status = s.name()
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
public void setPriority(int p) {
|
||||
super.setPriority(p)
|
||||
|
||||
issueNode.@priority = p
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
public void setText(String t) {
|
||||
super.setText(t)
|
||||
|
||||
issueNode.value = t
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
public void setTitle(String t) {
|
||||
super.setTitle(t)
|
||||
|
||||
issueNode.@title = t
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package com.jdbernard.pit.xml
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
|
||||
public class XmlProject extends Project {
|
||||
|
||||
def projectNode
|
||||
XmlRepository repository
|
||||
|
||||
XmlProject(def projectNode, XmlRepository repository) {
|
||||
super(projectNode.@name)
|
||||
|
||||
this.projectNode = projectNode
|
||||
this.repository = repository
|
||||
}
|
||||
|
||||
XmlProject(String name, def parentProject, XmlRepository repository) {
|
||||
super(name)
|
||||
|
||||
// Node constructor adds the node to the parent node
|
||||
projectNode = new Node(parentProject.projectNode, "Project",
|
||||
[name: name])
|
||||
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
super.setName(name)
|
||||
|
||||
projectNode.@name = name
|
||||
repository.persist()
|
||||
}
|
||||
|
||||
public XmlIssue createNewIssue(Map options) {
|
||||
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"
|
||||
else {
|
||||
id = (issues.values().max { it.id.toInteger() }).id
|
||||
id = (id.toInteger() + 1).toString().padLeft(id.length(), '0')
|
||||
}
|
||||
|
||||
// XmlIssue constructor will persist XML data
|
||||
issues[(id)] = new XmlIssue(id, options.category, options.status,
|
||||
options.priority, options.text, repository, this)
|
||||
|
||||
return issues[(id)]
|
||||
}
|
||||
|
||||
public XmlProject createNewProject(String name) {
|
||||
// XmlProject constructor persists the XML data
|
||||
projects[(name)] = new XmlProject(name, this, repository)
|
||||
return projects[(name)]
|
||||
}
|
||||
|
||||
public boolean deleteIssue(Issue issue) {
|
||||
if (!issues[(issue.id)]) return false
|
||||
|
||||
issues.remove(issue.id)
|
||||
if (issue instanceof XmlIssue)
|
||||
projectNode.remove(issue.issueNode)
|
||||
|
||||
repository.persist()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public boolean deleteProject(Project project) {
|
||||
if (!projects[(project.name)]) return false
|
||||
|
||||
projects.remove(project.name)
|
||||
if (project instanceof XmlProject)
|
||||
projectNode.remove(project.projectNode)
|
||||
|
||||
repository.persist()
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package com.jdbernard.pit.xml
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
import groovy.xml.XmlUtil
|
||||
|
||||
public class XmlRepository extends Repository {
|
||||
|
||||
def repository
|
||||
def projects = []
|
||||
File repoFile
|
||||
|
||||
public XmlRepository(File repoFile) {
|
||||
|
||||
this.repoFile = repoFile
|
||||
repository = new XmlParser().parse(repoFile)
|
||||
|
||||
repository.Project.each { projectNode ->
|
||||
projects << new XmlProject(projectNode)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized void persist() {
|
||||
repoFile.withOutputStream { XmlUtil.serialize(repository, it) }
|
||||
}
|
||||
|
||||
public Project[] getRootProjects() {
|
||||
return projects as XmlProject[]
|
||||
}
|
||||
|
||||
public XmlProject createNewProject(String name) {
|
||||
def newProject = new XmlProject(name, this, null)
|
||||
repository << newProject.projectNode
|
||||
|
||||
persist()
|
||||
return newProject
|
||||
}
|
||||
|
||||
public boolean deleteProject(Project p) {
|
||||
if (!projects.contains(p)) return false
|
||||
|
||||
projects.remove(p)
|
||||
repository.remove(p.projectNode)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
||||
import static com.jdbernard.pit.Category.toCategory
|
||||
|
||||
class CategoryTest {
|
||||
|
||||
@Test void testToCategory() {
|
||||
|
||||
assertEquals toCategory("BUG"), Category.BUG
|
||||
assertEquals toCategory("FEATURE"), Category.FEATURE
|
||||
assertEquals toCategory("TASK"), Category.TASK
|
||||
|
||||
assertEquals toCategory("bug"), Category.BUG
|
||||
assertEquals toCategory("feature"), Category.FEATURE
|
||||
assertEquals toCategory("task"), Category.TASK
|
||||
|
||||
assertEquals toCategory("b"), Category.BUG
|
||||
assertEquals toCategory("f"), Category.FEATURE
|
||||
assertEquals toCategory("t"), Category.TASK
|
||||
|
||||
}
|
||||
|
||||
@Test void testGetSymbol() {
|
||||
|
||||
assertEquals Category.BUG.symbol, "b"
|
||||
assertEquals Category.FEATURE.symbol, "f"
|
||||
assertEquals Category.TASK.symbol, "t"
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.Before
|
||||
import org.junit.After
|
||||
|
||||
import static org.junit.Assert.assertTrue
|
||||
import static org.junit.Assert.assertFalse
|
||||
|
||||
class FilterTest {
|
||||
|
||||
Project proj
|
||||
|
||||
@Before void setUpIssues() {
|
||||
|
||||
proj = new MockProject('proj1')
|
||||
|
||||
def issue = new MockIssue( '0000', Category.TASK, Status.NEW, 5)
|
||||
proj.issues['0000'] = issue
|
||||
|
||||
issue = new MockIssue('0001', Category.BUG, Status.REJECTED, 3)
|
||||
proj.issues['0001'] = issue
|
||||
|
||||
issue = new MockIssue('0002', Category.BUG, Status.RESOLVED, 9)
|
||||
proj.issues['0002'] = issue
|
||||
|
||||
issue = new MockIssue('0003', Category.FEATURE, Status.REASSIGNED, 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])
|
||||
|
||||
assertFalse f.accept(proj.issues['0000'])
|
||||
assertTrue f.accept(proj.issues['0001'])
|
||||
assertTrue f.accept(proj.issues['0002'])
|
||||
assertTrue f.accept(proj.issues['0003'])
|
||||
|
||||
f.categories = [ Category.TASK ]
|
||||
assertTrue f.accept(proj.issues['0000'])
|
||||
assertFalse f.accept(proj.issues['0001'])
|
||||
assertFalse f.accept(proj.issues['0002'])
|
||||
assertFalse f.accept(proj.issues['0003'])
|
||||
|
||||
f.categories = [ Category.BUG, Category.TASK ]
|
||||
assertTrue f.accept(proj.issues['0000'])
|
||||
assertTrue f.accept(proj.issues['0001'])
|
||||
assertTrue f.accept(proj.issues['0002'])
|
||||
assertFalse f.accept(proj.issues['0003'])
|
||||
|
||||
}
|
||||
|
||||
@Test void testStatusFilter() {
|
||||
Filter f = new Filter(status:
|
||||
[Status.NEW, Status.REASSIGNED, Status.REJECTED])
|
||||
|
||||
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.status = [ Status.RESOLVED ]
|
||||
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.status = [ Status.NEW, Status.RESOLVED ]
|
||||
assertTrue f.accept(proj.issues['0000'])
|
||||
assertFalse f.accept(proj.issues['0001'])
|
||||
assertTrue f.accept(proj.issues['0002'])
|
||||
assertFalse f.accept(proj.issues['0003'])
|
||||
}
|
||||
|
||||
@Test void testProjectFilter() {
|
||||
|
||||
}
|
||||
|
||||
@Test void testAcceptsProjectsFilter() {
|
||||
|
||||
}
|
||||
|
||||
@Test void testCompositeFilter() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
public class MockIssue extends Issue {
|
||||
public MockIssue(String id, Category c, Status s, int p) {
|
||||
super ([id: id, category: c, status: s, priority: p])
|
||||
}
|
||||
public boolean delete() { return true }
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
class MockProject extends Project {
|
||||
|
||||
public MockProject(String name) { super(name) }
|
||||
|
||||
public Issue createNewIssue(Map options) {
|
||||
return new MockIssue(options.id ?: 'n/a',
|
||||
options.c ?: Category.TASK, options.s ?: Status.NEW,
|
||||
options.p ?: 5)
|
||||
}
|
||||
|
||||
public Project createNewProject(String name) {
|
||||
return new MockProject(name)
|
||||
}
|
||||
|
||||
public boolean delete() { return true }
|
||||
public boolean deleteProject(Project project) { return true }
|
||||
public boolean deleteIssue(Issue issue) { return true }
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
class MockRepository extends Repository {
|
||||
|
||||
public void persist() {}
|
||||
|
||||
public Project[] getRootProjects() { return [] as Project[] }
|
||||
|
||||
public Project createNewProject(String name) {
|
||||
return new MockProject(name)
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package com.jdbernard.pit
|
||||
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
||||
import static com.jdbernard.pit.Status.toStatus
|
||||
|
||||
public class StatusTest {
|
||||
|
||||
@Test void testToStatus() {
|
||||
|
||||
assertEquals Status.REASSIGNED, toStatus('REASSIGNED')
|
||||
assertEquals Status.REJECTED, toStatus('REJECTED')
|
||||
assertEquals Status.NEW, toStatus('NEW')
|
||||
assertEquals Status.RESOLVED , toStatus('RESOLVED')
|
||||
assertEquals Status.VALIDATION_REQUIRED,
|
||||
toStatus('VALIDATION_REQUIRED')
|
||||
|
||||
assertEquals Status.REASSIGNED, toStatus('REA')
|
||||
assertEquals Status.REJECTED, toStatus('REJ')
|
||||
assertEquals Status.NEW, toStatus('NEW')
|
||||
assertEquals Status.RESOLVED , toStatus('RES')
|
||||
assertEquals Status.VALIDATION_REQUIRED,
|
||||
toStatus('VAL')
|
||||
|
||||
assertEquals Status.REASSIGNED, toStatus('reassigned')
|
||||
assertEquals Status.REJECTED, toStatus('rejected')
|
||||
assertEquals Status.NEW, toStatus('new')
|
||||
assertEquals Status.RESOLVED , toStatus('resolved')
|
||||
assertEquals Status.VALIDATION_REQUIRED,
|
||||
toStatus('validation_required')
|
||||
|
||||
assertEquals Status.REASSIGNED, toStatus('rea')
|
||||
assertEquals Status.REJECTED, toStatus('rej')
|
||||
assertEquals Status.NEW, toStatus('new')
|
||||
assertEquals Status.RESOLVED , toStatus('res')
|
||||
assertEquals Status.VALIDATION_REQUIRED,
|
||||
toStatus('val')
|
||||
|
||||
assertEquals Status.REASSIGNED, toStatus('A')
|
||||
assertEquals Status.REJECTED, toStatus('J')
|
||||
assertEquals Status.NEW, toStatus('N')
|
||||
assertEquals Status.RESOLVED , toStatus('S')
|
||||
assertEquals Status.VALIDATION_REQUIRED, toStatus('V')
|
||||
|
||||
assertEquals Status.REASSIGNED, toStatus('a')
|
||||
assertEquals Status.REJECTED, toStatus('j')
|
||||
assertEquals Status.NEW, toStatus('n')
|
||||
assertEquals Status.RESOLVED , toStatus('s')
|
||||
assertEquals Status.VALIDATION_REQUIRED, toStatus('v')
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
package com.jdbernard.pit.file
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
import org.junit.*
|
||||
import static org.junit.Assert.assertTrue
|
||||
import static org.junit.Assert.assertFalse
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
||||
class FileIssueTest {
|
||||
|
||||
def issues
|
||||
File testDir
|
||||
|
||||
@Before void makeIssueFiles() {
|
||||
File issueFile
|
||||
issues = []
|
||||
|
||||
testDir = new File('testdir')
|
||||
testDir.mkdirs()
|
||||
|
||||
issueFile = new File(testDir, '0001fn1.rst')
|
||||
issueFile.write(
|
||||
"Add the killer feature to the killer app.\n" +
|
||||
"=========================================\n\n" +
|
||||
"Make our killer app shine!.")
|
||||
issues << new FileIssue(issueFile)
|
||||
|
||||
issueFile = new File(testDir, '0002ts5.rst')
|
||||
issueFile.write(
|
||||
"Obtain donuts.\n" +
|
||||
"==============\n\n" +
|
||||
"The office is seriously lacking in sugary donuts.\n\n" +
|
||||
"We must rectify this at once!")
|
||||
issues << new FileIssue(issueFile)
|
||||
}
|
||||
|
||||
@After void deleteIssueFiles() {
|
||||
assert testDir.deleteDir()
|
||||
}
|
||||
|
||||
@Test void testSetCategory() {
|
||||
|
||||
assertEquals issues[0].category, Category.FEATURE
|
||||
assertEquals issues[1].category, Category.TASK
|
||||
|
||||
try {
|
||||
issues[0].category = Category.TASK
|
||||
issues[1].category = Category.BUG
|
||||
} catch (Exception e) {
|
||||
Assert.fail("An unexpected Exception occurred: "
|
||||
+ e.getLocalizedMessage())
|
||||
}
|
||||
|
||||
assertEquals issues[0].category, Category.TASK
|
||||
assertEquals issues[1].category, Category.BUG
|
||||
|
||||
assertTrue new File(testDir, '0001tn1.rst').exists()
|
||||
assertTrue new File(testDir, '0002bs5.rst').exists()
|
||||
assertFalse new File(testDir, '0001fn1.rst').exists()
|
||||
assertFalse new File(testDir, '0002ts5.rst').exists()
|
||||
|
||||
}
|
||||
|
||||
@Test void testSetCategoryFails() {
|
||||
FileInputStream fin
|
||||
try {
|
||||
// get a lock to the file to prevent the rename
|
||||
def issueFile = new File('0001fn1.rst')
|
||||
fin = new FileInputStream(issueFile)
|
||||
|
||||
// try to set the category
|
||||
issues[0].category = Category.TASK
|
||||
|
||||
// should throw IOE before here
|
||||
Assert.fail()
|
||||
} catch (IOException ioe) {
|
||||
} catch (Exception e) {
|
||||
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
|
||||
} finally {
|
||||
if (fin != null) fin.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Test void testSetStatus() {
|
||||
|
||||
assertEquals issues[0].status, Status.NEW
|
||||
assertEquals issues[1].status, Status.RESOLVED
|
||||
|
||||
try {
|
||||
issues[0].status = Status.RESOLVED
|
||||
issues[1].status = Status.REJECTED
|
||||
} catch (Exception e) {
|
||||
Assert.fail("An unexpected Exception occurred: "
|
||||
+ e.getLocalizedMessage())
|
||||
}
|
||||
|
||||
assertTrue new File(testDir, '0001fs1.rst').exists()
|
||||
assertTrue new File(testDir, '0002tj5.rst').exists()
|
||||
assertFalse new File(testDir, '0001fn1.rst').exists()
|
||||
assertFalse new File(testDir, '0002ts5.rst').exists()
|
||||
}
|
||||
|
||||
@Test void testSetStatusFails() {
|
||||
FileInputStream fin
|
||||
try {
|
||||
// get a lock to the file to prevent the rename
|
||||
def issueFile = new File('0001fn1.rst')
|
||||
fin = new FileInputStream(issueFile)
|
||||
|
||||
// try to set the status
|
||||
issues[0].status = Status.REJECTED
|
||||
|
||||
// should throw IOE before here
|
||||
Assert.fail()
|
||||
} catch (IOException ioe) {
|
||||
} catch (Exception e) {
|
||||
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
|
||||
} finally {
|
||||
if (fin != null) fin.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Test void testSetPriority() {
|
||||
|
||||
assertEquals issues[0].priority, 1
|
||||
assertEquals issues[1].priority, 5
|
||||
|
||||
try {
|
||||
issues[0].priority = 2
|
||||
issues[1].priority = 9
|
||||
} catch (Exception e) {
|
||||
Assert.fail("An unexpected Exception occurred: "
|
||||
+ e.getLocalizedMessage())
|
||||
}
|
||||
|
||||
assertEquals issues[0].priority, 2
|
||||
assertEquals issues[1].priority, 9
|
||||
|
||||
assertTrue new File(testDir, '0001fn2.rst').exists()
|
||||
assertTrue new File(testDir, '0002ts9.rst').exists()
|
||||
assertFalse new File(testDir, '0001fn1.rst').exists()
|
||||
assertFalse new File(testDir, '0002ts5.rst').exists()
|
||||
}
|
||||
|
||||
@Test void testSetPriorityFails() {
|
||||
FileInputStream fin
|
||||
try {
|
||||
// get a lock to the file to prevent the rename
|
||||
def issueFile = new File('0001fn1.rst')
|
||||
fin = new FileInputStream(issueFile)
|
||||
|
||||
// try to set the priority
|
||||
issues[0].priority = 9
|
||||
|
||||
// should throw IOE before here
|
||||
Assert.fail()
|
||||
} catch (IOException ioe) {
|
||||
} catch (Exception e) {
|
||||
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
|
||||
} finally {
|
||||
if (fin != null) fin.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Test void testConstruction() {
|
||||
File issueFile = new File(testDir, '0001fn1.rst')
|
||||
Issue issue = new FileIssue(issueFile)
|
||||
|
||||
assertEquals issue.id , "0001"
|
||||
assertEquals issue.category , Category.FEATURE
|
||||
assertEquals issue.status , Status.NEW
|
||||
assertEquals issue.priority , 1
|
||||
assertEquals issue.title , "Add the killer feature to the killer app."
|
||||
assertEquals issue.text , "Make our killer app shine!."
|
||||
assertEquals issue.source , issueFile
|
||||
}
|
||||
|
||||
@Test void testSetTextFails() {
|
||||
try {
|
||||
// make the issue file un-writable
|
||||
def issueFile = new File('0001fn1.rst')
|
||||
if (issueFile.setReadOnly()) {
|
||||
|
||||
// try to write something
|
||||
issues[0].text = "This should fail to be written."
|
||||
|
||||
// should throw IOE before here
|
||||
Assert.fail()
|
||||
} else {
|
||||
println "Could not run testSetTextFails, unable to change " +
|
||||
"the test isseu file's permissions."
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
} catch (Exception e) {
|
||||
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
|
||||
}
|
||||
}
|
||||
|
||||
@Test void testMakeFilename() {
|
||||
assertEquals FileIssue.makeFilename('0001', Category.BUG,
|
||||
Status.NEW, 5), '0001bn5.rst'
|
||||
assertEquals FileIssue.makeFilename('0010', Category.FEATURE,
|
||||
Status.REASSIGNED, 1), '0010fa1.rst'
|
||||
assertEquals FileIssue.makeFilename('0002', Category.FEATURE,
|
||||
Status.REJECTED, 3), '0002fj3.rst'
|
||||
assertEquals FileIssue.makeFilename('0001', Category.BUG,
|
||||
Status.RESOLVED, -2), '0001bs0.rst'
|
||||
assertEquals FileIssue.makeFilename('0001', Category.TASK,
|
||||
Status.VALIDATION_REQUIRED, 10) , '0001tv9.rst'
|
||||
assertEquals FileIssue.makeFilename('00101', Category.BUG,
|
||||
Status.NEW, 5), '00101bn5.rst'
|
||||
|
||||
try {
|
||||
FileIssue.makeFilename('badid', Category.BUG, Status.NEW, 5)
|
||||
assertTrue 'Issue.makeFilename() succeeded with bad id input.', false
|
||||
} catch (IllegalArgumentException iae) {}
|
||||
|
||||
try {
|
||||
FileIssue.makeFilename('0002', null, Status.NEW, 5)
|
||||
assertTrue 'Issue.makeFilename() succeeded given no Category.', false
|
||||
} catch (IllegalArgumentException iae) {}
|
||||
|
||||
try {
|
||||
FileIssue.makeFilename('0002', Category.BUG, null, 5)
|
||||
assertTrue 'Issue.makeFilename() succeeded given no Status.', false
|
||||
} catch (IllegalArgumentException iae) {}
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
package com.jdbernard.pit.file
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.assertFalse
|
||||
import static org.junit.Assert.assertNotNull
|
||||
import static org.junit.Assert.assertTrue
|
||||
|
||||
class FileProjectTest {
|
||||
|
||||
File testDir
|
||||
Project rootProj
|
||||
|
||||
@Before void createTestProjects() {
|
||||
|
||||
testDir = new File('testdir')
|
||||
assert !testDir.exists()
|
||||
testDir.mkdirs()
|
||||
|
||||
/* TEST SUITE:
|
||||
/testdir/
|
||||
0001t5.rst
|
||||
0002b5.rst
|
||||
0003f2.rst
|
||||
|
||||
subproj1/
|
||||
0001f3.rst
|
||||
0002b4.rst
|
||||
|
||||
emptyproj/
|
||||
|
||||
*/
|
||||
|
||||
def issueFile = new File(testDir, '0001tn5.rst')
|
||||
issueFile.createNewFile()
|
||||
issueFile.write('Test Issue 1\n' +
|
||||
'============\n\n' +
|
||||
'This is the first test issue.')
|
||||
|
||||
issueFile = new File(testDir, '0002ba5.rst')
|
||||
issueFile.createNewFile()
|
||||
issueFile.write('Test Bug\n' +
|
||||
'========\n\n' +
|
||||
'Yeah, it is a test bug.')
|
||||
|
||||
issueFile = new File(testDir, '0003fs2.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, '0001fv3.rst')
|
||||
issueFile.createNewFile()
|
||||
issueFile.write('First feature in subproject\n' +
|
||||
'===========================\n\n' +
|
||||
'Please make the grubblers grobble.')
|
||||
|
||||
issueFile = new File(subDir, '0002bj4.rst')
|
||||
issueFile.createNewFile()
|
||||
issueFile.write('Zippners are not zippning.\n' +
|
||||
'==========================\n\n' +
|
||||
'For some reason, the Zippners are bilperring, not zippning.')
|
||||
|
||||
subDir = new File(testDir, 'emptyproj')
|
||||
subDir.mkdirs()
|
||||
|
||||
rootProj = new FileProject(testDir)
|
||||
}
|
||||
|
||||
@After void deleteTestProjects() {
|
||||
assert testDir.deleteDir()
|
||||
|
||||
if (rootProj.source.exists())
|
||||
assert rootProj.source.deleteDir()
|
||||
}
|
||||
|
||||
@Test void testConstruction() {
|
||||
Project proj = new FileProject(testDir)
|
||||
|
||||
assertEquals proj.name, 'testdir'
|
||||
assertEquals proj.issues.size(), 3
|
||||
assertEquals proj.projects.size(), 2
|
||||
|
||||
// Issue construction in general is under test in IssueTest
|
||||
// just check that the issues actually exists
|
||||
assertEquals proj.issues['0001'].id, '0001'
|
||||
assertEquals proj.issues['0001'].title, 'Test Issue 1'
|
||||
|
||||
assertEquals proj.issues['0002'].id, '0002'
|
||||
assertEquals proj.issues['0002'].title, 'Test Bug'
|
||||
|
||||
assertEquals proj.issues['0003'].id, '0003'
|
||||
assertEquals proj.issues['0003'].title, 'Important Feature Request'
|
||||
|
||||
// check sub-project behaviour
|
||||
assertNotNull proj.projects.subproj1
|
||||
assertEquals proj.projects.subproj1.name, 'subproj1'
|
||||
assertEquals proj.projects.subproj1.issues.size(), 2
|
||||
assertEquals proj.projects.subproj1.projects.size(), 0
|
||||
assertEquals proj.projects.subproj1.issues['0001'].id, '0001'
|
||||
assertEquals proj.projects.subproj1.issues['0002'].id, '0002'
|
||||
assertEquals proj.projects.subproj1.issues['0001'].title,
|
||||
'First feature in subproject'
|
||||
assertEquals proj.projects.subproj1.issues['0002'].title,
|
||||
'Zippners are not zippning.'
|
||||
|
||||
assertNotNull proj.projects.emptyproj
|
||||
assertEquals proj.projects.emptyproj.issues.size(), 0
|
||||
assertEquals proj.projects.emptyproj.projects.size(), 0
|
||||
}
|
||||
|
||||
@Test void testRename() {
|
||||
assert rootProj.name == 'testdir'
|
||||
|
||||
rootProj.name = 'renamedTestDir'
|
||||
|
||||
assertEquals rootProj.name, 'renamedTestDir'
|
||||
assertTrue new File('renamedTestDir').exists()
|
||||
|
||||
assert rootProj.source.deleteDir()
|
||||
}
|
||||
|
||||
@Test void testCreateNewIssue() {
|
||||
|
||||
// test correct increment of id, application of values
|
||||
def newIssue = rootProj.createNewIssue(category: Category.BUG,
|
||||
status: Status.REASSIGNED, priority: 4,
|
||||
text: 'A newly made bug report.\n'+
|
||||
'========================\n\n' +
|
||||
'Testing the Project.createNewIssue() method.')
|
||||
|
||||
assertEquals newIssue.id, '0004'
|
||||
assertEquals newIssue.category, Category.BUG
|
||||
assertEquals newIssue.status, Status.REASSIGNED
|
||||
assertEquals newIssue.priority, 4
|
||||
assertEquals newIssue.text, 'A newly made bug report.\n'+
|
||||
'========================\n\n' +
|
||||
'Testing the Project.createNewIssue() method.'
|
||||
assertEquals rootProj.issues[(newIssue.id)], newIssue
|
||||
|
||||
//test defaults and creation of issue in an empty project
|
||||
newIssue = rootProj.projects.emptyproj.createNewIssue()
|
||||
|
||||
assertEquals newIssue.id, '0000'
|
||||
assertEquals newIssue.priority, 5
|
||||
assertEquals newIssue.category, Category.TASK
|
||||
assertEquals newIssue.status, Status.NEW
|
||||
assertEquals newIssue.text, 'Default issue title.\n' +
|
||||
'====================\n'
|
||||
|
||||
assertEquals rootProj.projects.emptyproj.issues[(newIssue.id)],
|
||||
newIssue
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.jdbernard.pit.xml
|
||||
|
||||
import com.jdbernard.pit.*
|
||||
import groovy.util.Node
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.assertFalse
|
||||
import static org.junit.Assert.assertTrue
|
||||
|
||||
public class XmlIssueTest {
|
||||
|
||||
Node issueNode = new Node(null, 'Issue',
|
||||
[id: '0000', category: 'BUG', status: 'RESOLVED', priority: 1],
|
||||
'Test Issue')
|
||||
|
||||
@Test public void testDummyTest() {}
|
||||
|
||||
/*@Test public void testNodeConstructor() {
|
||||
XmlIssue issue = new XmlIssue(issueNode)
|
||||
|
||||
assertEquals issue.text, 'Test Issue'
|
||||
assertEquals issue.id, '0000'
|
||||
assertEquals issue.category, Category.BUG
|
||||
assertEquals issue.status, Status.RESOLVED
|
||||
assertEquals issue.priority, 1
|
||||
}*/
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<project name="Personal Issue Tracker CLI">
|
||||
|
||||
<property file="project.properties"/>
|
||||
|
||||
<import file="../jdb-build-1.6.xml"/>
|
||||
|
||||
<target name="init">
|
||||
<fail
|
||||
unless="env.GROOVY_HOME"
|
||||
message="GROOVY_HOME environment variable is not set."/>
|
||||
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
|
||||
|
||||
<fail message="Could not find PIT ${version} library.">
|
||||
<condition>
|
||||
<not>
|
||||
<available
|
||||
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
|
||||
</not>
|
||||
</condition>
|
||||
</fail>
|
||||
</target>
|
||||
|
||||
<target name="clean-all" depends="clean">
|
||||
<delete dir="${release.dir}"/>
|
||||
</target>
|
||||
|
||||
<target name="lib">
|
||||
<copy todir="${build.dir}/lib/compile/jar"
|
||||
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
|
||||
<copy todir="${build.dir}/lib/runtime/jar"
|
||||
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
|
||||
</target>
|
||||
|
||||
<target name="release" depends="build">
|
||||
<mkdir dir="${release.dir}/lib"/>
|
||||
<copy todir="${release.dir}/lib">
|
||||
<fileset dir="${build.dir}/lib/runtime/jar"/></copy>
|
||||
<copy tofile="${release.dir}/${name}-${version}.jar"
|
||||
file="${build.dir}/${name}-${version}.${build.number}.jar"/>
|
||||
</target>
|
||||
</project>
|
@ -1,12 +0,0 @@
|
||||
#Thu, 26 Mar 2015 19:51:28 -0500
|
||||
build.dir=build
|
||||
src.dir=src
|
||||
build.jar=pit-cli-${application.version}.${build.number}.jar
|
||||
build.number=2
|
||||
version=3.3.3
|
||||
name=pit-cli
|
||||
lib.dir=lib
|
||||
lib.local=true
|
||||
release.dir=release
|
||||
release.jar=pit-cli-${application.version}.jar
|
||||
main.class=com.jdbernard.pit.PersonalIssueTrackerCLI
|
@ -1,631 +0,0 @@
|
||||
/**
|
||||
* # Personal Issue Tracker Command Line Interface
|
||||
* @author Jonathan Bernard <jdbernard@gmail.com>
|
||||
* @copyright 2009-2012 Jonathan Bernard
|
||||
*
|
||||
* This is a command-line interface to my personal issue tracker system.
|
||||
*/
|
||||
package com.jdbernard.pit
|
||||
|
||||
import com.jdbernard.pit.file.*
|
||||
|
||||
import org.joda.time.DateMidnight
|
||||
import org.joda.time.DateTime
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import static java.lang.Math.max
|
||||
import static java.lang.Math.min
|
||||
|
||||
def log = LoggerFactory.getLogger(getClass())
|
||||
|
||||
/// ## Command Line Options ##
|
||||
/// --------------------------
|
||||
/// @org cli-options
|
||||
def cli = new CliBuilder(usage: 'pit-cli [options]')
|
||||
|
||||
/// -h,--help
|
||||
/// : Show help information
|
||||
cli.h(longOpt: 'help', 'Show help information.')
|
||||
|
||||
/// -v,--verbose
|
||||
/// : Show verbose task information.
|
||||
cli.v(longOpt: 'verbose', 'Show verbose task information')
|
||||
|
||||
/// -l,--list
|
||||
/// : List issues in the current project.
|
||||
cli.l(longOpt: 'list', 'List issues in the current project.')
|
||||
|
||||
/// -i,--id
|
||||
/// : Filter issues by id. Accepts a comma-delimited list.
|
||||
/// *Example:* `pit -l -i 0001,0002`
|
||||
cli.i(argName: 'id', longOpt: 'id', args: 1,
|
||||
'Filter issues by id. Accepts a comma-delimited list.')
|
||||
|
||||
/// -c,--category
|
||||
/// : Filter issues by category (bug, feature, task). Accepts a
|
||||
/// comma-delimited list. By default all categories are selected. The full
|
||||
/// category name is not required, just enough to be uniquely identifiable.
|
||||
/// *Example:* `pit -l -c bug,t # List bugs and tasks.`
|
||||
cli.c(argName: 'category', longOpt: 'category', args: 1,
|
||||
'Filter issues by category (bug, feature, task). Accepts a '
|
||||
+ 'comma-delimited list. By default all categories are selected.')
|
||||
|
||||
/// -s,--status
|
||||
/// : Filter issues by status (new, reassigned, rejected, resolved,
|
||||
/// validation_required). The full status is not required, just enough to
|
||||
/// uniquely identify the status.
|
||||
/// *Example:* `pit -l -s reas,rej # List Reassigned and Rejected issues.`
|
||||
cli.s(argName: 'status', longOpt: 'status', args: 1,
|
||||
'Filter issues by status (new, reassigned, rejected, resolved, ' +
|
||||
'validation_required)')
|
||||
|
||||
/// -p,--priority
|
||||
/// : Filter issues by priority. This acts as a threshhold, listing all
|
||||
/// issues greater than or equal to the given priority.
|
||||
/// *Example:* `pit -l -p 5 # List all issues with priority >= 5`
|
||||
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.')
|
||||
|
||||
/// -r,--project
|
||||
/// : Filter issues by project (relative to the current directory). Accepts a
|
||||
/// comma-delimited list. This option should be used in conjunction with the
|
||||
/// `R,--recursive` option.
|
||||
/// *Example:* `pit -l -R --project <project_name>`
|
||||
cli.r(argName: 'project', longOpt: 'project', args: 1,
|
||||
'Filter issues by project (relative to the current directory). Accepts a '
|
||||
+ 'comma-delimited list.')
|
||||
|
||||
/// -R,--recursive
|
||||
/// : Recursively include subprojects.
|
||||
cli.R(longOpt: 'recursive', 'Include subprojects.')
|
||||
|
||||
/// -e,--extended-property
|
||||
/// : Filter for issues by extended property. Format is
|
||||
/// `-e <propname>=<propvalue>`.
|
||||
cli.e(argName: 'extended-property', args: 1, 'Filter for issues by extended ' +
|
||||
'property. Format is "-e <propname>=<propvalue>".')
|
||||
|
||||
/*cli.s(longOpt: 'show-subprojects',
|
||||
'Include sup projects in listing (default behaviour)')
|
||||
cli.S(longOpt: 'no-subprojects', 'Do not list subprojects.')*/ // TODO: figure out better flags for these options.
|
||||
|
||||
/// -P,--set-priority
|
||||
/// : Modify the priority of the selected issues. Requires a value from 0-9.
|
||||
cli.P(argName: 'new-priority', longOpt: 'set-priority', args: 1,
|
||||
'Modify the priority of the selected issues.')
|
||||
|
||||
/// -C,--set-category
|
||||
/// : Modify the category of the selected issues.
|
||||
cli.C(argName: 'new-category', longOpt: 'set-category', args: 1,
|
||||
'Modify the category of the selected issues.')
|
||||
|
||||
/// -S,--set-status
|
||||
/// : Modify the status of the selected issues.
|
||||
cli.S(argName: 'new-status', longOpt: 'set-status', args: 1,
|
||||
'Modify the status of the selected issues.')
|
||||
|
||||
/// -E,--new-issue
|
||||
/// : Modify the extended property of the selected issues. Format is
|
||||
/// `-E <propname>=<propvalue>`
|
||||
cli.E(argName: 'new-extended-property', longOpt: 'set-extended-property',
|
||||
args: 1, 'Modify the extended property of the selected issues. Format ' +
|
||||
'is "-E <propname>=<propvalue>"')
|
||||
|
||||
/// -n,--new-issue
|
||||
/// : Create a new issue
|
||||
cli.n(longOpt: 'new-issue', 'Create a new issue.')
|
||||
|
||||
/// --title
|
||||
/// : Give the title for a new issue or modify the title for an existing
|
||||
/// issue. By default the title for a new issue is expected on stanard
|
||||
/// input.
|
||||
cli._(longOpt: 'title', args: 1, argName: 'title', 'Give the title for a new' +
|
||||
' issue or modify the title for an existing issue. By default the title' +
|
||||
' for a new issue is expected on stanard input.')
|
||||
|
||||
/// --text
|
||||
/// : Give the text for a new issue or modify the text for an exising
|
||||
/// issue. By default the text for a new issue is expected on standard
|
||||
/// input.
|
||||
cli._(longOpt: 'text', args: 1, argName: 'text', 'Give the text for a new' +
|
||||
' issue or modify the text for an exising issue. By default the text for' +
|
||||
' a new issue is expected on standard input.')
|
||||
|
||||
/** -o,--order
|
||||
* : Order (sort) the results by the given properties. Provide a
|
||||
* comma-seperated list of property names to sort by in order of
|
||||
* importance. The basic properties (id, category, status, and priority)
|
||||
* can be given using their one-letter forms (i,c,s,p) for brevity. For
|
||||
* example: `-o Due,p,c` would sort first by the extended property `Due`,
|
||||
* then for items that have the same `Due` value it would sort by
|
||||
* priority, then by category. */
|
||||
cli.o(longOpt: 'order', argName: 'order', args: 1, required: false,
|
||||
'Order (sort) the results by the given properties. Provide a comma-' +
|
||||
'seperated list of property names to sort by in order of importance. The' +
|
||||
' basic properties (id, category, status, and priority) can be given' +
|
||||
' using their one-letter forms (i,c,s,p) for brevity. For example:' +
|
||||
' "-o Due,p,c" would sort first by the extended property "Due", then for' +
|
||||
' items that have the same "Due" value it would sort by priority, then' +
|
||||
' by category.')
|
||||
|
||||
/// -d,--dir
|
||||
/// : Use `<dir>` as the base directory (defaults to current directory).
|
||||
cli.d(longOpt: 'dir', argName: 'dir', args: 1, required: false,
|
||||
'Use <dir> as the base directory (defaults to current directory).')
|
||||
|
||||
/// -D,--daily-list
|
||||
/// : Print a Daily Task list based on issue Due, Scheduled, and Reminder
|
||||
/// extended properties.
|
||||
cli.D(longOpt: 'daily-list', 'Print a Daily Task list based on issue Due and' +
|
||||
' Reminder properties.')
|
||||
|
||||
/// --dl-scheduled
|
||||
/// : Show scheduled tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-scheduled', 'Show scheduled tasks in the daily list (all' +
|
||||
' are shown by default).')
|
||||
|
||||
/// --dl-due
|
||||
/// : Show due tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-due', 'Show due tasks in the daily list (all are shown by' +
|
||||
' default).')
|
||||
|
||||
/// --dl-upcoming
|
||||
/// : Show upcoming tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-upcoming', 'Show upcoming tasks in the daily list (all ' +
|
||||
' are shown by default).')
|
||||
|
||||
/// --dl-open
|
||||
/// : Show open tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-open', 'Show open tasks in the daily list (all are shown ' +
|
||||
' by default).')
|
||||
|
||||
/// --dl-hide-scheduled
|
||||
/// : Hide scheduled tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-hide-scheduled', 'Hide scheduled tasks in the daily list' +
|
||||
' (all are shown by default).')
|
||||
|
||||
/// --dl-hide-due
|
||||
/// : Show due tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-hide-due', 'Show due tasks in the daily list (all are' +
|
||||
' shown by default).')
|
||||
|
||||
/// --dl-hide-upcoming
|
||||
/// : Show upcoming tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-hide-upcoming', 'Show upcoming tasks in the daily list' +
|
||||
' (all are shown by default).')
|
||||
|
||||
/// --dl-hide-open
|
||||
/// : Show open tasks in the daily list (all are shown by default).
|
||||
cli._(longOpt: 'dl-hide-open', 'Show open tasks in the daily list (all are' +
|
||||
' shown by default).')
|
||||
|
||||
/// --dl-upcoming-days
|
||||
/// : The upcoming tasks section in the daily list includes any tasks due
|
||||
/// within the next seven days by default. This option overrides that
|
||||
/// default and allows you to specify the number of days ahead the upcoming
|
||||
/// section looks.
|
||||
cli._(longOpt: 'dl-upcoming-days', argName: 'num-days', args:1, required: false,
|
||||
'The upcoming tasks section in the daily list includes any tasks due ' +
|
||||
'within the next seven days by default. This option overrides that ' +
|
||||
'default and allows you to specify the number of days ahead the upcoming ' +
|
||||
'section looks.')
|
||||
|
||||
/// --version
|
||||
/// : Display PIT version information.
|
||||
cli._(longOpt: 'version', 'Display PIT version information.')
|
||||
|
||||
/// ## Parse CLI Options ##
|
||||
/// -----------------------
|
||||
|
||||
log.trace("Parsing options.")
|
||||
|
||||
def VERSION = "3.3.3"
|
||||
def opts = cli.parse(args)
|
||||
def issuedb = [:]
|
||||
def workingDir = new File('.')
|
||||
|
||||
/// Defaults for the issue filter/selector.
|
||||
def selectOpts = [
|
||||
categories: ['bug', 'feature', 'task'],
|
||||
status: ['new', 'reassigned', 'rejected',
|
||||
'resolved', 'validation_required'],
|
||||
priority: 9,
|
||||
projects: [],
|
||||
ids: [],
|
||||
extendedProperties: [:],
|
||||
acceptProjects: true]
|
||||
|
||||
/// Defaults for changing properties of issue(s)
|
||||
def assignOpts = [:]
|
||||
|
||||
if (!opts || opts.h) {
|
||||
cli.usage()
|
||||
System.exit(0) }
|
||||
|
||||
///Read the `-c` option: category filter designation(s).
|
||||
if (opts.c) {
|
||||
if (opts.c =~ /all/) {} // no-op, same as defaults
|
||||
else { selectOpts.categories = opts.c.split(/[,\s]/) } }
|
||||
|
||||
/// Parse the categories names into Category objects.
|
||||
try { selectOpts.categories =
|
||||
selectOpts.categories.collect { Category.toCategory(it) } }
|
||||
catch (Exception e) {
|
||||
println "Invalid category option: '-c ${e.localizedMessage}'."
|
||||
println "Valid options are: \n${Category.values().join(', ')}"
|
||||
println " (abbreviations are accepted)."
|
||||
System.exit(1) }
|
||||
|
||||
/// Read the `-s` option: status filter designation(s).
|
||||
if (opts.s) {
|
||||
// -s all
|
||||
if (opts.s =~ /all/) selectOpts.status = ['new', 'reassigned', 'rejected',
|
||||
'resolved', 'validation_required']
|
||||
// <list>
|
||||
else selectOpts.status = opts.s.split(/[,\s]/) }
|
||||
|
||||
/// Parse the statuses into Status objects.
|
||||
try { selectOpts.status =
|
||||
selectOpts.status.collect { Status.toStatus(it) } }
|
||||
catch (Exception e) {
|
||||
println "Invalid status option: '-s ${e.localizedMessage}'."
|
||||
print "Valid options are: \n${Status.values().join(', ')}"
|
||||
println " (abbreviations are accepted.)"
|
||||
System.exit(1) }
|
||||
|
||||
/// Read and parse the `-p` option: priority filter.
|
||||
if (opts.p) try {
|
||||
selectOpts.priority = opts.p.toInteger() }
|
||||
catch (NumberFormatException nfe) {
|
||||
println "Not a valid priority value: '-p ${opts.p}'."
|
||||
println "Valid values are: 0-9"
|
||||
System.exit(1) }
|
||||
|
||||
/// Read and parse the `-r` option: projects filter.
|
||||
if (opts.r) { selectOpts.projects =
|
||||
opts.r.toLowerCase().split(/[,\s]/).asType(List.class) }
|
||||
|
||||
/// Read and parse the `-i` option: id filter.
|
||||
if (opts.i) { selectOpts.ids = opts.i.split(/[,\s]/).asType(List.class) }
|
||||
|
||||
/// Read and parse the `-o` option: sort criteria.
|
||||
if (opts.o) {
|
||||
def sortProps = opts.o.split(',')
|
||||
selectOpts.issueSorter = sortProps.collect { prop ->
|
||||
switch (prop) {
|
||||
case ~/^i$/: return { issue -> issue.id }
|
||||
case ~/^p$/: return { issue -> issue.priority }
|
||||
case ~/^s$/: return { issue -> issue.status }
|
||||
case ~/^c$/: return { issue -> issue.category }
|
||||
default: return { issue -> issue[prop] } }}}
|
||||
|
||||
/// Read and parse any extended property selection criteria.
|
||||
if (opts.e) {
|
||||
opts.es.each { option ->
|
||||
def parts = option.split("=")
|
||||
selectOpts.extendedProperties[parts[0]] =
|
||||
ExtendedPropertyHelp.parse(parts[1]) }}
|
||||
|
||||
/// Read and parse the `-C` option: category to assign.
|
||||
if (opts.C) try { assignOpts.category = Category.toCategory(opts.C) }
|
||||
catch (Exception e) {
|
||||
println "Invalid category option: '-C ${e.localizedMessage}'."
|
||||
println "Valid categories are: \n${Category.values().join(', ')}"
|
||||
println " (abbreviations are accepted)."
|
||||
System.exit(1) }
|
||||
|
||||
/// Read and parse the `-S` option: status to assign.
|
||||
if (opts.S) try { assignOpts.status = Status.toStatus(opts.S) }
|
||||
catch (Exception e) {
|
||||
println "Invalid status option: '-S ${e.localizedMessage}'."
|
||||
println "Valid stasus options are: \n${Status.values().join(', ')}"
|
||||
println " (abbreviations are accepted)."
|
||||
System.exit(1) }
|
||||
|
||||
/// Read and parse the `-P` option: priority to assign.
|
||||
if (opts.P) try {assignOpts.priority = opts.P.toInteger() }
|
||||
catch (NumberFormatException nfe) {
|
||||
println "Not a valid priority value: '-P ${opts.P}'."
|
||||
println "Valid values are: 0-9"
|
||||
System.exit(1) }
|
||||
|
||||
/// Read an parse any extended properties to be set.
|
||||
if (opts.E) {
|
||||
opts.Es.each { option ->
|
||||
def parts = option.split("=")
|
||||
assignOpts[parts[0]] = ExtendedPropertyHelp.parse(parts[1]) }}
|
||||
|
||||
/// Read the title if given.
|
||||
if (opts.title) { assignOpts.title = opts.title }
|
||||
|
||||
/// Read the text if given.
|
||||
if (opts.text) { assignOpts.text = opts.text }
|
||||
|
||||
/// Set the project working directory.
|
||||
if (opts.d) {
|
||||
workingDir = new File(opts.d.trim())
|
||||
if (!workingDir.exists()) {
|
||||
println "Directory '${workingDir}' does not exist."
|
||||
return -1 } }
|
||||
|
||||
def EOL = System.getProperty('line.separator')
|
||||
|
||||
log.debug("Finished parsing options:\nworkingDir: {}\nselectOpts: {}\nassignOpts: {}",
|
||||
workingDir.canonicalPath, selectOpts, assignOpts)
|
||||
|
||||
/// ## Actions ##
|
||||
/// -------------
|
||||
|
||||
/// ### Version information.
|
||||
if (opts.version) {
|
||||
|
||||
println "PIT CLI Version ${VERSION}"
|
||||
println "Written by Jonathan Bernard\n" }
|
||||
|
||||
|
||||
/// ----
|
||||
else {
|
||||
|
||||
/// Build issue list.
|
||||
log.trace("Building issue database.")
|
||||
issuedb = new FileProject(workingDir)
|
||||
|
||||
/// Build filter from options.
|
||||
log.trace("Defining the filter.")
|
||||
def filter = new Filter(selectOpts)
|
||||
|
||||
/// ### List
|
||||
if (opts.l) {
|
||||
|
||||
log.trace("Listing issues.")
|
||||
|
||||
/// Local function (closure) to print a single issue.
|
||||
def printIssue = { issue, offset ->
|
||||
println "${offset}${issue}"
|
||||
if (opts.v) {
|
||||
println ""
|
||||
issue.text.eachLine { println "${offset} ${it}" }
|
||||
println ""
|
||||
issue.extendedProperties.each { name, value ->
|
||||
def formattedValue = ExtendedPropertyHelp.format(value)
|
||||
println "${offset} * ${name}: ${formattedValue}"}
|
||||
println ""}}
|
||||
|
||||
/// Local function (closure) to print a project and all visible subprojects.
|
||||
def printProject
|
||||
printProject = { project, offset ->
|
||||
println "\n${offset}${project.name}"
|
||||
println "${offset}${'-'.multiply(project.name.length())}"
|
||||
project.eachIssue(filter) { printIssue(it, offset) }
|
||||
project.eachProject(filter) { printProject(it, offset + " ") } }
|
||||
|
||||
/// Print all the issues in the root of this db.
|
||||
issuedb.eachIssue(filter) { printIssue(it, "") }
|
||||
|
||||
/// If the user set the recursive flag print all projects.
|
||||
if (opts.R) {
|
||||
issuedb.eachProject(filter) { printProject(it, "") }} }
|
||||
|
||||
/// ### Daily List
|
||||
else if (opts.D) {
|
||||
|
||||
log.trace("Showing a daily list.")
|
||||
|
||||
/// Set up our time intervals.
|
||||
def today = new DateMidnight()
|
||||
def tomorrow = today.plusDays(1)
|
||||
|
||||
/// #### Parse daily list specific display options.
|
||||
def visibleSections = []
|
||||
def suppressedSections
|
||||
def upcomingCutoff = today.plusDays(7)
|
||||
|
||||
/// Check for a custom upcoming section cutoff date.
|
||||
if (opts.'dl-upcoming-days') {
|
||||
int numDays = opts.'dl-upcoming-days' as int
|
||||
upcomingCutoff = today.plusDays(numDays) }
|
||||
|
||||
/// Parse the additive options first.
|
||||
if (opts.'dl-scheduled') { visibleSections << 'scheduled' }
|
||||
if (opts.'dl-due') { visibleSections << 'due' }
|
||||
if (opts.'dl-upcoming') { visibleSections << 'upcoming' }
|
||||
if (opts.'dl-open') { visibleSections << 'open' }
|
||||
|
||||
/// If the user did not add any sections assume they want them all.
|
||||
if (visibleSections.size() == 0) {
|
||||
visibleSections = ['scheduled', 'due', 'upcoming', 'open'] }
|
||||
|
||||
/// Now go through the negative options.
|
||||
if (opts.'dl-hide-scheduled') { visibleSections -= 'scheduled' }
|
||||
if (opts.'dl-hide-due') { visibleSections -= 'due' }
|
||||
if (opts.'dl-hide-upcoming') { visibleSections -= 'upcoming' }
|
||||
if (opts.'dl-hide-open') { visibleSections -= 'open' }
|
||||
|
||||
/// If the user did not specifically ask for a status filter, we want a
|
||||
/// different default filter when we are doing a daily list.
|
||||
if (!opts.s) { filter.status = [Status.NEW, Status.VALIDATION_REQUIRED] }
|
||||
|
||||
/// If the user did not give a specific sorting order, define our own: due
|
||||
/// date, then priority, then id.
|
||||
if (!opts.o) { filter.issueSorter = [ {it.due}, {it.priority}, {it.id} ] }
|
||||
|
||||
/// #### Get all the issues involved.
|
||||
def allIssues = opts.R ?
|
||||
/// If `-R` passed, get all issues, including subprojects.
|
||||
issuedb.getAllIssues(filter) :
|
||||
/// Otherwise, just use the issues for this project.
|
||||
issuedb.issues.values().findAll { filter ? filter.accept(it) : true }
|
||||
|
||||
/// We are going to sort the issues into these buckets based on when they are
|
||||
/// scheduled, when they are due and if they have a reminder set.
|
||||
def scheduledToday = []
|
||||
def dueToday = []
|
||||
def upcoming = []
|
||||
def notDueOrReminder = []
|
||||
|
||||
/// Helper closure to print an issue.
|
||||
def printIssue = { issue ->
|
||||
if (issue.due) println "${issue.due.toString('EEE, MM/dd')} -- ${issue}"
|
||||
else println " -- ${issue}" }
|
||||
|
||||
/// A sorter which sorts by date first, then by priority.
|
||||
def priorityDateSorter = { i1, i2 ->
|
||||
if (i1.priority == i2.priority) {
|
||||
def d1 = i1.due ?: new DateTime()
|
||||
def d2 = i2.due ?: new DateTime()
|
||||
|
||||
return d1.compareTo(d2) }
|
||||
else { return i1.priority - i2.priority }}
|
||||
|
||||
/// #### Categorize and sort the issues.
|
||||
/// Sort the issues into seperate lists based on their due dates and
|
||||
/// reminders.
|
||||
allIssues.each { issue ->
|
||||
/// * Find the issues that are scheduled for today.
|
||||
if (issue.scheduled && issue.scheduled < tomorrow) {
|
||||
scheduledToday << issue }
|
||||
|
||||
/// * Find the issues that are due today or are past due.
|
||||
else if (issue.due && issue.due < tomorrow) { dueToday << issue }
|
||||
|
||||
/// * Find the issues that are not yet due but have a reminder for
|
||||
/// today or days past as well as issues that are due before the
|
||||
/// `upcomingCutoff` date.
|
||||
else if ((issue.reminder && issue.reminder < tomorrow) ||
|
||||
(issue.due && issue.due < upcomingCutoff)) {
|
||||
upcoming << issue }
|
||||
|
||||
/// * All the others (not due and no reminder).
|
||||
else notDueOrReminder << issue }
|
||||
|
||||
/// #### Print the issues
|
||||
if (visibleSections.contains('scheduled') && scheduledToday.size() > 0) {
|
||||
println "Tasks Scheduled for Today"
|
||||
println "-------------------------"
|
||||
|
||||
scheduledToday.sort(priorityDateSorter).each { printIssue(it) }
|
||||
|
||||
println "" }
|
||||
|
||||
if (visibleSections.contains('due') && dueToday.size() > 0) {
|
||||
println "Tasks Due Today"
|
||||
println "---------------"
|
||||
|
||||
dueToday.sort(priorityDateSorter).each { printIssue(it) }
|
||||
|
||||
println ""}
|
||||
|
||||
if (visibleSections.contains('upcoming') && upcoming.size() > 0) {
|
||||
println "Upcoming Tasks"
|
||||
println "--------------"
|
||||
|
||||
upcoming.sort(priorityDateSorter).each { printIssue(it) }
|
||||
|
||||
println ""}
|
||||
|
||||
if (visibleSections.contains('open') && notDueOrReminder.size() > 0) {
|
||||
println "Other Open Issues"
|
||||
println "-----------------"
|
||||
|
||||
notDueOrReminder.sort(priorityDateSorter).each { printIssue(it) }
|
||||
|
||||
println "" }}
|
||||
|
||||
/// ### Create a New Issue.
|
||||
else if (opts.n) {
|
||||
|
||||
log.trace("Creating a new issue.")
|
||||
|
||||
Issue issue
|
||||
def sin = System.in.newReader()
|
||||
|
||||
/// Set the created extended property.
|
||||
assignOpts.created = new DateTime()
|
||||
|
||||
/// Prompt for the different options if they were not given on the command
|
||||
/// line. We will loop until they have entered a valid value. How it works:
|
||||
/// In the body of the loop we will try to read the input, parse it and
|
||||
/// assign it to a variable. If the input is invalid it will throw an
|
||||
/// exception before the assignment happens, the variable will still be
|
||||
/// null, and we will prompt the user again.
|
||||
|
||||
/// Prompt for category.
|
||||
while(!assignOpts.category) {
|
||||
try {
|
||||
print "Category (bug, feature, task): "
|
||||
assignOpts.category = Category.toCategory(sin.readLine())
|
||||
break }
|
||||
catch (e) {
|
||||
println "Invalid category: " + e.getLocalizedMessage()
|
||||
println "Valid options are: \n${Category.values().join(', ')}"
|
||||
println " (abbreviations are accepted)." } }
|
||||
|
||||
/// Prompt for the priority.
|
||||
while (!assignOpts.priority) {
|
||||
try {
|
||||
print "Priority (0-9): "
|
||||
assignOpts.priority = max(0, min(9, sin.readLine().toInteger()))
|
||||
break }
|
||||
catch (e) { println "Not a valid value." } }
|
||||
|
||||
/// Prompt for the issue title. No need to loop as the input does not need
|
||||
/// to be validated.
|
||||
if (!assignOpts.title) {
|
||||
println "Issue title: "
|
||||
assignOpts.title = sin.readLine().trim() }
|
||||
|
||||
/// Prompt for the issue text.
|
||||
if (!assignOpts.text) {
|
||||
assignOpts.text = ""
|
||||
println "Enter issue text (use EOF to stop): "
|
||||
try {
|
||||
def line = ""
|
||||
while(true) {
|
||||
line = sin.readLine()
|
||||
|
||||
// Stop when they enter EOF
|
||||
if (line ==~ /^EOF$/) break
|
||||
|
||||
assignOpts.text += line + EOL } }
|
||||
catch (e) {} }
|
||||
|
||||
/// Create the issue.
|
||||
issue = issuedb.createNewIssue(assignOpts)
|
||||
|
||||
println "New issue created: "
|
||||
println issue }
|
||||
|
||||
/// ### Change Existing Issues.
|
||||
else if (assignOpts.size() > 0) {
|
||||
|
||||
log.trace("Changing existing issues.")
|
||||
|
||||
/// We are going to add some extra properties if the status is being changed,
|
||||
/// because we are nice like that.
|
||||
if (assignOpts.status) { switch (assignOpts.status) {
|
||||
case Status.RESOLVED: assignOpts.resolved = new DateTime(); break
|
||||
case Status.REJECTED: assignOpts.rejected = new DateTime(); break
|
||||
default: break }}
|
||||
|
||||
/// #### processIssue
|
||||
/// A local function to handle the changes for one issue.
|
||||
def processIssue = { issue ->
|
||||
println issue
|
||||
/// Walk the assigned options map and set the properties on the issue.
|
||||
assignOpts.each { propName, value ->
|
||||
issue[propName] = value
|
||||
def formattedValue = ExtendedPropertyHelp.format(value)
|
||||
println " set ${propName} to ${formattedValue}" } }
|
||||
|
||||
/// If the user passed `-R`, walk the whole project, including subprojects.
|
||||
if (opts.R) { issuedb.walkProject(filter, processIssue) }
|
||||
/// Otherwise, just process the issues in this project.
|
||||
else {
|
||||
issuedb.issues.values()
|
||||
.findAll { filter ? filter.accept(it) : true }
|
||||
.each(processIssue) }}
|
||||
/// ### Invalid Input
|
||||
else {
|
||||
log.trace("Unknown request.")
|
||||
cli.usage(); return -1 }}
|
@ -1,6 +0,0 @@
|
||||
#Griffon Metadata file
|
||||
#Thu Aug 05 10:29:59 CDT 2010
|
||||
app.archetype=default
|
||||
app.griffon.version=0.9
|
||||
app.name=pit-swing
|
||||
app.version=2.5.1
|
@ -1,33 +0,0 @@
|
||||
application {
|
||||
title = 'PitSwing'
|
||||
startupGroups = ['PIT']
|
||||
|
||||
// Should Griffon exit when no Griffon created frames are showing?
|
||||
autoShutdown = true
|
||||
|
||||
// If you want some non-standard application class, apply it here
|
||||
//frameClass = 'javax.swing.JFrame'
|
||||
}
|
||||
mvcGroups {
|
||||
// MVC Group for "ProjectPanel"
|
||||
'ProjectPanel' {
|
||||
model = 'com.jdbernard.pit.swing.ProjectPanelModel'
|
||||
view = 'com.jdbernard.pit.swing.ProjectPanelView'
|
||||
controller = 'com.jdbernard.pit.swing.ProjectPanelController'
|
||||
}
|
||||
|
||||
// MVC Group for "NewIssueDialog"
|
||||
'NewIssueDialog' {
|
||||
model = 'com.jdbernard.pit.swing.NewIssueDialogModel'
|
||||
view = 'com.jdbernard.pit.swing.NewIssueDialogView'
|
||||
controller = 'com.jdbernard.pit.swing.NewIssueDialogController'
|
||||
}
|
||||
|
||||
// MVC Group for "PIT"
|
||||
'PIT' {
|
||||
model = 'com.jdbernard.pit.swing.PITModel'
|
||||
view = 'com.jdbernard.pit.swing.PITView'
|
||||
controller = 'com.jdbernard.pit.swing.PITController'
|
||||
}
|
||||
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
// key signing information
|
||||
environments {
|
||||
development {
|
||||
signingkey {
|
||||
params {
|
||||
sigfile = 'GRIFFON'
|
||||
keystore = "${basedir}/griffon-app/conf/keys/devKeystore"
|
||||
alias = 'development'
|
||||
storepass = 'BadStorePassword'
|
||||
keypass = 'BadKeyPassword'
|
||||
lazy = true // only sign when unsigned
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
test {
|
||||
griffon {
|
||||
jars {
|
||||
sign = false
|
||||
pack = false
|
||||
}
|
||||
}
|
||||
}
|
||||
production {
|
||||
signingkey {
|
||||
params {
|
||||
sigfile = 'GRIFFON'
|
||||
keystore = 'CHANGE ME'
|
||||
alias = 'CHANGE ME'
|
||||
// NOTE: for production keys it is more secure to rely on key prompting
|
||||
// no value means we will prompt //storepass = 'BadStorePassword'
|
||||
// no value means we will prompt //keypass = 'BadKeyPassword'
|
||||
lazy = false // sign, regardless of existing signatures
|
||||
}
|
||||
}
|
||||
|
||||
griffon {
|
||||
jars {
|
||||
sign = true
|
||||
pack = true
|
||||
destDir = "${basedir}/staging"
|
||||
}
|
||||
webstart {
|
||||
codebase = 'CHANGE ME'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
griffon {
|
||||
memory {
|
||||
//max = '64m'
|
||||
//min = '2m'
|
||||
//maxPermSize = '64m'
|
||||
}
|
||||
jars {
|
||||
sign = false
|
||||
pack = false
|
||||
destDir = "${basedir}/staging"
|
||||
jarName = "${appName}.jar"
|
||||
}
|
||||
extensions {
|
||||
jarUrls = []
|
||||
jnlpUrls = []
|
||||
/*
|
||||
props {
|
||||
someProperty = 'someValue'
|
||||
}
|
||||
resources {
|
||||
linux { // windows, macosx, solaris
|
||||
jars = []
|
||||
nativelibs = []
|
||||
props {
|
||||
someProperty = 'someValue'
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
webstart {
|
||||
codebase = "${new File(griffon.jars.destDir).toURI().toASCIIString()}"
|
||||
jnlp = 'application.jnlp'
|
||||
}
|
||||
applet {
|
||||
jnlp = 'applet.jnlp'
|
||||
html = 'applet.html'
|
||||
}
|
||||
}
|
||||
|
||||
// required for custom environments
|
||||
signingkey {
|
||||
params {
|
||||
def env = griffon.util.Environment.current.name
|
||||
sigfile = 'GRIFFON-' + env
|
||||
keystore = "${basedir}/griffon-app/conf/keys/${env}Keystore"
|
||||
alias = env
|
||||
// storepass = 'BadStorePassword'
|
||||
// keypass = 'BadKeyPassword'
|
||||
lazy = true // only sign when unsigned
|
||||
}
|
||||
}
|
||||
|
||||
griffon.project.dependency.resolution = {
|
||||
// inherit Griffon' default dependencies
|
||||
inherits("global") {
|
||||
}
|
||||
log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
|
||||
repositories {
|
||||
griffonPlugins()
|
||||
griffonHome()
|
||||
griffonCentral()
|
||||
|
||||
// uncomment the below to enable remote dependency resolution
|
||||
// from public Maven repositories
|
||||
//mavenLocal()
|
||||
//mavenCentral()
|
||||
//mavenRepo "http://snapshots.repository.codehaus.org"
|
||||
//mavenRepo "http://repository.codehaus.org"
|
||||
//mavenRepo "http://download.java.net/maven/2/"
|
||||
//mavenRepo "http://repository.jboss.com/maven2/"
|
||||
}
|
||||
dependencies {
|
||||
// specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
|
||||
|
||||
// runtime 'mysql:mysql-connector-java:5.1.5'
|
||||
}
|
||||
}
|
||||
|
||||
griffon {
|
||||
doc {
|
||||
logo = '<a href="http://griffon.codehaus.org" target="_blank"><img alt="The Griffon Framework" src="../img/griffon.png" border="0"/></a>'
|
||||
sponsorLogo = "<br/>"
|
||||
footer = "<br/><br/>Made with Griffon (0.9)"
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
root {
|
||||
'groovy.swing.SwingBuilder' {
|
||||
controller = ['Threading']
|
||||
view = '*'
|
||||
}
|
||||
'griffon.app.ApplicationBuilder' {
|
||||
view = '*'
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
// log4j configuration
|
||||
log4j {
|
||||
appender.stdout = 'org.apache.log4j.ConsoleAppender'
|
||||
appender.'stdout.layout'='org.apache.log4j.PatternLayout'
|
||||
appender.'stdout.layout.ConversionPattern'='[%r] %c{2} %m%n'
|
||||
appender.errors = 'org.apache.log4j.FileAppender'
|
||||
appender.'errors.layout'='org.apache.log4j.PatternLayout'
|
||||
appender.'errors.layout.ConversionPattern'='[%r] %c{2} %m%n'
|
||||
appender.'errors.File'='stacktrace.log'
|
||||
rootLogger='error,stdout'
|
||||
logger {
|
||||
griffon='error'
|
||||
StackTrace='error,errors'
|
||||
org {
|
||||
codehaus.griffon.commons='info' // core / classloading
|
||||
}
|
||||
}
|
||||
additivity.StackTrace=false
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
onNewInstance = { klass, type, instance ->
|
||||
instance.metaClass.logger = LoggerFactory.getLogger(klass.name)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script src="http://java.com/js/deployJava.js"></script>
|
||||
<script>
|
||||
var attributes = {id: '@griffonAppName@',
|
||||
codebase:'@griffonAppCodebase@',
|
||||
code:'@griffonAppletClass@',
|
||||
archive:'@appletJars@',
|
||||
width:'@applet.width@', height:'@applet.height@'} ;
|
||||
var parameters = {fontSize:16,
|
||||
java_arguments: "-Djnlp.packEnabled=true",
|
||||
jnlp_href:'@griffonAppCodebase@/applet.jnlp',
|
||||
draggable:'true',
|
||||
image:'griffon.png',
|
||||
boxmessage:'Loading @griffonAppName@',
|
||||
boxbgcolor:'#FFFFFF', boxfgcolor:'#000000',
|
||||
codebase_lookup: 'false'@applet.script.params@} ;
|
||||
var version = '1.5.0' ;
|
||||
deployJava.runApplet(attributes, parameters, version);
|
||||
</script>
|
||||
<!--
|
||||
<APPLET CODEBASE='@griffonAppCodebase@'
|
||||
CODE='@griffonAppletClass@'
|
||||
ARCHIVE='@appletJars@'
|
||||
WIDTH='@applet.width@' HEIGHT='@applet.height@'>
|
||||
<PARAM NAME="java_arguments" VALUE="-Djnlp.packEnabled=true">
|
||||
<PARAM NAME='jnlp_href' VALUE='@griffonAppCodebase@/applet.jnlp'>
|
||||
<PARAM NAME='dragggable' VALUE='true'>
|
||||
<PARAM NAME='image' VALUE='griffon.png'>
|
||||
<PARAM NAME='boxmessage' VALUE='Loading @griffonAppName@'>
|
||||
<PARAM NAME='boxbgcolor' VALUE='#FFFFFF'>
|
||||
<PARAM NAME='boxfgcolor' VALUE='#000000'>
|
||||
<PARAM NAME='codebase_lookup' VALUE='false'>
|
||||
@applet.tag.params@
|
||||
</APPLET>
|
||||
-->
|
||||
</body>
|
||||
</html>
|
@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
|
||||
<jnlp
|
||||
version="@griffonAppVersion@"
|
||||
codebase="@griffonAppCodebase@"
|
||||
href="@jnlpFileName@"
|
||||
>
|
||||
<information>
|
||||
<title>@griffonAppName@</title>
|
||||
<vendor>@griffonAppName@</vendor>
|
||||
<!--<homepage href="http://app.example.com/"/>-->
|
||||
<!--fallback description-->
|
||||
<description>@griffonAppName@</description>
|
||||
<description kind="one-line">@griffonAppName@</description>
|
||||
<description kind="short">@griffonAppName@</description>
|
||||
<description kind="tooltip">@griffonAppName@</description>
|
||||
<!-- fallback icon -->
|
||||
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
|
||||
<!-- icon used for splash screen -->
|
||||
<icon href="griffon.png" kind="splash" width="381" height="123"/>
|
||||
<!-- icon used in menu -->
|
||||
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
|
||||
<!-- icon used on desktop -->
|
||||
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
|
||||
<!-- to create shortcuts, uncomment this
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="@griffonAppName@"/>
|
||||
</shortcut>
|
||||
-->
|
||||
<offline-allowed/>
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
<!--<j2ee-application-client-permissions/>-->
|
||||
</security>
|
||||
<resources>
|
||||
<property name="griffon.runmode" value="applet"/>
|
||||
<property name="jnlp.packEnabled" value="true"/>
|
||||
<j2se version="1.5+" @memoryOptions@/>
|
||||
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
|
||||
@jnlpJars@
|
||||
<!-- Add all extra jars below here, or the app may break -->
|
||||
@jnlpExtensions@
|
||||
@jnlpProperties@
|
||||
</resources>
|
||||
@jnlpResources@
|
||||
<applet-desc
|
||||
documentbase="@griffonAppCodebase@"
|
||||
name="@griffonAppName@Applet"
|
||||
main-class="@griffonAppletClass@"
|
||||
width="@applet.width@"
|
||||
height="@applet.height@">
|
||||
<!-- params are ignored when referenced from web page for 6u10 -->
|
||||
<!--<param name="key1" value="value1"/>-->
|
||||
<!--<param name="key2" value="value2"/>-->
|
||||
@applet.tag.params@
|
||||
</applet-desc>
|
||||
</jnlp>
|
@ -1,54 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
|
||||
<jnlp
|
||||
version="@griffonAppVersion@"
|
||||
codebase="@griffonAppCodebase@"
|
||||
href="@jnlpFileName@"
|
||||
>
|
||||
<information>
|
||||
<title>@griffonAppName@</title>
|
||||
<vendor>@griffonAppName@</vendor>
|
||||
<!--<homepage href="http://app.example.com/"/>-->
|
||||
<!--fallback description-->
|
||||
<description>@griffonAppName@</description>
|
||||
<description kind="one-line">@griffonAppName@</description>
|
||||
<description kind="short">@griffonAppName@</description>
|
||||
<description kind="tooltip">@griffonAppName@</description>
|
||||
<!-- fallback icon -->
|
||||
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
|
||||
<!-- icon used for splash screen -->
|
||||
<icon href="griffon.png" kind="splash" width="381" height="123"/>
|
||||
<!-- icon used in menu -->
|
||||
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
|
||||
<!-- icon used on desktop -->
|
||||
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
|
||||
<!-- to create shortcuts, uncomment this
|
||||
<shortcut online="true">
|
||||
<desktop/>
|
||||
<menu submenu="@griffonAppName@"/>
|
||||
</shortcut>
|
||||
<offline-allowed/>
|
||||
-->
|
||||
</information>
|
||||
<security>
|
||||
<all-permissions/>
|
||||
<!--<j2ee-application-client-permissions/>-->
|
||||
</security>
|
||||
<resources>
|
||||
<property name="griffon.runmode" value="webstart"/>
|
||||
<property name="jnlp.packEnabled" value="true"/>
|
||||
<j2se version="1.5+" @memoryOptions@/>
|
||||
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
|
||||
@jnlpJars@
|
||||
<!-- Add all extra jars below here, or the app may break -->
|
||||
@jnlpExtensions@
|
||||
@jnlpProperties@
|
||||
</resources>
|
||||
@jnlpResources@
|
||||
<application-desc main-class="@griffonApplicationClass@">
|
||||
<!-- params are ignored when referenced from web page for 6u10 -->
|
||||
<!--<param name="key1" value="value1"/>-->
|
||||
<!--<param name="key2" value="value2"/>-->
|
||||
@applet.tag.params@
|
||||
</application-desc>
|
||||
</jnlp>
|
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,26 +0,0 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Category
|
||||
import com.jdbernard.pit.Status
|
||||
|
||||
class NewIssueDialogController {
|
||||
// these will be injected by Griffon
|
||||
def model
|
||||
def view
|
||||
|
||||
void mvcGroupInit(Map args) {
|
||||
// this method is called after model and view are injected
|
||||
}
|
||||
|
||||
def show = {
|
||||
view.titleTextField.text = ""
|
||||
model.text = ""
|
||||
view.categoryComboBox.selectedItem = Category.BUG
|
||||
model.category = Category.BUG
|
||||
view.statusComboBox.selectedItem = Status.NEW
|
||||
model.status = Status.NEW
|
||||
view.prioritySpinner.setValue(5)
|
||||
model.priority = 5
|
||||
view.dialog.visible = true
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Category
|
||||
import com.jdbernard.pit.FileProject
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
class PITController {
|
||||
|
||||
// these will be injected by Griffon
|
||||
def model
|
||||
def view
|
||||
|
||||
void mvcGroupInit(Map args) {
|
||||
|
||||
model.newIssueDialogMVC = buildMVCGroup('NewIssueDialog')
|
||||
|
||||
SwingUtilities.invokeAndWait {
|
||||
model.issueListRenderer = new IssueTableCellRenderer()
|
||||
|
||||
File pitHome, pitrcFile, pitswingrcFile
|
||||
boolean logDbg = logger.isDebugEnabled()
|
||||
Properties config = new Properties()
|
||||
|
||||
// look for config directory
|
||||
pitHome = new File(System.getProperty('user.home'), '.pit')
|
||||
if (logDbg) logger.debug("$pitHome is " +
|
||||
(pitHome.exists() ? '' : 'not ') + "present.")
|
||||
|
||||
// look for general config options
|
||||
pitrcFile = new File(pitHome, 'pitrc')
|
||||
if (logDbg) logger.debug("$pitrcFile is " +
|
||||
(pitrcFile.exists() ? '' : 'not ') + "present.")
|
||||
|
||||
// load general config (if present)
|
||||
if (pitrcFile.exists() && pitrcFile.canRead()) {
|
||||
pitrcFile.withInputStream { config.load(it) }
|
||||
if (logDbg) logger.debug("Loaded pitrc")
|
||||
}
|
||||
|
||||
// look for swing specific config
|
||||
pitswingrcFile = new File(pitHome, 'pitswingrc')
|
||||
if (logDbg) logger.debug("$pitswingrcFile is " +
|
||||
(pitswingrcFile.exists() ? '' : 'not ') + "present.")
|
||||
|
||||
// load swing specific config (if present)
|
||||
if (pitswingrcFile.exists() && pitswingrcFile.canRead()) {
|
||||
pitswingrcFile.withInputStream { config.load(it) }
|
||||
if (logDbg) logger.debug("Loaded pitswingrc")
|
||||
}
|
||||
|
||||
// Process configurable options
|
||||
// ----------------------------
|
||||
|
||||
if (logDbg) {
|
||||
logger.debug("Configurable properties:")
|
||||
config.keySet().each { logger.debug(it) }
|
||||
}
|
||||
|
||||
// add custom category templates
|
||||
Category.values().each { category ->
|
||||
def expectedKey = "issue." + category.name().toLowerCase() +
|
||||
".template"
|
||||
if (logDbg) logger.debug("Looking for key: $expectedKey")
|
||||
|
||||
config.keySet().each { currentKey ->
|
||||
if (currentKey == expectedKey)
|
||||
model.templates[(category)] =
|
||||
config.getProperty(expectedKey, "")
|
||||
if (logDbg) logger.debug("Template for category $category: '" +
|
||||
model.templates[(category)] + "'")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// load custom issueListRenderer
|
||||
// TODO: not yet supported (maybe no need)
|
||||
|
||||
// load initial repositories
|
||||
if (config.containsKey('initial-repositories')) {
|
||||
def initRepos = config.getProperty('initial-repositories', '')
|
||||
initRepos = initRepos.split(/[:;,]/)
|
||||
initRepos.each { repoPath -> loadProject(new File(repoPath)) }
|
||||
if (logDbg) logger.debug("Init repos: '$initRepos'")
|
||||
}
|
||||
|
||||
// load custom issue css
|
||||
if (config.containsKey('issue.display.css')) {
|
||||
def issueCSS = config.getProperty('issue.display.css', "")
|
||||
|
||||
// look for a file relative to the pit home directory
|
||||
def cssFile
|
||||
|
||||
// use short-circuit logic to test several possible locations
|
||||
if ((cssFile = new File(pitHome, issueCSS)).exists() ||
|
||||
(cssFile = new File(pitHome.parentFile(), issueCSS)).exists() ||
|
||||
(cssFile = new File(issueCSS)).exists())
|
||||
issueCSS = cssFile.text
|
||||
|
||||
if (logDbg) logger.debug("CSS for issue display: $issueCSS")
|
||||
model.issueCSS = issueCSS
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void refreshIssues() {
|
||||
model.projectPanelMVCs.each { title, mvc ->
|
||||
mvc.controller.refreshIssues()
|
||||
}
|
||||
}
|
||||
|
||||
def openProject = { evt = null ->
|
||||
if (view.openDialog.showOpenDialog(view.frame) !=
|
||||
JFileChooser.APPROVE_OPTIONS) return
|
||||
|
||||
loadProject(view.openDialog.selectedFile)
|
||||
}
|
||||
|
||||
def loadProject = { File projectDir ->
|
||||
def newMVC
|
||||
|
||||
// if this is not a valid directory, do nothing
|
||||
// TODO: log to the user that this is not a valid directory
|
||||
if (!projectDir.exists() || !projectDir.isDirectory()) return
|
||||
|
||||
// create new ProjectPanel MVC
|
||||
newMVC = buildMVCGroup('ProjectPanel',
|
||||
mainMVC: [model: model, view: view, controller: this],
|
||||
newIssueDialogMVC: model.newIssueDialogMVC,
|
||||
issueCellRenderer: model.issueListRenderer,
|
||||
issueCSS: model.issueCSS,
|
||||
rootProject: new FileProject(projectDir))
|
||||
newMVC.model.id = projectDir.name
|
||||
|
||||
// if we already have a tab with this id
|
||||
if (model.projectPanelMVCs[(newMVC.model.id)]) {
|
||||
|
||||
// try using the canonical path
|
||||
newMVC.model.id = projectDir.canonicalPath
|
||||
|
||||
// still not unique?
|
||||
if (model.projectPanelMVCs[(newMVC.model.id)]) {
|
||||
|
||||
// first time this has happened?
|
||||
if (!model.projectIdMap[(newMVC.model.id)])
|
||||
model.projectIdMap[(newMVC.model.id)] = 0
|
||||
|
||||
// no? increment
|
||||
else model.projectIdMap[(newMVC.model.id)] =
|
||||
model.projectIdMap[(newMVC.model.id)] + 1
|
||||
|
||||
// use our new, unique id
|
||||
newMVC.model.id += "-" + model.projectIdMap[(newMVC.model.id)]
|
||||
}
|
||||
}
|
||||
|
||||
model.projectPanelMVCs[(newMVC.model.id)] = newMVC
|
||||
view.mainTabbedPane.addTab(newMVC.model.id, newMVC.view.panel)
|
||||
}
|
||||
|
||||
def closeProject = { evt = null ->
|
||||
model.projectPanelMVCs.remove(view.mainTabbedPane.getTitleAt(
|
||||
view.mainTabbedPane.selectedIndex))
|
||||
view.mainTabbedPane.remove(view.mainTabbedPane.selectedComponent)
|
||||
}
|
||||
|
||||
def shutdown = { evt = null ->
|
||||
app.shutdown()
|
||||
}
|
||||
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Category
|
||||
import com.jdbernard.pit.FileProject
|
||||
import com.jdbernard.pit.FlatProjectView
|
||||
import com.jdbernard.pit.Issue
|
||||
import com.jdbernard.pit.Project
|
||||
import com.jdbernard.pit.Status
|
||||
import javax.swing.DefaultListModel
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import org.dom4j.Document
|
||||
import org.dom4j.io.OutputFormat
|
||||
import org.dom4j.io.XMLWriter
|
||||
import org.nuiton.jrst.JRSTGenerator
|
||||
import org.nuiton.jrst.JRSTReader
|
||||
|
||||
class ProjectPanelController {
|
||||
// these will be injected by Griffon
|
||||
def model
|
||||
def view
|
||||
|
||||
def jrstReader
|
||||
def jrstGen
|
||||
|
||||
static URL rst2htmlXSL =
|
||||
ProjectPanelController.class.getResource("/rst2xhtml.xsl")
|
||||
|
||||
void mvcGroupInit(Map args) {
|
||||
jrstReader = new JRSTReader()
|
||||
jrstGen = new JRSTGenerator()
|
||||
|
||||
refreshProject()
|
||||
}
|
||||
|
||||
/**
|
||||
* displayProject
|
||||
* @param project Project to display
|
||||
*/
|
||||
void displayProject(Project project) {
|
||||
if (!project) return
|
||||
|
||||
view.issueTextArea.text = ""
|
||||
view.issueTextDisplay.text = ""
|
||||
view.issueTextPanelLayout.show(view.issueTextPanel, 'display')
|
||||
|
||||
// build a new IssueTableModel if none cached
|
||||
if (!model.projectTableModels[(project.name)]) {
|
||||
def itm = new IssueTableModel(project,
|
||||
model.filter ?: model.mainMVC.model.filter)
|
||||
itm.categoryIcons = model.mainMVC.model.categoryIcons
|
||||
itm.statusIcons = model.mainMVC.model.statusIcons
|
||||
model.projectTableModels[(project.name)] = itm
|
||||
}
|
||||
|
||||
view.issueTable.setModel(model.projectTableModels[(project.name)])
|
||||
|
||||
def tcm = view.issueTable.columnModel
|
||||
tcm.getColumn(0).maxWidth = 24
|
||||
tcm.getColumn(1).maxWidth = 40
|
||||
tcm.getColumn(2).maxWidth = 35
|
||||
if (view.issueTable.model.columnCount == 5)
|
||||
tcm.getColumn(4).maxWidth = 150
|
||||
}
|
||||
|
||||
void displayIssue(Issue issue) {
|
||||
if (!issue) return
|
||||
|
||||
// hack because binding view.issueTextArea.font to
|
||||
// mainMVC.model.issueDetailFont causes problems
|
||||
if (view.issueTextArea.font != model.mainMVC.model.issueDetailFont)
|
||||
view.issueTextArea.font = model.mainMVC.model.issueDetailFont
|
||||
|
||||
view.issueTextArea.text = issue.text
|
||||
view.issueTextArea.caretPosition = 0
|
||||
view.issueTextDisplay.text = rst2html(issue.text)
|
||||
view.issueTextDisplay.caretPosition = 0
|
||||
view.issueTextPanelLayout.show(view.issueTextPanel, 'display')
|
||||
}
|
||||
|
||||
void showProejctPopup(Project project, def x, def y) {
|
||||
model.popupProject = project
|
||||
view.projectPopupMenu.show(view.projectTree, x, y)
|
||||
}
|
||||
|
||||
void showIssuePopup(Issue issue, def x, def y) {
|
||||
model.popupIssue = issue
|
||||
view.issuePopupMenu.show(view.issueTable, x, y)
|
||||
}
|
||||
|
||||
void refreshProject() {
|
||||
if (model.rootProject) {
|
||||
def rootNode = new DefaultMutableTreeNode()
|
||||
def flatview = new FlatProjectView('All Issues')
|
||||
flatview.projects[(model.rootProject.name)] = model.rootProject
|
||||
rootNode.add(new DefaultMutableTreeNode(flatview))
|
||||
rootNode.add(makeNodes(model.rootProject))
|
||||
view.projectTree.model = new DefaultTreeModel(rootNode)
|
||||
} else {
|
||||
view.projectTree.model = new DefaultTreeModel(
|
||||
new DefaultMutableTreeNode())
|
||||
}
|
||||
}
|
||||
|
||||
void refreshIssues() {
|
||||
model.projectTableModels.clear()
|
||||
displayProject(model.selectedProject)
|
||||
}
|
||||
|
||||
def makeNodes(Project project) {
|
||||
def rootNode = new DefaultMutableTreeNode(project)
|
||||
project.eachProject(model.filter ?: model.mainMVC.model.filter)
|
||||
{ rootNode.add(makeNodes(it)) }
|
||||
return rootNode
|
||||
}
|
||||
|
||||
def newProject = { evt ->
|
||||
def name = JOptionPane.showInputDialog(model.mainMVC.view.frame,
|
||||
'Project name:', 'New Project...', JOptionPane.QUESTION_MESSAGE)
|
||||
|
||||
def project
|
||||
|
||||
if (evt.source == view.newProjectButton)
|
||||
project = model.selectedProject ?: model.rootProject
|
||||
else project = model.popupProject ?: model.rootProject
|
||||
def newProject = project.createNewProject(name)
|
||||
|
||||
project.projects[(newProject.name)] = newProject
|
||||
refreshProject()
|
||||
}
|
||||
|
||||
def deleteProject = { evt ->
|
||||
def project
|
||||
|
||||
if (evt.source == view.deleteProjectButton)
|
||||
project = model.selectedProject ?: model.rootProject
|
||||
else project = model.popupProject ?: model.rootProject
|
||||
|
||||
project.delete()
|
||||
|
||||
model.rootProject = new FileProject(model.rootProject.source)
|
||||
}
|
||||
|
||||
def newIssue = { evt = null ->
|
||||
model.newIssueDialogMVC.controller.show()
|
||||
if (model.newIssueDialogMVC.model.accept) {
|
||||
def nidModel = model.newIssueDialogMVC.model
|
||||
def issueText = nidModel.text
|
||||
|
||||
if (model.mainMVC.model.templates[(nidModel.category)]) {
|
||||
issueText = model.mainMVC.model.templates[(nidModel.category)]
|
||||
issueText = issueText.replaceFirst(/TITLE/,
|
||||
nidModel.text)
|
||||
}
|
||||
|
||||
def issue = model.selectedProject.createNewIssue(
|
||||
category: nidModel.category,
|
||||
status: nidModel.status,
|
||||
priority: nidModel.priority,
|
||||
text: issueText)
|
||||
model.projectTableModels[(model.selectedProject.name)] = null
|
||||
displayProject(model.selectedProject)
|
||||
}
|
||||
}
|
||||
|
||||
def deleteIssue = { evt ->
|
||||
def issue
|
||||
if (evt.source == view.deleteIssueButton)
|
||||
issue = getSelectedIssue()
|
||||
else issue = model.popupIssue
|
||||
|
||||
model.selectedProject.issues.remove(issue.id)
|
||||
view.issueTable.model.issues.remove(issue)
|
||||
|
||||
issue.delete()
|
||||
view.issueTable.invalidate()
|
||||
}
|
||||
|
||||
def getSelectedIssue() {
|
||||
if (view.issueTable.selectionModel.isSelectionEmpty())
|
||||
return null
|
||||
|
||||
return view.issueTable.model.issues[view.issueTable.
|
||||
convertRowIndexToModel(view.issueTable.selectedRow)]
|
||||
}
|
||||
|
||||
String rst2html(String rst) {
|
||||
Document doc
|
||||
StringWriter outString
|
||||
StringBuilder result = new StringBuilder()
|
||||
|
||||
// read the RST in with the RST parser
|
||||
new StringReader(rst).withReader { doc = jrstReader.read(it) }
|
||||
|
||||
// transform to XHTML
|
||||
doc = jrstGen.transform(doc, rst2htmlXSL)
|
||||
|
||||
// write to the StringWriter
|
||||
outString = new StringWriter()
|
||||
outString.withWriter { new XMLWriter(it, new OutputFormat("", true)).write(doc) }
|
||||
|
||||
// java's embeded html is primitive, we need to massage the results
|
||||
outString.toString().eachLine { line ->
|
||||
|
||||
// remove the XML version and encoding, title element, meta elems
|
||||
if (line =~ /<\?.*\?>/ || line =~ /<meta.*$/ || line =~ /<title.*$/) { return }
|
||||
|
||||
// all other elements, remove all class, xmlns attributes
|
||||
def m = (line =~ /(<\S+)(\s*(class|xmlns)=".*"\s*)*(\/?>.*)/)
|
||||
if (m) line = m[0][1] + m[0][4]
|
||||
|
||||
result.append(line)
|
||||
|
||||
// add in the CSS information to the head
|
||||
if (line =~ /<head>/) result.append('<style type="text/css">' +
|
||||
model.issueCSS + '</style>')
|
||||
}
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* This script is executed inside the UI thread, so be sure to call
|
||||
* long running code in another thread.
|
||||
*
|
||||
* You have the following options
|
||||
* - execOutside { // your code }
|
||||
* - execFuture { // your code }
|
||||
* - Thread.start { // your code }
|
||||
*
|
||||
* You have the following options to run code again inside the UI thread
|
||||
* - execAsync { // your code }
|
||||
* - execSync { // your code }
|
||||
*/
|
||||
|
||||
import groovy.swing.SwingBuilder
|
||||
import griffon.util.GriffonPlatformHelper
|
||||
import static griffon.util.GriffonApplicationUtils.*
|
||||
|
||||
GriffonPlatformHelper.tweakForNativePlatform(app)
|
||||
SwingBuilder.lookAndFeel('org.pushingpixels.substance.api.skin.SubstanceCremeCoffeeLookAndFeel', 'nimbus', ['metal', [boldFonts: false]])
|
||||
|
||||
// make config directory
|
||||
def confDir = new File(System.getProperty('user.home'), '.pit')
|
||||
if (!confDir.exists()) confDir.mkdirs()
|
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* This script is executed inside the UI thread, so be sure to call
|
||||
* long running code in another thread.
|
||||
*
|
||||
* You have the following options
|
||||
* - execOutside { // your code }
|
||||
* - execFuture { // your code }
|
||||
* - Thread.start { // your code }
|
||||
*
|
||||
* You have the following options to run code again inside the UI thread
|
||||
* - execAsync { // your code }
|
||||
* - execSync { // your code }
|
||||
*/
|
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* This script is executed inside the UI thread, so be sure to call
|
||||
* long running code in another thread.
|
||||
*
|
||||
* You have the following options
|
||||
* - execOutside { // your code }
|
||||
* - execFuture { // your code }
|
||||
* - Thread.start { // your code }
|
||||
*
|
||||
* You have the following options to run code again inside the UI thread
|
||||
* - execAsync { // your code }
|
||||
* - execSync { // your code }
|
||||
*/
|
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* This script is executed inside the UI thread, so be sure to call
|
||||
* long running code in another thread.
|
||||
*
|
||||
* You have the following options
|
||||
* - execOutside { // your code }
|
||||
* - execFuture { // your code }
|
||||
* - Thread.start { // your code }
|
||||
*
|
||||
* You have the following options to run code again inside the UI thread
|
||||
* - execAsync { // your code }
|
||||
* - execSync { // your code }
|
||||
*/
|
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* This script is executed inside the UI thread, so be sure to call
|
||||
* long running code in another thread.
|
||||
*
|
||||
* You have the following options
|
||||
* - execOutside { // your code }
|
||||
* - execFuture { // your code }
|
||||
* - Thread.start { // your code }
|
||||
*
|
||||
* You have the following options to run code again inside the UI thread
|
||||
* - execAsync { // your code }
|
||||
* - execSync { // your code }
|
||||
*/
|
@ -1,13 +0,0 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Category
|
||||
import com.jdbernard.pit.Status
|
||||
import groovy.beans.Bindable
|
||||
|
||||
class NewIssueDialogModel {
|
||||
@Bindable boolean accept
|
||||
String text
|
||||
Category category
|
||||
Status status
|
||||
int priority
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Category
|
||||
import com.jdbernard.pit.Filter
|
||||
import com.jdbernard.pit.Issue
|
||||
import com.jdbernard.pit.Project
|
||||
import com.jdbernard.pit.Status
|
||||
import groovy.beans.Bindable
|
||||
import java.awt.Font
|
||||
import javax.swing.ImageIcon
|
||||
|
||||
class PITModel {
|
||||
|
||||
// filter for projects and classes
|
||||
Filter filter = new Filter(categories: [],
|
||||
status: [Status.NEW, Status.VALIDATION_REQUIRED])
|
||||
|
||||
def issueListRenderer
|
||||
|
||||
// map of category -> issue template
|
||||
Map<Category, String> templates = [:]
|
||||
|
||||
String issueCSS = getClass().getResourceAsStream("/default-issue.css").text
|
||||
|
||||
Map<Category, ImageIcon> categoryIcons = [:]
|
||||
Map<Category, ImageIcon> statusIcons = [:]
|
||||
|
||||
def newIssueDialogMVC
|
||||
Map projectPanelMVCs = [:]
|
||||
|
||||
Map projectIdMap = [:]
|
||||
|
||||
@Bindable Font issueDetailFont = new Font(Font.MONOSPACED, Font.PLAIN, 10)
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package com.jdbernard.pit.swing
|
||||
|
||||
import com.jdbernard.pit.Filter
|
||||
import com.jdbernard.pit.Issue
|
||||
import com.jdbernard.pit.Project
|
||||
import groovy.beans.Bindable
|
||||
|
||||
class ProjectPanelModel {
|
||||
|
||||
// other GUI components
|
||||
def mainMVC
|
||||
def newIssueDialogMVC
|
||||
|
||||
// data owned by this panel
|
||||
String id
|
||||
@Bindable Project rootProject
|
||||
@Bindable Project popupProject = null
|
||||
@Bindable Project selectedProject = null
|
||||
@Bindable Issue popupIssue = null
|
||||
|
||||
String issueCSS = ""
|
||||
|
||||
// cache the models
|
||||
def projectTableModels = [:]
|
||||
def issueCellRenderer
|
||||
|
||||
// local filter for projects and issues
|
||||
Filter filter
|
||||
}
|
Before Width: | Height: | Size: 733 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 587 B |
@ -1,22 +0,0 @@
|
||||
body {
|
||||
font-size: small;
|
||||
}
|
||||
h1 {
|
||||
font-size: medium;
|
||||
text-decoration: underline;
|
||||
}
|
||||
h2 {
|
||||
font-size: small;
|
||||
}
|
||||
h3 {
|
||||
font-size: small;
|
||||
font-style: italic;
|
||||
}
|
||||
h4 {
|
||||
font-size: small;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
table,th,td{
|
||||
border-style: solid;
|
||||
}
|
Before Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 670 B |
Before Width: | Height: | Size: 537 B |
Before Width: | Height: | Size: 306 B |
Before Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 822 B |
@ -1,10 +0,0 @@
|
||||
log4j.rootLogger=DEBUG,stdout,fileout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
|
||||
|
||||
log4j.appender.fileout=org.apache.log4j.FileAppender
|
||||
log4j.appender.fileout.file=pit-swing.log
|
||||
log4j.appender.fileout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.fileout.layout.ConversionPattern=%-5p %C %d{DATE}: %m%n
|
||||
log4j.appender.fileout.threshold=INFO
|
Before Width: | Height: | Size: 146 B |
Before Width: | Height: | Size: 559 B |
Before Width: | Height: | Size: 322 B |
Before Width: | Height: | Size: 221 B |
@ -1,495 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<xsl:stylesheet version="1.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns="http://www.w3.org/TR/xhtml1/strict">
|
||||
|
||||
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
|
||||
|
||||
<xsl:template match="/document">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="generator" content="JRST http://maven-site.nuiton.org/jrst" />
|
||||
<title><xsl:value-of select="title"/></title>
|
||||
</head>
|
||||
<body>
|
||||
<xsl:apply-templates/>
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="comment">
|
||||
<xsl:comment>
|
||||
<xsl:text> </xsl:text>
|
||||
<xsl:apply-templates/>
|
||||
<xsl:text> </xsl:text>
|
||||
</xsl:comment>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="title">
|
||||
<xsl:if test="name(..)='document'">
|
||||
<h1 class="mainTitle">
|
||||
<xsl:apply-templates/>
|
||||
</h1>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="not(name(..)='document')">
|
||||
<xsl:element name="h{count(ancestor::section) + 1}">
|
||||
<xsl:attribute name="class">title</xsl:attribute>
|
||||
<xsl:if test="@refid">
|
||||
<a class="toc-backref" href="#{@refid}" id="{../@id}"><xsl:apply-templates/></a>
|
||||
</xsl:if>
|
||||
<xsl:if test="not(@refid)">
|
||||
<xsl:apply-templates/>
|
||||
</xsl:if>
|
||||
</xsl:element>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="subtitle">
|
||||
<xsl:element name="h2">
|
||||
<xsl:apply-templates/>
|
||||
</xsl:element>
|
||||
</xsl:template>
|
||||
<!-- just eat it -->
|
||||
<xsl:template match="substitution_definition">
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="docinfo">
|
||||
<table class="docinfo" frame="void" rules="none">
|
||||
<col class="docinfo-name" />
|
||||
<col class="docinfo-content" />
|
||||
<tbody valign="top">
|
||||
<xsl:apply-templates/>
|
||||
</tbody>
|
||||
</table>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="organization|address|contact|version|revision|status|date|copyright">
|
||||
<tr>
|
||||
<th class="docinfo-name">
|
||||
<xsl:value-of select="name(.)"/> :
|
||||
</th>
|
||||
<td class="docinfo-content">
|
||||
<xsl:apply-templates/>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="author">
|
||||
<xsl:if test="not(../../authors)">
|
||||
<tr>
|
||||
<th class="docinfo-name">
|
||||
<xsl:value-of select="name(.)"/> :
|
||||
</th>
|
||||
<td class="docinfo-content">
|
||||
<xsl:apply-templates/>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
<xsl:if test="../../authors">
|
||||
<xsl:variable name="num" select="position()"/>
|
||||
<xsl:if test="$num=1">
|
||||
<tr>
|
||||
<th class="docinfo-name">
|
||||
<xsl:value-of select="authors"/>authors :
|
||||
</th>
|
||||
<td class="docinfo-content">
|
||||
<xsl:apply-templates/>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
<xsl:if test="$num>1">
|
||||
<tr>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
<td class="docinfo-content">
|
||||
<xsl:apply-templates/>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="transition">
|
||||
<hr/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="section">
|
||||
<a name="{@id}"></a>
|
||||
<xsl:apply-templates/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="list_item/paragraph[1] | definition_list_item/*/paragraph[1] | field/*/paragraph[1] | option/*/paragraph[1]">
|
||||
<!--XXX - Unclear how to handle multi-paragraph list items.
|
||||
| Certainly when they're single paragraphs, we don't want them
|
||||
| wrapped in a <P> tag. This seems to work okay.
|
||||
+-->
|
||||
<xsl:apply-templates/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="paragraph">
|
||||
<p><xsl:apply-templates/></p>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="reference">
|
||||
<xsl:if test="@refid">
|
||||
<a href="{@refuri}#{@refid}" id="{@id}"><xsl:apply-templates/></a>
|
||||
</xsl:if>
|
||||
<xsl:if test="not(@refid)">
|
||||
<a href="{@refuri}" id="{@id}"><xsl:apply-templates/></a>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="emphasis">
|
||||
<em><xsl:apply-templates/></em>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="strong">
|
||||
<b><xsl:apply-templates/></b>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="literal">
|
||||
<code><xsl:value-of select="text()"/></code>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="literal_block">
|
||||
<pre class="literal_block"><xsl:value-of select="text()"/></pre>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="bullet_list">
|
||||
<ul><xsl:apply-templates/></ul>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="enumerated_list">
|
||||
<ol>
|
||||
<xsl:choose>
|
||||
<xsl:when test="@enumtype='arabic'">
|
||||
<xsl:attribute name="type">1</xsl:attribute>
|
||||
</xsl:when>
|
||||
<xsl:when test="@enumtype='loweralpha'">
|
||||
<xsl:attribute name="type">a</xsl:attribute>
|
||||
</xsl:when>
|
||||
<xsl:when test="@enumtype='upperalpha'">
|
||||
<xsl:attribute name="type">A</xsl:attribute>
|
||||
</xsl:when>
|
||||
<xsl:when test="@enumtype='lowerroman'">
|
||||
<xsl:attribute name="type">i</xsl:attribute>
|
||||
</xsl:when>
|
||||
<xsl:when test="@enumtype='upperroman'">
|
||||
<xsl:attribute name="type">I</xsl:attribute>
|
||||
</xsl:when>
|
||||
</xsl:choose>
|
||||
<xsl:copy-of select="@start"/>
|
||||
<xsl:apply-templates/>
|
||||
</ol>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="list_item">
|
||||
<li><xsl:apply-templates/></li>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="field_list">
|
||||
<div class="field_list"><xsl:apply-templates/></div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="field">
|
||||
|
||||
<xsl:if test="not(../../docinfo)">
|
||||
<div class="field"><xsl:apply-templates/></div>
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="../../docinfo">
|
||||
<tr>
|
||||
<th class="docinfo-name">
|
||||
<xsl:value-of select="field_name/text()"/> :
|
||||
</th>
|
||||
<td>
|
||||
<xsl:apply-templates select="field_body/*"/>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:if>
|
||||
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="field_name">
|
||||
<span class="field_name"><xsl:apply-templates/></span>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="field_body">
|
||||
<span class="field_body"><xsl:apply-templates/></span>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="definition_list">
|
||||
<dl class="definition_list"><xsl:apply-templates/></dl>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="definition_list_item">
|
||||
<xsl:apply-templates/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="term">
|
||||
<dt class="term"><xsl:apply-templates/><xsl:call-template name="classifier"/></dt>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="classifier">
|
||||
<xsl:for-each select="../classifier">
|
||||
<span class="classifier"><xsl:apply-templates/></span>
|
||||
</xsl:for-each>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="classifier">
|
||||
<!-- do nothing -->
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="definition">
|
||||
<dd class="definition"><xsl:apply-templates/></dd>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="image">
|
||||
<xsl:choose>
|
||||
<xsl:when test="(@target) and (@align)">
|
||||
<div class="align-{@align}" align="{@align}">
|
||||
<a href="{@target}">
|
||||
<xsl:call-template name="img" />
|
||||
</a>
|
||||
</div>
|
||||
</xsl:when>
|
||||
<xsl:when test="@target">
|
||||
<a href="{@target}">
|
||||
<xsl:call-template name="img" />
|
||||
</a>
|
||||
</xsl:when>
|
||||
<xsl:when test="@align">
|
||||
<div class="align-{@align}" align="{@align}">
|
||||
<xsl:call-template name="img" />
|
||||
</div>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:call-template name="img" />
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="img">
|
||||
<xsl:element name="img">
|
||||
<xsl:attribute name="alt"><xsl:value-of select="@alt"/></xsl:attribute>
|
||||
<xsl:attribute name="src"><xsl:value-of select="@uri"/></xsl:attribute>
|
||||
<xsl:if test="@width"><xsl:attribute name="width"><xsl:value-of select="@width"/></xsl:attribute></xsl:if>
|
||||
<xsl:if test="@height"><xsl:attribute name="height"><xsl:value-of select="@height"/></xsl:attribute></xsl:if>
|
||||
<xsl:apply-templates/>
|
||||
</xsl:element>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="footer">
|
||||
<hr/>
|
||||
<p class="footer"><xsl:apply-templates/></p>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="header">
|
||||
<p class="header"><xsl:apply-templates/></p>
|
||||
<hr/>
|
||||
</xsl:template>
|
||||
|
||||
<!--
|
||||
| Table
|
||||
+-->
|
||||
<xsl:template match="table">
|
||||
<table border="1">
|
||||
<colgroup>
|
||||
<xsl:apply-templates select="tgroup/colspec"/>
|
||||
</colgroup>
|
||||
<xsl:apply-templates select="./tgroup/thead|./tgroup/tbody"/>
|
||||
</table>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="tgroup/colspec">
|
||||
<col width="{@colwidth}%"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="row">
|
||||
<tr><xsl:apply-templates/></tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="thead">
|
||||
<thead><xsl:apply-templates/></thead>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="thead/row/entry">
|
||||
<th>
|
||||
<xsl:if test="@morecols"><xsl:attribute name="colspan"><xsl:value-of select="@morecols+1"/></xsl:attribute></xsl:if>
|
||||
<xsl:if test="@morerows"><xsl:attribute name="rowspan"><xsl:value-of select="@morerows+1"/></xsl:attribute></xsl:if>
|
||||
<xsl:apply-templates/>
|
||||
</th>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="tbody">
|
||||
<tbody><xsl:apply-templates/></tbody>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="tbody/row/entry">
|
||||
<td>
|
||||
<xsl:if test="@morecols"><xsl:attribute name="colspan"><xsl:value-of select="@morecols+1"/></xsl:attribute></xsl:if>
|
||||
<xsl:if test="@morerows"><xsl:attribute name="rowspan"><xsl:value-of select="@morerows+1"/></xsl:attribute></xsl:if>
|
||||
<xsl:apply-templates/>
|
||||
</td>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="admonition">
|
||||
<div class="admonition">
|
||||
<div class="{@class}">
|
||||
<p class="{title}">
|
||||
<xsl:apply-templates select="./title"/>
|
||||
</p>
|
||||
<p class="body">
|
||||
<xsl:apply-templates select="child::*[position()>1]"/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="attention|caution|danger|error|hint|important|note|tip|warning">
|
||||
<div class="{name(.)}">
|
||||
<p class="title"><xsl:value-of select="name(.)"/> :</p>
|
||||
<p class="body">
|
||||
<xsl:apply-templates/>
|
||||
</p>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="block_quote">
|
||||
|
||||
<blockquote>
|
||||
<xsl:if test="./attribution">
|
||||
<p><xsl:apply-templates select="child::*[position()=1]"/></p>
|
||||
<p class="attribution">
|
||||
<xsl:apply-templates select="./attribution"/>
|
||||
</p>
|
||||
</xsl:if>
|
||||
<xsl:if test="not(./attribution)">
|
||||
<xsl:apply-templates select="child::*"/>
|
||||
</xsl:if>
|
||||
</blockquote>
|
||||
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="doctest_block">
|
||||
<pre class="doctest_block">
|
||||
<xsl:apply-templates/>
|
||||
</pre>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="line_block">
|
||||
<div class="line_block">
|
||||
<xsl:apply-templates/>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="line">
|
||||
<div class="line">
|
||||
<xsl:apply-templates/>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="sidebar">
|
||||
<div class="sidebar">
|
||||
<p class="title">
|
||||
<xsl:apply-templates select="./title"/>
|
||||
</p>
|
||||
<xsl:if test="./subtitle">
|
||||
<p class="subtitle">
|
||||
<xsl:apply-templates select="./subtitle"/>
|
||||
</p>
|
||||
</xsl:if>
|
||||
<xsl:choose>
|
||||
<xsl:when test="./subtitle">
|
||||
<xsl:apply-templates select="child::*[position()>2]"/>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:apply-templates select="child::*[position()>1]"/>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="topic">
|
||||
<div class="topic">
|
||||
<p class="title">
|
||||
<xsl:apply-templates select="./title"/>
|
||||
</p>
|
||||
<xsl:apply-templates select="child::*[position()>1]"/>
|
||||
</div>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="option_list">
|
||||
<table class="option_list">
|
||||
<col class="option" />
|
||||
<col class="description" />
|
||||
<tbody valign="top">
|
||||
<xsl:apply-templates/>
|
||||
</tbody>
|
||||
</table>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="option_list_item">
|
||||
<tr>
|
||||
<td class="option-group">
|
||||
<kbd>
|
||||
<xsl:apply-templates select="./option_group/option"/>
|
||||
</kbd>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:apply-templates select="./description"/>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="option">
|
||||
<span class="option">
|
||||
<xsl:value-of select="option_string/text()"/>
|
||||
<xsl:value-of select="./option_argument/@delimiter"/>
|
||||
<xsl:apply-templates select="./option_argument"/>
|
||||
</span>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="option_argument">
|
||||
<var>
|
||||
<xsl:value-of select="text()"/>,
|
||||
</var>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="footnote">
|
||||
<table class="footnote" frame="void" id="{@id}" rules="none">
|
||||
<colgroup>
|
||||
<col class="label"/>
|
||||
<col/>
|
||||
</colgroup>
|
||||
<tbody valign="top">
|
||||
<tr>
|
||||
<td class="label">
|
||||
<a class="backref" href="#{@backrefs}" name="{id}">
|
||||
[<xsl:value-of select="label"/>]
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!--
|
||||
| <xsl:value-of select="child::*[position()>1]"/>
|
||||
+-->
|
||||
<xsl:apply-templates select="child::*[position()>1]"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="footnote_reference">
|
||||
<a class="footnote_reference" href="#{@refid}" id="{@id}" name="{@id}">
|
||||
[<xsl:value-of select="text()"/>]
|
||||
</a>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
Before Width: | Height: | Size: 688 B |