├── Makefile ├── .gitignore ├── .github └── workflows │ └── semgrep.yml ├── LICENSE ├── README.md ├── conf.go └── conf_test.go /Makefile: -------------------------------------------------------------------------------- 1 | GCFLAGS := -B 2 | LDFLAGS := 3 | 4 | .PHONY: install 5 | install: 6 | @go install -v . 7 | 8 | .PHONY: test 9 | test: 10 | @go test -gcflags='$(GCFLAGS)' -ldflags='$(LDFLAGS)' . 11 | 12 | .PHONY: bench 13 | bench: 14 | @go test -gcflags='$(GCFLAGS)' -ldflags='$(LDFLAGS)' -bench . 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | *~ 25 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | name: Semgrep config 10 | jobs: 11 | semgrep: 12 | name: semgrep/ci 13 | runs-on: ubuntu-20.04 14 | env: 15 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 16 | SEMGREP_URL: https://cloudflare.semgrep.dev 17 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 19 | container: 20 | image: returntocorp/semgrep 21 | steps: 22 | - uses: actions/checkout@v3 23 | - run: semgrep ci 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 CloudFlare, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the CloudFlare, Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | conf 2 | ==== 3 | 4 | Really, really simple key=val configuration file parser for Go 5 | 6 | Example 7 | ======= 8 | 9 | Each parameter is in the form key=val. Keys are strings (can have 10 | embedded whitespace) and values are either strings or unsigned 11 | integers. 12 | 13 | Comments must be on lines by themselves and start with # 14 | 15 | # Moon Landing Configuration File 16 | 17 | program.name=Apollo 18 | sequence=11 19 | commander=N. A. Armstrong 20 | lm pilot=E. E. Aldrin 21 | cm pilot=M. Collins 22 | year=1969 23 | capcom=C. M. Duke 24 | 25 | API 26 | === 27 | 28 | Read the config file with ReadConfigFile, extract the values with 29 | GetString, GetUint and check for any extra values (often typos) with 30 | CheckUnread. 31 | 32 | // ReadConfigFile reads an entire config file and return a Config 33 | // structure that can be used to extract single values. 34 | func ReadConfigFile(file string) (c *Config, err error) 35 | 36 | // GetString reads a string value from the parsed config file and 37 | // return it (or if it is missing then return the default value passed 38 | // in d) 39 | func (c *Config) GetString(k string, d string) (v string) 40 | 41 | // GetUint reads an unsigned integer from the parsed config file and 42 | // return it (or if it is missing then return the default value passed 43 | // in d) 44 | func (c *Config) GetUint(k string, d uint) (v uint) 45 | 46 | // CheckUnread checks to see if any of the parameters in the file 47 | // have not been read and returns a string containing those that have 48 | // not been read. 49 | func (c *Config) CheckUnread() (s string) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /conf.go: -------------------------------------------------------------------------------- 1 | // conf.go: Simple config file reader. Config items are in the form 2 | // foo=bar and comments can be created by placing a # at the start of 3 | // the line. 4 | // 5 | // Copyright (c) 2011-2013 CloudFlare, Inc. 6 | 7 | package conf 8 | 9 | import ( 10 | "bufio" 11 | "fmt" 12 | "os" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | // ConfigFileRaw is a copy of the config file 18 | 19 | var ConfigFileRaw []byte 20 | 21 | // The parsed contents of the config file 22 | 23 | type parameter struct { 24 | s string // The parsed parameter string value 25 | r bool // Set to true if this parameter had been read 26 | l int // Line number at which this was found 27 | } 28 | 29 | // Config is a copy of the configuration file turned into a map 30 | 31 | type Config struct { 32 | v map[string]parameter 33 | } 34 | 35 | // ReadConfigFile reads an entire config file and return a Config 36 | // structure that can be used to extract single values. 37 | func ReadConfigFile(file string) (c *Config, err error) { 38 | c = new(Config) 39 | c.v = make(map[string]parameter) 40 | l := 0 41 | 42 | var f *os.File 43 | if f, err = os.Open(file); err == nil { 44 | defer f.Close() 45 | 46 | scanner := bufio.NewScanner(f) 47 | 48 | for scanner.Scan() { 49 | line := strings.TrimSpace(scanner.Text()) 50 | l += 1 51 | 52 | if len(line) > 0 && line[0] != '#' { 53 | parts := strings.SplitN(line, "=", 2) 54 | 55 | if len(parts) != 2 { 56 | err = fmt.Errorf("Config line %d invalid: %s (missing =)", l, 57 | line) 58 | return 59 | } 60 | 61 | k := strings.TrimSpace(parts[0]) 62 | v := strings.TrimSpace(parts[1]) 63 | 64 | if k == "" { 65 | err = fmt.Errorf("Config line %d invalid: %s (missing key)", l, 66 | line) 67 | return 68 | } 69 | 70 | if _, found := c.v[k]; !found { 71 | c.v[k] = parameter{v, false, l} 72 | } else { 73 | err = fmt.Errorf("Config line %d invalid: %s (repeated parameter)", l, 74 | line) 75 | return 76 | } 77 | } 78 | } 79 | 80 | err = scanner.Err() 81 | } 82 | 83 | return 84 | } 85 | 86 | // GetString reads a string value from the parsed config file and 87 | // return it (or if it is missing then return the default value passed 88 | // in d) 89 | func (c *Config) GetString(k string, d string) (v string) { 90 | s, present := c.v[k] 91 | if !present { 92 | v = d 93 | } else { 94 | v = s.s 95 | s.r = true 96 | c.v[k] = s 97 | } 98 | return 99 | } 100 | 101 | // GetUint reads an unsigned integer from the parsed config file and 102 | // return it (or if it is missing then return the default value passed 103 | // in d) 104 | func (c *Config) GetUint(k string, d uint) (v uint) { 105 | if s, present := c.v[k]; !present { 106 | v = d 107 | } else { 108 | s.r = true 109 | c.v[k] = s 110 | if iv, err := strconv.Atoi(s.s); err != nil { 111 | v = d 112 | } else { 113 | v = uint(iv) 114 | } 115 | } 116 | 117 | return 118 | } 119 | 120 | // CheckUnread checks to see if any of the parameters in the file 121 | // have not been read and returns a string containing those that have 122 | // not been read. 123 | func (c *Config) CheckUnread() (s string) { 124 | for k, v := range c.v { 125 | if !v.r { 126 | s += fmt.Sprintf("%s (%d) ", k, v.l) 127 | } 128 | } 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /conf_test.go: -------------------------------------------------------------------------------- 1 | // conf_test.go: test suite for conf.go 2 | // 3 | // Copyright (c) 2013 CloudFlare, Inc. 4 | 5 | package conf 6 | 7 | import ( 8 | "io/ioutil" 9 | "os" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func assert(t *testing.T, b bool) { 15 | if !b { 16 | t.Fail() 17 | } 18 | } 19 | 20 | // makeFile creates a file with the given contents, returns the file 21 | // name. Returns an empty string on error. 22 | func makeFile(c string) (fn string) { 23 | if f, err := ioutil.TempFile(os.TempDir(), "conf"); err == nil { 24 | f.WriteString(c) 25 | f.Close() 26 | return f.Name() 27 | } else { 28 | return "" 29 | } 30 | } 31 | 32 | func TestEmptyConfig(t *testing.T) { 33 | f := makeFile("") 34 | c, err := ReadConfigFile(f) 35 | assert(t, c != nil) 36 | assert(t, err == nil) 37 | assert(t, c.CheckUnread() == "") 38 | os.Remove(f) 39 | } 40 | 41 | func TestSimpleConfig(t *testing.T) { 42 | 43 | // Note: specific things being tested here: 44 | // 45 | // foo=1 (a normal line) 46 | // \n\n completely empty lines 47 | // \n \n lines with just whitespace 48 | // # comment lines that have comments in them 49 | // bar = baz lines with embedded whitespace and not trailing \n 50 | 51 | f := makeFile("foo=1\n \n \n# comment\n\n\n bar = baz ") 52 | c, err := ReadConfigFile(f) 53 | assert(t, c != nil) 54 | assert(t, err == nil) 55 | assert(t, c.CheckUnread() != "") 56 | assert(t, c.GetString("foo", "FOO") == "1") 57 | assert(t, c.GetUint("foo", 2) == 1) 58 | assert(t, c.GetString("bar", "BAR") == "baz") 59 | assert(t, c.GetUint("bar", 2) == 2) 60 | assert(t, c.CheckUnread() == "") 61 | assert(t, c.GetString("baz", "BAZ") == "BAZ") 62 | assert(t, c.GetUint("baz", 3) == 3) 63 | assert(t, c.GetString("bam", "") == "") 64 | assert(t, c.GetUint("bam", 0) == 0) 65 | os.Remove(f) 66 | 67 | // Check handling of equals sign in values 68 | 69 | f = makeFile("foo==FOO\nbar=BAR=\nbaz=Baz=Baz") 70 | c, err = ReadConfigFile(f) 71 | assert(t, c != nil) 72 | assert(t, err == nil) 73 | assert(t, c.GetString("foo", "") == "=FOO") 74 | assert(t, c.GetString("bar", "") == "BAR=") 75 | assert(t, c.GetString("baz", "") == "Baz=Baz") 76 | os.Remove(f) 77 | 78 | // Check handling of empty value 79 | 80 | f = makeFile("foo=1\nbar=") 81 | c, err = ReadConfigFile(f) 82 | assert(t, c != nil) 83 | assert(t, err == nil) 84 | assert(t, c.GetString("foo", "FOO") == "1") 85 | assert(t, c.GetUint("foo", 2) == 1) 86 | assert(t, c.GetString("bar", "BAR") == "") 87 | assert(t, c.GetUint("bar", 2) == 2) 88 | os.Remove(f) 89 | } 90 | 91 | func TestCheckUnread(t *testing.T) { 92 | f := makeFile("foo=1\nbar=baz") 93 | c, err := ReadConfigFile(f) 94 | assert(t, c != nil) 95 | assert(t, err == nil) 96 | assert(t, c.CheckUnread() != "") 97 | assert(t, strings.Contains(c.CheckUnread(), "foo (1)")) 98 | assert(t, strings.Contains(c.CheckUnread(), "bar (2)")) 99 | assert(t, c.GetString("bar", "BAR") == "baz") 100 | assert(t, strings.Contains(c.CheckUnread(), "foo (1)")) 101 | assert(t, !strings.Contains(c.CheckUnread(), "bar (2)")) 102 | assert(t, c.GetString("foo", "FOO") == "1") 103 | assert(t, !strings.Contains(c.CheckUnread(), "foo (1)")) 104 | assert(t, !strings.Contains(c.CheckUnread(), "bar (2)")) 105 | os.Remove(f) 106 | } 107 | 108 | func TestCheckErrors(t *testing.T) { 109 | f := makeFile("foo=1\nbaz") 110 | _, err := ReadConfigFile(f) 111 | assert(t, err != nil) 112 | os.Remove(f) 113 | 114 | f = makeFile("foo=1\n=baz") 115 | _, err = ReadConfigFile(f) 116 | assert(t, err != nil) 117 | os.Remove(f) 118 | 119 | f = makeFile("foo=1\nfoo=2") 120 | _, err = ReadConfigFile(f) 121 | assert(t, err != nil) 122 | os.Remove(f) 123 | } 124 | --------------------------------------------------------------------------------