~evanj/cms

da76b849bf78b932c60d245163df71b3677e37e4 — Evan M Jones 8 months ago ae6029e
WIP(*): Project init.
40 files changed, 1055 insertions(+), 1122 deletions(-)

M cms.go
A internal/c/content/content.go
A internal/c/content/content_test.go
M internal/c/contenttype/contenttype.go
M internal/m/content/content.go
M internal/m/value/value.go
A internal/s/db/content.go
M internal/s/db/db.go
A internal/s/tmpl/html/content.html
M internal/s/tmpl/html/contenttype.html
M internal/s/tmpl/html/index.html
M internal/s/tmpl/html/space.html
M internal/s/tmpl/tmpls_embed.go
A pkg/e3/e3.go
A pkg/e3/e3_test.go
D vendor/github.com/bmizerany/assert/.gitignore
D vendor/github.com/bmizerany/assert/README.md
D vendor/github.com/bmizerany/assert/assert.go
A vendor/github.com/go-playground/assert/v2/.gitignore
A vendor/github.com/go-playground/assert/v2/.travis.yml
R vendor/github.com/kr/pretty/{License => und/assert/v2/LICENSE}
A vendor/github.com/go-playground/assert/v2/README.md
A vendor/github.com/go-playground/assert/v2/assert.go
A vendor/github.com/go-playground/assert/v2/doc.go
A vendor/github.com/go-playground/assert/v2/go.mod
D vendor/github.com/kr/pretty/.gitignore
D vendor/github.com/kr/pretty/Readme
D vendor/github.com/kr/pretty/diff.go
D vendor/github.com/kr/pretty/formatter.go
D vendor/github.com/kr/pretty/go.mod
D vendor/github.com/kr/pretty/go.sum
D vendor/github.com/kr/pretty/pretty.go
D vendor/github.com/kr/pretty/zero.go
D vendor/github.com/kr/text/License
D vendor/github.com/kr/text/Readme
D vendor/github.com/kr/text/doc.go
D vendor/github.com/kr/text/go.mod
D vendor/github.com/kr/text/indent.go
D vendor/github.com/kr/text/wrap.go
M vendor/modules.txt
M cms.go => cms.go +17 -0
@@ 7,11 7,13 @@ import (
	"os"
	"strings"

	"git.sr.ht/~evanj/cms/internal/c/content"
	"git.sr.ht/~evanj/cms/internal/c/contenttype"
	"git.sr.ht/~evanj/cms/internal/c/ping"
	"git.sr.ht/~evanj/cms/internal/c/space"
	"git.sr.ht/~evanj/cms/internal/c/user"
	"git.sr.ht/~evanj/cms/internal/s/db"
	"git.sr.ht/~evanj/cms/pkg/e3"
	"git.sr.ht/~evanj/security"
)



@@ 27,6 29,7 @@ var (

type App struct {
	log         *log.Logger
	content     http.Handler
	contenttype http.Handler
	space       http.Handler
	user        http.Handler


@@ 55,6 58,9 @@ func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	case "contenttype":
		a.contenttype.ServeHTTP(w, r)
		return
	case "content":
		a.content.ServeHTTP(w, r)
		return
	}

	http.NotFound(w, r)


@@ 77,8 83,19 @@ func init() {
		applogger.Fatal(err)
	}

	fs := e3.New(
		os.Getenv("E3_USER"),
		os.Getenv("E3_PASS"),
		os.Getenv("E3_URL"),
	)

	app = &App{
		log: applogger,
		content: content.New(
			log.New(w, "[cms:content] ", 0),
			db,
			fs,
		),
		contenttype: contenttype.New(
			log.New(w, "[cms:contenttype] ", 0),
			db,

A internal/c/content/content.go => internal/c/content/content.go +167 -0
@@ 0,0 1,167 @@
package content

import (
	"fmt"
	"log"
	"mime/multipart"
	"net/http"
	"strings"

	"git.sr.ht/~evanj/cms/internal/c"
	"git.sr.ht/~evanj/cms/internal/m/content"
	"git.sr.ht/~evanj/cms/internal/m/contenttype"
	"git.sr.ht/~evanj/cms/internal/m/space"
	"git.sr.ht/~evanj/cms/internal/m/user"
	"git.sr.ht/~evanj/cms/internal/s/db"
	"git.sr.ht/~evanj/cms/internal/s/tmpl"
)

var (
	contentHTML = tmpl.MustParse("html/content.html")
)

type Content struct {
	*c.Controller
	log *log.Logger
	db  dber
	e3  e3er
}

type dber interface {
	UserGetFromToken(token string) (user.User, error)
	SpaceGet(user user.User, spaceID string) (space.Space, error)
	ContentTypeGet(space space.Space, contenttypeID string) (contenttype.ContentType, error)
	ContentNew(space space.Space, ct contenttype.ContentType, params []db.ContentNewParam) (content.Content, error)
	ContentGet(space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error)
	ContentPerContentType(space space.Space, ct contenttype.ContentType, page int) ([]content.Content, error)
}

type e3er interface {
	Upload(file multipart.File, hdr *multipart.FileHeader) (url string, err error)
}

func New(log *log.Logger, db dber, e3 e3er) *Content {
	return &Content{
		c.New(log, db),
		log,
		db,
		e3,
	}
}

func (c *Content) create(w http.ResponseWriter, r *http.Request) {
	spaceID := r.FormValue("space")
	contenttypeID := r.FormValue("contenttype")

	user, err := c.GetCookieUser(w, r)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, "must be logged in to create content")
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, "failed to find required space")
		return
	}

	ct, err := c.db.ContentTypeGet(space, contenttypeID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, "failed to find required contenttype")
		return
	}

	var params []db.ContentNewParam
	for key := range r.Form {
		if key == "space" || key == "contenttype" {
			continue
		}
		params = append(params, db.ContentNewParam{
			Name:  key,
			Value: r.FormValue(key),
		})
	}

	// TODO: Upload concurrently.
	_, _, _ = r.FormFile("") // Dummy read, loads internal r.MulitpartForm state.
	for key := range r.MultipartForm.File {
		file, header, err := r.FormFile(key)
		if err != nil {
			c.Error(w, r, http.StatusInternalServerError, "failed to retreive file")
			return
		}

		url, err := c.e3.Upload(file, header)
		if err != nil {
			c.log.Println("failed to upload file", err)
			c.Error(w, r, http.StatusInternalServerError, "failed to upload file")
			return
		}

		params = append(params, db.ContentNewParam{
			Name:  key,
			Value: url,
		})
	}

	content, err := c.db.ContentNew(space, ct, params)
	if err != nil {
		c.log.Println(err)
		c.Error(w, r, http.StatusInternalServerError, err.Error())
		return
	}

	url := fmt.Sprintf("/content/%s/%s/%s", space.ID(), ct.ID(), content.ID())
	c.log.Println("successfully created new content for user", user.Name(), "in space", space.Name(), "for contenttype", ct.Name(), "redirecting to", url)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func (c *Content) serve(w http.ResponseWriter, r *http.Request, spaceID, contenttypeID, contentID string) {
	user, err := c.GetCookieUser(w, r)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, "must be logged in to create content")
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, "failed to find required space")
		return
	}

	ct, err := c.db.ContentTypeGet(space, contenttypeID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, "failed to find required contenttype")
		return
	}

	content, err := c.db.ContentGet(space, ct, contentID)
	if err != nil {
		c.log.Println(err)
		c.Error(w, r, http.StatusInternalServerError, "failed to find desired content")
		return
	}

	c.HTML(w, r, contentHTML, map[string]interface{}{
		"User":        user,
		"Space":       space,
		"ContentType": ct,
		"Content":     content,
	})
}

func (c *Content) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/content/new":
		c.create(w, r)
		return
	}

	parts := strings.Split(r.URL.Path, "/")
	if len(parts) > 4 {
		c.serve(w, r, parts[2], parts[3], parts[4])
		return
	}

	http.NotFound(w, r)
}

A internal/c/content/content_test.go => internal/c/content/content_test.go +1 -0
@@ 0,0 1,1 @@
package content_test

M internal/c/contenttype/contenttype.go => internal/c/contenttype/contenttype.go +1 -1
@@ 104,7 104,7 @@ func (c *ContentType) serve(w http.ResponseWriter, r *http.Request, spaceID, con

	space, err := c.db.SpaceGet(user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, "failed to find desired space")
		c.Error(w, r, http.StatusInternalServerError, "failed to find required space")
		return
	}


M internal/m/content/content.go => internal/m/content/content.go +0 -1
@@ 8,6 8,5 @@ import (
type Content interface {
	ID() string
	Type() contenttype.ContentType
	Name() string
	Values() []value.Value
}

M internal/m/value/value.go => internal/m/value/value.go +1 -1
@@ 5,5 5,5 @@ import "git.sr.ht/~evanj/cms/internal/m/valuetype"
type Value interface {
	ID() string
	Type() valuetype.ValueType
	Value() interface{}
	Value() string
}

A internal/s/db/content.go => internal/s/db/content.go +181 -0
@@ 0,0 1,181 @@
package db

import (
	"fmt"

	"git.sr.ht/~evanj/cms/internal/m/content"
	"git.sr.ht/~evanj/cms/internal/m/contenttype"
	"git.sr.ht/~evanj/cms/internal/m/space"
	"git.sr.ht/~evanj/cms/internal/m/value"
	"git.sr.ht/~evanj/cms/internal/m/valuetype"
)

type Content struct {
	id            string
	contenttypeID string

	contenttype contenttype.ContentType
	values      []ContentValue
}

type ContentValue struct {
	id          string
	valuetypeID string
	value       string

	valuetype valuetype.ValueType
}

var (
	queryContentNew        = `INSERT INTO cms_content (CONTENTTYPE_ID) VALUES (?);`
	queryContentGetByID    = `SELECT ID, CONTENTTYPE_ID FROM cms_content WHERE ID = ?;`
	queryValueNew          = `INSERT INTO cms_value (CONTENT_ID, VALUETYPE_ID, VALUE) VALUES (?, (SELECT VALUETYPE_ID FROM cms_contenttype_to_valuetype WHERE CONTENTTYPE_ID = ? AND NAME = ? LIMIT 1), ?);`
	queryValueGetByID      = `SELECT ID, VALUETYPE_ID, VALUE FROM cms_value WHERE ID = ?;`
	queryValueGetByContent = `SELECT ID, VALUETYPE_ID, VALUE FROM cms_value WHERE CONTENT_ID = ?;`
)

type ContentNewParam struct {
	Name  string
	Value string
}

func (db *DB) ContentNew(space space.Space, ct contenttype.ContentType, params []ContentNewParam) (content.Content, error) {
	res, err := db.Exec(queryContentNew, ct.ID())
	if err != nil {
		db.log.Println("db.Exec", err)
		return nil, fmt.Errorf("failed to create new content attached to contenttype of '%s'", ct.Name())
	}

	contentID, err := res.LastInsertId()
	if err != nil {
		db.log.Println("res.LastInsertId", err)
		return nil, fmt.Errorf("failed to read new content attached to contenttype of '%s'", ct.Name())
	}

	var content Content
	if err := db.QueryRow(queryContentGetByID, contentID).Scan(&content.id, &content.contenttypeID); err != nil {
		db.log.Println("db.QueryRow", err)
		return nil, fmt.Errorf("failed to find space created")
	}

	for _, item := range params {
		res, err := db.Exec(queryValueNew, contentID, ct.ID(), item.Name, item.Value)
		if err != nil {
			// TODO cleanup orphan content.
			db.log.Println("db.Exec", err)
			return nil, fmt.Errorf("failed to attach field value of content")
		}

		valueID, err := res.LastInsertId()
		if err != nil {
			db.log.Println("res.LastInsertId", err)
			return nil, fmt.Errorf("failed to read new field value of content")
		}

		var value ContentValue
		if err := db.QueryRow(queryValueGetByID, valueID).Scan(&value.id, &value.valuetypeID, &value.value); err != nil {
			db.log.Println("db.QueryRow", err)
			return nil, fmt.Errorf("failed to find space created")
		}

		found := false
		for _, field := range ct.Fields() {
			if field.ID() == value.valuetypeID {
				found = true
				value.valuetype = field
			}
		}

		if !found {
			return nil, fmt.Errorf("failed to find value's valuetype")
		}

		content.values = append(content.values, value)
	}

	if len(ct.Fields()) != len(content.values) {
		return nil, fmt.Errorf("failed to create all values")
	}

	content.contenttype = ct
	return &content, nil
}

func (db *DB) ContentPerContentType(space space.Space, ct contenttype.ContentType, page int) ([]content.Content, error) {
	var ret []content.Content
	return ret, fmt.Errorf("TODO")
}

func (db *DB) ContentGet(space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error) {
	var content Content
	if err := db.QueryRow(queryContentGetByID, contentID).Scan(&content.id, &content.contenttypeID); err != nil {
		db.log.Println("db.QueryRow", err)
		return nil, fmt.Errorf("failed to find space")
	}

	rows, err := db.Query(queryValueGetByContent, content.ID())
	if err != nil {
		db.log.Println(err)
		return nil, fmt.Errorf("failed to find values(s)")
	}
	for rows.Next() {
		var value ContentValue
		if err := rows.Scan(&value.id, &value.valuetypeID, &value.value); err != nil {
			return nil, fmt.Errorf("failed to scan values(s)")
		}

		found := false
		for _, field := range ct.Fields() {
			if field.ID() == value.valuetypeID {
				found = true
				value.valuetype = field
			}
		}

		if !found {
			return nil, fmt.Errorf("failed to find value's valuetype")
		}

		content.values = append(content.values, value)
	}

	if len(ct.Fields()) != len(content.values) {
		return nil, fmt.Errorf("failed to find all values")
	}

	content.contenttype = ct
	return &content, nil
}

func (c *Content) ID() string {
	return c.id
}

func (c *Content) Type() contenttype.ContentType {
	return c.contenttype
}

func (c *Content) Values() []value.Value {
	var ret []value.Value
	for _, item := range c.values {
		ret = append(ret, &ContentValue{
			item.id,
			item.valuetypeID,
			item.value,
			item.valuetype,
		})
	}
	return ret
}

func (c *ContentValue) ID() string {
	return c.id
}

func (c *ContentValue) Type() valuetype.ValueType {
	return c.valuetype
}

func (c *ContentValue) Value() string {
	return c.value
}

M internal/s/db/db.go => internal/s/db/db.go +21 -0
@@ 85,6 85,7 @@ func (db *DB) EnsureSetup() error {
	`)

	// contenttype to valuetype
	// TODO: Make name + contenttype_id unique.
	_, _ = db.Exec(`
		CREATE TABLE cms_contenttype_to_valuetype (
			ID INTEGER PRIMARY KEY AUTOINCREMENT,


@@ 105,6 106,26 @@ func (db *DB) EnsureSetup() error {
		);
	`)

	// content
	_, _ = db.Exec(`
		CREATE TABLE cms_content (
			ID INTEGER PRIMARY KEY AUTOINCREMENT,
			CONTENTTYPE_ID INTEGER NOT NULL,
			FOREIGN KEY(CONTENTTYPE_ID) REFERENCES cms_contenttype(ID)
		);
	`)

	// value
	_, _ = db.Exec(`
		CREATE TABLE cms_value (
			ID INTEGER PRIMARY KEY AUTOINCREMENT,
			VALUE TEXT NOT NULL,
			CONTENT_ID INTEGER NOT NULL,
			VALUETYPE_ID INTEGER NOT NULL,
			FOREIGN KEY(VALUETYPE_ID) REFERENCES cms_valuetype(ID)
		);
	`)

	// Only valuetypes cms supports.
	_, _ = db.Exec(`INSERT INTO cms_valuetype (VALUE) values (?);`, valuetype.StringSmall)
	_, _ = db.Exec(`INSERT INTO cms_valuetype (VALUE) values (?);`, valuetype.StringBig)

A internal/s/tmpl/html/content.html => internal/s/tmpl/html/content.html +59 -0
@@ 0,0 1,59 @@
<!DOCTYPE html>
<html lang=en>

<head>
  <meta charset="utf-8">
  <title>CMS | {{ .Space.Name }}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <style>
    main { max-width: 600px; margin: 0 auto; }
    form input, form textarea, form button { display: block; margin: 5px 0; }
    form textarea { width: 100%%; resize: vertical; min-height: 150px;}
    form div input, form div textarea, form div button { display: inline-block; }
    form > input { margin-top: 10px; }
  </style>

  <main>
    <header>
      <h1>CMS</h1>
      <p>A flexible CMS for everyone.</p>
    </header>
    <hr/>
    <article>

      <nav>
        <a href='/contenttype/{{ .Space.ID }}/{{ .ContentType.ID }}'>Back</a>
      </nav>

      <h2>{{ .Space.Name }}: {{ .ContentType.Name }}: {{ .Content.ID }}</h2>

      <form method=POST action='/content/update' enctype='multipart/form-data'>
        <legend>Create Content:</legend>
        <input required type=hidden name=space value="{{ .Space.ID }}" />
        <input required type=hidden name=contenttype value="{{ .ContentType.ID }}" />
        <input required type=hidden name=content value="{{ .Content.ID }}" />

        {{ range .Content.Values }}

          {{ .Type.Name }}
          {{ .Type.Type }}
          {{ .Value }}
          <br/>

        {{ end }}

        <input type=submit value=Go />
      </form>

    </article>
    <hr/>
    <footer>
      <center>© 2015-2020 Evan Jones</center>
    </footer>
  </main>
</body>

</html>

M internal/s/tmpl/html/contenttype.html => internal/s/tmpl/html/contenttype.html +39 -3
@@ 10,8 10,9 @@
<body>
  <style>
    main { max-width: 600px; margin: 0 auto; }
    form input, form button { display: block; margin: 5px 0; }
    form div input, form div button { display: inline-block; }
    form input, form textarea, form button { display: block; margin: 5px 0; }
    form textarea { width: 100%%; resize: vertical; min-height: 150px;}
    form div input, form div textarea, form div button { display: inline-block; }
    form > input { margin-top: 10px; }
  </style>



@@ 33,9 34,44 @@
        <legend>Create Content:</legend>
        <input required type=hidden name=space value="{{ .Space.ID }}" />
        <input required type=hidden name=contenttype value="{{ .ContentType.ID }}" />

        {{ range .ContentType.Fields }}
          <input required type=text name=name placeholder="{{ .Name }}" />

          {{ if eq .Type "StringSmall" }}
            <input required type=text name="{{ .Name }}" placeholder="{{ .Name }}" />
          {{ end }}

          {{ if eq .Type "StringBig" }}
            <textarea required type=text name="{{ .Name }}" placeholder="{{ .Name }}" ></textarea>
          {{ end }}

          {{ if eq .Type "InputHTML" }}
            TODO: HTML
            <textarea required type=text name="{{ .Name }}" placeholder="{{ .Name }}" ></textarea>
          {{ end }}

          {{ if eq .Type "InputMarkdown" }}
            TODO: Markdown
            <textarea required type=text name="{{ .Name }}" placeholder="{{ .Name }}" ></textarea>
          {{ end }}

          {{ if eq .Type "File" }}
            <input required type=file name="{{ .Name }}" multiple=false />
          {{ end }}

          {{ if eq .Type "Date" }}
            <input required type=date name="{{ .Name }}" placeholder="{{ .Name }}" />
          {{ end }}

          {{ if eq .Type "Reference" }}
            TODO: Reference
            <input required type=text name="{{ .Name }}" placeholder="{{ .Name }}" />
          {{ end }}



        {{ end }}

        <input type=submit value=Go />
      </form>


M internal/s/tmpl/html/index.html => internal/s/tmpl/html/index.html +1 -1
@@ 24,7 24,7 @@

      {{ if .User }}

        <p>Welcome back, {{ .User.Name }}.</p>
        <h2>Welcome back, {{ .User.Name }}.</h2>

        <p>Available Spaces:</p>


M internal/s/tmpl/html/space.html => internal/s/tmpl/html/space.html +1 -10
@@ 34,7 34,7 @@
        <input required type=hidden name=space value="{{ .Space.ID }}" />
        <input required type=text name=name placeholder="content type name" />
        <div id='first-fieldset'>
          <input required type=text name="field_name_1" placeholder="name field (for searchability)" />
          <input required type=text name="field_name_1" placeholder="try 'name' (for searchability)" />
          <select required name="field_type_1">
            <option disabled value>Field Type</option>
            <option selected value="StringSmall">String Small</option>


@@ 50,15 50,6 @@
        <input type=submit value=Go />
      </form>

      {{ if .ContentTypes }}
        <p>Create Content:</p>
        <p>TODO</p>
      {{ else }}
        <p>Create Content:</p>
        <p>You haven't created any content types yet. To begin creating content
        you must have at least one content type.</p>
      {{ end }}

      <p>Browse Content By Type:</p>
      {{ if .ContentTypes }}
        <ul>

M internal/s/tmpl/tmpls_embed.go => internal/s/tmpl/tmpls_embed.go +102 -14
@@ 7,6 7,67 @@ var tmpls map[string]string
func init() {
	tmpls = make(map[string]string)

	tmpls["html/content.html"] = `<!DOCTYPE html>
<html lang=en>

<head>
  <meta charset="utf-8">
  <title>CMS | {{ .Space.Name }}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <style>
    main { max-width: 600px; margin: 0 auto; }
    form input, form textarea, form button { display: block; margin: 5px 0; }
    form textarea { width: 100%; resize: vertical; min-height: 150px;}
    form div input, form div textarea, form div button { display: inline-block; }
    form > input { margin-top: 10px; }
  </style>

  <main>
    <header>
      <h1>CMS</h1>
      <p>A flexible CMS for everyone.</p>
    </header>
    <hr/>
    <article>

      <nav>
        <a href='/contenttype/{{ .Space.ID }}/{{ .ContentType.ID }}'>Back</a>
      </nav>

      <h2>{{ .Space.Name }}: {{ .ContentType.Name }}: {{ .Content.ID }}</h2>

      <form method=POST action='/content/update' enctype='multipart/form-data'>
        <legend>Create Content:</legend>
        <input required type=hidden name=space value="{{ .Space.ID }}" />
        <input required type=hidden name=contenttype value="{{ .ContentType.ID }}" />
        <input required type=hidden name=content value="{{ .Content.ID }}" />

        {{ range .Content.Values }}

          {{ .Type.Name }}
          {{ .Type.Type }}
          {{ .Value }}
          <br/>

        {{ end }}

        <input type=submit value=Go />
      </form>

    </article>
    <hr/>
    <footer>
      <center>© 2015-2020 Evan Jones</center>
    </footer>
  </main>
</body>

</html>
`

	tmpls["html/contenttype.html"] = `<!DOCTYPE html>
<html lang=en>



@@ 19,8 80,9 @@ func init() {
<body>
  <style>
    main { max-width: 600px; margin: 0 auto; }
    form input, form button { display: block; margin: 5px 0; }
    form div input, form div button { display: inline-block; }
    form input, form textarea, form button { display: block; margin: 5px 0; }
    form textarea { width: 100%; resize: vertical; min-height: 150px;}
    form div input, form div textarea, form div button { display: inline-block; }
    form > input { margin-top: 10px; }
  </style>



@@ 42,9 104,44 @@ func init() {
        <legend>Create Content:</legend>
        <input required type=hidden name=space value="{{ .Space.ID }}" />
        <input required type=hidden name=contenttype value="{{ .ContentType.ID }}" />

        {{ range .ContentType.Fields }}
          <input required type=text name=name placeholder="{{ .Name }}" />

          {{ if eq .Type "StringSmall" }}
            <input required type=text name="{{ .Name }}" placeholder="{{ .Name }}" />
          {{ end }}

          {{ if eq .Type "StringBig" }}
            <textarea required type=text name="{{ .Name }}" placeholder="{{ .Name }}" ></textarea>
          {{ end }}

          {{ if eq .Type "InputHTML" }}
            TODO: HTML
            <textarea required type=text name="{{ .Name }}" placeholder="{{ .Name }}" ></textarea>
          {{ end }}

          {{ if eq .Type "InputMarkdown" }}
            TODO: Markdown
            <textarea required type=text name="{{ .Name }}" placeholder="{{ .Name }}" ></textarea>
          {{ end }}

          {{ if eq .Type "File" }}
            <input required type=file name="{{ .Name }}" multiple=false />
          {{ end }}

          {{ if eq .Type "Date" }}
            <input required type=date name="{{ .Name }}" placeholder="{{ .Name }}" />
          {{ end }}

          {{ if eq .Type "Reference" }}
            TODO: Reference
            <input required type=text name="{{ .Name }}" placeholder="{{ .Name }}" />
          {{ end }}



        {{ end }}

        <input type=submit value=Go />
      </form>



@@ 93,7 190,7 @@ func init() {

      {{ if .User }}

        <p>Welcome back, {{ .User.Name }}.</p>
        <h2>Welcome back, {{ .User.Name }}.</h2>

        <p>Available Spaces:</p>



@@ 185,7 282,7 @@ func init() {
        <input required type=hidden name=space value="{{ .Space.ID }}" />
        <input required type=text name=name placeholder="content type name" />
        <div id='first-fieldset'>
          <input required type=text name="field_name_1" placeholder="name field (for searchability)" />
          <input required type=text name="field_name_1" placeholder="try 'name' (for searchability)" />
          <select required name="field_type_1">
            <option disabled value>Field Type</option>
            <option selected value="StringSmall">String Small</option>


@@ 201,15 298,6 @@ func init() {
        <input type=submit value=Go />
      </form>

      {{ if .ContentTypes }}
        <p>Create Content:</p>
        <p>TODO</p>
      {{ else }}
        <p>Create Content:</p>
        <p>You haven't created any content types yet. To begin creating content
        you must have at least one content type.</p>
      {{ end }}

      <p>Browse Content By Type:</p>
      {{ if .ContentTypes }}
        <ul>

A pkg/e3/e3.go => pkg/e3/e3.go +79 -0
@@ 0,0 1,79 @@
package e3

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"strings"
)

type E3 struct {
	user, pass, url string
}

func New(user, pass, url string) *E3 {
	return &E3{
		user,
		pass,
		url,
	}
}

// TODO: Everything is public by default at the moment. FIX THIS. Private by
// default and proxy requests on behalf of users.
func (e3 *E3) Upload(file multipart.File, hdr *multipart.FileHeader) (string, error) {
	var requestBody bytes.Buffer
	mpwriter := multipart.NewWriter(&requestBody)

	// File field.
	filewriter, err := mpwriter.CreateFormFile("data", hdr.Filename)
	if err != nil {
		return "", err
	}
	if _, err := io.Copy(filewriter, file); err != nil {
		return "", err
	}

	// Acces field (TODO: Remove).
	fieldwriter, err := mpwriter.CreateFormField("access")
	if err != nil {
		return "", err
	}
	_, err = fieldwriter.Write([]byte("public"))
	if err != nil {
		return "", err
	}
	if err := mpwriter.Close(); err != nil {
		return "", err
	}

	// Request as usual.
	// TODO: Add timeout.
	req, err := http.NewRequest("POST", e3.url, &requestBody)
	if err != nil {
		return "", err
	}

	req.SetBasicAuth(e3.user, e3.pass)
	req.Header.Set("Content-Type", mpwriter.FormDataContentType())

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("%s", strings.TrimSpace(string(bytes)))
	}

	return strings.TrimSpace(string(bytes)), nil
}

A pkg/e3/e3_test.go => pkg/e3/e3_test.go +1 -0
@@ 0,0 1,1 @@
package e3_test

D vendor/github.com/bmizerany/assert/.gitignore => vendor/github.com/bmizerany/assert/.gitignore +0 -7
@@ 1,7 0,0 @@
_go_.*
_gotest_.*
_obj
_test
_testmain.go
*.out
*.[568]

D vendor/github.com/bmizerany/assert/README.md => vendor/github.com/bmizerany/assert/README.md +0 -47
@@ 1,47 0,0 @@
# NO LONGER MAINTAINED - Just use Go's testing package.

# Assert (c) Blake Mizerany and Keith Rarick -- MIT LICENCE

## Assertions for Go tests

## Install

    $ go get github.com/bmizerany/assert

## Use

**point.go**

    package point

    type Point struct {
        x, y int
    }

**point_test.go**


    package point

    import (
        "testing"
        "github.com/bmizerany/assert"
    )

    func TestAsserts(t *testing.T) {
        p1 := Point{1, 1}
        p2 := Point{2, 1}

        assert.Equal(t, p1, p2)
    }

**output**
    $ go test
     --- FAIL: TestAsserts (0.00 seconds)
	 assert.go:15: /Users/flavio.barbosa/dev/stewie/src/point_test.go:12
         assert.go:24: ! X: 1 != 2
	 FAIL

## Docs

    http://github.com/bmizerany/assert

D vendor/github.com/bmizerany/assert/assert.go => vendor/github.com/bmizerany/assert/assert.go +0 -76
@@ 1,76 0,0 @@
package assert
// Testing helpers for doozer.

import (
	"github.com/kr/pretty"
	"reflect"
	"testing"
	"runtime"
	"fmt"
)

func assert(t *testing.T, result bool, f func(), cd int) {
	if !result {
		_, file, line, _ := runtime.Caller(cd + 1)
		t.Errorf("%s:%d", file, line)
		f()
		t.FailNow()
	}
}

func equal(t *testing.T, exp, got interface{}, cd int, args ...interface{}) {
	fn := func() {
		for _, desc := range pretty.Diff(exp, got) {
			t.Error("!", desc)
		}
		if len(args) > 0 {
			t.Error("!", " -", fmt.Sprint(args...))
		}
	}
	result := reflect.DeepEqual(exp, got)
	assert(t, result, fn, cd+1)
}

func tt(t *testing.T, result bool, cd int, args ...interface{}) {
	fn := func() {
		t.Errorf("!  Failure")
		if len(args) > 0 {
			t.Error("!", " -", fmt.Sprint(args...))
		}
	}
	assert(t, result, fn, cd+1)
}

func T(t *testing.T, result bool, args ...interface{}) {
	tt(t, result, 1, args...)
}

func Tf(t *testing.T, result bool, format string, args ...interface{}) {
	tt(t, result, 1, fmt.Sprintf(format, args...))
}

func Equal(t *testing.T, exp, got interface{}, args ...interface{}) {
	equal(t, exp, got, 1, args...)
}

func Equalf(t *testing.T, exp, got interface{}, format string, args ...interface{}) {
	equal(t, exp, got, 1, fmt.Sprintf(format, args...))
}

func NotEqual(t *testing.T, exp, got interface{}, args ...interface{}) {
	fn := func() {
		t.Errorf("!  Unexpected: <%#v>", exp)
		if len(args) > 0 {
			t.Error("!", " -", fmt.Sprint(args...))
		}
	}
	result := !reflect.DeepEqual(exp, got)
	assert(t, result, fn, 1)
}

func Panic(t *testing.T, err interface{}, fn func()) {
	defer func() {
		equal(t, err, recover(), 3)
	}()
	fn()
}

A vendor/github.com/go-playground/assert/v2/.gitignore => vendor/github.com/go-playground/assert/v2/.gitignore +24 -0
@@ 0,0 1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

A vendor/github.com/go-playground/assert/v2/.travis.yml => vendor/github.com/go-playground/assert/v2/.travis.yml +21 -0
@@ 0,0 1,21 @@
language: go
go:
  - 1.13.1
  - tip
matrix:
  allow_failures:
    - go: tip

notifications:
  email:
    recipients: dean.karn@gmail.com
    on_success: change
    on_failure: always

# Only clone the most recent commit.
git:
  depth: 1

script:
  - go test -v -race ./...


R vendor/github.com/kr/pretty/License => vendor/github.com/go-playground/assert/v2/LICENSE +8 -5
@@ 1,4 1,6 @@
Copyright 2012 Keith Rarick
The MIT License (MIT)

Copyright (c) 2015 Dean Karn

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal


@@ 7,13 9,14 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


A vendor/github.com/go-playground/assert/v2/README.md => vendor/github.com/go-playground/assert/v2/README.md +80 -0
@@ 0,0 1,80 @@
Package assert
==============

[![Build Status](https://travis-ci.org/go-playground/assert.svg?branch=master)](https://travis-ci.org/go-playground/assert)
[![GoDoc](https://godoc.org/github.com/go-playground/assert?status.svg)](https://godoc.org/gopkg.in/go-playground/assert.v1)

Package assert is a Basic Assertion library used along side native go testing

Installation
------------

Use go get.

	go get github.com/go-playground/assert

Then import the assert package into your own code.

	import . "github.com/go-playground/assert/v2"

Usage and documentation
------

Please see http://godoc.org/github.com/go-playground/assert for detailed usage docs.

##### Example:
```go
package whatever

import (
	"errors"
	"testing"
	. "github.com/go-playground/assert/v2"
)

func AssertCustomErrorHandler(t *testing.T, errs map[string]string, key, expected string) {
	val, ok := errs[key]

	// using EqualSkip and NotEqualSkip as building blocks for my custom Assert function
	EqualSkip(t, 2, ok, true)
	NotEqualSkip(t, 2, val, nil)
	EqualSkip(t, 2, val, expected)
}

func TestEqual(t *testing.T) {

	// error comes from your package/library
	err := errors.New("my error")
	NotEqual(t, err, nil)
	Equal(t, err.Error(), "my error")

	err = nil
	Equal(t, err, nil)

	fn := func() {
		panic("omg omg omg!")
	}

	PanicMatches(t, func() { fn() }, "omg omg omg!")
	PanicMatches(t, func() { panic("omg omg omg!") }, "omg omg omg!")

	// errs would have come from your package/library
	errs := map[string]string{}
	errs["Name"] = "User Name Invalid"
	errs["Email"] = "User Email Invalid"

	AssertCustomErrorHandler(t, errs, "Name", "User Name Invalid")
	AssertCustomErrorHandler(t, errs, "Email", "User Email Invalid")
}
```

How to Contribute
------
Make a PR.

I strongly encourage everyone whom creates a usefull custom assertion function to contribute them and
help make this package even better.

License
------
Distributed under MIT License, please see license file in code for more details.

A vendor/github.com/go-playground/assert/v2/assert.go => vendor/github.com/go-playground/assert/v2/assert.go +197 -0
@@ 0,0 1,197 @@
package assert

import (
	"fmt"
	"path"
	"reflect"
	"regexp"
	"runtime"
	"testing"
)

// IsEqual returns whether val1 is equal to val2 taking into account Pointers, Interfaces and their underlying types
func IsEqual(val1, val2 interface{}) bool {
	v1 := reflect.ValueOf(val1)
	v2 := reflect.ValueOf(val2)

	if v1.Kind() == reflect.Ptr {
		v1 = v1.Elem()
	}

	if v2.Kind() == reflect.Ptr {
		v2 = v2.Elem()
	}

	if !v1.IsValid() && !v2.IsValid() {
		return true
	}

	switch v1.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
		if v1.IsNil() {
			v1 = reflect.ValueOf(nil)
		}
	}

	switch v2.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
		if v2.IsNil() {
			v2 = reflect.ValueOf(nil)
		}
	}

	v1Underlying := reflect.Zero(reflect.TypeOf(v1)).Interface()
	v2Underlying := reflect.Zero(reflect.TypeOf(v2)).Interface()

	if v1 == v1Underlying {
		if v2 == v2Underlying {
			goto CASE4
		} else {
			goto CASE3
		}
	} else {
		if v2 == v2Underlying {
			goto CASE2
		} else {
			goto CASE1
		}
	}

CASE1:
	return reflect.DeepEqual(v1.Interface(), v2.Interface())
CASE2:
	return reflect.DeepEqual(v1.Interface(), v2)
CASE3:
	return reflect.DeepEqual(v1, v2.Interface())
CASE4:
	return reflect.DeepEqual(v1, v2)
}

// NotMatchRegex validates that value matches the regex, either string or *regex
// and throws an error with line number
func NotMatchRegex(t *testing.T, value string, regex interface{}) {
	NotMatchRegexSkip(t, 2, value, regex)
}

// NotMatchRegexSkip validates that value matches the regex, either string or *regex
// and throws an error with line number
// but the skip variable tells NotMatchRegexSkip how far back on the stack to report the error.
// This is a building block to creating your own more complex validation functions.
func NotMatchRegexSkip(t *testing.T, skip int, value string, regex interface{}) {

	if r, ok, err := regexMatches(regex, value); ok || err != nil {
		_, file, line, _ := runtime.Caller(skip)

		if err != nil {
			fmt.Printf("%s:%d %v error compiling regex %v\n", path.Base(file), line, value, r.String())
		} else {
			fmt.Printf("%s:%d %v matches regex %v\n", path.Base(file), line, value, r.String())
		}

		t.FailNow()
	}
}

// MatchRegex validates that value matches the regex, either string or *regex
// and throws an error with line number
func MatchRegex(t *testing.T, value string, regex interface{}) {
	MatchRegexSkip(t, 2, value, regex)
}

// MatchRegexSkip validates that value matches the regex, either string or *regex
// and throws an error with line number
// but the skip variable tells MatchRegexSkip how far back on the stack to report the error.
// This is a building block to creating your own more complex validation functions.
func MatchRegexSkip(t *testing.T, skip int, value string, regex interface{}) {

	if r, ok, err := regexMatches(regex, value); !ok {
		_, file, line, _ := runtime.Caller(skip)

		if err != nil {
			fmt.Printf("%s:%d %v error compiling regex %v\n", path.Base(file), line, value, r.String())
		} else {
			fmt.Printf("%s:%d %v does not match regex %v\n", path.Base(file), line, value, r.String())
		}

		t.FailNow()
	}
}

func regexMatches(regex interface{}, value string) (*regexp.Regexp, bool, error) {

	var err error

	r, ok := regex.(*regexp.Regexp)

	// must be a string
	if !ok {
		if r, err = regexp.Compile(regex.(string)); err != nil {
			return r, false, err
		}
	}

	return r, r.MatchString(value), err
}

// Equal validates that val1 is equal to val2 and throws an error with line number
func Equal(t *testing.T, val1, val2 interface{}) {
	EqualSkip(t, 2, val1, val2)
}

// EqualSkip validates that val1 is equal to val2 and throws an error with line number
// but the skip variable tells EqualSkip how far back on the stack to report the error.
// This is a building block to creating your own more complex validation functions.
func EqualSkip(t *testing.T, skip int, val1, val2 interface{}) {

	if !IsEqual(val1, val2) {
		_, file, line, _ := runtime.Caller(skip)
		fmt.Printf("%s:%d %v does not equal %v\n", path.Base(file), line, val1, val2)
		t.FailNow()
	}
}

// NotEqual validates that val1 is not equal val2 and throws an error with line number
func NotEqual(t *testing.T, val1, val2 interface{}) {
	NotEqualSkip(t, 2, val1, val2)
}

// NotEqualSkip validates that val1 is not equal to val2 and throws an error with line number
// but the skip variable tells NotEqualSkip how far back on the stack to report the error.
// This is a building block to creating your own more complex validation functions.
func NotEqualSkip(t *testing.T, skip int, val1, val2 interface{}) {

	if IsEqual(val1, val2) {
		_, file, line, _ := runtime.Caller(skip)
		fmt.Printf("%s:%d %v should not be equal %v\n", path.Base(file), line, val1, val2)
		t.FailNow()
	}
}

// PanicMatches validates that the panic output of running fn matches the supplied string
func PanicMatches(t *testing.T, fn func(), matches string) {
	PanicMatchesSkip(t, 2, fn, matches)
}

// PanicMatchesSkip validates that the panic output of running fn matches the supplied string
// but the skip variable tells PanicMatchesSkip how far back on the stack to report the error.
// This is a building block to creating your own more complex validation functions.
func PanicMatchesSkip(t *testing.T, skip int, fn func(), matches string) {

	_, file, line, _ := runtime.Caller(skip)

	defer func() {
		if r := recover(); r != nil {
			err := fmt.Sprintf("%s", r)

			if err != matches {
				fmt.Printf("%s:%d Panic...  expected [%s] received [%s]", path.Base(file), line, matches, err)
				t.FailNow()
			}
		} else {
			fmt.Printf("%s:%d Panic Expected, none found...  expected [%s]", path.Base(file), line, matches)
			t.FailNow()
		}
	}()

	fn()
}

A vendor/github.com/go-playground/assert/v2/doc.go => vendor/github.com/go-playground/assert/v2/doc.go +49 -0
@@ 0,0 1,49 @@
/*
Package assert provides some basic assertion functions for testing and
also provides the building blocks for creating your own more complex
validations.

	package whatever

	import (
		"errors"
		"testing"
		. "github.com/go-playground/assert.v1"
	)

	func AssertCustomErrorHandler(t *testing.T, errs map[string]string, key, expected string) {
		val, ok := errs[key]

		// using EqualSkip and NotEqualSkip as building blocks for my custom Assert function
		EqualSkip(t, 2, ok, true)
		NotEqualSkip(t, 2, val, nil)
		EqualSkip(t, 2, val, expected)
	}

	func TestEqual(t *testing.T) {

		// error comes from your package/library
		err := errors.New("my error")
		NotEqual(t, err, nil)
		Equal(t, err.Error(), "my error")

		err = nil
		Equal(t, err, nil)

		fn := func() {
			panic("omg omg omg!")
		}

		PanicMatches(t, func() { fn() }, "omg omg omg!")
		PanicMatches(t, func() { panic("omg omg omg!") }, "omg omg omg!")

		// errs would have come from your package/library
		errs := map[string]string{}
		errs["Name"] = "User Name Invalid"
		errs["Email"] = "User Email Invalid"

		AssertCustomErrorHandler(t, errs, "Name", "User Name Invalid")
		AssertCustomErrorHandler(t, errs, "Email", "User Email Invalid")
	}
*/
package assert

A vendor/github.com/go-playground/assert/v2/go.mod => vendor/github.com/go-playground/assert/v2/go.mod +3 -0
@@ 0,0 1,3 @@
module github.com/go-playground/assert/v2

go 1.13

D vendor/github.com/kr/pretty/.gitignore => vendor/github.com/kr/pretty/.gitignore +0 -4
@@ 1,4 0,0 @@
[568].out
_go*
_test*
_obj

D vendor/github.com/kr/pretty/Readme => vendor/github.com/kr/pretty/Readme +0 -9
@@ 1,9 0,0 @@
package pretty

    import "github.com/kr/pretty"

    Package pretty provides pretty-printing for Go values.

Documentation

    http://godoc.org/github.com/kr/pretty

D vendor/github.com/kr/pretty/diff.go => vendor/github.com/kr/pretty/diff.go +0 -265
@@ 1,265 0,0 @@
package pretty

import (
	"fmt"
	"io"
	"reflect"
)

type sbuf []string

func (p *sbuf) Printf(format string, a ...interface{}) {
	s := fmt.Sprintf(format, a...)
	*p = append(*p, s)
}

// Diff returns a slice where each element describes
// a difference between a and b.
func Diff(a, b interface{}) (desc []string) {
	Pdiff((*sbuf)(&desc), a, b)
	return desc
}

// wprintfer calls Fprintf on w for each Printf call
// with a trailing newline.
type wprintfer struct{ w io.Writer }

func (p *wprintfer) Printf(format string, a ...interface{}) {
	fmt.Fprintf(p.w, format+"\n", a...)
}

// Fdiff writes to w a description of the differences between a and b.
func Fdiff(w io.Writer, a, b interface{}) {
	Pdiff(&wprintfer{w}, a, b)
}

type Printfer interface {
	Printf(format string, a ...interface{})
}

// Pdiff prints to p a description of the differences between a and b.
// It calls Printf once for each difference, with no trailing newline.
// The standard library log.Logger is a Printfer.
func Pdiff(p Printfer, a, b interface{}) {
	diffPrinter{w: p}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
}

type Logfer interface {
	Logf(format string, a ...interface{})
}

// logprintfer calls Fprintf on w for each Printf call
// with a trailing newline.
type logprintfer struct{ l Logfer }

func (p *logprintfer) Printf(format string, a ...interface{}) {
	p.l.Logf(format, a...)
}

// Ldiff prints to l a description of the differences between a and b.
// It calls Logf once for each difference, with no trailing newline.
// The standard library testing.T and testing.B are Logfers.
func Ldiff(l Logfer, a, b interface{}) {
	Pdiff(&logprintfer{l}, a, b)
}

type diffPrinter struct {
	w Printfer
	l string // label
}

func (w diffPrinter) printf(f string, a ...interface{}) {
	var l string
	if w.l != "" {
		l = w.l + ": "
	}
	w.w.Printf(l+f, a...)
}

func (w diffPrinter) diff(av, bv reflect.Value) {
	if !av.IsValid() && bv.IsValid() {
		w.printf("nil != %# v", formatter{v: bv, quote: true})
		return
	}
	if av.IsValid() && !bv.IsValid() {
		w.printf("%# v != nil", formatter{v: av, quote: true})
		return
	}
	if !av.IsValid() && !bv.IsValid() {
		return
	}

	at := av.Type()
	bt := bv.Type()
	if at != bt {
		w.printf("%v != %v", at, bt)
		return
	}

	switch kind := at.Kind(); kind {
	case reflect.Bool:
		if a, b := av.Bool(), bv.Bool(); a != b {
			w.printf("%v != %v", a, b)
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if a, b := av.Int(), bv.Int(); a != b {
			w.printf("%d != %d", a, b)
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		if a, b := av.Uint(), bv.Uint(); a != b {
			w.printf("%d != %d", a, b)
		}
	case reflect.Float32, reflect.Float64:
		if a, b := av.Float(), bv.Float(); a != b {
			w.printf("%v != %v", a, b)
		}
	case reflect.Complex64, reflect.Complex128:
		if a, b := av.Complex(), bv.Complex(); a != b {
			w.printf("%v != %v", a, b)
		}
	case reflect.Array:
		n := av.Len()
		for i := 0; i < n; i++ {
			w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
		}
	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
		if a, b := av.Pointer(), bv.Pointer(); a != b {
			w.printf("%#x != %#x", a, b)
		}
	case reflect.Interface:
		w.diff(av.Elem(), bv.Elem())
	case reflect.Map:
		ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
		for _, k := range ak {
			w := w.relabel(fmt.Sprintf("[%#v]", k))
			w.printf("%q != (missing)", av.MapIndex(k))
		}
		for _, k := range both {
			w := w.relabel(fmt.Sprintf("[%#v]", k))
			w.diff(av.MapIndex(k), bv.MapIndex(k))
		}
		for _, k := range bk {
			w := w.relabel(fmt.Sprintf("[%#v]", k))
			w.printf("(missing) != %q", bv.MapIndex(k))
		}
	case reflect.Ptr:
		switch {
		case av.IsNil() && !bv.IsNil():
			w.printf("nil != %# v", formatter{v: bv, quote: true})
		case !av.IsNil() && bv.IsNil():
			w.printf("%# v != nil", formatter{v: av, quote: true})
		case !av.IsNil() && !bv.IsNil():
			w.diff(av.Elem(), bv.Elem())
		}
	case reflect.Slice:
		lenA := av.Len()
		lenB := bv.Len()
		if lenA != lenB {
			w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
			break
		}
		for i := 0; i < lenA; i++ {
			w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
		}
	case reflect.String:
		if a, b := av.String(), bv.String(); a != b {
			w.printf("%q != %q", a, b)
		}
	case reflect.Struct:
		for i := 0; i < av.NumField(); i++ {
			w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
		}
	default:
		panic("unknown reflect Kind: " + kind.String())
	}
}

func (d diffPrinter) relabel(name string) (d1 diffPrinter) {
	d1 = d
	if d.l != "" && name[0] != '[' {
		d1.l += "."
	}
	d1.l += name
	return d1
}

// keyEqual compares a and b for equality.
// Both a and b must be valid map keys.
func keyEqual(av, bv reflect.Value) bool {
	if !av.IsValid() && !bv.IsValid() {
		return true
	}
	if !av.IsValid() || !bv.IsValid() || av.Type() != bv.Type() {
		return false
	}
	switch kind := av.Kind(); kind {
	case reflect.Bool:
		a, b := av.Bool(), bv.Bool()
		return a == b
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		a, b := av.Int(), bv.Int()
		return a == b
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		a, b := av.Uint(), bv.Uint()
		return a == b
	case reflect.Float32, reflect.Float64:
		a, b := av.Float(), bv.Float()
		return a == b
	case reflect.Complex64, reflect.Complex128:
		a, b := av.Complex(), bv.Complex()
		return a == b
	case reflect.Array:
		for i := 0; i < av.Len(); i++ {
			if !keyEqual(av.Index(i), bv.Index(i)) {
				return false
			}
		}
		return true
	case reflect.Chan, reflect.UnsafePointer, reflect.Ptr:
		a, b := av.Pointer(), bv.Pointer()
		return a == b
	case reflect.Interface:
		return keyEqual(av.Elem(), bv.Elem())
	case reflect.String:
		a, b := av.String(), bv.String()
		return a == b
	case reflect.Struct:
		for i := 0; i < av.NumField(); i++ {
			if !keyEqual(av.Field(i), bv.Field(i)) {
				return false
			}
		}
		return true
	default:
		panic("invalid map key type " + av.Type().String())
	}
}

func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
	for _, av := range a {
		inBoth := false
		for _, bv := range b {
			if keyEqual(av, bv) {
				inBoth = true
				both = append(both, av)
				break
			}
		}
		if !inBoth {
			ak = append(ak, av)
		}
	}
	for _, bv := range b {
		inBoth := false
		for _, av := range a {
			if keyEqual(av, bv) {
				inBoth = true
				break
			}
		}
		if !inBoth {
			bk = append(bk, bv)
		}
	}
	return
}

D vendor/github.com/kr/pretty/formatter.go => vendor/github.com/kr/pretty/formatter.go +0 -327
@@ 1,327 0,0 @@
package pretty

import (
	"fmt"
	"io"
	"reflect"
	"strconv"
	"text/tabwriter"

	"github.com/kr/text"
)

type formatter struct {
	v     reflect.Value
	force bool
	quote bool
}

// Formatter makes a wrapper, f, that will format x as go source with line
// breaks and tabs. Object f responds to the "%v" formatting verb when both the
// "#" and " " (space) flags are set, for example:
//
//     fmt.Sprintf("%# v", Formatter(x))
//
// If one of these two flags is not set, or any other verb is used, f will
// format x according to the usual rules of package fmt.
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
func Formatter(x interface{}) (f fmt.Formatter) {
	return formatter{v: reflect.ValueOf(x), quote: true}
}

func (fo formatter) String() string {
	return fmt.Sprint(fo.v.Interface()) // unwrap it
}

func (fo formatter) passThrough(f fmt.State, c rune) {
	s := "%"
	for i := 0; i < 128; i++ {
		if f.Flag(i) {
			s += string(i)
		}
	}
	if w, ok := f.Width(); ok {
		s += fmt.Sprintf("%d", w)
	}
	if p, ok := f.Precision(); ok {
		s += fmt.Sprintf(".%d", p)
	}
	s += string(c)
	fmt.Fprintf(f, s, fo.v.Interface())
}

func (fo formatter) Format(f fmt.State, c rune) {
	if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
		w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
		p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
		p.printValue(fo.v, true, fo.quote)
		w.Flush()
		return
	}
	fo.passThrough(f, c)
}

type printer struct {
	io.Writer
	tw      *tabwriter.Writer
	visited map[visit]int
	depth   int
}

func (p *printer) indent() *printer {
	q := *p
	q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
	q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
	return &q
}

func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
	if showType {
		io.WriteString(p, v.Type().String())
		fmt.Fprintf(p, "(%#v)", x)
	} else {
		fmt.Fprintf(p, "%#v", x)
	}
}

// printValue must keep track of already-printed pointer values to avoid
// infinite recursion.
type visit struct {
	v   uintptr
	typ reflect.Type
}

func (p *printer) printValue(v reflect.Value, showType, quote bool) {
	if p.depth > 10 {
		io.WriteString(p, "!%v(DEPTH EXCEEDED)")
		return
	}

	switch v.Kind() {
	case reflect.Bool:
		p.printInline(v, v.Bool(), showType)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		p.printInline(v, v.Int(), showType)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		p.printInline(v, v.Uint(), showType)
	case reflect.Float32, reflect.Float64:
		p.printInline(v, v.Float(), showType)
	case reflect.Complex64, reflect.Complex128:
		fmt.Fprintf(p, "%#v", v.Complex())
	case reflect.String:
		p.fmtString(v.String(), quote)
	case reflect.Map:
		t := v.Type()
		if showType {
			io.WriteString(p, t.String())
		}
		writeByte(p, '{')
		if nonzero(v) {
			expand := !canInline(v.Type())
			pp := p
			if expand {
				writeByte(p, '\n')
				pp = p.indent()
			}
			keys := v.MapKeys()
			for i := 0; i < v.Len(); i++ {
				k := keys[i]
				mv := v.MapIndex(k)
				pp.printValue(k, false, true)
				writeByte(pp, ':')
				if expand {
					writeByte(pp, '\t')
				}
				showTypeInStruct := t.Elem().Kind() == reflect.Interface
				pp.printValue(mv, showTypeInStruct, true)
				if expand {
					io.WriteString(pp, ",\n")
				} else if i < v.Len()-1 {
					io.WriteString(pp, ", ")
				}
			}
			if expand {
				pp.tw.Flush()
			}
		}
		writeByte(p, '}')
	case reflect.Struct:
		t := v.Type()
		if v.CanAddr() {
			addr := v.UnsafeAddr()
			vis := visit{addr, t}
			if vd, ok := p.visited[vis]; ok && vd < p.depth {
				p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
				break // don't print v again
			}
			p.visited[vis] = p.depth
		}

		if showType {
			io.WriteString(p, t.String())
		}
		writeByte(p, '{')
		if nonzero(v) {
			expand := !canInline(v.Type())
			pp := p
			if expand {
				writeByte(p, '\n')
				pp = p.indent()
			}
			for i := 0; i < v.NumField(); i++ {
				showTypeInStruct := true
				if f := t.Field(i); f.Name != "" {
					io.WriteString(pp, f.Name)
					writeByte(pp, ':')
					if expand {
						writeByte(pp, '\t')
					}
					showTypeInStruct = labelType(f.Type)
				}
				pp.printValue(getField(v, i), showTypeInStruct, true)
				if expand {
					io.WriteString(pp, ",\n")
				} else if i < v.NumField()-1 {
					io.WriteString(pp, ", ")
				}
			}
			if expand {
				pp.tw.Flush()
			}
		}
		writeByte(p, '}')
	case reflect.Interface:
		switch e := v.Elem(); {
		case e.Kind() == reflect.Invalid:
			io.WriteString(p, "nil")
		case e.IsValid():
			pp := *p
			pp.depth++
			pp.printValue(e, showType, true)
		default:
			io.WriteString(p, v.Type().String())
			io.WriteString(p, "(nil)")
		}
	case reflect.Array, reflect.Slice:
		t := v.Type()
		if showType {
			io.WriteString(p, t.String())
		}
		if v.Kind() == reflect.Slice && v.IsNil() && showType {
			io.WriteString(p, "(nil)")
			break
		}
		if v.Kind() == reflect.Slice && v.IsNil() {
			io.WriteString(p, "nil")
			break
		}
		writeByte(p, '{')
		expand := !canInline(v.Type())
		pp := p
		if expand {
			writeByte(p, '\n')
			pp = p.indent()
		}
		for i := 0; i < v.Len(); i++ {
			showTypeInSlice := t.Elem().Kind() == reflect.Interface
			pp.printValue(v.Index(i), showTypeInSlice, true)
			if expand {
				io.WriteString(pp, ",\n")
			} else if i < v.Len()-1 {
				io.WriteString(pp, ", ")
			}
		}
		if expand {
			pp.tw.Flush()
		}
		writeByte(p, '}')
	case reflect.Ptr:
		e := v.Elem()
		if !e.IsValid() {
			writeByte(p, '(')
			io.WriteString(p, v.Type().String())
			io.WriteString(p, ")(nil)")
		} else {
			pp := *p
			pp.depth++
			writeByte(pp, '&')
			pp.printValue(e, true, true)
		}
	case reflect.Chan:
		x := v.Pointer()
		if showType {
			writeByte(p, '(')
			io.WriteString(p, v.Type().String())
			fmt.Fprintf(p, ")(%#v)", x)
		} else {
			fmt.Fprintf(p, "%#v", x)
		}
	case reflect.Func:
		io.WriteString(p, v.Type().String())
		io.WriteString(p, " {...}")
	case reflect.UnsafePointer:
		p.printInline(v, v.Pointer(), showType)
	case reflect.Invalid:
		io.WriteString(p, "nil")
	}
}

func canInline(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Map:
		return !canExpand(t.Elem())
	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			if canExpand(t.Field(i).Type) {
				return false
			}
		}
		return true
	case reflect.Interface:
		return false
	case reflect.Array, reflect.Slice:
		return !canExpand(t.Elem())
	case reflect.Ptr:
		return false
	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
		return false
	}
	return true
}

func canExpand(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Map, reflect.Struct,
		reflect.Interface, reflect.Array, reflect.Slice,
		reflect.Ptr:
		return true
	}
	return false
}

func labelType(t reflect.Type) bool {
	switch t.Kind() {
	case reflect.Interface, reflect.Struct:
		return true
	}
	return false
}

func (p *printer) fmtString(s string, quote bool) {
	if quote {
		s = strconv.Quote(s)
	}
	io.WriteString(p, s)
}

func writeByte(w io.Writer, b byte) {
	w.Write([]byte{b})
}

func getField(v reflect.Value, i int) reflect.Value {
	val := v.Field(i)
	if val.Kind() == reflect.Interface && !val.IsNil() {
		val = val.Elem()
	}
	return val
}

D vendor/github.com/kr/pretty/go.mod => vendor/github.com/kr/pretty/go.mod +0 -5
@@ 1,5 0,0 @@
module github.com/kr/pretty

go 1.12

require github.com/kr/text v0.1.0

D vendor/github.com/kr/pretty/go.sum => vendor/github.com/kr/pretty/go.sum +0 -3
@@ 1,3 0,0 @@
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=

D vendor/github.com/kr/pretty/pretty.go => vendor/github.com/kr/pretty/pretty.go +0 -108
@@ 1,108 0,0 @@
// Package pretty provides pretty-printing for Go values. This is
// useful during debugging, to avoid wrapping long output lines in
// the terminal.
//
// It provides a function, Formatter, that can be used with any
// function that accepts a format string. It also provides
// convenience wrappers for functions in packages fmt and log.
package pretty

import (
	"fmt"
	"io"
	"log"
	"reflect"
)

// Errorf is a convenience wrapper for fmt.Errorf.
//
// Calling Errorf(f, x, y) is equivalent to
// fmt.Errorf(f, Formatter(x), Formatter(y)).
func Errorf(format string, a ...interface{}) error {
	return fmt.Errorf(format, wrap(a, false)...)
}

// Fprintf is a convenience wrapper for fmt.Fprintf.
//
// Calling Fprintf(w, f, x, y) is equivalent to
// fmt.Fprintf(w, f, Formatter(x), Formatter(y)).
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) {
	return fmt.Fprintf(w, format, wrap(a, false)...)
}

// Log is a convenience wrapper for log.Printf.
//
// Calling Log(x, y) is equivalent to
// log.Print(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Log(a ...interface{}) {
	log.Print(wrap(a, true)...)
}

// Logf is a convenience wrapper for log.Printf.
//
// Calling Logf(f, x, y) is equivalent to
// log.Printf(f, Formatter(x), Formatter(y)).
func Logf(format string, a ...interface{}) {
	log.Printf(format, wrap(a, false)...)
}

// Logln is a convenience wrapper for log.Printf.
//
// Calling Logln(x, y) is equivalent to
// log.Println(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Logln(a ...interface{}) {
	log.Println(wrap(a, true)...)
}

// Print pretty-prints its operands and writes to standard output.
//
// Calling Print(x, y) is equivalent to
// fmt.Print(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Print(a ...interface{}) (n int, errno error) {
	return fmt.Print(wrap(a, true)...)
}

// Printf is a convenience wrapper for fmt.Printf.
//
// Calling Printf(f, x, y) is equivalent to
// fmt.Printf(f, Formatter(x), Formatter(y)).
func Printf(format string, a ...interface{}) (n int, errno error) {
	return fmt.Printf(format, wrap(a, false)...)
}

// Println pretty-prints its operands and writes to standard output.
//
// Calling Println(x, y) is equivalent to
// fmt.Println(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Println(a ...interface{}) (n int, errno error) {
	return fmt.Println(wrap(a, true)...)
}

// Sprint is a convenience wrapper for fmt.Sprintf.
//
// Calling Sprint(x, y) is equivalent to
// fmt.Sprint(Formatter(x), Formatter(y)), but each operand is
// formatted with "%# v".
func Sprint(a ...interface{}) string {
	return fmt.Sprint(wrap(a, true)...)
}

// Sprintf is a convenience wrapper for fmt.Sprintf.
//
// Calling Sprintf(f, x, y) is equivalent to
// fmt.Sprintf(f, Formatter(x), Formatter(y)).
func Sprintf(format string, a ...interface{}) string {
	return fmt.Sprintf(format, wrap(a, false)...)
}

func wrap(a []interface{}, force bool) []interface{} {
	w := make([]interface{}, len(a))
	for i, x := range a {
		w[i] = formatter{v: reflect.ValueOf(x), force: force}
	}
	return w
}

D vendor/github.com/kr/pretty/zero.go => vendor/github.com/kr/pretty/zero.go +0 -41
@@ 1,41 0,0 @@
package pretty

import (
	"reflect"
)

func nonzero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool:
		return v.Bool()
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() != 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return v.Uint() != 0
	case reflect.Float32, reflect.Float64:
		return v.Float() != 0
	case reflect.Complex64, reflect.Complex128:
		return v.Complex() != complex(0, 0)
	case reflect.String:
		return v.String() != ""
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if nonzero(getField(v, i)) {
				return true
			}
		}
		return false
	case reflect.Array:
		for i := 0; i < v.Len(); i++ {
			if nonzero(v.Index(i)) {
				return true
			}
		}
		return false
	case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func:
		return !v.IsNil()
	case reflect.UnsafePointer:
		return v.Pointer() != 0
	}
	return true
}

D vendor/github.com/kr/text/License => vendor/github.com/kr/text/License +0 -19
@@ 1,19 0,0 @@
Copyright 2012 Keith Rarick

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

D vendor/github.com/kr/text/Readme => vendor/github.com/kr/text/Readme +0 -3
@@ 1,3 0,0 @@
This is a Go package for manipulating paragraphs of text.

See http://go.pkgdoc.org/github.com/kr/text for full documentation.

D vendor/github.com/kr/text/doc.go => vendor/github.com/kr/text/doc.go +0 -3
@@ 1,3 0,0 @@
// Package text provides rudimentary functions for manipulating text in
// paragraphs.
package text

D vendor/github.com/kr/text/go.mod => vendor/github.com/kr/text/go.mod +0 -3
@@ 1,3 0,0 @@
module "github.com/kr/text"

require "github.com/kr/pty" v1.1.1

D vendor/github.com/kr/text/indent.go => vendor/github.com/kr/text/indent.go +0 -74
@@ 1,74 0,0 @@
package text

import (
	"io"
)

// Indent inserts prefix at the beginning of each non-empty line of s. The
// end-of-line marker is NL.
func Indent(s, prefix string) string {
	return string(IndentBytes([]byte(s), []byte(prefix)))
}

// IndentBytes inserts prefix at the beginning of each non-empty line of b.
// The end-of-line marker is NL.
func IndentBytes(b, prefix []byte) []byte {
	var res []byte
	bol := true
	for _, c := range b {
		if bol && c != '\n' {
			res = append(res, prefix...)
		}
		res = append(res, c)
		bol = c == '\n'
	}
	return res
}

// Writer indents each line of its input.
type indentWriter struct {
	w   io.Writer
	bol bool
	pre [][]byte
	sel int
	off int
}

// NewIndentWriter makes a new write filter that indents the input
// lines. Each line is prefixed in order with the corresponding
// element of pre. If there are more lines than elements, the last
// element of pre is repeated for each subsequent line.
func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer {
	return &indentWriter{
		w:   w,
		pre: pre,
		bol: true,
	}
}

// The only errors returned are from the underlying indentWriter.
func (w *indentWriter) Write(p []byte) (n int, err error) {
	for _, c := range p {
		if w.bol {
			var i int
			i, err = w.w.Write(w.pre[w.sel][w.off:])
			w.off += i
			if err != nil {
				return n, err
			}
		}
		_, err = w.w.Write([]byte{c})
		if err != nil {
			return n, err
		}
		n++
		w.bol = c == '\n'
		if w.bol {
			w.off = 0
			if w.sel < len(w.pre)-1 {
				w.sel++
			}
		}
	}
	return n, nil
}

D vendor/github.com/kr/text/wrap.go => vendor/github.com/kr/text/wrap.go +0 -86
@@ 1,86 0,0 @@
package text

import (
	"bytes"
	"math"
)

var (
	nl = []byte{'\n'}
	sp = []byte{' '}
)

const defaultPenalty = 1e5

// Wrap wraps s into a paragraph of lines of length lim, with minimal
// raggedness.
func Wrap(s string, lim int) string {
	return string(WrapBytes([]byte(s), lim))
}

// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
// raggedness.
func WrapBytes(b []byte, lim int) []byte {
	words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
	var lines [][]byte
	for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
		lines = append(lines, bytes.Join(line, sp))
	}
	return bytes.Join(lines, nl)
}

// WrapWords is the low-level line-breaking algorithm, useful if you need more
// control over the details of the text wrapping process. For most uses, either
// Wrap or WrapBytes will be sufficient and more convenient.
//
// WrapWords splits a list of words into lines with minimal "raggedness",
// treating each byte as one unit, accounting for spc units between adjacent
// words on each line, and attempting to limit lines to lim units. Raggedness
// is the total error over all lines, where error is the square of the
// difference of the length of the line and lim. Too-long lines (which only
// happen when a single word is longer than lim units) have pen penalty units
// added to the error.
func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
	n := len(words)

	length := make([][]int, n)
	for i := 0; i < n; i++ {
		length[i] = make([]int, n)
		length[i][i] = len(words[i])
		for j := i + 1; j < n; j++ {
			length[i][j] = length[i][j-1] + spc + len(words[j])
		}
	}

	nbrk := make([]int, n)
	cost := make([]int, n)
	for i := range cost {
		cost[i] = math.MaxInt32
	}
	for i := n - 1; i >= 0; i-- {
		if length[i][n-1] <= lim || i == n-1 {
			cost[i] = 0
			nbrk[i] = n
		} else {
			for j := i + 1; j < n; j++ {
				d := lim - length[i][j-1]
				c := d*d + cost[j]
				if length[i][j-1] > lim {
					c += pen // too-long lines get a worse penalty
				}
				if c < cost[i] {
					cost[i] = c
					nbrk[i] = j
				}
			}
		}
	}

	var lines [][][]byte
	i := 0
	for i < n {
		lines = append(lines, words[i:nbrk[i]])
		i = nbrk[i]
	}
	return lines
}

M vendor/modules.txt => vendor/modules.txt +2 -6
@@ 1,13 1,9 @@
# git.sr.ht/~evanj/security v0.0.0-20200228044358-9b9bc6682997
git.sr.ht/~evanj/security
# github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/bmizerany/assert
# github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgrijalva/jwt-go
# github.com/kr/pretty v0.2.0
github.com/kr/pretty
# github.com/kr/text v0.1.0
github.com/kr/text
# github.com/go-playground/assert/v2 v2.0.1
github.com/go-playground/assert/v2
# github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mattn/go-sqlite3
# golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d