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