Namek Dev
a developer's log
NamekDev

Modules in The Console - filesystem module as an example

May 3, 2016

This time I present a filesystem  module which mimics the simplest cases of shell commands: ls, pwd, cd, cat. The module showcases module commands, scoping, Storage and onload/onunload events.

Module definition

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

Rules are fairly simple here:

  1. onload()  is called every time when module is loaded or reloaded.
  2. onunload()  is called on every reload
  3. commands  is expected to be just an object where keys are commands that are going to be registered in every tab of The Console and values are functions to be called on command invoke

It’s worth noting that reload is automatically triggered when one of a module’s .js file is edited.

Let’s look at filesystem definition:

var File = java.io.File
var Files = java.nio.file.Files
var Paths = java.nio.file.Paths

var scope = {
 Storage: null
}

module.exports = {
	onload: function() {
		scope.Storage = this.Storage.getGlobalStorage("filesystem")
	},
	onunload: function() {
		scope.Storage.save()
	},
	commands: getCommands()
}
  1. In the beginning I import some Java classes.
  2. The scope is just my helper object, note that it is not required to have such.
  3. in onload  I save a “filesystem” global Storage that well be available not just to this module but also to all scripts on all tabs
  4. getCommands()  is a function because it can be laid after module.exports part, while variable declaring an object would have to be put before this part (it’s simply all about order of execution).

Commands

First, some helpers:

function getCurrentPath() {
	return scope.Storage.get("path")
}
	
function setCurrentPath(path) {
	scope.Storage.set("path", path)
}

Now everything goes into function getCommands() { return { /* HERE!! */ } }  which is a function returning an object which will contain commands.

pwd - print current path

'pwd': function(args) {
	assert(args.length == 0, "Please, no args.")
	console.log(getCurrentPath())
},

First, we make sure that user understands that this command does not receive any argument (parameter).

cd - change current path

'cd': function(args) {
	assert(args.length == 1, "gimme one argument!")

	var path = args[0]
	var file = new File(path)

	if (!file.isDirectory() || !file.isAbsolute()) {
		file = new File(getCurrentPath() + '/' + path)
		assert(file.isAbsolute(), String("Given path is not a directory: " + args[0]))
	}

	assert(file.isDirectory(), String("Given path is not a directory: " + args[0]))

	var normalizedPath = Paths.get(file.getCanonicalPath()).normalize().toString()
	setCurrentPath(normalizedPath)
},

This functions is a little more complicated. The algorithm takes this approach:

  1. check if given argument is a full path
  2. if the argument wasn’t a full path then try joining old path with the argument
  3. if anything up to this point is a valid path then remember it by invoking setCurrentPath(path)  helper

A little trick here is file.getCanonicalPath() which helps with getting a path where real letter casing is used, e.g. if you were in C:/  and typed cd windows , then you expect to be in C:\Windows  rather than C:/windows .

ls - list files in current path

'ls': function(args) {
	assert(args.length < 2, "maximum one argument, please")
	var path = args.length == 1 ? args[0] : getCurrentPath()
	assert(!!path, "Path is invalid.")
	console.log(Java.from(new File(path).list()).join('\n'))
},

This command is simplier because when there is an argument then it’s assumed to be global. It could be improved to get a relative path as in cd  command.

cat - print contents of given file

'cat': function(args) {
	assert(args.length == 1, "expected argument: filename")

	path = scope.Storage.get("path")

	assert(!!path, "Current path is not set, please use `cd [path]`")

	var filename = args[0]
	path = String(path)
	var filePath = Paths.get(path).resolve(filename)
	var file = filePath.toFile()

	assert(file.isFile(), String("The path is not a file: " + path))

	console.log(new java.lang.String(Files.readAllBytes(filePath)))
}

Why having modular system?

The reasons behind modules were already described in previous posts. However, I’ll shortly summarize everything that looks like a benefit of having this system:

  • modules can be imported
  • modules can receive various events (even if right now there are only onload  and onunload )
  • a module can contain multiple commands
  • a module have custom scope that will live inside JS environment even after command finishes executing
Daj Się Poznać, the-console
comments powered by Disqus