@@ -8,49 +8,45 @@ import (
88 "go/parser"
99 "go/printer"
1010 "go/token"
11+ "html"
1112 "log"
1213 "os"
1314 "path/filepath"
1415 "regexp"
1516 "strings"
1617)
1718
18- // generateReferenceMarkdown creates a Markdown documentation file from Go source code comments.
19- // It parses the package found in inputDir and writes the formatted Markdown to outputFile.
19+ // generateReferenceMarkdown generates a Markdown reference file for the package
20+ // found in inputDir and writes it to outputFile.
2021func generateReferenceMarkdown (inputDir , outputFile string ) error {
2122 if _ , err := os .Stat (inputDir ); os .IsNotExist (err ) {
2223 return fmt .Errorf ("input directory does not exist: %s" , inputDir )
2324 }
2425
2526 outputDirPath := filepath .Dir (outputFile )
2627 if err := os .MkdirAll (outputDirPath , 0755 ); err != nil {
27- // Log warning but continue, os.Create might still work
2828 log .Printf ("Warning: Could not ensure output directory %s exists: %v" , outputDirPath , err )
2929 }
3030
3131 log .Printf ("Generating reference docs from '%s' to '%s'" , inputDir , outputFile )
3232
3333 fset := token .NewFileSet ()
3434 pkgs , err := parser .ParseDir (fset , inputDir , func (fi os.FileInfo ) bool {
35- // Basic filter to ignore test files
3635 return ! strings .HasSuffix (fi .Name (), "_test.go" )
3736 }, parser .ParseComments )
38-
3937 if err != nil {
4038 return fmt .Errorf ("failed to parse directory %s: %w" , inputDir , err )
4139 }
4240
43- // Find the primary package in the directory
4441 var pkg * ast.Package
4542 for _ , p := range pkgs {
4643 pkg = p
47- break // Use the first package found
44+ break
4845 }
4946 if pkg == nil {
5047 return fmt .Errorf ("no non-test Go package found in directory: %s" , inputDir )
5148 }
5249
53- // Extract documentation (exported symbols only by default)
5450 docPkg := doc .New (pkg , pkg .Name , doc .AllDecls )
5551
5652 out , err := os .Create (outputFile )
@@ -59,217 +55,172 @@ func generateReferenceMarkdown(inputDir, outputFile string) error {
5955 }
6056 defer out .Close ()
6157
62- // Add header
6358 header := "{{ title: Nova - Reference }}\n \n {{ include-block: doc.html markdown=\" true\" }}\n \n "
64- _ , err = out .WriteString (header )
65- if err != nil {
59+ if _ , err := out .WriteString (header ); err != nil {
6660 return fmt .Errorf ("failed to write header to output file: %w" , err )
6761 }
6862
69- var contentBuf bytes.Buffer
70-
71- title := "# Reference\n \n "
72-
73- // Package Documentation (if any)
74- if docPkg .Doc != "" {
75- contentBuf .WriteString (fmt .Sprintf ("## %s\n \n " , "Overview" ))
76- contentBuf .WriteString (formatDocText (docPkg .Doc ))
77- contentBuf .WriteString ("\n \n " )
78- }
79-
80- // Write Title
81- _ , err = out .WriteString (title )
82- if err != nil {
83- return fmt .Errorf ("failed to write title to output file: %w" , err )
84- }
63+ var content bytes.Buffer
64+ content .WriteString ("# Reference\n \n " )
8565
86- // Generate and Write Table of Contents
8766 toc := generateTOC (docPkg )
8867 if toc != "" {
89- _ , err = out .WriteString (toc )
90- if err != nil {
91- return fmt . Errorf ( "failed to write TOC to output file: %w" , err )
92- }
93- _ , err = out . WriteString ( " \n \n " ) // Add separation after TOC
94- if err != nil {
95- return fmt . Errorf ( "failed to write separator after TOC: %w" , err )
96- }
68+ content .WriteString (toc )
69+ content . WriteString ( " \n \n " )
70+ }
71+
72+ if docPkg . Doc != "" {
73+ content . WriteString ( "## Overview \n \n " )
74+ content . WriteString ( formatDocText ( docPkg . Doc ) )
75+ content . WriteString ( " \n \n " )
9776 }
9877
99- // Constants
10078 if len (docPkg .Consts ) > 0 {
101- contentBuf .WriteString ("## Constants\n \n " )
79+ content .WriteString ("## Constants\n \n " )
10280 for _ , c := range docPkg .Consts {
103- writeDocItem (& contentBuf , fset , c .Doc , c .Names , c .Decl , 3 )
81+ writeDocItem (& content , fset , c .Doc , c .Names , c .Decl , 3 )
10482 }
10583 }
10684
107- // Variables
10885 if len (docPkg .Vars ) > 0 {
109- contentBuf .WriteString ("## Variables\n \n " )
86+ content .WriteString ("## Variables\n \n " )
11087 for _ , v := range docPkg .Vars {
111- writeDocItem (& contentBuf , fset , v .Doc , v .Names , v .Decl , 3 )
88+ writeDocItem (& content , fset , v .Doc , v .Names , v .Decl , 3 )
11289 }
11390 }
11491
115- // Functions
11692 if len (docPkg .Funcs ) > 0 {
117- contentBuf .WriteString ("## Functions\n \n " )
93+ content .WriteString ("## Functions\n \n " )
11894 for _ , f := range docPkg .Funcs {
119- writeDocItem (& contentBuf , fset , f .Doc , []string {f .Name }, f .Decl , 3 )
95+ writeDocItem (& content , fset , f .Doc , []string {f .Name }, f .Decl , 3 )
12096 }
12197 }
12298
123- // Types
12499 if len (docPkg .Types ) > 0 {
125- contentBuf .WriteString ("## Types\n \n " )
100+ content .WriteString ("## Types\n \n " )
126101 for _ , t := range docPkg .Types {
127- writeDocItem (& contentBuf , fset , t .Doc , []string {t .Name }, t .Decl , 3 )
102+ fmt .Fprintf (& content , "### `%s`\n \n " , t .Name )
103+ printDeclaration (& content , fset , t .Decl , t .Name )
104+ if t .Doc != "" {
105+ content .WriteString (formatDocText (t .Doc ))
106+ content .WriteString ("\n \n " )
107+ }
128108
129109 if len (t .Consts ) > 0 {
130- contentBuf .WriteString ("#### Associated Constants\n \n " )
110+ content .WriteString ("#### Associated Constants\n \n " )
131111 for _ , c := range t .Consts {
132- writeDocItem (& contentBuf , fset , c .Doc , c .Names , c .Decl , 4 )
112+ writeDocItem (& content , fset , c .Doc , c .Names , c .Decl , 4 )
133113 }
134114 }
135115 if len (t .Vars ) > 0 {
136- contentBuf .WriteString ("#### Associated Variables\n \n " )
116+ content .WriteString ("#### Associated Variables\n \n " )
137117 for _ , v := range t .Vars {
138- writeDocItem (& contentBuf , fset , v .Doc , v .Names , v .Decl , 4 )
118+ writeDocItem (& content , fset , v .Doc , v .Names , v .Decl , 4 )
139119 }
140120 }
141121 if len (t .Funcs ) > 0 {
142- contentBuf .WriteString ("#### Associated Functions\n \n " )
122+ content .WriteString ("#### Associated Functions\n \n " )
143123 for _ , f := range t .Funcs {
144- writeDocItem (& contentBuf , fset , f .Doc , []string {f .Name }, f .Decl , 4 )
124+ writeDocItem (& content , fset , f .Doc , []string {f .Name }, f .Decl , 4 )
145125 }
146126 }
147127 if len (t .Methods ) > 0 {
148- contentBuf .WriteString ("#### Methods\n \n " )
128+ content .WriteString ("#### Methods\n \n " )
149129 for _ , m := range t .Methods {
150- writeDocItem (& contentBuf , fset , m .Doc , []string {m .Name }, m .Decl , 4 )
130+ writeDocItem (& content , fset , m .Doc , []string {m .Name }, m .Decl , 4 )
151131 }
152132 }
153133 }
154134 }
155135
156- // Write Main Content
157- _ , err = contentBuf .WriteTo (out )
158- if err != nil {
136+ if _ , err := content .WriteTo (out ); err != nil {
159137 return fmt .Errorf ("failed to write content buffer to output file: %w" , err )
160138 }
161139
162- // Add footer
163140 footer := "{{ endinclude }}"
164- _ , err = out .WriteString (footer )
165- if err != nil {
141+ if _ , err := out .WriteString (footer ); err != nil {
166142 return fmt .Errorf ("failed to write footer to output file: %w" , err )
167143 }
168144
169- log .Printf ("Successfully generated reference docs with TOC to %s" , outputFile )
145+ log .Printf ("Successfully generated reference docs to %s" , outputFile )
170146 return nil
171147}
172148
149+ // generateTOC builds a Table of Contents using GitHub-style heading anchors.
173150func generateTOC (docPkg * doc.Package ) string {
174151 var tocBuf bytes.Buffer
175152 tocBuf .WriteString ("## Table of Contents\n \n " )
176153
177154 hasContent := false
178155
179156 if docPkg .Doc != "" {
180- tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Overview" , "overview" ))
157+ tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Overview" , generateAnchor ( "Overview" ) ))
181158 hasContent = true
182159 }
183-
184160 if len (docPkg .Consts ) > 0 {
185- tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Constants" , "constants" ))
161+ tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Constants" , generateAnchor ( "Constants" ) ))
186162 hasContent = true
187163 }
188-
189164 if len (docPkg .Vars ) > 0 {
190- tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Variables" , "variables" ))
165+ tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Variables" , generateAnchor ( "Variables" ) ))
191166 hasContent = true
192167 }
193-
194168 if len (docPkg .Funcs ) > 0 {
195- tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Functions" , "functions" ))
169+ tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Functions" , generateAnchor ( "Functions" ) ))
196170 hasContent = true
197171 }
198-
199172 if len (docPkg .Types ) > 0 {
200- tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Types" , "types" ))
173+ tocBuf .WriteString (fmt .Sprintf ("- [%s](#%s)\n " , "Types" , generateAnchor ( "Types" ) ))
201174 hasContent = true
202175 for _ , t := range docPkg .Types {
203176 typeTitle := fmt .Sprintf ("`%s`" , t .Name )
204177 typeAnchor := generateAnchor (t .Name )
205178 tocBuf .WriteString (fmt .Sprintf (" - [%s](#%s)\n " , typeTitle , typeAnchor ))
206-
207- if len (t .Consts ) > 0 || len (t .Vars ) > 0 || len (t .Funcs ) > 0 || len (t .Methods ) > 0 {
208- if len (t .Consts ) > 0 {
209- tocBuf .WriteString (fmt .Sprintf (" - [Associated Constants](#%s-constants)\n " , typeAnchor ))
210- }
211- if len (t .Vars ) > 0 {
212- tocBuf .WriteString (fmt .Sprintf (" - [Associated Variables](#%s-variables)\n " , typeAnchor ))
213- }
214- if len (t .Funcs ) > 0 {
215- tocBuf .WriteString (fmt .Sprintf (" - [Associated Functions](#%s-functions)\n " , typeAnchor ))
216- }
217- if len (t .Methods ) > 0 {
218- tocBuf .WriteString (fmt .Sprintf (" - [Methods](#%s-methods)\n " , typeAnchor ))
219- }
220- }
221179 }
222180 }
223181
224182 if ! hasContent {
225183 return ""
226184 }
227-
228185 return tocBuf .String ()
229186}
230187
231- // writeDocItem formats a single documentation item (const, var, func, type, method)
232- // and writes it to the content buffer.
188+ // writeDocItem writes a documentation item heading, its declaration, and doc text.
233189func writeDocItem (contentBuf * bytes.Buffer , fset * token.FileSet , docComment string , names []string , decl ast.Node , level int ) {
234- displayNames := strings .Join (names , ", " )
235- itemTitle := fmt .Sprintf ("`%s`" , displayNames )
236- itemAnchor := generateAnchor (displayNames )
237-
238- // Create a clean anchor tag that is compatible with most Markdown renderers
239- fmt .Fprintf (contentBuf , "<a id=\" %s\" ></a>\n " , itemAnchor )
240- fmt .Fprintf (contentBuf , "%s %s\n \n " , strings .Repeat ("#" , level ), itemTitle )
190+ displayName := strings .Join (names , ", " )
191+ fmt .Fprintf (contentBuf , "%s `%s`\n \n " , strings .Repeat ("#" , level ), displayName )
241192
242- // Print the declaration (signature) using go/printer
243- var declBuf bytes.Buffer
244- cfg := printer.Config {Mode : printer .UseSpaces | printer .TabIndent , Tabwidth : 4 }
245- err := cfg .Fprint (& declBuf , fset , decl )
246- if err != nil {
247- log .Printf ("Warning: Failed to print declaration for %s: %v" , displayNames , err )
248- contentBuf .WriteString ("```go\n // Error printing declaration\n ```\n \n " )
249- } else {
250- contentBuf .WriteString ("```go\n " )
251- contentBuf .Write (declBuf .Bytes ())
252- contentBuf .WriteString ("\n ```\n \n " )
253- }
193+ printDeclaration (contentBuf , fset , decl , displayName )
254194
255- // Write the documentation comment
256195 if docComment != "" {
257196 contentBuf .WriteString (formatDocText (docComment ))
258197 contentBuf .WriteString ("\n \n " )
259198 }
260199}
261200
201+ // printDeclaration writes an AST declaration into a Go code fence.
202+ func printDeclaration (buf * bytes.Buffer , fset * token.FileSet , decl ast.Node , name string ) {
203+ var declBuf bytes.Buffer
204+ cfg := printer.Config {Mode : printer .UseSpaces | printer .TabIndent , Tabwidth : 4 }
205+ if err := cfg .Fprint (& declBuf , fset , decl ); err != nil {
206+ log .Printf ("Warning: Failed to print declaration for %s: %v" , name , err )
207+ buf .WriteString ("```go\n // Error printing declaration\n ```\n \n " )
208+ return
209+ }
210+ buf .WriteString ("```go\n " )
211+ buf .Write (declBuf .Bytes ())
212+ buf .WriteString ("\n ```\n \n " )
213+ }
214+
215+ // formatDocText converts Go doc comments to Markdown, unescaping HTML,
216+ // turning "Parameters:" into a small header and cleaning code fences.
262217func formatDocText (text string ) string {
263- // Trim leading/trailing whitespace
264218 trimmed := strings .TrimSpace (text )
265-
266- // Use doc.ToHTML to handle formatting, then convert to Markdown
267219 var buf bytes.Buffer
268220 doc .ToHTML (& buf , trimmed , nil )
269- html := buf .String ()
221+ htmlStr := buf .String ()
270222
271- // Basic conversion from HTML to Markdown
272- md := strings .ReplaceAll (html , "<p>" , "" )
223+ md := strings .ReplaceAll (htmlStr , "<p>" , "" )
273224 md = strings .ReplaceAll (md , "</p>" , "\n \n " )
274225 md = strings .ReplaceAll (md , "<pre>" , "```go\n " )
275226 md = strings .ReplaceAll (md , "</pre>" , "\n ```\n \n " )
@@ -280,27 +231,32 @@ func formatDocText(text string) string {
280231 md = strings .ReplaceAll (md , "<li>" , "- " )
281232 md = strings .ReplaceAll (md , "</li>" , "\n " )
282233
283- // Clean up extra newlines
284234 md = strings .TrimSpace (md )
285- md = regexp .MustCompile (`\n{3,}` ).ReplaceAllString (md , "\n \n " )
286235
287- return md
288- }
236+ // Unescape HTML entities.
237+ md = html . UnescapeString ( md )
289238
290- func generateAnchor (text string ) string {
291- // Remove backticks first
292- text = strings .ReplaceAll (text , "`" , "" )
293- text = strings .ToLower (text )
294- text = strings .ReplaceAll (text , " " , "-" )
239+ // Convert a "Parameters" paragraph into a small header.
240+ md = regexp .MustCompile (`(?m)^\s*Parameters:\s*$` ).ReplaceAllString (md , "#### Parameters" )
295241
296- // Remove any characters that are not letters, numbers, or hyphens
297- reg := regexp .MustCompile ("[^a-z0-9-]+ " )
298- text = reg .ReplaceAllString (text , "" )
242+ // Ensure code fences don't end up with an extra blank line before the closing fence.
243+ reFence := regexp .MustCompile ("(?s)```go \\ n(.*?) \\ n+``` " )
244+ md = reFence .ReplaceAllString (md , "```go \n $1 \n ``` " )
299245
300- // Replace multiple hyphens with a single hyphen
301- text = regexp .MustCompile ("-+" ).ReplaceAllString (text , "- " )
246+ // Collapse excessive blank lines.
247+ md = regexp .MustCompile (`\n{3,}` ).ReplaceAllString (md , "\n \n " )
302248
303- // Ensure it doesn't start or end with a hyphen
304- text = strings .Trim (text , "-" )
305- return text
249+ return strings .TrimSpace (md )
250+ }
251+
252+ // generateAnchor returns a GitHub-style slug for text.
253+ func generateAnchor (text string ) string {
254+ s := strings .TrimSpace (text )
255+ s = strings .ToLower (s )
256+ s = strings .ReplaceAll (s , "`" , "" )
257+ s = regexp .MustCompile (`[^a-z0-9 -]+` ).ReplaceAllString (s , "" )
258+ s = strings .ReplaceAll (s , " " , "-" )
259+ s = regexp .MustCompile (`-+` ).ReplaceAllString (s , "-" )
260+ s = strings .Trim (s , "-" )
261+ return s
306262}
0 commit comments