buildtags: doc exported symbols (#9)

This commit is contained in:
Michael McLoughlin
2019-01-04 23:22:35 -08:00
parent dc571a47df
commit c48569a6b0
2 changed files with 145 additions and 61 deletions

View File

@@ -1,3 +1,24 @@
// Package buildtags provides types for representing and manipulating build constraints.
//
// In Go, build constraints are represented as comments in source code together with file naming conventions. For example
//
// // +build linux,386 darwin,!cgo
// // +build !purego
//
// Any terms provided in the filename can be thought of as an implicit extra
// constraint comment line. Collectively, these are referred to as
// ``constraints''. Each line is a ``constraint''. Within each constraint the
// space-separated terms are ``options'', and within that the comma-separated
// items are ``terms'' which may be negated with at most one exclaimation mark.
//
// These represent a boolean formulae. The constraints are evaluated as the AND
// of constraint lines; a constraint is evaluated as the OR of its options and
// an option is evaluated as the AND of its terms. Overall build constraints are
// a boolean formula that is an AND of ORs of ANDs.
//
// This level of complexity is rarely used in Go programs. Therefore this
// package aims to provide access to all these layers of nesting if required,
// but make it easy to forget about for basic use cases too.
package buildtags package buildtags
import ( import (
@@ -31,6 +52,7 @@ import (
// // (linux OR darwin) AND 386 // // (linux OR darwin) AND 386
// //
// Interface represents a build constraint.
type Interface interface { type Interface interface {
ConstraintsConvertable ConstraintsConvertable
fmt.GoStringer fmt.GoStringer
@@ -38,27 +60,37 @@ type Interface interface {
Validate() error Validate() error
} }
// ConstraintsConvertable can be converted to a Constraints object.
type ConstraintsConvertable interface { type ConstraintsConvertable interface {
ToConstraints() Constraints ToConstraints() Constraints
} }
// ConstraintConvertable can be converted to a Constraint.
type ConstraintConvertable interface { type ConstraintConvertable interface {
ToConstraint() Constraint ToConstraint() Constraint
} }
// OptionConvertable can be converted to an Option.
type OptionConvertable interface { type OptionConvertable interface {
ToOption() Option ToOption() Option
} }
type ( // Constraints represents the AND of a list of Constraint lines.
Constraints []Constraint type Constraints []Constraint
Constraint []Option
Option []Term
Term string
)
// And builds Constraints that will be true if all of its constraints are true.
func And(cs ...ConstraintConvertable) Constraints {
constraints := Constraints{}
for _, c := range cs {
constraints = append(constraints, c.ToConstraint())
}
return constraints
}
// ToConstraints returns cs.
func (cs Constraints) ToConstraints() Constraints { return cs } func (cs Constraints) ToConstraints() Constraints { return cs }
// Validate validates the constraints set.
func (cs Constraints) Validate() error { func (cs Constraints) Validate() error {
for _, c := range cs { for _, c := range cs {
if err := c.Validate(); err != nil { if err := c.Validate(); err != nil {
@@ -68,6 +100,8 @@ func (cs Constraints) Validate() error {
return nil return nil
} }
// Evaluate the boolean formula represented by cs under the given assignment of
// tag values. This is the AND of the values of the constituent Constraints.
func (cs Constraints) Evaluate(v map[string]bool) bool { func (cs Constraints) Evaluate(v map[string]bool) bool {
r := true r := true
for _, c := range cs { for _, c := range cs {
@@ -76,6 +110,7 @@ func (cs Constraints) Evaluate(v map[string]bool) bool {
return r return r
} }
// GoString represents Constraints as +build comment lines.
func (cs Constraints) GoString() string { func (cs Constraints) GoString() string {
s := "" s := ""
for _, c := range cs { for _, c := range cs {
@@ -84,9 +119,38 @@ func (cs Constraints) GoString() string {
return s return s
} }
// Constraint represents the OR of a list of Options.
type Constraint []Option
// Any builds a Constraint that will be true if any of its options are true.
func Any(opts ...OptionConvertable) Constraint {
c := Constraint{}
for _, opt := range opts {
c = append(c, opt.ToOption())
}
return c
}
// ParseConstraint parses a space-separated list of options.
func ParseConstraint(expr string) (Constraint, error) {
c := Constraint{}
for _, field := range strings.Fields(expr) {
opt, err := ParseOption(field)
if err != nil {
return c, err
}
c = append(c, opt)
}
return c, nil
}
// ToConstraints returns the list of constraints containing just c.
func (c Constraint) ToConstraints() Constraints { return Constraints{c} } func (c Constraint) ToConstraints() Constraints { return Constraints{c} }
// ToConstraint returns c.
func (c Constraint) ToConstraint() Constraint { return c } func (c Constraint) ToConstraint() Constraint { return c }
// Validate validates the constraint.
func (c Constraint) Validate() error { func (c Constraint) Validate() error {
for _, o := range c { for _, o := range c {
if err := o.Validate(); err != nil { if err := o.Validate(); err != nil {
@@ -96,6 +160,8 @@ func (c Constraint) Validate() error {
return nil return nil
} }
// Evaluate the boolean formula represented by c under the given assignment of
// tag values. This is the OR of the values of the constituent Options.
func (c Constraint) Evaluate(v map[string]bool) bool { func (c Constraint) Evaluate(v map[string]bool) bool {
r := false r := false
for _, o := range c { for _, o := range c {
@@ -104,6 +170,7 @@ func (c Constraint) Evaluate(v map[string]bool) bool {
return r return r
} }
// GoString represents the Constraint as one +build comment line.
func (c Constraint) GoString() string { func (c Constraint) GoString() string {
s := "// +build" s := "// +build"
for _, o := range c { for _, o := range c {
@@ -112,10 +179,33 @@ func (c Constraint) GoString() string {
return s + "\n" return s + "\n"
} }
// Option represents the AND of a list of Terms.
type Option []Term
// Opt builds an Option from the list of Terms.
func Opt(terms ...Term) Option {
return Option(terms)
}
// ParseOption parses a comma-separated list of terms.
func ParseOption(expr string) (Option, error) {
opt := Option{}
for _, t := range strings.Split(expr, ",") {
opt = append(opt, Term(t))
}
return opt, opt.Validate()
}
// ToConstraints returns Constraints containing just this option.
func (o Option) ToConstraints() Constraints { return o.ToConstraint().ToConstraints() } func (o Option) ToConstraints() Constraints { return o.ToConstraint().ToConstraints() }
// ToConstraint returns a Constraint containing just this option.
func (o Option) ToConstraint() Constraint { return Constraint{o} } func (o Option) ToConstraint() Constraint { return Constraint{o} }
// ToOption returns o.
func (o Option) ToOption() Option { return o } func (o Option) ToOption() Option { return o }
// Validate validates o.
func (o Option) Validate() error { func (o Option) Validate() error {
for _, t := range o { for _, t := range o {
if err := t.Validate(); err != nil { if err := t.Validate(); err != nil {
@@ -125,6 +215,8 @@ func (o Option) Validate() error {
return nil return nil
} }
// Evaluate the boolean formula represented by o under the given assignment of
// tag values. This is the AND of the values of the constituent Terms.
func (o Option) Evaluate(v map[string]bool) bool { func (o Option) Evaluate(v map[string]bool) bool {
r := true r := true
for _, t := range o { for _, t := range o {
@@ -133,6 +225,7 @@ func (o Option) Evaluate(v map[string]bool) bool {
return r return r
} }
// GoString represents the Option as a comma-separated list of terms.
func (o Option) GoString() string { func (o Option) GoString() string {
var ts []string var ts []string
for _, t := range o { for _, t := range o {
@@ -141,16 +234,32 @@ func (o Option) GoString() string {
return strings.Join(ts, ",") return strings.Join(ts, ",")
} }
// Term is an atomic term in a build constraint: an identifier or its negation.
type Term string
// Not returns a term for the negation of ident.
func Not(ident string) Term {
return Term("!" + ident)
}
// ToConstraints returns Constraints containing just this term.
func (t Term) ToConstraints() Constraints { return t.ToOption().ToConstraints() } func (t Term) ToConstraints() Constraints { return t.ToOption().ToConstraints() }
// ToConstraint returns a Constraint containing just this term.
func (t Term) ToConstraint() Constraint { return t.ToOption().ToConstraint() } func (t Term) ToConstraint() Constraint { return t.ToOption().ToConstraint() }
// ToOption returns an Option containing just this term.
func (t Term) ToOption() Option { return Option{t} } func (t Term) ToOption() Option { return Option{t} }
// IsNegated reports whether t is the negation of an identifier.
func (t Term) IsNegated() bool { return strings.HasPrefix(string(t), "!") } func (t Term) IsNegated() bool { return strings.HasPrefix(string(t), "!") }
// Name returns the identifier for this term.
func (t Term) Name() string { func (t Term) Name() string {
return strings.TrimPrefix(string(t), "!") return strings.TrimPrefix(string(t), "!")
} }
// Validate the term.
func (t Term) Validate() error { func (t Term) Validate() error {
// Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L110-L112 // Reference: https://github.com/golang/go/blob/204a8f55dc2e0ac8d27a781dab0da609b98560da/src/cmd/go/internal/imports/build.go#L110-L112
// //
@@ -185,60 +294,19 @@ func (t Term) Validate() error {
return nil return nil
} }
// Evaluate the term under the given set of identifier values.
func (t Term) Evaluate(v map[string]bool) bool { func (t Term) Evaluate(v map[string]bool) bool {
return (t.Validate() == nil) && (v[t.Name()] == !t.IsNegated()) return (t.Validate() == nil) && (v[t.Name()] == !t.IsNegated())
} }
// GoString returns t.
func (t Term) GoString() string { return string(t) } func (t Term) GoString() string { return string(t) }
func Not(ident string) Term { // SetTags builds a set where the given list of identifiers are true.
return Term("!" + ident) func SetTags(idents ...string) map[string]bool {
}
func And(cs ...ConstraintConvertable) Constraints {
constraints := Constraints{}
for _, c := range cs {
constraints = append(constraints, c.ToConstraint())
}
return constraints
}
func Any(opts ...OptionConvertable) Constraint {
c := Constraint{}
for _, opt := range opts {
c = append(c, opt.ToOption())
}
return c
}
func Opt(terms ...Term) Option {
return Option(terms)
}
func ParseOption(expr string) (Option, error) {
opt := Option{}
for _, t := range strings.Split(expr, ",") {
opt = append(opt, Term(t))
}
return opt, opt.Validate()
}
func ParseConstraint(expr string) (Constraint, error) {
c := Constraint{}
for _, field := range strings.Fields(expr) {
opt, err := ParseOption(field)
if err != nil {
return c, err
}
c = append(c, opt)
}
return c, nil
}
func SetTags(names ...string) map[string]bool {
v := map[string]bool{} v := map[string]bool{}
for _, n := range names { for _, ident := range idents {
v[n] = true v[ident] = true
} }
return v return v
} }

View File

@@ -0,0 +1,16 @@
package buildtags_test
import (
"fmt"
"github.com/mmcloughlin/avo/buildtags"
)
func ExampleParseConstraint() {
c, err := buildtags.ParseConstraint("a,!b c")
fmt.Print(c.GoString())
fmt.Println(err)
// Output:
// // +build a,!b c
// <nil>
}