Permalink
Browse files

buildtags: doc exported symbols (#9)

  • Loading branch information...
mmcloughlin committed Jan 5, 2019
1 parent dc571a4 commit c48569a6b05e551bf70767f9fbae9a838f97e2a6
Showing with 142 additions and 58 deletions.
  1. +126 −58 buildtags/buildtags.go
  2. +16 −0 buildtags/examples_test.go
@@ -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

import (
@@ -31,34 +52,45 @@ import (
// // (linux OR darwin) AND 386
//

// Interface represents a build constraint.
type Interface interface {
ConstraintsConvertable
fmt.GoStringer
Evaluate(v map[string]bool) bool
Validate() error
}

// ConstraintsConvertable can be converted to a Constraints object.
type ConstraintsConvertable interface {
ToConstraints() Constraints
}

// ConstraintConvertable can be converted to a Constraint.
type ConstraintConvertable interface {
ToConstraint() Constraint
}

// OptionConvertable can be converted to an Option.
type OptionConvertable interface {
ToOption() Option
}

type (
Constraints []Constraint
Constraint []Option
Option []Term
Term string
)
// Constraints represents the AND of a list of Constraint lines.
type Constraints []Constraint

// 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 }

// Validate validates the constraints set.
func (cs Constraints) Validate() error {
for _, c := range cs {
if err := c.Validate(); err != nil {
@@ -68,6 +100,8 @@ func (cs Constraints) Validate() error {
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 {
r := true
for _, c := range cs {
@@ -76,6 +110,7 @@ func (cs Constraints) Evaluate(v map[string]bool) bool {
return r
}

// GoString represents Constraints as +build comment lines.
func (cs Constraints) GoString() string {
s := ""
for _, c := range cs {
@@ -84,9 +119,38 @@ func (cs Constraints) GoString() string {
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) ToConstraint() Constraint { return c }

// ToConstraint returns c.
func (c Constraint) ToConstraint() Constraint { return c }

// Validate validates the constraint.
func (c Constraint) Validate() error {
for _, o := range c {
if err := o.Validate(); err != nil {
@@ -96,6 +160,8 @@ func (c Constraint) Validate() error {
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 {
r := false
for _, o := range c {
@@ -104,6 +170,7 @@ func (c Constraint) Evaluate(v map[string]bool) bool {
return r
}

// GoString represents the Constraint as one +build comment line.
func (c Constraint) GoString() string {
s := "// +build"
for _, o := range c {
@@ -112,10 +179,33 @@ func (c Constraint) GoString() string {
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) ToConstraint() Constraint { return Constraint{o} }
func (o Option) ToOption() Option { return o }

// ToConstraint returns a Constraint containing just this option.
func (o Option) ToConstraint() Constraint { return Constraint{o} }

// ToOption returns o.
func (o Option) ToOption() Option { return o }

// Validate validates o.
func (o Option) Validate() error {
for _, t := range o {
if err := t.Validate(); err != nil {
@@ -125,6 +215,8 @@ func (o Option) Validate() error {
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 {
r := true
for _, t := range o {
@@ -133,6 +225,7 @@ func (o Option) Evaluate(v map[string]bool) bool {
return r
}

// GoString represents the Option as a comma-separated list of terms.
func (o Option) GoString() string {
var ts []string
for _, t := range o {
@@ -141,16 +234,32 @@ func (o Option) GoString() string {
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) ToConstraint() Constraint { return t.ToOption().ToConstraint() }
func (t Term) ToOption() Option { return Option{t} }

// ToConstraint returns a Constraint containing just this term.
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} }

// IsNegated reports whether t is the negation of an identifier.
func (t Term) IsNegated() bool { return strings.HasPrefix(string(t), "!") }

// Name returns the identifier for this term.
func (t Term) Name() string {
return strings.TrimPrefix(string(t), "!")
}

// Validate the term.
func (t Term) Validate() error {
// 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
}

// Evaluate the term under the given set of identifier values.
func (t Term) Evaluate(v map[string]bool) bool {
return (t.Validate() == nil) && (v[t.Name()] == !t.IsNegated())
}

// GoString returns t.
func (t Term) GoString() string { return string(t) }

func Not(ident string) Term {
return Term("!" + ident)
}

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 {
// SetTags builds a set where the given list of identifiers are true.
func SetTags(idents ...string) map[string]bool {
v := map[string]bool{}
for _, n := range names {
v[n] = true
for _, ident := range idents {
v[ident] = true
}
return v
}
@@ -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>
}

0 comments on commit c48569a

Please sign in to comment.