Importer-Exporter.st 20 KB

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