Render Markdown with Jsonnet natively (#26)

This commit is contained in:
Jeroen Op 't Eynde 2022-06-11 14:49:15 +02:00 committed by GitHub
parent fc3f9bca2d
commit f47f46f93f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 427 additions and 39 deletions

View file

@ -1,23 +1,19 @@
---
permalink: /
---
# package d
```jsonnet
local d = import "github.com/jsonnet-libs/docsonnet/doc-util"
local d = import 'github.com/jsonnet-libs/docsonnet/doc-util/main.libsonnet';
```
`doc-util` provides a Jsonnet interface for `docsonnet`,
a Jsonnet API doc generator that uses structured data instead of comments.
## Index
* [`fn arg(name, type, default)`](#fn-arg)
* [`fn fn(help, args)`](#fn-fn)
* [`fn obj(help, fields)`](#fn-obj)
* [`fn pkg(name, url, help)`](#fn-pkg)
* [`fn render(obj, filename)`](#fn-render)
* [`fn val(type, help, default)`](#fn-val)
* [`obj argument`](#obj-argument)
* [`fn new(name, type, default)`](#fn-argumentnew)
@ -28,10 +24,11 @@ local d = import "github.com/jsonnet-libs/docsonnet/doc-util"
* [`obj object`](#obj-object)
* [`fn new(help, fields)`](#fn-objectnew)
* [`fn withFields(fields)`](#fn-objectwithfields)
* [`obj package`](#obj-package)
* [`fn new(name, url, help)`](#fn-packagenew)
* [`obj value`](#obj-value)
* [`fn new(type, help, default)`](#fn-valuenew)
* [`obj T`](#obj-t)
* [`obj package`](#obj-package)
* [`fn new(name, url, help)`](#fn-packagenew)
## Fields
@ -67,6 +64,24 @@ pkg(name, url, help)
`new` is a shorthand for `package.new`
### fn render
```ts
render(obj, filename)
```
`render` converts the docstrings to human readable Markdown files.
Usage:
```jsonnet
// docs.jsonnet
d.render(import 'main.libsonnet', 'main.libsonnet')
```
Call with: `jsonnet -S -c -m docs/ docs.jsonnet`
### fn val
```ts
@ -75,11 +90,11 @@ val(type, help, default)
`val` is a shorthand for `value.new`
## obj argument
### obj argument
Utilities for creating function arguments
### fn argument.new
#### fn argument.new
```ts
new(name, type, default)
@ -87,11 +102,11 @@ new(name, type, default)
new creates a new function argument, taking the name, the type and optionally a default value
## obj func
### obj func
Utilities for documenting Jsonnet methods (functions of objects)
### fn func.new
#### fn func.new
```ts
new(help, args)
@ -99,7 +114,7 @@ new(help, args)
new creates a new function, optionally with description and arguments
### fn func.withArgs
#### fn func.withArgs
```ts
withArgs(args)
@ -107,7 +122,7 @@ withArgs(args)
The `withArgs` modifier overrides the arguments of that function
### fn func.withHelp
#### fn func.withHelp
```ts
withHelp(help)
@ -115,11 +130,11 @@ withHelp(help)
The `withHelp` modifier overrides the help text of that function
## obj object
### obj object
Utilities for documenting Jsonnet objects (`{ }`).
### fn object.new
#### fn object.new
```ts
new(help, fields)
@ -127,7 +142,7 @@ new(help, fields)
new creates a new object, optionally with description and fields
### fn object.withFields
#### fn object.withFields
```ts
withFields(fields)
@ -135,26 +150,37 @@ withFields(fields)
The `withFields` modifier overrides the fields property of an already created object
## obj package
### obj value
Utilities for documenting plain Jsonnet values (primitives)
#### fn value.new
```ts
new(type, help, default)
```
new creates a new object of given type, optionally with description and default value
### obj T
* `T.any` (`string`): `"any"` - argument of type "any"
* `T.array` (`string`): `"array"` - argument of type "array"
* `T.boolean` (`string`): `"bool"` - argument of type "boolean"
* `T.func` (`string`): `"function"` - argument of type "func"
* `T.null` (`string`): `"null"` - argument of type "null"
* `T.number` (`string`): `"number"` - argument of type "number"
* `T.object` (`string`): `"object"` - argument of type "object"
* `T.string` (`string`): `"string"` - argument of type "string"
### fn package.new
### obj package
#### fn package.new
```ts
new(name, url, help)
```
new creates a new package with given `name`, `import` URL and `help` text
## obj value
Utilities for documenting plain Jsonnet values (primitives)
### fn value.new
```ts
new(type, help, default)
```
new creates a new object of given type, optionally with description and default value

3
doc-util/docs.jsonnet Normal file
View file

@ -0,0 +1,3 @@
local d = import './main.libsonnet';
d.render(import 'main.libsonnet', 'main.libsonnet')

View file

@ -73,37 +73,68 @@
'#arg': self.argument['#new'] + self.func.withHelp('`arg` is a shorthand for `argument.new`'),
arg:: self.argument.new,
"#value": d.obj("Utilities for documenting plain Jsonnet values (primitives)"),
'#value': d.obj('Utilities for documenting plain Jsonnet values (primitives)'),
value:: {
"#new": d.fn("new creates a new object of given type, optionally with description and default value", [d.arg("type", d.T.string), d.arg("help", d.T.string), d.arg("default", d.T.any)]),
new(type, help='', default=null): { 'value': {
'#new': d.fn('new creates a new object of given type, optionally with description and default value', [d.arg('type', d.T.string), d.arg('help', d.T.string), d.arg('default', d.T.any)]),
new(type, help='', default=null): { value: {
help: help,
type: type,
default: default,
} }
} },
},
'#val': self.value['#new'] + self.func.withHelp('`val` is a shorthand for `value.new`'),
val: self.value.new,
val:: self.value.new,
// T contains constants for the Jsonnet types
T:: {
'#string': d.val(d.T.string, 'argument of type "string"'),
string: 'string',
'#number': d.val(d.T.string, 'argument of type "number"'),
number: 'number',
int: self.number,
integer: self.number,
'#boolean': d.val(d.T.string, 'argument of type "boolean"'),
boolean: 'bool',
bool: self.boolean,
'#object': d.val(d.T.string, 'argument of type "object"'),
object: 'object',
'#array': d.val(d.T.string, 'argument of type "array"'),
array: 'array',
'#any': d.val(d.T.string, 'argument of type "any"'),
any: 'any',
'null': "null",
nil: self["null"],
'#null': d.val(d.T.string, 'argument of type "null"'),
'null': 'null',
nil: self['null'],
'#func': d.val(d.T.string, 'argument of type "func"'),
func: 'function',
'function': self.func,
},
'#render': d.fn(
|||
`render` converts the docstrings to human readable Markdown files.
Usage:
```jsonnet
// docs.jsonnet
d.render(import 'main.libsonnet', 'main.libsonnet')
```
Call with: `jsonnet -S -c -m docs/ docs.jsonnet`
|||,
args=[
d.arg('obj', d.T.object),
d.arg('filename', d.T.string),
]
),
render:: (import './render.libsonnet').render,
}

324
doc-util/render.libsonnet Normal file
View file

@ -0,0 +1,324 @@
{
local root = self,
templates: {
package: |||
# package %(name)s
```jsonnet
local %(name)s = import '%(import)s/%(filename)s';
```
%(help)s
|||,
indexPage: |||
# %(prefix)s%(name)s
%(index)s
|||,
index: |||
## Index
%s
|||,
sectionTitle: '%(abbr)s %(prefix)s%(name)s',
sectionLink: '* [`%(abbr)s %(linkName)s`](#%(link)s)',
value: '* `%(prefix)s%(name)s` (`%(type)s`): `"%(value)s"` - %(help)s',
section: |||
%(headerDepth)s %(title)s
%(content)s
|||,
},
joinPrefixes(prefixes, sep='.')::
std.join(sep, prefixes)
+ (if std.length(prefixes) > 0
then sep
else ''),
renderSectionTitle(section, prefixes)::
root.templates.sectionTitle % {
name: section.name,
abbr: section.type.abbr,
prefix: root.joinPrefixes(prefixes),
},
renderValues(values, prefixes=[])::
if std.length(values) > 0
then
std.join('\n', [
root.templates.value
% value {
prefix: root.joinPrefixes(prefixes),
}
for value in values
]) + '\n'
else '',
renderSections(sections, depth=0, prefixes=[])::
if std.length(sections) > 0
then
std.join('\n', [
root.templates.section
% {
headerDepth: std.join('', [
'#'
for d in std.range(0, depth + 2)
]),
title: root.renderSectionTitle(
section,
prefixes,
),
content: section.content,
}
+ root.renderValues(
section.values,
prefixes + [section.name]
)
+ root.renderSections(
section.subSections,
depth + 1,
prefixes + [section.name]
)
for section in sections
])
else '',
renderPackage(package)::
(root.templates.package % package)
+ (
if std.length(package.subPackages) > 0
then
'## Subpackages\n\n'
+ std.join('\n', [
'* [%(name)s](%(path)s)' % {
name: sub.name,
path: package.name + '/' + sub.name + '.md',
}
for sub in package.subPackages
]) + '\n\n'
else ''
)
+ (root.templates.index % root.index(package.sections))
+ '\n## Fields\n\n'
+ root.renderValues(package.values)
+ root.renderSections(package.sections),
index(sections, depth=0, prefixes=[])::
std.join('\n', [
std.join('', [
' '
for d in std.range(0, (depth * 2) - 1)
])
+ (root.templates.sectionLink % {
abbr: section.type.abbr,
linkName: section.linkName,
link:
std.asciiLower(
std.strReplace(
std.strReplace(root.renderSectionTitle(section, prefixes), '.', '')
, ' ', '-'
)
),
})
+ (
if std.length(section.subSections) > 0
then '\n' + root.index(
section.subSections,
depth + 1,
prefixes + [section.name]
)
else ''
)
for section in sections
]),
sections: {
base: {
subSections: [],
values: [],
},
object(key, doc, obj, depth):: self.base {
name: std.strReplace(key, '#', ''),
local processed = root.prepare(obj, depth=depth + 1),
subSections: processed.sections,
values: processed.values,
type: { full: 'object', abbr: 'obj' },
abbr: self.type.abbr,
doc:
if self.type.full in doc
then doc[self.type.full]
else { help: '' },
help: self.doc.help,
linkName: self.name,
content:
if self.help != ''
then self.help + '\n'
else '',
},
'function'(key, doc):: self.base {
name: std.strReplace(key, '#', ''),
type: { full: 'function', abbr: 'fn' },
abbr: self.type.abbr,
doc: doc[self.type.full],
help: self.doc.help,
args: std.join(', ', [
if arg.default != null
then arg.name + '=' + arg.default
else arg.name
for arg in self.doc.args
]),
linkName: '%(name)s(%(args)s)' % self,
content: '```ts\n%(name)s(%(args)s)\n```\n\n%(help)s' % self,
},
value(key, doc, obj):: self.base {
name: std.strReplace(key, '#', ''),
type: doc.value.type,
help: doc.value.help,
value: obj,
},
},
prepare(obj, filename='', depth=0)::
std.foldl(
function(acc, key)
acc +
// Package definition
if key == '#'
then obj[key] { filename: filename }
// Field definition
else if std.startsWith(key, '#')
then (
local realKey = key[1:];
if 'value' in obj[key]
then {
values+: [root.sections.value(
key,
obj[key],
obj[realKey]
)],
}
else if 'function' in obj[key]
then {
functionSections+: [root.sections['function'](
key,
obj[key],
)],
}
else if 'object' in obj[key]
then {
objectSections+: [root.sections.object(
key,
obj[key],
obj[realKey],
depth
)],
}
else {}
)
// subPackage definition
else if std.isObject(obj[key]) && '#' in obj[key]
then {
subPackages+: [root.prepare(obj[key])],
}
// undocumented object
else if std.isObject(obj[key]) && !('#' + key in obj)
then (
local section = root.sections.object(
key,
{},
obj[key],
depth
);
// only add if has documented subSections or values
if std.length(section.subSections) > 0
|| std.length(section.values) > 0
then { objectSections+: [section] }
else {}
)
else {},
std.objectFieldsAll(obj),
{
functionSections: [],
objectSections: [],
sections:
self.functionSections
+ self.objectSections,
subPackages: [],
values: [],
}
),
renderIndexPage(package, prefixes)::
root.templates.indexPage % {
name: package.name,
prefix: root.joinPrefixes(prefixes),
index: std.join('\n', [
'* [%(name)s](%(name)s.md)' % sub
for sub in package.subPackages
]),
},
renderFiles(package, prefixes=[]):
local key =
if std.length(prefixes) > 0
then package.name + '.md'
else 'README.md';
local path = root.joinPrefixes(prefixes, '/');
{
[path + key]: root.renderPackage(package),
}
+ (
if std.length(package.subPackages) > 0
then {
[package.name + '/index.md']: root.renderIndexPage(package, prefixes),
}
else {}
)
+ std.foldl(
function(acc, sub)
acc + sub,
[
root.renderFiles(
sub,
prefixes=prefixes + [package.name]
)
for sub in package.subPackages
],
{}
),
render(obj, filename):
self.renderFiles(self.prepare(obj, filename)),
}