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.MediaLibrary
|
||||||
import com.jdbernard.wdiwtlt.db.ORM
|
import com.jdbernard.wdiwtlt.db.ORM
|
||||||
import com.jdbernard.io.NonBlockingInputStreamReader
|
import com.jdbernard.io.NonBlockingInputStreamReader
|
||||||
|
import com.jdbernard.util.AnsiEscapeCodeSequence as ANSI
|
||||||
import com.zaxxer.hikari.HikariConfig
|
import com.zaxxer.hikari.HikariConfig
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import org.docopt.Docopt
|
import org.docopt.Docopt
|
||||||
|
|
||||||
|
import static com.jdbernard.util.AnsiEscapeCodeSequence.*
|
||||||
|
|
||||||
public class CommandLineInterface {
|
public class CommandLineInterface {
|
||||||
|
|
||||||
public static final VERSION = "ALPHA"
|
public static final VERSION = "ALPHA"
|
||||||
@ -21,6 +24,10 @@ Usage:
|
|||||||
wdiwtlt --help
|
wdiwtlt --help
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
-c --config <config-file>
|
||||||
|
|
||||||
|
Config file for wdiwtlt CLI.
|
||||||
|
|
||||||
-L --library-root <root-dir>
|
-L --library-root <root-dir>
|
||||||
|
|
||||||
The path to a local media library directory. Providing a local library
|
The path to a local media library directory. Providing a local library
|
||||||
@ -37,8 +44,27 @@ Options:
|
|||||||
Configuration:
|
Configuration:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
private Properties cliConfig
|
||||||
private MediaLibrary library
|
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) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
def opts = new Docopt(DOC).withVersion("wdiwtlt v$VERSION").parse(args)
|
def opts = new Docopt(DOC).withVersion("wdiwtlt v$VERSION").parse(args)
|
||||||
@ -49,22 +75,43 @@ Configuration:
|
|||||||
System.out.err.println("wdiwtlt: $msg")
|
System.out.err.println("wdiwtlt: $msg")
|
||||||
System.exit(1) }
|
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)
|
if (!cfgFile || !cfgFile.exists() || !cfgFile.isFile())
|
||||||
File libRoot = opts["--library-root"] ?
|
cfgFile = new File("wdiwtlt.cli.properties")
|
||||||
new File(opts["--library-root"]) :
|
|
||||||
new File(wdiwtltCfg.libraryRootPath)
|
|
||||||
|
|
||||||
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: " +
|
exitErr("Library root does not exist or is not a directory: " +
|
||||||
libRoot.canonicalPath)
|
libRoot.canonicalPath)
|
||||||
|
|
||||||
// Get the library database
|
// Get the library database
|
||||||
HikariConfig hcfg
|
HikariConfig hcfg
|
||||||
File dbCfgFile = opts["--database-config"] ?
|
File dbCfgFile = new File(
|
||||||
new File(opts["--database-config"]) : null
|
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()) {
|
if (dbCfgFile && dbCfgFile.exists() && dbCfgFile.isFile()) {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
try { dbCfgFile.withInputStream { props.load(it) } }
|
try { dbCfgFile.withInputStream { props.load(it) } }
|
||||||
@ -72,7 +119,17 @@ Configuration:
|
|||||||
|
|
||||||
if (props) hcfg = new HikariConfig(props) }
|
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.")
|
if (!hcfg) exitErr("Cannot load database configuation.")
|
||||||
|
|
||||||
// Create our database instance
|
// Create our database instance
|
||||||
@ -85,14 +142,170 @@ Configuration:
|
|||||||
e.localizedMessage) }
|
e.localizedMessage) }
|
||||||
|
|
||||||
// Create our CLI object
|
// 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()
|
cliInst.repl()
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandLineInterface(MediaLibrary library) {
|
public CommandLineInterface(MediaLibrary library, InputStream sin,
|
||||||
|
PrintStream out, Properties cliConfig) {
|
||||||
|
this.cliConfig = cliConfig
|
||||||
this.library = library
|
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