AsciiDocGen.xtend 13.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*******************************************************************************
 * Copyright (c) 2011 protos software gmbh (http://www.protos.de).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * CONTRIBUTORS:
 * 		Thomas Jung, Thomas Schuetz (initial contribution)
 * 
 *******************************************************************************/

package org.eclipse.etrice.generator.doc.gen

import com.google.inject.Inject
import com.google.inject.Singleton
import java.util.List
20
import org.eclipse.emf.ecore.EObject
21
import org.eclipse.etrice.core.common.base.Documentation
22
import org.eclipse.etrice.core.fsm.fSM.StateGraph
23
24
25
26
27
28
import org.eclipse.etrice.core.genmodel.etricegen.Root
import org.eclipse.etrice.core.room.ActorClass
import org.eclipse.etrice.core.room.Attribute
import org.eclipse.etrice.core.room.DataClass
import org.eclipse.etrice.core.room.EnumerationType
import org.eclipse.etrice.core.room.LogicalSystem
29
import org.eclipse.etrice.core.room.Operation
30
31
32
33
34
35
36
import org.eclipse.etrice.core.room.Port
import org.eclipse.etrice.core.room.ProtocolClass
import org.eclipse.etrice.core.room.RoomClass
import org.eclipse.etrice.core.room.RoomModel
import org.eclipse.etrice.core.room.SubSystemClass
import org.eclipse.etrice.core.room.util.RoomHelpers
import org.eclipse.etrice.generator.base.io.IGeneratorFileIO
37
38
import org.eclipse.etrice.generator.doc.gen.URIBasedDiagramResolver.DiagramType
import org.eclipse.etrice.generator.doc.gen.URIBasedDiagramResolver.DiagramFormat
39
40
import org.eclipse.xtext.documentation.IEObjectDocumentationProvider

41
import static org.eclipse.etrice.core.common.documentation.DocumentationMarkup.*
42
43
import org.eclipse.etrice.generator.doc.Main
import org.eclipse.etrice.generator.base.AbstractGeneratorOptionsHelper
44
45

import static extension org.eclipse.xtext.EcoreUtil2.getContainerOfType
46

47
48
49
50
@Singleton
class AsciiDocGen {

	@Inject extension RoomHelpers
51
	@Inject protected extension AbstractGeneratorOptionsHelper
52
	@Inject IEObjectDocumentationProvider eObjDocuProvider
53
	@Inject extension URIBasedDiagramResolver
54
55
56
57
58
59
60
61
62
63
64
	
	def doGenerate(Root root, IGeneratorFileIO fileIO, boolean includeImages) {
		val packages = root.models.groupBy[name].entrySet.map[new RoomPackage(key, value)].sortBy[name]
		fileIO.generateFile("doc.adoc", generateSingleDoc(packages, includeImages))
	}
	
	def generateSingleDoc(Iterable<RoomPackage> packages, boolean includeImages) '''
		= Model Documentation
		:toc: left
		:toclevels: 2
		:table-caption!:
65
66
67
		tagStart("all")
		generated by eTrice
		{docdatetime}
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
		IF !packages.empty
			
			.Room Packages
			FOR pkg: packages
			* crossReference(pkg.name)
			ENDFOR
		ENDIF
		FOR pkg: packages
			
			generatePackageDoc(pkg)
			FOR en: pkg.enumerationTypes
				
				en.generateEnumerationDoc
			ENDFOR
			FOR dc: pkg.dataClasses
				
				dc.generateDataDoc
			ENDFOR
			FOR pc: pkg.protocolClasses
				
				pc.generateProtocolDoc
			ENDFOR
			FOR sys: pkg.systems
				
				sys.generateLogicalSystemDoc(includeImages)
			ENDFOR
			FOR subSys: pkg.subSystemClasses
				
				subSys.generateSubSystemDoc(includeImages)
			ENDFOR
			FOR ac: pkg.actorClasses
				
				ac.generateActorDoc(includeImages)
			ENDFOR
		ENDFOR
103
		tagEnd("all")
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
	'''
	
	def private generatePackageDoc(RoomPackage pkg) {
		'''
		defineAnchor(pkg.name)
		== pkg.name
		IF !pkg.systems.empty
			
			.Logical System Classes
			|===
			| Name | Description
			FOR s : pkg.systems
				
				| crossReference(s)
				| s.shortDocText
			ENDFOR
			|===
		ENDIF
		IF !pkg.subSystemClasses.empty
			
			.Subsystem Classes
			|===
			| Name | Description
			FOR s : pkg.subSystemClasses
				
				| crossReference(s)
				| s.shortDocText
			ENDFOR
			|===
		ENDIF
		IF !pkg.protocolClasses.empty
			
			.ProtocolClasses
			|===
			| Name | Description
			FOR c : pkg.protocolClasses
				
				| crossReference(c)
				| c.shortDocText
			ENDFOR
			|===
		ENDIF
		IF !pkg.enumerationTypes.empty
			
			.Enumeration Types
			|===
			| Name | Description
			FOR e : pkg.enumerationTypes
				
				| crossReference(e)
				| e.shortDocText
			ENDFOR
			|===
		ENDIF
		IF !pkg.dataClasses.empty
			
			.Data Classes
			|===
			| Name | Description
			FOR c : pkg.dataClasses
				
				| crossReference(c)
				| c.shortDocText
			ENDFOR
			|===
		ENDIF
		IF !pkg.actorClasses.empty
			
			.Actor Classes
			|===
			| Name | Description
			FOR c : pkg.actorClasses
				
				| crossReference(c)
				| c.shortDocText
			ENDFOR
			|===
		ENDIF
	'''
	}
	
	def private generateLogicalSystemDoc(LogicalSystem system, boolean includeImages) {
		'''
		defineAnchor(system)
		=== system.name
189
		tagStart(system)
190
191
192
193
		
		system.docText
		IF includeImages
			
194
			includeImage(system.instanceDiagramName + ".svg", true)
195
		ENDIF
196

197
		tagEnd(system)
198
199
200
201
202
203
		'''
	}
	
	def private generateSubSystemDoc(SubSystemClass ssc, boolean includeImages) '''
		defineAnchor(ssc)
		=== ssc.name
204
		tagStart(ssc)
205
206
		
		ssc.docText
207
		IF includeImages && ssc.hasDiagram(DiagramType.STRUCTURE)
208
			
209
			includeImage(ssc.getExportedDiagramFilename(DiagramType.STRUCTURE, DiagramFormat.JPG).orElse("!!unresolved_diagram_file!!"))
210
		ENDIF
211
		tagEnd(ssc)
212
213
214
215
216
	'''
	
	def private generateEnumerationDoc(EnumerationType en) '''
		defineAnchor(en)
		=== en.name
217
		tagStart(en)
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
		
		en.docText
		
		IF en.primitiveType !== null
			The literals of this enumeration are based on PrimitiveType en.primitiveType.name.
		ELSE
			The literals of this enumeration are of type int.
		ENDIF
		
		.Literals
		|===
		| Name | Value | Hex Value | Binary Value
		FOR lit: en.literals
			
			| lit.name
			| lit.literalValue
			| 0xLong.toHexString(lit.literalValue)
			| Long.toBinaryString(lit.literalValue)
		ENDFOR
		|===
238
		tagEnd(en)
239
240
241
242
243
	'''
	
	def private generateDataDoc(DataClass dc) '''
		defineAnchor(dc)
		=== dc.name
244
		tagStart(dc)
245
246
247
248
249
250
251
252
		
		dc.docText
		
		dc.attributes.generateAttributesDoc
		IF !dc.operations.empty
			
			dc.operations.generateOperationsDoc
		ENDIF
253
		tagEnd(dc)
254
255
	'''
	
256
	def private generateProtocolDoc(ProtocolClass pc) '''
257
258
		defineAnchor(pc)
		=== pc.name
259
		tagStart(pc)
260
261
262
263
264
265
266
267
268
269
270
		
		pc.docText
		IF !pc.allIncomingMessages.empty
			
			.Incoming Messages
			|===
			| Message | Type | Description
			FOR ims : pc.allIncomingMessages
				
				| ims.name
				| IF ims.data !== nullims.data.refType.type.nameELSEvoidENDIF
271
				a| ims.docText
272
273
			ENDFOR
			|===
274
			
275
276
277
278
279
280
281
282
283
284
		ENDIF
		IF !pc.allOutgoingMessages.empty
			
			.Outgoing Messages
			|===
			| Message | Type | Description
			FOR oms : pc.allOutgoingMessages
				
				| oms.name
				| IF oms.data !== nulloms.data.refType.type.nameELSEvoidENDIF
285
				a| oms.docText
286
287
			ENDFOR
			|===
288
289
290
291
			
		ENDIF
		
		IF !pc.getAllOperations(true).empty
292
			[discrete]
293
294
295
296
297
			==== Regular PortClass
			pc.getAllOperations(true).generateOperationsDoc
		ENDIF
		
		IF !pc.getAllOperations(false).empty
298
			[discrete]
299
300
			==== Conjugated PortClass
			pc.getAllOperations(false).generateOperationsDoc
301
		ENDIF
302
		tagEnd(pc)
303
304
305
306
307
	'''
	
	def private generateActorDoc(ActorClass ac, boolean includeImages) '''
		defineAnchor(ac)
		=== ac.name
308
		tagStart(ac)
309
310
311
		
		ac.docText
		
312
		[discrete]
313
314
315
316
317
318
319
320
		IF Main::settings.generateAsLibrary
			==== Interface
			IF !ac.allInterfacePorts.empty	

				generatePortInterfaceDoc(ac)
			ENDIF
		ELSE
			==== Structure
321
322
			IF includeImages && ac.hasDiagram(DiagramType.STRUCTURE)
				includeImage(ac.getExportedDiagramFilename(DiagramType.STRUCTURE, DiagramFormat.JPG).orElse("!!unresolved_diagram_file!!"))
323
324
			ENDIF
			IF !ac.allPorts.empty		
325
				
326
				generatePortDoc(ac)
327
			ENDIF
328
			IF !ac.attributes.empty
329
				
330
331
332
				ac.attributes.generateAttributesDoc
			ENDIF
			IF ac.hasNonEmptyStateMachine || !ac.operations.empty || ac.isBehaviorAnnotationPresent("BehaviorManual")
333
				
334
335
336
337
338
339
340
341
342
343
344
345
346
				[discrete]
				==== Behavior
				IF !ac.operations.empty
					
					ac.operations.generateOperationsDoc
				ENDIF
				IF ac.isBehaviorAnnotationPresent("BehaviorManual")
					
					The behavior for ActorClass ac.name is implemented manually.
				ELSEIF ac.hasNonEmptyStateMachine
					
					generateFsmDoc(ac, includeImages)
				ENDIF
347
348
			ENDIF
		ENDIF
349
		tagEnd(ac)
350
351
352
353
354
355
356
357
358
359
	'''

	def private generateFsmDoc(ActorClass ac, boolean includeImages) '''
		.State Machine
		Top Level State::
		generateStateGraphDoc(ac, ac.stateMachine, includeImages, 1)
	'''
	
	def private CharSequence generateStateGraphDoc(ActorClass ac, StateGraph stateGraph, boolean includeImages, int depth) {
		'''
360
361
			IF includeImages && stateGraph.hasDiagram(DiagramType.BEHAVIOR)
				includeImage(stateGraph.getExportedDiagramFilename(DiagramType.BEHAVIOR, DiagramFormat.JPG).orElse("!!unresolved_diagram_file!!"))
362
363
364
365
366
367
368
369
370
371
372
			ENDIF
			FOR state: stateGraph.states
				state.name::fill(':', depth)
					state.docText
					IF !state.leaf
						generateStateGraphDoc(ac, state.subgraph, includeImages, depth + 1)
					ENDIF
			ENDFOR
		'''
	}
	
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
	def private String generatePortInterfaceDoc(ActorClass ac) '''
		.Ports
		|===
		| Name | Protocol | Type | Multiplicity | Description
		FOR at : ac.allInterfacePorts
			
			| at.name
			| at.protocol.name
			| at.type
			| at.multAsText
			a| at.docText
		ENDFOR
		|===
	'''
	
388
389
390
391
392
393
394
395
396
397
398
	def private String generatePortDoc(ActorClass ac) '''
		.Ports
		|===
		| Name | Protocol | Type | Kind | Multiplicity | Description
		FOR at : ac.allPorts
			
			| at.name
			| at.protocol.name
			| at.type
			| at.kind
			| at.multAsText
399
			a| at.docText
400
401
402
403
404
405
406
407
408
409
410
411
		ENDFOR
		|===
	'''
	
	def private generateAttributesDoc(List<Attribute> attributes) '''
		.Attributes
		|===
		| Name | Type | Description
		FOR at : attributes
			
			| at.name
			| at.type.type.name
412
			a| at.docText
413
414
415
416
		ENDFOR	
		|===
	'''
	
417
	def private generateOperationsDoc(List<? extends Operation> operations) '''
418
419
420
421
422
423
424
425
		.Operations
		|===
		| Name | Return type | Arguments | Description
		FOR op : operations SEPARATOR '\n'
			
			|op.name
			| IF op.returnType !== nullop.returnType.type.nameELSEvoidENDIF
			| FOR pa : op.arguments SEPARATOR ", "pa.name: pa.refType.type.nameENDFOR
426
			a| op.docText
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
		ENDFOR
		|===
	'''
	
	def private getType(Port p) {
		if (p.conjugated) "conjugated" else "regular"
	}
	
	def private getKind(Port p) {
		if (p.internal) "internal"
		else if (p.external) "external"
		else if (p.relay) "relay"
		else "?"
	}
	
	def private String getMultAsText(Port p) {
		if(p.multiplicity == -1) "*"
		else p.multiplicity.toString
	}
	
	def private getDocText(EObject object) {
		val eClass = object.eClass;
		val feature = eClass.getEStructuralFeature("docu")
		if(feature !== null) {
			val docu = object.eGet(feature) as Documentation
			if(docu !== null) {
				return String.join('\n', docu.lines)
			}
		}
		
		val docu = object.documentation
		if(docu !== null) docu else ""
	}
	
	def private getShortDocText(EObject object) {
		val docText = object.docText
		val index = docText.indexOf('\n')
		if(index != -1) docText.subSequence(0, index) else docText
	}
	
467
468
469
470
471
	def private getInstanceDiagramName(LogicalSystem system) {
		val namespace = system.getContainerOfType(RoomModel)?.name?.concat(".") ?: ""
		namespace + system.name + "_instanceTree"
	}
	
472
	def private includeImage(String filename) {
473
474
475
476
477
478
		includeImage(filename, false)
	}

	def private includeImage(String filename, boolean limitWidth) {
		val options = limitWidth ? '''width=100%, link={imagesdir}/filename''' : ""
		'''image:filename[options]'''
479
480
481
	}
	
	def private static crossReference(RoomClass rc) {
482
		crossReference(rc.FQN)
483
484
485
486
487
488
489
	}
	
	def private static crossReference(CharSequence anchor) {
		'''<<anchor>>'''
	}
	
	def private static defineAnchor(RoomClass rc) {
490
		defineAnchor(rc.FQN)
491
492
493
494
495
496
	}
	
	def private static defineAnchor(CharSequence anchor) {
		'''[[anchor]]'''
	}
	
497
	def private static getFQN(RoomClass rc) {
498
499
500
		'''(rc.eContainer as RoomModel).name.rc.name'''
	}
	
501
	def private static tagStart(RoomClass rc) '''// tag::rc.FQN[]'''
502
	
503
	def private static tagEnd(RoomClass rc) '''// end::rc.FQN[]'''
504
505

	def private static tagStart(String name) '''// tag::.name[]'''
506
	
507
508
	def private static tagEnd(String name) '''// end::.name[]'''

509
510
511
512
513
514
515
	def private static String fill(char c, int length) {
		val builder = new StringBuilder(length)
		for(var i = 0; i < length; i++) {
			builder.append(c)
		}
		builder.toString
	}
516
517
518
519
520
521
	
	def private String documentation(EObject obj) {
		val raw = eObjDocuProvider.getDocumentation(obj)
		if(raw === null)
			return null;
		
522
523
		switch type : getMarkupType(raw) {
			case type == MARKUP_HTML && raw.contains("<"): '''
524
525
526
527
528
529
530
531
532
				++++
				<div class="paragraph"><p>trimMarkupTag(raw)</p></div>
				++++
			'''
			default: {
				trimMarkupTag(raw)
			}
		}
	}
533
534
535
536
537
538
		
	private static class RoomPackage {
		
		public final String name
		public final Iterable<LogicalSystem> systems
		public final Iterable<SubSystemClass> subSystemClasses
539
		public final Iterable<ProtocolClass> protocolClasses
540
541
542
543
544
545
546
		public final Iterable<EnumerationType> enumerationTypes
		public final Iterable<DataClass> dataClasses
		public final Iterable<ActorClass> actorClasses
		
		private new(String name, Iterable<RoomModel> models) {
			this.name = name
			
547
548
549
			val roomClasses = models.flatMap[roomClasses]
			systems = roomClasses.filter(LogicalSystem).sortBy[name]
			subSystemClasses = roomClasses.filter(SubSystemClass).sortBy[name]
550
			protocolClasses = roomClasses.filter(ProtocolClass).sortBy[name]
551
552
553
			enumerationTypes = roomClasses.filter(EnumerationType).sortBy[name]
			dataClasses = roomClasses.filter(DataClass).sortBy[name]
			actorClasses = roomClasses.filter(ActorClass).sortBy[name]
554
555
556
557
558
		}
		
	}
	
}