From a63ddd106176b478255e1ae796adadccc8fcdf1e Mon Sep 17 00:00:00 2001 From: sh0rez Date: Wed, 25 Mar 2020 16:20:51 +0100 Subject: [PATCH] feat: jsonnet interface --- doc-util.libsonnet | 75 ------------------------ doc-util/main.docsonnet | 8 +++ doc-util/main.libsonnet | 124 ++++++++++++++++++++++++++++++++++++++++ main.go | 24 +++++++- render.go | 18 +++--- 5 files changed, 166 insertions(+), 83 deletions(-) delete mode 100644 doc-util.libsonnet create mode 100644 doc-util/main.docsonnet create mode 100644 doc-util/main.libsonnet diff --git a/doc-util.libsonnet b/doc-util.libsonnet deleted file mode 100644 index 52940fd..0000000 --- a/doc-util.libsonnet +++ /dev/null @@ -1,75 +0,0 @@ -local d = { - new(name, url): { - name: name, - 'import': url, - }, - withAPI(obj): { - api: obj.object, - }, - withHelp(help): { - help: help, - }, - - obj: { - // obj creates an object, optionally with fields - new(help='', fields={}): { object: { - help: help, - fields: fields, - } }, - }, - Obj: self.obj.new, - - // fn creates a function, optionally with arguments - fn: { - new(help='', args=[]): { 'function': { - help: help, - args: args, - } }, - }, - Fn: self.fn.new, - - // arg creates a function argument of given name, - // type and optionally a default value - arg: { - new(name, type, default=null): { - name: name, - type: type, - default: default, - }, - }, - Arg: self.arg.new, - - // T contains constants for the Jsonnet types - T: { - string: 'string', - number: 'number', - bool: 'bool', - object: 'object', - array: 'array', - any: 'any', - func: 'function', - }, -}; - -local root = d.Obj('grafana.libsonnet is the offical Jsonnet library for Grafana', { - new: d.Fn('new returns Grafana resources with sane defaults'), - addConfig: d.Fn('addConfig adds config entries to grafana.ini', [ - d.Arg('config', d.T.object), - ]), - datasource: d.Obj('ds-util makes creating datasources easy', { - new: d.Fn('new creates a new datasource', [ - d.Arg('name', d.T.string), - d.Arg('type', d.T.string), - ]), - sheesh: d.Obj("sheesh is sheeshing around", { - shit: d.Fn("enough sheesh", [ - d.Arg("lel", d.T.any), - d.Arg("lol", d.T.func), - ]) - }) - }), -}); - -d.new('grafana', 'github.com/sh0rez/grafana.libsonnet') -+ d.withAPI(std.prune(root)) -+ d.withHelp("`grafana.libsonnet` is the offical Jsonnet package for using Grafana with Kubernetes") diff --git a/doc-util/main.docsonnet b/doc-util/main.docsonnet new file mode 100644 index 0000000..f9d0325 --- /dev/null +++ b/doc-util/main.docsonnet @@ -0,0 +1,8 @@ +local d = import './main.libsonnet'; + +d.new('d', url='github.com/jsonnet-libs/docsonnet/doc-util') ++ d.withAPI(import './main.libsonnet') ++ d.withHelp(||| + `doc-util` provides a Jsonnet interface for `docsonnet`, + a Jsonnet API doc generator that uses structured data instead of comments. +|||) diff --git a/doc-util/main.libsonnet b/doc-util/main.libsonnet new file mode 100644 index 0000000..4ecb563 --- /dev/null +++ b/doc-util/main.libsonnet @@ -0,0 +1,124 @@ +local _internal = { + // clean removes the hash from the key + clean(str): std.strReplace(str, '#', ''), + + include(api, key): + // include docsonnet fields + if std.startsWith(key, '#') then true + + // non-docsonnet object, might have nested docsonnet + else if std.isObject(api[key]) then + // skip if has docsonnet counterpart + (if std.objectHasAll(api, '#' + key) then false else true) + + // some other field, skip it + else false, + + // ds deals with docsonnet fields. Also checks for nested objects for + //docsonnet ones + ds(api, key): + local cleaned = self.clean(key); + if std.objectHasAll(api[key], 'object') && std.objectHasAll(api, cleaned) then + api[key] + $.object.withFields($.filter(api[cleaned])) + else + api[key], + + // filter returns only docsonnet related objects from the api + filter(api): { + [self.clean(key)]: + // include all docsonnet + if std.startsWith(key, '#') then + $.ds(api, key) + // create docsonnet objects from regular ones + else if std.isObject(api[key]) then + $.obj('', $.filter(api[key])) + + for key in std.objectFields(api) + if self.include(api, key) + }, + + fromMain(main): self.filter(main), +}; + +{ + local internal = self + _internal, + local d = self, + + '#new': d.fn('`new` initiates the api documentation model, taking the `name` and import `url` of your project', [d.arg('name', d.T.string), d.arg('url', d.T.string)]), + new(name, url):: { + name: name, + 'import': url, + }, + + '#withAPI': d.fn('`withAPI` automatically builds the docsonnet model from your public API. The `main` parameter is usually set to `import "./main.libsonnet"`.', [d.arg('main', d.T.object)]), + withAPI(main):: { + api: internal.fromMain(main), + }, + + '#withFields': d.fn('`withFields` allows to manually specify the docsonnet model. Usually `withAPI` is the better alternative', [d.arg('fields', d.T.object)]), + withFields(fields):: { + api: fields, + }, + + "#withHelp": d.fn("`withHelp` sets the main description text, displayed right under the package's name", [d.arg("help", d.T.string)]), + withHelp(help):: { + help: help, + }, + + "#object": d.obj("Utilities for documenting Jsonnet objects (`{ }`)."), + object:: { + "#new": d.fn("new creates a new object, optionally with description and fields", [d.arg("help", d.T.string), d.arg("fields", d.T.object)]), + new(help='', fields={}):: { object: { + help: help, + fields: fields, + } }, + + "#withFields": d.fn("The `withFields` modifier overrides the fields property of an already created object", [d.arg("fields", d.T.object)]), + withFields(fields):: { object+: { + fields: fields, + } }, + }, + + "#obj": self.object["#new"] + d.func.withHelp("`obj` is a shorthand for `object.new`"), + obj:: self.object.new, + + "#func": d.obj("Utilities for documenting Jsonnet methods (functions of objects)"), + func:: { + "#new": d.fn("new creates a new function, optionally with description and arguments", [d.arg("help", d.T.string), d.arg("args", d.T.array)]), + new(help='', args=[]):: { 'function': { + help: help, + args: args, + } }, + + "#withHelp": d.fn("The `withHelp` modifier overrides the help text of that function", [d.arg("help", d.T.string)]), + withHelp(help):: {'function'+: { + help: help, + }} + }, + + "#fn": self.func["#new"] + d.func.withHelp("`fn` is a shorthand for `func.new`"), + fn:: self.func.new, + + "#argument": d.obj("Utilities for creating function arguments"), + argument:: { + "#new": d.fn("new creates a new function argument, taking the name, the type and optionally a default value", [d.arg("name", d.T.string), d.arg("type", d.T.string), d.arg("default", d.T.any)]), + new(name, type, default=null): { + name: name, + type: type, + default: default, + }, + }, + "#arg": self.argument["#new"] + self.func.withHelp("`arg` is a shorthand for `argument.new`"), + arg:: self.argument.new, + + // T contains constants for the Jsonnet types + T:: { + string: 'string', + number: 'number', + bool: 'bool', + object: 'object', + array: 'array', + any: 'any', + func: 'function', + }, +} diff --git a/main.go b/main.go index 4cdc5c5..d1f1f2b 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ type Doc struct { Import string `json:"import"` Help string `json:"help"` - API Object `json:"api"` + API Fields `json:"api"` } func main() { @@ -78,6 +78,28 @@ type Field struct { Value *Value `json:"value,omitempty"` } +func (o *Field) UnmarshalJSON(data []byte) error { + type fake Field + + var f fake + if err := json.Unmarshal(data, &f); err != nil { + return err + } + + switch { + case f.Function != nil: + o.Function = f.Function + case f.Object != nil: + o.Object = f.Object + case f.Value != nil: + o.Value = f.Value + default: + return errors.New("field has no value") + } + + return nil +} + func (o Field) MarshalJSON() ([]byte, error) { if o.Function == nil && o.Object == nil && o.Value == nil { return nil, errors.New("field has no value") diff --git a/render.go b/render.go index 719265e..e82302c 100644 --- a/render.go +++ b/render.go @@ -37,7 +37,9 @@ var objTmpl = strings.Replace(` {{ .Help }} `, "ยด", "`", -1) -type obj Object +type obj struct { + Object +} func (o obj) Render() string { t := template.Must(template.New("").Parse(objTmpl)) @@ -48,7 +50,9 @@ func (o obj) Render() string { return buf.String() } -type fn Function +type fn struct { + Function +} func (f fn) Params() string { args := make([]string, 0, len(f.Args)) @@ -90,8 +94,8 @@ func render(d Doc) (string, error) { Name: d.Name, Import: d.Import, Help: d.Help, - Index: buildIndex(d.API.Fields, 0), - Fields: renderables(d.API.Fields, ""), + Index: buildIndex(d.API, 0), + Fields: renderables(d.API, ""), }); err != nil { return "", err } @@ -118,11 +122,11 @@ func renderables(fields map[string]Field, prefix string) []Renderable { for _, f := range fields { switch { case f.Function != nil: - fnc := fn(*f.Function) + fnc := fn{*f.Function} fnc.Name = strings.TrimPrefix(prefix+"."+fnc.Name, ".") rs = append(rs, fnc) case f.Object != nil: - o := obj(*f.Object) + o := obj{*f.Object} o.Name = strings.TrimPrefix(prefix+"."+o.Name, ".") rs = append(rs, o) @@ -153,7 +157,7 @@ func buildIndex(fields map[string]Field, level int) []indexElem { func indexLine(f Field) string { switch { case f.Function != nil: - return "fn " + fn(*f.Function).Signature() + return "fn " + fn{*f.Function}.Signature() case f.Object != nil: return fmt.Sprintf("obj %s", f.Object.Name) }