pgsh.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. function pgsh(ev) {
  2. var self = this
  3. /**
  4. * Terminal UI/Shell Entry Point
  5. */
  6. ev.on('cmd_entered', function(input) {
  7. var args = input.trim().split(' ')
  8. // Use the first argument as a command, or the value of `active`.
  9. var command = self.active ? self.active : args.shift()
  10. if (command in self.commands) {
  11. self[command](args)
  12. } else if (command) {
  13. show(command + ' not found')
  14. }
  15. })
  16. /**
  17. * Key functions
  18. */
  19. ev.on('mount', function() {
  20. document.getElementById(ev.id).onkeydown = function(e) {
  21. var code = e.keyCode || e.which
  22. // Tab for file path completion.
  23. if (code == 9) {
  24. var input = ev.tags['command-line'].refs.command.value.trim().split(' ')
  25. var command = input.shift()
  26. if (['cat', 'cd', 'ls'].indexOf(command) != -1) {
  27. var args = input.join(' ').trim()
  28. var isFile = (command == 'cat') ? true : false;
  29. var path = completePath(args, isFile)
  30. if (path) { ev.trigger('cmd_set', command + ' ' + path) }
  31. }
  32. e.preventDefault()
  33. }
  34. }
  35. })
  36. /**
  37. * Variables and Setup
  38. */
  39. var getHome = function() { return self.su_active ? '/root' : '/home/pgs' }
  40. this.cwd = getHome()
  41. var getPrompt = function() {
  42. if (self.su_active) {
  43. return '' +
  44. '<span style="color:#dc322f">root </span>' +
  45. '<span style="color:#b58900">' + self.cwd + ' </span>' +
  46. '<span style="color:#268bd2">% </span>'
  47. }
  48. return '' +
  49. '<span style="color:#d33682">pgs </span>' +
  50. '<span style="color:#b58900">' + self.cwd + ' </span>' +
  51. '<span style="color:#2aa198">$ </span>'
  52. }
  53. this.prompt = getPrompt()
  54. this.welcome = '' +
  55. 'Linux parsleygardens.net 3.4.5-6-7-i286 #8 PGS Vimputer' +
  56. '3.4.56-7 i286\n\n' +
  57. '~ Welcome to Parsley Gardens! ~\n' +
  58. '"He maketh me to lie down in green pa..."\n\n' +
  59. 'Type `<strong>help</strong>` for list of commands\n\n'
  60. this.tree = pgfs
  61. /**
  62. * Commands
  63. */
  64. this.commands = {
  65. 'about': { order: 1, help: 'Author Information' },
  66. 'cat': { order: 2, help: 'Con(cat)enate the contents of a file' },
  67. 'cd': { order: 2, help: 'Change folder/directory' },
  68. 'clear': { order: 2, help: 'Clears the screen' },
  69. 'exit': { order: 2, help: 'Leave the current context' },
  70. 'hello': { order: 2, help: 'Say hello to the computer\nUsage: hello [name]' },
  71. 'help': { order: 1, help: 'List commands or view information for one\nUsage: help [command]' },
  72. 'ls': { order: 2, help: 'List folder contents' },
  73. 'magic': { order: 1, help: 'The Website of an Artist' },
  74. 'pwd': { order: 2, help: 'Show the current working directory' },
  75. 'question': { order: 1, help: 'Displays a question' },
  76. 'search': { order: 2, help: 'Search the Web (with a Duck)\nUsage: search [query]' },
  77. 'su': { order: 2, help: 'Gain Phenomenal Cosmic Power' },
  78. 'version': { order: 1, help: 'Display shell information' }
  79. }
  80. this.about = function(args) { self.cat(['/home/pgs/about.md']) }
  81. this.magic = function(args) { self.cat(['/home/pgs/magic.txt']) }
  82. this.cat = function(args) {
  83. var arg = args.join(' ').trim()
  84. var path = resolveAbsPath(arg)
  85. if (pathExists(path, true)) {
  86. var content = getContents(path)
  87. if (path.search('.md|.sh') != -1) {
  88. content = hljs.highlightAuto(content).value
  89. }
  90. show('\n' + content + '\n\n')
  91. } else {
  92. show("cat: " + arg + ": no such file")
  93. }
  94. }
  95. this.cd = function(args) {
  96. var arg = args.join(' ').trim()
  97. arg = arg ? arg : getHome()
  98. var path = resolveAbsPath(arg)
  99. if (pathExists(path)) {
  100. self.cwd = path
  101. ev.trigger('prompt_set', getPrompt())
  102. } else {
  103. show("cd: " + arg + ": no such directory")
  104. }
  105. }
  106. this.clear = function(args) { ev.trigger('disp_clear') }
  107. this.exit = function(args) {
  108. if (self.su_active) {
  109. self.su_active = false
  110. ev.trigger('prompt_set', getPrompt())
  111. return
  112. }
  113. show('Close browser window to exit')
  114. }
  115. this.hello = function(args) {
  116. address = args.join(' ').trim()
  117. if (address.length == 0) {
  118. show('Hello to you too')
  119. } else if (address == 'pgsh') {
  120. show('Hello human')
  121. } else {
  122. show('My name is not "' + address + '"')
  123. }
  124. }
  125. this.help = function(args) {
  126. command = args.join(' ').trim()
  127. if (command in self.commands) {
  128. show(self.commands[command].help)
  129. } else if (command) {
  130. show('No information for: ' + command)
  131. } else {
  132. var main = []
  133. var other = []
  134. for (var name in self.commands) {
  135. if (self.commands[name].order == 1) {
  136. main.push(name)
  137. } else {
  138. other.push(name)
  139. }
  140. }
  141. show('\nAvailable commands:\n\n' +
  142. '* ' + main.sort().join(' ') + '\n\n' +
  143. '* ' + other.sort().join(' ') + '\n\n' +
  144. '`help [command]` for more information.')
  145. }
  146. }
  147. this.ls = function(args) {
  148. var arg = args.join(' ').trim()
  149. var folder = arg || self.cwd
  150. var path = resolveAbsPath(folder)
  151. if (pathExists(path)) {
  152. var contents = getContents(path)
  153. var dstat = 'drwxr-xr-x <span style="color:#268bd2">'
  154. var fstat = '-rw-r--r-- '
  155. var items = []
  156. for (var item in contents) {
  157. var prefix = (typeof contents[item] === 'string') ? fstat : dstat
  158. var suffix = (typeof contents[item] === 'string') ? '' : '</span>'
  159. items.push(prefix + item + suffix)
  160. }
  161. items.unshift(dstat + '..</span>')
  162. items.unshift('total ' + items.length)
  163. show(items.join('\n'))
  164. } else {
  165. show("ls: " + args + ": no such directory")
  166. }
  167. }
  168. this.pwd = function(args) { show(self.cwd) }
  169. this.question = function(args) {
  170. var questions = [
  171. 'Isn\'t <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"' +
  172. ' target="_blank">this song</a> the best?',
  173. '1 + 2 = ?',
  174. 'Am I a sandwich?',
  175. 'Where were you at 3:15am on April 14th?',
  176. "Don't you mean prism?",
  177. 'Butts twelve by pies?',
  178. 'I say there, Monstrosity. Do you know the times?',
  179. 'What is the name of the spaces between the teeth of a comb?',
  180. 'Why are you dressed up like Ship\'s Captain?'
  181. ]
  182. var rand = Math.floor(Math.random() * questions.length);
  183. show(questions[rand])
  184. }
  185. this.search = function(args) {
  186. if (!args.join(' ').trim()) { return show(self.commands.search.help) }
  187. ev.trigger('prompt_hide')
  188. show('Searching for "' + args.join(' ') + '" in new window...')
  189. setTimeout(function() {
  190. window.open('https://duckduckgo.com/?q=' + args.join('+'), '_blank')
  191. ev.trigger('prompt_show')
  192. }, 1000)
  193. }
  194. this.su = function(args) {
  195. if (self.su_active !== true) {
  196. self.su_active = true
  197. if (!self.su_warned) {
  198. self.su_warned = true
  199. show('\n\n' +
  200. 'We trust you have received the usual lecture from the ' +
  201. 'local System Administrator.\n' +
  202. 'It usually boils down to these three things:\n\n' +
  203. ' #1) Respect the privacy of others.\n' +
  204. ' #2) Think before you type.\n' +
  205. ' #3) With great power comes great\n\n')
  206. }
  207. ev.trigger('prompt_set', getPrompt())
  208. }
  209. }
  210. this.version = function(args) {
  211. show('Parsley Gardens Shell (pgsh) 1.2.0\nBuilt with: ' +
  212. '<a target="_blank" href="http://riotjs.com">Riot</a> ' +
  213. '<a target="_blank" href="https://skeleton-framework.github.io">Skeleton</a> ' +
  214. '<a target="_blank" href="https://highlightjs.org">highlight.js</a>')
  215. }
  216. /**
  217. * Helper Functions
  218. */
  219. var show = function(text) { ev.trigger('disp_add', text) }
  220. var set = function(text) { ev.trigger('disp_set', text) }
  221. // Calculate a destination's path with respect to the current folder.
  222. var resolveAbsPath = function(dest) {
  223. var parts = dest.trim().split('/')
  224. var absolute = dest.substring(0, 1) == '/' || self.cwd == '/'
  225. var path = absolute ? [''] : self.cwd.split('/')
  226. parts.forEach(function(part) {
  227. if (part == '..' && path.length > 1) { path.pop() }
  228. if (part != '..' && part) { path.push(part) }
  229. })
  230. return (path.length > 1) ? path.join('/') : '/'
  231. }
  232. // Check if a path exists in the tree.
  233. var pathExists = function(path, isFile) {
  234. // Old way of defining default values, for Safari.
  235. var isFile = (typeof isFile !== 'undefined') ? isFile : false;
  236. var parts = path.trim().split('/')
  237. var fname = isFile ? parts.pop() : ''
  238. var tree = self.tree
  239. for (var i = 0; i < parts.length; i++) {
  240. var part = parts[i]
  241. if (!part) {
  242. continue
  243. }
  244. if (!(tree.hasOwnProperty(part) && typeof tree[part] === 'object')) {
  245. return false
  246. }
  247. tree = tree[part]
  248. }
  249. if (isFile && !(tree.hasOwnProperty(fname) && typeof tree[fname] === 'string')) {
  250. return false
  251. }
  252. return true
  253. }
  254. // Return the contents of a given path.
  255. var getContents = function(path) {
  256. var parts = path.trim().split('/')
  257. var tree = self.tree
  258. parts.forEach(function(part) {
  259. if (part) { tree = tree[part] }
  260. })
  261. // Sort JS object. Not a great way, but more compatible.
  262. if (typeof tree === 'object') {
  263. var keys = []
  264. for (key in tree) {
  265. keys.push(key)
  266. }
  267. keys.sort()
  268. var temp = {}
  269. keys.forEach(function(key) {
  270. temp[key] = tree[key]
  271. })
  272. tree = temp
  273. }
  274. return tree
  275. }
  276. // Complete a given file path string based on the current tree.
  277. var completePath = function(input, isFile) {
  278. // Extract file fragment and get actual path.
  279. var slash = input.lastIndexOf('/') + 1
  280. var frag = input.substring(slash)
  281. var base = input.substring(0, slash)
  282. var path = resolveAbsPath(base)
  283. // Complete fragments.
  284. if (pathExists(path)) {
  285. var contents = getContents(path)
  286. var found = ''
  287. for (var name in contents) {
  288. if (!isFile && typeof contents[name] === 'string') { continue }
  289. if (!frag) { found = name; break }
  290. if (name.indexOf(frag) == 0) { found = name; break }
  291. }
  292. var end = (typeof contents[found] === 'object') ? '/' : ''
  293. return base + found + end
  294. }
  295. return ''
  296. }
  297. }