Clean slate.

This commit is contained in:
Jonathan Bernard 2018-05-11 11:33:28 -05:00
parent 582bf819f5
commit d6880d9cc1
134 changed files with 0 additions and 7065 deletions

View File

@ -1,37 +0,0 @@
<project name="Personal Issue Tracker" default="package">
<property file="version.properties"/>
<property environment="env"/>
<property
name="libpit.jar"
value="libpit/release/libpit-${application.version}.jar"/>
<target name="clean">
<ant dir="libpit" target="clean" inheritAll="false"/>
<ant dir="pit-cli" target="clean" inheritAll="false"/>
<!-- <ant dir="pit-swing" target="clean" inheritAll="false"/> -->
</target>
<target name="libpit">
<ant dir="libpit" target="release" inheritAll="false"/>
</target>
<target name="pit-cli" depends="libpit">
<copy file="${libpit.jar}" todir="pit-cli/lib"/>
<ant dir="pit-cli" target="release" inheritAll="false"/>
</target>
<!-- <target name="pit-swing" depends="libpit">
<copy file="${libpit.jar}" todir="pit-swing/lib"/>
<ant dir="pit-swing" fork="true" target="package" inheritAll="false"/>
</target> -->
<!-- <target name="package" depends="libpit,pit-cli,pit-swing"> -->
<target name="package" depends="libpit,pit-cli">
<mkdir dir="release/lib"/>
<copy file="pit-cli/release/pit-cli-${application.version}.jar" todir="release"/>
<!-- <copy file="pit-swing/dist/jar/pit-swing.jar"
tofile="release/pit-swing-${application.version}.jar"/> -->
<copy file="${libpit.jar}" todir="release/lib"/>
</target>
</project>

View File

@ -1,13 +0,0 @@
FileIssue is not formatting output in the same way it parses input.
===================================================================
`FileIssue.formatIssue(Issue)` introduces at least one extra line to the end
of the issue body compared to what is parsed in using the PegParser.
----
========= ===================
Created : 2011-12-18T22:53:45
Resolved: 2011-12-19T16:09:50
========= ===================

View File

@ -1,203 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<project name="Jonathan Bernard Build Common">
<property environment="env"/>
<!--======== INIT TARGETS ========-->
<target name="-init" depends="-common-init,init"/>
<target name="-common-init">
<!-- Set default values for some key properties. Since properties are
write once, any value set before this point takes precedence. -->
<property name="versioning.file" value="project.properties"/>
<property name="src.dir" value="${basedir}/src"/>
<property name="build.dir" value="${basedir}/build"/>
<property name="lib.dir" value="${basedir}/lib"/>
<property name="resources.dir" value="${basedir}/resources"/>
<!--======== PATHS ========-->
<path id="groovy.classpath">
<fileset dir="${env.GROOVY_HOME}/lib">
<include name="*.jar"/>
</fileset>
</path>
<path id="groovy.embeddable">
<fileset dir="${env.GROOVY_HOME}/embeddable">
<include name="*.jar"/>
</fileset>
</path>
<path id="compile-libs">
<fileset dir="${build.dir}/lib/compile/jar">
<include name="*.jar"/>
</fileset>
</path>
<path id="runtime-libs">
<fileset dir="${build.dir}/lib/runtime/jar">
<include name="*.jar"/>
</fileset>
</path>
</target>
<target name="-init-groovy">
<taskdef name="groovyc" classpathref="groovy.classpath"
classname="org.codehaus.groovy.ant.Groovyc"/>
<taskdef name="groovy" classpathref="groovy.classpath"
classname="org.codehaus.groovy.ant.Groovy"/>
</target>
<target name="init"/>
<target name="clean" depends="-init">
<delete dir="${build.dir}"/>
</target>
<!--======== LIBRARY TARGETS ========-->
<target name="-lib" depends="-lib-local,-lib-ivy,lib"/>
<target name="lib"/>
<target name="-lib-ivy" unless="${lib.local}"/>
<target name="-lib-local" if="${lib.local}">
<echo message="Resolving libraries locally."/>
<mkdir dir="${build.dir}/lib/compile/jar"/>
<mkdir dir="${build.dir}/lib/runtime/jar"/>
<copy todir="${build.dir}/lib/compile/jar" failonerror="false">
<fileset dir="${lib.dir}/compile/jar"/>
</copy>
<copy todir="${build.dir}/lib/runtime/jar" failonerror="false">
<fileset dir="${lib.dir}/runtime/jar"/>
</copy>
</target>
<!--======== VERSIONING TARGETS ========-->
<target name="increment-build-number" depends="-init">
<propertyfile file="${versioning.file}">
<entry key="build.number" default="0" type="int" value="1"
operation="+"/>
</propertyfile>
</target>
<target name="set-version" depends="-common-init">
<input
message="The current version is ${version}. Enter a new version: "
addproperty="new-version"/>
<propertyfile file="${versioning.file}">
<entry key="version" value="${new-version}" operation="="
type="string"/>
<entry key="build.number" value="0" type="int" operation="="/>
</propertyfile>
</target>
<!--======== COMPILATION TARGETS ========-->
<target name="-compile-groovy" depends="-init,-init-groovy,-lib">
<mkdir dir="${build.dir}/main/classes"/>
<groovyc srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
includeAntRuntime="false" fork="yes">
<classpath>
<path refid="groovy.classpath"/>
<path refid="compile-libs"/>
</classpath>
<javac/>
</groovyc>
</target>
<target name="-compile-java" depends="-init,-lib">
<mkdir dir="${build.dir}/main/classes"/>
<javac srcdir="${src.dir}/main" destdir="${build.dir}/main/classes"
includeAntRuntime="false" classpathref="compile-libs"/>
</target>
<target name="compile" depends="-compile-groovy"/>
<!--======== JUNIT TARGETS ========-->
<target name="-compile-tests-groovy" depends="-init,compile">
<mkdir dir="${build.dir}/test/classes"/>
<groovyc srcdir="${src.dir}/test" destdir="${build.dir}/test/classes"
includeAntRuntime="false" fork="true">
<classpath>
<path refid="groovy.classpath"/>
<path refid="compile-libs"/>
<path location="${build.dir}/main/classes"/>
</classpath>
</groovyc>
</target>
<target name="-compile-tests-java" depends="-init,compile">
<mkdir dir="${build.dir}/test/classes"/>
<javac srcdir="${src.dir}/test" destdir="${build.dir}/test/classes"
includeAntRuntime="false">
<classpath>
<path refid="compile-libs"/>
<path location="${build.dir}/main/classes"/>
</classpath>
</javac>
</target>
<target name="compile-tests" depends="-compile-tests-groovy"/>
<target name="run-tests" depends="compile-tests,resources-test">
<junit printsummary="true">
<classpath>
<path refid="groovy.classpath"/>
<path refid="compile-libs"/>
<path location="${build.dir}/main/classes"/>
<path location="${build.dir}/test/classes"/>
</classpath>
<formatter type="plain" usefile="false"/>
<batchtest>
<fileset dir="${build.dir}/test/classes">
<include name="**/*"/>
</fileset>
</batchtest>
</junit>
</target>
<!--======== RESOURCES TARGETS ========-->
<target name="resources" depends="-init">
<mkdir dir="${build.dir}/main/classes"/>
<copy todir="${build.dir}/main/classes" failonerror="false">
<fileset dir="${resources.dir}/main/"/>
</copy>
</target>
<target name="resources-test" depends="-init">
<mkdir dir="${build.dir}/test/classes"/>
<copy todir="${build.dir}/test/classes" failonerror="false">
<fileset dir="${resources.dir}/test/"/>
</copy>
</target>
<!--======== BUILD TARGETS ========-->
<target name="-build-modular"
depends="compile,increment-build-number,resources">
<jar destfile="${build.dir}/${name}-${version}.${build.number}.jar"
basedir="${build.dir}/main/classes"/>
</target>
<target name="-build-packed-libs"
depends="compile,increment-build-number,resources">
<unjar destdir="${build.dir}/main/classes">
<fileset dir="${build.dir}/lib/runtime/jar"/>
</unjar>
<jar destfile="${build.dir}/${name}-${version}.${build.number}.jar"
basedir="${build.dir}/main/classes"/>
</target>
<target name="build" depends="-build-modular"/>
</project>

View File

@ -1,27 +0,0 @@
<project name="Personal Issue Tracker" default="release">
<property file="project.properties"/>
<import file="../jdb-build-1.6.xml"/>
<target name="init">
<fail
unless="env.GROOVY_HOME"
message="GROOVY_HOME environment variable is not set."/>
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
</target>
<target name="clean-all" depends="clean">
<delete dir="${release.dir}"/>
</target>
<target name="release" depends="build">
<mkdir dir="${release.dir}/lib"/>
<copy file="${build.dir}/${name}-${version}.${build.number}.jar"
tofile="${release.dir}/${name}-${version}.jar"/>
<copy todir="${release.dir}/lib">
<fileset dir="${build.dir}/lib/runtime/jar"/>
</copy>
</target>
</project>

View File

@ -1,7 +0,0 @@
IssueFile - Title Body PropertyBlock?
Title - ONE_LINE TITLE_SEPARATOR
Body - ANY_LINE+
Separator - DASH{4} NEW_LINE
PropertyBlock - HorizontalRule TableSeparator PropertyDefinition+ TableSeparator
TableSeparator -
PropertyDefinition - PropertyKey COLON PropertyValue

View File

@ -1,13 +0,0 @@
#Thu, 26 Mar 2015 19:51:28 -0500
#Sat Apr 24 17:08:00 CDT 2010
build.dir=build
src.dir=src
lib.shared.dir=../shared-libs
test.dir=test
build.number=8
version=3.3.3
name=libpit
lib.dir=lib
lib.local=true
release.dir=release
release.jar=pit-${application.version}.jar

View File

@ -1,13 +0,0 @@
import com.jdbernard.pit.*
import com.jdbernard.pit.file.*
import org.parboiled.Parboiled
import org.parboiled.parserunners.ReportingParseRunner
import org.parboiled.parserunners.RecoveringParseRunner
parser = Parboiled.createParser(IssuePegParser.class)
parseRunner = new ReportingParseRunner(parser.IssueFile())
issueFile = new File('/Volumes/NO NAME/Dropbox/tasks/0015tn3.rst')
issueText = issueFile.text
result = parseRunner.run(issueText)
issueMap = result.valueStack.pop()

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
package com.jdbernard.pit
public enum Category {
BUG,
FEATURE,
TASK
public static Category toCategory(String s) {
for(c in Category.values())
if (c.name().startsWith(s.toUpperCase())) return c
throw new IllegalArgumentException("No category matches ${s}.")
}
public String getSymbol() { toString()[0].toLowerCase() }
public String toString() { return "${name()[0]}${name()[1..-1].toLowerCase()}" }
}

View File

@ -1,81 +0,0 @@
package com.jdbernard.pit
import org.joda.time.DateMidnight
import org.joda.time.DateTime
import java.text.SimpleDateFormat
public enum ExtendedPropertyHelp {
// Property types should be ordered here in order of decreasing specificity.
// That is, subclasses should come before the more general class so that
// objects are converted using the most specific class that
// ExtendedPropertyHelp knows how to work with.
DATE_MIDNIGHT(/^\d{4}-\d{2}-\d{2}$/, DateMidnight,
{ v -> DateMidnight.parse(v) },
{ d -> d.toString("YYYY-MM-dd") }),
DATETIME(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/, DateTime,
{ v -> DateTime.parse(v) },
{ d -> d.toString("YYYY-MM-dd'T'HH:mm:ss") }),
// We never want to parse a value into a java.util.Date or
// java.util.Calendar object (we are using Joda Time instead of the
// standard Java Date and Calendar objects) but we do want to be able to
// handle if someone gives us a Date or Calendar object.
DATE(NEVER_MATCH, Date,
{ v -> v }, // never called
{ d -> dateFormat.format(d) }),
CALENDAR(NEVER_MATCH, Calendar,
{ v -> v }, // never called
{ c ->
def df = dateFormat.clone()
df.calendar = c
df.format(c.time) }),
INTEGER(NEVER_MATCH, Integer,
{ v -> v as Integer }, // never called
{ i -> i as String }),
LONG(/^\d+$/, Long,
{ v -> v as Long },
{ l -> l as String }),
FLOAT(NEVER_MATCH, Float,
{ v -> v as Float}, // never called
{ f -> f as String}),
DOUBLE(/^\d+\.\d+$/, Double,
{ v -> v as Double },
{ d -> d as String });
String pattern;
Class klass;
def parseFun, formatFun;
private static SimpleDateFormat dateFormat =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
// This pattern for can never match (is uses negative lookahead to
// contradict itself).
private static String NEVER_MATCH = /(?!x)x/;
public ExtendedPropertyHelp(String pattern, Class klass, def parseFun,
def formatFun) {
this.pattern = pattern
this.klass = klass
this.parseFun = parseFun
this.formatFun = formatFun }
public boolean matches(String prop) { return prop ==~ pattern }
public boolean matches(Class klass) { return this.klass == klass }
public static Object parse(String value) {
def propertyType = ExtendedPropertyHelp.values().find {
it.matches(value) }
return propertyType ? propertyType.parseFun(value) : value }
public static String format(def object) {
def propertyType = ExtendedPropertyHelp.values().find {
it.klass.isInstance(object) }
return propertyType ? propertyType.formatFun(object) : object.toString() }
}

View File

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

View File

@ -1,37 +0,0 @@
package com.jdbernard.pit
public class FlatProjectView extends Project {
public FlatProjectView(String name) { super(name) }
public Issue createNewIssue(Map options) {
throw new UnsupportedOperationException("The FlatProjectView is " +
"read-only.")
}
public Project createNewProject(String name) {
throw new UnsupportedOperationException("The FlatProjectView is " +
"read-only.")
}
public boolean deleteIssue(Issue issue) { return false }
public boolean deleteProject(Project project) { return false }
public boolean delete() { return true }
public void eachIssue(Filter filter = null, Closure closure) {
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
def gatherIssues
def gatheredIssues = []
gatherIssues = { project, f ->
project.eachIssue(f) { gatheredIssues << it }
project.eachProject(f) { gatherIssues(it, f) }
}
for (p in projects.values())
if (!filter || filter.accept(p))
gatherIssues(p, filter)
gatheredIssues.sort(sorter).each(closure)
}
}

View File

@ -1,74 +0,0 @@
package com.jdbernard.pit
import java.lang.IllegalArgumentException as IAE
public class Issue {
protected String id
protected Category category
protected Status status
protected int priority
protected String text
protected String title
Map extendedProperties = [:]
Issue(Map props) {
this.id = props.id
this.category = props.category ?: Category.TASK
this.status = props.status ?: Status.NEW
this.priority = props.priority ?: 5
this.title = props.title ?: ''
this.text = props.text ?: ''
// Put all the non-native properties into our extendedProperties map.
def nativeProps =
["id", "category", "status", "priority", "title", "text"]
extendedProperties.putAll(props.findAll {
!nativeProps.contains(it.key) })}
public String getId() { return id; }
public Category getCategory() { return category }
public void setCategory(Category c) throws IOException {
if (c == null)
throw new IAE("Category cannot be null.")
this.category = c
}
public Status getStatus() { return status }
public void setStatus(Status s) throws IOException {
if (s == null)
throw new IAE("Status cannot be null.")
this.status = s
}
public int getPriority() { return priority }
public void setPriority(int p) throws IOException {
priority = Math.min(9, Math.max(0, p))
}
public String getTitle() { return title }
public void setTitle(String t) throws IOException { title = t }
public String getText() { return text }
public void setText(String t) throws IOException { text = t }
public def propertyMissing(String name) { extendedProperties[name] }
public def propertyMissing(String name, def value) {
extendedProperties[name] = value }
@Override
public String toString() {
return "${id}(${priority}-${status}): ${category} ${title}"
}
}

View File

@ -1,60 +0,0 @@
package com.jdbernard.pit
public abstract class Project {
protected String name
Map<String, Issue> issues = [:]
Map<String, Project> projects = [:]
Project(String name) { this.name = name }
public void eachIssue(Filter filter = null, Closure c) {
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
for (i in sort(issues.values(), sorter))
if (!filter || filter.accept(i))
c.call(i) }
public void eachProject(Filter filter = null, Closure c) {
def sorter = filter?.projectSorter ?: Filter.defaultProjectSorter
for (p in sort(projects.values(), sorter))
if (!filter || filter.accept(p))
c.call(p) }
// walk every issue and project in this project recursively and execute the
// given closure on each issue that meets the filter criteria
public void walkProject(Filter filter, Closure c) {
this.eachIssue(filter, c)
this.eachProject(filter) { p -> p.walkProject(filter, c) } }
// This get all issues, including subissues
public List getAllIssues(Filter filter = null) {
def sorter = filter?.issueSorter ?: Filter.defaultIssueSorter
List allIssues = this.issues.values().findAll {
filter ? filter.accept(it) : true }
this.eachProject(filter) { p -> allIssues += p.getAllIssues(filter) }
return sort(allIssues, sorter) }
public void setName(String name) { this.name = name }
public String getName() { return name }
@Override
String toString() { return name }
public abstract Issue createNewIssue(Map options)
public abstract Project createNewProject(String name)
public abstract boolean deleteIssue(Issue issue)
public abstract boolean deleteProject(Project project)
protected List sort(def collection, def sorter) {
if (sorter instanceof Closure) {
return collection.sort(sorter) }
else if (sorter instanceof List) {
return sorter.reverse().inject(collection) { c, s -> c.sort(s) }}}
}

View File

@ -1,8 +0,0 @@
package com.jdbernard.pit
public abstract class Repository {
public abstract void persist()
public abstract Project[] getRootProjects()
public abstract Project createNewProject(String name)
}

View File

@ -1,41 +0,0 @@
package com.jdbernard.pit
public enum Status {
REASSIGNED('a'),
REJECTED('j'),
NEW('n'),
RESOLVED('s'),
VALIDATION_REQUIRED('v')
String symbol
protected Status(String s) { symbol = s }
public static Status toStatus(String str) {
// Try to match based on symbol
def match = Status.values().find {it.symbol.equalsIgnoreCase(str)}
if (match) { return match }
// No match on the symbol, look for the status name (or abbreviations)
match = Status.values().findAll {
it.name().startsWith(str.toUpperCase()) }
// No matching status, oops.
if (match.size() == 0) {
throw new IllegalArgumentException("No status matches '${str}'") }
// More than one matching status, oops.
else if (match.size() > 1) {
throw new IllegalArgumentException("Request string is" +
" ambigous, '${str}' could represent any of ${match}.")}
// Only one matching status, yay!
else { return match[0] }}
public String toString() {
def words = name().split("_")
String result = ""
words.each { result += "${it[0]}${it[1..-1].toLowerCase()} " }
return result[0..-2]
}
}

View File

@ -1,189 +0,0 @@
package com.jdbernard.pit.file
import com.jdbernard.pit.*
import java.lang.IllegalArgumentException as IAE
import org.parboiled.Parboiled
import org.parboiled.parserunners.ReportingParseRunner
import org.slf4j.Logger
import org.slf4j.LoggerFactory
public class FileIssue extends Issue {
protected File source
private Logger log = LoggerFactory.getLogger(getClass())
public static final String fileExp = /(\d+)([bft])([ajnsv])(\d).*/
protected static parseRunner
static {
def parser = Parboiled.createParser(IssuePegParser)
parseRunner = new ReportingParseRunner(parser.IssueFile()) }
public FileIssue(File file) {
super(id: -1, title: 'REPLACE_ME')
if (log.isDebugEnabled()) {
log.debug("Loading a FileIssue from '{}'", file.canonicalPath) }
def matcher = file.name =~ fileExp
if (!matcher)
throw new IllegalArgumentException("${file} " +
"is not a valid Issue file.")
// Read issue attributes from the filename.
super.id = matcher[0][1]
super.category = Category.toCategory(matcher[0][2])
super.status = Status.toStatus(matcher[0][3])
super.priority = matcher[0][4].toInteger()
log.debug("id: {}\tcategory: {}\tstatus: {}\tpriority: {}",
super.id, super.category, super.status, super.priority)
this.source = file
// Parse the file and extract the title, text, and extended properties
// TODO: guard against parsing problems (null/empty value stack, etc.)
def parsedIssue = parseRunner.run(file.text).valueStack.pop()
super.text = parsedIssue.body
super.title = parsedIssue.title
// Add the extended properties
parsedIssue.extProperties.each { key, value ->
key = key.toLowerCase().replaceAll(/\s/, '_')
super.extendedProperties[key] =
ExtendedPropertyHelp.parse(value) }
}
public void setCategory(Category c) throws IOException {
File newSource = new File(source.canonicalFile.parentFile,
makeFilename(id, c, status, priority))
if (source.renameTo(newSource)) {
source = newSource
super.setCategory(c) }
else { throw new IOException("I was unable to set the category. "
+ "I need to rename the file for this issue, but something is "
+ "preventing me from doing so (maybe the path to the file is "
+ "no longer valid, or maybe the file is currently open in "
+ "some other program).") }}
public void setStatus(Status s) throws IOException {
File newSource = new File(source.canonicalFile.parentFile,
makeFilename(id, category, s, priority))
if (source.renameTo(newSource)) {
source = newSource
super.setStatus(s) }
else { throw new IOException("I was unable to set the status. "
+ "I need to rename the file for this issue, but something is "
+ "preventing me from doing so (maybe the path to the file is "
+ "no longer valid, or maybe the file is currently open in "
+ "some other program).") }}
public void setPriority(int p) throws IOException {
File newSource = new File(source.canonicalFile.parentFile,
makeFilename(id, category, status, p))
if (source.renameTo(newSource)) {
source = newSource
super.setPriority(p) }
else { throw new IOException("I was unable to set the priority. "
+ "I need to rename the file for this issue, but something is "
+ "preventing me from doing so (maybe the path to the file is "
+ "no longer valid, or maybe the file is currently open in "
+ "some other program).") }}
public String getFilename() {
return makeFilename(id, category, status, priority) }
public void setTitle(String title) throws IOException {
super.setTitle(title)
writeFile() }
public void setText(String text) throws IOException {
super.setText(text)
writeFile() }
public def propertyMissing(String name, def value) {
super.propertyMissing(name, value)
writeFile() }
boolean deleteFile() { return source.deleteDir() }
public static boolean isValidFilename(String name) {
return name ==~ fileExp }
public static String makeFilename(String id, Category category,
Status status, int priority) {
// bounds check priority
priority = Math.min(9, Math.max(0, priority))
//check for valid values of cateogry and id
if (category == null)
throw new IAE("Category must be non-null.")
if (status == null)
throw new IAE("Status must be non-null.")
if (!(id ==~ /\d+/))
throw new IAE( "'${id}' is not a legal value for id.")
return id + category.symbol + status.symbol + priority + ".rst" }
public static String formatIssue(Issue issue) {
def result = new StringBuilder()
result.append(issue.title)
result.append("\n")
result.append("=".multiply(issue.title.length()))
result.append("\n\n")
result.append(issue.text)
// If there are any extended properties, let's write those.
if (issue.extendedProperties.size() > 0) {
result.append("\n----\n\n")
def extOutput = [:]
def maxKeyLen = 0
def maxValLen = 0
// Find the longest key and value, convert all to strings.
issue.extendedProperties.each { key, val ->
def ks = key.toString().split('_').collect({it.capitalize()}).join(' ')
def vs = ExtendedPropertyHelp.format(val)
extOutput[ks] = vs
if (ks.length() > maxKeyLen) { maxKeyLen = ks.length() }
if (vs.length() > maxValLen) { maxValLen = vs.length() } }
result.append("=".multiply(maxKeyLen + 1))
result.append(" ")
result.append("=".multiply(maxValLen))
result.append("\n")
extOutput.sort().each { key, val ->
result.append(key.padRight(maxKeyLen))
result.append(": ")
result.append(val.padRight(maxValLen))
result.append("\n") }
result.append("=".multiply(maxKeyLen + 1))
result.append(" ")
result.append("=".multiply(maxValLen))
result.append("\n") }
return result.toString()}
protected void writeFile() {
try { source.write(formatIssue(this)) }
catch (IOException ioe) {
throw new IOException("I could not save the new text for this "
+ "issue. I can not write to the file for this issue. I do not"
+ " know why, I am sorry (maybe the file can not be reached).") } }
}

View File

@ -1,107 +0,0 @@
package com.jdbernard.pit.file
import com.jdbernard.pit.*
class FileProject extends Project {
protected File source
public FileProject(File dir) {
super(dir.canonicalFile.name)
if (!dir.isDirectory())
throw new IllegalArgumentException(
"${dir.name} is not a directory.")
this.source = dir
dir.eachFile { child ->
// add sub projects
if (child.isDirectory()) {
if (child.name ==~ /\d+/ ||
child.isHidden()) return // just an issue folder
// otherwise build and add to list
projects[(child.name)] = new FileProject(child) }
else if (child.isFile() &&
FileIssue.isValidFilename(child.name)) {
def issue
// if exception, then not an issue
try { issue = new FileIssue(child) } catch (all) { return }
issues[(issue.id)] = issue } }}
public void setName(String name) {
super.setName(name)
source.renameTo(new File(source.canonicalFile.parentFile, name)) }
public FileIssue createNewIssue(Map options) {
Issue issue
File issueFile
if (!options) options = [:]
// We want some different defaults for issues due to the parser being
// unable to handle empty title or text.
if (!options.title) options.title = "Default issue title."
if (!options.text) options.text = "Describe the issue here."
// We are also going to find the next id based on the issues already in the
// project.
if (issues.size() == 0) options.id = '0000'
else {
def lastId = (issues.values().max { it.id.toInteger() }).id
options.id = (lastId.toInteger() + 1).toString().padLeft(
lastId.length(), '0') }
// Create an Issue object from the options (we will discard it later).
issue = new Issue(options)
// Create the filename and File object based on the options given.
issueFile = new File(source, FileIssue.makeFilename(
issue.id, issue.category, issue.status, issue.priority))
// Create the actual file on the system
issueFile.createNewFile()
// Write the issue to the file created.
issueFile.write(FileIssue.formatIssue(issue))
// Read that new file back in as a FileIssue
issue = new FileIssue(issueFile)
// Add the issue to our collection.
issues[(issue.id)] = issue
return issue }
public FileProject createNewProject(String name) {
def newDir = new File(source, name)
newDir.mkdirs()
return new FileProject(newDir) }
public boolean deleteIssue(Issue issue) {
if (!issues[(issue.id)]) return false
issues.remove(issue.id)
if (issue instanceof FileIssue)
return issue.deleteFile()
else return true }
public boolean deleteProject(Project project) {
if (!projects[(project.name)]) return false
projects.remove(project.name)
if (project instanceof FileProject)
return project.source.delete()
return true }
@Override
public String toString() { return name }
}

View File

@ -1,22 +0,0 @@
package com.jdbernard.pit.file
import com.jdbernard.pit.*
public class FileRepository extends Repository {
@Delegate FileProject fileProject
public FileRepository(File dir) {
assert dir.isDirectory()
fileProject = new FileProject(dir)
}
public void persist() {} // nothing to do
public Project[] getRootProjects() {
return [fileProject] as Project[]
}
public FileProject createNewProject(String name) {
return fileProject.createNewProject()
}
}

View File

@ -1,67 +0,0 @@
package com.jdbernard.pit.file;
import java.util.HashMap;
import java.util.Map;
import org.parboiled.Action;
import org.parboiled.BaseParser;
import org.parboiled.Context;
import org.parboiled.Rule;
import org.parboiled.annotations.*;
@BuildParseTree
public class IssuePegParser extends BaseParser<Object> {
public Rule IssueFile() {
return Sequence(push(makeNode()),
Title(), Body(), Optional(PropertyBlock())); }
Rule Title() {
return Sequence(
OneOrMore(NOT_EOL), addToNode("title", match()), EOL,
HorizontalRule(), EOL,
ZeroOrMore(SPACE), EOL); }
Rule Body() { return Sequence(OneOrMore(Sequence(
TestNot(PropertyBlock()), ANY)), addToNode("body", match())); }
Rule PropertyBlock() {
return Sequence(push(makeNode()),
EOL, HorizontalRule(), OneOrMore(EOL), TableSeparator(),
EOL, OneOrMore(PropertyDefinition()), TableSeparator(),
addToNode("extProperties", pop())); }
Rule PropertyDefinition() {
return Sequence(
PropertyKey(), push(match()), COLON,
PropertyValue(), push(match()), EOL,
swap(), addToNode(popAsString().trim(), popAsString().trim())); }
Rule PropertyKey() { return OneOrMore(Sequence(TestNot(COLON), NOT_EOL)); }
Rule PropertyValue() { return OneOrMore(NOT_EOL); }
Rule TableSeparator() {
return Sequence(OneOrMore(SEPARATOR_CHAR), OneOrMore(SPACE),
OneOrMore(SEPARATOR_CHAR)); }
Rule HorizontalRule() {
return Sequence(SEPARATOR_CHAR, SEPARATOR_CHAR, SEPARATOR_CHAR,
OneOrMore(SEPARATOR_CHAR)); }
Rule EOL = Ch('\n');
Rule NOT_EOL = Sequence(TestNot(EOL), ANY);
Rule SEPARATOR_CHAR = AnyOf("\"'`~:-_=+*^#<>");
Rule SPACE = AnyOf(" \t");
Rule COLON = Ch(':');
Map makeNode() { return new HashMap(); }
boolean addToNode(Object key, Object val) {
Map node = (Map) pop();
node.put(key, val);
push(node);
return true; }
String popAsString() { return (String) pop(); }
}

View File

@ -1,36 +0,0 @@
package com.jdbernard.pit.util
import com.jdbernard.pit.*
if (args.size() != 1) {
println "Usage: Convert1_2 [dir]"
System.exit(1)
}
File rootDir = new File(args[0])
Scanner scan = new Scanner(System.in)
rootDir.eachFileRecurse { file ->
def m = file.name =~ /(\d+)([bcft])(\d).*/
if (m && file.isFile()) {
println m[0][0]
def parentFile = file.canonicalFile.parentFile
def c
def s
switch(m[0][2]) {
case "c":
println file.readLines()[0]
print "Issue was closed, was category does it belong in?"
c = Category.toCategory(scan.nextLine())
s = Status.RESOLVED
break
default:
c = Category.toCategory(m[0][2])
s = Status.NEW
break
}
println "${m[0][2]}: ${c}"
file.renameTo(new File(parentFile,
FileIssue.makeFilename(m[0][1], c, s, m[0][3].toInteger())))
}
}

View File

@ -1,74 +0,0 @@
package com.jdbernard.pit.xml
import com.jdbernard.pit.*
public class XmlIssue extends Issue {
def issueNode
XmlProject project
XmlRepository repository
XmlIssue(def issueNode, XmlRepository repository, XmlProject project) {
super(issueNode.@id, issueNode.@category ?: Category.TASK,
issueNode.@status ?: Status.NEW, issueNode.@priority ?: 9)
this.issueNode = issueNode
this.project = project
this.repository = repository
}
XmlIssue(String id, Category c = Category.TASK, Status s = Status.NEW,
int p = 9, String title, String text, XmlRepository repository,
XmlProject project) {
super(id, c, s, p)
this.project = project
this.repository = repository
// Node constructor adds the node to the parent node
issueNode = new Node(project.projectNode, "Issue",
[id: id, category: c, status: s, priority: p, title: title])
super.@title = title
super.@text = text
issueNode.value = text
repository.persist()
}
public void setCategory(Category c) {
super.setCategory(c)
issueNode.@category = c.name()
repository.persist()
}
public void setStatus(Status s) {
super.setStatus(s)
issueNode.@status = s.name()
repository.persist()
}
public void setPriority(int p) {
super.setPriority(p)
issueNode.@priority = p
repository.persist()
}
public void setText(String t) {
super.setText(t)
issueNode.value = t
repository.persist()
}
public void setTitle(String t) {
super.setTitle(t)
issueNode.@title = t
repository.persist()
}
}

View File

@ -1,83 +0,0 @@
package com.jdbernard.pit.xml
import com.jdbernard.pit.*
public class XmlProject extends Project {
def projectNode
XmlRepository repository
XmlProject(def projectNode, XmlRepository repository) {
super(projectNode.@name)
this.projectNode = projectNode
this.repository = repository
}
XmlProject(String name, def parentProject, XmlRepository repository) {
super(name)
// Node constructor adds the node to the parent node
projectNode = new Node(parentProject.projectNode, "Project",
[name: name])
repository.persist()
}
public void setName(String name) {
super.setName(name)
projectNode.@name = name
repository.persist()
}
public XmlIssue createNewIssue(Map options) {
if (!options) options = [:]
if (!options.category) options.category = Category.TASK
if (!options.status) options.status = Status.NEW
if (!options.priority) options.priority = 5
if (!options.text) options.text = "Default issue title.\n" +
"====================\n"
String id
if (issues.size() == 0) id = "0000"
else {
id = (issues.values().max { it.id.toInteger() }).id
id = (id.toInteger() + 1).toString().padLeft(id.length(), '0')
}
// XmlIssue constructor will persist XML data
issues[(id)] = new XmlIssue(id, options.category, options.status,
options.priority, options.text, repository, this)
return issues[(id)]
}
public XmlProject createNewProject(String name) {
// XmlProject constructor persists the XML data
projects[(name)] = new XmlProject(name, this, repository)
return projects[(name)]
}
public boolean deleteIssue(Issue issue) {
if (!issues[(issue.id)]) return false
issues.remove(issue.id)
if (issue instanceof XmlIssue)
projectNode.remove(issue.issueNode)
repository.persist()
return true
}
public boolean deleteProject(Project project) {
if (!projects[(project.name)]) return false
projects.remove(project.name)
if (project instanceof XmlProject)
projectNode.remove(project.projectNode)
repository.persist()
}
}

View File

@ -1,47 +0,0 @@
package com.jdbernard.pit.xml
import com.jdbernard.pit.*
import groovy.xml.XmlUtil
public class XmlRepository extends Repository {
def repository
def projects = []
File repoFile
public XmlRepository(File repoFile) {
this.repoFile = repoFile
repository = new XmlParser().parse(repoFile)
repository.Project.each { projectNode ->
projects << new XmlProject(projectNode)
}
}
public synchronized void persist() {
repoFile.withOutputStream { XmlUtil.serialize(repository, it) }
}
public Project[] getRootProjects() {
return projects as XmlProject[]
}
public XmlProject createNewProject(String name) {
def newProject = new XmlProject(name, this, null)
repository << newProject.projectNode
persist()
return newProject
}
public boolean deleteProject(Project p) {
if (!projects.contains(p)) return false
projects.remove(p)
repository.remove(p.projectNode)
return true
}
}

View File

@ -1,32 +0,0 @@
package com.jdbernard.pit
import org.junit.Test
import static org.junit.Assert.assertEquals
import static com.jdbernard.pit.Category.toCategory
class CategoryTest {
@Test void testToCategory() {
assertEquals toCategory("BUG"), Category.BUG
assertEquals toCategory("FEATURE"), Category.FEATURE
assertEquals toCategory("TASK"), Category.TASK
assertEquals toCategory("bug"), Category.BUG
assertEquals toCategory("feature"), Category.FEATURE
assertEquals toCategory("task"), Category.TASK
assertEquals toCategory("b"), Category.BUG
assertEquals toCategory("f"), Category.FEATURE
assertEquals toCategory("t"), Category.TASK
}
@Test void testGetSymbol() {
assertEquals Category.BUG.symbol, "b"
assertEquals Category.FEATURE.symbol, "f"
assertEquals Category.TASK.symbol, "t"
}
}

View File

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

View File

@ -1,8 +0,0 @@
package com.jdbernard.pit
public class MockIssue extends Issue {
public MockIssue(String id, Category c, Status s, int p) {
super ([id: id, category: c, status: s, priority: p])
}
public boolean delete() { return true }
}

View File

@ -1,20 +0,0 @@
package com.jdbernard.pit
class MockProject extends Project {
public MockProject(String name) { super(name) }
public Issue createNewIssue(Map options) {
return new MockIssue(options.id ?: 'n/a',
options.c ?: Category.TASK, options.s ?: Status.NEW,
options.p ?: 5)
}
public Project createNewProject(String name) {
return new MockProject(name)
}
public boolean delete() { return true }
public boolean deleteProject(Project project) { return true }
public boolean deleteIssue(Issue issue) { return true }
}

View File

@ -1,12 +0,0 @@
package com.jdbernard.pit
class MockRepository extends Repository {
public void persist() {}
public Project[] getRootProjects() { return [] as Project[] }
public Project createNewProject(String name) {
return new MockProject(name)
}
}

View File

@ -1,54 +0,0 @@
package com.jdbernard.pit
import org.junit.Test
import static org.junit.Assert.assertEquals
import static com.jdbernard.pit.Status.toStatus
public class StatusTest {
@Test void testToStatus() {
assertEquals Status.REASSIGNED, toStatus('REASSIGNED')
assertEquals Status.REJECTED, toStatus('REJECTED')
assertEquals Status.NEW, toStatus('NEW')
assertEquals Status.RESOLVED , toStatus('RESOLVED')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('VALIDATION_REQUIRED')
assertEquals Status.REASSIGNED, toStatus('REA')
assertEquals Status.REJECTED, toStatus('REJ')
assertEquals Status.NEW, toStatus('NEW')
assertEquals Status.RESOLVED , toStatus('RES')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('VAL')
assertEquals Status.REASSIGNED, toStatus('reassigned')
assertEquals Status.REJECTED, toStatus('rejected')
assertEquals Status.NEW, toStatus('new')
assertEquals Status.RESOLVED , toStatus('resolved')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('validation_required')
assertEquals Status.REASSIGNED, toStatus('rea')
assertEquals Status.REJECTED, toStatus('rej')
assertEquals Status.NEW, toStatus('new')
assertEquals Status.RESOLVED , toStatus('res')
assertEquals Status.VALIDATION_REQUIRED,
toStatus('val')
assertEquals Status.REASSIGNED, toStatus('A')
assertEquals Status.REJECTED, toStatus('J')
assertEquals Status.NEW, toStatus('N')
assertEquals Status.RESOLVED , toStatus('S')
assertEquals Status.VALIDATION_REQUIRED, toStatus('V')
assertEquals Status.REASSIGNED, toStatus('a')
assertEquals Status.REJECTED, toStatus('j')
assertEquals Status.NEW, toStatus('n')
assertEquals Status.RESOLVED , toStatus('s')
assertEquals Status.VALIDATION_REQUIRED, toStatus('v')
}
}

View File

@ -1,228 +0,0 @@
package com.jdbernard.pit.file
import com.jdbernard.pit.*
import org.junit.*
import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertEquals
class FileIssueTest {
def issues
File testDir
@Before void makeIssueFiles() {
File issueFile
issues = []
testDir = new File('testdir')
testDir.mkdirs()
issueFile = new File(testDir, '0001fn1.rst')
issueFile.write(
"Add the killer feature to the killer app.\n" +
"=========================================\n\n" +
"Make our killer app shine!.")
issues << new FileIssue(issueFile)
issueFile = new File(testDir, '0002ts5.rst')
issueFile.write(
"Obtain donuts.\n" +
"==============\n\n" +
"The office is seriously lacking in sugary donuts.\n\n" +
"We must rectify this at once!")
issues << new FileIssue(issueFile)
}
@After void deleteIssueFiles() {
assert testDir.deleteDir()
}
@Test void testSetCategory() {
assertEquals issues[0].category, Category.FEATURE
assertEquals issues[1].category, Category.TASK
try {
issues[0].category = Category.TASK
issues[1].category = Category.BUG
} catch (Exception e) {
Assert.fail("An unexpected Exception occurred: "
+ e.getLocalizedMessage())
}
assertEquals issues[0].category, Category.TASK
assertEquals issues[1].category, Category.BUG
assertTrue new File(testDir, '0001tn1.rst').exists()
assertTrue new File(testDir, '0002bs5.rst').exists()
assertFalse new File(testDir, '0001fn1.rst').exists()
assertFalse new File(testDir, '0002ts5.rst').exists()
}
@Test void testSetCategoryFails() {
FileInputStream fin
try {
// get a lock to the file to prevent the rename
def issueFile = new File('0001fn1.rst')
fin = new FileInputStream(issueFile)
// try to set the category
issues[0].category = Category.TASK
// should throw IOE before here
Assert.fail()
} catch (IOException ioe) {
} catch (Exception e) {
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
} finally {
if (fin != null) fin.close()
}
}
@Test void testSetStatus() {
assertEquals issues[0].status, Status.NEW
assertEquals issues[1].status, Status.RESOLVED
try {
issues[0].status = Status.RESOLVED
issues[1].status = Status.REJECTED
} catch (Exception e) {
Assert.fail("An unexpected Exception occurred: "
+ e.getLocalizedMessage())
}
assertTrue new File(testDir, '0001fs1.rst').exists()
assertTrue new File(testDir, '0002tj5.rst').exists()
assertFalse new File(testDir, '0001fn1.rst').exists()
assertFalse new File(testDir, '0002ts5.rst').exists()
}
@Test void testSetStatusFails() {
FileInputStream fin
try {
// get a lock to the file to prevent the rename
def issueFile = new File('0001fn1.rst')
fin = new FileInputStream(issueFile)
// try to set the status
issues[0].status = Status.REJECTED
// should throw IOE before here
Assert.fail()
} catch (IOException ioe) {
} catch (Exception e) {
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
} finally {
if (fin != null) fin.close()
}
}
@Test void testSetPriority() {
assertEquals issues[0].priority, 1
assertEquals issues[1].priority, 5
try {
issues[0].priority = 2
issues[1].priority = 9
} catch (Exception e) {
Assert.fail("An unexpected Exception occurred: "
+ e.getLocalizedMessage())
}
assertEquals issues[0].priority, 2
assertEquals issues[1].priority, 9
assertTrue new File(testDir, '0001fn2.rst').exists()
assertTrue new File(testDir, '0002ts9.rst').exists()
assertFalse new File(testDir, '0001fn1.rst').exists()
assertFalse new File(testDir, '0002ts5.rst').exists()
}
@Test void testSetPriorityFails() {
FileInputStream fin
try {
// get a lock to the file to prevent the rename
def issueFile = new File('0001fn1.rst')
fin = new FileInputStream(issueFile)
// try to set the priority
issues[0].priority = 9
// should throw IOE before here
Assert.fail()
} catch (IOException ioe) {
} catch (Exception e) {
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
} finally {
if (fin != null) fin.close()
}
}
@Test void testConstruction() {
File issueFile = new File(testDir, '0001fn1.rst')
Issue issue = new FileIssue(issueFile)
assertEquals issue.id , "0001"
assertEquals issue.category , Category.FEATURE
assertEquals issue.status , Status.NEW
assertEquals issue.priority , 1
assertEquals issue.title , "Add the killer feature to the killer app."
assertEquals issue.text , "Make our killer app shine!."
assertEquals issue.source , issueFile
}
@Test void testSetTextFails() {
try {
// make the issue file un-writable
def issueFile = new File('0001fn1.rst')
if (issueFile.setReadOnly()) {
// try to write something
issues[0].text = "This should fail to be written."
// should throw IOE before here
Assert.fail()
} else {
println "Could not run testSetTextFails, unable to change " +
"the test isseu file's permissions."
}
} catch (IOException ioe) {
} catch (Exception e) {
Assert.fail("Unexpected exception: " + e.getLocalizedMessage())
}
}
@Test void testMakeFilename() {
assertEquals FileIssue.makeFilename('0001', Category.BUG,
Status.NEW, 5), '0001bn5.rst'
assertEquals FileIssue.makeFilename('0010', Category.FEATURE,
Status.REASSIGNED, 1), '0010fa1.rst'
assertEquals FileIssue.makeFilename('0002', Category.FEATURE,
Status.REJECTED, 3), '0002fj3.rst'
assertEquals FileIssue.makeFilename('0001', Category.BUG,
Status.RESOLVED, -2), '0001bs0.rst'
assertEquals FileIssue.makeFilename('0001', Category.TASK,
Status.VALIDATION_REQUIRED, 10) , '0001tv9.rst'
assertEquals FileIssue.makeFilename('00101', Category.BUG,
Status.NEW, 5), '00101bn5.rst'
try {
FileIssue.makeFilename('badid', Category.BUG, Status.NEW, 5)
assertTrue 'Issue.makeFilename() succeeded with bad id input.', false
} catch (IllegalArgumentException iae) {}
try {
FileIssue.makeFilename('0002', null, Status.NEW, 5)
assertTrue 'Issue.makeFilename() succeeded given no Category.', false
} catch (IllegalArgumentException iae) {}
try {
FileIssue.makeFilename('0002', Category.BUG, null, 5)
assertTrue 'Issue.makeFilename() succeeded given no Status.', false
} catch (IllegalArgumentException iae) {}
}
}

View File

@ -1,162 +0,0 @@
package com.jdbernard.pit.file
import com.jdbernard.pit.*
import org.junit.After
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.assertTrue
class FileProjectTest {
File testDir
Project rootProj
@Before void createTestProjects() {
testDir = new File('testdir')
assert !testDir.exists()
testDir.mkdirs()
/* TEST SUITE:
/testdir/
0001t5.rst
0002b5.rst
0003f2.rst
subproj1/
0001f3.rst
0002b4.rst
emptyproj/
*/
def issueFile = new File(testDir, '0001tn5.rst')
issueFile.createNewFile()
issueFile.write('Test Issue 1\n' +
'============\n\n' +
'This is the first test issue.')
issueFile = new File(testDir, '0002ba5.rst')
issueFile.createNewFile()
issueFile.write('Test Bug\n' +
'========\n\n' +
'Yeah, it is a test bug.')
issueFile = new File(testDir, '0003fs2.rst')
issueFile.createNewFile()
issueFile.write('Important Feature Request\n' +
'=========================\n\n' +
'Here is our sweet feature. Please implement it!')
def subDir = new File(testDir, 'subproj1')
subDir.mkdirs()
issueFile = new File(subDir, '0001fv3.rst')
issueFile.createNewFile()
issueFile.write('First feature in subproject\n' +
'===========================\n\n' +
'Please make the grubblers grobble.')
issueFile = new File(subDir, '0002bj4.rst')
issueFile.createNewFile()
issueFile.write('Zippners are not zippning.\n' +
'==========================\n\n' +
'For some reason, the Zippners are bilperring, not zippning.')
subDir = new File(testDir, 'emptyproj')
subDir.mkdirs()
rootProj = new FileProject(testDir)
}
@After void deleteTestProjects() {
assert testDir.deleteDir()
if (rootProj.source.exists())
assert rootProj.source.deleteDir()
}
@Test void testConstruction() {
Project proj = new FileProject(testDir)
assertEquals proj.name, 'testdir'
assertEquals proj.issues.size(), 3
assertEquals proj.projects.size(), 2
// Issue construction in general is under test in IssueTest
// just check that the issues actually exists
assertEquals proj.issues['0001'].id, '0001'
assertEquals proj.issues['0001'].title, 'Test Issue 1'
assertEquals proj.issues['0002'].id, '0002'
assertEquals proj.issues['0002'].title, 'Test Bug'
assertEquals proj.issues['0003'].id, '0003'
assertEquals proj.issues['0003'].title, 'Important Feature Request'
// check sub-project behaviour
assertNotNull proj.projects.subproj1
assertEquals proj.projects.subproj1.name, 'subproj1'
assertEquals proj.projects.subproj1.issues.size(), 2
assertEquals proj.projects.subproj1.projects.size(), 0
assertEquals proj.projects.subproj1.issues['0001'].id, '0001'
assertEquals proj.projects.subproj1.issues['0002'].id, '0002'
assertEquals proj.projects.subproj1.issues['0001'].title,
'First feature in subproject'
assertEquals proj.projects.subproj1.issues['0002'].title,
'Zippners are not zippning.'
assertNotNull proj.projects.emptyproj
assertEquals proj.projects.emptyproj.issues.size(), 0
assertEquals proj.projects.emptyproj.projects.size(), 0
}
@Test void testRename() {
assert rootProj.name == 'testdir'
rootProj.name = 'renamedTestDir'
assertEquals rootProj.name, 'renamedTestDir'
assertTrue new File('renamedTestDir').exists()
assert rootProj.source.deleteDir()
}
@Test void testCreateNewIssue() {
// test correct increment of id, application of values
def newIssue = rootProj.createNewIssue(category: Category.BUG,
status: Status.REASSIGNED, priority: 4,
text: 'A newly made bug report.\n'+
'========================\n\n' +
'Testing the Project.createNewIssue() method.')
assertEquals newIssue.id, '0004'
assertEquals newIssue.category, Category.BUG
assertEquals newIssue.status, Status.REASSIGNED
assertEquals newIssue.priority, 4
assertEquals newIssue.text, 'A newly made bug report.\n'+
'========================\n\n' +
'Testing the Project.createNewIssue() method.'
assertEquals rootProj.issues[(newIssue.id)], newIssue
//test defaults and creation of issue in an empty project
newIssue = rootProj.projects.emptyproj.createNewIssue()
assertEquals newIssue.id, '0000'
assertEquals newIssue.priority, 5
assertEquals newIssue.category, Category.TASK
assertEquals newIssue.status, Status.NEW
assertEquals newIssue.text, 'Default issue title.\n' +
'====================\n'
assertEquals rootProj.projects.emptyproj.issues[(newIssue.id)],
newIssue
}
}

View File

@ -1,27 +0,0 @@
package com.jdbernard.pit.xml
import com.jdbernard.pit.*
import groovy.util.Node
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertTrue
public class XmlIssueTest {
Node issueNode = new Node(null, 'Issue',
[id: '0000', category: 'BUG', status: 'RESOLVED', priority: 1],
'Test Issue')
@Test public void testDummyTest() {}
/*@Test public void testNodeConstructor() {
XmlIssue issue = new XmlIssue(issueNode)
assertEquals issue.text, 'Test Issue'
assertEquals issue.id, '0000'
assertEquals issue.category, Category.BUG
assertEquals issue.status, Status.RESOLVED
assertEquals issue.priority, 1
}*/
}

View File

@ -1,41 +0,0 @@
<project name="Personal Issue Tracker CLI">
<property file="project.properties"/>
<import file="../jdb-build-1.6.xml"/>
<target name="init">
<fail
unless="env.GROOVY_HOME"
message="GROOVY_HOME environment variable is not set."/>
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
<fail message="Could not find PIT ${version} library.">
<condition>
<not>
<available
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
</not>
</condition>
</fail>
</target>
<target name="clean-all" depends="clean">
<delete dir="${release.dir}"/>
</target>
<target name="lib">
<copy todir="${build.dir}/lib/compile/jar"
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
<copy todir="${build.dir}/lib/runtime/jar"
file="${basedir}/../libpit/release/libpit-${version}.jar"/>
</target>
<target name="release" depends="build">
<mkdir dir="${release.dir}/lib"/>
<copy todir="${release.dir}/lib">
<fileset dir="${build.dir}/lib/runtime/jar"/></copy>
<copy tofile="${release.dir}/${name}-${version}.jar"
file="${build.dir}/${name}-${version}.${build.number}.jar"/>
</target>
</project>

View File

@ -1,12 +0,0 @@
#Thu, 26 Mar 2015 19:51:28 -0500
build.dir=build
src.dir=src
build.jar=pit-cli-${application.version}.${build.number}.jar
build.number=2
version=3.3.3
name=pit-cli
lib.dir=lib
lib.local=true
release.dir=release
release.jar=pit-cli-${application.version}.jar
main.class=com.jdbernard.pit.PersonalIssueTrackerCLI

View File

@ -1,631 +0,0 @@
/**
* # Personal Issue Tracker Command Line Interface
* @author Jonathan Bernard <jdbernard@gmail.com>
* @copyright 2009-2012 Jonathan Bernard
*
* This is a command-line interface to my personal issue tracker system.
*/
package com.jdbernard.pit
import com.jdbernard.pit.file.*
import org.joda.time.DateMidnight
import org.joda.time.DateTime
import org.slf4j.LoggerFactory
import static java.lang.Math.max
import static java.lang.Math.min
def log = LoggerFactory.getLogger(getClass())
/// ## Command Line Options ##
/// --------------------------
/// @org cli-options
def cli = new CliBuilder(usage: 'pit-cli [options]')
/// -h,--help
/// : Show help information
cli.h(longOpt: 'help', 'Show help information.')
/// -v,--verbose
/// : Show verbose task information.
cli.v(longOpt: 'verbose', 'Show verbose task information')
/// -l,--list
/// : List issues in the current project.
cli.l(longOpt: 'list', 'List issues in the current project.')
/// -i,--id
/// : Filter issues by id. Accepts a comma-delimited list.
/// *Example:* `pit -l -i 0001,0002`
cli.i(argName: 'id', longOpt: 'id', args: 1,
'Filter issues by id. Accepts a comma-delimited list.')
/// -c,--category
/// : Filter issues by category (bug, feature, task). Accepts a
/// comma-delimited list. By default all categories are selected. The full
/// category name is not required, just enough to be uniquely identifiable.
/// *Example:* `pit -l -c bug,t # List bugs and tasks.`
cli.c(argName: 'category', longOpt: 'category', args: 1,
'Filter issues by category (bug, feature, task). Accepts a '
+ 'comma-delimited list. By default all categories are selected.')
/// -s,--status
/// : Filter issues by status (new, reassigned, rejected, resolved,
/// validation_required). The full status is not required, just enough to
/// uniquely identify the status.
/// *Example:* `pit -l -s reas,rej # List Reassigned and Rejected issues.`
cli.s(argName: 'status', longOpt: 'status', args: 1,
'Filter issues by status (new, reassigned, rejected, resolved, ' +
'validation_required)')
/// -p,--priority
/// : Filter issues by priority. This acts as a threshhold, listing all
/// issues greater than or equal to the given priority.
/// *Example:* `pit -l -p 5 # List all issues with priority >= 5`
cli.p(argName: 'priority', longOpt: 'priority', args: 1,
'Filter issues by priority. This acts as a threshhold, listing all issues '
+ 'greater than or equal to the given priority.')
/// -r,--project
/// : Filter issues by project (relative to the current directory). Accepts a
/// comma-delimited list. This option should be used in conjunction with the
/// `R,--recursive` option.
/// *Example:* `pit -l -R --project <project_name>`
cli.r(argName: 'project', longOpt: 'project', args: 1,
'Filter issues by project (relative to the current directory). Accepts a '
+ 'comma-delimited list.')
/// -R,--recursive
/// : Recursively include subprojects.
cli.R(longOpt: 'recursive', 'Include subprojects.')
/// -e,--extended-property
/// : Filter for issues by extended property. Format is
/// `-e <propname>=<propvalue>`.
cli.e(argName: 'extended-property', args: 1, 'Filter for issues by extended ' +
'property. Format is "-e <propname>=<propvalue>".')
/*cli.s(longOpt: 'show-subprojects',
'Include sup projects in listing (default behaviour)')
cli.S(longOpt: 'no-subprojects', 'Do not list subprojects.')*/ // TODO: figure out better flags for these options.
/// -P,--set-priority
/// : Modify the priority of the selected issues. Requires a value from 0-9.
cli.P(argName: 'new-priority', longOpt: 'set-priority', args: 1,
'Modify the priority of the selected issues.')
/// -C,--set-category
/// : Modify the category of the selected issues.
cli.C(argName: 'new-category', longOpt: 'set-category', args: 1,
'Modify the category of the selected issues.')
/// -S,--set-status
/// : Modify the status of the selected issues.
cli.S(argName: 'new-status', longOpt: 'set-status', args: 1,
'Modify the status of the selected issues.')
/// -E,--new-issue
/// : Modify the extended property of the selected issues. Format is
/// `-E <propname>=<propvalue>`
cli.E(argName: 'new-extended-property', longOpt: 'set-extended-property',
args: 1, 'Modify the extended property of the selected issues. Format ' +
'is "-E <propname>=<propvalue>"')
/// -n,--new-issue
/// : Create a new issue
cli.n(longOpt: 'new-issue', 'Create a new issue.')
/// --title
/// : Give the title for a new issue or modify the title for an existing
/// issue. By default the title for a new issue is expected on stanard
/// input.
cli._(longOpt: 'title', args: 1, argName: 'title', 'Give the title for a new' +
' issue or modify the title for an existing issue. By default the title' +
' for a new issue is expected on stanard input.')
/// --text
/// : Give the text for a new issue or modify the text for an exising
/// issue. By default the text for a new issue is expected on standard
/// input.
cli._(longOpt: 'text', args: 1, argName: 'text', 'Give the text for a new' +
' issue or modify the text for an exising issue. By default the text for' +
' a new issue is expected on standard input.')
/** -o,--order
* : Order (sort) the results by the given properties. Provide a
* comma-seperated list of property names to sort by in order of
* importance. The basic properties (id, category, status, and priority)
* can be given using their one-letter forms (i,c,s,p) for brevity. For
* example: `-o Due,p,c` would sort first by the extended property `Due`,
* then for items that have the same `Due` value it would sort by
* priority, then by category. */
cli.o(longOpt: 'order', argName: 'order', args: 1, required: false,
'Order (sort) the results by the given properties. Provide a comma-' +
'seperated list of property names to sort by in order of importance. The' +
' basic properties (id, category, status, and priority) can be given' +
' using their one-letter forms (i,c,s,p) for brevity. For example:' +
' "-o Due,p,c" would sort first by the extended property "Due", then for' +
' items that have the same "Due" value it would sort by priority, then' +
' by category.')
/// -d,--dir
/// : Use `<dir>` as the base directory (defaults to current directory).
cli.d(longOpt: 'dir', argName: 'dir', args: 1, required: false,
'Use <dir> as the base directory (defaults to current directory).')
/// -D,--daily-list
/// : Print a Daily Task list based on issue Due, Scheduled, and Reminder
/// extended properties.
cli.D(longOpt: 'daily-list', 'Print a Daily Task list based on issue Due and' +
' Reminder properties.')
/// --dl-scheduled
/// : Show scheduled tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-scheduled', 'Show scheduled tasks in the daily list (all' +
' are shown by default).')
/// --dl-due
/// : Show due tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-due', 'Show due tasks in the daily list (all are shown by' +
' default).')
/// --dl-upcoming
/// : Show upcoming tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-upcoming', 'Show upcoming tasks in the daily list (all ' +
' are shown by default).')
/// --dl-open
/// : Show open tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-open', 'Show open tasks in the daily list (all are shown ' +
' by default).')
/// --dl-hide-scheduled
/// : Hide scheduled tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-hide-scheduled', 'Hide scheduled tasks in the daily list' +
' (all are shown by default).')
/// --dl-hide-due
/// : Show due tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-hide-due', 'Show due tasks in the daily list (all are' +
' shown by default).')
/// --dl-hide-upcoming
/// : Show upcoming tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-hide-upcoming', 'Show upcoming tasks in the daily list' +
' (all are shown by default).')
/// --dl-hide-open
/// : Show open tasks in the daily list (all are shown by default).
cli._(longOpt: 'dl-hide-open', 'Show open tasks in the daily list (all are' +
' shown by default).')
/// --dl-upcoming-days
/// : The upcoming tasks section in the daily list includes any tasks due
/// within the next seven days by default. This option overrides that
/// default and allows you to specify the number of days ahead the upcoming
/// section looks.
cli._(longOpt: 'dl-upcoming-days', argName: 'num-days', args:1, required: false,
'The upcoming tasks section in the daily list includes any tasks due ' +
'within the next seven days by default. This option overrides that ' +
'default and allows you to specify the number of days ahead the upcoming ' +
'section looks.')
/// --version
/// : Display PIT version information.
cli._(longOpt: 'version', 'Display PIT version information.')
/// ## Parse CLI Options ##
/// -----------------------
log.trace("Parsing options.")
def VERSION = "3.3.3"
def opts = cli.parse(args)
def issuedb = [:]
def workingDir = new File('.')
/// Defaults for the issue filter/selector.
def selectOpts = [
categories: ['bug', 'feature', 'task'],
status: ['new', 'reassigned', 'rejected',
'resolved', 'validation_required'],
priority: 9,
projects: [],
ids: [],
extendedProperties: [:],
acceptProjects: true]
/// Defaults for changing properties of issue(s)
def assignOpts = [:]
if (!opts || opts.h) {
cli.usage()
System.exit(0) }
///Read the `-c` option: category filter designation(s).
if (opts.c) {
if (opts.c =~ /all/) {} // no-op, same as defaults
else { selectOpts.categories = opts.c.split(/[,\s]/) } }
/// Parse the categories names into Category objects.
try { selectOpts.categories =
selectOpts.categories.collect { Category.toCategory(it) } }
catch (Exception e) {
println "Invalid category option: '-c ${e.localizedMessage}'."
println "Valid options are: \n${Category.values().join(', ')}"
println " (abbreviations are accepted)."
System.exit(1) }
/// Read the `-s` option: status filter designation(s).
if (opts.s) {
// -s all
if (opts.s =~ /all/) selectOpts.status = ['new', 'reassigned', 'rejected',
'resolved', 'validation_required']
// <list>
else selectOpts.status = opts.s.split(/[,\s]/) }
/// Parse the statuses into Status objects.
try { selectOpts.status =
selectOpts.status.collect { Status.toStatus(it) } }
catch (Exception e) {
println "Invalid status option: '-s ${e.localizedMessage}'."
print "Valid options are: \n${Status.values().join(', ')}"
println " (abbreviations are accepted.)"
System.exit(1) }
/// Read and parse the `-p` option: priority filter.
if (opts.p) try {
selectOpts.priority = opts.p.toInteger() }
catch (NumberFormatException nfe) {
println "Not a valid priority value: '-p ${opts.p}'."
println "Valid values are: 0-9"
System.exit(1) }
/// Read and parse the `-r` option: projects filter.
if (opts.r) { selectOpts.projects =
opts.r.toLowerCase().split(/[,\s]/).asType(List.class) }
/// Read and parse the `-i` option: id filter.
if (opts.i) { selectOpts.ids = opts.i.split(/[,\s]/).asType(List.class) }
/// Read and parse the `-o` option: sort criteria.
if (opts.o) {
def sortProps = opts.o.split(',')
selectOpts.issueSorter = sortProps.collect { prop ->
switch (prop) {
case ~/^i$/: return { issue -> issue.id }
case ~/^p$/: return { issue -> issue.priority }
case ~/^s$/: return { issue -> issue.status }
case ~/^c$/: return { issue -> issue.category }
default: return { issue -> issue[prop] } }}}
/// Read and parse any extended property selection criteria.
if (opts.e) {
opts.es.each { option ->
def parts = option.split("=")
selectOpts.extendedProperties[parts[0]] =
ExtendedPropertyHelp.parse(parts[1]) }}
/// Read and parse the `-C` option: category to assign.
if (opts.C) try { assignOpts.category = Category.toCategory(opts.C) }
catch (Exception e) {
println "Invalid category option: '-C ${e.localizedMessage}'."
println "Valid categories are: \n${Category.values().join(', ')}"
println " (abbreviations are accepted)."
System.exit(1) }
/// Read and parse the `-S` option: status to assign.
if (opts.S) try { assignOpts.status = Status.toStatus(opts.S) }
catch (Exception e) {
println "Invalid status option: '-S ${e.localizedMessage}'."
println "Valid stasus options are: \n${Status.values().join(', ')}"
println " (abbreviations are accepted)."
System.exit(1) }
/// Read and parse the `-P` option: priority to assign.
if (opts.P) try {assignOpts.priority = opts.P.toInteger() }
catch (NumberFormatException nfe) {
println "Not a valid priority value: '-P ${opts.P}'."
println "Valid values are: 0-9"
System.exit(1) }
/// Read an parse any extended properties to be set.
if (opts.E) {
opts.Es.each { option ->
def parts = option.split("=")
assignOpts[parts[0]] = ExtendedPropertyHelp.parse(parts[1]) }}
/// Read the title if given.
if (opts.title) { assignOpts.title = opts.title }
/// Read the text if given.
if (opts.text) { assignOpts.text = opts.text }
/// Set the project working directory.
if (opts.d) {
workingDir = new File(opts.d.trim())
if (!workingDir.exists()) {
println "Directory '${workingDir}' does not exist."
return -1 } }
def EOL = System.getProperty('line.separator')
log.debug("Finished parsing options:\nworkingDir: {}\nselectOpts: {}\nassignOpts: {}",
workingDir.canonicalPath, selectOpts, assignOpts)
/// ## Actions ##
/// -------------
/// ### Version information.
if (opts.version) {
println "PIT CLI Version ${VERSION}"
println "Written by Jonathan Bernard\n" }
/// ----
else {
/// Build issue list.
log.trace("Building issue database.")
issuedb = new FileProject(workingDir)
/// Build filter from options.
log.trace("Defining the filter.")
def filter = new Filter(selectOpts)
/// ### List
if (opts.l) {
log.trace("Listing issues.")
/// Local function (closure) to print a single issue.
def printIssue = { issue, offset ->
println "${offset}${issue}"
if (opts.v) {
println ""
issue.text.eachLine { println "${offset} ${it}" }
println ""
issue.extendedProperties.each { name, value ->
def formattedValue = ExtendedPropertyHelp.format(value)
println "${offset} * ${name}: ${formattedValue}"}
println ""}}
/// Local function (closure) to print a project and all visible subprojects.
def printProject
printProject = { project, offset ->
println "\n${offset}${project.name}"
println "${offset}${'-'.multiply(project.name.length())}"
project.eachIssue(filter) { printIssue(it, offset) }
project.eachProject(filter) { printProject(it, offset + " ") } }
/// Print all the issues in the root of this db.
issuedb.eachIssue(filter) { printIssue(it, "") }
/// If the user set the recursive flag print all projects.
if (opts.R) {
issuedb.eachProject(filter) { printProject(it, "") }} }
/// ### Daily List
else if (opts.D) {
log.trace("Showing a daily list.")
/// Set up our time intervals.
def today = new DateMidnight()
def tomorrow = today.plusDays(1)
/// #### Parse daily list specific display options.
def visibleSections = []
def suppressedSections
def upcomingCutoff = today.plusDays(7)
/// Check for a custom upcoming section cutoff date.
if (opts.'dl-upcoming-days') {
int numDays = opts.'dl-upcoming-days' as int
upcomingCutoff = today.plusDays(numDays) }
/// Parse the additive options first.
if (opts.'dl-scheduled') { visibleSections << 'scheduled' }
if (opts.'dl-due') { visibleSections << 'due' }
if (opts.'dl-upcoming') { visibleSections << 'upcoming' }
if (opts.'dl-open') { visibleSections << 'open' }
/// If the user did not add any sections assume they want them all.
if (visibleSections.size() == 0) {
visibleSections = ['scheduled', 'due', 'upcoming', 'open'] }
/// Now go through the negative options.
if (opts.'dl-hide-scheduled') { visibleSections -= 'scheduled' }
if (opts.'dl-hide-due') { visibleSections -= 'due' }
if (opts.'dl-hide-upcoming') { visibleSections -= 'upcoming' }
if (opts.'dl-hide-open') { visibleSections -= 'open' }
/// If the user did not specifically ask for a status filter, we want a
/// different default filter when we are doing a daily list.
if (!opts.s) { filter.status = [Status.NEW, Status.VALIDATION_REQUIRED] }
/// If the user did not give a specific sorting order, define our own: due
/// date, then priority, then id.
if (!opts.o) { filter.issueSorter = [ {it.due}, {it.priority}, {it.id} ] }
/// #### Get all the issues involved.
def allIssues = opts.R ?
/// If `-R` passed, get all issues, including subprojects.
issuedb.getAllIssues(filter) :
/// Otherwise, just use the issues for this project.
issuedb.issues.values().findAll { filter ? filter.accept(it) : true }
/// We are going to sort the issues into these buckets based on when they are
/// scheduled, when they are due and if they have a reminder set.
def scheduledToday = []
def dueToday = []
def upcoming = []
def notDueOrReminder = []
/// Helper closure to print an issue.
def printIssue = { issue ->
if (issue.due) println "${issue.due.toString('EEE, MM/dd')} -- ${issue}"
else println " -- ${issue}" }
/// A sorter which sorts by date first, then by priority.
def priorityDateSorter = { i1, i2 ->
if (i1.priority == i2.priority) {
def d1 = i1.due ?: new DateTime()
def d2 = i2.due ?: new DateTime()
return d1.compareTo(d2) }
else { return i1.priority - i2.priority }}
/// #### Categorize and sort the issues.
/// Sort the issues into seperate lists based on their due dates and
/// reminders.
allIssues.each { issue ->
/// * Find the issues that are scheduled for today.
if (issue.scheduled && issue.scheduled < tomorrow) {
scheduledToday << issue }
/// * Find the issues that are due today or are past due.
else if (issue.due && issue.due < tomorrow) { dueToday << issue }
/// * Find the issues that are not yet due but have a reminder for
/// today or days past as well as issues that are due before the
/// `upcomingCutoff` date.
else if ((issue.reminder && issue.reminder < tomorrow) ||
(issue.due && issue.due < upcomingCutoff)) {
upcoming << issue }
/// * All the others (not due and no reminder).
else notDueOrReminder << issue }
/// #### Print the issues
if (visibleSections.contains('scheduled') && scheduledToday.size() > 0) {
println "Tasks Scheduled for Today"
println "-------------------------"
scheduledToday.sort(priorityDateSorter).each { printIssue(it) }
println "" }
if (visibleSections.contains('due') && dueToday.size() > 0) {
println "Tasks Due Today"
println "---------------"
dueToday.sort(priorityDateSorter).each { printIssue(it) }
println ""}
if (visibleSections.contains('upcoming') && upcoming.size() > 0) {
println "Upcoming Tasks"
println "--------------"
upcoming.sort(priorityDateSorter).each { printIssue(it) }
println ""}
if (visibleSections.contains('open') && notDueOrReminder.size() > 0) {
println "Other Open Issues"
println "-----------------"
notDueOrReminder.sort(priorityDateSorter).each { printIssue(it) }
println "" }}
/// ### Create a New Issue.
else if (opts.n) {
log.trace("Creating a new issue.")
Issue issue
def sin = System.in.newReader()
/// Set the created extended property.
assignOpts.created = new DateTime()
/// Prompt for the different options if they were not given on the command
/// line. We will loop until they have entered a valid value. How it works:
/// In the body of the loop we will try to read the input, parse it and
/// assign it to a variable. If the input is invalid it will throw an
/// exception before the assignment happens, the variable will still be
/// null, and we will prompt the user again.
/// Prompt for category.
while(!assignOpts.category) {
try {
print "Category (bug, feature, task): "
assignOpts.category = Category.toCategory(sin.readLine())
break }
catch (e) {
println "Invalid category: " + e.getLocalizedMessage()
println "Valid options are: \n${Category.values().join(', ')}"
println " (abbreviations are accepted)." } }
/// Prompt for the priority.
while (!assignOpts.priority) {
try {
print "Priority (0-9): "
assignOpts.priority = max(0, min(9, sin.readLine().toInteger()))
break }
catch (e) { println "Not a valid value." } }
/// Prompt for the issue title. No need to loop as the input does not need
/// to be validated.
if (!assignOpts.title) {
println "Issue title: "
assignOpts.title = sin.readLine().trim() }
/// Prompt for the issue text.
if (!assignOpts.text) {
assignOpts.text = ""
println "Enter issue text (use EOF to stop): "
try {
def line = ""
while(true) {
line = sin.readLine()
// Stop when they enter EOF
if (line ==~ /^EOF$/) break
assignOpts.text += line + EOL } }
catch (e) {} }
/// Create the issue.
issue = issuedb.createNewIssue(assignOpts)
println "New issue created: "
println issue }
/// ### Change Existing Issues.
else if (assignOpts.size() > 0) {
log.trace("Changing existing issues.")
/// We are going to add some extra properties if the status is being changed,
/// because we are nice like that.
if (assignOpts.status) { switch (assignOpts.status) {
case Status.RESOLVED: assignOpts.resolved = new DateTime(); break
case Status.REJECTED: assignOpts.rejected = new DateTime(); break
default: break }}
/// #### processIssue
/// A local function to handle the changes for one issue.
def processIssue = { issue ->
println issue
/// Walk the assigned options map and set the properties on the issue.
assignOpts.each { propName, value ->
issue[propName] = value
def formattedValue = ExtendedPropertyHelp.format(value)
println " set ${propName} to ${formattedValue}" } }
/// If the user passed `-R`, walk the whole project, including subprojects.
if (opts.R) { issuedb.walkProject(filter, processIssue) }
/// Otherwise, just process the issues in this project.
else {
issuedb.issues.values()
.findAll { filter ? filter.accept(it) : true }
.each(processIssue) }}
/// ### Invalid Input
else {
log.trace("Unknown request.")
cli.usage(); return -1 }}

View File

@ -1,6 +0,0 @@
#Griffon Metadata file
#Thu Aug 05 10:29:59 CDT 2010
app.archetype=default
app.griffon.version=0.9
app.name=pit-swing
app.version=2.5.1

View File

@ -1,33 +0,0 @@
application {
title = 'PitSwing'
startupGroups = ['PIT']
// Should Griffon exit when no Griffon created frames are showing?
autoShutdown = true
// If you want some non-standard application class, apply it here
//frameClass = 'javax.swing.JFrame'
}
mvcGroups {
// MVC Group for "ProjectPanel"
'ProjectPanel' {
model = 'com.jdbernard.pit.swing.ProjectPanelModel'
view = 'com.jdbernard.pit.swing.ProjectPanelView'
controller = 'com.jdbernard.pit.swing.ProjectPanelController'
}
// MVC Group for "NewIssueDialog"
'NewIssueDialog' {
model = 'com.jdbernard.pit.swing.NewIssueDialogModel'
view = 'com.jdbernard.pit.swing.NewIssueDialogView'
controller = 'com.jdbernard.pit.swing.NewIssueDialogController'
}
// MVC Group for "PIT"
'PIT' {
model = 'com.jdbernard.pit.swing.PITModel'
view = 'com.jdbernard.pit.swing.PITView'
controller = 'com.jdbernard.pit.swing.PITController'
}
}

View File

@ -1,135 +0,0 @@
// key signing information
environments {
development {
signingkey {
params {
sigfile = 'GRIFFON'
keystore = "${basedir}/griffon-app/conf/keys/devKeystore"
alias = 'development'
storepass = 'BadStorePassword'
keypass = 'BadKeyPassword'
lazy = true // only sign when unsigned
}
}
}
test {
griffon {
jars {
sign = false
pack = false
}
}
}
production {
signingkey {
params {
sigfile = 'GRIFFON'
keystore = 'CHANGE ME'
alias = 'CHANGE ME'
// NOTE: for production keys it is more secure to rely on key prompting
// no value means we will prompt //storepass = 'BadStorePassword'
// no value means we will prompt //keypass = 'BadKeyPassword'
lazy = false // sign, regardless of existing signatures
}
}
griffon {
jars {
sign = true
pack = true
destDir = "${basedir}/staging"
}
webstart {
codebase = 'CHANGE ME'
}
}
}
}
griffon {
memory {
//max = '64m'
//min = '2m'
//maxPermSize = '64m'
}
jars {
sign = false
pack = false
destDir = "${basedir}/staging"
jarName = "${appName}.jar"
}
extensions {
jarUrls = []
jnlpUrls = []
/*
props {
someProperty = 'someValue'
}
resources {
linux { // windows, macosx, solaris
jars = []
nativelibs = []
props {
someProperty = 'someValue'
}
}
}
*/
}
webstart {
codebase = "${new File(griffon.jars.destDir).toURI().toASCIIString()}"
jnlp = 'application.jnlp'
}
applet {
jnlp = 'applet.jnlp'
html = 'applet.html'
}
}
// required for custom environments
signingkey {
params {
def env = griffon.util.Environment.current.name
sigfile = 'GRIFFON-' + env
keystore = "${basedir}/griffon-app/conf/keys/${env}Keystore"
alias = env
// storepass = 'BadStorePassword'
// keypass = 'BadKeyPassword'
lazy = true // only sign when unsigned
}
}
griffon.project.dependency.resolution = {
// inherit Griffon' default dependencies
inherits("global") {
}
log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
repositories {
griffonPlugins()
griffonHome()
griffonCentral()
// uncomment the below to enable remote dependency resolution
// from public Maven repositories
//mavenLocal()
//mavenCentral()
//mavenRepo "http://snapshots.repository.codehaus.org"
//mavenRepo "http://repository.codehaus.org"
//mavenRepo "http://download.java.net/maven/2/"
//mavenRepo "http://repository.jboss.com/maven2/"
}
dependencies {
// specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
// runtime 'mysql:mysql-connector-java:5.1.5'
}
}
griffon {
doc {
logo = '<a href="http://griffon.codehaus.org" target="_blank"><img alt="The Griffon Framework" src="../img/griffon.png" border="0"/></a>'
sponsorLogo = "<br/>"
footer = "<br/><br/>Made with Griffon (0.9)"
}
}

View File

@ -1,9 +0,0 @@
root {
'groovy.swing.SwingBuilder' {
controller = ['Threading']
view = '*'
}
'griffon.app.ApplicationBuilder' {
view = '*'
}
}

View File

@ -1,19 +0,0 @@
// log4j configuration
log4j {
appender.stdout = 'org.apache.log4j.ConsoleAppender'
appender.'stdout.layout'='org.apache.log4j.PatternLayout'
appender.'stdout.layout.ConversionPattern'='[%r] %c{2} %m%n'
appender.errors = 'org.apache.log4j.FileAppender'
appender.'errors.layout'='org.apache.log4j.PatternLayout'
appender.'errors.layout.ConversionPattern'='[%r] %c{2} %m%n'
appender.'errors.File'='stacktrace.log'
rootLogger='error,stdout'
logger {
griffon='error'
StackTrace='error,errors'
org {
codehaus.griffon.commons='info' // core / classloading
}
}
additivity.StackTrace=false
}

View File

@ -1,5 +0,0 @@
import org.slf4j.LoggerFactory
onNewInstance = { klass, type, instance ->
instance.metaClass.logger = LoggerFactory.getLogger(klass.name)
}

View File

@ -1,44 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
</head>
<body>
<script src="http://java.com/js/deployJava.js"></script>
<script>
var attributes = {id: '@griffonAppName@',
codebase:'@griffonAppCodebase@',
code:'@griffonAppletClass@',
archive:'@appletJars@',
width:'@applet.width@', height:'@applet.height@'} ;
var parameters = {fontSize:16,
java_arguments: "-Djnlp.packEnabled=true",
jnlp_href:'@griffonAppCodebase@/applet.jnlp',
draggable:'true',
image:'griffon.png',
boxmessage:'Loading @griffonAppName@',
boxbgcolor:'#FFFFFF', boxfgcolor:'#000000',
codebase_lookup: 'false'@applet.script.params@} ;
var version = '1.5.0' ;
deployJava.runApplet(attributes, parameters, version);
</script>
<!--
<APPLET CODEBASE='@griffonAppCodebase@'
CODE='@griffonAppletClass@'
ARCHIVE='@appletJars@'
WIDTH='@applet.width@' HEIGHT='@applet.height@'>
<PARAM NAME="java_arguments" VALUE="-Djnlp.packEnabled=true">
<PARAM NAME='jnlp_href' VALUE='@griffonAppCodebase@/applet.jnlp'>
<PARAM NAME='dragggable' VALUE='true'>
<PARAM NAME='image' VALUE='griffon.png'>
<PARAM NAME='boxmessage' VALUE='Loading @griffonAppName@'>
<PARAM NAME='boxbgcolor' VALUE='#FFFFFF'>
<PARAM NAME='boxfgcolor' VALUE='#000000'>
<PARAM NAME='codebase_lookup' VALUE='false'>
@applet.tag.params@
</APPLET>
-->
</body>
</html>

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
<jnlp
version="@griffonAppVersion@"
codebase="@griffonAppCodebase@"
href="@jnlpFileName@"
>
<information>
<title>@griffonAppName@</title>
<vendor>@griffonAppName@</vendor>
<!--<homepage href="http://app.example.com/"/>-->
<!--fallback description-->
<description>@griffonAppName@</description>
<description kind="one-line">@griffonAppName@</description>
<description kind="short">@griffonAppName@</description>
<description kind="tooltip">@griffonAppName@</description>
<!-- fallback icon -->
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
<!-- icon used for splash screen -->
<icon href="griffon.png" kind="splash" width="381" height="123"/>
<!-- icon used in menu -->
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
<!-- icon used on desktop -->
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
<!-- to create shortcuts, uncomment this
<shortcut online="true">
<desktop/>
<menu submenu="@griffonAppName@"/>
</shortcut>
-->
<offline-allowed/>
</information>
<security>
<all-permissions/>
<!--<j2ee-application-client-permissions/>-->
</security>
<resources>
<property name="griffon.runmode" value="applet"/>
<property name="jnlp.packEnabled" value="true"/>
<j2se version="1.5+" @memoryOptions@/>
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
@jnlpJars@
<!-- Add all extra jars below here, or the app may break -->
@jnlpExtensions@
@jnlpProperties@
</resources>
@jnlpResources@
<applet-desc
documentbase="@griffonAppCodebase@"
name="@griffonAppName@Applet"
main-class="@griffonAppletClass@"
width="@applet.width@"
height="@applet.height@">
<!-- params are ignored when referenced from web page for 6u10 -->
<!--<param name="key1" value="value1"/>-->
<!--<param name="key2" value="value2"/>-->
@applet.tag.params@
</applet-desc>
</jnlp>

View File

@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE jnlp SYSTEM "http://java.sun.com/dtd/JNLP-1.5.dtd">
<jnlp
version="@griffonAppVersion@"
codebase="@griffonAppCodebase@"
href="@jnlpFileName@"
>
<information>
<title>@griffonAppName@</title>
<vendor>@griffonAppName@</vendor>
<!--<homepage href="http://app.example.com/"/>-->
<!--fallback description-->
<description>@griffonAppName@</description>
<description kind="one-line">@griffonAppName@</description>
<description kind="short">@griffonAppName@</description>
<description kind="tooltip">@griffonAppName@</description>
<!-- fallback icon -->
<icon href="griffon-icon-48x48.png" kind="default" width="48" height="48"/>
<!-- icon used for splash screen -->
<icon href="griffon.png" kind="splash" width="381" height="123"/>
<!-- icon used in menu -->
<icon href="griffon-icon-16x16.png" kind="shortcut" width="16" height="16"/>
<!-- icon used on desktop -->
<icon href="griffon-icon-32x32.png" kind="shortcut" width="32" height="32"/>
<!-- to create shortcuts, uncomment this
<shortcut online="true">
<desktop/>
<menu submenu="@griffonAppName@"/>
</shortcut>
<offline-allowed/>
-->
</information>
<security>
<all-permissions/>
<!--<j2ee-application-client-permissions/>-->
</security>
<resources>
<property name="griffon.runmode" value="webstart"/>
<property name="jnlp.packEnabled" value="true"/>
<j2se version="1.5+" @memoryOptions@/>
<!-- auto-added jars follow, griffon-rt, app, and groovy -->
@jnlpJars@
<!-- Add all extra jars below here, or the app may break -->
@jnlpExtensions@
@jnlpProperties@
</resources>
@jnlpResources@
<application-desc main-class="@griffonApplicationClass@">
<!-- params are ignored when referenced from web page for 6u10 -->
<!--<param name="key1" value="value1"/>-->
<!--<param name="key2" value="value2"/>-->
@applet.tag.params@
</application-desc>
</jnlp>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,26 +0,0 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
class NewIssueDialogController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
}
def show = {
view.titleTextField.text = ""
model.text = ""
view.categoryComboBox.selectedItem = Category.BUG
model.category = Category.BUG
view.statusComboBox.selectedItem = Status.NEW
model.status = Status.NEW
view.prioritySpinner.setValue(5)
model.priority = 5
view.dialog.visible = true
}
}

View File

@ -1,172 +0,0 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.FileProject
import javax.swing.JFileChooser
import javax.swing.SwingUtilities
class PITController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
model.newIssueDialogMVC = buildMVCGroup('NewIssueDialog')
SwingUtilities.invokeAndWait {
model.issueListRenderer = new IssueTableCellRenderer()
File pitHome, pitrcFile, pitswingrcFile
boolean logDbg = logger.isDebugEnabled()
Properties config = new Properties()
// look for config directory
pitHome = new File(System.getProperty('user.home'), '.pit')
if (logDbg) logger.debug("$pitHome is " +
(pitHome.exists() ? '' : 'not ') + "present.")
// look for general config options
pitrcFile = new File(pitHome, 'pitrc')
if (logDbg) logger.debug("$pitrcFile is " +
(pitrcFile.exists() ? '' : 'not ') + "present.")
// load general config (if present)
if (pitrcFile.exists() && pitrcFile.canRead()) {
pitrcFile.withInputStream { config.load(it) }
if (logDbg) logger.debug("Loaded pitrc")
}
// look for swing specific config
pitswingrcFile = new File(pitHome, 'pitswingrc')
if (logDbg) logger.debug("$pitswingrcFile is " +
(pitswingrcFile.exists() ? '' : 'not ') + "present.")
// load swing specific config (if present)
if (pitswingrcFile.exists() && pitswingrcFile.canRead()) {
pitswingrcFile.withInputStream { config.load(it) }
if (logDbg) logger.debug("Loaded pitswingrc")
}
// Process configurable options
// ----------------------------
if (logDbg) {
logger.debug("Configurable properties:")
config.keySet().each { logger.debug(it) }
}
// add custom category templates
Category.values().each { category ->
def expectedKey = "issue." + category.name().toLowerCase() +
".template"
if (logDbg) logger.debug("Looking for key: $expectedKey")
config.keySet().each { currentKey ->
if (currentKey == expectedKey)
model.templates[(category)] =
config.getProperty(expectedKey, "")
if (logDbg) logger.debug("Template for category $category: '" +
model.templates[(category)] + "'")
}
}
// load custom issueListRenderer
// TODO: not yet supported (maybe no need)
// load initial repositories
if (config.containsKey('initial-repositories')) {
def initRepos = config.getProperty('initial-repositories', '')
initRepos = initRepos.split(/[:;,]/)
initRepos.each { repoPath -> loadProject(new File(repoPath)) }
if (logDbg) logger.debug("Init repos: '$initRepos'")
}
// load custom issue css
if (config.containsKey('issue.display.css')) {
def issueCSS = config.getProperty('issue.display.css', "")
// look for a file relative to the pit home directory
def cssFile
// use short-circuit logic to test several possible locations
if ((cssFile = new File(pitHome, issueCSS)).exists() ||
(cssFile = new File(pitHome.parentFile(), issueCSS)).exists() ||
(cssFile = new File(issueCSS)).exists())
issueCSS = cssFile.text
if (logDbg) logger.debug("CSS for issue display: $issueCSS")
model.issueCSS = issueCSS
}
}
}
void refreshIssues() {
model.projectPanelMVCs.each { title, mvc ->
mvc.controller.refreshIssues()
}
}
def openProject = { evt = null ->
if (view.openDialog.showOpenDialog(view.frame) !=
JFileChooser.APPROVE_OPTIONS) return
loadProject(view.openDialog.selectedFile)
}
def loadProject = { File projectDir ->
def newMVC
// if this is not a valid directory, do nothing
// TODO: log to the user that this is not a valid directory
if (!projectDir.exists() || !projectDir.isDirectory()) return
// create new ProjectPanel MVC
newMVC = buildMVCGroup('ProjectPanel',
mainMVC: [model: model, view: view, controller: this],
newIssueDialogMVC: model.newIssueDialogMVC,
issueCellRenderer: model.issueListRenderer,
issueCSS: model.issueCSS,
rootProject: new FileProject(projectDir))
newMVC.model.id = projectDir.name
// if we already have a tab with this id
if (model.projectPanelMVCs[(newMVC.model.id)]) {
// try using the canonical path
newMVC.model.id = projectDir.canonicalPath
// still not unique?
if (model.projectPanelMVCs[(newMVC.model.id)]) {
// first time this has happened?
if (!model.projectIdMap[(newMVC.model.id)])
model.projectIdMap[(newMVC.model.id)] = 0
// no? increment
else model.projectIdMap[(newMVC.model.id)] =
model.projectIdMap[(newMVC.model.id)] + 1
// use our new, unique id
newMVC.model.id += "-" + model.projectIdMap[(newMVC.model.id)]
}
}
model.projectPanelMVCs[(newMVC.model.id)] = newMVC
view.mainTabbedPane.addTab(newMVC.model.id, newMVC.view.panel)
}
def closeProject = { evt = null ->
model.projectPanelMVCs.remove(view.mainTabbedPane.getTitleAt(
view.mainTabbedPane.selectedIndex))
view.mainTabbedPane.remove(view.mainTabbedPane.selectedComponent)
}
def shutdown = { evt = null ->
app.shutdown()
}
}

View File

@ -1,222 +0,0 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.FileProject
import com.jdbernard.pit.FlatProjectView
import com.jdbernard.pit.Issue
import com.jdbernard.pit.Project
import com.jdbernard.pit.Status
import javax.swing.DefaultListModel
import javax.swing.JOptionPane
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel
import org.dom4j.Document
import org.dom4j.io.OutputFormat
import org.dom4j.io.XMLWriter
import org.nuiton.jrst.JRSTGenerator
import org.nuiton.jrst.JRSTReader
class ProjectPanelController {
// these will be injected by Griffon
def model
def view
def jrstReader
def jrstGen
static URL rst2htmlXSL =
ProjectPanelController.class.getResource("/rst2xhtml.xsl")
void mvcGroupInit(Map args) {
jrstReader = new JRSTReader()
jrstGen = new JRSTGenerator()
refreshProject()
}
/**
* displayProject
* @param project Project to display
*/
void displayProject(Project project) {
if (!project) return
view.issueTextArea.text = ""
view.issueTextDisplay.text = ""
view.issueTextPanelLayout.show(view.issueTextPanel, 'display')
// build a new IssueTableModel if none cached
if (!model.projectTableModels[(project.name)]) {
def itm = new IssueTableModel(project,
model.filter ?: model.mainMVC.model.filter)
itm.categoryIcons = model.mainMVC.model.categoryIcons
itm.statusIcons = model.mainMVC.model.statusIcons
model.projectTableModels[(project.name)] = itm
}
view.issueTable.setModel(model.projectTableModels[(project.name)])
def tcm = view.issueTable.columnModel
tcm.getColumn(0).maxWidth = 24
tcm.getColumn(1).maxWidth = 40
tcm.getColumn(2).maxWidth = 35
if (view.issueTable.model.columnCount == 5)
tcm.getColumn(4).maxWidth = 150
}
void displayIssue(Issue issue) {
if (!issue) return
// hack because binding view.issueTextArea.font to
// mainMVC.model.issueDetailFont causes problems
if (view.issueTextArea.font != model.mainMVC.model.issueDetailFont)
view.issueTextArea.font = model.mainMVC.model.issueDetailFont
view.issueTextArea.text = issue.text
view.issueTextArea.caretPosition = 0
view.issueTextDisplay.text = rst2html(issue.text)
view.issueTextDisplay.caretPosition = 0
view.issueTextPanelLayout.show(view.issueTextPanel, 'display')
}
void showProejctPopup(Project project, def x, def y) {
model.popupProject = project
view.projectPopupMenu.show(view.projectTree, x, y)
}
void showIssuePopup(Issue issue, def x, def y) {
model.popupIssue = issue
view.issuePopupMenu.show(view.issueTable, x, y)
}
void refreshProject() {
if (model.rootProject) {
def rootNode = new DefaultMutableTreeNode()
def flatview = new FlatProjectView('All Issues')
flatview.projects[(model.rootProject.name)] = model.rootProject
rootNode.add(new DefaultMutableTreeNode(flatview))
rootNode.add(makeNodes(model.rootProject))
view.projectTree.model = new DefaultTreeModel(rootNode)
} else {
view.projectTree.model = new DefaultTreeModel(
new DefaultMutableTreeNode())
}
}
void refreshIssues() {
model.projectTableModels.clear()
displayProject(model.selectedProject)
}
def makeNodes(Project project) {
def rootNode = new DefaultMutableTreeNode(project)
project.eachProject(model.filter ?: model.mainMVC.model.filter)
{ rootNode.add(makeNodes(it)) }
return rootNode
}
def newProject = { evt ->
def name = JOptionPane.showInputDialog(model.mainMVC.view.frame,
'Project name:', 'New Project...', JOptionPane.QUESTION_MESSAGE)
def project
if (evt.source == view.newProjectButton)
project = model.selectedProject ?: model.rootProject
else project = model.popupProject ?: model.rootProject
def newProject = project.createNewProject(name)
project.projects[(newProject.name)] = newProject
refreshProject()
}
def deleteProject = { evt ->
def project
if (evt.source == view.deleteProjectButton)
project = model.selectedProject ?: model.rootProject
else project = model.popupProject ?: model.rootProject
project.delete()
model.rootProject = new FileProject(model.rootProject.source)
}
def newIssue = { evt = null ->
model.newIssueDialogMVC.controller.show()
if (model.newIssueDialogMVC.model.accept) {
def nidModel = model.newIssueDialogMVC.model
def issueText = nidModel.text
if (model.mainMVC.model.templates[(nidModel.category)]) {
issueText = model.mainMVC.model.templates[(nidModel.category)]
issueText = issueText.replaceFirst(/TITLE/,
nidModel.text)
}
def issue = model.selectedProject.createNewIssue(
category: nidModel.category,
status: nidModel.status,
priority: nidModel.priority,
text: issueText)
model.projectTableModels[(model.selectedProject.name)] = null
displayProject(model.selectedProject)
}
}
def deleteIssue = { evt ->
def issue
if (evt.source == view.deleteIssueButton)
issue = getSelectedIssue()
else issue = model.popupIssue
model.selectedProject.issues.remove(issue.id)
view.issueTable.model.issues.remove(issue)
issue.delete()
view.issueTable.invalidate()
}
def getSelectedIssue() {
if (view.issueTable.selectionModel.isSelectionEmpty())
return null
return view.issueTable.model.issues[view.issueTable.
convertRowIndexToModel(view.issueTable.selectedRow)]
}
String rst2html(String rst) {
Document doc
StringWriter outString
StringBuilder result = new StringBuilder()
// read the RST in with the RST parser
new StringReader(rst).withReader { doc = jrstReader.read(it) }
// transform to XHTML
doc = jrstGen.transform(doc, rst2htmlXSL)
// write to the StringWriter
outString = new StringWriter()
outString.withWriter { new XMLWriter(it, new OutputFormat("", true)).write(doc) }
// java's embeded html is primitive, we need to massage the results
outString.toString().eachLine { line ->
// remove the XML version and encoding, title element, meta elems
if (line =~ /<\?.*\?>/ || line =~ /<meta.*$/ || line =~ /<title.*$/) { return }
// all other elements, remove all class, xmlns attributes
def m = (line =~ /(<\S+)(\s*(class|xmlns)=".*"\s*)*(\/?>.*)/)
if (m) line = m[0][1] + m[0][4]
result.append(line)
// add in the CSS information to the head
if (line =~ /<head>/) result.append('<style type="text/css">' +
model.issueCSS + '</style>')
}
return result.toString()
}
}

View File

@ -1,24 +0,0 @@
/*
* This script is executed inside the UI thread, so be sure to call
* long running code in another thread.
*
* You have the following options
* - execOutside { // your code }
* - execFuture { // your code }
* - Thread.start { // your code }
*
* You have the following options to run code again inside the UI thread
* - execAsync { // your code }
* - execSync { // your code }
*/
import groovy.swing.SwingBuilder
import griffon.util.GriffonPlatformHelper
import static griffon.util.GriffonApplicationUtils.*
GriffonPlatformHelper.tweakForNativePlatform(app)
SwingBuilder.lookAndFeel('org.pushingpixels.substance.api.skin.SubstanceCremeCoffeeLookAndFeel', 'nimbus', ['metal', [boldFonts: false]])
// make config directory
def confDir = new File(System.getProperty('user.home'), '.pit')
if (!confDir.exists()) confDir.mkdirs()

View File

@ -1,13 +0,0 @@
/*
* This script is executed inside the UI thread, so be sure to call
* long running code in another thread.
*
* You have the following options
* - execOutside { // your code }
* - execFuture { // your code }
* - Thread.start { // your code }
*
* You have the following options to run code again inside the UI thread
* - execAsync { // your code }
* - execSync { // your code }
*/

View File

@ -1,13 +0,0 @@
/*
* This script is executed inside the UI thread, so be sure to call
* long running code in another thread.
*
* You have the following options
* - execOutside { // your code }
* - execFuture { // your code }
* - Thread.start { // your code }
*
* You have the following options to run code again inside the UI thread
* - execAsync { // your code }
* - execSync { // your code }
*/

View File

@ -1,13 +0,0 @@
/*
* This script is executed inside the UI thread, so be sure to call
* long running code in another thread.
*
* You have the following options
* - execOutside { // your code }
* - execFuture { // your code }
* - Thread.start { // your code }
*
* You have the following options to run code again inside the UI thread
* - execAsync { // your code }
* - execSync { // your code }
*/

View File

@ -1,13 +0,0 @@
/*
* This script is executed inside the UI thread, so be sure to call
* long running code in another thread.
*
* You have the following options
* - execOutside { // your code }
* - execFuture { // your code }
* - Thread.start { // your code }
*
* You have the following options to run code again inside the UI thread
* - execAsync { // your code }
* - execSync { // your code }
*/

View File

@ -1,13 +0,0 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import groovy.beans.Bindable
class NewIssueDialogModel {
@Bindable boolean accept
String text
Category category
Status status
int priority
}

View File

@ -1,34 +0,0 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Filter
import com.jdbernard.pit.Issue
import com.jdbernard.pit.Project
import com.jdbernard.pit.Status
import groovy.beans.Bindable
import java.awt.Font
import javax.swing.ImageIcon
class PITModel {
// filter for projects and classes
Filter filter = new Filter(categories: [],
status: [Status.NEW, Status.VALIDATION_REQUIRED])
def issueListRenderer
// map of category -> issue template
Map<Category, String> templates = [:]
String issueCSS = getClass().getResourceAsStream("/default-issue.css").text
Map<Category, ImageIcon> categoryIcons = [:]
Map<Category, ImageIcon> statusIcons = [:]
def newIssueDialogMVC
Map projectPanelMVCs = [:]
Map projectIdMap = [:]
@Bindable Font issueDetailFont = new Font(Font.MONOSPACED, Font.PLAIN, 10)
}

View File

@ -1,29 +0,0 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Filter
import com.jdbernard.pit.Issue
import com.jdbernard.pit.Project
import groovy.beans.Bindable
class ProjectPanelModel {
// other GUI components
def mainMVC
def newIssueDialogMVC
// data owned by this panel
String id
@Bindable Project rootProject
@Bindable Project popupProject = null
@Bindable Project selectedProject = null
@Bindable Issue popupIssue = null
String issueCSS = ""
// cache the models
def projectTableModels = [:]
def issueCellRenderer
// local filter for projects and issues
Filter filter
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 587 B

View File

@ -1,22 +0,0 @@
body {
font-size: small;
}
h1 {
font-size: medium;
text-decoration: underline;
}
h2 {
font-size: small;
}
h3 {
font-size: small;
font-style: italic;
}
h4 {
font-size: small;
font-weight: normal;
font-style: italic;
}
table,th,td{
border-style: solid;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

View File

@ -1,10 +0,0 @@
log4j.rootLogger=DEBUG,stdout,fileout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
log4j.appender.fileout=org.apache.log4j.FileAppender
log4j.appender.fileout.file=pit-swing.log
log4j.appender.fileout.layout=org.apache.log4j.PatternLayout
log4j.appender.fileout.layout.ConversionPattern=%-5p %C %d{DATE}: %m%n
log4j.appender.fileout.threshold=INFO

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

View File

@ -1,495 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/TR/xhtml1/strict">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/document">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="generator" content="JRST http://maven-site.nuiton.org/jrst" />
<title><xsl:value-of select="title"/></title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="comment">
<xsl:comment>
<xsl:text> </xsl:text>
<xsl:apply-templates/>
<xsl:text> </xsl:text>
</xsl:comment>
</xsl:template>
<xsl:template match="title">
<xsl:if test="name(..)='document'">
<h1 class="mainTitle">
<xsl:apply-templates/>
</h1>
</xsl:if>
<xsl:if test="not(name(..)='document')">
<xsl:element name="h{count(ancestor::section) + 1}">
<xsl:attribute name="class">title</xsl:attribute>
<xsl:if test="@refid">
<a class="toc-backref" href="#{@refid}" id="{../@id}"><xsl:apply-templates/></a>
</xsl:if>
<xsl:if test="not(@refid)">
<xsl:apply-templates/>
</xsl:if>
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template match="subtitle">
<xsl:element name="h2">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<!-- just eat it -->
<xsl:template match="substitution_definition">
</xsl:template>
<xsl:template match="docinfo">
<table class="docinfo" frame="void" rules="none">
<col class="docinfo-name" />
<col class="docinfo-content" />
<tbody valign="top">
<xsl:apply-templates/>
</tbody>
</table>
</xsl:template>
<xsl:template match="organization|address|contact|version|revision|status|date|copyright">
<tr>
<th class="docinfo-name">
<xsl:value-of select="name(.)"/> :
</th>
<td class="docinfo-content">
<xsl:apply-templates/>
</td>
</tr>
</xsl:template>
<xsl:template match="author">
<xsl:if test="not(../../authors)">
<tr>
<th class="docinfo-name">
<xsl:value-of select="name(.)"/> :
</th>
<td class="docinfo-content">
<xsl:apply-templates/>
</td>
</tr>
</xsl:if>
<xsl:if test="../../authors">
<xsl:variable name="num" select="position()"/>
<xsl:if test="$num=1">
<tr>
<th class="docinfo-name">
<xsl:value-of select="authors"/>authors :
</th>
<td class="docinfo-content">
<xsl:apply-templates/>
</td>
</tr>
</xsl:if>
<xsl:if test="$num>1">
<tr>
<th>
</th>
<td class="docinfo-content">
<xsl:apply-templates/>
</td>
</tr>
</xsl:if>
</xsl:if>
</xsl:template>
<xsl:template match="transition">
<hr/>
</xsl:template>
<xsl:template match="section">
<a name="{@id}"></a>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="list_item/paragraph[1] | definition_list_item/*/paragraph[1] | field/*/paragraph[1] | option/*/paragraph[1]">
<!--XXX - Unclear how to handle multi-paragraph list items.
| Certainly when they're single paragraphs, we don't want them
| wrapped in a <P> tag. This seems to work okay.
+-->
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="paragraph">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="reference">
<xsl:if test="@refid">
<a href="{@refuri}#{@refid}" id="{@id}"><xsl:apply-templates/></a>
</xsl:if>
<xsl:if test="not(@refid)">
<a href="{@refuri}" id="{@id}"><xsl:apply-templates/></a>
</xsl:if>
</xsl:template>
<xsl:template match="emphasis">
<em><xsl:apply-templates/></em>
</xsl:template>
<xsl:template match="strong">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="literal">
<code><xsl:value-of select="text()"/></code>
</xsl:template>
<xsl:template match="literal_block">
<pre class="literal_block"><xsl:value-of select="text()"/></pre>
</xsl:template>
<xsl:template match="bullet_list">
<ul><xsl:apply-templates/></ul>
</xsl:template>
<xsl:template match="enumerated_list">
<ol>
<xsl:choose>
<xsl:when test="@enumtype='arabic'">
<xsl:attribute name="type">1</xsl:attribute>
</xsl:when>
<xsl:when test="@enumtype='loweralpha'">
<xsl:attribute name="type">a</xsl:attribute>
</xsl:when>
<xsl:when test="@enumtype='upperalpha'">
<xsl:attribute name="type">A</xsl:attribute>
</xsl:when>
<xsl:when test="@enumtype='lowerroman'">
<xsl:attribute name="type">i</xsl:attribute>
</xsl:when>
<xsl:when test="@enumtype='upperroman'">
<xsl:attribute name="type">I</xsl:attribute>
</xsl:when>
</xsl:choose>
<xsl:copy-of select="@start"/>
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="list_item">
<li><xsl:apply-templates/></li>
</xsl:template>
<xsl:template match="field_list">
<div class="field_list"><xsl:apply-templates/></div>
</xsl:template>
<xsl:template match="field">
<xsl:if test="not(../../docinfo)">
<div class="field"><xsl:apply-templates/></div>
</xsl:if>
<xsl:if test="../../docinfo">
<tr>
<th class="docinfo-name">
<xsl:value-of select="field_name/text()"/> :
</th>
<td>
<xsl:apply-templates select="field_body/*"/>
</td>
</tr>
</xsl:if>
</xsl:template>
<xsl:template match="field_name">
<span class="field_name"><xsl:apply-templates/></span>
</xsl:template>
<xsl:template match="field_body">
<span class="field_body"><xsl:apply-templates/></span>
</xsl:template>
<xsl:template match="definition_list">
<dl class="definition_list"><xsl:apply-templates/></dl>
</xsl:template>
<xsl:template match="definition_list_item">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="term">
<dt class="term"><xsl:apply-templates/><xsl:call-template name="classifier"/></dt>
</xsl:template>
<xsl:template name="classifier">
<xsl:for-each select="../classifier">
<span class="classifier"><xsl:apply-templates/></span>
</xsl:for-each>
</xsl:template>
<xsl:template match="classifier">
<!-- do nothing -->
</xsl:template>
<xsl:template match="definition">
<dd class="definition"><xsl:apply-templates/></dd>
</xsl:template>
<xsl:template match="image">
<xsl:choose>
<xsl:when test="(@target) and (@align)">
<div class="align-{@align}" align="{@align}">
<a href="{@target}">
<xsl:call-template name="img" />
</a>
</div>
</xsl:when>
<xsl:when test="@target">
<a href="{@target}">
<xsl:call-template name="img" />
</a>
</xsl:when>
<xsl:when test="@align">
<div class="align-{@align}" align="{@align}">
<xsl:call-template name="img" />
</div>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="img" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="img">
<xsl:element name="img">
<xsl:attribute name="alt"><xsl:value-of select="@alt"/></xsl:attribute>
<xsl:attribute name="src"><xsl:value-of select="@uri"/></xsl:attribute>
<xsl:if test="@width"><xsl:attribute name="width"><xsl:value-of select="@width"/></xsl:attribute></xsl:if>
<xsl:if test="@height"><xsl:attribute name="height"><xsl:value-of select="@height"/></xsl:attribute></xsl:if>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="footer">
<hr/>
<p class="footer"><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="header">
<p class="header"><xsl:apply-templates/></p>
<hr/>
</xsl:template>
<!--
| Table
+-->
<xsl:template match="table">
<table border="1">
<colgroup>
<xsl:apply-templates select="tgroup/colspec"/>
</colgroup>
<xsl:apply-templates select="./tgroup/thead|./tgroup/tbody"/>
</table>
</xsl:template>
<xsl:template match="tgroup/colspec">
<col width="{@colwidth}%"/>
</xsl:template>
<xsl:template match="row">
<tr><xsl:apply-templates/></tr>
</xsl:template>
<xsl:template match="thead">
<thead><xsl:apply-templates/></thead>
</xsl:template>
<xsl:template match="thead/row/entry">
<th>
<xsl:if test="@morecols"><xsl:attribute name="colspan"><xsl:value-of select="@morecols+1"/></xsl:attribute></xsl:if>
<xsl:if test="@morerows"><xsl:attribute name="rowspan"><xsl:value-of select="@morerows+1"/></xsl:attribute></xsl:if>
<xsl:apply-templates/>
</th>
</xsl:template>
<xsl:template match="tbody">
<tbody><xsl:apply-templates/></tbody>
</xsl:template>
<xsl:template match="tbody/row/entry">
<td>
<xsl:if test="@morecols"><xsl:attribute name="colspan"><xsl:value-of select="@morecols+1"/></xsl:attribute></xsl:if>
<xsl:if test="@morerows"><xsl:attribute name="rowspan"><xsl:value-of select="@morerows+1"/></xsl:attribute></xsl:if>
<xsl:apply-templates/>
</td>
</xsl:template>
<xsl:template match="admonition">
<div class="admonition">
<div class="{@class}">
<p class="{title}">
<xsl:apply-templates select="./title"/>
</p>
<p class="body">
<xsl:apply-templates select="child::*[position()>1]"/>
</p>
</div>
</div>
</xsl:template>
<xsl:template match="attention|caution|danger|error|hint|important|note|tip|warning">
<div class="{name(.)}">
<p class="title"><xsl:value-of select="name(.)"/> :</p>
<p class="body">
<xsl:apply-templates/>
</p>
</div>
</xsl:template>
<xsl:template match="block_quote">
<blockquote>
<xsl:if test="./attribution">
<p><xsl:apply-templates select="child::*[position()=1]"/></p>
<p class="attribution">
<xsl:apply-templates select="./attribution"/>
</p>
</xsl:if>
<xsl:if test="not(./attribution)">
<xsl:apply-templates select="child::*"/>
</xsl:if>
</blockquote>
</xsl:template>
<xsl:template match="doctest_block">
<pre class="doctest_block">
<xsl:apply-templates/>
</pre>
</xsl:template>
<xsl:template match="line_block">
<div class="line_block">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="line">
<div class="line">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="sidebar">
<div class="sidebar">
<p class="title">
<xsl:apply-templates select="./title"/>
</p>
<xsl:if test="./subtitle">
<p class="subtitle">
<xsl:apply-templates select="./subtitle"/>
</p>
</xsl:if>
<xsl:choose>
<xsl:when test="./subtitle">
<xsl:apply-templates select="child::*[position()>2]"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="child::*[position()>1]"/>
</xsl:otherwise>
</xsl:choose>
</div>
</xsl:template>
<xsl:template match="topic">
<div class="topic">
<p class="title">
<xsl:apply-templates select="./title"/>
</p>
<xsl:apply-templates select="child::*[position()>1]"/>
</div>
</xsl:template>
<xsl:template match="option_list">
<table class="option_list">
<col class="option" />
<col class="description" />
<tbody valign="top">
<xsl:apply-templates/>
</tbody>
</table>
</xsl:template>
<xsl:template match="option_list_item">
<tr>
<td class="option-group">
<kbd>
<xsl:apply-templates select="./option_group/option"/>
</kbd>
</td>
<td>
<xsl:apply-templates select="./description"/>
</td>
</tr>
</xsl:template>
<xsl:template match="option">
<span class="option">
<xsl:value-of select="option_string/text()"/>
<xsl:value-of select="./option_argument/@delimiter"/>
<xsl:apply-templates select="./option_argument"/>
</span>
</xsl:template>
<xsl:template match="option_argument">
<var>
<xsl:value-of select="text()"/>,
</var>
</xsl:template>
<xsl:template match="footnote">
<table class="footnote" frame="void" id="{@id}" rules="none">
<colgroup>
<col class="label"/>
<col/>
</colgroup>
<tbody valign="top">
<tr>
<td class="label">
<a class="backref" href="#{@backrefs}" name="{id}">
[<xsl:value-of select="label"/>]
</a>
</td>
<td>
<!--
| <xsl:value-of select="child::*[position()>1]"/>
+-->
<xsl:apply-templates select="child::*[position()>1]"/>
</td>
</tr>
</tbody>
</table>
</xsl:template>
<xsl:template match="footnote_reference">
<a class="footnote_reference" href="#{@refid}" id="{@id}" name="{@id}">
[<xsl:value-of select="text()"/>]
</a>
</xsl:template>
</xsl:stylesheet>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 B

Some files were not shown because too many files have changed in this diff Show More