/*------------------------------------------------------------------------* * Copyright 2013 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *-------------------------------------------------------------------------*/ (function(root, $, _) { Josh.Example = (function(root, $, _) { // Enable console debugging, when Josh.Debug is set and there is a console object on the document root. var _console = (Josh.Debug && root.console) ? root.console : { log: function() { } }; // Setup of Shell // -------------- // build the *fake* directory structure used to illustrate path commands and completions. var treeroot = buildTree(); // Create `History` and `KillRing` by hand since we will use the `KillRing` for an example command. var history = Josh.History(); var killring = new Josh.KillRing(); // Create the `ReadLine` instance by hand so that we can provide it our `KillRing`. Since the shell needs to share // the `History` object with `ReadLine` and `Shell` isn't getting to create `ReadLine` automatically as it usually does // we need to pass in `History` into `ReadLine` as well. var readline = new Josh.ReadLine({history: history, killring: killring, console: _console }); // Finally, create the `Shell`. var shell = Josh.Shell({readline: readline, history: history, console: _console}); // Create *killring* command // ------------------------- // Setup the `Underscore` template for displaying items in the `KillRing`. var killringItemTemplate = _.template("
<% _.each(items, function(item, i) { %>
<%- i %> <%- item %>
<% }); %>
") // Create a the command `killring` which will display all text currently in the `KillRing`, by attaching // a handler to the `Shell`. shell.setCommandHandler("killring", { // We don't implement any completion for the `killring` command, so we only provide an `exec` handler, and no `completion` handler. exec: function(cmd, args, callback) { // `killring` takes one optional argument **-c** which clears the killring (just like **history -c**). if(args[0] == "-c") { killring.clear(); // The callback of an `exec` handler expects the html to display as result of executing the command. Clearing the // killing has no output, so we just call the callback and exit the handler. callback(); return; } // Return the output of feeding all items from the killring into our template. callback(killringItemTemplate({items: killring.items()})); } }); // Setup PathHandler // ----------------- // `PathHandler` is a mix-in for `Shell` to provide provide the standard unix `ls`, `pwd` and `cd` commands, as well // as standard *bash*-style path tab-completion. It expects a `Shell` instance as its first argument so that it can // attach its command handlers to the shell as well as overrride the default handler to support completion of path's // starting with `.` or `/` without a leading command. var pathhandler = new Josh.PathHandler(shell, {console: _console}); // `PathHandler` operates on path nodes which are expected to be objects with the minimum structure of // // { // name: 'localname', // path: '/full/path/to/localname' // } // // where name is the `name` of the node and `path` is the absolute path to the node. PathHandler does not modify // these nodes, so any additional state your implementation requires can be attached to the nodes and be relied on // being part of the node when received by the handling methods you implement. // // The pathhandler expects to be initialized with the current *directory*, i.e. a path node. pathhandler.current = treeroot; // `PathHandler` requires two method, `getNode` and `getChildNodes`, to be provided in order to operate. // // `getNode` gets called with *path* string. This string is completely opaque to `PathHandler`, i.e. constructs such // as `.` and `..` are an implementation detail. `PathHandler` does assume that the path separator is `/`. `getNode` // is called anytime the pathhandler has a path and need to determine what if any node exists at that path which happens // during path completion as well as `cd` and `ls` execution. pathhandler.getNode = function(path, callback) { if(!path) { return callback(pathhandler.current); } var parts = _.filter(path.split('/'), function(x) { return x; }); var start = ((path || '')[0] == '/') ? treeroot : pathhandler.current; _console.log('start: ' + start.path + ', parts: ' + JSON.stringify(parts)); return findNode(start, parts, callback); }; // `getChildNodes` is used by path completion to determine the possible completion candidates. Path completion first // determines the node for the given path, looking for the nearest `/` in case if the given path does not return a // node via `getNode`. For our example, we've attached the child node objects directly to the node object, so we // can simply return it. Usually this would be used to call the server with the provided node's path or id so that // the appropriate children can be found. pathhandler.getChildNodes = function(node, callback) { _console.log("children for " + node.name); callback(node.childnodes); }; // `findNode` is called recursively from `getNode` with the current node and remaining path already split into // segments. It then simply resolves the node for the next segment in `parts` to a node, including relative // references like `.` and `..`. In implementations that let you explore an hierarchy on a server, this function // would live on the server side and be called remotely via `getNode`. function findNode(current, parts, callback) { if(!parts || parts.length == 0) { return callback(current); } if(parts[0] == ".") { } else if(parts[0] == "..") { current = current.parent; } else { current = _.first(_.filter(current.childnodes, function(node) { return node.name == parts[0]; })); } if(!current) { return callback(); } return findNode(current, _.rest(parts), callback); } // Setup Document Behavior // ----------------------- // Activation and display behavior happens at document ready time. $(root).ready(function() { // The default name for the div the shell uses as its container is `shell-panel`, although that can be changed via // the shell config parameter `shell-panel-id`. The `Shell` display model relies on a 'panel' to contain a 'view'. // The 'panel' acts as the view-port, i.e. the visible portion of the shell content, while the 'view' is appended // to and scrolled up as new content is added. var $consolePanel = $('#shell-panel'); // attach readline to the shell panel readline.attach($consolePanel.get(0)); $consolePanel.focus(); // We use **jquery-ui**'s `resizable` to let us drag the bottom edge of the console up and down. $consolePanel.resizable({ handles: "s"}); }); // We attach the various objects we've created here to `Josh.Instance` purely so they can be inspected via a // javascript console. This is not required for the functionality of the example. Josh.Instance = { Tree: treeroot, Shell: shell, PathHandler: pathhandler, KillRing: killring }; // This code builds our *fake* directory structure. Since most real applications of `Josh` would not keep their // entire hierarchy in memory, but instead make callbacks to a server to find nodes and node children, the details // of this function are of little interest. function buildTree() { var fs = { bin: {}, boot: {}, dev: {}, etc: { default: {}, 'rc.d': {}, sysconfig: {}, x11: {} }, home: { bob: { video: { 'firefly.m4v': {} }, videos: { 'Arrested Development': { 's1e1.m4v': {} }, 'Better Off Ted': { 's1e1.m4v': {} } } }, jane: {} }, lib: {}, 'lost+found': {}, misc: {}, mnt: { cdrom: {}, sysimage: {} }, net: {}, opt: {}, proc: {}, root: {}, sbin: {}, usr: { x11: {}, bin: {}, include: {}, lib: {}, local: {}, man: {}, sbin: {}, share: { doc: {} }, src: {} }, var: { lib: {}, lock: {}, run: {}, log: { httpd: { access_log: {}, error_log: {} }, 'boot.log': {}, cron: {}, messages: {} } } }; function build(parent, node) { parent.childnodes = _.map(_.pairs(node), function(pair) { var child = { name: pair[0], path: parent.path + "/" + pair[0], parent: parent }; build(child, pair[1]); return child; }); parent.children = _.keys(node); return parent; } var tree = build({name: "", path: ""}, fs); tree.path = '/'; return tree; } })(root, $, _); })(this, $, _);