feat: ignore

Implements ignoring certain keys right in Jsonnet, by adding:

  {
    '#foo': "ignore",
    foo: {}
  }

This is especially useful for objects that include `self` references,
which might otherwise recurse infinitely
This commit is contained in:
sh0rez 2020-05-12 22:48:02 +02:00
parent 14e9fc8f3f
commit 66a158a8f2
No known key found for this signature in database
GPG key ID: 87C71DF9F8181FF1
7 changed files with 85 additions and 46 deletions

View file

@ -12,6 +12,8 @@ local lib = {
local aux(old, key) = local aux(old, key) =
if !std.isObject(pkg[key]) then if !std.isObject(pkg[key]) then
old old
else if std.objectHasAll(pkg, '#' + key) && pkg['#' + key] == 'ignore' then
old
else if std.startsWith(key, '#') then else if std.startsWith(key, '#') then
old { [key]: pkg[key] } old { [key]: pkg[key] }
else if self.scan(pkg[key]) then else if self.scan(pkg[key]) then
@ -21,4 +23,5 @@ local lib = {
std.foldl(aux, std.objectFieldsAll(pkg), {}), std.foldl(aux, std.objectFieldsAll(pkg), {}),
}; };
lib.load(std.extVar('main')) lib.load(std.extVar('main'))

45
main.go
View file

@ -15,33 +15,52 @@ func main() {
log.SetFlags(0) log.SetFlags(0)
root := &cli.Command{ root := &cli.Command{
Use: "docsonnet", Use: "docsonnet <file>",
Short: "Utility to parse and transform Jsonnet code that uses the docsonnet extension", Short: "Utility to parse and transform Jsonnet code that uses the docsonnet extension",
Args: cli.ArgsExact(1),
} }
dir := root.Flags().StringP("output", "o", "docs", "directory to write the .md files to") dir := root.Flags().StringP("output", "o", "docs", "directory to write the .md files to")
outputMd := root.Flags().Bool("md", true, "render as markdown files")
outputJSON := root.Flags().Bool("json", false, "print loaded docsonnet as JSON") outputJSON := root.Flags().Bool("json", false, "print loaded docsonnet as JSON")
outputRaw := root.Flags().Bool("raw", false, "don't transform, dump raw eval result")
urlPrefix := root.Flags().String("urlPrefix", "/", "url-prefix for frontmatter")
root.Run = func(cmd *cli.Command, args []string) error { root.Run = func(cmd *cli.Command, args []string) error {
file := args[0] file := args[0]
switch { log.Println("Extracting from Jsonnet")
case *outputJSON: data, err := docsonnet.Extract(file)
model, err := docsonnet.Load(file)
if err != nil { if err != nil {
return err log.Fatalln("Extracting:", err)
} }
data, err := json.MarshalIndent(model, "", " ") if *outputRaw {
if err != nil {
return err
}
fmt.Println(string(data)) fmt.Println(string(data))
case *outputMd: return nil
return render.To(file, *dir)
} }
log.Println("Transforming to docsonnet model")
pkg, err := docsonnet.Transform(data)
if err != nil {
log.Fatalln("Transforming:", err)
}
if *outputJSON {
data, err := json.MarshalIndent(pkg, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}
log.Println("Rendering markdown")
n, err := render.To(*pkg, *dir, render.Opts{
URLPrefix: *urlPrefix,
})
if err != nil {
log.Fatalln("Rendering:", err)
}
log.Printf("Success! Rendered %v packages from '%s' to '%s'", n, file, *dir)
return nil return nil
} }

View file

@ -11,7 +11,7 @@ import (
// Data assumptions: // Data assumptions:
// - only map[string]interface{} and fields // - only map[string]interface{} and fields
// - fields (#...) coming first // - fields (#...) coming first
func fastLoad(d DS) Package { func fastLoad(d ds) Package {
pkg := d.Package() pkg := d.Package()
pkg.API = make(Fields) pkg.API = make(Fields)
@ -34,7 +34,7 @@ func fastLoad(d DS) Package {
// non-docsonnet // non-docsonnet
// subpackage? // subpackage?
if _, ok := f["#"]; ok { if _, ok := f["#"]; ok {
p := fastLoad(DS(f)) p := fastLoad(ds(f))
pkg.Sub[p.Name] = p pkg.Sub[p.Name] = p
continue continue
} }
@ -94,7 +94,7 @@ func loadField(name string, field map[string]interface{}, parent map[string]inte
return loadObj(name, iobj.(map[string]interface{}), parent) return loadObj(name, iobj.(map[string]interface{}), parent)
} }
panic("field lacking {function | object}") panic(fmt.Sprintf("field %s lacking {function | object}", name))
} }
func loadFn(name string, msi map[string]interface{}) Field { func loadFn(name string, msi map[string]interface{}) Field {
@ -161,9 +161,9 @@ func loadObj(name string, msi map[string]interface{}, parent map[string]interfac
return Field{Object: &obj} return Field{Object: &obj}
} }
type DS map[string]interface{} type ds map[string]interface{}
func (d DS) Package() Package { func (d ds) Package() Package {
hash, ok := d["#"] hash, ok := d["#"]
if !ok { if !ok {
log.Fatalln("Package declaration missing") log.Fatalln("Package declaration missing")

View file

@ -10,8 +10,22 @@ import (
"github.com/markbates/pkger" "github.com/markbates/pkger"
) )
// Load extracts docsonnet data from the given Jsonnet document // Load extracts and transforms the docsonnet data in `filename`, returning the
// top level docsonnet package.
func Load(filename string) (*Package, error) { func Load(filename string) (*Package, error) {
data, err := Extract(filename)
if err != nil {
return nil, err
}
return Transform([]byte(data))
}
// Extract parses the Jsonnet file at `filename`, extracting all docsonnet related
// information, exactly as they appear in Jsonnet. Keep in mind this
// representation is usually not suitable for any use, use `Transform` to
// convert it to the familiar docsonnet data model.
func Extract(filename string) ([]byte, error) {
// get load.libsonnet from embedded data // get load.libsonnet from embedded data
file, err := pkger.Open("/load.libsonnet") file, err := pkger.Open("/load.libsonnet")
if err != nil { if err != nil {
@ -33,15 +47,18 @@ func Load(filename string) (*Package, error) {
// invoke load.libsonnet // invoke load.libsonnet
vm.ExtCode("main", fmt.Sprintf(`(import "%s")`, filename)) vm.ExtCode("main", fmt.Sprintf(`(import "%s")`, filename))
log.Println("Evaluating Jsonnet")
data, err := vm.EvaluateSnippet("load.libsonnet", string(load)) data, err := vm.EvaluateSnippet("load.libsonnet", string(load))
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Println("Transforming result") return []byte(data), nil
// parse the result }
var d DS
// Transform converts the raw result of `Extract` to the actual docsonnet object
// model `*docsonnet.Package`.
func Transform(data []byte) (*Package, error) {
var d ds
if err := json.Unmarshal([]byte(data), &d); err != nil { if err := json.Unmarshal([]byte(data), &d); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View file

@ -2,31 +2,26 @@ package render
import ( import (
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"github.com/sh0rez/docsonnet/pkg/docsonnet" "github.com/sh0rez/docsonnet/pkg/docsonnet"
) )
func To(api, out string) error { func To(pkg docsonnet.Package, dir string, opts Opts) (int, error) {
pkg, err := docsonnet.Load(api) if err := os.MkdirAll(dir, os.ModePerm); err != nil {
if err != nil { return 0, err
return err
} }
if err := os.MkdirAll(out, os.ModePerm); err != nil { data := Render(pkg, opts)
return err
}
log.Println("Rendering .md files") n := 0
data := Render(*pkg)
for k, v := range data { for k, v := range data {
if err := ioutil.WriteFile(filepath.Join(out, k), []byte(v), 0644); err != nil { if err := ioutil.WriteFile(filepath.Join(dir, k), []byte(v), 0644); err != nil {
return err return n, err
} }
n++
} }
log.Printf("Success! Rendered %v packages from '%s' to '%s'", len(data), api, out) return n, nil
return nil
} }

View file

@ -2,6 +2,7 @@ package render
import ( import (
"fmt" "fmt"
"path"
"sort" "sort"
"strings" "strings"
@ -10,14 +11,18 @@ import (
"github.com/sh0rez/docsonnet/pkg/slug" "github.com/sh0rez/docsonnet/pkg/slug"
) )
func Render(pkg docsonnet.Package) map[string]string { type Opts struct {
return render(pkg, nil, true) URLPrefix string
} }
func render(pkg docsonnet.Package, parents []string, root bool) map[string]string { func Render(pkg docsonnet.Package, opts Opts) map[string]string {
link := "/" + strings.Join(append(parents, pkg.Name), "/") return render(pkg, nil, true, opts.URLPrefix)
}
func render(pkg docsonnet.Package, parents []string, root bool, urlPrefix string) map[string]string {
link := path.Join("/", urlPrefix, strings.Join(append(parents, pkg.Name), "/"))
if root { if root {
link = "/" link = path.Join("/", urlPrefix)
} }
// head // head
@ -83,7 +88,7 @@ func render(pkg docsonnet.Package, parents []string, root bool) map[string]strin
if root { if root {
path = parents path = parents
} }
got := render(s, path, false) got := render(s, path, false, urlPrefix)
for k, v := range got { for k, v := range got {
out[k] = v out[k] = v
} }

File diff suppressed because one or more lines are too long