internal/cli: hoist cli parsing code from tool/env

Change-Id: I4983d7e1dd13816b4bb2db798b87bfb421ffb52b
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4901
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/cli/cli.go b/internal/cli/cli.go
new file mode 100644
index 0000000..f6ffd25
--- /dev/null
+++ b/internal/cli/cli.go
@@ -0,0 +1,91 @@
+// Copyright 2020 CUE 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 cli
+
+import (
+	"strings"
+
+	"cuelang.org/go/cue"
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+)
+
+func ParseValue(pos token.Pos, name, str string, k cue.Kind) (x ast.Expr, errs errors.Error) {
+	var expr ast.Expr
+
+	if k&cue.NumberKind != 0 {
+		var err error
+		expr, err = parser.ParseExpr(name, str)
+		if err != nil {
+			errs = errors.Wrapf(err, pos,
+				"invalid number for environment variable %s", name)
+		}
+	}
+
+	if k&cue.BoolKind != 0 {
+		str = strings.TrimSpace(str)
+		b, ok := boolValues[str]
+		if !ok {
+			errs = errors.Append(errs, errors.Newf(pos,
+				"invalid boolean value %q for environment variable %s", str, name))
+		} else if expr != nil || k&cue.StringKind != 0 {
+			// Convert into an expression
+			bl := ast.NewBool(b)
+			if expr != nil {
+				expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: bl}
+			} else {
+				expr = bl
+			}
+		} else {
+			x = ast.NewBool(b)
+		}
+	}
+
+	if k&cue.StringKind != 0 {
+		if expr != nil {
+			expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: ast.NewString(str)}
+		} else {
+			x = ast.NewString(str)
+		}
+	}
+
+	switch {
+	case expr != nil:
+		return expr, nil
+	case x != nil:
+		return x, nil
+	case errs == nil:
+		return nil, errors.Newf(pos,
+			"invalid type for environment variable %s", name)
+	}
+	return nil, errs
+}
+
+var boolValues = map[string]bool{
+	"1":     true,
+	"0":     false,
+	"t":     true,
+	"f":     false,
+	"T":     true,
+	"F":     false,
+	"true":  true,
+	"false": false,
+	"TRUE":  true,
+	"FALSE": false,
+	"True":  true,
+	"False": false,
+}
diff --git a/pkg/tool/os/env.go b/pkg/tool/os/env.go
index 32880ff..962ec03 100644
--- a/pkg/tool/os/env.go
+++ b/pkg/tool/os/env.go
@@ -24,8 +24,7 @@
 	"cuelang.org/go/cue"
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/errors"
-	"cuelang.org/go/cue/parser"
-	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/cli"
 	"cuelang.org/go/internal/task"
 )
 
@@ -205,70 +204,7 @@
 	return nil
 }
 
-func fromString(name, str string, v cue.Value) (x interface{}, err error) {
+func fromString(name, str string, v cue.Value) (x ast.Node, err error) {
 	k := v.IncompleteKind()
-
-	var expr ast.Expr
-	var errs errors.Error
-
-	if k&cue.NumberKind != 0 {
-		expr, err = parser.ParseExpr(name, str)
-		if err != nil {
-			errs = errors.Wrapf(err, v.Pos(),
-				"invalid number for environment variable %s", name)
-		}
-	}
-
-	if k&cue.BoolKind != 0 {
-		str = strings.TrimSpace(str)
-		b, ok := boolValues[str]
-		if !ok {
-			errors.Append(errs, errors.Newf(v.Pos(),
-				"invalid boolean value %q for environment variable %s", str, name))
-		} else if expr != nil || k&cue.StringKind != 0 {
-			// Convert into an expression
-			bl := ast.NewBool(b)
-			if expr != nil {
-				expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: bl}
-			} else {
-				expr = bl
-			}
-		} else {
-			x = b
-		}
-	}
-
-	if k&cue.StringKind != 0 {
-		if expr != nil {
-			expr = &ast.BinaryExpr{Op: token.OR, X: expr, Y: ast.NewString(str)}
-		} else {
-			x = str
-		}
-	}
-
-	switch {
-	case expr != nil:
-		return expr, nil
-	case x != nil:
-		return x, nil
-	case errs == nil:
-		return nil, errors.Newf(v.Pos(),
-			"invalid type for environment variable %s", name)
-	}
-	return nil, errs
-}
-
-var boolValues = map[string]bool{
-	"1":     true,
-	"0":     false,
-	"t":     true,
-	"f":     false,
-	"T":     true,
-	"F":     false,
-	"true":  true,
-	"false": false,
-	"TRUE":  true,
-	"FALSE": false,
-	"True":  true,
-	"False": false,
+	return cli.ParseValue(v.Pos(), name, str, k)
 }
diff --git a/pkg/tool/os/env_test.go b/pkg/tool/os/env_test.go
index a234cab..a5bfae9 100644
--- a/pkg/tool/os/env_test.go
+++ b/pkg/tool/os/env_test.go
@@ -95,8 +95,8 @@
 	}`
 
 	want := map[string]interface{}{
-		"CUEOSTESTMOOD": "yippie",
-		"CUEOSTESTTRUE": true,
+		"CUEOSTESTMOOD": ast.NewString("yippie"),
+		"CUEOSTESTTRUE": ast.NewBool(true),
 		"CUEOSTESTFALSE": &ast.BinaryExpr{
 			Op: token.OR,
 			X:  ast.NewBool(false),
@@ -108,7 +108,7 @@
 			Y:  ast.NewBool(true),
 		},
 		"CUEOSTESTNUM":  &ast.BasicLit{Kind: token.INT, Value: "34K"},
-		"CUEOSTESTNUMD": "not a num",
+		"CUEOSTESTNUMD": ast.NewString("not a num"),
 		"CUEOSTESTMULTI": &ast.BinaryExpr{
 			Op: token.OR,
 			X:  &ast.BasicLit{Kind: token.INT, Value: "10"},