Mon 21 Jul 22:43:21 CEST 2025

This commit is contained in:
sbosse 2025-07-21 23:13:21 +02:00
parent 11b3c17a54
commit ae02146f3a

264
js/web/example.js Normal file
View File

@ -0,0 +1,264 @@
/*------------------------------------------------------------------------*
* 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("<div><% _.each(items, function(item, i) { %><div><%- i %>&nbsp;<%- item %></div><% }); %></div>")
// 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, $, _);