232 lines
4.4 KiB
Go
232 lines
4.4 KiB
Go
// 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
|
|
}
|