// Copyright 2015 Google Inc. All rights reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kati import ( "errors" "fmt" "io" "strings" "time" ) var shBuiltins = []struct { name string pattern expr compact func(*funcShell, []Value) Value }{ { name: "android:rot13", // in repo/android/build/core/definisions.mk // echo $(1) | tr 'a-zA-Z' 'n-za-mN-ZA-M' pattern: expr{ literal("echo "), matchVarref{}, literal(" | tr 'a-zA-Z' 'n-za-mN-ZA-M'"), }, compact: func(sh *funcShell, matches []Value) Value { return &funcShellAndroidRot13{ funcShell: sh, v: matches[0], } }, }, { name: "shell-date", pattern: expr{ mustLiteralRE(`date \+(\S+)`), }, compact: compactShellDate, }, { name: "shell-date-quoted", pattern: expr{ mustLiteralRE(`date "\+([^"]+)"`), }, compact: compactShellDate, }, } type funcShellAndroidRot13 struct { *funcShell v Value } func rot13(buf []byte) { for i, b := range buf { // tr 'a-zA-Z' 'n-za-mN-ZA-M' if b >= 'a' && b <= 'z' { b += 'n' - 'a' if b > 'z' { b -= 'z' - 'a' + 1 } } else if b >= 'A' && b <= 'Z' { b += 'N' - 'A' if b > 'Z' { b -= 'Z' - 'A' + 1 } } buf[i] = b } } func (f *funcShellAndroidRot13) Eval(w evalWriter, ev *Evaluator) error { abuf := newEbuf() fargs, err := ev.args(abuf, f.v) if err != nil { return err } rot13(fargs[0]) w.Write(fargs[0]) abuf.release() return nil } var ( // ShellDateTimestamp is an timestamp used for $(shell date). ShellDateTimestamp time.Time shellDateFormatRef = map[string]string{ "%Y": "2006", "%m": "01", "%d": "02", "%H": "15", "%M": "04", "%S": "05", "%b": "Jan", "%k": "15", // XXX } ) type funcShellDate struct { *funcShell format string } func compactShellDate(sh *funcShell, v []Value) Value { if ShellDateTimestamp.IsZero() { return sh } tf, ok := v[0].(literal) if !ok { return sh } tfstr := string(tf) for k, v := range shellDateFormatRef { tfstr = strings.Replace(tfstr, k, v, -1) } return &funcShellDate{ funcShell: sh, format: tfstr, } } func (f *funcShellDate) Eval(w evalWriter, ev *Evaluator) error { fmt.Fprint(w, ShellDateTimestamp.Format(f.format)) return nil } type buildinCommand interface { run(w evalWriter) } var errFindEmulatorDisabled = errors.New("builtin: find emulator disabled") func parseBuiltinCommand(cmd string) (buildinCommand, error) { if !UseFindEmulator { return nil, errFindEmulatorDisabled } if strings.HasPrefix(trimLeftSpace(cmd), "build/tools/findleaves") { return parseFindleavesCommand(cmd) } return parseFindCommand(cmd) } type shellParser struct { cmd string ungetToken string } func (p *shellParser) token() (string, error) { if p.ungetToken != "" { tok := p.ungetToken p.ungetToken = "" return tok, nil } p.cmd = trimLeftSpace(p.cmd) if len(p.cmd) == 0 { return "", io.EOF } if p.cmd[0] == ';' { tok := p.cmd[0:1] p.cmd = p.cmd[1:] return tok, nil } if p.cmd[0] == '&' { if len(p.cmd) == 1 || p.cmd[1] != '&' { return "", errFindBackground } tok := p.cmd[0:2] p.cmd = p.cmd[2:] return tok, nil } // TODO(ukai): redirect token. i := 0 for i < len(p.cmd) { if isWhitespace(rune(p.cmd[i])) || p.cmd[i] == ';' || p.cmd[i] == '&' { break } i++ } tok := p.cmd[0:i] p.cmd = p.cmd[i:] c := tok[0] if c == '\'' || c == '"' { if len(tok) < 2 || tok[len(tok)-1] != c { return "", errFindUnbalancedQuote } // todo: unquote? tok = tok[1 : len(tok)-1] } return tok, nil } func (p *shellParser) unget(s string) { if s != "" { p.ungetToken = s } } func (p *shellParser) expect(toks ...string) error { tok, err := p.token() if err != nil { return err } for _, t := range toks { if tok == t { return nil } } return fmt.Errorf("shell: token=%q; want=%q", tok, toks) } func (p *shellParser) expectSeq(toks ...string) error { for _, tok := range toks { err := p.expect(tok) if err != nil { return err } } return nil }