Working on UI using ANSI escape sequences.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,166 @@
|
||||
package com.jdblabs.timestamper.cli
|
||||
|
||||
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 = "0.1"
|
||||
|
||||
protected static def cli = [
|
||||
'v': [longOpt: 'version'],
|
||||
'd': [longOpt: 'working-directory', arguments: 1],
|
||||
't': [longOpt: 'timeline-config', 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 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 ?: '.')
|
||||
|
||||
if (opts.t) {
|
||||
File propFile = new File(workingDir, opts.t)
|
||||
inst.showTimeline(propFile, sin, out, err) }
|
||||
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) } }
|
||||
else { inst.showTimeline(sin, out, err) } }
|
||||
|
||||
public TimeStamperCLI() { }
|
||||
|
||||
public void showTimeline(File timelinePropertiesFile,
|
||||
def sin, def out, def err) {
|
||||
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) }
|
||||
|
||||
public void showTimeline(final def sin, def out, def err) {
|
||||
|
||||
//out.println ""
|
||||
def currentMarker = timeline.getLastMarker(new Date())
|
||||
|
||||
boolean running = true
|
||||
|
||||
def reader = new NonBlockingReader(sin)
|
||||
def readerThread = new Thread(reader)
|
||||
readerThread.start()
|
||||
|
||||
def blockingReadLine = {
|
||||
String line = null;
|
||||
while (line == null && readerThread.isAlive()) {
|
||||
line = reader.readLine()
|
||||
|
||||
Thread.sleep(200) }
|
||||
|
||||
return line }
|
||||
|
||||
String line = null
|
||||
println ""
|
||||
while (running && readerThread.isAlive()) {
|
||||
|
||||
// Refresh the display
|
||||
out.print(
|
||||
ansi().saveCursorPosition().
|
||||
cursorUp(1).eraseLine(Erase.ALL).fgBright(CYAN).
|
||||
a(Timeline.shortFormat.format(currentMarker.timestamp) + " ").
|
||||
fg(GREEN).a("(" + getDuration(currentMarker) + ") ").
|
||||
fg(WHITE).a(currentMarker.mark).a(EOL).restorCursorPosition()
|
||||
)
|
||||
|
||||
// Handle user input
|
||||
if ((line = reader.readLine()) != null) {
|
||||
if (line =~ /quit|exit/) running = false }
|
||||
|
||||
Thread.sleep(200); }
|
||||
|
||||
if (readerThread.isAlive()) {
|
||||
readerThread.interrupt();
|
||||
readerThread.join(500);
|
||||
if (readerThread.isAlive()) readerThread.stop(); }
|
||||
}
|
||||
|
||||
protected 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() }
|
||||
|
||||
class NonBlockingReader implements Runnable {
|
||||
|
||||
private Reader rin
|
||||
private LinkedList buffer = []
|
||||
|
||||
public void run() {
|
||||
String line = null
|
||||
try {
|
||||
while((line = rin.readLine()) != null &&
|
||||
!Thread.currentThread().isInterrupted())
|
||||
storeLine(line) }
|
||||
catch (InterruptedException ie) { Thread.currentThread().interrupt() } }
|
||||
|
||||
public synchronized String readLine() { return buffer.poll() }
|
||||
private synchronized void storeLine(String line) { buffer << line }
|
||||
|
||||
public NonBlockingReader(def sin) {
|
||||
this.rin = new InputStreamReader(sin) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user