2
0

Importer-Exporter.st 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. Smalltalk current createPackage: 'Importer-Exporter'!
  2. Object subclass: #AbstractExporter
  3. instanceVariableNames: ''
  4. package: 'Importer-Exporter'!
  5. !AbstractExporter commentStamp!
  6. I am an abstract exporter for Amber source code.
  7. ## API
  8. Use `#exportPackage:on:` to export a given package on a Stream.!
  9. !AbstractExporter methodsFor: 'accessing'!
  10. extensionMethodsOfPackage: aPackage
  11. | result |
  12. result := OrderedCollection new.
  13. (self extensionProtocolsOfPackage: aPackage) do: [ :each |
  14. result addAll: each methods ].
  15. ^ result
  16. !
  17. extensionProtocolsOfPackage: aPackage
  18. | extensionName result |
  19. extensionName := '*', aPackage name.
  20. result := OrderedCollection new.
  21. "The classes must be loaded since it is extensions only.
  22. Therefore sorting (dependency resolution) does not matter here.
  23. Not sorting improves the speed by a number of magnitude."
  24. Smalltalk current classes do: [ :each |
  25. {each. each class} do: [ :behavior |
  26. (behavior protocols includes: extensionName) ifTrue: [
  27. result add: (ExportMethodProtocol name: extensionName theClass: behavior) ] ] ].
  28. ^result
  29. ! !
  30. !AbstractExporter methodsFor: 'convenience'!
  31. chunkEscape: aString
  32. "Replace all occurrences of !! with !!!! and trim at both ends."
  33. ^(aString replace: '!!' with: '!!!!') trimBoth
  34. !
  35. classNameFor: aClass
  36. ^aClass isMetaclass
  37. ifTrue: [ aClass instanceClass name, ' class' ]
  38. ifFalse: [
  39. aClass isNil
  40. ifTrue: [ 'nil' ]
  41. ifFalse: [ aClass name ] ]
  42. ! !
  43. !AbstractExporter methodsFor: 'output'!
  44. exportPackage: aPackage on: aStream
  45. self subclassResponsibility
  46. ! !
  47. AbstractExporter class instanceVariableNames: 'default'!
  48. !AbstractExporter class methodsFor: 'instance creation'!
  49. default
  50. ^ default ifNil: [ default := self new ]
  51. ! !
  52. AbstractExporter subclass: #ChunkExporter
  53. instanceVariableNames: ''
  54. package: 'Importer-Exporter'!
  55. !ChunkExporter commentStamp!
  56. I am an exporter dedicated to outputting Amber source code in the classic Smalltalk chunk format.
  57. I do not output any compiled code.!
  58. !ChunkExporter methodsFor: 'accessing'!
  59. ownMethodProtocolsOfClass: aClass
  60. "Answer a collection of ExportMethodProtocol object of aClass that are not package extensions"
  61. ^ aClass ownProtocols collect: [ :each |
  62. ExportMethodProtocol name: each theClass: aClass ]
  63. ! !
  64. !ChunkExporter methodsFor: 'output'!
  65. exportDefinitionOf: aClass on: aStream
  66. "Chunk format."
  67. aStream
  68. nextPutAll: (self classNameFor: aClass superclass);
  69. nextPutAll: ' subclass: #', (self classNameFor: aClass); lf;
  70. tab; nextPutAll: 'instanceVariableNames: '''.
  71. aClass instanceVariableNames
  72. do: [:each | aStream nextPutAll: each]
  73. separatedBy: [aStream nextPutAll: ' '].
  74. aStream
  75. nextPutAll: ''''; lf;
  76. tab; nextPutAll: 'package: ''', aClass category, '''!!'; lf.
  77. aClass comment notEmpty ifTrue: [
  78. aStream
  79. nextPutAll: '!!', (self classNameFor: aClass), ' commentStamp!!';lf;
  80. nextPutAll: (self chunkEscape: aClass comment), '!!';lf].
  81. aStream lf
  82. !
  83. exportMetaDefinitionOf: aClass on: aStream
  84. aClass class instanceVariableNames isEmpty ifFalse: [
  85. aStream
  86. nextPutAll: (self classNameFor: aClass class);
  87. nextPutAll: ' instanceVariableNames: '''.
  88. aClass class instanceVariableNames
  89. do: [:each | aStream nextPutAll: each]
  90. separatedBy: [aStream nextPutAll: ' '].
  91. aStream
  92. nextPutAll: '''!!'; lf; lf]
  93. !
  94. exportMethod: aMethod on: aStream
  95. aStream
  96. lf; lf; nextPutAll: (self chunkEscape: aMethod source); lf;
  97. nextPutAll: '!!'
  98. !
  99. exportPackage: aPackage on: aStream
  100. self exportPackageDefinitionOf: aPackage on: aStream.
  101. aPackage sortedClasses do: [ :each |
  102. self exportDefinitionOf: each on: aStream.
  103. self
  104. exportProtocols: (self ownMethodProtocolsOfClass: each)
  105. on: aStream.
  106. self exportMetaDefinitionOf: each on: aStream.
  107. self
  108. exportProtocols: (self ownMethodProtocolsOfClass: each class)
  109. on: aStream ].
  110. self
  111. exportProtocols: (self extensionProtocolsOfPackage: aPackage)
  112. on: aStream
  113. !
  114. exportPackageDefinitionOf: aPackage on: aStream
  115. aStream
  116. nextPutAll: 'Smalltalk current createPackage: ''', aPackage name, '''!!';
  117. lf
  118. !
  119. exportProtocol: aProtocol on: aStream
  120. self exportProtocolPrologueOf: aProtocol on: aStream.
  121. aProtocol methods do: [ :method |
  122. self exportMethod: method on: aStream ].
  123. self exportProtocolEpilogueOf: aProtocol on: aStream
  124. !
  125. exportProtocolEpilogueOf: aProtocol on: aStream
  126. aStream nextPutAll: ' !!'; lf; lf
  127. !
  128. exportProtocolPrologueOf: aProtocol on: aStream
  129. aStream
  130. nextPutAll: '!!', (self classNameFor: aProtocol theClass);
  131. nextPutAll: ' methodsFor: ''', aProtocol name, '''!!'
  132. !
  133. exportProtocols: aCollection on: aStream
  134. aCollection do: [ :each |
  135. self exportProtocol: each on: aStream ]
  136. ! !
  137. AbstractExporter subclass: #Exporter
  138. instanceVariableNames: ''
  139. package: 'Importer-Exporter'!
  140. !Exporter commentStamp!
  141. I am responsible for outputting Amber code into a JavaScript string.
  142. The generated output is enough to reconstruct the exported data, including Smalltalk source code and other metadata.
  143. ## Use case
  144. I am typically used to save code outside of the Amber runtime (committing to disk, etc.).!
  145. !Exporter methodsFor: 'convenience'!
  146. classNameFor: aClass
  147. ^aClass isMetaclass
  148. ifTrue: [ aClass instanceClass name, '.klass' ]
  149. ifFalse: [
  150. aClass isNil
  151. ifTrue: [ 'nil' ]
  152. ifFalse: [ aClass name ] ]
  153. ! !
  154. !Exporter methodsFor: 'output'!
  155. exportDefinitionOf: aClass on: aStream
  156. aStream
  157. lf;
  158. nextPutAll: 'smalltalk.addClass(';
  159. nextPutAll: '''', (self classNameFor: aClass), ''', ';
  160. nextPutAll: 'smalltalk.', (self classNameFor: aClass superclass);
  161. nextPutAll: ', ['.
  162. aClass instanceVariableNames
  163. do: [:each | aStream nextPutAll: '''', each, '''']
  164. separatedBy: [aStream nextPutAll: ', '].
  165. aStream
  166. nextPutAll: '], ''';
  167. nextPutAll: aClass category, '''';
  168. nextPutAll: ');'.
  169. aClass comment notEmpty ifTrue: [
  170. aStream
  171. lf;
  172. nextPutAll: 'smalltalk.';
  173. nextPutAll: (self classNameFor: aClass);
  174. nextPutAll: '.comment=';
  175. nextPutAll: aClass comment asJavascript;
  176. nextPutAll: ';'].
  177. aStream lf
  178. !
  179. exportMetaDefinitionOf: aClass on: aStream
  180. aStream lf.
  181. aClass class instanceVariableNames isEmpty ifFalse: [
  182. aStream
  183. nextPutAll: 'smalltalk.', (self classNameFor: aClass class);
  184. nextPutAll: '.iVarNames = ['.
  185. aClass class instanceVariableNames
  186. do: [:each | aStream nextPutAll: '''', each, '''']
  187. separatedBy: [aStream nextPutAll: ','].
  188. aStream nextPutAll: '];', String lf]
  189. !
  190. exportMethod: aMethod on: aStream
  191. aStream
  192. nextPutAll: 'smalltalk.addMethod(';lf;
  193. "nextPutAll: aMethod selector asSelector asJavascript, ',';lf;"
  194. nextPutAll: 'smalltalk.method({';lf;
  195. nextPutAll: 'selector: ', aMethod selector asJavascript, ',';lf;
  196. nextPutAll: 'category: ''', aMethod category, ''',';lf;
  197. nextPutAll: 'fn: ', aMethod fn compiledSource, ',';lf;
  198. nextPutAll: 'args: ', aMethod arguments asJavascript, ','; lf;
  199. nextPutAll: 'source: ', aMethod source asJavascript, ',';lf;
  200. nextPutAll: 'messageSends: ', aMethod messageSends asJavascript, ',';lf;
  201. nextPutAll: 'referencedClasses: ', aMethod referencedClasses asJavascript.
  202. aStream
  203. lf;
  204. nextPutAll: '}),';lf;
  205. nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);
  206. nextPutAll: ');';lf;lf
  207. !
  208. exportPackage: aPackage on: aStream
  209. self
  210. exportPackagePrologueOf: aPackage on: aStream;
  211. exportPackageDefinitionOf: aPackage on: aStream;
  212. exportPackageTransportOf: aPackage on: aStream.
  213. aPackage sortedClasses do: [ :each |
  214. self exportDefinitionOf: each on: aStream.
  215. each ownMethods do: [ :method |
  216. self exportMethod: method on: aStream ].
  217. self exportMetaDefinitionOf: each on: aStream.
  218. each class ownMethods do: [ :method |
  219. self exportMethod: method on: aStream ] ].
  220. (self extensionMethodsOfPackage: aPackage) do: [ :each |
  221. self exportMethod: each on: aStream ].
  222. self exportPackageEpilogueOf: aPackage on: aStream
  223. !
  224. exportPackageDefinitionOf: aPackage on: aStream
  225. aStream
  226. nextPutAll: 'smalltalk.addPackage(';
  227. nextPutAll: '''', aPackage name, ''');';
  228. lf
  229. !
  230. exportPackageEpilogueOf: aPackage on: aStream
  231. aStream
  232. nextPutAll: '})(global_smalltalk,global_nil,global__st);';
  233. lf
  234. !
  235. exportPackagePrologueOf: aPackage on: aStream
  236. aStream
  237. nextPutAll: '(function(smalltalk,nil,_st){';
  238. lf
  239. !
  240. exportPackageTransportOf: aPackage on: aStream
  241. | json |
  242. json := aPackage transportJson.
  243. json = 'null' ifFalse: [
  244. aStream
  245. nextPutAll: 'smalltalk.packages[';
  246. nextPutAll: aPackage name asJavascript;
  247. nextPutAll: '].transport = ';
  248. nextPutAll: json;
  249. nextPutAll: ';';
  250. lf ]
  251. ! !
  252. Exporter subclass: #AmdExporter
  253. instanceVariableNames: ''
  254. package: 'Importer-Exporter'!
  255. !AmdExporter methodsFor: 'output'!
  256. exportPackageEpilogueOf: aPackage on: aStream
  257. aStream
  258. nextPutAll: '});';
  259. lf
  260. !
  261. exportPackagePrologueOf: aPackage on: aStream
  262. aStream
  263. nextPutAll: 'define("';
  264. nextPutAll: (aPackage amdNamespace ifNil: [ 'amber' ]); "ifNil: only for LegacyPH, it should not happen with AmdPH"
  265. nextPutAll: '/';
  266. nextPutAll: aPackage name;
  267. nextPutAll: '", ';
  268. nextPutAll: (#('amber_vm/smalltalk' 'amber_vm/nil' 'amber_vm/_st'), (self amdNamesOfPackages: aPackage loadDependencies)) asJavascript;
  269. nextPutAll: ', function(smalltalk,nil,_st){';
  270. lf
  271. ! !
  272. !AmdExporter methodsFor: 'private'!
  273. amdNamesOfPackages: anArray
  274. ^ (anArray
  275. select: [ :each | each amdNamespace notNil ])
  276. collect: [ :each | each amdNamespace, '/', each name ]
  277. ! !
  278. Object subclass: #ChunkParser
  279. instanceVariableNames: 'stream'
  280. package: 'Importer-Exporter'!
  281. !ChunkParser commentStamp!
  282. I am responsible for parsing aStream contents in the chunk format.
  283. ## API
  284. ChunkParser new
  285. stream: aStream;
  286. nextChunk!
  287. !ChunkParser methodsFor: 'accessing'!
  288. stream: aStream
  289. stream := aStream
  290. ! !
  291. !ChunkParser methodsFor: 'reading'!
  292. nextChunk
  293. "The chunk format (Smalltalk Interchange Format or Fileout format)
  294. is a trivial format but can be a bit tricky to understand:
  295. - Uses the exclamation mark as delimiter of chunks.
  296. - Inside a chunk a normal exclamation mark must be doubled.
  297. - A non empty chunk must be a valid Smalltalk expression.
  298. - A chunk on top level with a preceding empty chunk is an instruction chunk:
  299. - The object created by the expression then takes over reading chunks.
  300. This method returns next chunk as a String (trimmed), empty String (all whitespace) or nil."
  301. | char result chunk |
  302. result := '' writeStream.
  303. [char := stream next.
  304. char notNil] whileTrue: [
  305. char = '!!' ifTrue: [
  306. stream peek = '!!'
  307. ifTrue: [stream next "skipping the escape double"]
  308. ifFalse: [^result contents trimBoth "chunk end marker found"]].
  309. result nextPut: char].
  310. ^nil "a chunk needs to end with !!"
  311. ! !
  312. !ChunkParser class methodsFor: 'instance creation'!
  313. on: aStream
  314. ^self new stream: aStream
  315. ! !
  316. Object subclass: #ExportMethodProtocol
  317. instanceVariableNames: 'name theClass'
  318. package: 'Importer-Exporter'!
  319. !ExportMethodProtocol commentStamp!
  320. I am an abstraction for a method protocol in a class / metaclass.
  321. I know of my class, name and methods.
  322. I am used when exporting a package.!
  323. !ExportMethodProtocol methodsFor: 'accessing'!
  324. methods
  325. ^ self theClass methodsInProtocol: self name
  326. !
  327. name
  328. ^name
  329. !
  330. name: aString
  331. name := aString
  332. !
  333. sortedMethods
  334. ^ self methods sorted: [ :a :b | a selector <= b selector ]
  335. !
  336. theClass
  337. ^theClass
  338. !
  339. theClass: aClass
  340. theClass := aClass
  341. ! !
  342. !ExportMethodProtocol class methodsFor: 'instance creation'!
  343. name: aString theClass: aClass
  344. ^self new
  345. name: aString;
  346. theClass: aClass;
  347. yourself
  348. ! !
  349. Object subclass: #ExportRecipeInterpreter
  350. instanceVariableNames: ''
  351. package: 'Importer-Exporter'!
  352. !ExportRecipeInterpreter commentStamp!
  353. I am an interpreter for export recipes.
  354. ## Recipe format
  355. Recipe is an array, which can contain two kinds of elements:
  356. - an assocation where the key is the receiver and the value is a two-arguments selector
  357. In this case, `receiver perform: selector withArguments: { data. stream }` is called.
  358. This essentially defines one step of export process.
  359. The key (eg. receiver) is presumed to be some kind of 'repository' of the exporting methods
  360. that just format appropriate aspect of data into a stream; like a class or a singleton,
  361. so that the recipe itself can be decoupled from data.
  362. - a subarray, where first element is special and the rest is recursive recipe.
  363. `subarray first` must be an association similar to one above,
  364. with key being the 'repository' receiver, but value is one-arg selector.
  365. In this case, `receiver perform: selector withArguments: { data }` should create a collection.
  366. Then, the sub-recipe (`subarray allButFirst`) is applied to every element of a collection, eg.
  367. collection do: [ :each | self export: each using: sa allButFirst on: stream ]!
  368. !ExportRecipeInterpreter methodsFor: 'interpreting'!
  369. interpret: aRecipe for: anObject on: aStream
  370. | recipeStream |
  371. recipeStream := aRecipe readStream.
  372. [ recipeStream atEnd ] whileFalse: [
  373. self
  374. interpretStep: recipeStream next
  375. for: anObject
  376. on: aStream ]
  377. !
  378. interpretStep: aRecipeStep for: anObject on: aStream
  379. aRecipeStep value == aRecipeStep ifTrue: [
  380. ^ self interpretSubRecipe: aRecipeStep for: anObject on: aStream ].
  381. aRecipeStep key perform: aRecipeStep value withArguments: { anObject. aStream }
  382. !
  383. interpretSubRecipe: aRecipe for: anObject on: aStream
  384. | selection |
  385. selection := aRecipe first key
  386. perform: aRecipe first value
  387. withArguments: { anObject }.
  388. selection do: [ :each |
  389. self interpret: aRecipe allButFirst for: each on: aStream ]
  390. ! !
  391. Object subclass: #Importer
  392. instanceVariableNames: ''
  393. package: 'Importer-Exporter'!
  394. !Importer commentStamp!
  395. I can import Amber code from a string in the chunk format.
  396. ## API
  397. Importer new import: aString!
  398. !Importer methodsFor: 'fileIn'!
  399. import: aStream
  400. | chunk result parser lastEmpty |
  401. parser := ChunkParser on: aStream.
  402. lastEmpty := false.
  403. [chunk := parser nextChunk.
  404. chunk isNil] whileFalse: [
  405. chunk isEmpty
  406. ifTrue: [lastEmpty := true]
  407. ifFalse: [
  408. result := Compiler new evaluateExpression: chunk.
  409. lastEmpty
  410. ifTrue: [
  411. lastEmpty := false.
  412. result scanFrom: parser]]]
  413. ! !
  414. InterfacingObject subclass: #PackageHandler
  415. instanceVariableNames: ''
  416. package: 'Importer-Exporter'!
  417. !PackageHandler commentStamp!
  418. I am responsible for handling package loading and committing.
  419. I should not be used directly. Instead, use the corresponding `Package` methods.!
  420. !PackageHandler methodsFor: 'accessing'!
  421. chunkContentsFor: aPackage
  422. ^ String streamContents: [ :str |
  423. self chunkExporter exportPackage: aPackage on: str ]
  424. !
  425. chunkExporterClass
  426. ^ ChunkExporter
  427. !
  428. commitPathJsFor: aPackage
  429. self subclassResponsibility
  430. !
  431. commitPathStFor: aPackage
  432. self subclassResponsibility
  433. !
  434. contentsFor: aPackage
  435. ^ String streamContents: [ :str |
  436. self exporter exportPackage: aPackage on: str ]
  437. !
  438. exporterClass
  439. ^ Exporter
  440. ! !
  441. !PackageHandler methodsFor: 'committing'!
  442. commit: aPackage
  443. {
  444. [ self commitStFileFor: aPackage ].
  445. [ self commitJsFileFor: aPackage ]
  446. }
  447. do: [ :each | each value ]
  448. displayingProgress: 'Committing package ', aPackage name
  449. !
  450. commitJsFileFor: aPackage
  451. self
  452. ajaxPutAt: (self commitPathJsFor: aPackage), '/', aPackage name, '.js'
  453. data: (self contentsFor: aPackage)
  454. !
  455. commitStFileFor: aPackage
  456. self
  457. ajaxPutAt: (self commitPathStFor: aPackage), '/', aPackage name, '.st'
  458. data: (self chunkContentsFor: aPackage)
  459. ! !
  460. !PackageHandler methodsFor: 'factory'!
  461. chunkExporter
  462. ^ self chunkExporterClass default
  463. !
  464. exporter
  465. ^ self exporterClass default
  466. ! !
  467. !PackageHandler methodsFor: 'private'!
  468. ajaxPutAt: aURL data: aString
  469. self
  470. ajax: #{
  471. 'url' -> aURL.
  472. 'type' -> 'PUT'.
  473. 'data' -> aString.
  474. 'contentType' -> 'text/plain;charset=UTF-8'.
  475. 'error' -> [ :xhr | self error: 'Commiting ' , aURL , ' failed with reason: "' , (xhr responseText) , '"'] }
  476. ! !
  477. PackageHandler class instanceVariableNames: 'registry'!
  478. !PackageHandler class methodsFor: 'accessing'!
  479. classRegisteredFor: aString
  480. ^ registry at: aString
  481. !
  482. for: aString
  483. ^ (self classRegisteredFor: aString) new
  484. ! !
  485. !PackageHandler class methodsFor: 'initialization'!
  486. initialize
  487. super initialize.
  488. registry := #{}
  489. ! !
  490. !PackageHandler class methodsFor: 'registry'!
  491. register: aClass for: aString
  492. registry at: aString put: aClass
  493. !
  494. registerFor: aString
  495. PackageHandler register: self for: aString
  496. ! !
  497. PackageHandler subclass: #AmdPackageHandler
  498. instanceVariableNames: ''
  499. package: 'Importer-Exporter'!
  500. !AmdPackageHandler commentStamp!
  501. I am responsible for handling package loading and committing.
  502. I should not be used directly. Instead, use the corresponding `Package` methods.!
  503. !AmdPackageHandler methodsFor: 'accessing'!
  504. commitPathJsFor: aPackage
  505. ^self toUrl: (self namespaceFor: aPackage)
  506. !
  507. commitPathStFor: aPackage
  508. "if _source is not mapped, .st commit will likely fail"
  509. ^self toUrl: (self namespaceFor: aPackage), '/_source'.
  510. !
  511. exporterClass
  512. ^ AmdExporter
  513. ! !
  514. !AmdPackageHandler methodsFor: 'committing'!
  515. namespaceFor: aPackage
  516. ^ aPackage amdNamespace
  517. ifNil: [ aPackage amdNamespace: self class defaultNamespace; amdNamespace ]
  518. ! !
  519. !AmdPackageHandler methodsFor: 'private'!
  520. toUrl: aString
  521. ^ Smalltalk current amdRequire
  522. ifNil: [ self error: 'AMD loader not present' ]
  523. ifNotNil: [ :require | (require basicAt: 'toUrl') value: aString ]
  524. ! !
  525. !AmdPackageHandler class methodsFor: 'commit paths'!
  526. defaultNamespace
  527. ^ Smalltalk current defaultAMDNamespace
  528. !
  529. defaultNamespace: aString
  530. Smalltalk current defaultAMDNamespace: aString
  531. !
  532. resetCommitPaths
  533. defaultNamespace := nil
  534. ! !
  535. !AmdPackageHandler class methodsFor: 'initialization'!
  536. initialize
  537. super initialize.
  538. self registerFor: 'amd'
  539. ! !
  540. Object subclass: #PluggableExporter
  541. instanceVariableNames: 'recipe'
  542. package: 'Importer-Exporter'!
  543. !PluggableExporter commentStamp!
  544. I am an engine for exporting structured data on a Stream.
  545. My instances are created using
  546. PluggableExporter forRecipe: aRecipe,
  547. where recipe is structured description of the exporting algorithm (see `ExportRecipeInterpreter`).
  548. The actual exporting is done by interpreting the recipe using a `RecipeInterpreter`.
  549. I am used to export amber packages, so I have a convenience method
  550. `exportPackage: aPackage on: aStream`
  551. which exports `aPackage` using the `recipe`
  552. (it is otherwise no special, so it may be renamed to export:on:)!
  553. !PluggableExporter methodsFor: 'accessing'!
  554. interpreter
  555. ^ ExportRecipeInterpreter new
  556. !
  557. recipe
  558. ^recipe
  559. !
  560. recipe: anArray
  561. recipe := anArray
  562. ! !
  563. !PluggableExporter methodsFor: 'fileOut'!
  564. exportAllPackages
  565. "Export all packages in the system."
  566. ^String streamContents: [:stream |
  567. Smalltalk current packages do: [:pkg |
  568. self exportPackage: pkg on: stream]]
  569. !
  570. exportPackage: aPackage on: aStream
  571. self interpreter interpret: self recipe for: aPackage on: aStream
  572. ! !
  573. !PluggableExporter class methodsFor: 'convenience'!
  574. ownClassesOfPackage: package
  575. "Export classes in dependency order.
  576. Update (issue #171): Remove duplicates for export"
  577. ^package sortedClasses asSet
  578. ! !
  579. !PluggableExporter class methodsFor: 'instance creation'!
  580. forRecipe: aRecipe
  581. ^self new recipe: aRecipe; yourself
  582. ! !
  583. !Package methodsFor: '*Importer-Exporter'!
  584. amdNamespace
  585. <return (self.transport && self.transport.amdNamespace) || nil>
  586. !
  587. amdNamespace: aString
  588. <
  589. if (!!self.transport) { self.transport = { type: 'amd' }; }
  590. if (self.transport.type !!== 'amd') { throw new Error('Package '+self._name()+' has transport type '+self.transport.type+', not "amd".'); }
  591. self.transport.amdNamespace = aString;
  592. >
  593. !
  594. commit
  595. ^ self handler commit: self
  596. !
  597. commitPathJs
  598. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathJs ifAbsent: [ self handler commitPathJsFor: self ]
  599. !
  600. commitPathJs: aString
  601. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathJs put: aString
  602. !
  603. commitPathSt
  604. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathSt ifAbsent: [ self handler commitPathStFor: self ]
  605. !
  606. commitPathSt: aString
  607. ^ (extension ifNil: [ extension := #{} ]) at: #commitPathSt put: aString
  608. !
  609. handler
  610. ^ PackageHandler for: self transportType
  611. !
  612. transportJson
  613. <return JSON.stringify(self.transport || null);>
  614. !
  615. transportType
  616. <return (self.transport && self.transport.type) || 'unknown';>
  617. ! !