function pgsh(ev) { var self = this /** * Terminal UI/Shell Entry Point */ ev.on('cmd_entered', function(input) { var args = input.trim().split(' ') // Use the first argument as a command, or the value of `active`. var command = self.active ? self.active : args.shift() if (command in self.commands) { self[command](args) } else if (command) { show(command + ' not found') } }) /** * Key functions */ ev.on('mount', function() { document.getElementById(ev.id).onkeydown = function(e) { var code = e.keyCode || e.which // Tab for file path completion. if (code == 9) { var input = ev.tags['command-line'].refs.command.value.trim().split(' ') var command = input.shift() if (['cat', 'cd', 'ls'].indexOf(command) != -1) { var args = input.join(' ').trim() var isFile = (command == 'cat') ? true : false; var path = completePath(args, isFile) if (path) { ev.trigger('cmd_set', command + ' ' + path) } } e.preventDefault() } } }) /** * Variables and Setup */ var getHome = function() { return self.su_active ? '/root' : '/home/pgs' } this.cwd = getHome() var getPrompt = function() { if (self.su_active) { return '' + 'root ' + '' + self.cwd + ' ' + '% ' } return '' + 'pgs ' + '' + self.cwd + ' ' + '$ ' } this.prompt = getPrompt() this.welcome = '' + 'Linux parsleygardens.net 3.4.5-6-7-i286 #8 PGS Vimputer' + '3.4.56-7 i286\n\n' + '~ Welcome to Parsley Gardens! ~\n' + '"He maketh me to lie down in green pa..."\n\n' + 'Type `help` for list of commands\n\n' this.tree = pgfs /** * Commands */ this.commands = { 'about': { order: 1, help: 'Author Information' }, 'cat': { order: 2, help: 'Con(cat)enate the contents of a file' }, 'cd': { order: 2, help: 'Change folder/directory' }, 'clear': { order: 2, help: 'Clears the screen' }, 'exit': { order: 2, help: 'Leave the current context' }, 'hello': { order: 2, help: 'Say hello to the computer\nUsage: hello [name]' }, 'help': { order: 1, help: 'List commands or view information for one\nUsage: help [command]' }, 'ls': { order: 2, help: 'List folder contents' }, 'pwd': { order: 2, help: 'Show the current working directory' }, 'search': { order: 2, help: 'Search the Web (with a Duck)\nUsage: search [query]' }, 'su': { order: 2, help: 'Gain Phenomenal Cosmic Power' }, 'version': { order: 1, help: 'Display shell information' } } this.about = function(args) { self.cat(['/home/pgs/about.md']) } this.cat = function(args) { var arg = args.join(' ').trim() var path = resolveAbsPath(arg) if (pathExists(path, true)) { var content = getContents(path) if (path.search('.md|.sh') != -1) { content = hljs.highlightAuto(content).value } show('\n' + content + '\n\n') } else { show("cat: " + arg + ": no such file") } } this.cd = function(args) { var arg = args.join(' ').trim() arg = arg ? arg : getHome() var path = resolveAbsPath(arg) if (pathExists(path)) { self.cwd = path ev.trigger('prompt_set', getPrompt()) } else { show("cd: " + arg + ": no such directory") } } this.clear = function(args) { ev.trigger('disp_clear') } this.exit = function(args) { if (self.su_active) { self.su_active = false ev.trigger('prompt_set', getPrompt()) return } show('Close browser window to exit') } this.hello = function(args) { address = args.join(' ').trim() if (address.length == 0) { show('Hello to you too') } else if (address == 'pgsh') { show('Hello human') } else { show('My name is not "' + address + '"') } } this.help = function(args) { command = args.join(' ').trim() if (command in self.commands) { show(self.commands[command].help) } else if (command) { show('No information for: ' + command) } else { var main = [] var other = [] for (var name in self.commands) { if (self.commands[name].order == 1) { main.push(name) } else { other.push(name) } } show('\nAvailable commands:\n\n' + '* ' + main.sort().join(' ') + '\n\n' + '* ' + other.sort().join(' ') + '\n\n' + '`help [command]` for more information.') } } this.ls = function(args) { var arg = args.join(' ').trim() var folder = arg || self.cwd var path = resolveAbsPath(folder) if (pathExists(path)) { var contents = getContents(path) var dstat = 'drwxr-xr-x ' var fstat = '-rw-r--r-- ' var items = [] for (var item in contents) { var prefix = (typeof contents[item] === 'string') ? fstat : dstat var suffix = (typeof contents[item] === 'string') ? '' : '' items.push(prefix + item + suffix) } items.unshift(dstat + '..') items.unshift('total ' + items.length) show(items.join('\n')) } else { show("ls: " + args + ": no such directory") } } this.pwd = function(args) { show(self.cwd) } this.search = function(args) { if (!args.join(' ').trim()) { return show(self.commands.search.help) } ev.trigger('prompt_hide') show('Searching for "' + args.join(' ') + '" in new window...') setTimeout(function() { window.open('https://duckduckgo.com/?q=' + args.join('+'), '_blank') ev.trigger('prompt_show') }, 1000) } this.su = function(args) { if (self.su_active !== true) { self.su_active = true if (!self.su_warned) { self.su_warned = true show('\n\n' + 'We trust you have received the usual lecture from the ' + 'local System Administrator.\n' + 'It usually boils down to these three things:\n\n' + ' #1) Respect the privacy of others.\n' + ' #2) Think before you type.\n' + ' #3) With great power comes great\n\n') } ev.trigger('prompt_set', getPrompt()) } } this.version = function(args) { show('Parsley Gardens Shell (pgsh) 1.2.0\nBuilt with: ' + 'Riot ' + 'Skeleton ' + 'highlight.js') } /** * Helper Functions */ var show = function(text) { ev.trigger('disp_add', text) } var set = function(text) { ev.trigger('disp_set', text) } // Calculate a destination's path with respect to the current folder. var resolveAbsPath = function(dest) { var parts = dest.trim().split('/') var absolute = dest.substring(0, 1) == '/' || self.cwd == '/' var path = absolute ? [''] : self.cwd.split('/') parts.forEach(function(part) { if (part == '..' && path.length > 1) { path.pop() } if (part != '..' && part) { path.push(part) } }) return (path.length > 1) ? path.join('/') : '/' } // Check if a path exists in the tree. var pathExists = function(path, isFile) { // Old way of defining default values, for Safari. var isFile = (typeof isFile !== 'undefined') ? isFile : false; var parts = path.trim().split('/') var fname = isFile ? parts.pop() : '' var tree = self.tree for (var i = 0; i < parts.length; i++) { var part = parts[i] if (!part) { continue } if (!(tree.hasOwnProperty(part) && typeof tree[part] === 'object')) { return false } tree = tree[part] } if (isFile && !(tree.hasOwnProperty(fname) && typeof tree[fname] === 'string')) { return false } return true } // Return the contents of a given path. var getContents = function(path) { var parts = path.trim().split('/') var tree = self.tree parts.forEach(function(part) { if (part) { tree = tree[part] } }) // Sort JS object. Not a great way, but more compatible. if (typeof tree === 'object') { var keys = [] for (key in tree) { keys.push(key) } keys.sort() var temp = {} keys.forEach(function(key) { temp[key] = tree[key] }) tree = temp } return tree } // Complete a given file path string based on the current tree. var completePath = function(input, isFile) { // Extract file fragment and get actual path. var slash = input.lastIndexOf('/') + 1 var frag = input.substring(slash) var base = input.substring(0, slash) var path = resolveAbsPath(base) // Complete fragments. if (pathExists(path)) { var contents = getContents(path) var found = '' for (var name in contents) { if (!isFile && typeof contents[name] === 'string') { continue } if (!frag) { found = name; break } if (name.indexOf(frag) == 0) { found = name; break } } var end = (typeof contents[found] === 'object') ? '/' : '' return base + found + end } return '' } }