]> Cypherpunks repositories - gostls13.git/commitdiff
os: add Expand function to evaluate environment variables.
authorRob Pike <r@golang.org>
Mon, 8 Nov 2010 21:25:01 +0000 (13:25 -0800)
committerRob Pike <r@golang.org>
Mon, 8 Nov 2010 21:25:01 +0000 (13:25 -0800)
Fixes #1258.

R=gri, msolo, bradfitzpatrick, r2
CC=golang-dev
https://golang.org/cl/2988041

src/pkg/os/Makefile
src/pkg/os/env.go [new file with mode: 0644]
src/pkg/os/env_test.go [new file with mode: 0644]

index 0b0881c0ac2f425bf1c0f19b20ae101be4dca14a..e4b6fb98dde1af8f873e6a967cb0773b254a2ae5 100644 (file)
@@ -8,6 +8,7 @@ TARG=os
 GOFILES=\
        dir_$(GOOS).go\
        error.go\
+       env.go\
        exec.go\
        file.go\
        getwd.go\
diff --git a/src/pkg/os/env.go b/src/pkg/os/env.go
new file mode 100644 (file)
index 0000000..9783674
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright 2010 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// General environment variables.
+
+package os
+
+// Expand replaces ${var} or $var in the string based on the mapping function.
+// Invocations of undefined variables are replaced with the empty string.
+func Expand(s string, mapping func(string) string) string {
+       buf := make([]byte, 0, 2*len(s))
+       // ${} is all ASCII, so bytes are fine for this operation.
+       for i := 0; i < len(s); {
+               if s[i] != '$' || i == len(s)-1 {
+                       buf = append(buf, s[i])
+                       i++
+                       continue
+               }
+               name, w := getShellName(s[i+1:])
+               buf = append(buf, []byte(mapping(name))...)
+               i += 1 + w
+       }
+       return string(buf)
+}
+
+// ShellExpand replaces ${var} or $var in the string according to the values
+// of the operating system's environment variables.  References to undefined
+// variables are replaced by the empty string.
+func ShellExpand(s string) string {
+       return Expand(s, Getenv)
+}
+
+// isSpellSpecialVar reports whether the character identifies a special
+// shell variable such as $*.
+func isShellSpecialVar(c uint8) bool {
+       switch c {
+       case '*', '#', '$', '@', '!', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+               return true
+       }
+       return false
+}
+
+// isAlphaNum reports whether the byte is an ASCII letter, number, or underscore
+func isAlphaNum(c uint8) bool {
+       return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
+}
+
+// getName returns the name that begins the string and the number of bytes
+// consumed to extract it.  If the name is enclosed in {}, it's part of a ${}
+// expansion and two more bytes are needed than the length of the name.
+func getShellName(s string) (string, int) {
+       switch {
+       case s[0] == '{':
+               if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == '}' {
+                       return s[1:2], 3
+               }
+               // Scan to closing brace
+               for i := 1; i < len(s); i++ {
+                       if s[i] == '}' {
+                               return s[1:i], i + 1
+                       }
+               }
+               return "", 1 // Bad syntax; just eat the brace.
+       case isShellSpecialVar(s[0]):
+               return s[0:1], 1
+       }
+       // Scan alphanumerics.
+       var i int
+       for i = 0; i < len(s) && isAlphaNum(s[i]); i++ {
+       }
+       return s[:i], i
+}
diff --git a/src/pkg/os/env_test.go b/src/pkg/os/env_test.go
new file mode 100644 (file)
index 0000000..04ff390
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+       . "os"
+       "testing"
+)
+
+// testGetenv gives us a controlled set of variables for testing Expand.
+func testGetenv(s string) string {
+       switch s {
+       case "*":
+               return "all the args"
+       case "#":
+               return "NARGS"
+       case "$":
+               return "PID"
+       case "1":
+               return "ARGUMENT1"
+       case "HOME":
+               return "/usr/gopher"
+       case "H":
+               return "(Value of H)"
+       case "home_1":
+               return "/usr/foo"
+       case "_":
+               return "underscore"
+       }
+       return ""
+}
+
+var expandTests = []struct {
+       in, out string
+}{
+       {"", ""},
+       {"$*", "all the args"},
+       {"$$", "PID"},
+       {"${*}", "all the args"},
+       {"$1", "ARGUMENT1"},
+       {"${1}", "ARGUMENT1"},
+       {"now is the time", "now is the time"},
+       {"$HOME", "/usr/gopher"},
+       {"$home_1", "/usr/foo"},
+       {"${HOME}", "/usr/gopher"},
+       {"${H}OME", "(Value of H)OME"},
+       {"A$$$#$1$H$home_1*B", "APIDNARGSARGUMENT1(Value of H)/usr/foo*B"},
+}
+
+func TestExpand(t *testing.T) {
+       for _, test := range expandTests {
+               result := Expand(test.in, testGetenv)
+               if result != test.out {
+                       t.Errorf("Expand(%q)=%q; expected %q", test.in, result, test.out)
+               }
+       }
+}