github.com/vektra/tachyon@v0.0.0-20150921164542-0da4f3861aef/expand.go (about)

     1  package tachyon
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/vektra/tachyon/lisp"
     8  	"strings"
     9  	"unicode"
    10  )
    11  
    12  var cTemplateStart = []byte(`{{`)
    13  var cTemplateEnd = []byte(`}}`)
    14  var cExprStart = []byte(`$(`)
    15  var cExprEnd = []byte(`)`)
    16  
    17  var eUnclosedTemplate = errors.New("Unclosed template")
    18  var eUnclosedExpr = errors.New("Unclosed lisp expression")
    19  
    20  func expandTemplates(s Scope, args string) (string, error) {
    21  	a := []byte(args)
    22  
    23  	var buf bytes.Buffer
    24  
    25  	for {
    26  		idx := bytes.Index(a, cTemplateStart)
    27  
    28  		if idx == -1 {
    29  			buf.Write(a)
    30  			break
    31  		}
    32  
    33  		buf.Write(a[:idx])
    34  
    35  		in := a[idx+2:]
    36  
    37  		fin := bytes.Index(in, cTemplateEnd)
    38  
    39  		if fin == -1 {
    40  			return "", eUnclosedTemplate
    41  		}
    42  
    43  		name := bytes.TrimSpace(in[:fin])
    44  
    45  		parts := strings.Split(string(name), ".")
    46  
    47  		var (
    48  			val Value
    49  			ok  bool
    50  		)
    51  
    52  		if len(parts) == 1 {
    53  			val, ok = s.Get(string(name))
    54  		} else {
    55  			cur := parts[0]
    56  
    57  			val, ok = s.Get(cur)
    58  
    59  			for _, sub := range parts[1:] {
    60  				m, ok := val.(Map)
    61  				if !ok {
    62  					m, ok = val.Read().(Map)
    63  					if !ok {
    64  						return "", fmt.Errorf("Variable '%s' is not a Map (%T)", cur, val.Read())
    65  					}
    66  				}
    67  
    68  				val, ok = m.Get(sub)
    69  				if !ok {
    70  					return "", fmt.Errorf("Variable '%s' has no key '%s'", cur, sub)
    71  				}
    72  				cur = sub
    73  			}
    74  		}
    75  
    76  		if ok {
    77  			switch val := val.Read().(type) {
    78  			case int64, int:
    79  				buf.WriteString(fmt.Sprintf("%d", val))
    80  			default:
    81  				buf.WriteString(fmt.Sprintf("%s", val))
    82  			}
    83  
    84  			a = in[fin+2:]
    85  		} else {
    86  			return "", fmt.Errorf("Undefined variable: %s", string(name))
    87  		}
    88  	}
    89  
    90  	return buf.String(), nil
    91  }
    92  
    93  func findExprClose(buf []byte) int {
    94  	opens := 0
    95  
    96  	for idx, r := range buf {
    97  		switch r {
    98  		case ')':
    99  			opens--
   100  
   101  			if opens == 0 {
   102  				return idx
   103  			}
   104  
   105  		case '(':
   106  			opens++
   107  		}
   108  	}
   109  
   110  	return -1
   111  }
   112  
   113  func varChar(r rune) bool {
   114  	if unicode.IsLetter(r) {
   115  		return true
   116  	}
   117  	if unicode.IsDigit(r) {
   118  		return true
   119  	}
   120  	if r == '_' {
   121  		return true
   122  	}
   123  	return false
   124  }
   125  
   126  func inferValue(val Value) lisp.Value {
   127  	switch lv := val.Read().(type) {
   128  	case int:
   129  		return lisp.NumberValue(int64(lv))
   130  	case int32:
   131  		return lisp.NumberValue(int64(lv))
   132  	case int64:
   133  		return lisp.NumberValue(lv)
   134  	case string:
   135  		return lisp.StringValue(lv)
   136  	case *Result:
   137  		return lisp.MapValue(&lispResult{lv})
   138  	default:
   139  	}
   140  
   141  	return lisp.StringValue(fmt.Sprintf("%s", val.Read()))
   142  }
   143  
   144  type lispResult struct {
   145  	res *Result
   146  }
   147  
   148  func (lr *lispResult) Get(key string) (lisp.Value, bool) {
   149  	v, ok := lr.res.Get(key)
   150  
   151  	if !ok {
   152  		return lisp.Nil, false
   153  	}
   154  
   155  	return inferValue(v), true
   156  }
   157  
   158  type lispInferredScope struct {
   159  	Scope Scope
   160  }
   161  
   162  func (s lispInferredScope) Get(key string) (lisp.Value, bool) {
   163  	val, ok := s.Scope.Get(key)
   164  
   165  	if !ok {
   166  		return lisp.Nil, false
   167  	}
   168  
   169  	return inferValue(val), true
   170  }
   171  
   172  func (s lispInferredScope) Set(key string, v lisp.Value) lisp.Value {
   173  	s.Scope.Set(key, v.Interface())
   174  	return v
   175  }
   176  
   177  func (s lispInferredScope) Create(key string, v lisp.Value) lisp.Value {
   178  	s.Scope.Set(key, v.Interface())
   179  	return v
   180  }
   181  
   182  var cDollar = []byte(`$`)
   183  
   184  func ExpandVars(s Scope, args string) (string, error) {
   185  	args, err := expandTemplates(s, args)
   186  
   187  	if err != nil {
   188  		return "", err
   189  	}
   190  
   191  	a := []byte(args)
   192  
   193  	var buf bytes.Buffer
   194  
   195  	for {
   196  		idx := bytes.Index(a, cDollar)
   197  
   198  		if idx == -1 {
   199  			buf.Write(a)
   200  			break
   201  		} else if a[idx+1] == '(' {
   202  			buf.Write(a[:idx])
   203  
   204  			in := a[idx+1:]
   205  
   206  			fin := findExprClose(in)
   207  
   208  			if fin == -1 {
   209  				return "", eUnclosedExpr
   210  			}
   211  
   212  			sexp := in[:fin+1]
   213  
   214  			ls := lispInferredScope{s}
   215  
   216  			val, err := lisp.EvalString(string(sexp), ls)
   217  
   218  			if err != nil {
   219  				return "", err
   220  			}
   221  
   222  			buf.WriteString(val.String())
   223  			a = in[fin+1:]
   224  		} else {
   225  			buf.Write(a[:idx])
   226  
   227  			in := a[idx+1:]
   228  
   229  			fin := 0
   230  
   231  			for fin < len(in) {
   232  				if !varChar(rune(in[fin])) {
   233  					break
   234  				}
   235  				fin++
   236  			}
   237  
   238  			if val, ok := s.Get(string(in[:fin])); ok {
   239  				switch val := val.Read().(type) {
   240  				case int64, int:
   241  					buf.WriteString(fmt.Sprintf("%d", val))
   242  				default:
   243  					buf.WriteString(fmt.Sprintf("%s", val))
   244  				}
   245  
   246  				a = in[fin:]
   247  			} else {
   248  				return "", fmt.Errorf("Undefined variable: %s", string(in[:fin]))
   249  			}
   250  		}
   251  	}
   252  
   253  	return buf.String(), nil
   254  }