vebben

package module
v0.0.0-...-6d3a5ba Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2023 License: MIT Imports: 10 Imported by: 0

README

vebben

A jarful of golang web glue.

GoDoc Coverage Status

Thank you for stopping by. I hope you find something useful here, even if it's only the counter-example of my preoccupations. :-)

WARNING! ALPHA SOFTWARE!

This package has not been tested much, and the author hasn't actually used it in production in years. Web templating has come a long way since vebben was written, and you should probably use something else!

Like all software, it probably contains bugs, and like all new software it probably contains a lot of them. 🪲🪲🪲


I don't know how much this will encompass in the end, but my goal is to include in the vebben package all web-dev helpers I write in the course of developing a couple of different systems: a bloggy thing that's still on the eternal drawing board but will eventually be open-source; and a salon-management tool in Google App Engine that will remain private.

Right now, it's just a form processor I built because I couldn't make the ones I found do what I needed; and a collection of utility functions for standard templates that leans heavily on the work of Kyoung-chan Lee.

And it may change at any time. If you use it (I'd be flattered) be sure to vendor it in, at least until I reach a 1.0 sort of release.

FormSpec Example

// vebben simple demo
package main

import (
    "fmt"
    "net/http"

    "github.com/biztos/vebben"
)

type Flubber struct {
    Variant  string  `json:"variant"`
    Size     int     `json:"size"`
    Strength float64 `json:"strength"`
}

var FlubberSpecs = []*vebben.FormSpec{
    vebben.RequiredFormSpec("variant", "string", "4", "The 4-letter variant"),
    vebben.RequiredFormSpec("size", "int", "1-4", "The size (1-4)"),
    vebben.OptionalFormSpec("strength", "float", "", "Flubber strength"),
}

func main() {

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        if r.Method == "POST" {
            f := &Flubber{}
            err := vebben.DecodeForm(r, FlubberSpecs, f)
            if err != nil {
                http.Error(w, err.Error(), http.StatusBadRequest)
                return
            }
            fmt.Fprintf(w, "Flubber %s: size %d, strength %0.2f\n",
                f.Variant, f.Size, f.Strength)
        } else {
            fmt.Fprintf(w, "POST to describe some Flubber.")
        }

    })
    fmt.Println(http.ListenAndServe(":8080", nil))
}

And then, with the above code running, from the comfort of your favorite UNIX shell try this:

curl -i http://localhost:8080/ -d variant=FLUB -d size=4 -d strength=99.4522

...and variations thereof.

FuncMap Example


dot := map[string]interface{}{
	"Title": "Hello World!",
	"Id":    "examples",
	"Prices": []int{
		15,
		11800002,
		3582,
		999231,
		10012,
	},
}
var tsrc = `<h1 id="{{ .Id | capfirst }}">{{ .Title }}</h1>
<h2>Top Three Prices:</h2>
<ol>
{{- $p := sortintsdesc .Prices | truncintsto 3 }}{{ range $p }}
<li>${{ intcomma . }}</li>
{{- end }}
</ol>
`

tmpl, err := template.New("test").Funcs(vebben.NewFuncMap()).Parse(tsrc)
if err != nil {
	panic(err)
}
if err := tmpl.Execute(os.Stdout, dot); err != nil {
	panic(err)
}

// Output:
// <h1 id="Examples">Hello World!</h1>
// <h2>Top Three Prices:</h2>
// <ol>
// <li>$11,800,002</li>
// <li>$999,231</li>
// <li>$10,012</li>
// </ol>

Acknowledgements

The following packages have helped tremendously, and have even wittier names than this package. All kudos to their authors!

About the Name

While it's arguably true that vebben would be a great name for a Norwegian Black Metal band, the only such band I really listen to is Mayhem, and I only started listening to them because of their Hungarian connection.

Which brings us back to vebben. Say it loud and say it proud:

Mindenféle szar program van fönt a vebben...

Documentation

Overview

Package vebben provides form-processing helpers for web development.

The package name should be pronounced in Hungarian.

WARNING

This is pre-alpha software and may change at any time.

Example
package main

import (
	"html/template"
	"os"

	"github.com/biztos/vebben"
)

func main() {

	dot := map[string]interface{}{
		"Title": "Hello World!",
		"Id":    "examples",
		"Prices": []int{
			15,
			11800002,
			3582,
			999231,
			10012,
		},
	}
	var tsrc = `<h1 id="{{ .Id | capfirst }}">{{ .Title }}</h1>
<h2>Top Three Prices:</h2>
<ol>
{{- $p := sortintsdesc .Prices | truncintsto 3 }}{{ range $p }}
<li>${{ intcomma . }}</li>
{{- end }}
</ol>
`

	tmpl, err := template.New("test").Funcs(vebben.NewFuncMap()).Parse(tsrc)
	if err != nil {
		panic(err)
	}
	if err := tmpl.Execute(os.Stdout, dot); err != nil {
		panic(err)
	}

}
Output:

<h1 id="Examples">Hello World!</h1>
<h2>Top Three Prices:</h2>
<ol>
<li>$11,800,002</li>
<li>$999,231</li>
<li>$10,012</li>
</ol>

Index

Examples

Constants

This section is empty.

Variables

View Source
var DateFormats = []string{
	"2006. 01. 02.",
	"2006. 01. 02",
	"2006. 1. 2.",
	"2006. 1. 2",
	"2006.01.02.",
	"2006.01.02",
	"2006.1.2.",
	"2006.1.2",
	"2006-01-02",
	"2006-1-2",
	"2006 01 02",
	"2006 1 2",
	"20060102",
	"02.01.2006",
	"2.1.2006",
	"01/02/2006",
	"1/2/2006",
}

DateFormats holds the date formats we accept in forms (note: not times, just dates!)

View Source
var DateTimeFormats = []string{
	"2006. 01. 02. 15:04",
	"2006. 01. 02 15:04",
	"2006. 1. 2. 15:04",
	"2006. 1. 2 15:04",
	"2006.01.02. 15:04",
	"2006.01.02 15:04",
	"2006.1.2. 15:04",
	"2006.1.2 15:04",
	"2006-01-02 15:04",
	"2006-1-2 15:04",
	"2006 01 02 15:04",
	"2006 1 2 15:04",
	"20060102150405",
	"02.01.2006 15:04",
	"2.1.2006 15:04",
	"01/02/2006 15:04",
	"1/2/2006 15:04",
}

DateTimeFormats holds the datetime formats we accept in forms (note: all require times, and only to minute precision; this might change in a general library).

View Source
var DecodeFormTrimSpace = true

By default, trim space from form values.

View Source
var FormValueTimeLocation, _ = time.LoadLocation("CET")

FormValueTimeLocation is the location (time zone) used for all form input.

Functions

func AddFormSpecType

func AddFormSpecType(t string, cf func(string) (interface{}, bool),
	vf func(*FormSpec, interface{}) error)

AddFormSpecType adds or replaces FormSpec type t with converter function cf and optional default validator vf. The converter must return a type that survives JSON marshaling and unmarshaling or runtime errors will occur in DecodeForm; its bool return value is the success or failure of the conversion. Note that in many cases this is unnecessary, as the struct's final type will unmarshal from a simple string.

func DecodeForm

func DecodeForm(f FormValuer, specs []*FormSpec, target interface{}) error

DecodeForm populates the target structure from the values of a submitted form (or any other FormValuer). On failure, returns an error which may be cast as a MultiError for formatting.

Q: Why this and not one of the introspection-based libraries?
A: None of those examined yet would work without major changes:
* gorilla schema doesn't handle times
* ajg/form is close but doesn't do times at locations, nor any validation
* vala (with form) would almost work but is stubbornly non-idiomatic

Values are whitespace-trimmed before any processing occurs, unless DecodeFormTrimSpace is set to false.

Missing form fields are treated as the zero value unless they are required. Unhandled fields are ignored. Bad spec entries result in a panic.

Optional empty fields are converted to the zero value for the type.

Yes, this is messy, but whatchagonnado?

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/biztos/vebben"
)

func main() {

	type ExampleFoo struct {
		Foo string `json:"foo"`
		Bar int    `json:"bar"`
	}

	specs := []*vebben.FormSpec{
		vebben.RequiredFormSpec("foo", "string", "6", "Foo"),
		vebben.RequiredFormSpec("bar", "int", "0-100", "Bar Percentage"),
	}

	target := &ExampleFoo{}

	// A validation failure:
	r, _ := http.NewRequest("GET", "/example?foo=bar", nil)
	err := vebben.DecodeForm(r, specs, target)
	if err != nil {
		fmt.Println(err.Error())
	}

	// And a success:
	r2, _ := http.NewRequest("GET", "/example?foo=Bärfuß&bar=23", nil)
	err = vebben.DecodeForm(r2, specs, target)
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(target.Foo, target.Bar)

}
Output:

Foo has the wrong length
Bar Percentage is required
Bärfuß 23

func GlyphLength

func GlyphLength(s string) int

GlyphLength returns the number of NFKD-normalized Unicode glyphs in a utf-8 enconded string. For more information see here: http://unicode.org/reports/tr15/#Norm_Forms and here: http://stackoverflow.com/a/12668840

fmt.Println(GylphLength("műemlék")) // prints 7

This is used in DecodeForm but is also handy for length-checking any international input.

DEPRECATION WARNING

It appears at first glance that one gets exactly the same result from this, which is presumably much faster:

len([]rune(s))

If that turns out to be the case, GlyphLength will be deprecated at some point. (Per the stackoverflow link above it's not, but the evidence given might no longer hold.)

func Indent

func Indent(depth int) string

Indent returns a string with four spaces repeated up to the given depth.

func IntRange

func IntRange(a, b int) []int

IntRange returns an integer slice containing the range from a to b inclusive.

func NewFuncMap

func NewFuncMap() template.FuncMap

NewFuncMap returns a function map containing all the available functions, mapped to names as shown below. This should be included in a template, usually the master template, as:

fm := vebben.NewFuncMap()
tmpl, err := template.New("").Funcs(fm).Parse(MyTemplateData)

Functions Defined Here

These correspond to the exported function names. By convention(?) the map keys, i.e. the function names called in the template, are lowercased.

truncints
truncintsto
sortints
sortintsasc
sortintsdesc
reverseints
intrange
pathdepth
indent

Functions From Kyoung-chan Lee's Gtf

See https://github.com/leekchan/gtf for documentation.

replace
default
length
lower
upper
truncatechars
urlencode
wordcount
divisibleby
lengthis
trim
capfirst
pluralize
yesno
rjust
ljust
center
filesizeformat
apnumber
intcomma
ordinal
first
last
join
slice
random
striptags

func PathDepth

func PathDepth(p string) int

PathDepth returns the depth of a cleaned URL path (or similar string), i.e. the number of slashes it contains.

func ReverseInts

func ReverseInts(ii []int) []int

ReverseInts returns a copy of the input slice in reverse order, i.e. the opposite of the given order.

func SortInts

func SortInts(ii []int) []int

SortInts returns a sorted (Ascending) copy of a slice of integers.

func SortIntsDesc

func SortIntsDesc(ii []int) []int

SortIntsDesc returns a reverse-sorted (Descending) copy of a slice of integers.

func TruncInts

func TruncInts(i []int) []int

TruncInts returns a slice of the input slice with its final element removed.

func TruncIntsTo

func TruncIntsTo(t int, i []int) []int

TruncIntsTo returns a slice of the input slice with a maximum of t elements. If t is negative, truncates to len(i) - t. Note the argument order: this is to facilitate piping in the template:

{{ $r := .SomeIntSlice | truncintsto 5 }}

Types

type FormSpec

type FormSpec struct {
	Key       string
	Type      string
	Required  bool
	Limit     string
	Name      string
	Validator func(*FormSpec, interface{}) error
	// contains filtered or unexported fields
}

FormSpec defines a single specification item for validating a form value corresponding to Key. It is used by DecodeForm.

Valid Type values include:

"string"       // plain string
"int"          // int, max 32 bits large
"int64"        // int64
"float"        // float64
"bool"         // bool: input must be "true" or "false" if required
"date"         // date, without time part; see below.
"datetime"     // date, with time part; see below.
"dateflex"     // date, with or without time part; see below.

This list can be extended using the AddFormSpecType function.

The Limit describes a validation check, and may be left as an empty string. Limits include:

"123"          // length (strings, int, int64)
"1-10"         // allowed range of value (numeric) or length (string)
"a,b,c"        // list of simple string values accepted
"1,3,5"        // list of simple numeric values accepted
"re:^\w\d+$"   // regular expression (strings only)

Note that the Limit is only processed during the Init phase. If Init is not called, the Validator should enforce any custom limits.

The optional Name is used in formatting error messages that may be shown to the user, e.g. "<Name> is out of range."

Dates are valid in any format listed under DateFormats; DateTimes use those in DateTimeFormat; DateFlex use both.

The Validator function is called with the FormSpec itself and the type-converted value (cf. Convert). Standard Validator functions are set by Init if no Validator exists when it is called.

func NewFormSpec

func NewFormSpec(r bool, k, t string, limitAndName ...string) *FormSpec

NewFormSpec returns a pointer to an initialized FormSpec that is ready for use and whose Required property is set to r. The final two arguments, limit and name, may be omitted. If the spec is not understood, the function panics.

func OptionalFormSpec

func OptionalFormSpec(k, t string, limitAndName ...string) *FormSpec

OptionalFormSpec returns a pointer to an initialized FormSpec that is ready for use, and whose Required property is false, with key k, type t and the provided limit and name. The latter two may be omitted.

func RequiredFormSpec

func RequiredFormSpec(k, t string, limitAndName ...string) *FormSpec

RequiredFormSpec returns a pointer to a validated FormSpec that is ready for use, and whose Required property is true, with key k, type t and the provided limit and name. The latter two may be omitted. If the spec is not understood, the function panics.

func (*FormSpec) Convert

func (fs *FormSpec) Convert(raw string) (interface{}, error)

Convert converts raw to the type indicated in the FormSpec's Type property, returning an error if it can not be converted. If there is no error then the returned value is safe to pass to a standard Validator function.

func (*FormSpec) Copy

func (fs *FormSpec) Copy(key, name string) *FormSpec

Copy returns a copy of the FormSpec with a new Key and Name. The Key must not be an empty or whitespace-only string (it is whitespace-trimmed); the Name defaults to the Key. Init is not called on the new object, as the source state is preserved.

Use this to more efficiently create many functionally identical spec items, e.g. required-string validators.

func (*FormSpec) Init

func (fs *FormSpec) Init()

Init validates the FormSpec and prepares it for use. This should usually not be called directly; use OptionalFormSpec and RequiredFormSpec instead wherever possible. Since bad specs indicate programmer error, failures result in panic.

type FormValuer

type FormValuer interface {
	FormValue(string) string
}

FormValuer is implemented by http.Request, and also in the unit tests for this package. It may also be useful for overriding the standard, permissive form parsing behavior in net/http.

type MultiError

type MultiError struct {
	Errors []error
}

MultiError is a type of error that contains a slice of errors. In the standard Error method they are joined with a newline, but if cast to type the errors may be examined (or formatted) individually.

func (*MultiError) Error

func (e *MultiError) Error() string

Error implements the error interface for MultiError.

Directories

Path Synopsis
vebben simple demo
vebben simple demo

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL