Importer-Exporter.st 23 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  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 subclass: #ChunkExporter
  48. instanceVariableNames: ''
  49. package: 'Importer-Exporter'!
  50. !ChunkExporter commentStamp!
  51. I am an exporter dedicated to outputting Amber source code in the classic Smalltalk chunk format.
  52. I do not output any compiled code.!
  53. !ChunkExporter methodsFor: 'accessing'!
  54. extensionCategoriesOfPackage: aPackage
  55. "Issue #143: sort protocol alphabetically"
  56. | name map result |
  57. name := aPackage name.
  58. result := OrderedCollection new.
  59. (Package sortedClasses: Smalltalk current classes) do: [ :each |
  60. {each. each class} do: [ :aClass |
  61. map := Dictionary new.
  62. aClass protocolsDo: [ :category :methods |
  63. category = ('*', name) ifTrue: [ map at: category put: methods ] ].
  64. result addAll: ((map keys sorted: [:a :b | a <= b ]) collect: [ :category |
  65. MethodCategory name: category theClass: aClass methods: (map at: category) ]) ] ].
  66. ^result
  67. !
  68. methodsOfCategory: aCategory
  69. "Issue #143: sort methods alphabetically"
  70. ^(aCategory methods) sorted: [ :a :b | a selector <= b selector ]
  71. !
  72. ownCategoriesOfClass: aClass
  73. "Answer the protocols of aClass that are not package extensions"
  74. "Issue #143: sort protocol alphabetically"
  75. | map |
  76. map := Dictionary new.
  77. aClass protocolsDo: [ :each :methods |
  78. (each match: '^\*') ifFalse: [ map at: each put: methods ] ].
  79. ^(map keys sorted: [:a :b | a <= b ]) collect: [ :each |
  80. MethodCategory name: each theClass: aClass methods: (map at: each) ]
  81. !
  82. ownCategoriesOfMetaClass: aClass
  83. "Issue #143: sort protocol alphabetically"
  84. ^self ownCategoriesOfClass: aClass class
  85. !
  86. ownMethodProtocolsOfClass: aClass
  87. "Answer a collection of ExportMethodProtocol object of aClass that are not package extensions"
  88. ^ aClass ownProtocols collect: [ :each |
  89. ExportMethodProtocol name: each theClass: aClass ]
  90. ! !
  91. !ChunkExporter methodsFor: 'fileOut'!
  92. recipe
  93. "Export a given package."
  94. | exportCategoryRecipe |
  95. exportCategoryRecipe := {
  96. self -> #exportCategoryPrologueOf:on:.
  97. {
  98. self -> #methodsOfCategory:.
  99. self -> #exportMethod:on: }.
  100. self -> #exportCategoryEpilogueOf:on: }.
  101. ^{
  102. self -> #exportPackageDefinitionOf:on:.
  103. {
  104. PluggableExporter -> #ownClassesOfPackage:.
  105. self -> #exportDefinitionOf:on:.
  106. { self -> #ownCategoriesOfClass: }, exportCategoryRecipe.
  107. self -> #exportMetaDefinitionOf:on:.
  108. { self -> #ownCategoriesOfMetaClass: }, exportCategoryRecipe }.
  109. { self -> #extensionCategoriesOfPackage: }, exportCategoryRecipe
  110. }
  111. ! !
  112. !ChunkExporter methodsFor: 'output'!
  113. exportCategoryEpilogueOf: aCategory on: aStream
  114. aStream nextPutAll: ' !!'; lf; lf
  115. !
  116. exportCategoryPrologueOf: aCategory on: aStream
  117. aStream
  118. nextPutAll: '!!', (self classNameFor: aCategory theClass);
  119. nextPutAll: ' methodsFor: ''', aCategory name, '''!!'
  120. !
  121. exportDefinitionOf: aClass on: aStream
  122. "Chunk format."
  123. aStream
  124. nextPutAll: (self classNameFor: aClass superclass);
  125. nextPutAll: ' subclass: #', (self classNameFor: aClass); lf;
  126. tab; nextPutAll: 'instanceVariableNames: '''.
  127. aClass instanceVariableNames
  128. do: [:each | aStream nextPutAll: each]
  129. separatedBy: [aStream nextPutAll: ' '].
  130. aStream
  131. nextPutAll: ''''; lf;
  132. tab; nextPutAll: 'package: ''', aClass category, '''!!'; lf.
  133. aClass comment notEmpty ifTrue: [
  134. aStream
  135. nextPutAll: '!!', (self classNameFor: aClass), ' commentStamp!!';lf;
  136. nextPutAll: (self chunkEscape: aClass comment), '!!';lf].
  137. aStream lf
  138. !
  139. exportMetaDefinitionOf: aClass on: aStream
  140. aClass class instanceVariableNames isEmpty ifFalse: [
  141. aStream
  142. nextPutAll: (self classNameFor: aClass class);
  143. nextPutAll: ' instanceVariableNames: '''.
  144. aClass class instanceVariableNames
  145. do: [:each | aStream nextPutAll: each]
  146. separatedBy: [aStream nextPutAll: ' '].
  147. aStream
  148. nextPutAll: '''!!'; lf; lf]
  149. !
  150. exportMethod: aMethod on: aStream
  151. aStream
  152. lf; lf; nextPutAll: (self chunkEscape: aMethod source); lf;
  153. nextPutAll: '!!'
  154. !
  155. exportPackage: aPackage on: aStream
  156. self exportPackageDefinitionOf: aPackage on: aStream.
  157. aPackage sortedClasses do: [ :each |
  158. self exportDefinitionOf: each on: aStream.
  159. self
  160. exportProtocols: (self ownMethodProtocolsOfClass: each)
  161. on: aStream.
  162. self exportMetaDefinitionOf: each on: aStream.
  163. self
  164. exportProtocols: (self ownMethodProtocolsOfClass: each class)
  165. on: aStream ].
  166. self
  167. exportProtocols: (self extensionProtocolsOfPackage: aPackage)
  168. on: aStream
  169. !
  170. exportPackageDefinitionOf: aPackage on: aStream
  171. aStream
  172. nextPutAll: 'Smalltalk current createPackage: ''', aPackage name, '''!!';
  173. lf
  174. !
  175. exportProtocol: aProtocol on: aStream
  176. self exportProtocolPrologueOf: aProtocol on: aStream.
  177. aProtocol methods do: [ :method |
  178. self exportMethod: method on: aStream ].
  179. self exportProtocolEpilogueOf: aProtocol on: aStream
  180. !
  181. exportProtocolEpilogueOf: aProtocol on: aStream
  182. aStream nextPutAll: ' !!'; lf; lf
  183. !
  184. exportProtocolPrologueOf: aProtocol on: aStream
  185. aStream
  186. nextPutAll: '!!', (self classNameFor: aProtocol theClass);
  187. nextPutAll: ' methodsFor: ''', aProtocol name, '''!!'
  188. !
  189. exportProtocols: aCollection on: aStream
  190. aCollection do: [ :each |
  191. self exportProtocol: each on: aStream ]
  192. ! !
  193. AbstractExporter subclass: #Exporter
  194. instanceVariableNames: ''
  195. package: 'Importer-Exporter'!
  196. !Exporter commentStamp!
  197. I am responsible for outputting Amber code into a JavaScript string.
  198. The generated output is enough to reconstruct the exported data, including Smalltalk source code and other metadata.
  199. ## Use case
  200. I am typically used to save code outside of the Amber runtime (committing to disk, etc.).!
  201. !Exporter methodsFor: 'accessing'!
  202. ownMethodsOfClass: aClass
  203. "Issue #143: sort methods alphabetically"
  204. ^((aClass methodDictionary values) sorted: [:a :b | a selector <= b selector])
  205. reject: [:each | (each category match: '^\*')]
  206. !
  207. ownMethodsOfMetaClass: aClass
  208. "Issue #143: sort methods alphabetically"
  209. ^self ownMethodsOfClass: aClass class
  210. ! !
  211. !Exporter methodsFor: 'convenience'!
  212. classNameFor: aClass
  213. ^aClass isMetaclass
  214. ifTrue: [ aClass instanceClass name, '.klass' ]
  215. ifFalse: [
  216. aClass isNil
  217. ifTrue: [ 'nil' ]
  218. ifFalse: [ aClass name ] ]
  219. ! !
  220. !Exporter methodsFor: 'fileOut'!
  221. amdRecipe
  222. "Export a given package with amd transport type."
  223. | result |
  224. result := self recipe.
  225. result first key: AmdExporter.
  226. result last key: AmdExporter.
  227. ^result
  228. !
  229. recipe
  230. "Export a given package."
  231. ^{
  232. self -> #exportPackagePrologueOf:on:.
  233. self -> #exportPackageDefinitionOf:on:.
  234. self -> #exportPackageTransportOf:on:.
  235. {
  236. PluggableExporter -> #ownClassesOfPackage:.
  237. self -> #exportDefinitionOf:on:.
  238. {
  239. self -> #ownMethodsOfClass:.
  240. self -> #exportMethod:on: }.
  241. self -> #exportMetaDefinitionOf:on:.
  242. {
  243. self -> #ownMethodsOfMetaClass:.
  244. self -> #exportMethod:on: } }.
  245. {
  246. self -> #extensionMethodsOfPackage:.
  247. self -> #exportMethod:on: }.
  248. self -> #exportPackageEpilogueOf:on:
  249. }
  250. ! !
  251. !Exporter methodsFor: 'output'!
  252. exportDefinitionOf: aClass on: aStream
  253. aStream
  254. lf;
  255. nextPutAll: 'smalltalk.addClass(';
  256. nextPutAll: '''', (self classNameFor: aClass), ''', ';
  257. nextPutAll: 'smalltalk.', (self classNameFor: aClass superclass);
  258. nextPutAll: ', ['.
  259. aClass instanceVariableNames
  260. do: [:each | aStream nextPutAll: '''', each, '''']
  261. separatedBy: [aStream nextPutAll: ', '].
  262. aStream
  263. nextPutAll: '], ''';
  264. nextPutAll: aClass category, '''';
  265. nextPutAll: ');'.
  266. aClass comment notEmpty ifTrue: [
  267. aStream
  268. lf;
  269. nextPutAll: 'smalltalk.';
  270. nextPutAll: (self classNameFor: aClass);
  271. nextPutAll: '.comment=';
  272. nextPutAll: aClass comment asJavascript;
  273. nextPutAll: ';'].
  274. aStream lf
  275. !
  276. exportMetaDefinitionOf: aClass on: aStream
  277. aStream lf.
  278. aClass class instanceVariableNames isEmpty ifFalse: [
  279. aStream
  280. nextPutAll: 'smalltalk.', (self classNameFor: aClass class);
  281. nextPutAll: '.iVarNames = ['.
  282. aClass class instanceVariableNames
  283. do: [:each | aStream nextPutAll: '''', each, '''']
  284. separatedBy: [aStream nextPutAll: ','].
  285. aStream nextPutAll: '];', String lf]
  286. !
  287. exportMethod: aMethod on: aStream
  288. aStream
  289. nextPutAll: 'smalltalk.addMethod(';lf;
  290. "nextPutAll: aMethod selector asSelector asJavascript, ',';lf;"
  291. nextPutAll: 'smalltalk.method({';lf;
  292. nextPutAll: 'selector: ', aMethod selector asJavascript, ',';lf;
  293. nextPutAll: 'category: ''', aMethod category, ''',';lf;
  294. nextPutAll: 'fn: ', aMethod fn compiledSource, ',';lf;
  295. nextPutAll: 'args: ', aMethod arguments asJavascript, ','; lf;
  296. nextPutAll: 'source: ', aMethod source asJavascript, ',';lf;
  297. nextPutAll: 'messageSends: ', aMethod messageSends asJavascript, ',';lf;
  298. nextPutAll: 'referencedClasses: ', aMethod referencedClasses asJavascript.
  299. aStream
  300. lf;
  301. nextPutAll: '}),';lf;
  302. nextPutAll: 'smalltalk.', (self classNameFor: aMethod methodClass);
  303. nextPutAll: ');';lf;lf
  304. !
  305. exportPackage: aPackage on: aStream
  306. self
  307. exportPackagePrologueOf: aPackage on: aStream;
  308. exportPackageDefinitionOf: aPackage on: aStream;
  309. exportPackageTransportOf: aPackage on: aStream.
  310. aPackage sortedClasses do: [ :each |
  311. self exportDefinitionOf: each on: aStream.
  312. each ownMethods do: [ :method |
  313. self exportMethod: method on: aStream ].
  314. self exportMetaDefinitionOf: each on: aStream.
  315. each class ownMethods do: [ :method |
  316. self exportMethod: method on: aStream ] ].
  317. (self extensionMethodsOfPackage: aPackage) do: [ :each |
  318. self exportMethod: each on: aStream ].
  319. self exportPackageEpilogueOf: aPackage on: aStream
  320. !
  321. exportPackageDefinitionOf: aPackage on: aStream
  322. aStream
  323. nextPutAll: 'smalltalk.addPackage(';
  324. nextPutAll: '''', aPackage name, ''');';
  325. lf
  326. !
  327. exportPackageEpilogueOf: aPackage on: aStream
  328. aStream
  329. nextPutAll: '})(global_smalltalk,global_nil,global__st);';
  330. lf
  331. !
  332. exportPackagePrologueOf: aPackage on: aStream
  333. aStream
  334. nextPutAll: '(function(smalltalk,nil,_st){';
  335. lf
  336. !
  337. exportPackageTransportOf: aPackage on: aStream
  338. | json |
  339. json := aPackage transportJson.
  340. json = 'null' ifFalse: [
  341. aStream
  342. nextPutAll: 'smalltalk.packages[';
  343. nextPutAll: aPackage name asJavascript;
  344. nextPutAll: '].transport = ';
  345. nextPutAll: json;
  346. nextPutAll: ';';
  347. lf ]
  348. ! !
  349. Exporter subclass: #AmdExporter
  350. instanceVariableNames: 'namespace'
  351. package: 'Importer-Exporter'!
  352. !AmdExporter commentStamp!
  353. I am used to export Packages in an AMD (Asynchronous Module Definition) JavaScript format.!
  354. !AmdExporter methodsFor: 'output'!
  355. exportPackageEpilogueOf: aPackage on: aStream
  356. aStream
  357. nextPutAll: '});';
  358. lf
  359. !
  360. exportPackagePrologueOf: aPackage on: aStream
  361. aStream
  362. nextPutAll: 'define("';
  363. nextPutAll: (self amdNamespaceOfPackage: aPackage);
  364. nextPutAll: '/';
  365. nextPutAll: aPackage name;
  366. nextPutAll: '", ';
  367. nextPutAll: (#('amber_vm/smalltalk' 'amber_vm/nil' 'amber_vm/_st'), (self amdNamesOfPackages: aPackage loadDependencies)) asJavascript;
  368. nextPutAll: ', function(smalltalk,nil,_st){';
  369. lf
  370. ! !
  371. !AmdExporter methodsFor: 'private'!
  372. amdNamesOfPackages: anArray
  373. ^ (anArray
  374. select: [ :each | (self amdNamespaceOfPackage: each) notNil ])
  375. collect: [ :each | (self amdNamespaceOfPackage: each), '/', each name ]
  376. !
  377. amdNamespaceOfPackage: aPackage
  378. ^ (aPackage transport type = 'amd')
  379. ifTrue: [ aPackage transport namespace ]
  380. ifFalse: [ nil ]
  381. ! !
  382. Object subclass: #ChunkParser
  383. instanceVariableNames: 'stream'
  384. package: 'Importer-Exporter'!
  385. !ChunkParser commentStamp!
  386. I am responsible for parsing aStream contents in the chunk format.
  387. ## API
  388. ChunkParser new
  389. stream: aStream;
  390. nextChunk!
  391. !ChunkParser methodsFor: 'accessing'!
  392. stream: aStream
  393. stream := aStream
  394. ! !
  395. !ChunkParser methodsFor: 'reading'!
  396. nextChunk
  397. "The chunk format (Smalltalk Interchange Format or Fileout format)
  398. is a trivial format but can be a bit tricky to understand:
  399. - Uses the exclamation mark as delimiter of chunks.
  400. - Inside a chunk a normal exclamation mark must be doubled.
  401. - A non empty chunk must be a valid Smalltalk expression.
  402. - A chunk on top level with a preceding empty chunk is an instruction chunk:
  403. - The object created by the expression then takes over reading chunks.
  404. This method returns next chunk as a String (trimmed), empty String (all whitespace) or nil."
  405. | char result chunk |
  406. result := '' writeStream.
  407. [char := stream next.
  408. char notNil] whileTrue: [
  409. char = '!!' ifTrue: [
  410. stream peek = '!!'
  411. ifTrue: [stream next "skipping the escape double"]
  412. ifFalse: [^result contents trimBoth "chunk end marker found"]].
  413. result nextPut: char].
  414. ^nil "a chunk needs to end with !!"
  415. ! !
  416. !ChunkParser class methodsFor: 'instance creation'!
  417. on: aStream
  418. ^self new stream: aStream
  419. ! !
  420. Object subclass: #ExportMethodProtocol
  421. instanceVariableNames: 'name theClass'
  422. package: 'Importer-Exporter'!
  423. !ExportMethodProtocol commentStamp!
  424. I am an abstraction for a method protocol in a class / metaclass.
  425. I know of my class, name and methods.
  426. I am used when exporting a package.!
  427. !ExportMethodProtocol methodsFor: 'accessing'!
  428. methods
  429. ^ self theClass methodsInProtocol: self name
  430. !
  431. name
  432. ^name
  433. !
  434. name: aString
  435. name := aString
  436. !
  437. sortedMethods
  438. ^ self methods sorted: [ :a :b | a selector <= b selector ]
  439. !
  440. theClass
  441. ^theClass
  442. !
  443. theClass: aClass
  444. theClass := aClass
  445. ! !
  446. !ExportMethodProtocol class methodsFor: 'instance creation'!
  447. name: aString theClass: aClass
  448. ^self new
  449. name: aString;
  450. theClass: aClass;
  451. yourself
  452. ! !
  453. Object subclass: #Importer
  454. instanceVariableNames: ''
  455. package: 'Importer-Exporter'!
  456. !Importer commentStamp!
  457. I can import Amber code from a string in the chunk format.
  458. ## API
  459. Importer new import: aString!
  460. !Importer methodsFor: 'fileIn'!
  461. import: aStream
  462. | chunk result parser lastEmpty |
  463. parser := ChunkParser on: aStream.
  464. lastEmpty := false.
  465. [chunk := parser nextChunk.
  466. chunk isNil] whileFalse: [
  467. chunk isEmpty
  468. ifTrue: [lastEmpty := true]
  469. ifFalse: [
  470. result := Compiler new evaluateExpression: chunk.
  471. lastEmpty
  472. ifTrue: [
  473. lastEmpty := false.
  474. result scanFrom: parser]]]
  475. ! !
  476. Object subclass: #MethodCategory
  477. instanceVariableNames: 'methods name theClass'
  478. package: 'Importer-Exporter'!
  479. !MethodCategory commentStamp!
  480. I am an abstraction for a method category in a class / metaclass.
  481. I know of my class, name and methods.
  482. I am used when exporting a package.!
  483. !MethodCategory methodsFor: 'accessing'!
  484. methods
  485. ^methods
  486. !
  487. methods: aCollection
  488. methods := aCollection
  489. !
  490. name
  491. ^name
  492. !
  493. name: aString
  494. name := aString
  495. !
  496. theClass
  497. ^theClass
  498. !
  499. theClass: aClass
  500. theClass := aClass
  501. ! !
  502. !MethodCategory class methodsFor: 'not yet classified'!
  503. name: aString theClass: aClass methods: anArray
  504. ^self new
  505. name: aString;
  506. theClass: aClass;
  507. methods: anArray;
  508. yourself
  509. ! !
  510. InterfacingObject subclass: #PackageHandler
  511. instanceVariableNames: ''
  512. package: 'Importer-Exporter'!
  513. !PackageHandler commentStamp!
  514. I am responsible for handling package loading and committing.
  515. I should not be used directly. Instead, use the corresponding `Package` methods.!
  516. !PackageHandler methodsFor: 'accessing'!
  517. chunkContentsFor: aPackage
  518. ^ String streamContents: [ :str |
  519. self chunkExporter exportPackage: aPackage on: str ]
  520. !
  521. chunkExporterClass
  522. ^ ChunkExporter
  523. !
  524. commitPathJsFor: aPackage
  525. self subclassResponsibility
  526. !
  527. commitPathStFor: aPackage
  528. self subclassResponsibility
  529. !
  530. contentsFor: aPackage
  531. ^ String streamContents: [ :str |
  532. self exporter exportPackage: aPackage on: str ]
  533. !
  534. exporterClass
  535. ^ Exporter
  536. ! !
  537. !PackageHandler methodsFor: 'committing'!
  538. commit: aPackage
  539. {
  540. [ self commitStFileFor: aPackage ].
  541. [ self commitJsFileFor: aPackage ]
  542. }
  543. do: [ :each | each value ]
  544. displayingProgress: 'Committing package ', aPackage name
  545. !
  546. commitJsFileFor: aPackage
  547. self
  548. ajaxPutAt: (self commitPathJsFor: aPackage), '/', aPackage name, '.js'
  549. data: (self contentsFor: aPackage)
  550. !
  551. commitStFileFor: aPackage
  552. self
  553. ajaxPutAt: (self commitPathStFor: aPackage), '/', aPackage name, '.st'
  554. data: (self chunkContentsFor: aPackage)
  555. !
  556. oldCommit: aPackage
  557. self commitChannels
  558. do: [ :commitStrategyFactory || fileContents commitStrategy |
  559. commitStrategy := commitStrategyFactory value: aPackage.
  560. fileContents := String streamContents: [ :stream |
  561. (PluggableExporter forRecipe: commitStrategy key) exportPackage: aPackage on: stream ].
  562. self ajaxPutAt: commitStrategy value data: fileContents ]
  563. displayingProgress: 'Committing package ', aPackage name
  564. ! !
  565. !PackageHandler methodsFor: 'factory'!
  566. chunkExporter
  567. ^ self chunkExporterClass new
  568. !
  569. exporter
  570. ^ self exporterClass new
  571. ! !
  572. !PackageHandler methodsFor: 'private'!
  573. ajaxPutAt: aURL data: aString
  574. self
  575. ajax: #{
  576. 'url' -> aURL.
  577. 'type' -> 'PUT'.
  578. 'data' -> aString.
  579. 'contentType' -> 'text/plain;charset=UTF-8'.
  580. 'error' -> [ :xhr | self error: 'Commiting ' , aURL , ' failed with reason: "' , (xhr responseText) , '"'] }
  581. ! !
  582. PackageHandler subclass: #AmdPackageHandler
  583. instanceVariableNames: ''
  584. package: 'Importer-Exporter'!
  585. !AmdPackageHandler commentStamp!
  586. I am responsible for handling package loading and committing.
  587. I should not be used directly. Instead, use the corresponding `Package` methods.!
  588. !AmdPackageHandler methodsFor: 'accessing'!
  589. commitPathJsFor: aPackage
  590. ^self toUrl: (self namespaceFor: aPackage)
  591. !
  592. commitPathStFor: aPackage
  593. "if _source is not mapped, .st commit will likely fail"
  594. ^self toUrl: (self namespaceFor: aPackage), '/_source'.
  595. !
  596. exporterClass
  597. ^ AmdExporter
  598. ! !
  599. !AmdPackageHandler methodsFor: 'committing'!
  600. namespaceFor: aPackage
  601. ^ aPackage transport namespace
  602. ! !
  603. !AmdPackageHandler methodsFor: 'private'!
  604. toUrl: aString
  605. ^ Smalltalk current amdRequire
  606. ifNil: [ self error: 'AMD loader not present' ]
  607. ifNotNil: [ :require | (require basicAt: 'toUrl') value: aString ]
  608. ! !
  609. !AmdPackageHandler class methodsFor: 'commit paths'!
  610. defaultNamespace
  611. ^ Smalltalk current defaultAmdNamespace
  612. !
  613. defaultNamespace: aString
  614. Smalltalk current defaultAMDNamespace: aString
  615. !
  616. resetCommitPaths
  617. defaultNamespace := nil
  618. ! !
  619. !AmdPackageHandler class methodsFor: 'initialization'!
  620. initialize
  621. super initialize.
  622. self registerFor: AMDPackageTransport type
  623. ! !
  624. Object subclass: #PackageTransport
  625. instanceVariableNames: 'package'
  626. package: 'Importer-Exporter'!
  627. !PackageTransport commentStamp!
  628. I represent the transport mechanism used to commit a package.
  629. My concrete subclasses have a `#handler` to which committing is delegated.!
  630. !PackageTransport methodsFor: 'accessing'!
  631. commitHandlerClass
  632. self subclassResponsibility
  633. !
  634. package
  635. ^ package
  636. !
  637. package: aPackage
  638. package := aPackage
  639. !
  640. type
  641. ^ self class type
  642. ! !
  643. !PackageTransport methodsFor: 'committing'!
  644. commit
  645. self commitHandler commit: self package
  646. ! !
  647. !PackageTransport methodsFor: 'factory'!
  648. commitHandler
  649. ^ self commitHandlerClass new
  650. ! !
  651. !PackageTransport methodsFor: 'initialization'!
  652. setupFromJson: anObject
  653. "no op. override if needed in subclasses"
  654. ! !
  655. PackageTransport class instanceVariableNames: 'registry'!
  656. !PackageTransport class methodsFor: 'accessing'!
  657. classRegisteredFor: aString
  658. ^ registry at: aString
  659. !
  660. for: aString
  661. ^ (self classRegisteredFor: aString) new
  662. !
  663. type
  664. "Override in subclasses"
  665. ^ nil
  666. ! !
  667. !PackageTransport class methodsFor: 'initialization'!
  668. initialize
  669. super initialize.
  670. registry := #{}.
  671. self register
  672. ! !
  673. !PackageTransport class methodsFor: 'registration'!
  674. register
  675. PackageTransport register: self
  676. !
  677. register: aClass
  678. aClass type ifNotNil: [
  679. registry at: aClass type put: aClass ]
  680. ! !
  681. PackageTransport subclass: #AmdPackageTransport
  682. instanceVariableNames: 'namespace'
  683. package: 'Importer-Exporter'!
  684. !AmdPackageTransport commentStamp!
  685. I am the default transport for committing packages.
  686. See `AmdExporter` and `AmdPackageHandler`.!
  687. !AmdPackageTransport methodsFor: 'accessing'!
  688. commitHandlerClass
  689. ^ AmdPackageHandler
  690. !
  691. namespace
  692. ^ namespace ifNil: [ self defaultNamespace ]
  693. !
  694. namespace: aString
  695. namespace := aString
  696. ! !
  697. !AmdPackageTransport methodsFor: 'defaults'!
  698. defaultNamespace
  699. ^ Smalltalk current defaultAmdNamespace
  700. ! !
  701. !AmdPackageTransport methodsFor: 'initialization'!
  702. setupFromJson: anObject
  703. self namespace: (anObject at: 'amdNamespace')
  704. ! !
  705. !AmdPackageTransport class methodsFor: 'accessing'!
  706. type
  707. ^ 'amd'
  708. ! !
  709. !AmdPackageTransport class methodsFor: 'instance creation'!
  710. namespace: aString
  711. ^ self new
  712. namespace: aString;
  713. yourself
  714. ! !
  715. Object subclass: #PluggableExporter
  716. instanceVariableNames: 'recipe'
  717. package: 'Importer-Exporter'!
  718. !PluggableExporter commentStamp!
  719. I am an engine for exporting structured data on a Stream.
  720. My instances are created using
  721. PluggableExporter forRecipe: aRecipe,
  722. where recipe is structured description of the exporting algorithm (see `ExportRecipeInterpreter`).
  723. The actual exporting is done by interpreting the recipe using a `RecipeInterpreter`.
  724. I am used to export amber packages, so I have a convenience method
  725. `exportPackage: aPackage on: aStream`
  726. which exports `aPackage` using the `recipe`
  727. (it is otherwise no special, so it may be renamed to export:on:)!
  728. !PluggableExporter methodsFor: 'accessing'!
  729. interpreter
  730. ^ ExportRecipeInterpreter new
  731. !
  732. recipe
  733. ^recipe
  734. !
  735. recipe: anArray
  736. recipe := anArray
  737. ! !
  738. !PluggableExporter methodsFor: 'fileOut'!
  739. exportAllPackages
  740. "Export all packages in the system."
  741. ^String streamContents: [:stream |
  742. Smalltalk current packages do: [:pkg |
  743. self exportPackage: pkg on: stream]]
  744. !
  745. exportPackage: aPackage on: aStream
  746. self interpreter interpret: self recipe for: aPackage on: aStream
  747. ! !
  748. !PluggableExporter class methodsFor: 'convenience'!
  749. ownClassesOfPackage: package
  750. "Export classes in dependency order.
  751. Update (issue #171): Remove duplicates for export"
  752. ^package sortedClasses asSet
  753. ! !
  754. !PluggableExporter class methodsFor: 'instance creation'!
  755. forRecipe: aRecipe
  756. ^self new recipe: aRecipe; yourself
  757. ! !
  758. !Package methodsFor: '*Importer-Exporter'!
  759. commit
  760. ^ self transport commit
  761. !
  762. transportJson
  763. <return JSON.stringify(self.transport || null);>
  764. !
  765. transportType
  766. <return (self.transport && self.transport.type) || 'unknown';>
  767. !
  768. transport
  769. ^ (PackageTransport for: self transportType)
  770. setupFromJson: self basicTransport;
  771. package: self;
  772. yourself
  773. !
  774. basicTransport
  775. <return self.transport>
  776. ! !