This adds support for Dockerfile commands to have options - e.g:
COPY --user=john foo /tmp/
COPY --ignore-mtime foo /tmp/
Supports both booleans and strings.
Signed-off-by: Doug Davis <dug@us.ibm.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,155 @@ |
| 0 |
+package builder |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+type FlagType int |
|
| 8 |
+ |
|
| 9 |
+const ( |
|
| 10 |
+ boolType FlagType = iota |
|
| 11 |
+ stringType |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+type BuilderFlags struct {
|
|
| 15 |
+ Args []string // actual flags/args from cmd line |
|
| 16 |
+ flags map[string]*Flag |
|
| 17 |
+ used map[string]*Flag |
|
| 18 |
+ Err error |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+type Flag struct {
|
|
| 22 |
+ bf *BuilderFlags |
|
| 23 |
+ name string |
|
| 24 |
+ flagType FlagType |
|
| 25 |
+ Value string |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+func NewBuilderFlags() *BuilderFlags {
|
|
| 29 |
+ return &BuilderFlags{
|
|
| 30 |
+ flags: make(map[string]*Flag), |
|
| 31 |
+ used: make(map[string]*Flag), |
|
| 32 |
+ } |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func (bf *BuilderFlags) AddBool(name string, def bool) *Flag {
|
|
| 36 |
+ flag := bf.addFlag(name, boolType) |
|
| 37 |
+ if flag == nil {
|
|
| 38 |
+ return nil |
|
| 39 |
+ } |
|
| 40 |
+ if def {
|
|
| 41 |
+ flag.Value = "true" |
|
| 42 |
+ } else {
|
|
| 43 |
+ flag.Value = "false" |
|
| 44 |
+ } |
|
| 45 |
+ return flag |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func (bf *BuilderFlags) AddString(name string, def string) *Flag {
|
|
| 49 |
+ flag := bf.addFlag(name, stringType) |
|
| 50 |
+ if flag == nil {
|
|
| 51 |
+ return nil |
|
| 52 |
+ } |
|
| 53 |
+ flag.Value = def |
|
| 54 |
+ return flag |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+func (bf *BuilderFlags) addFlag(name string, flagType FlagType) *Flag {
|
|
| 58 |
+ if _, ok := bf.flags[name]; ok {
|
|
| 59 |
+ bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
|
| 60 |
+ return nil |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ newFlag := &Flag{
|
|
| 64 |
+ bf: bf, |
|
| 65 |
+ name: name, |
|
| 66 |
+ flagType: flagType, |
|
| 67 |
+ } |
|
| 68 |
+ bf.flags[name] = newFlag |
|
| 69 |
+ |
|
| 70 |
+ return newFlag |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+func (fl *Flag) IsUsed() bool {
|
|
| 74 |
+ if _, ok := fl.bf.used[fl.name]; ok {
|
|
| 75 |
+ return true |
|
| 76 |
+ } |
|
| 77 |
+ return false |
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+func (fl *Flag) IsTrue() bool {
|
|
| 81 |
+ if fl.flagType != boolType {
|
|
| 82 |
+ // Should never get here |
|
| 83 |
+ panic(fmt.Errorf("Trying to use IsTrue on a non-boolean: %s", fl.name))
|
|
| 84 |
+ } |
|
| 85 |
+ return fl.Value == "true" |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+func (bf *BuilderFlags) Parse() error {
|
|
| 89 |
+ // If there was an error while defining the possible flags |
|
| 90 |
+ // go ahead and bubble it back up here since we didn't do it |
|
| 91 |
+ // earlier in the processing |
|
| 92 |
+ if bf.Err != nil {
|
|
| 93 |
+ return fmt.Errorf("Error setting up flags: %s", bf.Err)
|
|
| 94 |
+ } |
|
| 95 |
+ |
|
| 96 |
+ for _, arg := range bf.Args {
|
|
| 97 |
+ if !strings.HasPrefix(arg, "--") {
|
|
| 98 |
+ return fmt.Errorf("Arg should start with -- : %s", arg)
|
|
| 99 |
+ } |
|
| 100 |
+ |
|
| 101 |
+ if arg == "--" {
|
|
| 102 |
+ return nil |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ arg = arg[2:] |
|
| 106 |
+ value := "" |
|
| 107 |
+ |
|
| 108 |
+ index := strings.Index(arg, "=") |
|
| 109 |
+ if index >= 0 {
|
|
| 110 |
+ value = arg[index+1:] |
|
| 111 |
+ arg = arg[:index] |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ flag, ok := bf.flags[arg] |
|
| 115 |
+ if !ok {
|
|
| 116 |
+ return fmt.Errorf("Unknown flag: %s", arg)
|
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 119 |
+ if _, ok = bf.used[arg]; ok {
|
|
| 120 |
+ return fmt.Errorf("Duplicate flag specified: %s", arg)
|
|
| 121 |
+ } |
|
| 122 |
+ |
|
| 123 |
+ bf.used[arg] = flag |
|
| 124 |
+ |
|
| 125 |
+ switch flag.flagType {
|
|
| 126 |
+ case boolType: |
|
| 127 |
+ // value == "" is only ok if no "=" was specified |
|
| 128 |
+ if index >= 0 && value == "" {
|
|
| 129 |
+ return fmt.Errorf("Missing a value on flag: %s", arg)
|
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ lower := strings.ToLower(value) |
|
| 133 |
+ if lower == "" {
|
|
| 134 |
+ flag.Value = "true" |
|
| 135 |
+ } else if lower == "true" || lower == "false" {
|
|
| 136 |
+ flag.Value = lower |
|
| 137 |
+ } else {
|
|
| 138 |
+ return fmt.Errorf("Expecting boolean value for flag %s, not: %s", arg, value)
|
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ case stringType: |
|
| 142 |
+ if index < 0 {
|
|
| 143 |
+ return fmt.Errorf("Missing a value on flag: %s", arg)
|
|
| 144 |
+ } |
|
| 145 |
+ flag.Value = value |
|
| 146 |
+ |
|
| 147 |
+ default: |
|
| 148 |
+ panic(fmt.Errorf("No idea what kind of flag we have! Should never get here!"))
|
|
| 149 |
+ } |
|
| 150 |
+ |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ return nil |
|
| 154 |
+} |
| 0 | 155 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,187 @@ |
| 0 |
+package builder |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestBuilderFlags(t *testing.T) {
|
|
| 7 |
+ var expected string |
|
| 8 |
+ var err error |
|
| 9 |
+ |
|
| 10 |
+ // --- |
|
| 11 |
+ |
|
| 12 |
+ bf := NewBuilderFlags() |
|
| 13 |
+ bf.Args = []string{}
|
|
| 14 |
+ if err := bf.Parse(); err != nil {
|
|
| 15 |
+ t.Fatalf("Test1 of %q was supposed to work: %s", bf.Args, err)
|
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ // --- |
|
| 19 |
+ |
|
| 20 |
+ bf = NewBuilderFlags() |
|
| 21 |
+ bf.Args = []string{"--"}
|
|
| 22 |
+ if err := bf.Parse(); err != nil {
|
|
| 23 |
+ t.Fatalf("Test2 of %q was supposed to work: %s", bf.Args, err)
|
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ // --- |
|
| 27 |
+ |
|
| 28 |
+ bf = NewBuilderFlags() |
|
| 29 |
+ flStr1 := bf.AddString("str1", "")
|
|
| 30 |
+ flBool1 := bf.AddBool("bool1", false)
|
|
| 31 |
+ bf.Args = []string{}
|
|
| 32 |
+ if err = bf.Parse(); err != nil {
|
|
| 33 |
+ t.Fatalf("Test3 of %q was supposed to work: %s", bf.Args, err)
|
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ if flStr1.IsUsed() == true {
|
|
| 37 |
+ t.Fatalf("Test3 - str1 was not used!")
|
|
| 38 |
+ } |
|
| 39 |
+ if flBool1.IsUsed() == true {
|
|
| 40 |
+ t.Fatalf("Test3 - bool1 was not used!")
|
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ // --- |
|
| 44 |
+ |
|
| 45 |
+ bf = NewBuilderFlags() |
|
| 46 |
+ flStr1 = bf.AddString("str1", "HI")
|
|
| 47 |
+ flBool1 = bf.AddBool("bool1", false)
|
|
| 48 |
+ bf.Args = []string{}
|
|
| 49 |
+ |
|
| 50 |
+ if err = bf.Parse(); err != nil {
|
|
| 51 |
+ t.Fatalf("Test4 of %q was supposed to work: %s", bf.Args, err)
|
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ if flStr1.Value != "HI" {
|
|
| 55 |
+ t.Fatalf("Str1 was supposed to default to: HI")
|
|
| 56 |
+ } |
|
| 57 |
+ if flBool1.IsTrue() {
|
|
| 58 |
+ t.Fatalf("Bool1 was supposed to default to: false")
|
|
| 59 |
+ } |
|
| 60 |
+ if flStr1.IsUsed() == true {
|
|
| 61 |
+ t.Fatalf("Str1 was not used!")
|
|
| 62 |
+ } |
|
| 63 |
+ if flBool1.IsUsed() == true {
|
|
| 64 |
+ t.Fatalf("Bool1 was not used!")
|
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ // --- |
|
| 68 |
+ |
|
| 69 |
+ bf = NewBuilderFlags() |
|
| 70 |
+ flStr1 = bf.AddString("str1", "HI")
|
|
| 71 |
+ bf.Args = []string{"--str1"}
|
|
| 72 |
+ |
|
| 73 |
+ if err = bf.Parse(); err == nil {
|
|
| 74 |
+ t.Fatalf("Test %q was supposed to fail", bf.Args)
|
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ // --- |
|
| 78 |
+ |
|
| 79 |
+ bf = NewBuilderFlags() |
|
| 80 |
+ flStr1 = bf.AddString("str1", "HI")
|
|
| 81 |
+ bf.Args = []string{"--str1="}
|
|
| 82 |
+ |
|
| 83 |
+ if err = bf.Parse(); err != nil {
|
|
| 84 |
+ t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
|
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ expected = "" |
|
| 88 |
+ if flStr1.Value != expected {
|
|
| 89 |
+ t.Fatalf("Str1 (%q) should be: %q", flStr1.Value, expected)
|
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ // --- |
|
| 93 |
+ |
|
| 94 |
+ bf = NewBuilderFlags() |
|
| 95 |
+ flStr1 = bf.AddString("str1", "HI")
|
|
| 96 |
+ bf.Args = []string{"--str1=BYE"}
|
|
| 97 |
+ |
|
| 98 |
+ if err = bf.Parse(); err != nil {
|
|
| 99 |
+ t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
|
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ expected = "BYE" |
|
| 103 |
+ if flStr1.Value != expected {
|
|
| 104 |
+ t.Fatalf("Str1 (%q) should be: %q", flStr1.Value, expected)
|
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ // --- |
|
| 108 |
+ |
|
| 109 |
+ bf = NewBuilderFlags() |
|
| 110 |
+ flBool1 = bf.AddBool("bool1", false)
|
|
| 111 |
+ bf.Args = []string{"--bool1"}
|
|
| 112 |
+ |
|
| 113 |
+ if err = bf.Parse(); err != nil {
|
|
| 114 |
+ t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
|
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ if !flBool1.IsTrue() {
|
|
| 118 |
+ t.Fatalf("Test-b1 Bool1 was supposed to be true")
|
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ // --- |
|
| 122 |
+ |
|
| 123 |
+ bf = NewBuilderFlags() |
|
| 124 |
+ flBool1 = bf.AddBool("bool1", false)
|
|
| 125 |
+ bf.Args = []string{"--bool1=true"}
|
|
| 126 |
+ |
|
| 127 |
+ if err = bf.Parse(); err != nil {
|
|
| 128 |
+ t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
|
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ if !flBool1.IsTrue() {
|
|
| 132 |
+ t.Fatalf("Test-b2 Bool1 was supposed to be true")
|
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ // --- |
|
| 136 |
+ |
|
| 137 |
+ bf = NewBuilderFlags() |
|
| 138 |
+ flBool1 = bf.AddBool("bool1", false)
|
|
| 139 |
+ bf.Args = []string{"--bool1=false"}
|
|
| 140 |
+ |
|
| 141 |
+ if err = bf.Parse(); err != nil {
|
|
| 142 |
+ t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
|
|
| 143 |
+ } |
|
| 144 |
+ |
|
| 145 |
+ if flBool1.IsTrue() {
|
|
| 146 |
+ t.Fatalf("Test-b3 Bool1 was supposed to be false")
|
|
| 147 |
+ } |
|
| 148 |
+ |
|
| 149 |
+ // --- |
|
| 150 |
+ |
|
| 151 |
+ bf = NewBuilderFlags() |
|
| 152 |
+ flBool1 = bf.AddBool("bool1", false)
|
|
| 153 |
+ bf.Args = []string{"--bool1=false1"}
|
|
| 154 |
+ |
|
| 155 |
+ if err = bf.Parse(); err == nil {
|
|
| 156 |
+ t.Fatalf("Test %q was supposed to fail", bf.Args)
|
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ // --- |
|
| 160 |
+ |
|
| 161 |
+ bf = NewBuilderFlags() |
|
| 162 |
+ flBool1 = bf.AddBool("bool1", false)
|
|
| 163 |
+ bf.Args = []string{"--bool2"}
|
|
| 164 |
+ |
|
| 165 |
+ if err = bf.Parse(); err == nil {
|
|
| 166 |
+ t.Fatalf("Test %q was supposed to fail", bf.Args)
|
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ // --- |
|
| 170 |
+ |
|
| 171 |
+ bf = NewBuilderFlags() |
|
| 172 |
+ flStr1 = bf.AddString("str1", "HI")
|
|
| 173 |
+ flBool1 = bf.AddBool("bool1", false)
|
|
| 174 |
+ bf.Args = []string{"--bool1", "--str1=BYE"}
|
|
| 175 |
+ |
|
| 176 |
+ if err = bf.Parse(); err != nil {
|
|
| 177 |
+ t.Fatalf("Test %q was supposed to work: %s", bf.Args, err)
|
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ if flStr1.Value != "BYE" {
|
|
| 181 |
+ t.Fatalf("Teset %s, str1 should be BYE", bf.Args)
|
|
| 182 |
+ } |
|
| 183 |
+ if !flBool1.IsTrue() {
|
|
| 184 |
+ t.Fatalf("Teset %s, bool1 should be true", bf.Args)
|
|
| 185 |
+ } |
|
| 186 |
+} |
| ... | ... |
@@ -47,6 +47,22 @@ func env(b *Builder, args []string, attributes map[string]bool, original string) |
| 47 | 47 |
return fmt.Errorf("Bad input to ENV, too many args")
|
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 |
+ // TODO/FIXME/NOT USED |
|
| 51 |
+ // Just here to show how to use the builder flags stuff within the |
|
| 52 |
+ // context of a builder command. Will remove once we actually add |
|
| 53 |
+ // a builder command to something! |
|
| 54 |
+ /* |
|
| 55 |
+ flBool1 := b.BuilderFlags.AddBool("bool1", false)
|
|
| 56 |
+ flStr1 := b.BuilderFlags.AddString("str1", "HI")
|
|
| 57 |
+ |
|
| 58 |
+ if err := b.BuilderFlags.Parse(); err != nil {
|
|
| 59 |
+ return err |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ fmt.Printf("Bool1:%v\n", flBool1)
|
|
| 63 |
+ fmt.Printf("Str1:%v\n", flStr1)
|
|
| 64 |
+ */ |
|
| 65 |
+ |
|
| 50 | 66 |
commitStr := "ENV" |
| 51 | 67 |
|
| 52 | 68 |
for j := 0; j < len(args); j++ {
|
| ... | ... |
@@ -116,6 +116,7 @@ type Builder struct {
|
| 116 | 116 |
image string // image name for commit processing |
| 117 | 117 |
maintainer string // maintainer name. could probably be removed. |
| 118 | 118 |
cmdSet bool // indicates is CMD was set in current Dockerfile |
| 119 |
+ BuilderFlags *BuilderFlags // current cmd's BuilderFlags - temporary |
|
| 119 | 120 |
context tarsum.TarSum // the context is a tarball that is uploaded by the client |
| 120 | 121 |
contextPath string // the path of the temporary directory the local context is unpacked to (server side) |
| 121 | 122 |
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system. |
| ... | ... |
@@ -276,8 +277,9 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
| 276 | 276 |
cmd := ast.Value |
| 277 | 277 |
attrs := ast.Attributes |
| 278 | 278 |
original := ast.Original |
| 279 |
+ flags := ast.Flags |
|
| 279 | 280 |
strs := []string{}
|
| 280 |
- msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
|
|
| 281 |
+ msg := fmt.Sprintf("Step %d : %s", stepN, original)
|
|
| 281 | 282 |
|
| 282 | 283 |
if cmd == "onbuild" {
|
| 283 | 284 |
if ast.Next == nil {
|
| ... | ... |
@@ -325,6 +327,8 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
| 325 | 325 |
// XXX yes, we skip any cmds that are not valid; the parser should have |
| 326 | 326 |
// picked these out already. |
| 327 | 327 |
if f, ok := evaluateTable[cmd]; ok {
|
| 328 |
+ b.BuilderFlags = NewBuilderFlags() |
|
| 329 |
+ b.BuilderFlags.Args = flags |
|
| 328 | 330 |
return f(b, strList, attrs, original) |
| 329 | 331 |
} |
| 330 | 332 |
|
| ... | ... |
@@ -29,6 +29,7 @@ type Node struct {
|
| 29 | 29 |
Children []*Node // the children of this sexp |
| 30 | 30 |
Attributes map[string]bool // special attributes for this node |
| 31 | 31 |
Original string // original line used before parsing |
| 32 |
+ Flags []string // only top Node should have this set |
|
| 32 | 33 |
} |
| 33 | 34 |
|
| 34 | 35 |
var ( |
| ... | ... |
@@ -75,7 +76,7 @@ func parseLine(line string) (string, *Node, error) {
|
| 75 | 75 |
return line, nil, nil |
| 76 | 76 |
} |
| 77 | 77 |
|
| 78 |
- cmd, args, err := splitCommand(line) |
|
| 78 |
+ cmd, flags, args, err := splitCommand(line) |
|
| 79 | 79 |
if err != nil {
|
| 80 | 80 |
return "", nil, err |
| 81 | 81 |
} |
| ... | ... |
@@ -91,6 +92,7 @@ func parseLine(line string) (string, *Node, error) {
|
| 91 | 91 |
node.Next = sexp |
| 92 | 92 |
node.Attributes = attrs |
| 93 | 93 |
node.Original = line |
| 94 |
+ node.Flags = flags |
|
| 94 | 95 |
|
| 95 | 96 |
return "", node, nil |
| 96 | 97 |
} |
| 97 | 98 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,10 @@ |
| 0 |
+FROM scratch |
|
| 1 |
+COPY foo /tmp/ |
|
| 2 |
+COPY --user=me foo /tmp/ |
|
| 3 |
+COPY --doit=true foo /tmp/ |
|
| 4 |
+COPY --user=me --doit=true foo /tmp/ |
|
| 5 |
+COPY --doit=true -- foo /tmp/ |
|
| 6 |
+COPY -- foo /tmp/ |
|
| 7 |
+CMD --doit [ "a", "b" ] |
|
| 8 |
+CMD --doit=true -- [ "a", "b" ] |
|
| 9 |
+CMD --doit -- [ ] |
| 0 | 10 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,10 @@ |
| 0 |
+(from "scratch") |
|
| 1 |
+(copy "foo" "/tmp/") |
|
| 2 |
+(copy ["--user=me"] "foo" "/tmp/") |
|
| 3 |
+(copy ["--doit=true"] "foo" "/tmp/") |
|
| 4 |
+(copy ["--user=me" "--doit=true"] "foo" "/tmp/") |
|
| 5 |
+(copy ["--doit=true"] "foo" "/tmp/") |
|
| 6 |
+(copy "foo" "/tmp/") |
|
| 7 |
+(cmd ["--doit"] "a" "b") |
|
| 8 |
+(cmd ["--doit=true"] "a" "b") |
|
| 9 |
+(cmd ["--doit"]) |
| ... | ... |
@@ -1,8 +1,10 @@ |
| 1 | 1 |
package parser |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 4 | 5 |
"strconv" |
| 5 | 6 |
"strings" |
| 7 |
+ "unicode" |
|
| 6 | 8 |
) |
| 7 | 9 |
|
| 8 | 10 |
// dumps the AST defined by `node` as a list of sexps. Returns a string |
| ... | ... |
@@ -11,6 +13,10 @@ func (node *Node) Dump() string {
|
| 11 | 11 |
str := "" |
| 12 | 12 |
str += node.Value |
| 13 | 13 |
|
| 14 |
+ if len(node.Flags) > 0 {
|
|
| 15 |
+ str += fmt.Sprintf(" %q", node.Flags)
|
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 14 | 18 |
for _, n := range node.Children {
|
| 15 | 19 |
str += "(" + n.Dump() + ")\n"
|
| 16 | 20 |
} |
| ... | ... |
@@ -48,20 +54,23 @@ func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
|
| 48 | 48 |
|
| 49 | 49 |
// splitCommand takes a single line of text and parses out the cmd and args, |
| 50 | 50 |
// which are used for dispatching to more exact parsing functions. |
| 51 |
-func splitCommand(line string) (string, string, error) {
|
|
| 51 |
+func splitCommand(line string) (string, []string, string, error) {
|
|
| 52 | 52 |
var args string |
| 53 |
+ var flags []string |
|
| 53 | 54 |
|
| 54 | 55 |
// Make sure we get the same results irrespective of leading/trailing spaces |
| 55 | 56 |
cmdline := TOKEN_WHITESPACE.Split(strings.TrimSpace(line), 2) |
| 56 | 57 |
cmd := strings.ToLower(cmdline[0]) |
| 57 | 58 |
|
| 58 | 59 |
if len(cmdline) == 2 {
|
| 59 |
- args = strings.TrimSpace(cmdline[1]) |
|
| 60 |
+ var err error |
|
| 61 |
+ args, flags, err = extractBuilderFlags(cmdline[1]) |
|
| 62 |
+ if err != nil {
|
|
| 63 |
+ return "", nil, "", err |
|
| 64 |
+ } |
|
| 60 | 65 |
} |
| 61 | 66 |
|
| 62 |
- // the cmd should never have whitespace, but it's possible for the args to |
|
| 63 |
- // have trailing whitespace. |
|
| 64 |
- return cmd, args, nil |
|
| 67 |
+ return cmd, flags, strings.TrimSpace(args), nil |
|
| 65 | 68 |
} |
| 66 | 69 |
|
| 67 | 70 |
// covers comments and empty lines. Lines should be trimmed before passing to |
| ... | ... |
@@ -74,3 +83,94 @@ func stripComments(line string) string {
|
| 74 | 74 |
|
| 75 | 75 |
return line |
| 76 | 76 |
} |
| 77 |
+ |
|
| 78 |
+func extractBuilderFlags(line string) (string, []string, error) {
|
|
| 79 |
+ // Parses the BuilderFlags and returns the remaining part of the line |
|
| 80 |
+ |
|
| 81 |
+ const ( |
|
| 82 |
+ inSpaces = iota // looking for start of a word |
|
| 83 |
+ inWord |
|
| 84 |
+ inQuote |
|
| 85 |
+ ) |
|
| 86 |
+ |
|
| 87 |
+ words := []string{}
|
|
| 88 |
+ phase := inSpaces |
|
| 89 |
+ word := "" |
|
| 90 |
+ quote := '\000' |
|
| 91 |
+ blankOK := false |
|
| 92 |
+ var ch rune |
|
| 93 |
+ |
|
| 94 |
+ for pos := 0; pos <= len(line); pos++ {
|
|
| 95 |
+ if pos != len(line) {
|
|
| 96 |
+ ch = rune(line[pos]) |
|
| 97 |
+ } |
|
| 98 |
+ |
|
| 99 |
+ if phase == inSpaces { // Looking for start of word
|
|
| 100 |
+ if pos == len(line) { // end of input
|
|
| 101 |
+ break |
|
| 102 |
+ } |
|
| 103 |
+ if unicode.IsSpace(ch) { // skip spaces
|
|
| 104 |
+ continue |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ // Only keep going if the next word starts with -- |
|
| 108 |
+ if ch != '-' || pos+1 == len(line) || rune(line[pos+1]) != '-' {
|
|
| 109 |
+ return line[pos:], words, nil |
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 112 |
+ phase = inWord // found someting with "--", fall thru |
|
| 113 |
+ } |
|
| 114 |
+ if (phase == inWord || phase == inQuote) && (pos == len(line)) {
|
|
| 115 |
+ if word != "--" && (blankOK || len(word) > 0) {
|
|
| 116 |
+ words = append(words, word) |
|
| 117 |
+ } |
|
| 118 |
+ break |
|
| 119 |
+ } |
|
| 120 |
+ if phase == inWord {
|
|
| 121 |
+ if unicode.IsSpace(ch) {
|
|
| 122 |
+ phase = inSpaces |
|
| 123 |
+ if word == "--" {
|
|
| 124 |
+ return line[pos:], words, nil |
|
| 125 |
+ } |
|
| 126 |
+ if blankOK || len(word) > 0 {
|
|
| 127 |
+ words = append(words, word) |
|
| 128 |
+ } |
|
| 129 |
+ word = "" |
|
| 130 |
+ blankOK = false |
|
| 131 |
+ continue |
|
| 132 |
+ } |
|
| 133 |
+ if ch == '\'' || ch == '"' {
|
|
| 134 |
+ quote = ch |
|
| 135 |
+ blankOK = true |
|
| 136 |
+ phase = inQuote |
|
| 137 |
+ continue |
|
| 138 |
+ } |
|
| 139 |
+ if ch == '\\' {
|
|
| 140 |
+ if pos+1 == len(line) {
|
|
| 141 |
+ continue // just skip \ at end |
|
| 142 |
+ } |
|
| 143 |
+ pos++ |
|
| 144 |
+ ch = rune(line[pos]) |
|
| 145 |
+ } |
|
| 146 |
+ word += string(ch) |
|
| 147 |
+ continue |
|
| 148 |
+ } |
|
| 149 |
+ if phase == inQuote {
|
|
| 150 |
+ if ch == quote {
|
|
| 151 |
+ phase = inWord |
|
| 152 |
+ continue |
|
| 153 |
+ } |
|
| 154 |
+ if ch == '\\' {
|
|
| 155 |
+ if pos+1 == len(line) {
|
|
| 156 |
+ phase = inWord |
|
| 157 |
+ continue // just skip \ at end |
|
| 158 |
+ } |
|
| 159 |
+ pos++ |
|
| 160 |
+ ch = rune(line[pos]) |
|
| 161 |
+ } |
|
| 162 |
+ word += string(ch) |
|
| 163 |
+ } |
|
| 164 |
+ } |
|
| 165 |
+ |
|
| 166 |
+ return "", words, nil |
|
| 167 |
+} |