Skip to content

mariotoffia/goasciidoc

Repository files navigation

GoDoc GitHub Actions CodeQL

goasciidoc

Document your go code using asciidoc. It allows you to have asciidoc markup in all code documentation. Asciidoc do support many plugins to e.g. render sequence diagrams, svg images, ERD, BPMN, RackDiag and many more.

One such component is kroki that renders the ascii into fine-art :).

🐬 Quick Install

go install github.com/mariotoffia/goasciidoc@latest

💡 See the plugins section below for examples on kroki rendered images

To generate documentation for this project as mydoc.adoc, do the following:

goasciidoc -o mydoc.adoc

The above will generate standard code documentation, internal and test is excluded. By default it renders a index with some defaults including a table of contents. Is is possible to override the contents by supplying a JSON string with overrides.

You may have more properties in the -c (configuration) parameter, for example:

 {
  "author": "Mario Toffia",
  "email": "[email protected]",
  "web": "https://github.com/mariotoffia/goasciidoc",
  "images": "../meta/assets",
  "title": "Go Asciidoc Document Generator",
  "toc": "Table of Contents",
  "toclevel": 2
}

Everything is rendered using go templates and it is possible to override each of them using the -t switch (or if in same folder using --templatedir switch). Take a look at defaults/*.gtpl to view how such may look like. It is standard go templates.

All code is parsed thus you may annotate with asciidoc wherever you want, e.g.

// HealthChecker is responsible for doing various health checks on patients.
// 
// Its main flow is conceptualized on following sequence diagram
//
// [mermaid,config-override,svg]
// ....
// sequenceDiagram
//    participant Alice
//    participant Bob
//    Alice->John: Hello John, how are you?
//    loop Healthcheck
//        John->John: Fight against hypochondria
//    end
//    Note right of John: Rational thoughts prevail...
//    John-->Alice: Great!
//    John->Bob: How about you?
//    Bob-->John: Jolly good!
// ....
type HealthChecker struct {
  
}

Installation & Usage

This installs the latest version. Use the repository tags to determine the version you want to install (if not latest).

go install github.com/mariotoffia/goasciidoc@latest

You may now use the goasciidoc e.g. in the goasciidoc repo by goasciidoc --stdout. This will emit this project documentation onto the stdout. If you need help on flags and parameters jus do a goasciidoc --h.

goasciidoc v0.4.1
Usage: goasciidoc [--out PATH] [--stdout] [--module PATH] [--internal] [--private] [--nonexported] [--test] [--noindex] [--notoc] [--indexconfig JSON] [--overrides OVERRIDES] [--list-template] [--out-template OUT-TEMPLATE] [--packagedoc FILEPATH] [--templatedir TEMPLATEDIR] [PATH [PATH ...]]

Positional arguments:
  PATH                   Directory or files to be included in scan (if none, current path is used)

Options:
  --out PATH, -o PATH    The out filepath to write the generated document, default module path, file docs.adoc
  --stdout               If output the generated asciidoc to stdout instead of file
  --module PATH, -m PATH
                         an optional folder or file path to module, otherwise current directory
  --internal, -i         If internal go code shall be rendered as well
  --private, -p          If files beneath directories starting with an underscore shall be included
  --nonexported          Renders Non exported as well as the exported. Default only Exported is rendered.
  --test, -t             If test code should be included
  --noindex, -n          If no index header shall be generated
  --notoc                Removes the table of contents if index document
  --indexconfig JSON, -c JSON
                         JSON document to override the IndexConfig
  --overrides OVERRIDES, -r OVERRIDES
                         name=template filepath to override default templates
  --list-template        Lists all default templates in the binary
  --out-template OUT-TEMPLATE
                         outputs a template to stdout
  --packagedoc FILEPATH, -d FILEPATH
                         set relative package search filepaths for package documentation
  --templatedir TEMPLATEDIR
                         Loads template files *.gtpl from a directory, use --list to get valid names of templates
  --help, -h             display this help and exit
  --version              display version and exit

Overriding Default Package Overview

By default goasciidoc will use overview.adoc or _design/overview.adoc to generate the package overview. If those are not found, it will default back to the golang package documentation (if any).

It is possible to set other search paths for those document. The search-path is relative the package path.

NOTE: That the path is a relative filepath i.e both directory and file. Directory may be omitted.

For example, look for package-overview.adoc in package folder instead of the default overview.adoc, _design/overview.adoc:

goasciidoc --stdout -d package-overview.adoc 

Macros

There are a initial support for macros in goasciidoc, for example ${gad:current:fq} is supported and will substitute the macro to the current fully qualified path to the source file. This can be e.g. used for inclusions of source code.

Example Documentation

// ParseConfig to use when invoking ParseAny, ParseSingleFileWalker, and
// ParseSinglePackageWalker.
//
// .ParserConfig
// [source,go]
// ----
// include::${gad:current:fq}[tag=parse-config,indent=0]
// ----
// <1> These are usually excluded since many testcases is not documented anyhow
// <2> As of _go 1.16_ it is recommended to *only* use module based parsing
// tag::parse-config[]
type ParseConfig struct {
	// Test denotes if test files (ending with _test.go) should be included or not
	// (default not included)
	Test bool // <1>
	// Internal determines if internal folders are included or not (default not)
	Internal bool
	// UnderScore, when set to true it will include directories beginning with _
	UnderScore bool
	// Optional module to resolve fully qualified package paths
	Module *GoModule // <2>
}

// end::parse-config[]

It will then get rendered as follows: macro-expansion

Supported Macros

Macro Description
${gad:current:fq} The fully qualified path to file being processed.
${gad:current:fqdir} The fully qualified path to folder being processed.
${gad:current:dir} The directory name where being processed.
${gad:current:file} The file name where being processed.

Templates

This project consists of a parser to parse go-code and a producer to produce asciidoc files from the code & code documentation. It bases its rendering system heavily on templates (asciidoc/template.go) with some "sane" default so it may be rather easily overridden. The default templates is embedded in the binary from the defaults/*.gtpl files.

List Default Templates

To list the default templates just do goasciidoc --list-template. Version 0.4.0 will list the following template names:

  • interfaces
  • interface
  • consts
  • typedeffunc
  • package
  • import
  • typedefvars
  • vars
  • index
  • function
  • typedeffuncs
  • functions
  • structs
  • struct
  • typedefvars
  • var
  • const
  • receivers

Get Default Templates

It is possible to retrieve the default templates (use list to get the template names) using a command switch --out-template NAME, for example:

goasciidoc --out-template struct

The above outputs (for v0.0.6):

"=== {{.Struct.Name}}
[source, go]
----
{{.Struct.Decl}} {
{{- range .Struct.Fields}}
        {{if .Nested}}{{.Nested.Name}}{{"\t"}}struct{{else}}{{tabify .Decl}}{{end}}
{{- end}}
}
----

{{.Struct.Doc}}
{{range .Struct.Fields}}{{if not .Nested}}
==== {{.Decl}}
{{.Doc}}
{{- end}}
{{end}}
{{range .Struct.Fields}}{{if .Nested}}{{render $ .Nested}}{{end}}{{end}}
"

Override Default Templates

If you're unhappy with one of the default templates, you may override it (one or more) using the -t FILEPATH switch. It may be several -t on same command if multiple overrides. The filepath is either relative or fully qualified filepath to a template file.

For example, overriding the package template can be done like this:

echo "== Override Package {{.File.FqPackage}}" > t.txt; goasciidoc -r package=t.txt --stdout; rm t.txt

In the stdout you may observe, now, it has Override Package instead of Package as heading

== Override Package github.com/mariotoffia/goasciidoc/goparser
=== Imports
...

Override default templates using a files in a directory

It is possible to set a template directory where goasciidoc will search for files named (see list templates) and file extension .gtpl e.g. import.gtpl.

Example usage: goasciidoc --templatedir defaults

It reads all files and overrides those found, the rest is using the default. You can checkout the defaults folder (or copy as starting point) when you make your own layout. You can remove those not needed, and the defaults will kick in.

ls -l defaults
total 72
-rw-r--r-- 1 martoffi martoffi 104 Mar 19 21:33 const.gtpl
-rw-r--r-- 1 martoffi martoffi 256 Mar 19 21:33 consts.gtpl
-rw-r--r-- 1 martoffi martoffi 208 Mar 19 21:25 function.gtpl
-rw-r--r-- 1 martoffi martoffi 142 Mar 19 21:25 functions.gtpl
-rw-r--r-- 1 martoffi martoffi 159 Mar 19 21:33 import.gtpl
-rw-r--r-- 1 martoffi martoffi 623 Mar 19 21:24 index.gtpl
-rw-r--r-- 1 martoffi martoffi 307 Mar 19 21:26 interface.gtpl
-rw-r--r-- 1 martoffi martoffi 111 Mar 19 21:26 interfaces.gtpl
-rw-r--r-- 1 martoffi martoffi 220 Mar 19 21:24 package.gtpl
-rw-r--r-- 1 martoffi martoffi 148 Mar 19 21:27 receivers.gtpl
-rw-r--r-- 1 martoffi martoffi 562 Mar 19 21:26 struct.gtpl
-rw-r--r-- 1 martoffi martoffi 105 Mar 19 21:27 structs.gtpl
-rw-r--r-- 1 martoffi martoffi  92 Mar 19 21:32 typedeffunc.gtpl
-rw-r--r-- 1 martoffi martoffi 120 Mar 19 21:32 typedeffuncs.gtpl
-rw-r--r-- 1 martoffi martoffi 175 Mar 19 21:34 typedefvar.gtpl
-rw-r--r-- 1 martoffi martoffi 126 Mar 19 21:34 typedefvars.gtpl
-rw-r--r-- 1 martoffi martoffi 102 Mar 19 21:34 var.gtpl
-rw-r--r-- 1 martoffi martoffi 111 Mar 19 21:34 vars.gtpl

Thanks

The package goparser was taken from an open source project by zpatrick. It seemed abandoned so I've integrated it into this project (and extended it) and now it deviates rather much from it's earlier pure form ;). Many thanks @zpatrick!! That part has a MIT License.

copy.go is created by Roland Singer [[email protected]] and is used for unit test. Many thanks @r0l1. You may find the original here.

Plugins

Since asciidoc supports plugins, thus is very versatile, myself is using kroki that may render many types of diagrams (can be done online or offline using docker-compose). Below there are just a few of many, many diagrams that may be outputted just using kroki.

For example a sequence diagram based on the following text in your documentation

sequenceDiagram
    participant Alice
    participant Bob
    Alice->John: Hello John, how are you?
    loop Healthcheck
        John->John: Fight against hypochondria
    end
    Note right of John: Rational thoughts prevail...
    John-->Alice: Great!
    John->Bob: How about you?
    Bob-->John: Jolly good!

will render the following sequence diagram sequence diagram sample

or are you into packet diagrams will the following text

packetdiag {
  colwidth = 32;
  node_height = 72;

  0-15: Source Port;
  16-31: Destination Port;
  32-63: Sequence Number;
  64-95: Acknowledgment Number;
  96-99: Data Offset;
  100-105: Reserved;
  106: URG [rotate = 270];
  107: ACK [rotate = 270];
  108: PSH [rotate = 270];
  109: RST [rotate = 270];
  110: SYN [rotate = 270];
  111: FIN [rotate = 270];
  112-127: Window;
  128-143: Checksum;
  144-159: Urgent Pointer;
  160-191: (Options and Padding);
  192-223: data [colheight = 3];
}

render the packet diagram image packet diagram

Simple activity diagram can be annotated like this

actdiag {
  write -> convert -> image

  lane user {
    label = "User"
    write [label = "Writing reST"];
    image [label = "Get diagram IMAGE"];
  }
  lane actdiag {
    convert [label = "Convert reST to Image"];
  }
}

and outputs the following: Activity

If you're into UML, you may use this annotation format

[Pirate|eyeCount: Int|raid();pillage()|
  [beard]--[parrot]
  [beard]-:>[foul mouth]
]

[<abstract>Marauder]<:--[Pirate]
[Pirate]- 0..7[mischief]
[jollyness]->[Pirate]
[jollyness]->[rum]
[jollyness]->[singing]
[Pirate]-> *[rum|tastiness: Int|swig()]
[Pirate]->[singing]
[singing]<->[rum]

[<start>st]->[<state>plunder]
[plunder]->[<choice>more loot]
[more loot]->[st]
[more loot] no ->[<end>e]

[<actor>Sailor] - [<usecase>shiver me;timbers]

to output this UML

You may also be a component fan

@startuml
!include C4_Container.puml

LAYOUT_TOP_DOWN
LAYOUT_WITH_LEGEND()

title Container diagram for Internet Banking System

Person(customer, Customer, "A customer of the bank, with personal bank accounts")

System_Boundary(c1, "Internet Banking") {
    Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
    Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
    Container(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
    ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")
    Container(backend_api, "API Application", "Java, Docker Container", "Provides Internet banking functionality via API")
}

System_Ext(email_system, "E-Mail System", "The internal Microsoft Exchange system")
System_Ext(banking_system, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")

Rel(customer, web_app, "Uses", "HTTPS")
Rel(customer, spa, "Uses", "HTTPS")
Rel(customer, mobile_app, "Uses")

Rel_Neighbor(web_app, spa, "Delivers")
Rel(spa, backend_api, "Uses", "async, JSON/HTTPS")
Rel(mobile_app, backend_api, "Uses", "async, JSON/HTTPS")
Rel_Back_Neighbor(database, backend_api, "Reads from and writes to", "sync, JDBC")

Rel_Back(customer, email_system, "Sends e-mails to")
Rel_Back(email_system, backend_api, "Sends e-mails using", "sync, SMTP")
Rel_Neighbor(backend_api, banking_system, "Uses", "sync/async, XML/HTTPS")
@enduml

I use excalidraw.com diagrams a lot when documenting my code. They stored in a versionable JSON documents, that kroki can render natively. You may use them embedded in the comment or store the JSON document on filesystem and reference it (I use the latter).

Excalidraw

It is even possible to generate a nice bar-chart like this (with some obscure JSON syntax ;)

Vega-Lite