Added edit and delete commands.

* Added `edit` which uses the program in $EDITOR to allow the user to edit the
  current timeline marker.
* Added `delete` to remove the current timeline marker from the timeline.
* Added support for using a named TTY device in order to use an interactive
  program ($EDITOR in this case) to take over interaction with the user. There
  is still a problem with this, in that a process by default only has access to
  its controlling TTY device. This mean, for example, that redirecting the
  subprocess input and output to the TTY will fail to work properly if the
  timestamper CLI process is not part of the same process group as the process
  owning the TTY the user is interacting with. This is the case when using
  nailgun: the java process running TimeStamperCLI is not part of the same
  process group as the user's client shell.

  I think TTY device permissions may be alterable, and we can work around this
  by changing the permissions for the current TTY in the launcher script that
  invokes the nailgun client. Needs more investigation.
* Added package build target to create a zip of the CLI standalone installation.
This commit is contained in:
Jonathan Bernard 2013-08-10 01:30:43 -05:00
parent 284a4159d1
commit 76bf676c2c
7 changed files with 91 additions and 13 deletions

View File

@ -4,4 +4,16 @@
<property file="project.properties"/> <property file="project.properties"/>
<import file="jdb-build-1.10.xml"/> <import file="jdb-build-1.10.xml"/>
<target name="package" depends="build">
<property name="package.dir" value="${build.dir}/${name}-${version}"/>
<mkdir dir="${package.dir}/lib"/>
<copy file="${build.dir}/${name}-${version}.${build.number}.jar"
tofile="${package.dir}/${name}-${version}.jar"/>
<copy todir="${package.dir}/lib">
<fileset dir="${build.dir}/lib/runtime/jar"/>
</copy>
<zip basedir="${build.dir}" includes="${name}-${version}/"
destfile="${build.dir}/${name}-${version}.zip"/>
</target>
</project> </project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
#Fri, 09 Aug 2013 11:44:57 -0500 #Sat, 10 Aug 2013 01:38:31 -0500
lib.local=true lib.local=true
name=timestamper-cli name=timestamper-cli
version=0.3 version=0.4
build.number=1 build.number=13

View File

@ -23,12 +23,13 @@ public class TimeStamperCLI {
protected TimelineProperties timelineProperties protected TimelineProperties timelineProperties
protected Timeline timeline protected Timeline timeline
public static final String VERSION = "0.1" public static final String VERSION = "0.4"
protected static def cli = [ protected static def cli = [
'v': [longOpt: 'version'], 'v': [longOpt: 'version'],
'd': [longOpt: 'working-directory', arguments: 1], 'd': [longOpt: 'working-directory', arguments: 1],
't': [longOpt: 'timeline-config', arguments: 1] ] 't': [longOpt: 'timeline-config', arguments: 1],
'tty': [longOpt: 'tty', arguments: 1]]
public static void main(String[] args) { public static void main(String[] args) {
@ -41,17 +42,22 @@ public class TimeStamperCLI {
doMain(nailgunInst, context.args, context.in, context.out, context.err) } doMain(nailgunInst, context.args, context.in, context.out, context.err) }
protected static doMain(TimeStamperCLI inst, String[] args, protected static void doMain(TimeStamperCLI inst, String[] args,
def sin, def out, def err) { def sin, def out, def err) {
//out = new PrintStream(AnsiConsole.wrapOutputStream(out)) //out = new PrintStream(AnsiConsole.wrapOutputStream(out))
def opts = LightOptionParser.parseOptions(cli, args as List) def opts = LightOptionParser.parseOptions(cli, args as List)
File workingDir = new File(opts.d ?: '.') File workingDir = new File(opts.d ?: '.')
String ttyDevice = opts.tty ?: '/dev/tty'
if (opts.v) {
println "TimeStamperCLI v${VERSION}"
return }
if (opts.t) { if (opts.t) {
File propFile = new File(workingDir, opts.t) File propFile = new File(workingDir, opts.t)
inst.showTimeline(propFile, sin, out, err) } inst.showTimeline(propFile, sin, out, err, ttyDevice) }
else if (inst.timeline == null) { else if (inst.timeline == null) {
// Look for .timestamperrc user config file // Look for .timestamperrc user config file
File cfgFile = new File( File cfgFile = new File(
@ -61,13 +67,13 @@ public class TimeStamperCLI {
cfgFile.canonicalPath cfgFile.canonicalPath
else { else {
def cfg = new SmartConfig(cfgFile) def cfg = new SmartConfig(cfgFile)
inst.showTimeline(new File(cfg.lastUsed), sin, out, err) } } inst.showTimeline(new File(cfg.lastUsed), sin, out, err, ttyDevice) } }
else { inst.showTimeline(sin, out, err) } } else { inst.showTimeline(sin, out, err, ttyDevice) } }
public TimeStamperCLI() { } public TimeStamperCLI() { }
public void showTimeline(File timelinePropertiesFile, public void showTimeline(File timelinePropertiesFile,
def sin, def out, def err) { def sin, def out, def err, String ttyDevice) {
if (!timelinePropertiesFile.exists() || if (!timelinePropertiesFile.exists() ||
!timelinePropertiesFile.isFile()) { !timelinePropertiesFile.isFile()) {
@ -79,9 +85,9 @@ public class TimeStamperCLI {
this.timelineProperties = new TimelineProperties(timelinePropertiesFile) this.timelineProperties = new TimelineProperties(timelinePropertiesFile)
this.timeline = timelineProperties.timeline this.timeline = timelineProperties.timeline
showTimeline(sin, out, err) } showTimeline(sin, out, err, ttyDevice) }
public void showTimeline(final def sin, def out, def err) { public void showTimeline(final def sin, def out, def err, String ttyDevice) {
//out.println "" //out.println ""
def currentMarker = timeline.getLastMarker(new Date()) def currentMarker = timeline.getLastMarker(new Date())
@ -136,7 +142,7 @@ public class TimeStamperCLI {
case ~/n|new/: case ~/n|new/:
// Read mark // Read mark
out.println(ansi().fg(YELLOW).a("New timestamp:").reset()) out.println(ansi().fg(YELLOW).a("New timestamp mark:").reset())
String mark = blockingReadLine() String mark = blockingReadLine()
// Read notes // Read notes
@ -168,6 +174,21 @@ public class TimeStamperCLI {
currentMarker = timeline.getLastMarker(new Date()) currentMarker = timeline.getLastMarker(new Date())
break break
case ~/e|ed|edit/:
reader.pause()
out.println(ansi().fg(YELLOW).
a("Press ENTER to launch an editor for the current marker..."))
out.flush()
blockingReadLine()
currentMarker = edit(currentMarker, ttyDevice)
reader.resume()
break
case ~/d|del|delete/:
timeline.removeMarker(currentMarker)
currentMarker = timeline.getLastMarker(new Date())
break
default: default:
String notes = readNotes() String notes = readNotes()
currentMarker = new TimelineMarker(new Date(), line, notes) currentMarker = new TimelineMarker(new Date(), line, notes)
@ -195,6 +216,51 @@ public class TimeStamperCLI {
out.println "" out.println ""
} }
protected TimelineMarker edit(TimelineMarker tm, String ttyDevice) {
File temp = File.createTempFile('timestamp-mark-', '.txt')
temp.withPrintWriter { fout ->
fout.println("""\
# Edit the time, mark, and notes below. Any lines starting with '#' will be
# ignored. When done, save the file and close the editor.""")
fout.println(Timeline.longFormat.format(tm.timestamp))
fout.println(tm.mark)
fout.println("""\
# Everything from the line below to the end of the file will be considered
# notes for this timeline mark.""")
fout.println(tm.notes) }
['sh', '-c', "\$EDITOR ${temp.canonicalPath} <${ttyDevice} >${ttyDevice}"].execute().waitFor()
Thread.sleep(200)
String newTimeStr
String newMark
String newNotes = ""
temp.eachLine { line ->
if (line =~ /^\s*#/) return
if (!newTimeStr) newTimeStr = line
else if (!newMark) newMark = line
else newNotes += line + EOL }
Date newTime
try { newTime = Timeline.longFormat.parse(newTimeStr) }
catch(Exception e) {
out.println(ansi().fg(RED).a("Invalid timestamp format. The " +
"previous timestamp value will be retained."))
newTime = currentMark.timestamp }
timeline.removeMarker(tm)
def newMarker = new TimelineMarker(newTime, newMark, newNotes)
timeline.addMarker(newMarker)
if (timelineProperties.persistOnUpdate)
timelineProperties.save()
return newMarker
}
protected static String formatMarker(TimelineMarker tm) { protected static String formatMarker(TimelineMarker tm) {
return ansi().fgBright(CYAN). return ansi().fgBright(CYAN).
a(Timeline.shortFormat.format(tm.timestamp) + " ").fg(GREEN). a(Timeline.shortFormat.format(tm.timestamp) + " ").fg(GREEN).