Merge CLI project.

This commit is contained in:
Jonathan Bernard 2024-08-04 20:42:37 -05:00
commit 0a49df1828
7 changed files with 369 additions and 0 deletions

20
build.gradle Normal file
View File

@ -0,0 +1,20 @@
apply plugin: "groovy"
apply plugin: "maven"
group = "com.jdblabs.timestamper"
version = "1.2"
repositories {
mavenLocal()
mavenCentral() }
dependencies {
compile 'ch.qos.logback:logback-classic:1.1.2'
compile 'ch.qos.logback:logback-core:1.1.2'
compile 'com.martiansoftware:nailgun-server:0.9.1'
compile 'org.slf4j:slf4j-api:1.7.10'
compile 'com.jdbernard:jdb-util:3.4'
compile 'com.jdblabs.timestamper:timestamper-lib:2.1'
compile files('lib/jansi-1.12-SNAPSHOT.jar')
}

24
build.xml Normal file
View File

@ -0,0 +1,24 @@
<project name="JDB Labs TimeStamper CLI" default="build">
<property environment="env"/>
<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}">
<filterset><filter token="VERSION" value="${version}"/></filterset>
<fileset dir="${resources.dir}/bin"/>
</copy>
<copy todir="${package.dir}/lib">
<fileset dir="${build.dir}/lib/runtime/jar"/>
<fileset dir="${resources.dir}/config"/>
</copy>
<zip basedir="${build.dir}" includes="${name}-${version}/"
destfile="${build.dir}/${name}-${version}.zip"/>
</target>
</project>

BIN
lib/jansi-1.12-SNAPSHOT.jar Normal file

Binary file not shown.

4
resources/bin/ts Executable file
View File

@ -0,0 +1,4 @@
curdir="`pwd`"
cd ~/programs/timestamper-cli-@VERSION@
java -cp "lib:lib/*:./*" com.jdblabs.timestamper.cli.TimeStamperCLI -d "$curdir" "$@"
cd "$curdir"

View File

1
settings.gradle Normal file
View File

@ -0,0 +1 @@
rootProject.name = "timestamper-cli"

View File

@ -0,0 +1,320 @@
package com.jdblabs.timestamper.cli
import com.jdbernard.io.NonBlockingInputStreamReader
import com.jdbernard.util.SmartConfig
import com.jdbernard.util.LightOptionParser
import com.jdblabs.timestamper.core.Timeline
import com.jdblabs.timestamper.core.TimelineMarker
import com.jdblabs.timestamper.core.TimelineProperties
import com.martiansoftware.nailgun.NGContext
import org.fusesource.jansi.AnsiConsole
import static org.fusesource.jansi.Ansi.*
import static org.fusesource.jansi.Ansi.*
import static org.fusesource.jansi.Ansi.Color.*
public class TimeStamperCLI {
private static String EOL = System.getProperty("line.separator")
private static TimeStamperCLI nailgunInst
protected TimelineProperties timelineProperties
protected Timeline timeline
public static final String VERSION = "1.2"
protected static def cli = [
'h': [longName: 'help'],
'v': [longName: 'version'],
'd': [longName: 'working-directory', arguments: 1],
't': [longName: 'timeline-config', arguments: 1],
'tty': [longName: 'tty', arguments: 1]]
public static void main(String[] args) {
TimeStamperCLI inst = new TimeStamperCLI()
doMain(inst, args, System.in, System.out, System.err); }
public static void nailMain(NGContext context) {
if (nailgunInst == null)
nailgunInst = new TimeStamperCLI()
doMain(nailgunInst, context.args, context.in, context.out, context.err) }
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[0] ?: '.')
String ttyDevice = opts.tty ?: '/dev/tty'
if (opts.h) println USAGE
if (opts.v) {
println "TimeStamperCLI v${VERSION}"
println "By JDB Labs (https://www.jdb-labs.com)"
return }
if (opts.t) {
File propFile = new File(opts.t)
if (!propFile.isAbsolute()) propFile = new File(workingDir, opts.t)
inst.showTimeline(propFile, sin, out, err, ttyDevice) }
else if (inst.timeline == null) {
// Look for .timestamperrc user config file
File cfgFile = new File(
System.getProperty('user.home'), ".timestamperrc")
if (!cfgFile.exists() || !cfgFile.isFile())
err.println "Could not find the user configuration file: " +
cfgFile.canonicalPath
else {
def cfg = new SmartConfig(cfgFile)
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, String ttyDevice) {
if (!timelinePropertiesFile.exists() ||
!timelinePropertiesFile.isFile()) {
err.println "No such timeline property file: " +
timelinePropertiesFile.canonicalPath
return }
this.timelineProperties = new TimelineProperties(timelinePropertiesFile)
this.timeline = timelineProperties.timeline
showTimeline(sin, out, err, ttyDevice) }
public void showTimeline(final def sin, def out, def err, String ttyDevice) {
//out.println ""
def currentMarker = timeline.getLastMarker(new Date())
def reader = new NonBlockingInputStreamReader(sin)
Thread readerThread = new Thread(reader)
readerThread.start()
boolean running = true
def blockingReadLine = {
String line = null;
while (line == null && readerThread.isAlive()) {
line = reader.readLine()
Thread.sleep(200) }
return line }
def readNotes = {
out.println(ansi().fg(YELLOW).
a("Notes (end with EOF or a blank line):").reset())
String notes = ""
String line = null
line = blockingReadLine()
while(line != "" && line != "EOF" && line != null) {
notes += line + EOL
line = blockingReadLine() }
return notes }
def printPrompt = {
out.print(formatMarker(currentMarker) + EOL +
ansi().fg(YELLOW).a("> ").reset())
out.flush() }
String line = null
printPrompt()
while (running && readerThread.isAlive()) {
// Handle user input
line = reader.readLine()
if (line != null) {
out.flush();
switch (line) {
case ~/quit|exit|\u0004/:
running = false;
break
case ~/n|new/:
// Read mark
out.println(ansi().fg(YELLOW).a("New timestamp mark:").reset())
String mark = blockingReadLine()
// Read notes
String notes = readNotes();
// Create marker
currentMarker = new TimelineMarker(new Date(), mark, notes)
timeline.addMarker(currentMarker)
if (timelineProperties.persistOnUpdate)
timelineProperties.save()
break
case ~/h|help/:
out.println(ansi().fg(RED).
a("Not yet implemented.").reset());
break
case ~/l|list|history/:
out.println(ansi().fg(RED).
a("Not yet implemented.").reset());
break
case ~/s|save/:
timelineProperties.save()
break
case ~/reload/:
timeline = timelineProperties.reloadTimeline()
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)
timeline.addMarker(currentMarker)
if (timelineProperties.persistOnUpdate)
timelineProperties.save()
break
}
printPrompt()
} else {
out.print(ansi().saveCursorPosition().cursorUpLine().eraseLine().toString() +
formatMarker(currentMarker) +
ansi().cursorDown(1).restorCursorPosition().toString())
out.flush();
Thread.sleep(200)
}
}
this.timelineProperties.syncTargets.each { it.shutdown() }
if (readerThread.isAlive()) {
readerThread.interrupt();
readerThread.join(500);
if (readerThread.isAlive()) readerThread.stop(); }
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).
a("(" + getDuration(tm) + ") ").fg(WHITE).a(tm.mark).toString() }
protected static String getDuration(TimelineMarker tm) {
Date currentTime = new Date()
long seconds = currentTime.time - tm.timestamp.time
seconds /= 1000
long minutes = seconds / 60
seconds = seconds % 60
long hours = minutes / 60
minutes %= 60
long days = hours / 24
hours %= 24
StringBuilder sb = new StringBuilder()
if (days > 0) sb.append(days + "day ")
if (hours > 0) sb.append(hours + "hr ")
if (minutes > 0) sb.append(minutes + "min ")
sb.append(seconds + "sec")
return sb.toString() }
public static final String USAGE = """\
TimeStamperCLI v${VERSION}
By JDB Labs (https://www.jdb-labs.com)
Usage:
ts <OPTIONS>
where OPTIONS is one or more of:
-h, --help Print this usage information.
-v, --version Print version information.
-d, --working-directory Set the application's working direcotry (defaults to
the current directory of the executing process).
-t, --timeline-config Set the timeline configuration file to use to access
the timeline. By default, the value of the
`lastUsed` property in the \$HOME/.timestamperrc
file is used to find the timeline configuration file
to use.
--tty Manually set the name of the TTY device to use. This
defaults to `/dev/tty`.
"""
}