WIP Implementing fancy CLI input handling (not working).
This commit is contained in:
parent
f5ab973b43
commit
6a73b1ca8c
@ -4,10 +4,13 @@ import com.jdbernard.wdiwtlt.ConfigWrapper
|
||||
import com.jdbernard.wdiwtlt.MediaLibrary
|
||||
import com.jdbernard.wdiwtlt.db.ORM
|
||||
import com.jdbernard.io.NonBlockingInputStreamReader
|
||||
import com.jdbernard.util.AnsiEscapeCodeSequence as ANSI
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import org.docopt.Docopt
|
||||
|
||||
import static com.jdbernard.util.AnsiEscapeCodeSequence.*
|
||||
|
||||
public class CommandLineInterface {
|
||||
|
||||
public static final VERSION = "ALPHA"
|
||||
@ -21,6 +24,10 @@ Usage:
|
||||
wdiwtlt --help
|
||||
|
||||
Options:
|
||||
-c --config <config-file>
|
||||
|
||||
Config file for wdiwtlt CLI.
|
||||
|
||||
-L --library-root <root-dir>
|
||||
|
||||
The path to a local media library directory. Providing a local library
|
||||
@ -37,8 +44,27 @@ Options:
|
||||
Configuration:
|
||||
"""
|
||||
|
||||
private Properties cliConfig
|
||||
private MediaLibrary library
|
||||
|
||||
private String[] linesBuffer = new String[1024]
|
||||
private StringBuilder currentLine = new StringBuilder()
|
||||
private int nextAvailableLineIdx = 0
|
||||
private int newLineIdx = 0
|
||||
private String selectedLineIdx = 0
|
||||
private InputStream sin
|
||||
|
||||
private PrintStream out
|
||||
|
||||
private boolean running
|
||||
|
||||
private String titleStyle, normalStyle, promptStyle, errorStyle
|
||||
private String clearRemainingLine = new ANSI().eraseLine(Erase.ToEnd).toString()
|
||||
private String clearLine = new ANSI().eraseLine(Erase.All).toString()
|
||||
|
||||
private String errorMsg = ""
|
||||
private int lastPromptLineCount = 0
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
def opts = new Docopt(DOC).withVersion("wdiwtlt v$VERSION").parse(args)
|
||||
@ -49,22 +75,43 @@ Configuration:
|
||||
System.out.err.println("wdiwtlt: $msg")
|
||||
System.exit(1) }
|
||||
|
||||
def wdiwtltCfg = new ConfigWrapper()
|
||||
// Look for a given CLI config file.
|
||||
def givenCfg = new Properties()
|
||||
File cfgFile
|
||||
if (opts["--config"]) cfgFile = new File(opts["--config"])
|
||||
|
||||
// Get the library root (if local)
|
||||
File libRoot = opts["--library-root"] ?
|
||||
new File(opts["--library-root"]) :
|
||||
new File(wdiwtltCfg.libraryRootPath)
|
||||
if (!cfgFile || !cfgFile.exists() || !cfgFile.isFile())
|
||||
cfgFile = new File("wdiwtlt.cli.properties")
|
||||
|
||||
if (libRoot && (!libRoot.exist() || !libRoot.isDirectory()))
|
||||
if (!cfgFile || !cfgFile.exists() || !cfgFile.isFile())
|
||||
cfgFile = new File(System.getenv().HOME, ".wdiwtlt.cli.properties")
|
||||
|
||||
if (cfgFile.exists() && cfgFile.isFile()) {
|
||||
try { cfgFile.withInputStream { givenCfg.load(it) } }
|
||||
catch (Exception e) { givenCfg.clear() } }
|
||||
|
||||
// Just in case, load up the general WDIWTLT default config service
|
||||
def wdiwtltDefaultConfig = new ConfigWrapper()
|
||||
|
||||
// Find the library root directory (TODO: eventually this should be
|
||||
// enabled only when local mode is chosen).
|
||||
File libRoot = new File(
|
||||
opts["--library-root"] ?:
|
||||
givenCfg[ConfigWrapper.LIBRARY_DIR_KEY] ?:
|
||||
wdiwtltDefaultConfig.libraryRootPath)
|
||||
|
||||
if (libRoot && (!libRoot.exists() || !libRoot.isDirectory()))
|
||||
exitErr("Library root does not exist or is not a directory: " +
|
||||
libRoot.canonicalPath)
|
||||
|
||||
// Get the library database
|
||||
HikariConfig hcfg
|
||||
File dbCfgFile = opts["--database-config"] ?
|
||||
new File(opts["--database-config"]) : null
|
||||
File dbCfgFile = new File(
|
||||
opts["--database-config"] ?:
|
||||
givenCfg[ConfigWrapper.DB_CONFIG_FILE_KEY] ?:
|
||||
"database.properties")
|
||||
|
||||
// Try to load from the given DB config file
|
||||
if (dbCfgFile && dbCfgFile.exists() && dbCfgFile.isFile()) {
|
||||
Properties props = new Properties()
|
||||
try { dbCfgFile.withInputStream { props.load(it) } }
|
||||
@ -72,7 +119,17 @@ Configuration:
|
||||
|
||||
if (props) hcfg = new HikariConfig(props) }
|
||||
|
||||
if (!hcfg) hcfg = wdiwtltCfg.hikariConfig
|
||||
// Look to see if database connection properties are given in the CLI
|
||||
// config file.
|
||||
if (givenCfg && givenCfg["database.config.dataSourceClassName"]) {
|
||||
Properties props = new Properties()
|
||||
props.putAll(givenCfg
|
||||
.findAll { it.key.startsWith("database.config.") }
|
||||
.collectEntries { [it.key[16..-1], it.value] } )
|
||||
if (props) hcfg = new HikariConfig(props) }
|
||||
|
||||
// Fall back to the WDIWTLT general database defaults
|
||||
if (!hcfg) hcfg = wdiwtltDefaultConfig.hikariConfig
|
||||
if (!hcfg) exitErr("Cannot load database configuation.")
|
||||
|
||||
// Create our database instance
|
||||
@ -85,14 +142,170 @@ Configuration:
|
||||
e.localizedMessage) }
|
||||
|
||||
// Create our CLI object
|
||||
def cliInst = new CommandLineInterface(new MediaLibrary(orm, libRoot))
|
||||
def cliInst = new CommandLineInterface(new MediaLibrary(orm, libRoot),
|
||||
System.in, System.out, givenCfg)
|
||||
cliInst.repl()
|
||||
}
|
||||
|
||||
public CommandLineInterface(MediaLibrary library) {
|
||||
public CommandLineInterface(MediaLibrary library, InputStream sin,
|
||||
PrintStream out, Properties cliConfig) {
|
||||
this.cliConfig = cliConfig
|
||||
this.library = library
|
||||
this.out = out
|
||||
this.sin = sin
|
||||
|
||||
setupTextStyles()
|
||||
}
|
||||
|
||||
public void repl(InputStream sin, PrintStream out) {
|
||||
void setupTextStyles() {
|
||||
titleStyle = new ANSI().color(Colors.BLUE, Colors.DEFAULT, true).toString()
|
||||
normalStyle = new ANSI().resetText().toString()
|
||||
promptStyle = new ANSI().color(Colors.YELLOW, Colors.DEFAULT, true).toString()
|
||||
errorStyle = new ANSI().color(Colors.RED, Colors.DEFAULT, true).toString()
|
||||
}
|
||||
|
||||
public void repl() {
|
||||
this.running = true
|
||||
String line = null
|
||||
|
||||
printPrompt()
|
||||
while(this.running) {
|
||||
|
||||
line = readInputLine()
|
||||
if (line != null) {
|
||||
out.flush()
|
||||
errorMsg = ""
|
||||
switch(line) {
|
||||
case ~/quit|exit|\u0004/:
|
||||
running = false
|
||||
break
|
||||
|
||||
default:
|
||||
errorMsg = "Unrecognized command: '$line'"
|
||||
Thread.sleep(200)
|
||||
break
|
||||
}
|
||||
printPrompt(true)
|
||||
} else {
|
||||
printPrompt(true)
|
||||
Thread.sleep(200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printPrompt(boolean redraw = false) {
|
||||
StringBuilder prompt = new StringBuilder()
|
||||
|
||||
if (redraw) prompt.append(new ANSI()
|
||||
.saveCursor()
|
||||
.cursorPrevLine(lastPromptLineCount))
|
||||
.append(clearLine)
|
||||
|
||||
prompt.append(titleStyle)
|
||||
.append("WDIWTLT - v")
|
||||
.append(VERSION)
|
||||
.append("-- ")
|
||||
.append(normalStyle)
|
||||
.append("TODO\n")
|
||||
|
||||
if (errorMsg) prompt
|
||||
.append(clearLine)
|
||||
.append(errorStyle)
|
||||
.append(errorMsg)
|
||||
.append("\n")
|
||||
|
||||
if (redraw) prompt.append(new ANSI().restoreCursor())
|
||||
else prompt.append(promptStyle)
|
||||
.append("> ")
|
||||
.append(normalStyle)
|
||||
|
||||
|
||||
lastPromptLineCount = 1 + (errorMsg ? 1 : 0)
|
||||
|
||||
out.print(prompt.toString())
|
||||
out.flush()
|
||||
}
|
||||
|
||||
private void replaceCurrentLine(String newLine) {
|
||||
String toPrint = new StringBuilder()
|
||||
.append(new ANSI().cursorHorizontalAbsolute(3))
|
||||
.append(clearRemainingLine)
|
||||
.append(newLine)
|
||||
.toString()
|
||||
currentLine = new StringBuilder(newLine)
|
||||
out.print(toPrint)
|
||||
out.flush()
|
||||
}
|
||||
|
||||
private String readInputLine() {
|
||||
int bytesAvailable = sin.available()
|
||||
if (bytesAvailable) {
|
||||
byte[] input = new byte[bytesAvailable]
|
||||
sin.read(input)
|
||||
|
||||
int idx = 0
|
||||
while (idx < input.length) {
|
||||
switch (input[idx]) {
|
||||
case 0x08: // backspace
|
||||
case 0x0B: // enter
|
||||
linesBuffer[newLineIdx] = currentLine.toString()
|
||||
newLineIdx = (newLineIdx + 1) % linesBuffer.length
|
||||
linesBuffer[newLineIdx] = null
|
||||
replaceCurrentLine("")
|
||||
break
|
||||
case 0x1B: // escape (prefix to arrows, etc)
|
||||
// arrows
|
||||
if (input.length > idx + 2 && input[idx + 1] == 0x5B) {
|
||||
switch(input[idx + 2]) {
|
||||
case 0x41: // up
|
||||
int ni = selectedLineIdx
|
||||
ni = (ni - 1 + linesBuffer.length) %
|
||||
linesBuffer.length
|
||||
if (linesBuffer[ni] != null) {
|
||||
selectedLineIdx = ni
|
||||
replaceCurrentLine(linesBuffer[ni]) }
|
||||
else {
|
||||
out.print(new ANSI().cursorBackwards(4) +
|
||||
clearRemainingLine)
|
||||
out.flush() }
|
||||
break
|
||||
case 0x42: // down
|
||||
int ni = selectedLineIdx
|
||||
ni = (ni + 1) % linesBuffer.length
|
||||
if (linesBuffer[ni] != null) {
|
||||
selectedLineIdx = ni
|
||||
replaceCurrentLine(linesBuffer[ni]) }
|
||||
else {
|
||||
out.print(new ANSI().cursorBackwards(4) +
|
||||
clearRemainingLine)
|
||||
out.flush() }
|
||||
break
|
||||
case 0x43: case 0x44: // right & left
|
||||
out.print(new ANSI().cursorBackwards(4) +
|
||||
clearRemainingLine)
|
||||
out.flush()
|
||||
break
|
||||
default: break
|
||||
}
|
||||
|
||||
idx += 2
|
||||
}
|
||||
|
||||
else currentLine.append((char)input[idx])
|
||||
break
|
||||
default:
|
||||
currentLine.append((char)input[idx])
|
||||
break
|
||||
}
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
if (linesBuffer[nextAvailableLineIdx] != null) {
|
||||
String line = linesBuffer[nextAvailableLineIdx]
|
||||
nextAvailableLineIdx =
|
||||
(nextAvailableLineIdx + 1) % linesBuffer.length
|
||||
return line }
|
||||
else return null
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user