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"/>
<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>

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
name=timestamper-cli
version=0.3
build.number=1
version=0.4
build.number=13

View File

@ -23,12 +23,13 @@ public class TimeStamperCLI {
protected TimelineProperties timelineProperties
protected Timeline timeline
public static final String VERSION = "0.1"
public static final String VERSION = "0.4"
protected static def cli = [
'v': [longOpt: 'version'],
'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) {
@ -41,17 +42,22 @@ public class TimeStamperCLI {
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) {
//out = new PrintStream(AnsiConsole.wrapOutputStream(out))
def opts = LightOptionParser.parseOptions(cli, args as List)
File workingDir = new File(opts.d ?: '.')
String ttyDevice = opts.tty ?: '/dev/tty'
if (opts.v) {
println "TimeStamperCLI v${VERSION}"
return }
if (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) {
// Look for .timestamperrc user config file
File cfgFile = new File(
@ -61,13 +67,13 @@ public class TimeStamperCLI {
cfgFile.canonicalPath
else {
def cfg = new SmartConfig(cfgFile)
inst.showTimeline(new File(cfg.lastUsed), sin, out, err) } }
else { inst.showTimeline(sin, out, err) } }
inst.showTimeline(new File(cfg.lastUsed), sin, out, err, ttyDevice) } }
else { inst.showTimeline(sin, out, err, ttyDevice) } }
public TimeStamperCLI() { }
public void showTimeline(File timelinePropertiesFile,
def sin, def out, def err) {
def sin, def out, def err, String ttyDevice) {
if (!timelinePropertiesFile.exists() ||
!timelinePropertiesFile.isFile()) {
@ -79,9 +85,9 @@ public class TimeStamperCLI {
this.timelineProperties = new TimelineProperties(timelinePropertiesFile)
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 ""
def currentMarker = timeline.getLastMarker(new Date())
@ -136,7 +142,7 @@ public class TimeStamperCLI {
case ~/n|new/:
// Read mark
out.println(ansi().fg(YELLOW).a("New timestamp:").reset())
out.println(ansi().fg(YELLOW).a("New timestamp mark:").reset())
String mark = blockingReadLine()
// Read notes
@ -168,6 +174,21 @@ public class TimeStamperCLI {
currentMarker = timeline.getLastMarker(new Date())
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:
String notes = readNotes()
currentMarker = new TimelineMarker(new Date(), line, notes)
@ -195,6 +216,51 @@ public class TimeStamperCLI {
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) {
return ansi().fgBright(CYAN).
a(Timeline.shortFormat.format(tm.timestamp) + " ").fg(GREEN).