@@ -2,77 +2,225 @@ package org.sdpi
22
33import kotlinx.serialization.encodeToString
44import kotlinx.serialization.json.Json
5+ import org.apache.logging.log4j.kotlin.logger
56import org.asciidoctor.Asciidoctor
67import org.asciidoctor.Options
78import org.asciidoctor.SafeMode
89import org.sdpi.asciidoc.extension.*
9- import org.sdpi.asciidoc.github.Issues
1010import java.io.File
1111import java.io.OutputStream
1212import java.nio.file.Files
1313import java.nio.file.Path
14+ import kotlin.io.path.absolutePathString
15+
16+ /* *
17+ * Options to configure AsciidocConverter.
18+ */
19+ class ConverterOptions (
20+ /* *
21+ * Token to access the GitHub api. For including issues
22+ * in the document output, for example.
23+ */
24+ val githubToken : String? = null ,
25+
26+ /* *
27+ * Defines the target format for the document output. See
28+ * https://docs.asciidoctor.org/asciidoctorj/latest/asciidoctor-api-options/#backend
29+ * Typically "html" or "pdf"
30+ */
31+ val outputFormat : String = " html" ,
32+
33+ /* *
34+ * When true, document structure is written to the log stream
35+ * for diagnostics.
36+ */
37+ val dumpStructure : Boolean = false ,
38+
39+ /* *
40+ * When true, the document output is simplified for unit
41+ * tests. For example, the style sheet is not included.
42+ */
43+ val generateTestOutput : Boolean = false ,
44+
45+ /* *
46+ * Folder where extracts (requirements, use-cases, etc.) should
47+ * be placed. If null, the extracts won't be written.
48+ */
49+ val extractsFolder : Path ? = null ,
50+ ) {
51+ companion object {
52+ private const val DEFAULT_EXTRACTS_FOLDER : String = " referenced-artifacts"
53+
54+ fun makeDefaultPath (strOutputFolder : String ): Path {
55+ return Path .of(strOutputFolder, DEFAULT_EXTRACTS_FOLDER )
56+ }
57+ }
58+ }
59+
1460
1561class AsciidocConverter (
1662 private val inputType : Input ,
17- private val outputFile : File ,
18- private val githubToken : String? ,
19- private val mode : Mode = Mode .Productive ,
63+ private val outputFile : OutputStream ,
64+ private val conversionOptions : ConverterOptions ,
2065) : Runnable {
66+
67+ val anchorCollector = DocumentAnchorCollector ()
68+
69+ fun documentAnchors () = anchorCollector.getKnownAnchors()
70+
71+ val bibliographyCollector = BibliographyCollector ()
72+ val transactionActorsProcessor = TransactionActorsProcessor ()
73+ val profileTransactionCollector = TransactionIncludeProcessor ()
74+ val profileUseCaseCollector = UseCaseIncludeProcessor ()
75+ val profileContentModuleCollector = ContentModuleIncludeProcessor ()
76+
77+ val infoCollector = SdpiInformationCollector (
78+ bibliographyCollector,
79+ transactionActorsProcessor,
80+ profileTransactionCollector,
81+ profileUseCaseCollector,
82+ profileContentModuleCollector
83+ )
84+
2185 override fun run () {
2286 val options = Options .builder()
2387 .safe(SafeMode .UNSAFE )
24- .backend(BACKEND )
88+ .backend(conversionOptions.outputFormat )
2589 .sourcemap(true )
26- .toFile(outputFile).build()
90+ .headerFooter(! conversionOptions.generateTestOutput)
91+ .toStream(outputFile).build()
92+ val bEnablePrePostProcessing = true
2793
2894 val asciidoctor = Asciidoctor .Factory .create()
2995
3096 val anchorReplacements = AnchorReplacementsMap ()
3197
32- val requirementsBlockProcessor = RequirementsBlockProcessor ()
33- asciidoctor.javaExtensionRegistry().block(requirementsBlockProcessor)
34- asciidoctor.javaExtensionRegistry().treeprocessor(
35- NumberingProcessor (
36- when (mode) {
37- is Mode .Test -> mode.structureDump
38- else -> null
39- },
40- anchorReplacements
41- )
42- )
43- asciidoctor.javaExtensionRegistry().treeprocessor(RequirementLevelProcessor ())
44- asciidoctor.javaExtensionRegistry().preprocessor(IssuesSectionPreprocessor (githubToken))
45- asciidoctor.javaExtensionRegistry().preprocessor(DisableSectNumsProcessor ())
46- asciidoctor.javaExtensionRegistry().preprocessor(ReferenceSanitizerPreprocessor (anchorReplacements))
47- asciidoctor.javaExtensionRegistry()
48- .postprocessor(ReferenceSanitizerPostprocessor (anchorReplacements))
98+ // Formats sdpi_requirement blocks & their content.
99+ // * RequirementBlockProcessor2 handles the containing sdpi_requirement block
100+ // * RelatedBlockProcessor handles [RELATED] blocks within requirement blocks.
101+ // * RequirementExampleBlockProcessor handles [EXAMPLE] blocks within requirement blocks.
102+ // * TransactionActorsProcessor handles definitions of actor contributions within
103+ // transaction sections.
104+ asciidoctor.javaExtensionRegistry().block(RequirementBlockProcessor2 ())
105+ asciidoctor.javaExtensionRegistry().block(RelatedBlockProcessor ())
106+ asciidoctor.javaExtensionRegistry().block(RequirementExampleBlockProcessor ())
107+ asciidoctor.javaExtensionRegistry().block(transactionActorsProcessor)
108+
109+ asciidoctor.javaExtensionRegistry().treeprocessor(NumberingProcessor (null , anchorReplacements))
110+
111+ // Gather bibliography entries.
112+
113+ asciidoctor.javaExtensionRegistry().treeprocessor(bibliographyCollector)
114+
115+ // Gather profiles, the transactions, use cases they include.
116+ asciidoctor.javaExtensionRegistry().blockMacro(profileTransactionCollector)
117+
118+ asciidoctor.javaExtensionRegistry().blockMacro(profileUseCaseCollector)
119+
120+ asciidoctor.javaExtensionRegistry().blockMacro(profileContentModuleCollector)
121+
122+ // Gather SDPI specific information from the document such as
123+ // requirements and use-cases.
124+
125+ asciidoctor.javaExtensionRegistry().treeprocessor(infoCollector)
126+
127+ // Gather anchors in the document so we can verify there aren't any invalid links.
128+ asciidoctor.javaExtensionRegistry().treeprocessor(anchorCollector)
129+
130+ // Support to insert tables of requirements etc. sdpi_requirement_table macros.
131+ // Block macro processors insert placeholders that are populated when the tree is ready.
132+ // Tree processors fill in the placeholders.
133+ asciidoctor.javaExtensionRegistry().blockMacro(AddRequirementQueryPlaceholder ())
134+ asciidoctor.javaExtensionRegistry().blockMacro(AddICSPlaceholder ())
135+ asciidoctor.javaExtensionRegistry().blockMacro(AddTransactionQueryPlaceholder ())
136+ asciidoctor.javaExtensionRegistry().blockMacro(AddContentModuleQueryPlaceholder ())
137+
138+ asciidoctor.javaExtensionRegistry().treeprocessor(PopulateTables (infoCollector))
139+
140+ // Handle inline macros to cross-reference information from the document tree.
141+ asciidoctor.javaExtensionRegistry().inlineMacro(RequirementReferenceMacroProcessor (infoCollector))
142+ asciidoctor.javaExtensionRegistry().inlineMacro(UseCaseReferenceMacroProcessor (infoCollector))
143+ asciidoctor.javaExtensionRegistry().inlineMacro(ActorReferenceMacroProcessor (infoCollector))
144+ asciidoctor.javaExtensionRegistry().inlineMacro(ContentModuleReferenceMacroProcessor (infoCollector))
145+ asciidoctor.javaExtensionRegistry().inlineMacro(TransactionReferenceMacroProcessor (infoCollector))
146+ asciidoctor.javaExtensionRegistry().inlineMacro(ProfileReferenceMacroProcessor (infoCollector))
147+
148+ if (bEnablePrePostProcessing) {
149+ asciidoctor.javaExtensionRegistry().preprocessor(IssuesSectionPreprocessor (conversionOptions.githubToken))
150+ asciidoctor.javaExtensionRegistry().preprocessor(DisableSectNumsProcessor ())
151+ }
152+
153+ if (bEnablePrePostProcessing) {
154+ println (" Enable pre post processing." )
155+ val referenceSanitizerPre = ReferenceSanitizerPreprocessor (anchorReplacements)
156+ asciidoctor.javaExtensionRegistry().preprocessor(referenceSanitizerPre)
157+ if (conversionOptions.outputFormat == " html" ) {
158+ // Post processor not supported for PDFs
159+ // https://docs.asciidoctor.org/asciidoctorj/latest/extensions/postprocessor/
160+ asciidoctor.javaExtensionRegistry().postprocessor(ReferenceSanitizerPostprocessor (anchorReplacements))
161+ }
162+ }
163+
164+ // Dumps tree of document structure to stdio.
165+ // Best not to use for very large documents!
166+ // Note: enabling this breaks variable replacement for {var_transaction_id}. Unclear why.
167+ if (conversionOptions.dumpStructure) {
168+ asciidoctor.javaExtensionRegistry().treeprocessor(DumpTreeInfo ())
169+ }
49170
50171 asciidoctor.requireLibrary(" asciidoctor-diagram" ) // enables plantuml
172+
51173 when (inputType) {
52174 is Input .FileInput -> asciidoctor.convertFile(inputType.file, options)
53175 is Input .StringInput -> asciidoctor.convert(inputType.string, options)
54176 }
55177
178+ // profileTransactionCollector.dump()
179+ // anchorReplacements.dump()
180+ // anchorCollector.dumpKnownAnchors()
181+
182+ if (conversionOptions.extractsFolder != null ) {
183+ val jsonFormatter = Json {
184+ prettyPrint = true
185+ explicitNulls = false
186+ }
187+
188+ writeArtifact(
189+ " sdpi-profiles" ,
190+ jsonFormatter.encodeToString(infoCollector.profiles())
191+ )
192+ writeArtifact(
193+ " sdpi-use-cases" ,
194+ jsonFormatter.encodeToString(infoCollector.useCases())
195+ )
196+ writeArtifact(
197+ " sdpi-content-modules" ,
198+ jsonFormatter.encodeToString(infoCollector.contentModules().values)
199+ )
200+ writeArtifact(
201+ " sdpi-transactions" ,
202+ jsonFormatter.encodeToString(infoCollector.transactions().values)
203+ )
204+ writeArtifact(
205+ " sdpi-requirements" ,
206+ jsonFormatter.encodeToString(infoCollector.requirements().values)
207+ )
208+ }
209+
56210 asciidoctor.shutdown()
57211
58- val referencedArtifactsName = " referenced-artifacts"
59- val path = Path .of(outputFile.parentFile.absolutePath, referencedArtifactsName)
60- Files .createDirectories(path)
61- val reqsDump = Json .encodeToString(requirementsBlockProcessor.detectedRequirements())
62- Path .of(path.toFile().absolutePath, " sdpi-requirements.json" ).toFile().writeText(reqsDump)
63212 }
64213
65- private companion object {
66- const val BACKEND = " html"
214+ private fun writeArtifact (strArtifactName : String , strArtifact : String ) {
215+ if (conversionOptions.extractsFolder != null ) {
216+ Files .createDirectories(conversionOptions.extractsFolder)
217+ Path .of(conversionOptions.extractsFolder.absolutePathString(), " ${strArtifactName} .json" ).toFile()
218+ .writeText(strArtifact)
219+ }
67220 }
68221
69222 sealed interface Input {
70223 data class FileInput (val file : File ) : Input
71224 data class StringInput (val string : String ) : Input
72225 }
73-
74- sealed interface Mode {
75- object Productive : Mode
76- data class Test (val structureDump : OutputStream ) : Mode
77- }
78226}
0 commit comments