blob: 12224f6fdc2a41ece12e762fba1a375789368141 [file] [log] [blame] [edit]
// Copyright 2017 The LUCI Authors.
//
// 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 python
import (
"strings"
"unicode/utf8"
"github.com/luci/luci-go/common/errors"
)
// Target describes a Python invocation target.
//
// Targets are identified by parsing a Python command-line using
// ParseCommandLine.
//
// A Target is identified through type assertion, and will be one of:
//
// - NoTarget
// - ScriptTarget
// - CommandTarget
// - ModuleTarget
type Target interface {
// implementsTarget is an internal method used to constrain Target
// implementations to internal packages.
implementsTarget()
}
// NoTarget is a Target implementation indicating no Python target (i.e.,
// interactive).
type NoTarget struct{}
func (st NoTarget) implementsTarget() {}
// ScriptTarget is a Python executable script target.
type ScriptTarget struct {
// Path is the path to the script that is being invoked.
//
// This may be "-", indicating that the script is being read from STDIN.
Path string
}
func (st ScriptTarget) implementsTarget() {}
// CommandTarget is a Target implementation for a command-line string
// (-c ...).
type CommandTarget struct {
// Command is the command contents.
Command string
}
func (st CommandTarget) implementsTarget() {}
// ModuleTarget is a Target implementating indicating a Python module (-m ...).
type ModuleTarget struct {
// Module is the name of the target module.
Module string
}
func (st ModuleTarget) implementsTarget() {}
// CommandLine is a parsed Python command-line.
//
// CommandLine can be parsed from arguments via ParseCommandLine.
type CommandLine struct {
// Target is the Python target type.
Target Target
// Flags are flags to the Python interpreter.
Flags []string
// Args are arguments passed to the Python script.
Args []string
}
// ParseCommandLine parses Python command-line arguments and returns a
// structured representation.
func ParseCommandLine(args []string) (cmd CommandLine, err error) {
flags := 0
i := 0
for i < len(args) && cmd.Target == nil {
arg := args[i]
i++
flag, has := trimPrefix(arg, "-")
if !has {
// Non-flag argument, so path to script.
cmd.Target = ScriptTarget{
Path: flag,
}
break
}
// -<flag>
if len(flag) == 0 {
// "-" instructs Python to load the script from STDIN.
cmd.Target = ScriptTarget{
Path: "-",
}
break
}
// Extract the flag Target. -f<lag>
r, l := utf8.DecodeRuneInString(flag)
if r == utf8.RuneError {
err = errors.Reason("invalid rune in flag #%d", i).Err()
return
}
// Is this a combined flag/value (e.g., -c'paoskdpo') ?
flag = flag[l:]
twoVarType := func() (string, error) {
if len(flag) > 0 {
return flag, nil
}
if i >= len(args) {
return "", errors.Reason("two-value flag -%c missing second value at %d", r, i).Err()
}
value := args[i]
i++
return value, nil
}
switch r {
// Two-variable execution flags.
case 'c':
var target CommandTarget
if target.Command, err = twoVarType(); err != nil {
return
}
cmd.Target = target
case 'm':
var target ModuleTarget
if target.Module, err = twoVarType(); err != nil {
return
}
cmd.Target = target
case 'Q', 'W', 'X':
// Random two-argument Python flags.
if len(flag) == 0 {
flags++
i++
}
fallthrough
default:
// One-argument Python flags.
flags++
}
}
if i > len(args) {
err = errors.New("truncated two-variable argument")
return
}
// If no target was specified, use NoTarget.
if cmd.Target == nil {
cmd.Target = NoTarget{}
}
cmd.Flags = args[:flags]
cmd.Args = args[i:]
return
}
func trimPrefix(v, pfx string) (string, bool) {
if strings.HasPrefix(v, pfx) {
return v[len(pfx):], true
}
return v, false
}