AmberCli.st 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318
  1. Smalltalk createPackage: 'AmberCli'!
  2. Object subclass: #AmberCli
  3. instanceVariableNames: ''
  4. package: 'AmberCli'!
  5. !AmberCli commentStamp!
  6. I am the Amber CLI (CommandLine Interface) tool which runs on Node.js.
  7. My responsibility is to start different Amber programs like the FileServer or the Repl.
  8. Which program to start is determined by the first commandline parameters passed to the AmberCli executable.
  9. Use `help` to get a list of all available options.
  10. Any further commandline parameters are passed to the specific program.
  11. ## Commands
  12. New commands can be added by creating a class side method in the `commands` protocol which takes one parameter.
  13. This parameter is an array of all commandline options + values passed on to the program.
  14. Any `camelCaseCommand` is transformed into a commandline parameter of the form `camel-case-command` and vice versa.!
  15. !AmberCli class methodsFor: 'commandline'!
  16. commandLineSwitches
  17. "Collect all methodnames from the 'commands' protocol of the class
  18. and select the ones with only one parameter.
  19. Then remove the ':' at the end of the name.
  20. Additionally all uppercase letters are made lowercase and preceded by a '-'.
  21. Example: fallbackPage: becomes --fallback-page.
  22. Return the Array containing the commandline switches."
  23. | switches |
  24. switches := ((self class methodsInProtocol: 'commands') collect: [ :each | each selector]).
  25. switches := switches select: [ :each | each match: '^[^:]*:$'].
  26. switches :=switches collect: [ :each |
  27. (each allButLast replace: '([A-Z])' with: '-$1') asLowercase].
  28. ^ switches
  29. !
  30. handleArguments: args
  31. | selector |
  32. selector := self selectorForCommandLineSwitch: (args first).
  33. args remove: args first.
  34. self perform: selector withArguments: (Array with: args)
  35. !
  36. selectorForCommandLineSwitch: aSwitch
  37. "Add ':' at the end and replace all occurences of a lowercase letter preceded by a '-' with the Uppercase letter.
  38. Example: fallback-page becomes fallbackPage:.
  39. If no correct selector is found return 'help:'"
  40. | command selector |
  41. (self commandLineSwitches includes: aSwitch)
  42. ifTrue: [ selector := (aSwitch replace: '-[a-z]' with: [ :each | each second asUppercase ]), ':']
  43. ifFalse: [ selector := 'help:' ].
  44. ^ selector
  45. ! !
  46. !AmberCli class methodsFor: 'commands'!
  47. config: args
  48. Configurator new start
  49. !
  50. help: args
  51. Transcript show: 'Available commands'.
  52. self commandLineSwitches do: [ :each | console log: each ]
  53. !
  54. init: args
  55. Initer new start
  56. !
  57. repl: args
  58. ^ Repl new createInterface
  59. !
  60. serve: args
  61. ^ (FileServer createServerWithArguments: args) start
  62. !
  63. tests: arguments
  64. ^ NodeTestRunner runTestSuite
  65. !
  66. version: arguments
  67. ! !
  68. !AmberCli class methodsFor: 'startup'!
  69. main
  70. "Main entry point for Amber applications.
  71. Parses commandline arguments and starts the according subprogram."
  72. | args nodeMinorVersion |
  73. Transcript show: 'Welcome to Amber version ', Smalltalk version, ' (NodeJS ', process versions node, ').'.
  74. nodeMinorVersion := ((process version) tokenize: '.') second asNumber.
  75. nodeMinorVersion < 8 ifTrue: [
  76. Transcript show: 'You are currently using Node.js ', (process version).
  77. Transcript show: 'Required is at least Node.js v0.8.x or greater.'.
  78. ^ -1.
  79. ].
  80. args := process argv.
  81. "Remove the first args which contain the path to the node executable and the script file."
  82. args removeFrom: 1 to: 2.
  83. (args isEmpty)
  84. ifTrue: [self help: nil]
  85. ifFalse: [^self handleArguments: args]
  86. ! !
  87. Object subclass: #BaseFileManipulator
  88. instanceVariableNames: 'path fs'
  89. package: 'AmberCli'!
  90. !BaseFileManipulator methodsFor: 'initialization'!
  91. initialize
  92. super initialize.
  93. path := require value: 'path'.
  94. fs := require value: 'fs'
  95. ! !
  96. !BaseFileManipulator methodsFor: 'private'!
  97. dirname
  98. <return __dirname>
  99. !
  100. rootDirname
  101. ^ path join: self dirname with: '..'
  102. ! !
  103. BaseFileManipulator subclass: #Configurator
  104. instanceVariableNames: ''
  105. package: 'AmberCli'!
  106. !Configurator methodsFor: 'action'!
  107. start
  108. (require value: 'amber-dev/lib/config')
  109. writeConfig: process cwd
  110. ! !
  111. !Configurator methodsFor: 'initialization'!
  112. initialize
  113. super initialize
  114. ! !
  115. BaseFileManipulator subclass: #FileServer
  116. instanceVariableNames: 'http url host port basePath util username password fallbackPage'
  117. package: 'AmberCli'!
  118. !FileServer commentStamp!
  119. I am the Amber Smalltalk FileServer.
  120. My runtime requirement is a functional Node.js executable.
  121. To start a FileServer instance on port `4000` use the following code:
  122. FileServer new start
  123. A parameterized instance can be created with the following code:
  124. FileServer createServerWithArguments: options
  125. Here, `options` is an array of commandline style strings each followed by a value e.g. `#('--port', '6000', '--host', '0.0.0.0')`.
  126. A list of all available parameters can be printed to the commandline by passing `--help` as parameter.
  127. See the `Options` section for further details on how options are mapped to instance methods.
  128. After startup FileServer checks if the directory layout required by Amber is present and logs a warning on absence.
  129. ## Options
  130. Each option is of the form `--some-option-string` which is transformed into a selector of the format `someOptionString:`.
  131. The trailing `--` gets removed, each `-[a-z]` gets transformed into the according uppercase letter, and a `:` is appended to create a selector which takes a single argument.
  132. Afterwards, the selector gets executed on the `FileServer` instance with the value following in the options array as parameter.
  133. ## Adding new commandline parameters
  134. Adding new commandline parameters to `FileServer` is as easy as adding a new single parameter method to the `accessing` protocol.!
  135. !FileServer methodsFor: 'accessing'!
  136. basePath
  137. ^ basePath ifNil: [self class defaultBasePath]
  138. !
  139. basePath: aString
  140. basePath := aString.
  141. self validateBasePath.
  142. !
  143. fallbackPage
  144. ^ fallbackPage
  145. !
  146. fallbackPage: aString
  147. fallbackPage := aString
  148. !
  149. host
  150. ^ host
  151. !
  152. host: hostname
  153. host := hostname
  154. !
  155. password: aPassword
  156. password := aPassword.
  157. !
  158. port
  159. ^ port
  160. !
  161. port: aNumber
  162. port := aNumber
  163. !
  164. username: aUsername
  165. username := aUsername.
  166. ! !
  167. !FileServer methodsFor: 'initialization'!
  168. checkDirectoryLayout
  169. (fs existsSync: (self withBasePath: 'index.html')) ifFalse: [
  170. console warn: 'Warning: project directory does not contain index.html.'.
  171. console warn: ' You can specify the directory containing index.html with --base-path.'.
  172. console warn: ' You can also specify a page to be served by default,'.
  173. console warn: ' for all paths that do not map to a file, with --fallback-page.'].
  174. !
  175. initialize
  176. super initialize.
  177. http := self require: 'http'.
  178. util := self require: 'util'.
  179. url := self require: 'url'.
  180. host := self class defaultHost.
  181. port := self class defaultPort.
  182. username := nil.
  183. password := nil.
  184. fallbackPage := nil.
  185. ! !
  186. !FileServer methodsFor: 'private'!
  187. base64Decode: aString
  188. <return (new Buffer(aString, 'base64').toString())>
  189. !
  190. isAuthenticated: aRequest
  191. "Basic HTTP Auth: http://stackoverflow.com/a/5957629/293175
  192. and https://gist.github.com/1686663"
  193. | header token auth parts|
  194. (username isNil and: [password isNil]) ifTrue: [^ true].
  195. "get authentication header"
  196. header := (aRequest headers at: 'authorization') ifNil:[''].
  197. (header isEmpty)
  198. ifTrue: [^ false]
  199. ifFalse: [
  200. "get authentication token"
  201. token := (header tokenize: ' ') ifNil:[''].
  202. "convert back from base64"
  203. auth := self base64Decode: (token at: 2).
  204. "split token at colon"
  205. parts := auth tokenize: ':'.
  206. ((username = (parts at: 1)) and: [password = (parts at: 2)])
  207. ifTrue: [^ true]
  208. ifFalse: [^ false]
  209. ].
  210. !
  211. require: aModuleString
  212. "call to the require function"
  213. ^require value: aModuleString
  214. !
  215. validateBasePath
  216. "The basePath must be an existing directory. "
  217. fs stat: self basePath then: [ :err :stat | err
  218. ifNil: [ stat isDirectory ifFalse: [ console warn: 'Warning: --base-path parameter ' , self basePath , ' is not a directory.' ]]
  219. ifNotNil: [ console warn: 'Warning: path at --base-path parameter ' , self basePath , ' does not exist.' ]].
  220. !
  221. withBasePath: aBaseRelativePath
  222. "return a file path which is relative to the basePath."
  223. ^ path join: self basePath with: aBaseRelativePath
  224. !
  225. writeData: data toFileNamed: aFilename
  226. console log: aFilename
  227. ! !
  228. !FileServer methodsFor: 'request handling'!
  229. handleGETRequest: aRequest respondTo: aResponse
  230. | uri filename |
  231. uri := url parse: aRequest url.
  232. filename := path join: self basePath with: uri pathname.
  233. fs exists: filename do: [:aBoolean |
  234. aBoolean
  235. ifFalse: [self respondNotFoundTo: aResponse]
  236. ifTrue: [(fs statSync: filename) isDirectory
  237. ifTrue: [self respondDirectoryNamed: filename from: uri to: aResponse]
  238. ifFalse: [self respondFileNamed: filename to: aResponse]]]
  239. !
  240. handleOPTIONSRequest: aRequest respondTo: aResponse
  241. aResponse writeHead: 200 options: #{'Access-Control-Allow-Origin' -> '*'.
  242. 'Access-Control-Allow-Methods' -> 'GET, PUT, POST, DELETE, OPTIONS'.
  243. 'Access-Control-Allow-Headers' -> 'Content-Type, Accept'.
  244. 'Content-Length' -> 0.
  245. 'Access-Control-Max-Age' -> 10}.
  246. aResponse end
  247. !
  248. handlePUTRequest: aRequest respondTo: aResponse
  249. | file stream |
  250. (self isAuthenticated: aRequest)
  251. ifFalse: [self respondAuthenticationRequiredTo: aResponse. ^ nil].
  252. file := '.', aRequest url.
  253. stream := fs createWriteStream: file.
  254. stream on: 'error' do: [:error |
  255. console warn: 'Error creating WriteStream for file ', file.
  256. console warn: ' Did you forget to create the necessary directory in your project (often /src)?'.
  257. console warn: ' The exact error is: ', error.
  258. self respondNotCreatedTo: aResponse].
  259. stream on: 'close' do: [
  260. self respondCreatedTo: aResponse].
  261. aRequest setEncoding: 'utf8'.
  262. aRequest on: 'data' do: [:data |
  263. stream write: data].
  264. aRequest on: 'end' do: [
  265. stream writable ifTrue: [stream end]]
  266. !
  267. handleRequest: aRequest respondTo: aResponse
  268. aRequest method = 'PUT'
  269. ifTrue: [self handlePUTRequest: aRequest respondTo: aResponse].
  270. aRequest method = 'GET'
  271. ifTrue:[self handleGETRequest: aRequest respondTo: aResponse].
  272. aRequest method = 'OPTIONS'
  273. ifTrue:[self handleOPTIONSRequest: aRequest respondTo: aResponse]
  274. !
  275. respondAuthenticationRequiredTo: aResponse
  276. aResponse
  277. writeHead: 401 options: #{'WWW-Authenticate' -> 'Basic realm="Secured Developer Area"'};
  278. write: '<html><body>Authentication needed</body></html>';
  279. end.
  280. !
  281. respondCreatedTo: aResponse
  282. aResponse
  283. writeHead: 201 options: #{'Content-Type' -> 'text/plain'. 'Access-Control-Allow-Origin' -> '*'};
  284. end.
  285. !
  286. respondDirectoryNamed: aDirname from: aUrl to: aResponse
  287. (aUrl pathname endsWith: '/')
  288. ifTrue: [self respondFileNamed: aDirname, 'index.html' to: aResponse]
  289. ifFalse: [self respondRedirect: aUrl pathname, '/', (aUrl search ifNil: ['']) to: aResponse]
  290. !
  291. respondFileNamed: aFilename to: aResponse
  292. | type filename |
  293. filename := aFilename.
  294. fs readFile: filename do: [:ex :file |
  295. ex notNil
  296. ifTrue: [
  297. console log: filename, ' does not exist'.
  298. self respondNotFoundTo: aResponse]
  299. ifFalse: [
  300. type := self class mimeTypeFor: filename.
  301. type = 'application/javascript'
  302. ifTrue: [ type:=type,';charset=utf-8' ].
  303. aResponse
  304. writeHead: 200 options: #{'Content-Type' -> type};
  305. write: file encoding: 'binary';
  306. end]]
  307. !
  308. respondInternalErrorTo: aResponse
  309. aResponse
  310. writeHead: 500 options: #{'Content-Type' -> 'text/plain'};
  311. write: '500 Internal server error';
  312. end
  313. !
  314. respondNotCreatedTo: aResponse
  315. aResponse
  316. writeHead: 400 options: #{'Content-Type' -> 'text/plain'};
  317. write: 'File could not be created. Did you forget to create the src directory on the server?';
  318. end.
  319. !
  320. respondNotFoundTo: aResponse
  321. self fallbackPage isNil ifFalse: [^self respondFileNamed: self fallbackPage to: aResponse].
  322. aResponse
  323. writeHead: 404 options: #{'Content-Type' -> 'text/html'};
  324. write: '<html><body><p>404 Not found</p>';
  325. write: '<p>Did you forget to put an index.html file into the directory which is served by "bin/amber serve"? To solve this you can:<ul>';
  326. write: '<li>create an index.html in the served directory.</li>';
  327. write: '<li>can also specify the location of a page to be served whenever path does not resolve to a file with the "--fallback-page" option.</li>';
  328. write: '<li>change the directory to be served with the "--base-path" option.</li>';
  329. write: '</ul></p></body></html>';
  330. end
  331. !
  332. respondOKTo: aResponse
  333. aResponse
  334. writeHead: 200 options: #{'Content-Type' -> 'text/plain'. 'Access-Control-Allow-Origin' -> '*'};
  335. end.
  336. !
  337. respondRedirect: aString to: aResponse
  338. aResponse
  339. writeHead: 303 options: #{'Location' -> aString};
  340. end.
  341. ! !
  342. !FileServer methodsFor: 'starting'!
  343. start
  344. "Checks if required directory layout is present (issue warning if not).
  345. Afterwards start the server."
  346. self checkDirectoryLayout.
  347. (http createServer: [:request :response |
  348. self handleRequest: request respondTo: response])
  349. on: 'error' do: [:error | console log: 'Error starting server: ', error];
  350. on: 'listening' do: [console log: 'Starting file server on http://', self host, ':', self port asString];
  351. listen: self port host: self host.
  352. !
  353. startOn: aPort
  354. self port: aPort.
  355. self start
  356. ! !
  357. FileServer class instanceVariableNames: 'mimeTypes'!
  358. !FileServer class methodsFor: 'accessing'!
  359. commandLineSwitches
  360. "Collect all methodnames from the 'accessing' protocol
  361. and select the ones with only one parameter.
  362. Then remove the ':' at the end of the name
  363. and add a '--' at the beginning.
  364. Additionally all uppercase letters are made lowercase and preceded by a '-'.
  365. Example: fallbackPage: becomes --fallback-page.
  366. Return the Array containing the commandline switches."
  367. | switches |
  368. switches := ((self methodsInProtocol: 'accessing') collect: [ :each | each selector]).
  369. switches := switches select: [ :each | each match: '^[^:]*:$'].
  370. switches :=switches collect: [ :each |
  371. (each allButLast replace: '([A-Z])' with: '-$1') asLowercase replace: '^([a-z])' with: '--$1' ].
  372. ^ switches
  373. !
  374. defaultBasePath
  375. ^ './'
  376. !
  377. defaultHost
  378. ^ '127.0.0.1'
  379. !
  380. defaultMimeTypes
  381. ^ #{
  382. '%' -> 'application/x-trash'.
  383. '323' -> 'text/h323'.
  384. 'abw' -> 'application/x-abiword'.
  385. 'ai' -> 'application/postscript'.
  386. 'aif' -> 'audio/x-aiff'.
  387. 'aifc' -> 'audio/x-aiff'.
  388. 'aiff' -> 'audio/x-aiff'.
  389. 'alc' -> 'chemical/x-alchemy'.
  390. 'art' -> 'image/x-jg'.
  391. 'asc' -> 'text/plain'.
  392. 'asf' -> 'video/x-ms-asf'.
  393. 'asn' -> 'chemical/x-ncbi-asn1-spec'.
  394. 'aso' -> 'chemical/x-ncbi-asn1-binary'.
  395. 'asx' -> 'video/x-ms-asf'.
  396. 'au' -> 'audio/basic'.
  397. 'avi' -> 'video/x-msvideo'.
  398. 'b' -> 'chemical/x-molconn-Z'.
  399. 'bak' -> 'application/x-trash'.
  400. 'bat' -> 'application/x-msdos-program'.
  401. 'bcpio' -> 'application/x-bcpio'.
  402. 'bib' -> 'text/x-bibtex'.
  403. 'bin' -> 'application/octet-stream'.
  404. 'bmp' -> 'image/x-ms-bmp'.
  405. 'book' -> 'application/x-maker'.
  406. 'bsd' -> 'chemical/x-crossfire'.
  407. 'c' -> 'text/x-csrc'.
  408. 'c++' -> 'text/x-c++src'.
  409. 'c3d' -> 'chemical/x-chem3d'.
  410. 'cac' -> 'chemical/x-cache'.
  411. 'cache' -> 'chemical/x-cache'.
  412. 'cascii' -> 'chemical/x-cactvs-binary'.
  413. 'cat' -> 'application/vnd.ms-pki.seccat'.
  414. 'cbin' -> 'chemical/x-cactvs-binary'.
  415. 'cc' -> 'text/x-c++src'.
  416. 'cdf' -> 'application/x-cdf'.
  417. 'cdr' -> 'image/x-coreldraw'.
  418. 'cdt' -> 'image/x-coreldrawtemplate'.
  419. 'cdx' -> 'chemical/x-cdx'.
  420. 'cdy' -> 'application/vnd.cinderella'.
  421. 'cef' -> 'chemical/x-cxf'.
  422. 'cer' -> 'chemical/x-cerius'.
  423. 'chm' -> 'chemical/x-chemdraw'.
  424. 'chrt' -> 'application/x-kchart'.
  425. 'cif' -> 'chemical/x-cif'.
  426. 'class' -> 'application/java-vm'.
  427. 'cls' -> 'text/x-tex'.
  428. 'cmdf' -> 'chemical/x-cmdf'.
  429. 'cml' -> 'chemical/x-cml'.
  430. 'cod' -> 'application/vnd.rim.cod'.
  431. 'com' -> 'application/x-msdos-program'.
  432. 'cpa' -> 'chemical/x-compass'.
  433. 'cpio' -> 'application/x-cpio'.
  434. 'cpp' -> 'text/x-c++src'.
  435. 'cpt' -> 'image/x-corelphotopaint'.
  436. 'crl' -> 'application/x-pkcs7-crl'.
  437. 'crt' -> 'application/x-x509-ca-cert'.
  438. 'csf' -> 'chemical/x-cache-csf'.
  439. 'csh' -> 'text/x-csh'.
  440. 'csm' -> 'chemical/x-csml'.
  441. 'csml' -> 'chemical/x-csml'.
  442. 'css' -> 'text/css'.
  443. 'csv' -> 'text/comma-separated-values'.
  444. 'ctab' -> 'chemical/x-cactvs-binary'.
  445. 'ctx' -> 'chemical/x-ctx'.
  446. 'cu' -> 'application/cu-seeme'.
  447. 'cub' -> 'chemical/x-gaussian-cube'.
  448. 'cxf' -> 'chemical/x-cxf'.
  449. 'cxx' -> 'text/x-c++src'.
  450. 'dat' -> 'chemical/x-mopac-input'.
  451. 'dcr' -> 'application/x-director'.
  452. 'deb' -> 'application/x-debian-package'.
  453. 'dif' -> 'video/dv'.
  454. 'diff' -> 'text/plain'.
  455. 'dir' -> 'application/x-director'.
  456. 'djv' -> 'image/vnd.djvu'.
  457. 'djvu' -> 'image/vnd.djvu'.
  458. 'dl' -> 'video/dl'.
  459. 'dll' -> 'application/x-msdos-program'.
  460. 'dmg' -> 'application/x-apple-diskimage'.
  461. 'dms' -> 'application/x-dms'.
  462. 'doc' -> 'application/msword'.
  463. 'dot' -> 'application/msword'.
  464. 'dv' -> 'video/dv'.
  465. 'dvi' -> 'application/x-dvi'.
  466. 'dx' -> 'chemical/x-jcamp-dx'.
  467. 'dxr' -> 'application/x-director'.
  468. 'emb' -> 'chemical/x-embl-dl-nucleotide'.
  469. 'embl' -> 'chemical/x-embl-dl-nucleotide'.
  470. 'ent' -> 'chemical/x-pdb'.
  471. 'eps' -> 'application/postscript'.
  472. 'etx' -> 'text/x-setext'.
  473. 'exe' -> 'application/x-msdos-program'.
  474. 'ez' -> 'application/andrew-inset'.
  475. 'fb' -> 'application/x-maker'.
  476. 'fbdoc' -> 'application/x-maker'.
  477. 'fch' -> 'chemical/x-gaussian-checkpoint'.
  478. 'fchk' -> 'chemical/x-gaussian-checkpoint'.
  479. 'fig' -> 'application/x-xfig'.
  480. 'flac' -> 'application/x-flac'.
  481. 'fli' -> 'video/fli'.
  482. 'fm' -> 'application/x-maker'.
  483. 'frame' -> 'application/x-maker'.
  484. 'frm' -> 'application/x-maker'.
  485. 'gal' -> 'chemical/x-gaussian-log'.
  486. 'gam' -> 'chemical/x-gamess-input'.
  487. 'gamin' -> 'chemical/x-gamess-input'.
  488. 'gau' -> 'chemical/x-gaussian-input'.
  489. 'gcd' -> 'text/x-pcs-gcd'.
  490. 'gcf' -> 'application/x-graphing-calculator'.
  491. 'gcg' -> 'chemical/x-gcg8-sequence'.
  492. 'gen' -> 'chemical/x-genbank'.
  493. 'gf' -> 'application/x-tex-gf'.
  494. 'gif' -> 'image/gif'.
  495. 'gjc' -> 'chemical/x-gaussian-input'.
  496. 'gjf' -> 'chemical/x-gaussian-input'.
  497. 'gl' -> 'video/gl'.
  498. 'gnumeric' -> 'application/x-gnumeric'.
  499. 'gpt' -> 'chemical/x-mopac-graph'.
  500. 'gsf' -> 'application/x-font'.
  501. 'gsm' -> 'audio/x-gsm'.
  502. 'gtar' -> 'application/x-gtar'.
  503. 'h' -> 'text/x-chdr'.
  504. 'h++' -> 'text/x-c++hdr'.
  505. 'hdf' -> 'application/x-hdf'.
  506. 'hh' -> 'text/x-c++hdr'.
  507. 'hin' -> 'chemical/x-hin'.
  508. 'hpp' -> 'text/x-c++hdr'.
  509. 'hqx' -> 'application/mac-binhex40'.
  510. 'hs' -> 'text/x-haskell'.
  511. 'hta' -> 'application/hta'.
  512. 'htc' -> 'text/x-component'.
  513. 'htm' -> 'text/html'.
  514. 'html' -> 'text/html'.
  515. 'hxx' -> 'text/x-c++hdr'.
  516. 'ica' -> 'application/x-ica'.
  517. 'ice' -> 'x-conference/x-cooltalk'.
  518. 'ico' -> 'image/x-icon'.
  519. 'ics' -> 'text/calendar'.
  520. 'icz' -> 'text/calendar'.
  521. 'ief' -> 'image/ief'.
  522. 'iges' -> 'model/iges'.
  523. 'igs' -> 'model/iges'.
  524. 'iii' -> 'application/x-iphone'.
  525. 'inp' -> 'chemical/x-gamess-input'.
  526. 'ins' -> 'application/x-internet-signup'.
  527. 'iso' -> 'application/x-iso9660-image'.
  528. 'isp' -> 'application/x-internet-signup'.
  529. 'ist' -> 'chemical/x-isostar'.
  530. 'istr' -> 'chemical/x-isostar'.
  531. 'jad' -> 'text/vnd.sun.j2me.app-descriptor'.
  532. 'jar' -> 'application/java-archive'.
  533. 'java' -> 'text/x-java'.
  534. 'jdx' -> 'chemical/x-jcamp-dx'.
  535. 'jmz' -> 'application/x-jmol'.
  536. 'jng' -> 'image/x-jng'.
  537. 'jnlp' -> 'application/x-java-jnlp-file'.
  538. 'jpe' -> 'image/jpeg'.
  539. 'jpeg' -> 'image/jpeg'.
  540. 'jpg' -> 'image/jpeg'.
  541. 'js' -> 'application/javascript'.
  542. 'kar' -> 'audio/midi'.
  543. 'key' -> 'application/pgp-keys'.
  544. 'kil' -> 'application/x-killustrator'.
  545. 'kin' -> 'chemical/x-kinemage'.
  546. 'kpr' -> 'application/x-kpresenter'.
  547. 'kpt' -> 'application/x-kpresenter'.
  548. 'ksp' -> 'application/x-kspread'.
  549. 'kwd' -> 'application/x-kword'.
  550. 'kwt' -> 'application/x-kword'.
  551. 'latex' -> 'application/x-latex'.
  552. 'lha' -> 'application/x-lha'.
  553. 'lhs' -> 'text/x-literate-haskell'.
  554. 'lsf' -> 'video/x-la-asf'.
  555. 'lsx' -> 'video/x-la-asf'.
  556. 'ltx' -> 'text/x-tex'.
  557. 'lzh' -> 'application/x-lzh'.
  558. 'lzx' -> 'application/x-lzx'.
  559. 'm3u' -> 'audio/x-mpegurl'.
  560. 'm4a' -> 'audio/mpeg'.
  561. 'maker' -> 'application/x-maker'.
  562. 'man' -> 'application/x-troff-man'.
  563. 'mcif' -> 'chemical/x-mmcif'.
  564. 'mcm' -> 'chemical/x-macmolecule'.
  565. 'mdb' -> 'application/msaccess'.
  566. 'me' -> 'application/x-troff-me'.
  567. 'mesh' -> 'model/mesh'.
  568. 'mid' -> 'audio/midi'.
  569. 'midi' -> 'audio/midi'.
  570. 'mif' -> 'application/x-mif'.
  571. 'mm' -> 'application/x-freemind'.
  572. 'mmd' -> 'chemical/x-macromodel-input'.
  573. 'mmf' -> 'application/vnd.smaf'.
  574. 'mml' -> 'text/mathml'.
  575. 'mmod' -> 'chemical/x-macromodel-input'.
  576. 'mng' -> 'video/x-mng'.
  577. 'moc' -> 'text/x-moc'.
  578. 'mol' -> 'chemical/x-mdl-molfile'.
  579. 'mol2' -> 'chemical/x-mol2'.
  580. 'moo' -> 'chemical/x-mopac-out'.
  581. 'mop' -> 'chemical/x-mopac-input'.
  582. 'mopcrt' -> 'chemical/x-mopac-input'.
  583. 'mov' -> 'video/quicktime'.
  584. 'movie' -> 'video/x-sgi-movie'.
  585. 'mp2' -> 'audio/mpeg'.
  586. 'mp3' -> 'audio/mpeg'.
  587. 'mp4' -> 'video/mp4'.
  588. 'mpc' -> 'chemical/x-mopac-input'.
  589. 'mpe' -> 'video/mpeg'.
  590. 'mpeg' -> 'video/mpeg'.
  591. 'mpega' -> 'audio/mpeg'.
  592. 'mpg' -> 'video/mpeg'.
  593. 'mpga' -> 'audio/mpeg'.
  594. 'ms' -> 'application/x-troff-ms'.
  595. 'msh' -> 'model/mesh'.
  596. 'msi' -> 'application/x-msi'.
  597. 'mvb' -> 'chemical/x-mopac-vib'.
  598. 'mxu' -> 'video/vnd.mpegurl'.
  599. 'nb' -> 'application/mathematica'.
  600. 'nc' -> 'application/x-netcdf'.
  601. 'nwc' -> 'application/x-nwc'.
  602. 'o' -> 'application/x-object'.
  603. 'oda' -> 'application/oda'.
  604. 'odb' -> 'application/vnd.oasis.opendocument.database'.
  605. 'odc' -> 'application/vnd.oasis.opendocument.chart'.
  606. 'odf' -> 'application/vnd.oasis.opendocument.formula'.
  607. 'odg' -> 'application/vnd.oasis.opendocument.graphics'.
  608. 'odi' -> 'application/vnd.oasis.opendocument.image'.
  609. 'odm' -> 'application/vnd.oasis.opendocument.text-master'.
  610. 'odp' -> 'application/vnd.oasis.opendocument.presentation'.
  611. 'ods' -> 'application/vnd.oasis.opendocument.spreadsheet'.
  612. 'odt' -> 'application/vnd.oasis.opendocument.text'.
  613. 'ogg' -> 'application/ogg'.
  614. 'old' -> 'application/x-trash'.
  615. 'oth' -> 'application/vnd.oasis.opendocument.text-web'.
  616. 'oza' -> 'application/x-oz-application'.
  617. 'p' -> 'text/x-pascal'.
  618. 'p7r' -> 'application/x-pkcs7-certreqresp'.
  619. 'pac' -> 'application/x-ns-proxy-autoconfig'.
  620. 'pas' -> 'text/x-pascal'.
  621. 'pat' -> 'image/x-coreldrawpattern'.
  622. 'pbm' -> 'image/x-portable-bitmap'.
  623. 'pcf' -> 'application/x-font'.
  624. 'pcf.Z' -> 'application/x-font'.
  625. 'pcx' -> 'image/pcx'.
  626. 'pdb' -> 'chemical/x-pdb'.
  627. 'pdf' -> 'application/pdf'.
  628. 'pfa' -> 'application/x-font'.
  629. 'pfb' -> 'application/x-font'.
  630. 'pgm' -> 'image/x-portable-graymap'.
  631. 'pgn' -> 'application/x-chess-pgn'.
  632. 'pgp' -> 'application/pgp-signature'.
  633. 'pk' -> 'application/x-tex-pk'.
  634. 'pl' -> 'text/x-perl'.
  635. 'pls' -> 'audio/x-scpls'.
  636. 'pm' -> 'text/x-perl'.
  637. 'png' -> 'image/png'.
  638. 'pnm' -> 'image/x-portable-anymap'.
  639. 'pot' -> 'text/plain'.
  640. 'ppm' -> 'image/x-portable-pixmap'.
  641. 'pps' -> 'application/vnd.ms-powerpoint'.
  642. 'ppt' -> 'application/vnd.ms-powerpoint'.
  643. 'prf' -> 'application/pics-rules'.
  644. 'prt' -> 'chemical/x-ncbi-asn1-ascii'.
  645. 'ps' -> 'application/postscript'.
  646. 'psd' -> 'image/x-photoshop'.
  647. 'psp' -> 'text/x-psp'.
  648. 'py' -> 'text/x-python'.
  649. 'pyc' -> 'application/x-python-code'.
  650. 'pyo' -> 'application/x-python-code'.
  651. 'qt' -> 'video/quicktime'.
  652. 'qtl' -> 'application/x-quicktimeplayer'.
  653. 'ra' -> 'audio/x-realaudio'.
  654. 'ram' -> 'audio/x-pn-realaudio'.
  655. 'rar' -> 'application/rar'.
  656. 'ras' -> 'image/x-cmu-raster'.
  657. 'rd' -> 'chemical/x-mdl-rdfile'.
  658. 'rdf' -> 'application/rdf+xml'.
  659. 'rgb' -> 'image/x-rgb'.
  660. 'rm' -> 'audio/x-pn-realaudio'.
  661. 'roff' -> 'application/x-troff'.
  662. 'ros' -> 'chemical/x-rosdal'.
  663. 'rpm' -> 'application/x-redhat-package-manager'.
  664. 'rss' -> 'application/rss+xml'.
  665. 'rtf' -> 'text/rtf'.
  666. 'rtx' -> 'text/richtext'.
  667. 'rxn' -> 'chemical/x-mdl-rxnfile'.
  668. 'sct' -> 'text/scriptlet'.
  669. 'sd' -> 'chemical/x-mdl-sdfile'.
  670. 'sd2' -> 'audio/x-sd2'.
  671. 'sda' -> 'application/vnd.stardivision.draw'.
  672. 'sdc' -> 'application/vnd.stardivision.calc'.
  673. 'sdd' -> 'application/vnd.stardivision.impress'.
  674. 'sdf' -> 'chemical/x-mdl-sdfile'.
  675. 'sdp' -> 'application/vnd.stardivision.impress'.
  676. 'sdw' -> 'application/vnd.stardivision.writer'.
  677. 'ser' -> 'application/java-serialized-object'.
  678. 'sgf' -> 'application/x-go-sgf'.
  679. 'sgl' -> 'application/vnd.stardivision.writer-global'.
  680. 'sh' -> 'text/x-sh'.
  681. 'shar' -> 'application/x-shar'.
  682. 'shtml' -> 'text/html'.
  683. 'sid' -> 'audio/prs.sid'.
  684. 'sik' -> 'application/x-trash'.
  685. 'silo' -> 'model/mesh'.
  686. 'sis' -> 'application/vnd.symbian.install'.
  687. 'sit' -> 'application/x-stuffit'.
  688. 'skd' -> 'application/x-koan'.
  689. 'skm' -> 'application/x-koan'.
  690. 'skp' -> 'application/x-koan'.
  691. 'skt' -> 'application/x-koan'.
  692. 'smf' -> 'application/vnd.stardivision.math'.
  693. 'smi' -> 'application/smil'.
  694. 'smil' -> 'application/smil'.
  695. 'snd' -> 'audio/basic'.
  696. 'spc' -> 'chemical/x-galactic-spc'.
  697. 'spl' -> 'application/x-futuresplash'.
  698. 'src' -> 'application/x-wais-source'.
  699. 'stc' -> 'application/vnd.sun.xml.calc.template'.
  700. 'std' -> 'application/vnd.sun.xml.draw.template'.
  701. 'sti' -> 'application/vnd.sun.xml.impress.template'.
  702. 'stl' -> 'application/vnd.ms-pki.stl'.
  703. 'stw' -> 'application/vnd.sun.xml.writer.template'.
  704. 'sty' -> 'text/x-tex'.
  705. 'sv4cpio' -> 'application/x-sv4cpio'.
  706. 'sv4crc' -> 'application/x-sv4crc'.
  707. 'svg' -> 'image/svg+xml'.
  708. 'svgz' -> 'image/svg+xml'.
  709. 'sw' -> 'chemical/x-swissprot'.
  710. 'swf' -> 'application/x-shockwave-flash'.
  711. 'swfl' -> 'application/x-shockwave-flash'.
  712. 'sxc' -> 'application/vnd.sun.xml.calc'.
  713. 'sxd' -> 'application/vnd.sun.xml.draw'.
  714. 'sxg' -> 'application/vnd.sun.xml.writer.global'.
  715. 'sxi' -> 'application/vnd.sun.xml.impress'.
  716. 'sxm' -> 'application/vnd.sun.xml.math'.
  717. 'sxw' -> 'application/vnd.sun.xml.writer'.
  718. 't' -> 'application/x-troff'.
  719. 'tar' -> 'application/x-tar'.
  720. 'taz' -> 'application/x-gtar'.
  721. 'tcl' -> 'text/x-tcl'.
  722. 'tex' -> 'text/x-tex'.
  723. 'texi' -> 'application/x-texinfo'.
  724. 'texinfo' -> 'application/x-texinfo'.
  725. 'text' -> 'text/plain'.
  726. 'tgf' -> 'chemical/x-mdl-tgf'.
  727. 'tgz' -> 'application/x-gtar'.
  728. 'tif' -> 'image/tiff'.
  729. 'tiff' -> 'image/tiff'.
  730. 'tk' -> 'text/x-tcl'.
  731. 'tm' -> 'text/texmacs'.
  732. 'torrent' -> 'application/x-bittorrent'.
  733. 'tr' -> 'application/x-troff'.
  734. 'ts' -> 'text/texmacs'.
  735. 'tsp' -> 'application/dsptype'.
  736. 'tsv' -> 'text/tab-separated-values'.
  737. 'txt' -> 'text/plain'.
  738. 'udeb' -> 'application/x-debian-package'.
  739. 'uls' -> 'text/iuls'.
  740. 'ustar' -> 'application/x-ustar'.
  741. 'val' -> 'chemical/x-ncbi-asn1-binary'.
  742. 'vcd' -> 'application/x-cdlink'.
  743. 'vcf' -> 'text/x-vcard'.
  744. 'vcs' -> 'text/x-vcalendar'.
  745. 'vmd' -> 'chemical/x-vmd'.
  746. 'vms' -> 'chemical/x-vamas-iso14976'.
  747. 'vor' -> 'application/vnd.stardivision.writer'.
  748. 'vrm' -> 'x-world/x-vrml'.
  749. 'vrml' -> 'x-world/x-vrml'.
  750. 'vsd' -> 'application/vnd.visio'.
  751. 'wad' -> 'application/x-doom'.
  752. 'wav' -> 'audio/x-wav'.
  753. 'wax' -> 'audio/x-ms-wax'.
  754. 'wbmp' -> 'image/vnd.wap.wbmp'.
  755. 'wbxml' -> 'application/vnd.wap.wbxml'.
  756. 'wk' -> 'application/x-123'.
  757. 'wm' -> 'video/x-ms-wm'.
  758. 'wma' -> 'audio/x-ms-wma'.
  759. 'wmd' -> 'application/x-ms-wmd'.
  760. 'wml' -> 'text/vnd.wap.wml'.
  761. 'wmlc' -> 'application/vnd.wap.wmlc'.
  762. 'wmls' -> 'text/vnd.wap.wmlscript'.
  763. 'wmlsc' -> 'application/vnd.wap.wmlscriptc'.
  764. 'wmv' -> 'video/x-ms-wmv'.
  765. 'wmx' -> 'video/x-ms-wmx'.
  766. 'wmz' -> 'application/x-ms-wmz'.
  767. 'wp5' -> 'application/wordperfect5.1'.
  768. 'wpd' -> 'application/wordperfect'.
  769. 'wrl' -> 'x-world/x-vrml'.
  770. 'wsc' -> 'text/scriptlet'.
  771. 'wvx' -> 'video/x-ms-wvx'.
  772. 'wz' -> 'application/x-wingz'.
  773. 'xbm' -> 'image/x-xbitmap'.
  774. 'xcf' -> 'application/x-xcf'.
  775. 'xht' -> 'application/xhtml+xml'.
  776. 'xhtml' -> 'application/xhtml+xml'.
  777. 'xlb' -> 'application/vnd.ms-excel'.
  778. 'xls' -> 'application/vnd.ms-excel'.
  779. 'xlt' -> 'application/vnd.ms-excel'.
  780. 'xml' -> 'application/xml'.
  781. 'xpi' -> 'application/x-xpinstall'.
  782. 'xpm' -> 'image/x-xpixmap'.
  783. 'xsl' -> 'application/xml'.
  784. 'xtel' -> 'chemical/x-xtel'.
  785. 'xul' -> 'application/vnd.mozilla.xul+xml'.
  786. 'xwd' -> 'image/x-xwindowdump'.
  787. 'xyz' -> 'chemical/x-xyz'.
  788. 'zip' -> 'application/zip'.
  789. 'zmt' -> 'chemical/x-mopac-input'.
  790. '~' -> 'application/x-trash'
  791. }
  792. !
  793. defaultPort
  794. ^ 4000
  795. !
  796. mimeTypeFor: aString
  797. ^ self mimeTypes at: (aString replace: '.*[\.]' with: '') ifAbsent: ['text/plain']
  798. !
  799. mimeTypes
  800. ^ mimeTypes ifNil: [mimeTypes := self defaultMimeTypes]
  801. !
  802. printHelp
  803. console log: 'Available commandline options are:'.
  804. console log: '--help'.
  805. self commandLineSwitches do: [ :each |
  806. console log: each, ' <parameter>']
  807. !
  808. selectorForCommandLineSwitch: aSwitch
  809. "Remove the trailing '--', add ':' at the end
  810. and replace all occurences of a lowercase letter preceded by a '-' with
  811. the Uppercase letter.
  812. Example: --fallback-page becomes fallbackPage:"
  813. ^ ((aSwitch replace: '^--' with: '')
  814. replace: '-[a-z]' with: [ :each | each second asUppercase ]), ':'
  815. ! !
  816. !FileServer class methodsFor: 'initialization'!
  817. createServerWithArguments: options
  818. "If options are empty return a default FileServer instance.
  819. If options are given loop through them and set the passed in values
  820. on the FileServer instance.
  821. Commanline options map directly to methods in the 'accessing' protocol
  822. taking one parameter.
  823. Adding a method to this protocol makes it directly settable through
  824. command line options.
  825. "
  826. | server popFront front optionName optionValue switches |
  827. switches := self commandLineSwitches.
  828. server := self new.
  829. options ifEmpty: [^server].
  830. (options size even) ifFalse: [
  831. console log: 'Using default parameters.'.
  832. console log: 'Wrong commandline options or not enough arguments for: ' , options.
  833. console log: 'Use any of the following ones: ', switches.
  834. ^server].
  835. popFront := [:args |
  836. front := args first.
  837. args remove: front.
  838. front].
  839. [options notEmpty] whileTrue: [
  840. optionName := popFront value: options.
  841. optionValue := popFront value: options.
  842. (switches includes: optionName) ifTrue: [
  843. optionName := self selectorForCommandLineSwitch: optionName.
  844. server perform: optionName withArguments: (Array with: optionValue)]
  845. ifFalse: [
  846. console log: optionName, ' is not a valid commandline option'.
  847. console log: 'Use any of the following ones: ', switches ]].
  848. ^ server.
  849. !
  850. main
  851. "Main entry point for Amber applications.
  852. Creates and starts a FileServer instance."
  853. | fileServer args |
  854. args := process argv.
  855. "Remove the first args which contain the path to the node executable and the script file."
  856. args removeFrom: 1 to: 3.
  857. args detect: [ :each |
  858. (each = '--help') ifTrue: [FileServer printHelp]]
  859. ifNone: [
  860. fileServer := FileServer createServerWithArguments: args.
  861. ^ fileServer start]
  862. ! !
  863. BaseFileManipulator subclass: #Initer
  864. instanceVariableNames: 'childProcess nmPath'
  865. package: 'AmberCli'!
  866. !Initer methodsFor: 'action'!
  867. bowerInstallThenDo: aBlock
  868. | child |
  869. child := childProcess
  870. exec: (path join: nmPath with: '.bin' with: 'bower'), ' install'
  871. thenDo: aBlock.
  872. child stdout pipe: process stdout options: #{ 'end' -> false }
  873. !
  874. finishMessage
  875. console log: (#(
  876. ' '
  877. 'The project should now be set up.'
  878. ' '
  879. ' '
  880. 'To manage project from cli (run tests, recompile),'
  881. 'the `grunt` command-line tool needs to be installed.'
  882. 'If not present, it can be installed with:'
  883. ' (sudo) npm install -g grunt-cli'
  884. ' '
  885. 'To manage project dependencies,'
  886. 'the `bower` command-line tool needs to be installed.'
  887. 'If not present, it can be installed with:'
  888. ' (sudo) npm install -g bower'
  889. ' '
  890. ) join: String lf).
  891. [] valueWithTimeout: 600
  892. !
  893. gruntInitThenDo: aBlock
  894. | child |
  895. child := childProcess
  896. exec: (path join: nmPath with: '.bin' with: 'grunt-init'), ' ', (((path join: nmPath with: 'grunt-init-amber') replace: '\\' with: '\\') replace: ':' with: '\:')
  897. thenDo: aBlock.
  898. child stdout pipe: process stdout options: #{ 'end' -> false }.
  899. process stdin resume.
  900. process stdin pipe: child stdin options: #{ 'end' -> false }
  901. !
  902. gruntThenDo: aBlock
  903. | child |
  904. child := childProcess
  905. exec: (path join: nmPath with: '.bin' with: 'grunt')
  906. thenDo: aBlock.
  907. child stdout pipe: process stdout options: #{ 'end' -> false }
  908. !
  909. npmInstallThenDo: aBlock
  910. | child |
  911. child := childProcess
  912. exec: 'npm install'
  913. thenDo: aBlock.
  914. child stdout pipe: process stdout options: #{ 'end' -> false }
  915. !
  916. start
  917. self gruntInitThenDo: [ :error | error
  918. ifNotNil: [
  919. console log: 'grunt-init exec error:'; log: error.
  920. process exit ]
  921. ifNil: [
  922. self bowerInstallThenDo: [ :error2 | error2
  923. ifNotNil: [
  924. console log: 'bower install exec error:'; log: error2.
  925. process exit ]
  926. ifNil: [
  927. self npmInstallThenDo: [ :error3 | error3
  928. ifNotNil: [
  929. console log: 'npm install exec error:'; log: error3.
  930. process exit ]
  931. ifNil: [
  932. self gruntThenDo: [ :error4 | error4
  933. ifNotNil: [
  934. console log: 'grunt exec error:'; log: error4.
  935. process exit ]
  936. ifNil: [
  937. self finishMessage.
  938. process exit ]]]]]]]]
  939. ! !
  940. !Initer methodsFor: 'initialization'!
  941. initialize
  942. super initialize.
  943. childProcess := require value: 'child_process'.
  944. nmPath := path join: self rootDirname with: 'node_modules'
  945. ! !
  946. Object subclass: #NodeTestRunner
  947. instanceVariableNames: ''
  948. package: 'AmberCli'!
  949. !NodeTestRunner class methodsFor: 'not yet classified'!
  950. runTestSuite
  951. | suite worker |
  952. suite := OrderedCollection new.
  953. (TestCase allSubclasses select: [ :each | each isAbstract not ])
  954. do: [ :each | suite addAll: each buildSuite ].
  955. worker := TestSuiteRunner on: suite.
  956. worker announcer on: ResultAnnouncement do:
  957. [ :ann | | result |
  958. result := ann result.
  959. result runs = result total ifTrue: [
  960. console log: result runs asString, ' tests run, ', result failures size asString, ' failures, ', result errors size asString, ' errors.'.
  961. result failures isEmpty ifFalse: [
  962. result failures first runCase.
  963. "the line above should throw, normally, but just in case I leave the line below"
  964. self throw: result failures first class name, ' >> ', result failures first selector, ' is failing!!!!' ].
  965. result errors isEmpty ifFalse: [
  966. result errors first runCase.
  967. "the line above should throw, normally, but just in case I leave the line below"
  968. self throw: result errors first class name, ' >> ', result errors first selector, ' has errors!!!!' ].
  969. ]].
  970. worker run
  971. ! !
  972. Object subclass: #Repl
  973. instanceVariableNames: 'readline interface util session resultCount commands'
  974. package: 'AmberCli'!
  975. !Repl commentStamp!
  976. I am a class representing a REPL (Read Evaluate Print Loop) and provide a command line interface to Amber Smalltalk.
  977. On the prompt you can type Amber statements which will be evaluated after pressing <Enter>.
  978. The evaluation is comparable with executing a 'DoIt' in a workspace.
  979. My runtime requirement is a functional Node.js executable with working Readline support.!
  980. !Repl methodsFor: 'accessing'!
  981. commands
  982. ^ commands
  983. !
  984. prompt
  985. ^ 'amber >> '
  986. ! !
  987. !Repl methodsFor: 'actions'!
  988. clearScreen
  989. | esc cls |
  990. esc := String fromCharCode: 27.
  991. cls := esc, '[2J', esc, '[0;0f'.
  992. process stdout write: cls.
  993. interface prompt
  994. !
  995. close
  996. process stdin destroy
  997. !
  998. createInterface
  999. interface := readline createInterface: process stdin stdout: process stdout.
  1000. interface on: 'line' do: [:buffer | self processLine: buffer].
  1001. interface on: 'close' do: [self close].
  1002. self printWelcome; setupHotkeys; setPrompt.
  1003. interface prompt
  1004. !
  1005. eval: buffer
  1006. ^ self eval: buffer on: DoIt new.
  1007. !
  1008. eval: buffer on: anObject
  1009. | result |
  1010. buffer isEmpty ifFalse: [
  1011. [result := Compiler new evaluateExpression: buffer on: anObject]
  1012. tryCatch: [:e |
  1013. e isSmalltalkError
  1014. ifTrue: [ e resignal ]
  1015. ifFalse: [ process stdout write: e jsStack ]]].
  1016. ^ result
  1017. !
  1018. printWelcome
  1019. Transcript show: 'Type :q to exit.'; cr.
  1020. !
  1021. setPrompt
  1022. interface setPrompt: self prompt
  1023. ! !
  1024. !Repl methodsFor: 'initialization'!
  1025. initialize
  1026. super initialize.
  1027. session := DoIt new.
  1028. readline := require value: 'readline'.
  1029. util := require value: 'util'.
  1030. self setupCommands
  1031. !
  1032. setupCommands
  1033. commands := Dictionary from: {
  1034. {':q'} -> [process exit].
  1035. {''} -> [interface prompt]}
  1036. !
  1037. setupHotkeys
  1038. process stdin on: 'keypress' do: [:s :key | key ifNotNil: [self onKeyPress: key]].
  1039. ! !
  1040. !Repl methodsFor: 'private'!
  1041. addVariableNamed: aString to: anObject
  1042. | newClass newObject |
  1043. newClass := self subclass: anObject class withVariable: aString.
  1044. self encapsulateVariable: aString withValue: anObject in: newClass.
  1045. newObject := newClass new.
  1046. self setPreviousVariablesFor: newObject from: anObject.
  1047. ^ newObject
  1048. !
  1049. assignNewVariable: buffer do: aBlock
  1050. "Assigns a new variable and calls the given block with the variable's name and value
  1051. if buffer contains an assignment expression. If it doesn't the block is called with nil for
  1052. both arguments."
  1053. ^ self parseAssignment: buffer do: [ :name :expr || varName value |
  1054. varName := name ifNil: [self nextResultName].
  1055. session := self addVariableNamed: varName to: session.
  1056. [ value := self eval: varName, ' := ', (expr ifNil: [buffer]) on: session ]
  1057. on: Error
  1058. do: [ :e | ConsoleErrorHandler new logError: e. value := nil].
  1059. aBlock value: varName value: value]
  1060. !
  1061. encapsulateVariable: aString withValue: anObject in: aClass
  1062. "Add getter and setter for given variable to session."
  1063. | compiler |
  1064. compiler := Compiler new.
  1065. compiler install: aString, ': anObject ^ ', aString, ' := anObject' forClass: aClass protocol: 'session'.
  1066. compiler install: aString, ' ^ ', aString forClass: aClass protocol: 'session'.
  1067. !
  1068. executeCommand: aString
  1069. "Tries to process the given string as a command. Returns true if it was a command, false if not."
  1070. self commands keysAndValuesDo: [:names :cmd |
  1071. (names includes: aString) ifTrue: [
  1072. cmd value.
  1073. ^ true]].
  1074. ^ false
  1075. !
  1076. instanceVariableNamesFor: aClass
  1077. "Yields all instance variable names for the given class, including inherited ones."
  1078. ^ aClass superclass
  1079. ifNotNil: [
  1080. aClass instanceVariableNames copyWithAll: (self instanceVariableNamesFor: aClass superclass)]
  1081. ifNil: [
  1082. aClass instanceVariableNames]
  1083. !
  1084. isIdentifier: aString
  1085. ^ aString match: '^[a-z_]\w*$' asRegexp
  1086. !
  1087. isVariableDefined: aString
  1088. ^ (self instanceVariableNamesFor: session class) includes: aString
  1089. !
  1090. nextResultName
  1091. resultCount := resultCount
  1092. ifNotNil: [resultCount + 1]
  1093. ifNil: [1].
  1094. ^ 'res', resultCount asString
  1095. !
  1096. onKeyPress: key
  1097. (key ctrl and: [key name = 'l'])
  1098. ifTrue: [self clearScreen]
  1099. !
  1100. parseAssignment: aString do: aBlock
  1101. "Assigns a new variable if the given string is an assignment expression. Calls the given block with name and value.
  1102. If the string is not one no variable will be assigned and the block will be called with nil for both arguments."
  1103. | assignment |
  1104. assignment := (aString tokenize: ':=') collect: [:s | s trimBoth].
  1105. ^ (assignment size = 2 and: [self isIdentifier: assignment first])
  1106. ifTrue: [ aBlock value: assignment first value: assignment last ]
  1107. ifFalse: [ aBlock value: nil value: nil ]
  1108. !
  1109. presentResultNamed: varName withValue: value
  1110. Transcript show: varName, ': ', value class name, ' = ', value asString; cr.
  1111. interface prompt
  1112. !
  1113. processLine: buffer
  1114. "Processes lines entered through the readline interface."
  1115. | show |
  1116. show := [:varName :value | self presentResultNamed: varName withValue: value].
  1117. (self executeCommand: buffer) ifFalse: [
  1118. (self isVariableDefined: buffer)
  1119. ifTrue: [show value: buffer value: (session perform: buffer)]
  1120. ifFalse: [self assignNewVariable: buffer do: show]]
  1121. !
  1122. setPreviousVariablesFor: newObject from: oldObject
  1123. (self instanceVariableNamesFor: oldObject class) do: [:each |
  1124. newObject perform: each, ':' withArguments: {oldObject perform: each}].
  1125. !
  1126. subclass: aClass withVariable: varName
  1127. "Create subclass with new variable."
  1128. ^ ClassBuilder new
  1129. addSubclassOf: aClass
  1130. named: (self subclassNameFor: aClass) asSymbol
  1131. instanceVariableNames: {varName}
  1132. package: 'Compiler-Core'
  1133. !
  1134. subclassNameFor: aClass
  1135. ^ (aClass name matchesOf: '\d+$')
  1136. ifNotNil: [ | counter |
  1137. counter := (aClass name matchesOf: '\d+$') first asNumber + 1.
  1138. aClass name replaceRegexp: '\d+$' asRegexp with: counter asString]
  1139. ifNil: [
  1140. aClass name, '2'].
  1141. ! !
  1142. !Repl class methodsFor: 'initialization'!
  1143. main
  1144. self new createInterface
  1145. ! !