Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
ae0d782a5b | |||
846d1edc74 | |||
fd94f0e41a | |||
6f58a83ad4 | |||
47cf3cf0a4 | |||
e00e2e296e | |||
c26ba17dbb | |||
447e74f956 | |||
66b68160e5 | |||
f86316c68f | |||
5ff4665a07 | |||
faacfd859a |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
|
release/
|
||||||
*.sw*
|
*.sw*
|
||||||
*/build/
|
*/build/
|
||||||
|
12
build.xml
12
build.xml
@ -9,7 +9,7 @@
|
|||||||
<target name="clean">
|
<target name="clean">
|
||||||
<ant dir="libpit" target="clean" inheritAll="false"/>
|
<ant dir="libpit" target="clean" inheritAll="false"/>
|
||||||
<ant dir="pit-cli" target="clean" inheritAll="false"/>
|
<ant dir="pit-cli" target="clean" inheritAll="false"/>
|
||||||
<ant dir="pit-swing" target="clean" inheritAll="false"/>
|
<!-- <ant dir="pit-swing" target="clean" inheritAll="false"/> -->
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="libpit">
|
<target name="libpit">
|
||||||
@ -21,15 +21,17 @@
|
|||||||
<ant dir="pit-cli" target="release" inheritAll="false"/>
|
<ant dir="pit-cli" target="release" inheritAll="false"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="pit-swing" depends="libpit">
|
<!-- <target name="pit-swing" depends="libpit">
|
||||||
<copy file="${libpit.jar}" todir="pit-swing/lib"/>
|
<copy file="${libpit.jar}" todir="pit-swing/lib"/>
|
||||||
<ant dir="pit-swing" fork="true" target="package" inheritAll="false"/>
|
<ant dir="pit-swing" fork="true" target="package" inheritAll="false"/>
|
||||||
</target>
|
</target> -->
|
||||||
|
|
||||||
<target name="package" depends="libpit,pit-cli,pit-swing">
|
<!-- <target name="package" depends="libpit,pit-cli,pit-swing"> -->
|
||||||
|
<target name="package" depends="libpit,pit-cli">
|
||||||
<mkdir dir="release/lib"/>
|
<mkdir dir="release/lib"/>
|
||||||
<copy file="pit-cli/release/pit-clii-${application.version}.jar" todir="release"/>
|
<copy file="pit-cli/release/pit-clii-${application.version}.jar" todir="release"/>
|
||||||
<copy file="pit-swing/dist/jar/pit-swing.jar" tofile="release/pit-swing-${application.version}.jar"/>
|
<!-- <copy file="pit-swing/dist/jar/pit-swing.jar"
|
||||||
|
tofile="release/pit-swing-${application.version}.jar"/> -->
|
||||||
<copy file="libpit/release/pit-${application.version}.jar" todir="release/lib"/>
|
<copy file="libpit/release/pit-${application.version}.jar" todir="release/lib"/>
|
||||||
</target>
|
</target>
|
||||||
</project>
|
</project>
|
||||||
|
203
jdb-build-1.6.xml
Normal file
203
jdb-build-1.6.xml
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<?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="-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>
|
106
libpit/build.xml
106
libpit/build.xml
@ -1,30 +1,8 @@
|
|||||||
<project name="Personal Issue Tracker" default="release">
|
<project name="Personal Issue Tracker" default="release">
|
||||||
<property file="../version.properties"/>
|
|
||||||
<property file="project.properties"/>
|
<property file="project.properties"/>
|
||||||
<property environment="env"/>
|
|
||||||
|
|
||||||
<path id="groovy.libs">
|
<import file="../jdb-build-1.6.xml"/>
|
||||||
<fileset dir="${env.GROOVY_HOME}/lib">
|
|
||||||
<include name="**/*.jar"/>
|
|
||||||
</fileset>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<path id="groovyc.classpath">
|
|
||||||
<path refid="groovy.libs"/>
|
|
||||||
<fileset dir="${lib.dir}">
|
|
||||||
<include name="**/*.jar"/>
|
|
||||||
</fileset>
|
|
||||||
<pathelement path="${build.dir}/classes"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<path id="test.classpath">
|
|
||||||
<path refid="groovyc.classpath"/>
|
|
||||||
<pathelement path="${build.dir}/tests"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<taskdef name="groovyc"
|
|
||||||
classname="org.codehaus.groovy.ant.Groovyc"
|
|
||||||
classpathref="groovy.libs"/>
|
|
||||||
|
|
||||||
<target name="init">
|
<target name="init">
|
||||||
<fail
|
<fail
|
||||||
@ -33,79 +11,13 @@
|
|||||||
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
|
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="increment-build-number" depends="init">
|
|
||||||
<!-- Check to see if the application version has changed.
|
|
||||||
If it has, reset the build number to 0 -->
|
|
||||||
<condition property="build.number.final"
|
|
||||||
value="${build.number}"
|
|
||||||
else="0" >
|
|
||||||
<equals
|
|
||||||
arg1="${application.version}"
|
|
||||||
arg2="${expected.application.version}"/>
|
|
||||||
</condition>
|
|
||||||
|
|
||||||
<echo message="Version: ${application.version}"/>
|
|
||||||
<echo message="Build number: ${build.number.final}"/>
|
|
||||||
|
|
||||||
<!-- Write the actual application version and build number -->
|
|
||||||
<propertyfile file="project.properties">
|
|
||||||
<entry key="build.number" value="${build.number.final}"/>
|
|
||||||
<entry
|
|
||||||
key="expected.application.version"
|
|
||||||
value="${application.version}"/>
|
|
||||||
</propertyfile>
|
|
||||||
|
|
||||||
<!-- increment build number -->
|
|
||||||
<propertyfile file="project.properties">
|
|
||||||
<entry key="build.number" operation="+" type="int" default="0"/>
|
|
||||||
</propertyfile>
|
|
||||||
<property file="project.properties"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="clean">
|
|
||||||
<delete dir="${build.dir}"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="compile" depends="init,increment-build-number">
|
|
||||||
<mkdir dir="${build.dir}/classes"/>
|
|
||||||
<groovyc
|
|
||||||
srcdir="${src.dir}"
|
|
||||||
destdir="${build.dir}/classes"
|
|
||||||
classpathref="groovyc.classpath"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="compile-tests" depends="init,compile">
|
|
||||||
<mkdir dir="${build.dir}/tests"/>
|
|
||||||
<groovyc
|
|
||||||
srcdir="${test.dir}"
|
|
||||||
destdir="${build.dir}/tests"
|
|
||||||
classpathref="groovyc.classpath"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="test" depends="compile-tests">
|
|
||||||
<junit fork="yes" haltonfailure="yes">
|
|
||||||
<classpath refid="test.classpath"/>
|
|
||||||
<formatter type="brief" usefile="false" />
|
|
||||||
<batchtest>
|
|
||||||
<fileset dir="${build.dir}/tests">
|
|
||||||
<include name="**/*Test.class"/>
|
|
||||||
</fileset>
|
|
||||||
</batchtest>
|
|
||||||
</junit>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="build" depends="compile,test">
|
|
||||||
<mkdir dir="${build.dir}/jar"/>
|
|
||||||
<jar
|
|
||||||
destfile="${build.dir}/jar/pit-${application.version}.${build.number.final}.jar"
|
|
||||||
basedir="${build.dir}/classes"
|
|
||||||
compress="on"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="release" depends="build">
|
<target name="release" depends="build">
|
||||||
<delete dir="${release.dir}"/>
|
<mkdir dir="${release.dir}/lib"/>
|
||||||
<mkdir dir="${release.dir}"/>
|
<copy file="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||||
<copy file="${build.dir}/jar/pit-${application.version}.${build.number.final}.jar"
|
tofile="${release.dir}/${name}-${version}.jar"/>
|
||||||
tofile="${release.dir}/${release.jar}"/>
|
<copy todir="${release.dir}/lib">
|
||||||
|
<fileset dir="${build.dir}/lib/runtime/jar"/>
|
||||||
|
</copy>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
7
libpit/doc/grammar.txt
Normal file
7
libpit/doc/grammar.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
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
|
BIN
libpit/lib/compile/jar/joda-time-2.0.jar
Normal file
BIN
libpit/lib/compile/jar/joda-time-2.0.jar
Normal file
Binary file not shown.
BIN
libpit/lib/compile/jar/parboiled-core-1.0.2.jar
Normal file
BIN
libpit/lib/compile/jar/parboiled-core-1.0.2.jar
Normal file
Binary file not shown.
BIN
libpit/lib/compile/jar/parboiled-java-1.0.2.jar
Normal file
BIN
libpit/lib/compile/jar/parboiled-java-1.0.2.jar
Normal file
Binary file not shown.
BIN
libpit/lib/compile/jar/slf4j-api-1.6.1.jar
Normal file
BIN
libpit/lib/compile/jar/slf4j-api-1.6.1.jar
Normal file
Binary file not shown.
BIN
libpit/lib/runtime/jar/joda-time-2.0.jar
Normal file
BIN
libpit/lib/runtime/jar/joda-time-2.0.jar
Normal file
Binary file not shown.
BIN
libpit/lib/runtime/jar/logback-classic-0.9.26.jar
Normal file
BIN
libpit/lib/runtime/jar/logback-classic-0.9.26.jar
Normal file
Binary file not shown.
BIN
libpit/lib/runtime/jar/logback-core-0.9.26.jar
Normal file
BIN
libpit/lib/runtime/jar/logback-core-0.9.26.jar
Normal file
Binary file not shown.
BIN
libpit/lib/runtime/jar/parboiled-core-1.0.2.jar
Normal file
BIN
libpit/lib/runtime/jar/parboiled-core-1.0.2.jar
Normal file
Binary file not shown.
BIN
libpit/lib/runtime/jar/parboiled-java-1.0.2.jar
Normal file
BIN
libpit/lib/runtime/jar/parboiled-java-1.0.2.jar
Normal file
Binary file not shown.
BIN
libpit/lib/runtime/jar/slf4j-api-1.6.1.jar
Normal file
BIN
libpit/lib/runtime/jar/slf4j-api-1.6.1.jar
Normal file
Binary file not shown.
@ -1,11 +1,13 @@
|
|||||||
#Fri, 21 Oct 2011 16:18:33 -0500
|
#Thu, 08 Dec 2011 14:35:45 -0600
|
||||||
#Sat Apr 24 17:08:00 CDT 2010
|
#Sat Apr 24 17:08:00 CDT 2010
|
||||||
build.dir=build
|
build.dir=build
|
||||||
src.dir=src
|
src.dir=src
|
||||||
lib.shared.dir=../shared-libs
|
lib.shared.dir=../shared-libs
|
||||||
test.dir=test
|
test.dir=test
|
||||||
build.number=11
|
build.number=10
|
||||||
expected.application.version=2.6.0
|
version=3.2.0
|
||||||
|
name=libpit
|
||||||
lib.dir=lib
|
lib.dir=lib
|
||||||
|
lib.local=true
|
||||||
release.dir=release
|
release.dir=release
|
||||||
release.jar=pit-${application.version}.jar
|
release.jar=pit-${application.version}.jar
|
||||||
|
Binary file not shown.
13
libpit/resources/main/test.groovy
Normal file
13
libpit/resources/main/test.groovy
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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()
|
@ -1,33 +0,0 @@
|
|||||||
package com.jdbernard.pit
|
|
||||||
|
|
||||||
class Filter {
|
|
||||||
|
|
||||||
List<Category> categories = null
|
|
||||||
List<Status> status = null
|
|
||||||
List<String> projects = null
|
|
||||||
List<String> ids = null
|
|
||||||
int priority = 9
|
|
||||||
boolean acceptProjects = true
|
|
||||||
Closure issueSorter = defaultIssueSorter
|
|
||||||
Closure projectSorter = defaultProjectSorter
|
|
||||||
|
|
||||||
public static Closure defaultIssueSorter = { it.id.toInteger() }
|
|
||||||
public static Closure defaultProjectSorter = { it.name }
|
|
||||||
|
|
||||||
public boolean accept(Issue i) {
|
|
||||||
return (i.priority <= priority &&
|
|
||||||
(!categories || categories.contains(i.category)) &&
|
|
||||||
(!status || status.contains(i.status)) &&
|
|
||||||
(!ids || ids.contains(i.id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean accept(Project p) {
|
|
||||||
return (acceptProjects &&
|
|
||||||
(!projects || projects.contains(p.name)))
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean accept(String name) {
|
|
||||||
return (acceptProjects &&
|
|
||||||
(!projects || projects.contains(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) {
|
|
||||||
Status retVal = null
|
|
||||||
for(status in Status.values()) {
|
|
||||||
if (status.symbol.equalsIgnoreCase(str) ||
|
|
||||||
status.name().startsWith(str.toUpperCase())) {
|
|
||||||
|
|
||||||
if (retVal != null)
|
|
||||||
throw new IllegalArgumentException("Request string is" +
|
|
||||||
" ambigous, '${str}' could represent ${retVal} or " +
|
|
||||||
"${status}, possibly others.")
|
|
||||||
|
|
||||||
retVal = status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retVal == null)
|
|
||||||
throw new IllegalArgumentException("No status matches '${str}'")
|
|
||||||
|
|
||||||
return retVal
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
def words = name().split("_")
|
|
||||||
String result = ""
|
|
||||||
words.each { result += "${it[0]}${it[1..-1].toLowerCase()} " }
|
|
||||||
return result[0..-2]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
package com.jdbernard.pit.file
|
|
||||||
|
|
||||||
import com.jdbernard.pit.*
|
|
||||||
import java.lang.IllegalArgumentException as IAE
|
|
||||||
|
|
||||||
public class FileIssue extends Issue {
|
|
||||||
|
|
||||||
protected File source
|
|
||||||
public static final String fileExp = /(\d+)([bft])([ajnsv])(\d).*/
|
|
||||||
|
|
||||||
public FileIssue(File file) {
|
|
||||||
|
|
||||||
super('REPLACE_ME')
|
|
||||||
|
|
||||||
def matcher = file.name =~ fileExp
|
|
||||||
if (!matcher)
|
|
||||||
throw new IllegalArgumentException("${file} " +
|
|
||||||
"is not a valid Issue file.")
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
this.source = file
|
|
||||||
|
|
||||||
super.@text = file.text
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCategory(Category c) throws IOException {
|
|
||||||
boolean renamed
|
|
||||||
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
|
|
||||||
makeFilename(id, c, status, priority)))
|
|
||||||
|
|
||||||
if (!renamed)
|
|
||||||
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).")
|
|
||||||
else super.setCategory(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(Status s) throws IOException {
|
|
||||||
boolean renamed
|
|
||||||
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
|
|
||||||
makeFilename(id, category, s, priority)))
|
|
||||||
|
|
||||||
if (!renamed)
|
|
||||||
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).")
|
|
||||||
else super.setStatus(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPriority(int p) throws IOException {
|
|
||||||
boolean renamed
|
|
||||||
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
|
|
||||||
makeFilename(id, category, status, p)))
|
|
||||||
|
|
||||||
if (!renamed)
|
|
||||||
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).")
|
|
||||||
else super.setPriority(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFilename() {
|
|
||||||
return makeFilename(id, category, status, priority)
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setText(String text) throws IOException {
|
|
||||||
try { source.write(text) }
|
|
||||||
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).")
|
|
||||||
}
|
|
||||||
|
|
||||||
super.setText(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,81 @@
|
|||||||
|
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() }
|
||||||
|
}
|
42
libpit/src/main/com/jdbernard/pit/Filter.groovy
Executable file
42
libpit/src/main/com/jdbernard/pit/Filter.groovy
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
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)))
|
||||||
|
}
|
||||||
|
}
|
@ -2,25 +2,30 @@ package com.jdbernard.pit
|
|||||||
|
|
||||||
import java.lang.IllegalArgumentException as IAE
|
import java.lang.IllegalArgumentException as IAE
|
||||||
|
|
||||||
public abstract class Issue {
|
public class Issue {
|
||||||
|
|
||||||
protected String id
|
protected String id
|
||||||
protected Category category
|
protected Category category
|
||||||
protected Status status
|
protected Status status
|
||||||
protected int priority
|
protected int priority
|
||||||
protected String text
|
protected String text
|
||||||
protected Date deliveryDate
|
protected String title
|
||||||
protected Date creationDate
|
|
||||||
|
|
||||||
Issue(String id, Category c = Category.TASK, Status s = Status.NEW,
|
Map extendedProperties = [:]
|
||||||
int p = 9) {
|
|
||||||
this.id = id
|
Issue(Map props) {
|
||||||
this.category = c
|
this.id = props.id
|
||||||
this.status = s
|
this.category = props.category ?: Category.TASK
|
||||||
this.priority = p
|
this.status = props.status ?: Status.NEW
|
||||||
this.creationDate = new Date()
|
this.priority = props.priority ?: 5
|
||||||
this.deliveryDate = null
|
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 String getId() { return id; }
|
||||||
|
|
||||||
@ -48,19 +53,18 @@ public abstract class Issue {
|
|||||||
priority = Math.min(9, Math.max(0, p))
|
priority = Math.min(9, Math.max(0, p))
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() { return text.readLines()[0] }
|
public String getTitle() { return title }
|
||||||
|
|
||||||
|
public void setTitle(String t) throws IOException { title = t }
|
||||||
|
|
||||||
public String getText() { return text }
|
public String getText() { return text }
|
||||||
|
|
||||||
public void setText(String t) throws IOException { text = t }
|
public void setText(String t) throws IOException { text = t }
|
||||||
|
|
||||||
public boolean hasDelivery() { return deliveryDate == null }
|
public def propertyMissing(String name) { extendedProperties[name] }
|
||||||
|
|
||||||
public Date getCreationDate() { return creationDate }
|
public def propertyMissing(String name, def value) {
|
||||||
|
extendedProperties[name] = value }
|
||||||
public Date getDeliveryDate() { return deliveryDate }
|
|
||||||
|
|
||||||
public void setDeliveryDate(Date dd) { deliveryDate = dd }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
@ -10,30 +10,32 @@ public abstract class Project {
|
|||||||
|
|
||||||
public void eachIssue(Filter filter = null, Closure c) {
|
public void eachIssue(Filter filter = null, Closure c) {
|
||||||
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
||||||
for (i in issues.values().sort(sorter))
|
for (i in sort(issues.values(), sorter))
|
||||||
if (!filter || filter.accept(i))
|
if (!filter || filter.accept(i))
|
||||||
c.call(i)
|
c.call(i) }
|
||||||
}
|
|
||||||
|
|
||||||
public void eachProject(Filter filter = null, Closure c) {
|
public void eachProject(Filter filter = null, Closure c) {
|
||||||
def sorter = filter?.projectSorter ?: Filter.defaultProjectSorter
|
def sorter = filter?.projectSorter ?: Filter.defaultProjectSorter
|
||||||
for (p in projects.values().sort(sorter))
|
for (p in sort(projects.values(), sorter))
|
||||||
if (!filter || filter.accept(p))
|
if (!filter || filter.accept(p))
|
||||||
c.call(p)
|
c.call(p) }
|
||||||
}
|
|
||||||
|
|
||||||
// walk every issue and project in this project recursively and execute the
|
// walk every issue and project in this project recursively and execute the
|
||||||
// given closure on each issue that meets the filter criteria
|
// given closure on each issue that meets the filter criteria
|
||||||
public void walkProject(Filter filter, Closure c) {
|
public void walkProject(Filter filter, Closure c) {
|
||||||
this.eachIssue(filter, c)
|
this.eachIssue(filter, c)
|
||||||
this.eachProject(filter) { p -> p.walkProject(filter, c) }
|
this.eachProject(filter) { p -> p.walkProject(filter, c) } }
|
||||||
}
|
|
||||||
|
|
||||||
// This get all issues, including subissues
|
// This get all issues, including subissues
|
||||||
public List getAllIssues(Filter filter = null) {
|
public List getAllIssues(Filter filter = null) {
|
||||||
List result = this.issues.findAll { filter.accept(it) }
|
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
|
||||||
this.eachProject(filter) { p -> result += p.getAllIssues(filter) }
|
|
||||||
}
|
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 void setName(String name) { this.name = name }
|
||||||
|
|
||||||
@ -49,4 +51,10 @@ public abstract class Project {
|
|||||||
public abstract boolean deleteIssue(Issue issue)
|
public abstract boolean deleteIssue(Issue issue)
|
||||||
|
|
||||||
public abstract boolean deleteProject(Project project)
|
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) }}}
|
||||||
}
|
}
|
41
libpit/src/main/com/jdbernard/pit/Status.groovy
Executable file
41
libpit/src/main/com/jdbernard/pit/Status.groovy
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
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]
|
||||||
|
}
|
||||||
|
}
|
183
libpit/src/main/com/jdbernard/pit/file/FileIssue.groovy
Executable file
183
libpit/src/main/com/jdbernard/pit/file/FileIssue.groovy
Executable file
@ -0,0 +1,183 @@
|
|||||||
|
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 {
|
||||||
|
boolean renamed
|
||||||
|
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
|
||||||
|
makeFilename(id, c, status, priority)))
|
||||||
|
|
||||||
|
if (!renamed)
|
||||||
|
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).")
|
||||||
|
else super.setCategory(c) }
|
||||||
|
|
||||||
|
public void setStatus(Status s) throws IOException {
|
||||||
|
boolean renamed
|
||||||
|
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
|
||||||
|
makeFilename(id, category, s, priority)))
|
||||||
|
|
||||||
|
if (!renamed)
|
||||||
|
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).")
|
||||||
|
else super.setStatus(s) }
|
||||||
|
|
||||||
|
public void setPriority(int p) throws IOException {
|
||||||
|
boolean renamed
|
||||||
|
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
|
||||||
|
makeFilename(id, category, status, p)))
|
||||||
|
|
||||||
|
if (!renamed)
|
||||||
|
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).")
|
||||||
|
else super.setPriority(p) }
|
||||||
|
|
||||||
|
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() }
|
||||||
|
|
||||||
|
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() > maxKeyLen) { 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).") } }
|
||||||
|
|
||||||
|
}
|
@ -23,56 +23,65 @@ class FileProject extends Project {
|
|||||||
child.isHidden()) return // just an issue folder
|
child.isHidden()) return // just an issue folder
|
||||||
|
|
||||||
// otherwise build and add to list
|
// otherwise build and add to list
|
||||||
projects[(child.name)] = new FileProject(child)
|
projects[(child.name)] = new FileProject(child) }
|
||||||
} else if (child.isFile() &&
|
else if (child.isFile() &&
|
||||||
FileIssue.isValidFilename(child.name)) {
|
FileIssue.isValidFilename(child.name)) {
|
||||||
def issue
|
def issue
|
||||||
|
|
||||||
// if exception, then not an issue
|
// if exception, then not an issue
|
||||||
try { issue = new FileIssue(child) } catch (all) { return }
|
try { issue = new FileIssue(child) } catch (all) { return }
|
||||||
|
|
||||||
issues[(issue.id)] = issue
|
issues[(issue.id)] = issue } }}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
super.setName(name)
|
super.setName(name)
|
||||||
source.renameTo(new File(source.canonicalFile.parentFile, name))
|
source.renameTo(new File(source.canonicalFile.parentFile, name)) }
|
||||||
}
|
|
||||||
|
|
||||||
public FileIssue createNewIssue(Map options) {
|
public FileIssue createNewIssue(Map options) {
|
||||||
|
Issue issue
|
||||||
|
File issueFile
|
||||||
|
|
||||||
if (!options) options = [:]
|
if (!options) options = [:]
|
||||||
if (!options.category) options.category = Category.TASK
|
|
||||||
if (!options.status) options.status = Status.NEW
|
// We want some different defaults for issues due to the parser being
|
||||||
if (!options.priority) options.priority = 5
|
// unable to handle empty title or text.
|
||||||
if (!options.text) options.text = "Default issue title.\n" +
|
if (!options.title) options.title = "Default issue title."
|
||||||
"====================\n"
|
if (!options.text) options.text = "Describe the issue here."
|
||||||
String id
|
|
||||||
if (issues.size() == 0) id = '0000'
|
// We are also going to find the next id based on the issues already in the
|
||||||
|
// project.
|
||||||
|
if (issues.size() == 0) options.id = '0000'
|
||||||
else {
|
else {
|
||||||
id = (issues.values().max { it.id.toInteger() }).id
|
def lastId = (issues.values().max { it.id.toInteger() }).id
|
||||||
id = (id.toInteger() + 1).toString().padLeft(id.length(), '0')
|
options.id = (lastId.toInteger() + 1).toString().padLeft(
|
||||||
}
|
lastId.length(), '0') }
|
||||||
|
|
||||||
def issueFile = new File(source, FileIssue.makeFilename(id,
|
// Create an Issue object from the options (we will discard it later).
|
||||||
options.category, options.status, options.priority))
|
issue = new Issue(options)
|
||||||
|
|
||||||
|
// Create the filename and File object based on the options given.
|
||||||
|
issueFile = new File(source, FileIssue.makeFilename(
|
||||||
|
issue.id, issue.category, issue.status, issue.priority))
|
||||||
|
|
||||||
|
// Create the actual file on the system
|
||||||
issueFile.createNewFile()
|
issueFile.createNewFile()
|
||||||
issueFile.write(options.text)
|
|
||||||
|
|
||||||
def issue = new FileIssue(issueFile)
|
// Write the issue to the file created.
|
||||||
|
issueFile.write(FileIssue.formatIssue(issue))
|
||||||
|
|
||||||
|
// Read that new file back in as a FileIssue
|
||||||
|
issue = new FileIssue(issueFile)
|
||||||
|
|
||||||
|
// Add the issue to our collection.
|
||||||
issues[(issue.id)] = issue
|
issues[(issue.id)] = issue
|
||||||
|
|
||||||
return issue
|
return issue }
|
||||||
}
|
|
||||||
|
|
||||||
public FileProject createNewProject(String name) {
|
public FileProject createNewProject(String name) {
|
||||||
def newDir = new File(source, name)
|
def newDir = new File(source, name)
|
||||||
newDir.mkdirs()
|
newDir.mkdirs()
|
||||||
|
|
||||||
return new FileProject(newDir)
|
return new FileProject(newDir) }
|
||||||
}
|
|
||||||
|
|
||||||
public boolean deleteIssue(Issue issue) {
|
public boolean deleteIssue(Issue issue) {
|
||||||
if (!issues[(issue.id)]) return false
|
if (!issues[(issue.id)]) return false
|
||||||
@ -81,8 +90,7 @@ class FileProject extends Project {
|
|||||||
if (issue instanceof FileIssue)
|
if (issue instanceof FileIssue)
|
||||||
return issue.deleteFile()
|
return issue.deleteFile()
|
||||||
|
|
||||||
else return true
|
else return true }
|
||||||
}
|
|
||||||
|
|
||||||
public boolean deleteProject(Project project) {
|
public boolean deleteProject(Project project) {
|
||||||
if (!projects[(project.name)]) return false
|
if (!projects[(project.name)]) return false
|
||||||
@ -91,8 +99,7 @@ class FileProject extends Project {
|
|||||||
if (project instanceof FileProject)
|
if (project instanceof FileProject)
|
||||||
return project.source.delete()
|
return project.source.delete()
|
||||||
|
|
||||||
return true
|
return true }
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return name }
|
public String toString() { return name }
|
67
libpit/src/main/com/jdbernard/pit/file/IssuePegParser.java
Normal file
67
libpit/src/main/com/jdbernard/pit/file/IssuePegParser.java
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
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()),
|
||||||
|
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(); }
|
||||||
|
}
|
@ -18,7 +18,8 @@ public class XmlIssue extends Issue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
XmlIssue(String id, Category c = Category.TASK, Status s = Status.NEW,
|
XmlIssue(String id, Category c = Category.TASK, Status s = Status.NEW,
|
||||||
int p = 9, String text, XmlRepository repository, XmlProject project) {
|
int p = 9, String title, String text, XmlRepository repository,
|
||||||
|
XmlProject project) {
|
||||||
super(id, c, s, p)
|
super(id, c, s, p)
|
||||||
|
|
||||||
this.project = project
|
this.project = project
|
||||||
@ -26,9 +27,10 @@ public class XmlIssue extends Issue {
|
|||||||
|
|
||||||
// Node constructor adds the node to the parent node
|
// Node constructor adds the node to the parent node
|
||||||
issueNode = new Node(project.projectNode, "Issue",
|
issueNode = new Node(project.projectNode, "Issue",
|
||||||
[id: id, category: c, status: s, priority: p])
|
[id: id, category: c, status: s, priority: p, title: title])
|
||||||
|
|
||||||
this.text = text
|
super.@title = title
|
||||||
|
super.@text = text
|
||||||
issueNode.value = text
|
issueNode.value = text
|
||||||
|
|
||||||
repository.persist()
|
repository.persist()
|
||||||
@ -62,4 +64,11 @@ public class XmlIssue extends Issue {
|
|||||||
repository.persist()
|
repository.persist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTitle(String t) {
|
||||||
|
super.setTitle(t)
|
||||||
|
|
||||||
|
issueNode.@title = t
|
||||||
|
repository.persist()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ package com.jdbernard.pit
|
|||||||
|
|
||||||
public class MockIssue extends Issue {
|
public class MockIssue extends Issue {
|
||||||
public MockIssue(String id, Category c, Status s, int p) {
|
public MockIssue(String id, Category c, Status s, int p) {
|
||||||
super (id, c, s, p)
|
super ([id: id, category: c, status: s, priority: p])
|
||||||
}
|
}
|
||||||
public boolean delete() { return true }
|
public boolean delete() { return true }
|
||||||
}
|
}
|
@ -171,9 +171,7 @@ class FileIssueTest {
|
|||||||
assertEquals issue.status , Status.NEW
|
assertEquals issue.status , Status.NEW
|
||||||
assertEquals issue.priority , 1
|
assertEquals issue.priority , 1
|
||||||
assertEquals issue.title , "Add the killer feature to the killer app."
|
assertEquals issue.title , "Add the killer feature to the killer app."
|
||||||
assertEquals issue.text , "Add the killer feature to the killer app.\n" +
|
assertEquals issue.text , "Make our killer app shine!."
|
||||||
"=========================================\n\n" +
|
|
||||||
"Make our killer app shine!."
|
|
||||||
assertEquals issue.source , issueFile
|
assertEquals issue.source , issueFile
|
||||||
}
|
}
|
||||||
|
|
3
pit-cli/.gitignore
vendored
3
pit-cli/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
release/
|
|
||||||
build/
|
|
||||||
*.sw?
|
|
@ -1,40 +1,8 @@
|
|||||||
<project name="Personal Issue Tracker CLI">
|
<project name="Personal Issue Tracker CLI">
|
||||||
<property file="../version.properties"/>
|
|
||||||
<property file="project.properties"/>
|
<property file="project.properties"/>
|
||||||
<property environment="env" />
|
|
||||||
|
|
||||||
<path id="groovy.libs">
|
<import file="../jdb-build-1.6.xml"/>
|
||||||
<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="project.libs">
|
|
||||||
<fileset dir="${lib.dir}">
|
|
||||||
<include name="**/*.jar"/>
|
|
||||||
</fileset>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<path id="groovyc.classpath">
|
|
||||||
<path refid="groovy.libs"/>
|
|
||||||
<path refid="project.libs"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<path id="package.jars">
|
|
||||||
<path refid="groovy.embeddable"/>
|
|
||||||
<path refid="project.libs"/>
|
|
||||||
</path>
|
|
||||||
|
|
||||||
<taskdef name="groovyc"
|
|
||||||
classname="org.codehaus.groovy.ant.Groovyc"
|
|
||||||
classpathref="groovy.libs"/>
|
|
||||||
|
|
||||||
<target name="init">
|
<target name="init">
|
||||||
<fail
|
<fail
|
||||||
@ -42,77 +10,28 @@
|
|||||||
message="GROOVY_HOME environment variable is not set."/>
|
message="GROOVY_HOME environment variable is not set."/>
|
||||||
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
|
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
|
||||||
|
|
||||||
<fail message="Could not find PIT ${application.version} library.">
|
<fail message="Could not find PIT ${version} library.">
|
||||||
<condition>
|
<condition>
|
||||||
<not>
|
<not>
|
||||||
<available
|
<available
|
||||||
file="${lib.dir}/pit-${application.version}.jar"/>
|
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
|
||||||
</not>
|
</not>
|
||||||
</condition>
|
</condition>
|
||||||
</fail>
|
</fail>
|
||||||
<echo message="PIT library found at ${lib.dir}/pit-${application.version}.jar"/>
|
|
||||||
|
|
||||||
<fail message="The PIT project is at version ${application.version} but pit-cli is versioned as ${expected.application.version}. Ensure that pit-cli is updated tp reflect the changes in libpit and then run the 'upgrade-version' task to sync the pit-vli subproject with the PIT project.">
|
|
||||||
<condition>
|
|
||||||
<not>
|
|
||||||
<equals
|
|
||||||
arg1="${application.version}"
|
|
||||||
arg2="${expected.application.version}"/>
|
|
||||||
</not>
|
|
||||||
</condition>
|
|
||||||
</fail>
|
|
||||||
<echo message="Application version: ${application.version}"/>
|
|
||||||
|
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="upgrade-version">
|
<target name="lib">
|
||||||
<propertyfile file="project.properties">
|
<copy todir="${build.dir}/lib/compile/jar"
|
||||||
<entry
|
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
|
||||||
key="expected.application.version"
|
<copy todir="${build.dir}/lib/runtime/jar"
|
||||||
value="${application.version}"/>
|
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
|
||||||
<entry key="build.number" value="0"/>
|
|
||||||
</propertyfile>
|
|
||||||
<echo message="pit-cli version upgraded to ${application.version}"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="increment-build-number" depends="init">
|
|
||||||
<propertyfile file="project.properties">
|
|
||||||
<entry key="build.number" operation="+" type="int" default="0"/>
|
|
||||||
</propertyfile>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="clean">
|
|
||||||
<delete dir="${build.dir}"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="compile" depends="init,increment-build-number">
|
|
||||||
<mkdir dir="${build.dir}/classes"/>
|
|
||||||
<groovyc
|
|
||||||
srcdir="${src.dir}"
|
|
||||||
destdir="${build.dir}/classes"
|
|
||||||
classpathref="groovyc.classpath"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="build" depends="compile">
|
|
||||||
<mkdir dir="${build.dir}/jar"/>
|
|
||||||
<unjar dest="${build.dir}/classes">
|
|
||||||
<path refid="package.jars"/>
|
|
||||||
</unjar>
|
|
||||||
<jar
|
|
||||||
destfile="${build.dir}/jar/${build.jar}"
|
|
||||||
basedir="${build.dir}/classes"
|
|
||||||
compress="on">
|
|
||||||
<manifest>
|
|
||||||
<attribute name="Main-Class" value="${main.class}"/>
|
|
||||||
</manifest>
|
|
||||||
</jar>
|
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="release" depends="build">
|
<target name="release" depends="build">
|
||||||
<delete dir="${release.dir}"/>
|
<mkdir dir="${release.dir}/lib"/>
|
||||||
<mkdir dir="${release.dir}"/>
|
<copy todir="${release.dir}/lib">
|
||||||
<copy file="${build.dir}/jar/${build.jar}"
|
<fileset dir="${build.dir}/lib/runtime/jar"/></copy>
|
||||||
tofile="${release.dir}/${release.jar}"/>
|
<copy tofile="${release.dir}/${name}-${version}.jar"
|
||||||
|
file="${build.dir}/${name}-${version}.${build.number}.jar"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
BIN
pit-cli/lib/compile/jar/joda-time-2.0.jar
Normal file
BIN
pit-cli/lib/compile/jar/joda-time-2.0.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
pit-cli/lib/runtime/jar/joda-time-2.0.jar
Normal file
BIN
pit-cli/lib/runtime/jar/joda-time-2.0.jar
Normal file
Binary file not shown.
@ -1,10 +1,12 @@
|
|||||||
#Tue, 25 Oct 2011 11:32:31 -0500
|
#Thu, 08 Dec 2011 14:59:30 -0600
|
||||||
build.dir=build
|
build.dir=build
|
||||||
src.dir=src
|
src.dir=src
|
||||||
build.jar=pit-cli-${application.version}.${build.number}.jar
|
build.jar=pit-cli-${application.version}.${build.number}.jar
|
||||||
build.number=13
|
build.number=12
|
||||||
expected.application.version=2.6.0
|
version=3.2.0
|
||||||
|
name=pit-cli
|
||||||
lib.dir=lib
|
lib.dir=lib
|
||||||
|
lib.local=true
|
||||||
release.dir=release
|
release.dir=release
|
||||||
release.jar=pit-cli-${application.version}.jar
|
release.jar=pit-cli-${application.version}.jar
|
||||||
main.class=com.jdbernard.pit.PersonalIssueTrackerCLI
|
main.class=com.jdbernard.pit.PersonalIssueTrackerCLI
|
||||||
|
0
pit-cli/resources/main/logback.groovy
Normal file
0
pit-cli/resources/main/logback.groovy
Normal file
@ -1,253 +0,0 @@
|
|||||||
package com.jdbernard.pit
|
|
||||||
|
|
||||||
import com.jdbernard.pit.file.*
|
|
||||||
|
|
||||||
import static java.lang.Math.max
|
|
||||||
import static java.lang.Math.min
|
|
||||||
|
|
||||||
// -------- command-line interface specification -------- //
|
|
||||||
|
|
||||||
def cli = new CliBuilder(usage: 'pit-cli [options]')
|
|
||||||
cli.h(longOpt: 'help', 'Show help information.')
|
|
||||||
cli.v(longOpt: 'verbose', 'Show verbose task information')
|
|
||||||
cli.l(longOpt: 'list', 'List issues. Unless otherwise specified it lists all '
|
|
||||||
+ 'sub projects and all unclosed issue categories.')
|
|
||||||
cli.i(argName: 'id', longOpt: 'id', args: 1,
|
|
||||||
'Filter issues by id. Accepts a comma-delimited list.')
|
|
||||||
cli.c(argName: 'category', longOpt: 'category', args: 1,
|
|
||||||
'Filter issues by category (bug, feature, task). Accepts a '
|
|
||||||
+ 'comma-delimited list. By default all categories are selected.')
|
|
||||||
cli.s(argName: 'status', longOpt: 'status', args: 1,
|
|
||||||
'Filter issues by status (new, reassigned, rejected, resolved, ' +
|
|
||||||
'validation_required)')
|
|
||||||
cli.p(argName: 'priority', longOpt: 'priority', args: 1,
|
|
||||||
'Filter issues by priority. This acts as a threshhold, listing all issues '
|
|
||||||
+ 'greater than or equal to the given priority.')
|
|
||||||
cli.r(argName: 'project', longOpt: 'project', args: 1,
|
|
||||||
'Filter issues by project (relative to the current directory). Accepts a '
|
|
||||||
+ 'comma-delimited list.')
|
|
||||||
/*cli.s(longOpt: 'show-subprojects',
|
|
||||||
'Include sup projects in listing (default behaviour)')
|
|
||||||
cli.S(longOpt: 'no-subprojects', 'Do not list subprojects.')*/ // TODO: figure out better flags for these options.
|
|
||||||
cli.P(argName: 'new-priority', longOpt: 'set-priority', args: 1,
|
|
||||||
required: false, 'Modify the priority of the selected issues.')
|
|
||||||
cli.C(argName: 'new-category', longOpt: 'set-category', args: 1,
|
|
||||||
required: false, 'Modify the category of the selected issues.')
|
|
||||||
cli.S(argName: 'new-status', longOpt: 'set-status', args: 1,
|
|
||||||
required: false, 'Modify the status of the selected issues.')
|
|
||||||
cli.n(longOpt: 'new-issue', 'Create a new issue.')
|
|
||||||
cli.d(longOpt: 'dir', argName: 'dir', args: 1, required: false,
|
|
||||||
'Use <dir> as the base directory (defaults to current directory).')
|
|
||||||
cli._(longOpt: 'version', 'Display PIT version information.')
|
|
||||||
|
|
||||||
// -------- parse CLI options -------- //
|
|
||||||
def VERSION = "2.6.0"
|
|
||||||
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: [],
|
|
||||||
acceptProjects: true]
|
|
||||||
|
|
||||||
// defaults for changing properties of issue(s)
|
|
||||||
def assignOpts = [
|
|
||||||
category: Category.TASK,
|
|
||||||
status: Status.NEW,
|
|
||||||
priority: 5,
|
|
||||||
text: "New issue."]
|
|
||||||
|
|
||||||
if (!opts) opts.l = true; // default to 'list'
|
|
||||||
|
|
||||||
if (opts.h) {
|
|
||||||
cli.usage()
|
|
||||||
System.exit(0) }
|
|
||||||
|
|
||||||
// read the 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 status filter designation(s)
|
|
||||||
if (opts.s) {
|
|
||||||
// -s all
|
|
||||||
if (opts.s =~ /all/) selectOpts.status = ['new', 'reassigned', 'rejected',
|
|
||||||
'resolved', 'validation_required']
|
|
||||||
// is <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}'."
|
|
||||||
println "Valid options are: \b${Status.values().join(', ')}"
|
|
||||||
println " (abbreviations are accepted.)"
|
|
||||||
System.exit(1) }
|
|
||||||
|
|
||||||
// read and parse the 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 projects filter
|
|
||||||
if (opts.r) { selectOpts.projects =
|
|
||||||
opts.r.toLowerCase().split(/[,\s]/).asType(List.class) }
|
|
||||||
|
|
||||||
// read and parse the ids filter
|
|
||||||
if (opts.i) { selectOpts.ids = opts.i.split(/[,\s]/).asType(List.class) }
|
|
||||||
|
|
||||||
// TODO: accept projects value from input
|
|
||||||
|
|
||||||
// read and parse the 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 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 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) }
|
|
||||||
|
|
||||||
// look for assignment text
|
|
||||||
if (opts.getArgs().length > 0) {
|
|
||||||
assignOpts.text = opts.getArgs()[0] }
|
|
||||||
|
|
||||||
// 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')
|
|
||||||
|
|
||||||
// build issue list
|
|
||||||
issuedb = new FileProject(workingDir)
|
|
||||||
|
|
||||||
// build filter from options
|
|
||||||
def filter = new Filter(selectOpts)
|
|
||||||
|
|
||||||
// -------- Actions -------- //
|
|
||||||
// list version information first
|
|
||||||
if (opts.version) {
|
|
||||||
|
|
||||||
println "PIT CLI Version ${VERSION}"
|
|
||||||
println "Written by Jonathan Bernard\n" }
|
|
||||||
|
|
||||||
// list second
|
|
||||||
else if (opts.l) {
|
|
||||||
|
|
||||||
// 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 "" } }
|
|
||||||
|
|
||||||
// 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, "") }
|
|
||||||
// print all projects
|
|
||||||
issuedb.eachProject(filter) { printProject(it, "") } }
|
|
||||||
|
|
||||||
// new issues third
|
|
||||||
else if (opts.n) {
|
|
||||||
def cat, priority
|
|
||||||
String text = ""
|
|
||||||
Issue issue
|
|
||||||
def sin = System.in.newReader()
|
|
||||||
|
|
||||||
if (opts.C) { cat = assignOpts.category }
|
|
||||||
else while(true) {
|
|
||||||
try {
|
|
||||||
print "Category (bug, feature, task, closed): "
|
|
||||||
cat = Category.toCategory(sin.readLine())
|
|
||||||
break }
|
|
||||||
catch (e) {
|
|
||||||
println "Invalid category: " + e.getLocalizedMessage()
|
|
||||||
println "Valid options are: \n${Category.values().join(', ')}"
|
|
||||||
println " (abbreviations are accepted)." } }
|
|
||||||
|
|
||||||
if (opts.P) { priority = assignOpts.priority }
|
|
||||||
else while (true) {
|
|
||||||
try {
|
|
||||||
print "Priority (0-9): "
|
|
||||||
priority = max(0, min(9, sin.readLine().toInteger()))
|
|
||||||
break }
|
|
||||||
catch (e) { println "Not a valid value." } }
|
|
||||||
|
|
||||||
if (opts.getArgs().length > 0) { text = assignOpts.text }
|
|
||||||
else {
|
|
||||||
println "Enter issue (use EOF): "
|
|
||||||
try {
|
|
||||||
def line = ""
|
|
||||||
while(true) {
|
|
||||||
line = sin.readLine()
|
|
||||||
|
|
||||||
if (line =~ /EOF/) break
|
|
||||||
|
|
||||||
text += line + EOL
|
|
||||||
} }
|
|
||||||
catch (e) {} }
|
|
||||||
|
|
||||||
issue = issuedb.createNewIssue(category: cat, priority: priority, text: text)
|
|
||||||
|
|
||||||
println "New issue created: "
|
|
||||||
println issue }
|
|
||||||
|
|
||||||
// last, changes to existing issues
|
|
||||||
else {
|
|
||||||
// change priority
|
|
||||||
if (opts.P) issuedb.walkProject(filter) {
|
|
||||||
it.priority = assignOpts.priority
|
|
||||||
println "[${it}] -- set priority to ${assignOpts.priority}"}
|
|
||||||
|
|
||||||
// change third
|
|
||||||
else if (opts.C) issuedb.walkProject(filter) {
|
|
||||||
it.category = assignOpts.cat
|
|
||||||
println "[${it}] -- set category to ${assignOpts.category}"}
|
|
||||||
|
|
||||||
// change status
|
|
||||||
else if (opts.S) issuedb.walkProject(filter) {
|
|
||||||
it.status = assignOpts.status
|
|
||||||
println "[${it}] -- set status to ${assignOpts.status}"}
|
|
||||||
}
|
|
@ -0,0 +1,439 @@
|
|||||||
|
package com.jdbernard.pit
|
||||||
|
|
||||||
|
import com.jdbernard.pit.file.*
|
||||||
|
|
||||||
|
import org.joda.time.DateMidnight
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
|
import static java.lang.Math.max
|
||||||
|
import static java.lang.Math.min
|
||||||
|
|
||||||
|
// -------- command-line interface specification -------- //
|
||||||
|
|
||||||
|
def cli = new CliBuilder(usage: 'pit-cli [options]')
|
||||||
|
cli.h(longOpt: 'help', 'Show help information.')
|
||||||
|
cli.v(longOpt: 'verbose', 'Show verbose task information')
|
||||||
|
cli.l(longOpt: 'list', 'List issues. Unless otherwise specified it lists all '
|
||||||
|
+ 'sub projects and all unclosed issue categories.')
|
||||||
|
cli.i(argName: 'id', longOpt: 'id', args: 1,
|
||||||
|
'Filter issues by id. Accepts a comma-delimited list.')
|
||||||
|
cli.c(argName: 'category', longOpt: 'category', args: 1,
|
||||||
|
'Filter issues by category (bug, feature, task). Accepts a '
|
||||||
|
+ 'comma-delimited list. By default all categories are selected.')
|
||||||
|
cli.s(argName: 'status', longOpt: 'status', args: 1,
|
||||||
|
'Filter issues by status (new, reassigned, rejected, resolved, ' +
|
||||||
|
'validation_required)')
|
||||||
|
cli.p(argName: 'priority', longOpt: 'priority', args: 1,
|
||||||
|
'Filter issues by priority. This acts as a threshhold, listing all issues '
|
||||||
|
+ 'greater than or equal to the given priority.')
|
||||||
|
cli.r(argName: 'project', longOpt: 'project', args: 1,
|
||||||
|
'Filter issues by project (relative to the current directory). Accepts a '
|
||||||
|
+ 'comma-delimited list.')
|
||||||
|
cli.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.
|
||||||
|
cli.P(argName: 'new-priority', longOpt: 'set-priority', args: 1,
|
||||||
|
'Modify the priority of the selected issues.')
|
||||||
|
cli.C(argName: 'new-category', longOpt: 'set-category', args: 1,
|
||||||
|
'Modify the category of the selected issues.')
|
||||||
|
cli.S(argName: 'new-status', longOpt: 'set-status', args: 1,
|
||||||
|
'Modify the status of the selected issues.')
|
||||||
|
cli.E(argName: 'new-extended-property', args: 1, 'Modify the extended ' +
|
||||||
|
'property of the selected issues. Format is "-E <propname>=<propvalue>"')
|
||||||
|
cli.n(longOpt: 'new-issue', 'Create a new issue.')
|
||||||
|
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.')
|
||||||
|
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.')
|
||||||
|
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.')
|
||||||
|
cli.d(longOpt: 'dir', argName: 'dir', args: 1, required: false,
|
||||||
|
'Use <dir> as the base directory (defaults to current directory).')
|
||||||
|
cli.D(longOpt: 'daily-list', 'Print a Daily Task list based on issue Due and' +
|
||||||
|
' Reminder properties.')
|
||||||
|
cli._(longOpt: 'dl-scheduled', 'Show scheduled 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).')
|
||||||
|
cli._(longOpt: 'dl-reminder', 'Show upcoming 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).')
|
||||||
|
cli._(longOpt: 'dl-hide-scheduled', 'Hide scheduled 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).')
|
||||||
|
cli._(longOpt: 'dl-hide-reminder', 'Show upcoming 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).')
|
||||||
|
cli._(longOpt: 'version', 'Display PIT version information.')
|
||||||
|
|
||||||
|
// =================================== //
|
||||||
|
// ======== Parse CLI Options ======== //
|
||||||
|
// =================================== //
|
||||||
|
|
||||||
|
def VERSION = "3.2.0"
|
||||||
|
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]
|
||||||
|
|
||||||
|
// options for changing properties of issue(s)
|
||||||
|
def assignOpts = [:]
|
||||||
|
|
||||||
|
if (!opts) opts.l = true; // default to 'list'
|
||||||
|
|
||||||
|
if (opts.h) {
|
||||||
|
cli.usage()
|
||||||
|
System.exit(0) }
|
||||||
|
|
||||||
|
// read the 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 status filter designation(s)
|
||||||
|
if (opts.s) {
|
||||||
|
// -s all
|
||||||
|
if (opts.s =~ /all/) selectOpts.status = ['new', 'reassigned', 'rejected',
|
||||||
|
'resolved', 'validation_required']
|
||||||
|
// is <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 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 projects filter
|
||||||
|
if (opts.r) { selectOpts.projects =
|
||||||
|
opts.r.toLowerCase().split(/[,\s]/).asType(List.class) }
|
||||||
|
|
||||||
|
// read and parse the ids filter
|
||||||
|
if (opts.i) { selectOpts.ids = opts.i.split(/[,\s]/).asType(List.class) }
|
||||||
|
|
||||||
|
// read and parse 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 extended property selection criteria
|
||||||
|
if (opts.e) {
|
||||||
|
opts.es.each { option ->
|
||||||
|
def parts = option.split("=")
|
||||||
|
selectOpts.extendedProperties[parts[0]] =
|
||||||
|
ExtendedPropertyHelp.parse(parts[1]) }}
|
||||||
|
|
||||||
|
// TODO: accept projects value from input
|
||||||
|
|
||||||
|
// read and parse the 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 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 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) }
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
// ========================= //
|
||||||
|
// ======== Actions ======== //
|
||||||
|
// ========================= //
|
||||||
|
|
||||||
|
// list version information first
|
||||||
|
if (opts.version) {
|
||||||
|
|
||||||
|
println "PIT CLI Version ${VERSION}"
|
||||||
|
println "Written by Jonathan Bernard\n" }
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
|
// build issue list
|
||||||
|
issuedb = new FileProject(workingDir)
|
||||||
|
|
||||||
|
// build filter from options
|
||||||
|
def filter = new Filter(selectOpts)
|
||||||
|
|
||||||
|
// list second
|
||||||
|
if (opts.l) {
|
||||||
|
|
||||||
|
// 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}" }
|
||||||
|
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, "") }
|
||||||
|
// print all projects
|
||||||
|
issuedb.eachProject(filter) { printProject(it, "") } }
|
||||||
|
|
||||||
|
// daily list second
|
||||||
|
else if (opts.D) {
|
||||||
|
|
||||||
|
// Parse daily list specific display options
|
||||||
|
def visibleSections = []
|
||||||
|
def suppressedSections
|
||||||
|
|
||||||
|
// Parse the additive options first.
|
||||||
|
if (opts.'dl-scheduled') { visibleSections << 'scheduled' }
|
||||||
|
if (opts.'dl-due') { visibleSections << 'due' }
|
||||||
|
if (opts.'dl-reminder') { visibleSections << 'reminder' }
|
||||||
|
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', 'reminder', '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-reminder') { visibleSections -= 'reminder' }
|
||||||
|
if (opts.'dl-hide-open') { visibleSections -= 'open' }
|
||||||
|
|
||||||
|
// If the user did not specifically ask for a status filter, we want a
|
||||||
|
// different filter for the default 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.
|
||||||
|
if (!opts.o) { filter.issueSorter = [ {it.due}, {it.priority}, {it.id} ] }
|
||||||
|
|
||||||
|
// Get our issues
|
||||||
|
def allIssues = issuedb.getAllIssues(filter)
|
||||||
|
|
||||||
|
// Set up our time interval.
|
||||||
|
def today = new DateMidnight()
|
||||||
|
def tomorrow = today.plusDays(1)
|
||||||
|
|
||||||
|
def scheduledToday = []
|
||||||
|
def dueToday = []
|
||||||
|
def reminderToday = []
|
||||||
|
def notDueOrReminder = []
|
||||||
|
|
||||||
|
def printIssue = { issue ->
|
||||||
|
if (issue.due) println "${issue.due.toString('EEE, MM/dd')} -- ${issue}"
|
||||||
|
else println " -- ${issue}" }
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
else if (issue.reminder && issue.reminder < tomorrow) {
|
||||||
|
reminderToday << 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.each { printIssue(it) }
|
||||||
|
|
||||||
|
println "" }
|
||||||
|
|
||||||
|
if (visibleSections.contains('due') && dueToday.size() > 0) {
|
||||||
|
println "Tasks Due Today"
|
||||||
|
println "---------------"
|
||||||
|
|
||||||
|
dueToday.each { printIssue(it) }
|
||||||
|
|
||||||
|
println ""}
|
||||||
|
|
||||||
|
if (visibleSections.contains('reminder') && reminderToday.size() > 0) {
|
||||||
|
println "Upcoming Tasks"
|
||||||
|
println "--------------"
|
||||||
|
|
||||||
|
reminderToday.each { printIssue(it) }
|
||||||
|
|
||||||
|
println ""}
|
||||||
|
|
||||||
|
if (visibleSections.contains('open') && notDueOrReminder.size() > 0) {
|
||||||
|
println "Other Open Issues"
|
||||||
|
println "-----------------"
|
||||||
|
|
||||||
|
notDueOrReminder.each { printIssue(it) }
|
||||||
|
|
||||||
|
println "" }}
|
||||||
|
|
||||||
|
// new issues fourth
|
||||||
|
else if (opts.n) {
|
||||||
|
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 as
|
||||||
|
// 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) {} }
|
||||||
|
|
||||||
|
issue = issuedb.createNewIssue(assignOpts)
|
||||||
|
|
||||||
|
println "New issue created: "
|
||||||
|
println issue }
|
||||||
|
|
||||||
|
// last, changes to existing issues
|
||||||
|
else if (assignOpts.size() > 0) {
|
||||||
|
|
||||||
|
// 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 }}
|
||||||
|
|
||||||
|
issuedb.walkProject(filter) { issue ->
|
||||||
|
println issue
|
||||||
|
assignOpts.each { propName, value ->
|
||||||
|
issue[propName] = value
|
||||||
|
println " set ${propName} to ${value}" } }}
|
||||||
|
|
||||||
|
else { cli.usage(); return -1 }}
|
@ -1 +1 @@
|
|||||||
application.version=2.6.0
|
application.version=3.0.0
|
||||||
|
Reference in New Issue
Block a user