pgsh.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. 'pwd': { order: 2, help: 'Show the current working directory' },
  74. 'search': { order: 2, help: 'Search the Web (with a Duck)\nUsage: search [query]' },
  75. 'su': { order: 2, help: 'Gain Phenomenal Cosmic Power' },
  76. 'version': { order: 1, help: 'Display shell information' }
  77. }
  78. this.about = function(args) { self.cat(['/home/pgs/about.md']) }
  79. this.cat = function(args) {
  80. var arg = args.join(' ').trim()
  81. var path = resolveAbsPath(arg)
  82. if (pathExists(path, true)) {
  83. var content = getContents(path)
  84. if (path.search('.md|.sh') != -1) {
  85. content = hljs.highlightAuto(content).value
  86. }
  87. show('\n' + content + '\n\n')
  88. } else {
  89. show("cat: " + arg + ": no such file")
  90. }
  91. }
  92. this.cd = function(args) {
  93. var arg = args.join(' ').trim()
  94. arg = arg ? arg : getHome()
  95. var path = resolveAbsPath(arg)
  96. if (pathExists(path)) {
  97. self.cwd = path
  98. ev.trigger('prompt_set', getPrompt())
  99. } else {
  100. show("cd: " + arg + ": no such directory")
  101. }
  102. }
  103. this.clear = function(args) { ev.trigger('disp_clear') }
  104. this.exit = function(args) {
  105. if (self.su_active) {
  106. self.su_active = false
  107. ev.trigger('prompt_set', getPrompt())
  108. return
  109. }
  110. show('Close browser window to exit')
  111. }
  112. this.hello = function(args) {
  113. address = args.join(' ').trim()
  114. if (address.length == 0) {
  115. show('Hello to you too')
  116. } else if (address == 'pgsh') {
  117. show('Hello human')
  118. } else {
  119. show('My name is not "' + address + '"')
  120. }
  121. }
  122. this.help = function(args) {
  123. command = args.join(' ').trim()
  124. if (command in self.commands) {
  125. show(self.commands[command].help)
  126. } else if (command) {
  127. show('No information for: ' + command)
  128. } else {
  129. var main = []
  130. var other = []
  131. for (var name in self.commands) {
  132. if (self.commands[name].order == 1) {
  133. main.push(name)
  134. } else {
  135. other.push(name)
  136. }
  137. }
  138. show('\nAvailable commands:\n\n' +
  139. '* ' + main.sort().join(' ') + '\n\n' +
  140. '* ' + other.sort().join(' ') + '\n\n' +
  141. '`help [command]` for more information.')
  142. }
  143. }
  144. this.ls = function(args) {
  145. var arg = args.join(' ').trim()
  146. var folder = arg || self.cwd
  147. var path = resolveAbsPath(folder)
  148. if (pathExists(path)) {
  149. var contents = getContents(path)
  150. var dstat = 'drwxr-xr-x <span style="color:#268bd2">'
  151. var fstat = '-rw-r--r-- '
  152. var items = []
  153. for (var item in contents) {
  154. var prefix = (typeof contents[item] === 'string') ? fstat : dstat
  155. var suffix = (typeof contents[item] === 'string') ? '' : '</span>'
  156. items.push(prefix + item + suffix)
  157. }
  158. items.unshift(dstat + '..</span>')
  159. items.unshift('total ' + items.length)
  160. show(items.join('\n'))
  161. } else {
  162. show("ls: " + args + ": no such directory")
  163. }
  164. }
  165. this.pwd = function(args) { show(self.cwd) }
  166. this.search = function(args) {
  167. if (!args.join(' ').trim()) { return show(self.commands.search.help) }
  168. ev.trigger('prompt_hide')
  169. show('Searching for "' + args.join(' ') + '" in new window...')
  170. setTimeout(function() {
  171. window.open('https://duckduckgo.com/?q=' + args.join('+'), '_blank')
  172. ev.trigger('prompt_show')
  173. }, 1000)
  174. }
  175. this.su = function(args) {
  176. if (self.su_active !== true) {
  177. self.su_active = true
  178. if (!self.su_warned) {
  179. self.su_warned = true
  180. show('\n\n' +
  181. 'We trust you have received the usual lecture from the ' +
  182. 'local System Administrator.\n' +
  183. 'It usually boils down to these three things:\n\n' +
  184. ' #1) Respect the privacy of others.\n' +
  185. ' #2) Think before you type.\n' +
  186. ' #3) With great power comes great\n\n')
  187. }
  188. ev.trigger('prompt_set', getPrompt())
  189. }
  190. }
  191. this.version = function(args) {
  192. show('Parsley Gardens Shell (pgsh) 1.2.0\nBuilt with: ' +
  193. '<a target="_blank" href="http://riot.js.org">Riot</a> ' +
  194. '<a target="_blank" href="https://skeleton-framework.github.io">Skeleton</a> ' +
  195. '<a target="_blank" href="https://highlightjs.org">highlight.js</a>')
  196. }
  197. /**
  198. * Helper Functions
  199. */
  200. var show = function(text) { ev.trigger('disp_add', text) }
  201. var set = function(text) { ev.trigger('disp_set', text) }
  202. // Calculate a destination's path with respect to the current folder.
  203. var resolveAbsPath = function(dest) {
  204. var parts = dest.trim().split('/')
  205. var absolute = dest.substring(0, 1) == '/' || self.cwd == '/'
  206. var path = absolute ? [''] : self.cwd.split('/')
  207. parts.forEach(function(part) {
  208. if (part == '..' && path.length > 1) { path.pop() }
  209. if (part != '..' && part) { path.push(part) }
  210. })
  211. return (path.length > 1) ? path.join('/') : '/'
  212. }
  213. // Check if a path exists in the tree.
  214. var pathExists = function(path, isFile) {
  215. // Old way of defining default values, for Safari.
  216. var isFile = (typeof isFile !== 'undefined') ? isFile : false;
  217. var parts = path.trim().split('/')
  218. var fname = isFile ? parts.pop() : ''
  219. var tree = self.tree
  220. for (var i = 0; i < parts.length; i++) {
  221. var part = parts[i]
  222. if (!part) {
  223. continue
  224. }
  225. if (!(tree.hasOwnProperty(part) && typeof tree[part] === 'object')) {
  226. return false
  227. }
  228. tree = tree[part]
  229. }
  230. if (isFile && !(tree.hasOwnProperty(fname) && typeof tree[fname] === 'string')) {
  231. return false
  232. }
  233. return true
  234. }
  235. // Return the contents of a given path.
  236. var getContents = function(path) {
  237. var parts = path.trim().split('/')
  238. var tree = self.tree
  239. parts.forEach(function(part) {
  240. if (part) { tree = tree[part] }
  241. })
  242. // Sort JS object. Not a great way, but more compatible.
  243. if (typeof tree === 'object') {
  244. var keys = []
  245. for (key in tree) {
  246. keys.push(key)
  247. }
  248. keys.sort()
  249. var temp = {}
  250. keys.forEach(function(key) {
  251. temp[key] = tree[key]
  252. })
  253. tree = temp
  254. }
  255. return tree
  256. }
  257. // Complete a given file path string based on the current tree.
  258. var completePath = function(input, isFile) {
  259. // Extract file fragment and get actual path.
  260. var slash = input.lastIndexOf('/') + 1
  261. var frag = input.substring(slash)
  262. var base = input.substring(0, slash)
  263. var path = resolveAbsPath(base)
  264. // Complete fragments.
  265. if (pathExists(path)) {
  266. var contents = getContents(path)
  267. var found = ''
  268. for (var name in contents) {
  269. if (!isFile && typeof contents[name] === 'string') { continue }
  270. if (!frag) { found = name; break }
  271. if (name.indexOf(frag) == 0) { found = name; break }
  272. }
  273. var end = (typeof contents[found] === 'object') ? '/' : ''
  274. return base + found + end
  275. }
  276. return ''
  277. }
  278. }