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