1+ package sk.ainet.tools.docgen
2+
3+ import kotlinx.cli.*
4+ import kotlinx.serialization.json.Json
5+ import java.io.File
6+ import java.time.Instant
7+ import java.time.format.DateTimeFormatter
8+
9+ /* *
10+ * Main documentation generator that converts JSON operator documentation to AsciiDoc format.
11+ *
12+ * Usage: DocGen -i input.json -o output_directory
13+ */
14+ object DocGen {
15+
16+ private val json = Json {
17+ ignoreUnknownKeys = true
18+ prettyPrint = true
19+ }
20+
21+ fun generateDocumentation (inputFile : File , outputDir : File ) {
22+ println (" Reading JSON from: ${inputFile.absolutePath} " )
23+
24+ val jsonContent = inputFile.readText()
25+ val module = json.decodeFromString<OperatorDocModule >(jsonContent)
26+
27+ println (" Parsed module: ${module.module} with ${module.operators.size} operators" )
28+
29+ // Create output directory structure
30+ outputDir.mkdirs()
31+ val generatedDir = File (outputDir, " _generated_" )
32+ generatedDir.mkdirs()
33+
34+ // Generate main index page
35+ generateMainIndex(module, generatedDir)
36+
37+ // Generate individual operator pages
38+ module.operators.forEach { operator ->
39+ generateOperatorPage(operator , module, generatedDir)
40+ }
41+
42+ println (" Generated documentation in: ${generatedDir.absolutePath} " )
43+ }
44+
45+ private fun generateMainIndex (module : OperatorDocModule , outputDir : File ) {
46+ val content = buildString {
47+ appendLine(" = ${module.module} Operators" )
48+ appendLine()
49+ appendLine(" // Generated on ${formatTimestamp(module.timestamp)} " )
50+ appendLine(" // Version: ${module.version} " )
51+ appendLine(" // Commit: ${module.commit} " )
52+ appendLine()
53+ appendLine(" This documentation is automatically generated from the codebase annotations." )
54+ appendLine()
55+ appendLine(" == Operators" )
56+ appendLine()
57+
58+ // Group operators by modality
59+ val operatorsByModality = module.operators.groupBy { it.modality }
60+ operatorsByModality.entries.sortedBy { it.key }.forEach { (modality, operators) ->
61+ appendLine(" === ${modality.capitalize()} Operators" )
62+ appendLine()
63+ operators.sortedBy { it.name }.forEach { operator ->
64+ appendLine(" * xref:${operator .name.lowercase()} .adoc[${operator .name} ] - ${operator .packageName} " )
65+ }
66+ appendLine()
67+ }
68+ }
69+
70+ File (outputDir, " index.adoc" ).writeText(content)
71+ }
72+
73+ private fun generateOperatorPage (operator : OperatorDoc , module : OperatorDocModule , outputDir : File ) {
74+ val content = buildString {
75+ appendLine(" = ${operator .name} " )
76+ appendLine()
77+ appendLine(" // Generated on ${formatTimestamp(module.timestamp)} " )
78+ appendLine(" // Package: ${operator .packageName} " )
79+ appendLine(" // Modality: ${operator .modality} " )
80+ appendLine()
81+ appendLine(" Package: `${operator .packageName} `" )
82+ appendLine()
83+ appendLine(" Modality: *${operator .modality} *" )
84+ appendLine()
85+
86+ if (operator .functions.isNotEmpty()) {
87+ appendLine(" == Functions" )
88+ appendLine()
89+
90+ operator .functions.sortedBy { it.name }.forEach { function ->
91+ generateFunctionSection(function, this )
92+ }
93+ }
94+ }
95+
96+ File (outputDir, " ${operator .name.lowercase()} .adoc" ).writeText(content)
97+ }
98+
99+ private fun generateFunctionSection (function : FunctionDoc , builder : StringBuilder ) {
100+ builder.apply {
101+ appendLine(" === ${function.name} " )
102+ appendLine()
103+ appendLine(" [source,kotlin]" )
104+ appendLine(" ----" )
105+ appendLine(function.signature)
106+ appendLine(" ----" )
107+ appendLine()
108+
109+ // Parameters table
110+ if (function.parameters.isNotEmpty()) {
111+ appendLine(" ==== Parameters" )
112+ appendLine()
113+ appendLine(" [cols=\" 1,2,3\" ]" )
114+ appendLine(" |===" )
115+ appendLine(" | Name | Type | Description" )
116+ appendLine()
117+ function.parameters.forEach { param ->
118+ appendLine(" | ${param.name} " )
119+ appendLine(" | `${param.type} `" )
120+ appendLine(" | ${param.description.ifEmpty { " _No description_" }} " )
121+ appendLine()
122+ }
123+ appendLine(" |===" )
124+ appendLine()
125+ }
126+
127+ // Return type
128+ appendLine(" ==== Returns" )
129+ appendLine()
130+ appendLine(" `${function.returnType} `" )
131+ appendLine()
132+
133+ // Backend status table
134+ if (function.statusByBackend.isNotEmpty()) {
135+ appendLine(" ==== Backend Status" )
136+ appendLine()
137+ generateBackendStatusTable(function, this )
138+ }
139+
140+ appendLine()
141+ }
142+ }
143+
144+ private fun generateBackendStatusTable (function : FunctionDoc , builder : StringBuilder ) {
145+ builder.apply {
146+ appendLine(" [cols=\" 1,1,2\" ]" )
147+ appendLine(" |===" )
148+ appendLine(" | Backend | Status | Notes" )
149+ appendLine()
150+
151+ function.statusByBackend.entries.sortedBy { it.key }.forEach { (backend, status) ->
152+ appendLine(" | ${backend} " )
153+ appendLine(" | ${formatStatus(status)} " )
154+
155+ val backendNotes = function.notes.filter { it.backend == backend }
156+ if (backendNotes.isNotEmpty()) {
157+ val notesText = backendNotes.joinToString(" , " ) { note ->
158+ when (note.type) {
159+ " owner" -> " Owner: ${note.content} "
160+ " issue" -> " Issue: ${note.content} "
161+ else -> " ${note.type} : ${note.content} "
162+ }
163+ }
164+ appendLine(" | ${notesText} " )
165+ } else {
166+ appendLine(" | _None_" )
167+ }
168+ appendLine()
169+ }
170+ appendLine(" |===" )
171+ appendLine()
172+ }
173+ }
174+
175+ private fun formatStatus (status : String ): String {
176+ return when (status) {
177+ " implemented" -> " ✅ Implemented"
178+ " not_implemented" -> " ❌ Not Implemented"
179+ " in_progress" -> " 🚧 In Progress"
180+ else -> status
181+ }
182+ }
183+
184+ private fun formatTimestamp (timestamp : String ): String {
185+ return try {
186+ val instant = Instant .parse(timestamp)
187+ DateTimeFormatter .ISO_LOCAL_DATE_TIME .format(instant.atZone(java.time.ZoneId .systemDefault()))
188+ } catch (e: Exception ) {
189+ timestamp
190+ }
191+ }
192+
193+ private fun String.capitalize (): String {
194+ return this .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
195+ }
196+ }
197+
198+ fun main (args : Array <String >) {
199+ val parser = ArgParser (" docgen" )
200+ val input by parser.option(ArgType .String , shortName = " i" , description = " Input JSON file" ).required()
201+ val output by parser.option(ArgType .String , shortName = " o" , description = " Output directory" ).required()
202+
203+ parser.parse(args)
204+
205+ val inputFile = File (input)
206+ val outputDir = File (output)
207+
208+ if (! inputFile.exists()) {
209+ println (" Error: Input file does not exist: $input " )
210+ return
211+ }
212+
213+ DocGen .generateDocumentation(inputFile, outputDir)
214+ }
0 commit comments