Add TO Golang Perl config parser

Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/70b7e5ce
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/70b7e5ce
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/70b7e5ce

Branch: refs/heads/master
Commit: 70b7e5ce10721205d5b0b0db90ee5c906fe787da
Parents: 9e67e6a
Author: Robert Butts <robert.o.bu...@gmail.com>
Authored: Sun Jul 23 12:12:11 2017 -0600
Committer: Dewayne Richardson <dewr...@apache.org>
Committed: Thu Aug 10 09:46:03 2017 -0600

----------------------------------------------------------------------
 traffic_ops/traffic_ops_golang/perlhash.go      | 249 +++++++++++++++++++
 traffic_ops/traffic_ops_golang/perlhash_test.go | 217 ++++++++++++++++
 2 files changed, 466 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/70b7e5ce/traffic_ops/traffic_ops_golang/perlhash.go
----------------------------------------------------------------------
diff --git a/traffic_ops/traffic_ops_golang/perlhash.go 
b/traffic_ops/traffic_ops_golang/perlhash.go
new file mode 100644
index 0000000..f278e42
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/perlhash.go
@@ -0,0 +1,249 @@
+package main
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+*/
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+       "unicode"
+)
+
+func ParsePerlObj(s string) (map[string]interface{}, error) {
+       obj, _, err := getObj(s)
+       return obj, err
+}
+
+func getObj(s string) (map[string]interface{}, string, error) {
+       obj := map[string]interface{}{}
+
+       s = strings.TrimSpace(s)
+       if len(s) < 1 || s[0] != '{' {
+               return obj, "", fmt.Errorf("expected first character '{': %v", 
s)
+       }
+       s = s[1:] // strip opening {
+       s = strings.TrimSpace(s)
+
+       // read top-level keys
+       for {
+               s = stripComment(s)
+               s = strings.TrimSpace(s)
+               // s = stripComment(s)
+               if len(s) > 0 && s[0] == '}' {
+                       return obj, s[1:], nil
+               }
+
+               key := ""
+               key, s = getKey(s)
+
+               s = strings.TrimSpace(s)
+               if len(s) == 0 {
+                       return obj, "", fmt.Errorf("malformed string after key 
'%v'", key)
+               }
+
+               err := error(nil)
+               switch {
+               case s[0] == '{':
+                       v := map[string]interface{}{}
+                       v, s, err = getObj(s)
+                       if err != nil {
+                               return obj, "", fmt.Errorf("Error getting 
object value after key %v: %v", key, err)
+                       }
+                       obj[key] = v
+               case s[0] == '\'':
+                       v := ""
+                       v, s, err = getStr(s)
+                       if err != nil {
+                               return obj, "", fmt.Errorf("Error getting 
string value after key %v: %v", key, err)
+                       }
+                       obj[key] = v
+               case unicode.IsDigit(rune(s[0])):
+                       v := float64(0.0)
+                       v, s, err = getNum(s)
+                       if err != nil {
+                               return obj, "", fmt.Errorf("Error getting 
numeric value after key %v: %v", key, err)
+                       }
+                       obj[key] = v
+               case s[0] == '[':
+                       v := []interface{}{}
+                       v, s, err = getArr(s)
+                       if err != nil {
+                               return obj, "", fmt.Errorf("Error getting array 
value after key %v: %v", key, err)
+                       }
+                       obj[key] = v
+               default:
+                       return obj, "", fmt.Errorf(`malformed string after key 
"%v"`, key)
+               }
+               s = strings.TrimSpace(s)
+               s = stripComment(s)
+               if len(s) > 0 && s[0] == ',' {
+                       s = s[1:]
+                       s = strings.TrimSpace(s)
+                       s = stripComment(s)
+                       s = strings.TrimSpace(s)
+               }
+       }
+}
+
+func getNum(s string) (float64, string, error) {
+       s = strings.TrimSpace(s)
+       i := strings.IndexFunc(s, func(r rune) bool { return 
!unicode.IsDigit(r) && r != '.' })
+
+       numStr := ""
+       if i < 0 {
+               numStr = s
+               s = ""
+       } else {
+               numStr = s[:i]
+               s = s[i:]
+       }
+       num, err := strconv.ParseFloat(numStr, 64)
+       if err != nil {
+               return 0, "", fmt.Errorf("malformed number: %v", err)
+       }
+
+       return num, s, nil
+}
+
+func getArr(s string) ([]interface{}, string, error) {
+       arr := []interface{}{}
+
+       s = strings.TrimSpace(s)
+       if len(s) == 0 || s[0] != '[' {
+               return nil, "", fmt.Errorf("malformed array, doesn't start with 
[")
+       }
+       s = s[1:]
+       for {
+               s = strings.TrimSpace(s)
+               if len(s) == 0 {
+                       return nil, "", fmt.Errorf("malformed array, doesn't 
end with ]")
+               }
+               if s[0] == ']' {
+                       return arr, s[1:], nil
+               }
+
+               switch {
+               case unicode.IsDigit(rune(s[0])) || s[0] == '-' || s[0] == '+' 
|| s[0] == '.':
+                       num := float64(0.0)
+                       err := error(nil)
+                       num, s, err = getNum(s)
+                       if err != nil {
+                               return nil, "", fmt.Errorf("malformed number in 
array: %v", err)
+                       }
+                       arr = append(arr, num)
+               case s[0] == '\'':
+                       str := ""
+                       err := error(nil)
+                       str, s, err = getStr(s)
+                       if err != nil {
+                               return nil, "", fmt.Errorf("malformed string in 
array: %v", err)
+                       }
+                       arr = append(arr, str)
+               case s[0] == '[':
+                       narr := []interface{}{}
+                       err := error(nil)
+                       narr, s, err = getArr(s)
+                       if err != nil {
+                               return nil, "", fmt.Errorf("malformed array in 
array: %v", err)
+                       }
+                       arr = append(arr, narr)
+               case s[0] == '{':
+                       obj := map[string]interface{}{}
+                       err := error(nil)
+                       obj, s, err = getObj(s)
+                       if err != nil {
+                               return nil, "", fmt.Errorf("malformed object in 
array: %v", err)
+                       }
+                       arr = append(arr, obj)
+               default:
+                       return nil, "", fmt.Errorf("malformed element in array, 
unknown initial character: %v", string(s[0]))
+               }
+               s = strings.TrimSpace(s)
+               s = stripComment(s)
+               if len(s) > 0 && s[0] == ',' {
+                       s = s[1:]
+                       s = strings.TrimSpace(s)
+                       s = stripComment(s)
+                       s = strings.TrimSpace(s)
+               }
+       }
+}
+
+func getStr(s string) (string, string, error) {
+       s = strings.TrimSpace(s)
+       if len(s) == 0 || s[0] != '\'' {
+               return "", "", fmt.Errorf("malformed string, doesn't start with 
'")
+       }
+       s = s[1:]
+
+       str := ""
+
+       escaping := false
+       for {
+               if len(s) == 0 {
+                       return "", "", fmt.Errorf("malformed string, doesn't 
terminate '")
+               }
+
+               if !escaping && s[0] == '\'' {
+                       return str, s[1:], nil
+               }
+
+               if escaping {
+                       str += s[0:1]
+                       s = s[1:]
+                       escaping = false
+                       continue
+               }
+
+               if s[0] == '\\' {
+                       escaping = true
+                       s = s[1:]
+                       continue
+               }
+
+               str += s[0:1]
+               s = s[1:]
+               continue
+       }
+
+}
+
+func stripComment(s string) string {
+       if len(s) == 0 || s[0] != '#' {
+               return s
+       }
+       i := strings.Index(s, "\n")
+       if i < 0 {
+               return ""
+       }
+       return s[i:]
+}
+
+func getKey(s string) (string, string) {
+       i := strings.Index(s, "=>")
+       if i < 0 {
+               return "", s
+       }
+       key := s[:i]
+       key = strings.TrimSpace(key)
+
+       s = s[i+len("=>"):]
+       return key, s
+}

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/70b7e5ce/traffic_ops/traffic_ops_golang/perlhash_test.go
----------------------------------------------------------------------
diff --git a/traffic_ops/traffic_ops_golang/perlhash_test.go 
b/traffic_ops/traffic_ops_golang/perlhash_test.go
new file mode 100644
index 0000000..beb9ac7
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/perlhash_test.go
@@ -0,0 +1,217 @@
+package main
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+*/
+
+import (
+       "reflect"
+       "testing"
+)
+
+func TestGetStr(t *testing.T) {
+       type Strtst struct {
+               Input       string
+               ExpectedStr string
+               ExpectedS   string
+       }
+       strs := []Strtst{
+               Strtst{`'foo'asdf`, `foo`, `asdf`},
+               Strtst{`''asdf`, ``, `asdf`},
+               Strtst{`'1234'asdf`, `1234`, `asdf`},
+               Strtst{`'\\'asdf`, `\`, `asdf`},
+               Strtst{`'\''asdf`, `'`, `asdf`},
+               Strtst{`'\''asdfqhrewoipjasf`, `'`, `asdfqhrewoipjasf`},
+       }
+
+       for _, st := range strs {
+               str, s, err := getStr(st.Input)
+               if err != nil {
+                       t.Errorf("expected nil err actual %v", err)
+               }
+               if str != st.ExpectedStr {
+                       t.Errorf("expected str '%v' actual '%v'", 
st.ExpectedStr, str)
+               }
+               if s != st.ExpectedS {
+                       t.Errorf("expected s '%v' actual '%v'", st.ExpectedS, s)
+               }
+       }
+}
+
+func TestGetNum(t *testing.T) {
+       type Tst struct {
+               Input       string
+               ExpectedNum float64
+               ExpectedS   string
+       }
+       strs := []Tst{
+               Tst{`42asdf`, 42, `asdf`},
+               Tst{`42 asdf`, 42, ` asdf`},
+               Tst{`42.1asdf`, 42.1, `asdf`},
+               Tst{`42.1 asdf`, 42.1, ` asdf`},
+               Tst{`42. asdf`, 42, ` asdf`},
+               Tst{`.42 asdf`, 0.42, ` asdf`},
+               Tst{`0.42 asdf`, 0.42, ` asdf`},
+               Tst{`0.42 {asdf`, 0.42, ` {asdf`},
+               Tst{`9{asdf`, 9, `{asdf`},
+               Tst{`42.1`, 42.1, ``},
+               Tst{`9`, 9, ``},
+       }
+
+       for _, st := range strs {
+               num, s, err := getNum(st.Input)
+               if err != nil {
+                       t.Errorf("expected nil err actual %v", err)
+               }
+               if num != st.ExpectedNum {
+                       t.Errorf("expected num '%v' actual '%v'", 
st.ExpectedNum, num)
+               }
+               if s != st.ExpectedS {
+                       t.Errorf("expected s '%v' actual '%v'", st.ExpectedS, s)
+               }
+       }
+}
+
+func TestGetArr(t *testing.T) {
+       type Tst struct {
+               Input       string
+               ExpectedVal []interface{}
+               ExpectedS   string
+       }
+       tsts := []Tst{
+               Tst{`[42]asdf`, []interface{}{42.0}, `asdf`},
+               Tst{`[42, 'foo']],1]asdf`, []interface{}{42.0, `foo`}, 
`],1]asdf`},
+               Tst{`[
+                       
'https://[::]:443?cert=/etc/pki/tls/certs/localhost.crt&key=/etc/pki/tls/private/localhost.key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED'
+               ],`, 
[]interface{}{`https://[::]:443?cert=/etc/pki/tls/certs/localhost.crt&key=/etc/pki/tls/private/localhost.key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED`},
 `,`},
+               Tst{`[ 'foobar' ],`, []interface{}{"foobar"}, `,`},
+               Tst{`[ 'foo' , 'bar', 'baz'   ],`, []interface{}{`foo`, `bar`, 
`baz`}, `,`},
+       }
+
+       for _, st := range tsts {
+               val, s, err := getArr(st.Input)
+               if err != nil {
+                       t.Errorf("expected nil err actual %v", err)
+               }
+               if !reflect.DeepEqual(val, st.ExpectedVal) {
+                       t.Errorf("expected arr '%v' actual '%v'", 
st.ExpectedVal, val)
+               }
+               if s != st.ExpectedS {
+                       t.Errorf("expected s '%v' actual '%v'", st.ExpectedS, s)
+               }
+       }
+}
+
+func TestGetObj(t *testing.T) {
+       type Tst struct {
+               Input       string
+               ExpectedVal map[string]interface{}
+               ExpectedS   string
+       }
+       tsts := []Tst{
+               Tst{`{ a => 'b', c => [42.0, 'd']},asdf`,
+                       map[string]interface{}{
+                               `a`: `b`,
+                               `c`: []interface{}{42.0, `d`},
+                       },
+                       `,asdf`},
+       }
+
+       for _, st := range tsts {
+               val, s, err := getObj(st.Input)
+               if err != nil {
+                       t.Errorf("expected nil err actual %v", err)
+               }
+               if !reflect.DeepEqual(val, st.ExpectedVal) {
+                       t.Errorf("expected obj '%+v' actual '%+v'", 
st.ExpectedVal, val)
+               }
+               if s != st.ExpectedS {
+                       t.Errorf("expected s '%v' actual '%v'", st.ExpectedS, s)
+               }
+       }
+}
+
+func TestParsePerlObj(t *testing.T) {
+       input := `
+{
+       hypnotoad => {
+               listen => [
+                       
'https://[::]:443?cert=/etc/pki/tls/certs/localhost.crt&key=/etc/pki/tls/private/localhost.key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED'
+               ],
+               user     => 'trafops',
+               group    => 'trafops',
+               heartbeat_timeout => 20,
+               pid_file => '/var/run/traffic_ops.pid',
+               workers  => 96
+       },
+       cors => {
+               access_control_allow_origin => '*'
+       },
+       to => {
+               base_url   => 'http://localhost:3000',                    # 
this is where traffic ops app resides
+               email_from => 'no-re...@traffic-ops-domain.com'           # 
traffic ops email address
+       },
+       portal => {
+               base_url   => 'http://localhost:8080',                    # 
this is where the traffic portal resides (a javascript client that consumes the 
TO API)
+               email_from => 'no-re...@traffic-portal-domain.com'        # 
traffic portal email address
+       },
+
+       # 1st secret is used to generate new signatures. Older one kept around 
for existing signed cookies.
+               #  Remove old one(s) when ready to invalidate old cookies.
+               secrets => [ 'walrus' ],
+       geniso  => {
+               iso_root_path => '/opt/traffic_ops/app/public',          # the 
location where the iso files will be written
+       },
+       inactivity_timeout => 60
+};
+`
+       expected := map[string]interface{}{
+               "hypnotoad": map[string]interface{}{
+                       "listen":            
[]interface{}{`https://[::]:443?cert=/etc/pki/tls/certs/localhost.crt&key=/etc/pki/tls/private/localhost.key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED`},
+                       "user":              `trafops`,
+                       "group":             `trafops`,
+                       "heartbeat_timeout": 20.0,
+                       "pid_file":          `/var/run/traffic_ops.pid`,
+                       "workers":           96.0,
+               },
+               "cors": map[string]interface{}{
+                       "access_control_allow_origin": `*`,
+               },
+               "to": map[string]interface{}{
+                       "base_url":   `http://localhost:3000`,
+                       "email_from": `no-re...@traffic-ops-domain.com`,
+               },
+               "portal": map[string]interface{}{
+                       "base_url":   `http://localhost:8080`,
+                       "email_from": `no-re...@traffic-portal-domain.com`,
+               },
+               "secrets": []interface{}{`walrus`},
+               "geniso": map[string]interface{}{
+                       "iso_root_path": `/opt/traffic_ops/app/public`,
+               },
+               "inactivity_timeout": 60.0,
+       }
+
+       val, err := ParsePerlObj(input)
+       if err != nil {
+               t.Errorf("expected nil err actual %v", err)
+       }
+       if !reflect.DeepEqual(val, expected) {
+               t.Errorf("expected '%+v' actual '%+v'", expected, val)
+       }
+}

Reply via email to