-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(render): add render command for executing templates against data…
…sets Pumped about this one. I've realized we can get a long way by just executing templates against qri datasets, which have a rigid structure that makes templating much easier. So with that, I'd like to introduce a new command: `qri render`. `qri render` accepts a dataset name, executes a HTML template, using the specified dataset to populate template data. It has a few features: * render uses the `template/html` package from golang. users of the Hugo static site generator will feel at home with this syntax * users can provide custom templates with the --template argument * if no template is specified, qri uses a default "stock" template that is resolved over IPFS * this stock template is updated using IPNS, similar to the way we distribute app updates * template fetching falls back to a P2P gateway over HTTP if configured correctly * render automatically loads data using the same logic as the `qri data` command, accepting --limit, --offset, and --all params to control data loading * writes to stdout by default, to a file with --output flag To get this to work I had to introduce some other new stuff: * render section in config: this holds the place to lookup default template IPNS addresses * config.P2P.HTTPGatewayAddr: url of a gateway to resolve content-addressed hashes with qri isn't connected to the p2p network This opens up a whole world of possiblities, users can use `qri render` to create custom representations of data that suites their needs, and leverage qri to generate these assets as they go Limitations: * currently the template must be loaded as a single file, in the future we should add support for partials & stuff * currently HTML templates only. I've added a "TemplateFormat" param that currently does nothing, but in the future we can use this to expand to other common template output formats (raw text, PDF?)
- Loading branch information
Showing
16 changed files
with
564 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
|
||
"github.com/qri-io/qri/core" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// renderCmd represents the render command | ||
var renderCmd = &cobra.Command{ | ||
Use: "render", | ||
Short: "execute a template against a dataset", | ||
Long: `the most common use for render is to generate html from a qri dataset`, | ||
Example: ` render a dataset called me/schools: | ||
$ qri render -o=schools.html me/schools | ||
render a dataset with a custom template: | ||
$ qri render --template=template.html me/schools`, | ||
Annotations: map[string]string{ | ||
"group": "dataset", | ||
}, | ||
PreRun: func(cmd *cobra.Command, args []string) { | ||
loadConfig() | ||
}, | ||
Args: cobra.MinimumNArgs(1), | ||
Run: func(cmd *cobra.Command, args []string) { | ||
var template []byte | ||
|
||
fp, err := cmd.Flags().GetString("template") | ||
ExitIfErr(err) | ||
out, err := cmd.Flags().GetString("output") | ||
ExitIfErr(err) | ||
limit, err := cmd.Flags().GetInt("limit") | ||
ExitIfErr(err) | ||
offset, err := cmd.Flags().GetInt("offset") | ||
ExitIfErr(err) | ||
all, err := cmd.Flags().GetBool("all") | ||
ExitIfErr(err) | ||
|
||
req, err := renderRequests(false) | ||
ExitIfErr(err) | ||
|
||
if fp != "" { | ||
template, err = ioutil.ReadFile(fp) | ||
ExitIfErr(err) | ||
} | ||
|
||
p := &core.RenderParams{ | ||
Ref: args[0], | ||
Template: template, | ||
TemplateFormat: "html", | ||
All: all, | ||
Limit: limit, | ||
Offset: offset, | ||
} | ||
|
||
res := []byte{} | ||
err = req.Render(p, &res) | ||
ExitIfErr(err) | ||
|
||
if out == "" { | ||
fmt.Print(string(res)) | ||
} else { | ||
ioutil.WriteFile(out, res, 0777) | ||
} | ||
}, | ||
} | ||
|
||
func init() { | ||
renderCmd.Flags().StringP("template", "t", "", "path to template file") | ||
renderCmd.Flags().StringP("output", "o", "", "path to write output file") | ||
renderCmd.Flags().BoolP("all", "a", false, "read all dataset entries (overrides limit, offest)") | ||
renderCmd.Flags().IntP("limit", "l", 50, "max number of records to read") | ||
renderCmd.Flags().IntP("offset", "s", 0, "number of records to skip") | ||
|
||
RootCmd.AddCommand(renderCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package config | ||
|
||
import "github.com/qri-io/jsonschema" | ||
|
||
// Render configures the qri render command | ||
type Render struct { | ||
// TemplateUpdateAddress is currently an IPNS location to check for updates. api.Server starts | ||
// this address is checked, and if the hash there differs from DefaultTemplateHash, it'll use that instead | ||
TemplateUpdateAddress string `json:"templateUpdateAddress"` | ||
// DefaultTemplateHash is a hash of the compiled template | ||
// this is fetched and replaced via dnslink when the render server starts | ||
// the value provided here is just a sensible fallback for when dnslink lookup fails, | ||
// pointing to a known prior version of the the render | ||
DefaultTemplateHash string `json:"defaultTemplateHash"` | ||
} | ||
|
||
// DefaultRender creates a new default Render configuration | ||
func DefaultRender() *Render { | ||
return &Render{ | ||
TemplateUpdateAddress: "/ipns/defaulttmpl.qri.io", | ||
DefaultTemplateHash: "/ipfs/QmeqeRTf2Cvkqdx4xUdWi1nJB2TgCyxmemsL3H4f1eTBaw", | ||
} | ||
} | ||
|
||
// Validate validates all fields of render returning all errors found. | ||
func (cfg Render) Validate() error { | ||
schema := jsonschema.Must(`{ | ||
"$schema": "http://json-schema.org/draft-06/schema#", | ||
"title": "Render", | ||
"description": "Render for the render", | ||
"type": "object", | ||
"required": ["templateUpdateAddress", "defaultTemplateHash"], | ||
"properties": { | ||
"templateUpdateAddress": { | ||
"description": "address to check for app updates", | ||
"type": "string" | ||
}, | ||
"defaultTemplateHash": { | ||
"description": "A hash of the compiled render. This is fetched and replaced via dsnlink when the render server starts. The value provided here is just a sensible fallback for when dnslink lookup fails.", | ||
"type": "string" | ||
} | ||
} | ||
}`) | ||
return validate(schema, &cfg) | ||
} | ||
|
||
// Copy returns a deep copy of the Render struct | ||
func (cfg *Render) Copy() *Render { | ||
res := &Render{ | ||
TemplateUpdateAddress: cfg.TemplateUpdateAddress, | ||
DefaultTemplateHash: cfg.DefaultTemplateHash, | ||
} | ||
return res | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package config | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestRenderValidate(t *testing.T) { | ||
err := DefaultRender().Validate() | ||
if err != nil { | ||
t.Errorf("error validating default render: %s", err) | ||
} | ||
} | ||
|
||
func TestRenderCopy(t *testing.T) { | ||
cases := []struct { | ||
render *Render | ||
}{ | ||
{DefaultRender()}, | ||
} | ||
for i, c := range cases { | ||
cpy := c.render.Copy() | ||
if !reflect.DeepEqual(cpy, c.render) { | ||
t.Errorf("Render Copy test case %v, render structs are not equal: \ncopy: %v, \noriginal: %v", i, cpy, c.render) | ||
continue | ||
} | ||
cpy.DefaultTemplateHash = "foo" | ||
if reflect.DeepEqual(cpy, c.render) { | ||
t.Errorf("Render Copy test case %v, editing one render struct should not affect the other: \ncopy: %v, \noriginal: %v", i, cpy, c.render) | ||
continue | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,3 +23,4 @@ api: null | |
webapp: null | ||
rpc: null | ||
logging: null | ||
render: null |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.