#DajSiePoznac


Kamil Dąbrowski




full stack dev?

prof: webdev
hobby: webdev
hobby: gamedev

The Console


JavaScript'able shell for everybody?

The Idea

  1. recurring operations
    • currency exchange rates
    • calculate md5 hash of text or file
    • reminder
    • ...everything else!
    • it's best to script it
  2. something handy...

something handy

Proof of Concept

The Console

Features:

  • one .js file = command
  • API
  • auto-reload
  • command invocation history
  • command aliasing
  • multiple tabs
  • no command? invoke JS one-liner
  • modules similiar to those in nodejs
  • Command Line Argument Completion
  • custom REPL (Read-Eval-Print Loop)

The Console

help.js

console.log(
	"|\n" +
	"|  Welcome to command line of The Console\n" +
	"|\n" +
	"|   ---- the only console you'll need\n" +
	"|\n\n" +
	"Hit TAB to see command list.\n"
);

The Console

single-file script vs module

script

// some JS code
console.log("Hello")

module

module.exports = {
    onload: function() { },
    onunload: function() { },

    commands: { },

    completeArgument: function(arg) { }

    commandLineHandler: {   // this is REPL
        initContext: function() { },
        handleCompletion: function(input) { },
        handleExecution: function(input, utils, context) { },
        dispose: function() { }
    }   
}

currency.js

assertInfo(args.length != 0,
    "Usages:\n" +
    " - currency 12 gbp eur\n" +
    " - currency gbp eur"
)

var amount = args.length == 3 ? args[0] : 1.0
var from = args[args.length == 3 ? 1 : 0]
var to = args[args.length == 3 ? 2 : 1]

var url = "http://finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s="
 + from.toUpperCase() + to.toUpperCase() + '=X'
var csv = Utils.requestUrl(url)
console.log(csv.split(',')[1] * amount)

currency module

module.exports.commands = {
  currency: function(args) {
    // TODO: put same code as for single-file script
  }
}

filesystem module

var currentDir = "C:/"

exports.commands = {
	pwd: function(args) {
		console.log(currentDir)
	},
	cd: function(args) {
		// TODO: change directory
	},
	ls: function(args) {
		// TODO list files in current directory
	},
	cat: function(args) {
		// TODO: print whole file from given path
		// TODO: the path is relative OR absolute
	}
}

(custom) Argument Completion

module.exports = {
    completeArgument: function(arg) {
    	return Utils.completePath(arg)
    }
}

completeArgument(arg):

  • receives text between caret position and previous space
  • should return an array of results

REPL

module.exports = {
    commandLineHandler: {
        // optional
        initContext: function() { /* ... */ },

        // optional
        dispose: function() { /* ... */ },

        // optional
        handleCompletion: function(input) {
            // TODO: auto-complete
        },

        handleExecution: function(input, utils, context) {
            // TODO: execute given input
        }  
    }
}

REPL: JavaScript eval()

exports.commandLineHandler = {
	handleExecution: function(input, utils, context) {
		this.context.output.addInputEntry(input)
		this.context.output.addTextEntry("" + eval(input))
	}
}

REPL: reversed polish notation

exports.commandLineHandler = {
	handleExecution: function(input, utils, context) {
		context.getOutput().addInputEntry(input)
		context.getOutput().addTextEntry(rpn(input))
	}
}

function rpn(input) {
  var ar = input.split( /\s+/ ), st = [], token
  while (token = ar.shift()) { 
    if (token == +token) {
      st.push( token )
    }
    else {
      var n2 = st.pop(), n1 = st.pop()
      st.push( eval( n1 + token + ' ' + n2 ) )
    }
  }
  return st.pop()
}

REPL: bash

var InputStreamReader = Java.type("java.io.InputStreamReader")
var BufferedReader = Java.type("java.io.BufferedReader")
var ProcessBuilder = Java.type("java.lang.ProcessBuilder")
var Thread = Java.type("java.lang.Thread")
 
var CYGWIN_DIR = "N:/.babun/cygwin/"
 
var pb, process, inputThread, keepAlive
 
exports.commandLineHandler = {
	init: function() {
		pb = new ProcessBuilder(CYGWIN_DIR + "bin/bash.exe")
		pb.redirectErrorStream(true)
 
		var env = pb.environment()
		env.put('Path', env.get('Path') + ";" + CYGWIN_DIR + "bin")
		
		process = pb.start()
		
		keepAlive = true
		inputThread = new Thread(listenToInput.bind(null, this.context))
		inputThread.start()
	},
 
	handleExecution: function(input, utils, context) {
		var os = process.getOutputStream()
 
		// display in console what a user typed in
		context.output.addInputEntry(input)
 
		// send input text to bash
		os.write(input.getBytes())
		os.write(10) // ENTER
		os.flush()
	},
	
	dispose: function() {
		keepAlive = false
		inputThread = null
	}
}

function listenToInput(consoleContext) {
    var input = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"))
    var output = consoleContext.getOutput()

    while (keepAlive) {
        if (!process.isAlive()) {
            // this can be achieved by invoking `exit` command
            output.addErrorEntry("bash process is dead.")

            // bring back standard handler
            consoleContext.commandLineService.resetHandler()
            return
        }

        while (input.ready()) {
            output.addTextEntry(input.readLine())
        }

        // don't consume too much CPU
        Thread.sleep(50)
    }
}

API

args.length
args[0], args[1], ...
Utils.exec(filePath)
Utils.execAsync(filePath)
Utils.getClassName(object)
Utils.openUrl(encodeURI(someUrl))
Utils.requestUrl(url) //HTTP GET
Utils.completePath(absolutePath)
console.log(text)
console.log(text, color)
console.error(text)
console.clear()
console.hide()
console.window
assert(bool, string)
assertInfo(bool, string)
JavaClass(className) // Java: Class.forName(className);
System // Java: System.class

+ Nashorn API

Tech

  • Java 8, Nashorn
  • JavaFx
  • Xtend lang

Nashorn

Xtend

  • promotes itself as a better syntax for JVM
  • transpiles to Java, not bytecode


www.eclipse.org/xtend/

Xtend - The Good

  • works well with existing Java code
  • nice defaults, e.g.:
    • classes and methods are public
    • fields are private
  • type inference
  • synctatic sugar:
    • cool syntax for functional approach
    • auto-conversion between array and ArrayList
    • extension methods
    • getOutput() -> output
    • setInput() -> input=

Xtend - The Bad

  • arrays: no index operator, instead get() and set() methods
  • no bitwise operators, instead bitwiseXor(), bitwiseOr() etc.
  • no pre-increment/pre-decrement operator, only post-*
  • String interpolation:
    '''some string «someVariable»'''
  • no default value for method argument (???)

Xtend - The Bad

val a = #["a", "b"]
val String[] b = #["a", "b"]
compiles to
final List<String> a = Collections.<String>unmodifiableList(
    CollectionLiterals.<String>newArrayList("a", "b"));
	
String[] b = { "a", "b" }

watch out on your performance!




github.com/Namek/TheConsole

(Wiki included)

(Feedback appreciated)