| OLD | NEW |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. | 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package python | 5 package python |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" | |
| 9 "strings" | 8 "strings" |
| 10 "unicode/utf8" | 9 "unicode/utf8" |
| 11 | 10 |
| 12 "github.com/luci/luci-go/common/errors" | 11 "github.com/luci/luci-go/common/errors" |
| 13 ) | 12 ) |
| 14 | 13 |
| 15 // Error is a Python error return code. | 14 // Target describes a Python invocation target. |
| 16 type Error int | |
| 17 | |
| 18 func (err Error) Error() string { | |
| 19 » return fmt.Sprintf("python interpreter returned non-zero error: %d", err
) | |
| 20 } | |
| 21 | |
| 22 // TargetType is an enumeration of possible Python invocation targets. | |
| 23 // | 15 // |
| 24 // Targets are identified by parsing a Python command-line using | 16 // Targets are identified by parsing a Python command-line using |
| 25 // ParseCommandLine. | 17 // ParseCommandLine. |
| 26 type TargetType int | 18 // |
| 19 // A Target is identified through type assertion, and will be one of: |
| 20 // |
| 21 //» - NoTarget |
| 22 //» - ScriptTarget |
| 23 //» - CommandTarget |
| 24 //» - ModuleTarget |
| 25 type Target interface { |
| 26 » // implementsTarget is an internal method used to constrain Target |
| 27 » // implementations to internal packages. |
| 28 » implementsTarget() |
| 29 } |
| 27 | 30 |
| 28 const ( | 31 // NoTarget is a Target implementation indicating no Python target (i.e., |
| 29 » // TargetNone means no Python target (i.e., interactive). | 32 // interactive). |
| 30 » TargetNone TargetType = iota | 33 type NoTarget struct{} |
| 31 » // TargetScript is a Python executable script target. | 34 |
| 32 » TargetScript | 35 func (st NoTarget) implementsTarget() {} |
| 33 » // TargetCommand runs a command-line string (-c ...). | 36 |
| 34 » TargetCommand | 37 // ScriptTarget is a Python executable script target. |
| 35 » // TargetModule runs a Python module (-m ...). | 38 type ScriptTarget struct { |
| 36 » TargetModule | 39 » // Path is the path to the script that is being invoked. |
| 37 ) | 40 » // |
| 41 » // This may be "-", indicating that the script is being read from STDIN. |
| 42 » Path string |
| 43 } |
| 44 |
| 45 func (st ScriptTarget) implementsTarget() {} |
| 46 |
| 47 // CommandTarget is a Target implementation for a command-line string |
| 48 // (-c ...). |
| 49 type CommandTarget struct { |
| 50 » // Command is the command contents. |
| 51 » Command string |
| 52 } |
| 53 |
| 54 func (st CommandTarget) implementsTarget() {} |
| 55 |
| 56 // ModuleTarget is a Target implementating indicating a Python module (-m ...). |
| 57 type ModuleTarget struct { |
| 58 » // Module is the name of the target module. |
| 59 » Module string |
| 60 } |
| 61 |
| 62 func (st ModuleTarget) implementsTarget() {} |
| 38 | 63 |
| 39 // CommandLine is a parsed Python command-line. | 64 // CommandLine is a parsed Python command-line. |
| 40 // | 65 // |
| 41 // CommandLine can be parsed from arguments via ParseCommandLine. | 66 // CommandLine can be parsed from arguments via ParseCommandLine. |
| 42 type CommandLine struct { | 67 type CommandLine struct { |
| 43 » // Type is the Python target type. | 68 » // Target is the Python target type. |
| 44 » Type TargetType | 69 » Target Target |
| 45 » // Value is the execution value for Type. | |
| 46 » Value string | |
| 47 | 70 |
| 48 // Flags are flags to the Python interpreter. | 71 // Flags are flags to the Python interpreter. |
| 49 Flags []string | 72 Flags []string |
| 50 // Args are arguments passed to the Python script. | 73 // Args are arguments passed to the Python script. |
| 51 Args []string | 74 Args []string |
| 52 } | 75 } |
| 53 | 76 |
| 54 // ParseCommandLine parses Python command-line arguments and returns a | 77 // ParseCommandLine parses Python command-line arguments and returns a |
| 55 // structured representation. | 78 // structured representation. |
| 56 func ParseCommandLine(args []string) (cmd CommandLine, err error) { | 79 func ParseCommandLine(args []string) (cmd CommandLine, err error) { |
| 57 flags := 0 | 80 flags := 0 |
| 58 i := 0 | 81 i := 0 |
| 59 » for i < len(args) && cmd.Type == TargetNone { | 82 » for i < len(args) && cmd.Target == nil { |
| 60 arg := args[i] | 83 arg := args[i] |
| 61 i++ | 84 i++ |
| 62 | 85 |
| 63 flag, has := trimPrefix(arg, "-") | 86 flag, has := trimPrefix(arg, "-") |
| 64 if !has { | 87 if !has { |
| 65 // Non-flag argument, so path to script. | 88 // Non-flag argument, so path to script. |
| 66 » » » cmd.Type = TargetScript | 89 » » » cmd.Target = ScriptTarget{ |
| 67 » » » cmd.Value = flag | 90 » » » » Path: flag, |
| 91 » » » } |
| 68 break | 92 break |
| 69 } | 93 } |
| 70 | 94 |
| 71 // -<flag> | 95 // -<flag> |
| 72 if len(flag) == 0 { | 96 if len(flag) == 0 { |
| 73 // "-" instructs Python to load the script from STDIN. | 97 // "-" instructs Python to load the script from STDIN. |
| 74 » » » cmd.Type, cmd.Value = TargetScript, "-" | 98 » » » cmd.Target = ScriptTarget{ |
| 99 » » » » Path: "-", |
| 100 » » » } |
| 75 break | 101 break |
| 76 } | 102 } |
| 77 | 103 |
| 78 » » // Extract the flag value. -f<lag> | 104 » » // Extract the flag Target. -f<lag> |
| 79 r, l := utf8.DecodeRuneInString(flag) | 105 r, l := utf8.DecodeRuneInString(flag) |
| 80 if r == utf8.RuneError { | 106 if r == utf8.RuneError { |
| 81 err = errors.Reason("invalid rune in flag #%(index)d").D
("index", i).Err() | 107 err = errors.Reason("invalid rune in flag #%(index)d").D
("index", i).Err() |
| 82 return | 108 return |
| 83 } | 109 } |
| 84 | 110 |
| 85 // Is this a combined flag/value (e.g., -c'paoskdpo') ? | 111 // Is this a combined flag/value (e.g., -c'paoskdpo') ? |
| 86 flag = flag[l:] | 112 flag = flag[l:] |
| 87 » » twoVarType := func() error { | 113 » » twoVarType := func() (string, error) { |
| 88 if len(flag) > 0 { | 114 if len(flag) > 0 { |
| 89 » » » » cmd.Value = flag | 115 » » » » return flag, nil |
| 90 » » » » return nil | |
| 91 } | 116 } |
| 92 | 117 |
| 93 if i >= len(args) { | 118 if i >= len(args) { |
| 94 » » » » return errors.Reason("two-value flag -%(flag)c m
issing second value at %(index)d"). | 119 » » » » return "", errors.Reason("two-value flag -%(flag
)c missing second value at %(index)d"). |
| 95 D("flag", r). | 120 D("flag", r). |
| 96 D("index", i). | 121 D("index", i). |
| 97 Err() | 122 Err() |
| 98 } | 123 } |
| 99 » » » cmd.Value = args[i] | 124 |
| 125 » » » value := args[i] |
| 100 i++ | 126 i++ |
| 101 » » » return nil | 127 » » » return value, nil |
| 102 } | 128 } |
| 103 | 129 |
| 104 switch r { | 130 switch r { |
| 105 // Two-variable execution flags. | 131 // Two-variable execution flags. |
| 106 case 'c': | 132 case 'c': |
| 107 » » » cmd.Type = TargetCommand | 133 » » » var target CommandTarget |
| 108 » » » if err = twoVarType(); err != nil { | 134 » » » if target.Command, err = twoVarType(); err != nil { |
| 109 return | 135 return |
| 110 } | 136 } |
| 137 cmd.Target = target |
| 111 | 138 |
| 112 case 'm': | 139 case 'm': |
| 113 » » » cmd.Type = TargetModule | 140 » » » var target ModuleTarget |
| 114 » » » if err = twoVarType(); err != nil { | 141 » » » if target.Module, err = twoVarType(); err != nil { |
| 115 return | 142 return |
| 116 } | 143 } |
| 144 cmd.Target = target |
| 117 | 145 |
| 118 case 'Q', 'W', 'X': | 146 case 'Q', 'W', 'X': |
| 119 // Random two-argument Python flags. | 147 // Random two-argument Python flags. |
| 120 if len(flag) == 0 { | 148 if len(flag) == 0 { |
| 121 flags++ | 149 flags++ |
| 122 i++ | 150 i++ |
| 123 } | 151 } |
| 124 fallthrough | 152 fallthrough |
| 125 | 153 |
| 126 default: | 154 default: |
| 127 // One-argument Python flags. | 155 // One-argument Python flags. |
| 128 flags++ | 156 flags++ |
| 129 } | 157 } |
| 130 } | 158 } |
| 131 | 159 |
| 132 if i > len(args) { | 160 if i > len(args) { |
| 133 err = errors.New("truncated two-variable argument") | 161 err = errors.New("truncated two-variable argument") |
| 134 return | 162 return |
| 135 } | 163 } |
| 136 | 164 |
| 165 // If no target was specified, use NoTarget. |
| 166 if cmd.Target == nil { |
| 167 cmd.Target = NoTarget{} |
| 168 } |
| 169 |
| 137 cmd.Flags = args[:flags] | 170 cmd.Flags = args[:flags] |
| 138 cmd.Args = args[i:] | 171 cmd.Args = args[i:] |
| 139 return | 172 return |
| 140 } | 173 } |
| 141 | 174 |
| 142 func trimPrefix(v, pfx string) (string, bool) { | 175 func trimPrefix(v, pfx string) (string, bool) { |
| 143 if strings.HasPrefix(v, pfx) { | 176 if strings.HasPrefix(v, pfx) { |
| 144 return v[len(pfx):], true | 177 return v[len(pfx):], true |
| 145 } | 178 } |
| 146 return v, false | 179 return v, false |
| 147 } | 180 } |
| OLD | NEW |