github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/starlarkrule/rule.go (about)

     1  // Copyright 2022 Edward McFarlane. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package starlarkrule
     6  
     7  import (
     8  	"fmt"
     9  	"net/url"
    10  	"path"
    11  	"regexp"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/emcfarlane/larking/starlib/starext"
    16  	"github.com/emcfarlane/larking/starlib/starlarkstruct"
    17  	"go.starlark.net/starlark"
    18  	"go.starlark.net/syntax"
    19  )
    20  
    21  func NewModule() *starlarkstruct.Module {
    22  	return &starlarkstruct.Module{
    23  		Name: "rule",
    24  		Members: starlark.StringDict{
    25  			"rule":          starext.MakeBuiltin("rule.rule", MakeRule),
    26  			"attr":          NewAttrModule(),
    27  			"attrs":         starext.MakeBuiltin("rule.attrs", MakeAttrs),
    28  			"DefaultInfo":   DefaultInfo,
    29  			"ContainerInfo": ContainerInfo,
    30  		},
    31  	}
    32  }
    33  
    34  // Label is a resource URL.
    35  type Label struct {
    36  	url.URL
    37  	frozen bool
    38  }
    39  
    40  func (*Label) Type() string           { return "label" }
    41  func (l *Label) Truth() starlark.Bool { return l != nil }
    42  func (l *Label) Hash() (uint32, error) {
    43  	// Hash simplified from struct hash.
    44  	var x uint32 = 8731
    45  	namehash, _ := starlark.String(l.String()).Hash()
    46  	x = x ^ 3*namehash
    47  	return x, nil
    48  }
    49  func (l *Label) Freeze() { l.frozen = true }
    50  
    51  type labelAttr func(l *Label) starlark.Value
    52  
    53  var labelAttrs = map[string]labelAttr{
    54  	"scheme":   func(l *Label) starlark.Value { return starlark.String(l.Scheme) },
    55  	"opaque":   func(l *Label) starlark.Value { return starlark.String(l.Opaque) },
    56  	"user":     func(l *Label) starlark.Value { return starlark.String(l.User.String()) },
    57  	"host":     func(l *Label) starlark.Value { return starlark.String(l.Host) },
    58  	"path":     func(l *Label) starlark.Value { return starlark.String(l.Path) },
    59  	"query":    func(l *Label) starlark.Value { return starlark.String(l.RawQuery) },
    60  	"fragment": func(l *Label) starlark.Value { return starlark.String(l.Fragment) },
    61  
    62  	// Blob type parameters.
    63  	"bucket": func(l *Label) starlark.Value { return starlark.String(l.BucketURL()) },
    64  	"key":    func(l *Label) starlark.Value { return starlark.String(l.Key()) },
    65  }
    66  
    67  func (v *Label) Attr(name string) (starlark.Value, error) {
    68  	if a := labelAttrs[name]; a != nil {
    69  		return a(v), nil
    70  	}
    71  	return nil, nil
    72  }
    73  func (v *Label) AttrNames() []string {
    74  	names := make([]string, 0, len(labelAttrs))
    75  	for name := range labelAttrs {
    76  		names = append(names, name)
    77  	}
    78  	sort.Strings(names)
    79  	return names
    80  }
    81  
    82  func (l *Label) BucketURL() string {
    83  	u := l.URL
    84  	q := u.Query()
    85  	q.Del("key")
    86  	q.Del("keyargs")
    87  	u.RawQuery = q.Encode()
    88  	return u.String()
    89  }
    90  func (l *Label) Key() string { return l.Query().Get("key") }
    91  func (l *Label) KeyArgs() (url.Values, error) {
    92  	s := l.Query().Get("keyargs")
    93  	return url.ParseQuery(s)
    94  }
    95  
    96  // Strip keyargs to match target URL.
    97  func (l *Label) CleanURL() string {
    98  	u := l.URL
    99  	q := u.Query()
   100  	q.Del("keyargs")
   101  	u.RawQuery = q.Encode()
   102  	return u.String()
   103  }
   104  
   105  func (x *Label) CompareSameType(op syntax.Token, _y starlark.Value, depth int) (bool, error) {
   106  	y := _y.(*Label)
   107  	switch op {
   108  	case syntax.EQL:
   109  		return x.String() == y.String(), nil
   110  	default:
   111  		return false, fmt.Errorf("unsupported comparison: %v", op)
   112  	}
   113  
   114  }
   115  
   116  // Parse accepts a full formed label or a relative URL.
   117  func (x *Label) Parse(relative string) (*Label, error) {
   118  	u := x.URL // copy
   119  
   120  	y, err := url.Parse(relative)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("invalid source: %v", err)
   123  	}
   124  	if y.Scheme != "" {
   125  		return &Label{*y, false}, nil
   126  	}
   127  
   128  	// If empty scheme take path as key.
   129  	q := u.Query()
   130  	key := q.Get("key")
   131  	dir, _ := path.Split(key)
   132  
   133  	key = path.Join(dir, y.Path)
   134  	q.Set("key", key)
   135  	if rq := y.RawQuery; rq != "" {
   136  		q.Set("keyargs", rq)
   137  	}
   138  	u.RawQuery = q.Encode()
   139  
   140  	return &Label{u, false}, nil
   141  
   142  }
   143  
   144  // ParseLabel creates a new Label relative to the source.
   145  func ParseLabel(label string) (*Label, error) {
   146  	u, err := url.Parse(label)
   147  	if err != nil {
   148  		return nil, fmt.Errorf("invalid label: %v", err)
   149  	}
   150  
   151  	// If empty scheme take path as key.
   152  	if u.Scheme == "" {
   153  		return nil, fmt.Errorf("expected absolute URL")
   154  	}
   155  	return &Label{*u, false}, nil
   156  }
   157  
   158  func ParseRelativeLabel(source, label string) (*Label, error) {
   159  	l, err := ParseLabel(source)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	return l.Parse(label)
   164  }
   165  
   166  func MakeLabel(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   167  	var name string
   168  	if err := starlark.UnpackPositionalArgs(
   169  		fnname, args, kwargs, 1, &name,
   170  	); err != nil {
   171  		return nil, err
   172  	}
   173  	return ParseRelativeLabel(thread.Name, name)
   174  }
   175  
   176  func AsLabel(v starlark.Value) (*Label, error) {
   177  	l, ok := v.(*Label)
   178  	if !ok {
   179  		return nil, fmt.Errorf("expected label, got %s", v.Type())
   180  	}
   181  	return l, nil
   182  }
   183  
   184  type Rule struct {
   185  	impl  *starlark.Function // implementation function
   186  	attrs *Attrs             // input attribute types
   187  	doc   string
   188  	//outs map[string]*Attr   // output attribute types
   189  	provides []*Attrs
   190  
   191  	frozen bool
   192  }
   193  
   194  // MakeRule creates a new rule instance. Accepts the following optional kwargs:
   195  // "impl", "attrs" and "provides".
   196  func MakeRule(_ *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   197  	var (
   198  		impl     = new(starlark.Function)
   199  		attrs    = new(Attrs)
   200  		doc      string
   201  		provides = new(starlark.List)
   202  		//ins  = new(starlark.Dict)
   203  		//outs = new(starlark.Dict)
   204  	)
   205  	if err := starlark.UnpackArgs(
   206  		fnname, args, kwargs,
   207  		"impl", &impl,
   208  		"attrs?", &attrs,
   209  		"doc?", &doc,
   210  		"provides?", &provides,
   211  	); err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	// type checks
   216  	if impl.NumParams() != 1 {
   217  		return nil, fmt.Errorf("unexpected number of params: %d", impl.NumParams())
   218  	}
   219  
   220  	if err := attrs.checkMutable("use"); err != nil {
   221  		return nil, err
   222  	}
   223  	keyName := "name"
   224  	if _, ok := attrs.osd.Get(keyName); ok {
   225  		return nil, fmt.Errorf("reserved %q keyword", keyName)
   226  	}
   227  	attrs.osd.Insert(keyName, &Attr{
   228  		Typ:       AttrTypeString,
   229  		Def:       starlark.String(""),
   230  		Doc:       "Name of rule",
   231  		Mandatory: true,
   232  	})
   233  
   234  	pvds := make([]*Attrs, provides.Len())
   235  	for i, n := 0, provides.Len(); i < n; i++ {
   236  		x := provides.Index(i)
   237  		attr, ok := x.(*Attrs)
   238  		if !ok {
   239  			return nil, fmt.Errorf(
   240  				"invalid provides[%d] %q, expected %q",
   241  				i, x.Type(), (&Attrs{}).Type(),
   242  			)
   243  		}
   244  		pvds[i] = attr
   245  	}
   246  
   247  	// key=dir:target.tar.gz
   248  	// key=dir/target.tar.gz
   249  
   250  	return &Rule{
   251  		impl:     impl,
   252  		doc:      doc,
   253  		attrs:    attrs,
   254  		provides: pvds,
   255  		//ins:  inAttrs,
   256  		//outs: outAttrs,
   257  	}, nil
   258  
   259  }
   260  
   261  func (r *Rule) String() string       { return "rule()" }
   262  func (r *Rule) Type() string         { return "rule" }
   263  func (r *Rule) Freeze()              { r.frozen = true }
   264  func (r *Rule) Truth() starlark.Bool { return starlark.Bool(!r.frozen) }
   265  func (r *Rule) Hash() (uint32, error) {
   266  	// TODO: can a rule be hashed?
   267  	return 0, fmt.Errorf("unhashable type: rule")
   268  }
   269  func (r *Rule) Impl() *starlark.Function { return r.impl }
   270  func (r *Rule) Attrs() *Attrs            { return r.attrs }
   271  func (r *Rule) Provides() *starlark.Set {
   272  	s := starlark.NewSet(len(r.provides))
   273  	for _, attrs := range r.provides {
   274  		if err := s.Insert(attrs); err != nil {
   275  			panic(err)
   276  		}
   277  	}
   278  	return s
   279  }
   280  
   281  //func (r *Rule) Outs() AttrFields         { return AttrFields{r.outs} }
   282  
   283  const bldKey = "builder"
   284  
   285  func setBuilder(thread *starlark.Thread, builder *Builder) {
   286  	thread.SetLocal(bldKey, builder)
   287  }
   288  func getBuilder(thread *starlark.Thread) (*Builder, error) {
   289  	if bld, ok := thread.Local(bldKey).(*Builder); ok {
   290  		return bld, nil
   291  	}
   292  	return nil, fmt.Errorf("missing builder")
   293  }
   294  
   295  // genrule(
   296  // 	cmd = "protoc ...",
   297  // 	deps = ["//:label"],
   298  // 	outs = ["//"],
   299  // 	executable = "file",
   300  // )
   301  
   302  var isStringAlphabetic = regexp.MustCompile(`^[a-zA-Z0-9_.]*$`).MatchString
   303  
   304  func (r *Rule) Name() string { return "rule" }
   305  
   306  func (r *Rule) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   307  	bld, err := getBuilder(thread)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	if len(args) > 0 {
   313  		return nil, fmt.Errorf("unexpected args")
   314  	}
   315  
   316  	source, err := ParseLabel(thread.Name)
   317  	if err != nil {
   318  		return nil, err
   319  	}
   320  
   321  	attrArgs, err := r.attrs.MakeArgs(source, kwargs)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  
   326  	target, err := NewTarget(source, r, attrArgs)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  
   331  	if err := bld.RegisterTarget(thread, target); err != nil {
   332  		return nil, err
   333  	}
   334  	return r, nil
   335  }
   336  
   337  // Target is defined by a call to a rule.
   338  type Target struct {
   339  	label *Label
   340  	rule  *Rule
   341  	args  AttrArgs //starext.OrderedStringDict // attribute args
   342  	//frozen bool
   343  }
   344  
   345  func NewTarget(
   346  	source *Label,
   347  	rule *Rule,
   348  	args *AttrArgs, //*starext.OrderedStringDict,
   349  ) (*Target, error) {
   350  	// Assert name exists.
   351  	const field = "name"
   352  	nv, err := args.Attr(field)
   353  	if err != nil {
   354  		return nil, fmt.Errorf("missing required field %q", field)
   355  	}
   356  	name, ok := starlark.AsString(nv)
   357  	if !ok || !isStringAlphabetic(name) {
   358  		return nil, fmt.Errorf("invalid field %q: %q", field, name)
   359  	}
   360  
   361  	l, err := source.Parse(name)
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	return &Target{
   367  		label: l,
   368  		rule:  rule,
   369  		args:  *args,
   370  	}, nil
   371  }
   372  
   373  func (t *Target) Clone() *Target {
   374  	return &Target{
   375  		label: t.label,         // immutable?
   376  		rule:  t.rule,          // immuatble
   377  		args:  *t.args.Clone(), // cloned
   378  	}
   379  }
   380  
   381  //// TODO?
   382  //func (t *Target) Hash() (uint32, error) {
   383  //	return 0, fmt.Errorf("unhashable type: %s", t.Type())
   384  //}
   385  
   386  func (t *Target) String() string {
   387  	var buf strings.Builder
   388  	buf.WriteString(t.Type())
   389  	buf.WriteRune('(')
   390  
   391  	buf.WriteString(t.label.String())
   392  	for i, n := 0, t.args.Len(); i < n; i++ {
   393  		if i == 0 {
   394  			buf.WriteRune('?')
   395  		} else {
   396  			buf.WriteRune('&')
   397  		}
   398  
   399  		key, val := t.args.KeyIndex(i)
   400  		buf.WriteString(key) // already escaped
   401  		buf.WriteRune('=')
   402  		buf.WriteString(url.QueryEscape(val.String()))
   403  	}
   404  
   405  	buf.WriteRune(')')
   406  	return buf.String()
   407  }
   408  func (t *Target) Type() string { return "target" }
   409  
   410  // SetQuery params, override args.
   411  func (t *Target) SetQuery(values url.Values) error {
   412  	// TODO: check mutability
   413  
   414  	for key, vals := range values {
   415  		x, ok := t.rule.attrs.osd.Get(key)
   416  		if !ok {
   417  			return fmt.Errorf("error: unknown query param: %s", key)
   418  		}
   419  		attr := x.(*Attr)
   420  
   421  		switch attr.AttrType() {
   422  		case AttrTypeString:
   423  			if len(vals) > 1 {
   424  				return fmt.Errorf("error: unexpected number of params: %v", vals)
   425  			}
   426  			s := vals[0]
   427  			// TODO: attr validation?
   428  			if err := t.args.SetField(key, starlark.String(s)); err != nil {
   429  				panic(err)
   430  			}
   431  
   432  		default:
   433  			panic("TODO: query parsing")
   434  		}
   435  	}
   436  	return nil
   437  }
   438  
   439  func (t *Target) Args() *AttrArgs { return &t.args }
   440  func (t *Target) Rule() *Rule     { return t.rule }