Compare commits

..

No commits in common. "main" and "2.6.0" have entirely different histories.
main ... 2.6.0

131 changed files with 6348 additions and 1908 deletions

4
.gitignore vendored
View File

@ -1,4 +1,2 @@
*.sw*
nimcache/
/pit
/pit_api
*/build/

View File

@ -1,2 +0,0 @@
[tools]
nim = "2.2.0"

138
README.md
View File

@ -1,138 +0,0 @@
# Personal Issue Tracker
This is [Jonathan Bernard's](mailto:jonathan@jdbernard.com) personal issue
tracker. In it's current form it is essentially a way to keep an curated list of
TODO's, organizing them by workflow category (todo, todo-today, dormant, etc.)
and context (Personal, Work, etc.).
## Categories
`pit` organizes issues into the following workflow categories:
- `current` - actively in progress
- `todo` - to be addressed in the future
- `todo-today` - chosen to be addressed today
- `pending` - blocked by some third party
- `dormant` - long-term things I don't want to forget but don't need in front
of me every day.
- `done`
In my typical workflow the `todo` category serves as a collection point for
things I want to keep track of. Then on a a daily basis I review issues in the
`todo` category and move a selection to the `todo-today` category. I also try
to keep the total number of issues in the `todo` below about a dozen. If there
are more than a dozen things in my `todo` category I will identify the lowest
priority items and move them to the `dormant` category.
## Issue Properties
`pit` allows arbitrary properties to be attached to issues in the form of
key-value pairs. On the command line these can be provided via the `-p` or
`--properties` parameter in the form
`-p <prop1Name>:<prop1Value>;<prop2Name>:<prop2Value>[;...]`
There are a couple of properties that pit will recognize automatically:
- `context`: the context organization feature is implemented using issue
properties.
- `created`: `pit` uses this property to timestamp an issue when it is created.
- `completed`: `pit` uses this property to timestamp an issue when it is moved
to the `done` category.
- `pending`: `pit` looks to this property to provide extra information about
issues in the `pending` category. Typically I use this to note who or what is
blocking the issue and why.
Some other common properties I use are:
- `resolution`: for short notes about why an issue was moved to `done`,
especially if it the action wasn't taken or if it is not completely clear
that this issue was completed.
## Configuration Options
`pit` allows configuration via command-line options and via a configuration
file. There is some overlap between the two methods of configuring `pit`, but
it is not a complete mapping.
### Config File
`pit` looks for a JSON configuration file in the following places (in order):
1. From a file path passed on the command line via the `--config <cfgFile>` parameter,
2. `./.pitrc`, in the current working directory,
3. From a file path set in the `PITRC` environment variable.
4. `$HOME/.pitrc`, in the user's home directory.
#### Sample Config File
This example illustrates all of the possible configuration options.
```json
{
"api": {
"apiKeys": [
"50cdcb660554e2d50fd88bd40b6579717bf00643f6ff57f108baf16c8c083f77",
"e4fc1aac49fc1f2f7f4ca6b1f04d41a4ccdd58e13bb53b41da97703d47267ceb",
]
},
"cli": {
"defaultContext": "personal",
"verbose": false,
"termWidth": 120,
"triggerPtk": true
},
"contexts": {
"nla-music": "New Life Music",
"nla-youth-band": "New Life Youth Band",
"acn": "Accenture",
"hff": "Hope Family Fellowship"
},
"tasksDir": "/mnt/c/Users/Jonathan Bernard/synced/tasks"
}
```
#### Explanation of configurable options.
In general, options supplied on the CLI directly will override options supplied
in the configuration file. All options are optional unless stated otherwise.
* `api`: configuration options specific to the API service.
- `apiKeys`: a list of Bearer tokens accepted by the API for the purpose of
authenticating API requests.
* `cli`: configuration options specific to the CLI.
- `defaultContext`: if present all invokations to the CLI will
be in this context. This is like adding a `--context <defaultContext>`
parameter to every CLI invocation. Any actual `--context` parameter will
override this value.
- `verbose`: Show issue details when listing issues (same as
`--verbose` flag).
- `termWidth`: Set the expected width of the terminal (for wrapping text).
- `triggerPtk`: If set to `true`, invoke the `ptk` command to start and stop
timers when issues move to the `current` and `done` categories
respectively.
* `contexts`: `pit` allows issues to be organized into different contexts via
a `context` property on the issue. The CLI groups issues according to
context. When printing contexts the CLI will take the value from the issues'
`context` properties and capatalize it. In some cases you may wish to have a
different display value for a context. I like to use abbreviations for long
context names to reduce the need to type, `hff` for "Hope Family Fellowship",
for example. The `contexts` config option allows you to provide a map of
context values to context display names See the sample file below for an
example.
Note that this mapping does not have to have entries for all contexts, only
those you wish to provide with an alternate display form. For example, in the
configuration sample above the default context is `personal`, a value not
present in the `contexts` configuration. `personal` will be displayed as
"Personal"; it does not need an alternate display name.
* `tasksDir` **required**: a file path to the root directory for the issue
repository (same as `--tasks-dir` CLI parameter).

35
build.xml Executable file
View File

@ -0,0 +1,35 @@
<project name="Personal Issue Tracker" default="package">
<property file="version.properties"/>
<property environment="env"/>
<property
name="libpit.jar"
value="libpit/release/pit-${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">
<mkdir dir="release/lib"/>
<copy file="pit-cli/release/pit-clii-${application.version}.jar" todir="release"/>
<copy file="pit-swing/dist/jar/pit-swing.jar" tofile="release/pit-swing-${application.version}.jar"/>
<copy file="libpit/release/pit-${application.version}.jar" todir="release/lib"/>
</target>
</project>

111
libpit/build.xml Executable file
View File

@ -0,0 +1,111 @@
<project name="Personal Issue Tracker" default="release">
<property file="../version.properties"/>
<property file="project.properties"/>
<property environment="env"/>
<path id="groovy.libs">
<fileset dir="${env.GROOVY_HOME}/lib">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="groovyc.classpath">
<path refid="groovy.libs"/>
<fileset dir="${lib.dir}">
<include name="**/*.jar"/>
</fileset>
<pathelement path="${build.dir}/classes"/>
</path>
<path id="test.classpath">
<path refid="groovyc.classpath"/>
<pathelement path="${build.dir}/tests"/>
</path>
<taskdef name="groovyc"
classname="org.codehaus.groovy.ant.Groovyc"
classpathref="groovy.libs"/>
<target name="init">
<fail
unless="env.GROOVY_HOME"
message="GROOVY_HOME environment variable is not set."/>
<echo message="GROOVY_HOME: ${env.GROOVY_HOME}"/>
</target>
<target name="increment-build-number" depends="init">
<!-- Check to see if the application version has changed.
If it has, reset the build number to 0 -->
<condition property="build.number.final"
value="${build.number}"
else="0" >
<equals
arg1="${application.version}"
arg2="${expected.application.version}"/>
</condition>
<echo message="Version: ${application.version}"/>
<echo message="Build number: ${build.number.final}"/>
<!-- Write the actual application version and build number -->
<propertyfile file="project.properties">
<entry key="build.number" value="${build.number.final}"/>
<entry
key="expected.application.version"
value="${application.version}"/>
</propertyfile>
<!-- increment build number -->
<propertyfile file="project.properties">
<entry key="build.number" operation="+" type="int" default="0"/>
</propertyfile>
<property file="project.properties"/>
</target>
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="compile" depends="init,increment-build-number">
<mkdir dir="${build.dir}/classes"/>
<groovyc
srcdir="${src.dir}"
destdir="${build.dir}/classes"
classpathref="groovyc.classpath"/>
</target>
<target name="compile-tests" depends="init,compile">
<mkdir dir="${build.dir}/tests"/>
<groovyc
srcdir="${test.dir}"
destdir="${build.dir}/tests"
classpathref="groovyc.classpath"/>
</target>
<target name="test" depends="compile-tests">
<junit fork="yes" haltonfailure="yes">
<classpath refid="test.classpath"/>
<formatter type="brief" usefile="false" />
<batchtest>
<fileset dir="${build.dir}/tests">
<include name="**/*Test.class"/>
</fileset>
</batchtest>
</junit>
</target>
<target name="build" depends="compile,test">
<mkdir dir="${build.dir}/jar"/>
<jar
destfile="${build.dir}/jar/pit-${application.version}.${build.number.final}.jar"
basedir="${build.dir}/classes"
compress="on"/>
</target>
<target name="release" depends="build">
<delete dir="${release.dir}"/>
<mkdir dir="${release.dir}"/>
<copy file="${build.dir}/jar/pit-${application.version}.${build.number.final}.jar"
tofile="${release.dir}/${release.jar}"/>
</target>
</project>

11
libpit/project.properties Executable file
View File

@ -0,0 +1,11 @@
#Fri, 21 Oct 2011 16:18:33 -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=11
expected.application.version=2.6.0
lib.dir=lib
release.dir=release
release.jar=pit-${application.version}.jar

Binary file not shown.

1835
libpit/session.vim Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
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

@ -0,0 +1,33 @@
package com.jdbernard.pit
class Filter {
List<Category> categories = null
List<Status> status = null
List<String> projects = null
List<String> ids = null
int priority = 9
boolean acceptProjects = true
Closure issueSorter = defaultIssueSorter
Closure projectSorter = defaultProjectSorter
public static Closure defaultIssueSorter = { it.id.toInteger() }
public static Closure defaultProjectSorter = { it.name }
public boolean accept(Issue i) {
return (i.priority <= priority &&
(!categories || categories.contains(i.category)) &&
(!status || status.contains(i.status)) &&
(!ids || ids.contains(i.id)))
}
public boolean accept(Project p) {
return (acceptProjects &&
(!projects || projects.contains(p.name)))
}
public boolean accept(String name) {
return (acceptProjects &&
(!projects || projects.contains(name)))
}
}

View File

@ -0,0 +1,37 @@
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

@ -0,0 +1,70 @@
package com.jdbernard.pit
import java.lang.IllegalArgumentException as IAE
public abstract class Issue {
protected String id
protected Category category
protected Status status
protected int priority
protected String text
protected Date deliveryDate
protected Date creationDate
Issue(String id, Category c = Category.TASK, Status s = Status.NEW,
int p = 9) {
this.id = id
this.category = c
this.status = s
this.priority = p
this.creationDate = new Date()
this.deliveryDate = null
}
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 text.readLines()[0] }
public String getText() { return text }
public void setText(String t) throws IOException { text = t }
public boolean hasDelivery() { return deliveryDate == null }
public Date getCreationDate() { return creationDate }
public Date getDeliveryDate() { return deliveryDate }
public void setDeliveryDate(Date dd) { deliveryDate = dd }
@Override
public String toString() {
return "${id}(${priority}-${status}): ${category} ${title}"
}
}

View File

@ -0,0 +1,52 @@
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 issues.values().sort(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 projects.values().sort(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) {
List result = this.issues.findAll { filter.accept(it) }
this.eachProject(filter) { p -> result += p.getAllIssues(filter) }
}
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)
}

View File

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

View File

@ -0,0 +1,41 @@
package com.jdbernard.pit
public enum Status {
REASSIGNED('a'),
REJECTED('j'),
NEW('n'),
RESOLVED('s'),
VALIDATION_REQUIRED('v')
String symbol
protected Status(String s) { symbol = s }
public static Status toStatus(String str) {
Status retVal = null
for(status in Status.values()) {
if (status.symbol.equalsIgnoreCase(str) ||
status.name().startsWith(str.toUpperCase())) {
if (retVal != null)
throw new IllegalArgumentException("Request string is" +
" ambigous, '${str}' could represent ${retVal} or " +
"${status}, possibly others.")
retVal = status
}
}
if (retVal == null)
throw new IllegalArgumentException("No status matches '${str}'")
return retVal
}
public String toString() {
def words = name().split("_")
String result = ""
words.each { result += "${it[0]}${it[1..-1].toLowerCase()} " }
return result[0..-2]
}
}

View File

@ -0,0 +1,110 @@
package com.jdbernard.pit.file
import com.jdbernard.pit.*
import java.lang.IllegalArgumentException as IAE
public class FileIssue extends Issue {
protected File source
public static final String fileExp = /(\d+)([bft])([ajnsv])(\d).*/
public FileIssue(File file) {
super('REPLACE_ME')
def matcher = file.name =~ fileExp
if (!matcher)
throw new IllegalArgumentException("${file} " +
"is not a valid Issue file.")
super.@id = matcher[0][1]
super.@category = Category.toCategory(matcher[0][2])
super.@status = Status.toStatus(matcher[0][3])
super.@priority = matcher[0][4].toInteger()
this.source = file
super.@text = file.text
}
public void setCategory(Category c) throws IOException {
boolean renamed
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
makeFilename(id, c, status, priority)))
if (!renamed)
throw new IOException("I was unable to set the category. "
+ "I need to rename the file for this issue, but something is "
+ "preventing me from doing so (maybe the path to the file is "
+ "no longer valid, or maybe the file is currently open in "
+ "some other program).")
else super.setCategory(c)
}
public void setStatus(Status s) throws IOException {
boolean renamed
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
makeFilename(id, category, s, priority)))
if (!renamed)
throw new IOException("I was unable to set the status. "
+ "I need to rename the file for this issue, but something is "
+ "preventing me from doing so (maybe the path to the file is "
+ "no longer valid, or maybe the file is currently open in "
+ "some other program).")
else super.setStatus(s)
}
public void setPriority(int p) throws IOException {
boolean renamed
renamed = source.renameTo(new File(source.canonicalFile.parentFile,
makeFilename(id, category, status, p)))
if (!renamed)
throw new IOException("I was unable to set the priority. "
+ "I need to rename the file for this issue, but something is "
+ "preventing me from doing so (maybe the path to the file is "
+ "no longer valid, or maybe the file is currently open in "
+ "some other program).")
else super.setPriority(p)
}
public String getFilename() {
return makeFilename(id, category, status, priority)
}
public void setText(String text) throws IOException {
try { source.write(text) }
catch (IOException ioe) {
throw new IOException("I could not save the new text for this "
+ "issue. I can not write to the file for this issue. I do not"
+ " know why, I am sorry (maybe the file can not be reached).")
}
super.setText(text)
}
boolean deleteFile() { return source.deleteDir() }
public static boolean isValidFilename(String name) {
return name ==~ fileExp
}
public static String makeFilename(String id, Category category,
Status status, int priority) {
// bounds check priority
priority = Math.min(9, Math.max(0, priority))
//check for valid values of cateogry and id
if (category == null)
throw new IAE("Category must be non-null.")
if (status == null)
throw new IAE("Status must be non-null.")
if (!(id ==~ /\d+/))
throw new IAE( "'${id}' is not a legal value for id.")
return id + category.symbol + status.symbol + priority + ".rst";
}
}

View File

@ -0,0 +1,100 @@
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) {
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')
}
def issueFile = new File(source, FileIssue.makeFilename(id,
options.category, options.status, options.priority))
issueFile.createNewFile()
issueFile.write(options.text)
def issue = new FileIssue(issueFile)
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

@ -0,0 +1,22 @@
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

@ -0,0 +1,36 @@
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

@ -0,0 +1,65 @@
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 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])
this.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()
}
}

View File

@ -0,0 +1,83 @@
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

@ -0,0 +1,47 @@
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

@ -0,0 +1,32 @@
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

@ -0,0 +1,127 @@
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

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

View File

@ -0,0 +1,20 @@
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

@ -0,0 +1,12 @@
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

@ -0,0 +1,54 @@
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

@ -0,0 +1,230 @@
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 , "Add the killer feature to the killer app.\n" +
"=========================================\n\n" +
"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

@ -0,0 +1,162 @@
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

@ -0,0 +1,27 @@
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
}*/
}

3
pit-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
release/
build/
*.sw?

118
pit-cli/build.xml Executable file
View File

@ -0,0 +1,118 @@
<project name="Personal Issue Tracker CLI">
<property file="../version.properties"/>
<property file="project.properties"/>
<property environment="env" />
<path id="groovy.libs">
<fileset dir="${env.GROOVY_HOME}/lib">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="groovy.embeddable">
<fileset dir="${env.GROOVY_HOME}/embeddable">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="project.libs">
<fileset dir="${lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="groovyc.classpath">
<path refid="groovy.libs"/>
<path refid="project.libs"/>
</path>
<path id="package.jars">
<path refid="groovy.embeddable"/>
<path refid="project.libs"/>
</path>
<taskdef name="groovyc"
classname="org.codehaus.groovy.ant.Groovyc"
classpathref="groovy.libs"/>
<target name="init">
<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 ${application.version} library.">
<condition>
<not>
<available
file="${lib.dir}/pit-${application.version}.jar"/>
</not>
</condition>
</fail>
<echo message="PIT library found at ${lib.dir}/pit-${application.version}.jar"/>
<fail message="The PIT project is at version ${application.version} but pit-cli is versioned as ${expected.application.version}. Ensure that pit-cli is updated tp reflect the changes in libpit and then run the 'upgrade-version' task to sync the pit-vli subproject with the PIT project.">
<condition>
<not>
<equals
arg1="${application.version}"
arg2="${expected.application.version}"/>
</not>
</condition>
</fail>
<echo message="Application version: ${application.version}"/>
</target>
<target name="upgrade-version">
<propertyfile file="project.properties">
<entry
key="expected.application.version"
value="${application.version}"/>
<entry key="build.number" value="0"/>
</propertyfile>
<echo message="pit-cli version upgraded to ${application.version}"/>
</target>
<target name="increment-build-number" depends="init">
<propertyfile file="project.properties">
<entry key="build.number" operation="+" type="int" default="0"/>
</propertyfile>
</target>
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="compile" depends="init,increment-build-number">
<mkdir dir="${build.dir}/classes"/>
<groovyc
srcdir="${src.dir}"
destdir="${build.dir}/classes"
classpathref="groovyc.classpath"/>
</target>
<target name="build" depends="compile">
<mkdir dir="${build.dir}/jar"/>
<unjar dest="${build.dir}/classes">
<path refid="package.jars"/>
</unjar>
<jar
destfile="${build.dir}/jar/${build.jar}"
basedir="${build.dir}/classes"
compress="on">
<manifest>
<attribute name="Main-Class" value="${main.class}"/>
</manifest>
</jar>
</target>
<target name="release" depends="build">
<delete dir="${release.dir}"/>
<mkdir dir="${release.dir}"/>
<copy file="${build.dir}/jar/${build.jar}"
tofile="${release.dir}/${release.jar}"/>
</target>
</project>

BIN
pit-cli/lib/commons-cli-1.2.jar Executable file

Binary file not shown.

BIN
pit-cli/lib/pit-2.6.0.jar Normal file

Binary file not shown.

10
pit-cli/project.properties Executable file
View File

@ -0,0 +1,10 @@
#Tue, 25 Oct 2011 11:32:31 -0500
build.dir=build
src.dir=src
build.jar=pit-cli-${application.version}.${build.number}.jar
build.number=13
expected.application.version=2.6.0
lib.dir=lib
release.dir=release
release.jar=pit-cli-${application.version}.jar
main.class=com.jdbernard.pit.PersonalIssueTrackerCLI

View File

@ -0,0 +1,253 @@
package com.jdbernard.pit
import com.jdbernard.pit.file.*
import static java.lang.Math.max
import static java.lang.Math.min
// -------- command-line interface specification -------- //
def cli = new CliBuilder(usage: 'pit-cli [options]')
cli.h(longOpt: 'help', 'Show help information.')
cli.v(longOpt: 'verbose', 'Show verbose task information')
cli.l(longOpt: 'list', 'List issues. Unless otherwise specified it lists all '
+ 'sub projects and all unclosed issue categories.')
cli.i(argName: 'id', longOpt: 'id', args: 1,
'Filter issues by id. Accepts a comma-delimited list.')
cli.c(argName: 'category', longOpt: 'category', args: 1,
'Filter issues by category (bug, feature, task). Accepts a '
+ 'comma-delimited list. By default all categories are selected.')
cli.s(argName: 'status', longOpt: 'status', args: 1,
'Filter issues by status (new, reassigned, rejected, resolved, ' +
'validation_required)')
cli.p(argName: 'priority', longOpt: 'priority', args: 1,
'Filter issues by priority. This acts as a threshhold, listing all issues '
+ 'greater than or equal to the given priority.')
cli.r(argName: 'project', longOpt: 'project', args: 1,
'Filter issues by project (relative to the current directory). Accepts a '
+ 'comma-delimited list.')
/*cli.s(longOpt: 'show-subprojects',
'Include sup projects in listing (default behaviour)')
cli.S(longOpt: 'no-subprojects', 'Do not list subprojects.')*/ // TODO: figure out better flags for these options.
cli.P(argName: 'new-priority', longOpt: 'set-priority', args: 1,
required: false, 'Modify the priority of the selected issues.')
cli.C(argName: 'new-category', longOpt: 'set-category', args: 1,
required: false, 'Modify the category of the selected issues.')
cli.S(argName: 'new-status', longOpt: 'set-status', args: 1,
required: false, 'Modify the status of the selected issues.')
cli.n(longOpt: 'new-issue', 'Create a new issue.')
cli.d(longOpt: 'dir', argName: 'dir', args: 1, required: false,
'Use <dir> as the base directory (defaults to current directory).')
cli._(longOpt: 'version', 'Display PIT version information.')
// -------- parse CLI options -------- //
def VERSION = "2.6.0"
def opts = cli.parse(args)
def issuedb = [:]
def workingDir = new File('.')
// defaults for the issue filter/selector
def selectOpts = [
categories: ['bug', 'feature', 'task'],
status: ['new', 'reassigned', 'rejected',
'resolved', 'validation_required'],
priority: 9,
projects: [],
ids: [],
acceptProjects: true]
// defaults for changing properties of issue(s)
def assignOpts = [
category: Category.TASK,
status: Status.NEW,
priority: 5,
text: "New issue."]
if (!opts) opts.l = true; // default to 'list'
if (opts.h) {
cli.usage()
System.exit(0) }
// read the category filter designation(s)
if (opts.c) {
if (opts.c =~ /all/) {} // no-op, same as defaults
else { selectOpts.categories = opts.c.split(/[,\s]/) } }
// parse the categories names into Category objects
try { selectOpts.categories =
selectOpts.categories.collect { Category.toCategory(it) } }
catch (Exception e) {
println "Invalid category option: '-c ${e.localizedMessage}'."
println "Valid options are: \n${Category.values().join(', ')}"
println " (abbreviations are accepted)."
System.exit(1) }
// read the status filter designation(s)
if (opts.s) {
// -s all
if (opts.s =~ /all/) selectOpts.status = ['new', 'reassigned', 'rejected',
'resolved', 'validation_required']
// is <list>
else selectOpts.status = opts.s.split(/[,\s]/) }
// parse the statuses into Status objects
try { selectOpts.status =
selectOpts.status.collect { Status.toStatus(it) } }
catch (Exception e) {
println "Invalid status option: '-s ${e.localizedMessage}'."
println "Valid options are: \b${Status.values().join(', ')}"
println " (abbreviations are accepted.)"
System.exit(1) }
// read and parse the priority filter
if (opts.p) try {
selectOpts.priority = opts.p.toInteger() }
catch (NumberFormatException nfe) {
println "Not a valid priority value: '-p ${opts.p}'."
println "Valid values are: 0-9"
System.exit(1) }
// read and parse the projects filter
if (opts.r) { selectOpts.projects =
opts.r.toLowerCase().split(/[,\s]/).asType(List.class) }
// read and parse the ids filter
if (opts.i) { selectOpts.ids = opts.i.split(/[,\s]/).asType(List.class) }
// TODO: accept projects value from input
// read and parse the category to assign
if (opts.C) try { assignOpts.category = Category.toCategory(opts.C) }
catch (Exception e) {
println "Invalid category option: '-C ${e.localizedMessage}'."
println "Valid categories are: \n${Category.values().join(', ')}"
println " (abbreviations are accepted)."
System.exit(1) }
// read and parse the status to assign
if (opts.S) try { assignOpts.status = Status.toStatus(opts.S) }
catch (Exception e) {
println "Invalid status option: '-S ${e.localizedMessage}'."
println "Valid stasus options are: \n{Status.values().join(', ')}"
println " (abbreviations are accepted)."
System.exit(1) }
// read and parse the priority to assign
if (opts.P) try {assignOpts.priority = opts.P.toInteger() }
catch (NumberFormatException nfe) {
println "Not a valid priority value: '-P ${opts.P}'."
println "Valid values are: 0-9"
System.exit(1) }
// look for assignment text
if (opts.getArgs().length > 0) {
assignOpts.text = opts.getArgs()[0] }
// set the project working directory
if (opts.d) {
workingDir = new File(opts.d.trim())
if (!workingDir.exists()) {
println "Directory '${workingDir}' does not exist."
return -1 } }
def EOL = System.getProperty('line.separator')
// build issue list
issuedb = new FileProject(workingDir)
// build filter from options
def filter = new Filter(selectOpts)
// -------- Actions -------- //
// list version information first
if (opts.version) {
println "PIT CLI Version ${VERSION}"
println "Written by Jonathan Bernard\n" }
// list second
else if (opts.l) {
// local function (closure) to print a single issue
def printIssue = { issue, offset ->
println "${offset}${issue}"
if (opts.v) {
println ""
issue.text.eachLine { println "${offset} ${it}" }
println "" } }
// local function (closure) to print a project and all visible subprojects
def printProject
printProject = { project, offset ->
println "\n${offset}${project.name}"
println "${offset}${'-'.multiply(project.name.length())}"
project.eachIssue(filter) { printIssue(it, offset) }
project.eachProject(filter) { printProject(it, offset + " ") } }
// print all the issues in the root of this db
issuedb.eachIssue(filter) { printIssue(it, "") }
// print all projects
issuedb.eachProject(filter) { printProject(it, "") } }
// new issues third
else if (opts.n) {
def cat, priority
String text = ""
Issue issue
def sin = System.in.newReader()
if (opts.C) { cat = assignOpts.category }
else while(true) {
try {
print "Category (bug, feature, task, closed): "
cat = Category.toCategory(sin.readLine())
break }
catch (e) {
println "Invalid category: " + e.getLocalizedMessage()
println "Valid options are: \n${Category.values().join(', ')}"
println " (abbreviations are accepted)." } }
if (opts.P) { priority = assignOpts.priority }
else while (true) {
try {
print "Priority (0-9): "
priority = max(0, min(9, sin.readLine().toInteger()))
break }
catch (e) { println "Not a valid value." } }
if (opts.getArgs().length > 0) { text = assignOpts.text }
else {
println "Enter issue (use EOF): "
try {
def line = ""
while(true) {
line = sin.readLine()
if (line =~ /EOF/) break
text += line + EOL
} }
catch (e) {} }
issue = issuedb.createNewIssue(category: cat, priority: priority, text: text)
println "New issue created: "
println issue }
// last, changes to existing issues
else {
// change priority
if (opts.P) issuedb.walkProject(filter) {
it.priority = assignOpts.priority
println "[${it}] -- set priority to ${assignOpts.priority}"}
// change third
else if (opts.C) issuedb.walkProject(filter) {
it.category = assignOpts.cat
println "[${it}] -- set category to ${assignOpts.category}"}
// change status
else if (opts.S) issuedb.walkProject(filter) {
it.status = assignOpts.status
println "[${it}] -- set status to ${assignOpts.status}"}
}

View File

@ -0,0 +1,6 @@
#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

@ -0,0 +1,33 @@
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

@ -0,0 +1,135 @@
// 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

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

View File

@ -0,0 +1,19 @@
// 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

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

View File

@ -0,0 +1,44 @@
<!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

@ -0,0 +1,59 @@
<?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

@ -0,0 +1,54 @@
<?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.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,26 @@
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

@ -0,0 +1,172 @@
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

@ -0,0 +1,222 @@
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

View File

@ -0,0 +1,24 @@
/*
* 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

@ -0,0 +1,13 @@
/*
* 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

@ -0,0 +1,13 @@
/*
* 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

@ -0,0 +1,13 @@
/*
* 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

@ -0,0 +1,13 @@
/*
* 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

@ -0,0 +1,13 @@
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

@ -0,0 +1,34 @@
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

@ -0,0 +1,29 @@
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.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

View File

@ -0,0 +1,22 @@
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.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View File

@ -0,0 +1,10 @@
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.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

View File

@ -0,0 +1,495 @@
<?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.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

View File

@ -0,0 +1,65 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import java.awt.GridBagConstraints as GBC
import javax.swing.DefaultComboBoxModel
dialog = dialog(title: 'New Task...', modal: true, pack: true,
locationRelativeTo: null) {
gridBagLayout()
label('Title/Summary:',
constraints: gbc(gridx: 0, gridy: 0, gridwidth: 3,
insets: [5, 5, 0, 5], fill: GBC.HORIZONTAL))
titleTextField = textField(
constraints: gbc(gridx: 0, gridy: 1, gridwidth: 3,
insets: [0, 10, 0, 5], fill: GBC.HORIZONTAL))
label('Category:',
constraints: gbc(gridx: 0, gridy: 2, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
categoryComboBox = comboBox(
constraints: gbc(gridx: 1, gridy: 2, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: new DefaultComboBoxModel(Category.values()),
editable: false,
itemStateChanged: { model.category = categoryComboBox.selectedItem })
label('Status:',
constraints: gbc(gridx: 0, gridy: 3, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
statusComboBox = comboBox(
constraints: gbc(gridx: 1, gridy: 3, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: new DefaultComboBoxModel(Status.values()),
editable: false,
itemStateChanged: { model.status = statusComboBox.selectedItem })
label('Priority (0-9, 0 is highest priority):',
constraints: gbc(gridx: 0, gridy: 4, insets: [5, 5, 0, 0],
fill: GBC.HORIZONTAL))
prioritySpinner = spinner(
constraints: gbc( gridx: 1, gridy: 4, insets: [5, 5, 0, 5],
fill: GBC.HORIZONTAL),
model: spinnerNumberModel(maximum: 9, minimum: 0),
stateChanged: { model.priority = prioritySpinner.value })
button('Cancel',
actionPerformed: {
model.accept = false
dialog.visible = false
},
constraints: gbc(gridx: 0, gridy: 5, insets: [5, 5, 5, 5],
anchor: GBC.EAST))
button('Create Issue',
actionPerformed: {
model.text = titleTextField.text
model.accept = true
dialog.visible = false
},
constraints: gbc(gridx: 1, gridy: 5, insets: [5, 5, 5, 5],
anchor: GBC.WEST))
}

View File

@ -0,0 +1,167 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import com.jdbernard.pit.Filter
import com.jdbernard.pit.Issue
import com.jdbernard.pit.Project
import com.jdbernard.pit.FileProject
import groovy.beans.Bindable
import java.awt.BorderLayout as BL
import java.awt.Color
import java.awt.GridBagConstraints as GBC
import javax.swing.DefaultComboBoxModel
import javax.swing.DefaultListModel
import javax.swing.JDialog
import javax.swing.JFileChooser
import javax.swing.JOptionPane
import net.miginfocom.swing.MigLayout
actions {
action(
id: 'openProject',
name: 'Open...',
icon: imageIcon('/folder.png'),
accelerator: shortcut('O'),
closure: controller.openProject
)
action(
id: 'closeProject',
name: 'Close',
enabled: bind { projectPanelMVCs.size() > 0 },
closure: controller.closeProject
)
action(
id: 'shutdown',
name: 'Exit',
icon: imageIcon('/shutdown.png'),
accelerator: shortcut('x'),
closure: controller.shutdown
)
}
// initialize category-related view data
Category.values().each {
model.categoryIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
model.filter.categories.add(it)
}
Status.values().each {
model.statusIcons[(it)] = imageIcon("/${it.name().toLowerCase()}.png")
}
frame = application(title: 'Personal Issue Tracker',
minimumSize: [400, 200],
preferredSize: [800, 500],
pack: true,
locationRelativeTo: null,
iconImage: imageIcon('/icon64x64.png').image,
iconImages: [imageIcon('/icon64x64.png').image,
imageIcon('/icon32x32.png').image,
imageIcon('/icon16x16.png').image]
) {
// main menu
menuBar() {
menu("File") {
menuItem(openProject)
menuItem(closeProject)
separator()
menuItem(shutdown)
}
menu("View") {
menu('Category') {
Category.values().each { cat ->
checkBoxMenuItem(cat.toString(),
selected: model.filter.categories.contains(cat),
actionPerformed: {
if (model.filter.categories.contains(cat)) {
model.filter.categories.remove(cat)
evt.source.selected = false
} else {
model.filter.categories.add(cat)
evt.source.selected = true
}
controller.refreshIssues()
})
}
}
menu('Status') {
Status.values().each { st ->
checkBoxMenuItem(st.toString(),
selected: model.filter.status.contains(st),
actionPerformed: { evt ->
if (model.filter.status.contains(st)) {
model.filter.status.remove(st)
evt.source.selected = false
} else {
model.filter.status.add(st)
evt.source.selected = true
}
controller.refreshIssues()
})
}
}
separator()
menuItem('Detail Text Size...',
actionPerformed: {
def newSize = JOptionPane.showInputDialog(frame,
'New text size: ', 'Change Issue Detail Text Size...',
JOptionPane.QUESTION_MESSAGE)
if (newSize == null || !newSize.isFloat())
JOptionPane.showMessageDialog(frame,
'$newSize is not a valid size.',
'Change Issue Detail Size...',
JOptionPane.ERROR_MESSAGE)
else model.issueDetailFont = model.issueDetailFont
.deriveFont(newSize.toFloat())
})
}
menu("Sort") {
sortMenuButtonGroup = buttonGroup()
checkBoxMenuItem('By ID',
buttonGroup: sortMenuButtonGroup,
actionPerformed: {
model.filter.issueSorter = { it.id }
controller.refreshIssues()
})
checkBoxMenuItem('By Category',
buttonGroup: sortMenuButtonGroup,
actionPerformed: {
model.filter.issueSorter = { it.category }
controller.refreshIssues()
})
checkBoxMenuItem('By Status',
buttonGroup: sortMenuButtonGroup,
actionPerformed: {
model.filter.issueSorter = { it.status }
controller.refreshIssues()
})
checkBoxMenuItem('By Priority',
buttonGroup: sortMenuButtonGroup,
actionPerformed: {
model.filter.issueSorter = { it.priority }
controller.refreshIssues()
})
checkBoxMenuItem('By Title',
buttonGroup: sortMenuButtonGroup,
actionPerformed: {
model.filter.issueSorter = { it.title }
controller.refreshIssues()
})
}
}
mainTabbedPane = tabbedPane() {
}
}

View File

@ -0,0 +1,298 @@
package com.jdbernard.pit.swing
import com.jdbernard.pit.Category
import com.jdbernard.pit.Status
import com.jdbernard.pit.Project
import com.jdbernard.pit.FlatProjectView
import java.awt.Font
import java.awt.GridBagConstraints as GBC
import java.awt.Point
import java.awt.event.MouseEvent
import javax.swing.JOptionPane
import javax.swing.JSplitPane
import javax.swing.JLabel
import javax.swing.JScrollPane
import javax.swing.JTable
import javax.swing.JTextField
import javax.swing.ListSelectionModel
import javax.swing.table.TableCellRenderer
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeCellRenderer
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreeSelectionModel
actions {
action (
id: 'newIssue',
name: 'New Issue',
icon: imageIcon("/add.png"),
accelerator: shortcut('N'),
closure: controller.newIssue,
enabled: bind { model.selectedProject != null }
)
action (
id: 'newProject',
name: 'New Project...',
icon: imageIcon("/add.png"),
closure: controller.newProject
)
action (
id: 'deleteProject',
name: 'Delete Project',
closure: controller.deleteProject,
enabled: bind { model.selectedProject != null }
)
action (
id: 'deleteProjectPop',
name: 'Delete Project',
icon: imageIcon("/delete.png"),
closure: controller.deleteProject,
enabled: bind { model.popupProject != null }
)
action (
id: 'deleteIssue',
name: 'Delete Issue',
icon: imageIcon("/delete.png"),
closure: controller.deleteIssue,
)
action (
id: 'deleteIssuePop',
name: 'Delete Issue',
icon: imageIcon("/delete.png"),
closure: controller.deleteIssue,
enabled: bind { model.popupIssue != null }
)
}
// popup menu for projects
projectPopupMenu = popupMenu() {
menuItem(newProject)
menuItem(deleteProjectPop)
}
// popup menu for issues
issuePopupMenu = popupMenu() {
menuItem(newIssue)
menuItem(deleteIssuePop)
separator()
menu('Change Category', enabled: bind { model.popupIssue != null }) {
Category.values().each { category ->
menuItem(category.toString(),
icon: model.mainMVC.model.categoryIcons[(category)],
enabled: bind { model.popupIssue != null },
actionPerformed: {
try {
model.popupIssue.category = category
controller.refreshIssues()
} catch (IOException ioe) {
JOptionPane.showMessage(mainMVC.view.frame,
ioe.getLocalizedMessage(), "Change Category",
JOptionPane.ERROR_MESSAGE)
}
})
}
}
menu('Change Status', enabled: bind { model.popupIssue != null}) {
Status.values().each { status ->
menuItem(status.toString(),
icon: model.mainMVC.model.statusIcons[(status)],
enabled: bind { model.popupIssue != null },
actionPerformed: {
try {
model.popupIssue.status = status
controller.refreshIssues()
} catch (IOException ioe) {
JOptionPane.showMessage(model.mainMVC.view.frame,
ioe.getLocalizedMessage(), 'Change Status',
JOptionPane.ERROR_MESSAGE)
}
})
}
}
menuItem('Change Priority...',
enabled: bind { model.popupIssue != null },
actionPerformed: {
def newPriority = JOptionPane.showInputDialog(mainMVC.view.frame,
'New priority (0-9)', 'Change Priority...',
JOptionPane.QUESTION_MESSAGE)
try { model.popupIssue.priority = newPriority.toInteger() }
catch (NumberFormatException nfe) {
JOptionPane.showMessageDialog(mainMVC.view.frame,
'The priority value must be an integer in [0-9].',
'Change Priority...', JOptionPane.ERROR_MESSAGE)
return
} catch (IOException ioe) {
JOptionPane.showMessageDialog(model.mainMVC.view.frame,
ioe.getLocalizedMessage(), 'Change Priority...',
JOptionPane.ERROR_MESSAGE)
}
controller.refreshIssues()
})
}
// main split view
panel = splitPane(orientation: JSplitPane.HORIZONTAL_SPLIT,
dividerLocation: 200,
oneTouchExpandable: true,
constraints: gbc(fill: GBC.BOTH, insets: [10, 10, 10, 10],
weightx: 2, weighty: 2)) {
// left side (project tree and buttons
panel(constraints: 'left') {
gridBagLayout()
// tree view of projects
scrollPane(constraints: gbc(fill: GBC.BOTH, gridx: 0, gridy: 0,
gridwidth: 2, weightx: 2, weighty: 2)) {
treeCellRenderer = new DefaultTreeCellRenderer()
treeCellRenderer.leafIcon = treeCellRenderer.closedIcon
projectTree = tree(cellRenderer: treeCellRenderer,
model: bind(source: model, sourceProperty: 'rootProject',
sourceValue: {
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(controller.makeNodes(model.rootProject))
new DefaultTreeModel(rootNode)
} else {
new DefaultTreeModel(new DefaultMutableTreeNode())
}
}),
valueChanged: { evt ->
model.selectedProject = evt?.newLeadSelectionPath?.
lastPathComponent?.userObject ?: model.rootProject
controller.displayProject(model.selectedProject)
},
mouseClicked: { evt ->
if (evt.button == MouseEvent.BUTTON3) {
controller.showProjectPopup(
projectTree.getPathForLocation(evt.x, evt.y)?.
lastPathComponent?.userObject,
evt.x, evt.y)
}
})
projectTree.rootVisible = false
projectTree.selectionModel.selectionMode =
TreeSelectionModel.SINGLE_TREE_SELECTION
}
newProjectButton = button(newProject,
constraints: gbc(fill: GBC.NONE, gridx: 0, gridy: 1,
anchor: GBC.WEST))
deleteProjectButton = button(deleteProject,
constraints: gbc(fill: GBC.NONE, gridx: 1, gridy: 1,
anchor: GBC.WEST))
}
// split between issues list and issue details
splitPane(orientation: JSplitPane.VERTICAL_SPLIT,
dividerLocation: 200, constraints: "right") {
panel(constraints: "top") {
gridBagLayout()
scrollPane(constraints: gbc(fill: GBC.BOTH, weightx: 2,
weighty: 2, gridx: 0, gridy: 0, gridwidth: 3)) {
issueTable = table(
autoCreateRowSorter: true,
autoResizeMode: JTable.AUTO_RESIZE_LAST_COLUMN,
cellSelectionEnabled: false,
columnSelectionAllowed: false,
dragEnabled: false,
rowSelectionAllowed: true,
showHorizontalLines: false,
showVerticalLines: false,
mouseClicked: { evt ->
if (evt.button == MouseEvent.BUTTON3) {
def translatedPoint = evt.locationOnScreen.clone()
translatedPoint.translate(-issueTable.locationOnScreen.@x,
-issueTable.locationOnScreen.@y)
def row = issueTable.rowAtPoint(translatedPoint)
issueTable.setRowSelectionInterval(row, row)
controller.showIssuePopup(
controller.getSelectedIssue(), evt.x, evt.y)
}
})
issueTable.setDefaultRenderer(Object.class,
model.issueCellRenderer)
issueTable.selectionModel.valueChanged = { evt ->
if (evt.valueIsAdjusting) return
controller.displayIssue(controller.getSelectedIssue())
}
}
wordWrapCheckBox = checkBox('Word wrap',
constraints: gbc(gridx: 0, gridy: 1, weightx: 2,
anchor: GBC.WEST), selected: true)
button(newIssue,
constraints: gbc(gridx: 1, gridy: 1, anchor: GBC.EAST))
deleteIssueButton = button(deleteIssue,
constraints: gbc(gridx: 2, gridy: 1, anchor: GBC.EAST),
enabled: bind(source: issueTable.selectionModel,
sourceEvent: 'valueChanged',
sourceValue: { !issueTable.selectionModel.isSelectionEmpty() }))
}
scrollPane(constraints: "bottom",
horizontalScrollBarPolicy: JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
issueTextPanel = panel {
issueTextPanelLayout = cardLayout()
def leavingEditorClosure = {
def issue = controller.getSelectedIssue()
if (issue == null) return
if (issueTextArea.text != issue.text) {
issue.text = issueTextArea.text
issueTextDisplay.text = controller.rst2html(
issueTextArea.text)
}
issueTextPanelLayout.show(issueTextPanel, 'display')
}
issueTextArea = textArea(
constraints: 'editor',
wrapStyleWord: true,
lineWrap: bind(source: wordWrapCheckBox,
sourceProperty: 'selected'),
editable: bind(source: issueTable.selectionModel,
sourceEvent: 'valueChanged',
sourceValue:
{ !issueTable.selectionModel.isSelectionEmpty() }),
font: model.mainMVC.model.issueDetailFont,
focusGained: {},
focusLost: leavingEditorClosure,
mouseExited: leavingEditorClosure)
issueTextDisplay = editorPane(contentType: 'text/html',
constraints: 'display',
editable: false,
preferredSize: [10, 10],
mouseClicked: {evt ->
if (evt.clickCount > 1)
issueTextPanelLayout.show(issueTextPanel, 'editor')
})
}
issueTextPanelLayout.show(issueTextPanel, "display")
}
}
}

141
pit-swing/griffonw Executable file
View File

@ -0,0 +1,141 @@
#!/bin/bash
##############################################################################
## ##
## Griffon wrapper script for UN*X ##
## ##
##############################################################################
# Uncomment those lines to set JVM options. GRIFFON_OPTS and JAVA_OPTS can be used together.
# GRIFFON_OPTS="$GRIFFON_OPTS -Xmx512"
# JAVA_OPTS="$JAVA_OPTS -Xmx512"
GRIFFON_APP_NAME=Griffon
warn ( ) {
echo "${PROGNAME}: $*"
}
die ( ) {
warn "$*"
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set JAVA_HOME if it's not already set.
if [ -z "$JAVA_HOME" ] ; then
if $darwin ; then
[ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
[ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
else
javaExecutable="`which javac`"
[ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
[ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
javaExecutable="`readlink -f \"$javaExecutable\"`"
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
export JAVA_HOME="$javaHome"
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
STARTER_MAIN_CLASS=org.gradle.wrapper.GriffonWrapperMain
CLASSPATH=`dirname "$0"`/wrapper/griffon-wrapper.jar
WRAPPER_PROPERTIES=`dirname "$0"`/wrapper/griffon-wrapper.properties
# Determine the Java command to use to start the JVM.
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="java"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
die "JAVA_HOME is not defined correctly, can not execute: $JAVACMD"
fi
if [ -z "$JAVA_HOME" ] ; then
warn "JAVA_HOME environment variable is not set"
fi
# For Darwin, add GRIFFON_APP_NAME to the JAVA_OPTS as -Xdock:name
if $darwin; then
JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRIFFON_APP_NAME"
# we may also want to set -Xdock:image
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRIFFON_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRIFFON_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
"$JAVACMD" $JAVA_OPTS $GRIFFON_OPTS \
-classpath "$CLASSPATH" \
-Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \
$STARTER_MAIN_CLASS \
"$@"

126
pit-swing/griffonw.bat Executable file
View File

@ -0,0 +1,126 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem ##
@rem Griffon startup script for Windows ##
@rem ##
@rem ##########################################################################
@rem
@rem $Revision: 10602 $ $Date: 2008-01-25 02:49:54 +0100 (ven., 25 janv. 2008) $
@rem
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Uncomment those lines to set JVM options. GRIFFON_OPTS and JAVA_OPTS can be used together.
@rem set GRIFFON_OPTS=%GRIFFON_OPTS% -Xmx512
@rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.\
@rem Determine the command interpreter to execute the "CD" later
set COMMAND_COM="cmd.exe"
if exist "%SystemRoot%\system32\cmd.exe" set COMMAND_COM="%SystemRoot%\system32\cmd.exe"
if exist "%SystemRoot%\command.com" set COMMAND_COM="%SystemRoot%\command.com"
@rem Use explicit find.exe to prevent cygwin and others find.exe from being used
set FIND_EXE="find.exe"
if exist "%SystemRoot%\system32\find.exe" set FIND_EXE="%SystemRoot%\system32\find.exe"
if exist "%SystemRoot%\command\find.exe" set FIND_EXE="%SystemRoot%\command\find.exe"
:check_JAVA_HOME
@rem Make sure we have a valid JAVA_HOME
if not "%JAVA_HOME%" == "" goto have_JAVA_HOME
echo.
echo ERROR: Environment variable JAVA_HOME has not been set.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo.
goto end
:have_JAVA_HOME
@rem Validate JAVA_HOME
%COMMAND_COM% /C DIR "%JAVA_HOME%" 2>&1 | %FIND_EXE% /I /C "%JAVA_HOME%" >nul
if not errorlevel 1 goto init
echo.
echo ERROR: JAVA_HOME might be set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation if there are problems.
echo.
:init
@rem get name of script to launch with full path
@rem Get command-line arguments, handling Windowz variants
SET _marker=%JAVA_HOME: =%
@rem IF NOT "%_marker%" == "%JAVA_HOME%" ECHO JAVA_HOME "%JAVA_HOME%" contains spaces. Please change to a location without spaces if this causes problems.
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%eval[2+2]" == "4" goto 4NT_args
IF "%_marker%" == "%JAVA_HOME%" goto :win9xME_args
set _FIXPATH=
call :fixpath "%JAVA_HOME%"
set JAVA_HOME=%_FIXPATH:~1%
goto win9xME_args
:fixpath
if not %1.==. (
for /f "tokens=1* delims=;" %%a in (%1) do (
call :shortfilename "%%a" & call :fixpath "%%b"
)
)
goto :EOF
:shortfilename
for %%i in (%1) do set _FIXPATH=%_FIXPATH%;%%~fsi
goto :EOF
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set STARTER_MAIN_CLASS=org.gradle.wrapper.GriffonWrapperMain
set CLASSPATH=%DIRNAME%\wrapper\griffon-wrapper.jar
set WRAPPER_PROPERTIES=%DIRNAME%\wrapper\griffon-wrapper.properties
set JAVA_EXE=%JAVA_HOME%\bin\java.exe
set GRIFFON_OPTS=%JAVA_OPTS% %GRIFFON_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%"
"%JAVA_EXE%" %GRIFFON_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
rem Set variable GRIFFON_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRIFFON_EXIT_CONSOLE%" exit "%ERRORLEVEL%"
exit /b "%ERRORLEVEL%"
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
pit-swing/lib/dom4j-1.6.1.jar Executable file

Binary file not shown.

BIN
pit-swing/lib/jaxen-1.1.1.jar Executable file

Binary file not shown.

BIN
pit-swing/lib/jrst-1.1.1.jar Executable file

Binary file not shown.

BIN
pit-swing/lib/log4j-1.2.15.jar Executable file

Binary file not shown.

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