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