Was this seriously simple enough for me to do in one commit?
commit
9d186a3b3f
|
@ -0,0 +1,2 @@
|
|||
# Emacs
|
||||
*~
|
|
@ -0,0 +1,25 @@
|
|||
# BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2022, Sean Hickey (Wisellama)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,16 @@
|
|||
all: release
|
||||
|
||||
dependencies:
|
||||
go mod tidy
|
||||
|
||||
build: dependencies
|
||||
go build -x -v
|
||||
|
||||
release: dependencies
|
||||
go build -x -v -ldflags "-s -w"
|
||||
|
||||
test: dependencies
|
||||
go test ./pkg
|
||||
|
||||
goimports_everything:
|
||||
find . -name "*.go" -exec goimports -w {} \;
|
|
@ -0,0 +1,35 @@
|
|||
# go-simpleconf
|
||||
|
||||
This is a small library for parsing super simple configuration files.
|
||||
|
||||
It expects a file with the following format:
|
||||
* Lines with key-values pairs separated by `=` (e.g. `foo = bar`)
|
||||
* Lines that start with `#` are ignored (used for comments)
|
||||
* Empty and whitespace-only lines are ignore
|
||||
* Lines with data that don't have an `=` will cause an error.
|
||||
|
||||
The key-values pairs are simply parsed as strings and plopped into a
|
||||
map for you to do whatever your program wants with them. If you need a
|
||||
value to not be a string, you will have to convert the value from a
|
||||
string as needed for your program (e.g. using the `strconv` library).
|
||||
|
||||
## Why?
|
||||
|
||||
I've always seen configuration files similar to this show up in
|
||||
Unix-like systems and programs, but it doesn't seem to be fully
|
||||
standardized under a real name other than just "conf files". The
|
||||
closest I've seen are INI files, but those don't seem to fit the style
|
||||
I was looking for (they don't use `#` for comments and they support
|
||||
sections in square brackets `[section]`).
|
||||
|
||||
Instead, I just wanted the bare minimum configuration format of `foo =
|
||||
bar` on each line with `#` for comments. So that's what this library
|
||||
does.
|
||||
|
||||
## This project looks abandoned
|
||||
|
||||
This library is so simple that I almost never expect to need to update
|
||||
it unless Go fundamentally breaks the language in the future. So while
|
||||
the project may eventually look like it has been abandoned, it is
|
||||
rather simply "complete". (*gasp* I know, such a rare thing in the
|
||||
software world).
|
|
@ -0,0 +1,3 @@
|
|||
module gitea.wisellama.rocks/Wisellama/go-simpleconf
|
||||
|
||||
go 1.17
|
|
@ -0,0 +1,61 @@
|
|||
package simpleconf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Load(filename string) (map[string]string, error) {
|
||||
var err error
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
parsedMap, err := parse(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedMap, nil
|
||||
}
|
||||
|
||||
func parse(config io.Reader) (map[string]string, error) {
|
||||
var err error
|
||||
parsedMap := make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(config)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if len(trimmed) == 0 {
|
||||
// ignore empty lines
|
||||
continue
|
||||
}
|
||||
|
||||
if trimmed[0] == '#' {
|
||||
// ignore comments
|
||||
continue
|
||||
}
|
||||
|
||||
split := strings.Split(trimmed, "=")
|
||||
if len(split) != 2 {
|
||||
return nil, fmt.Errorf("failed to parse config line: %v", trimmed)
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(split[0])
|
||||
value := strings.TrimSpace(split[1])
|
||||
parsedMap[key] = value
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedMap, nil
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package simpleconf
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseWithValidConfig(t *testing.T) {
|
||||
var err error
|
||||
configFileStr := `
|
||||
# Some config file
|
||||
|
||||
foo=bar
|
||||
asdf = 1234
|
||||
|
||||
# Things
|
||||
wat.wat= thing
|
||||
foo.wat =stuff
|
||||
|
||||
immaempty=
|
||||
|
||||
technically valid = haha hmm...
|
||||
|
||||
#Done
|
||||
`
|
||||
configFile := strings.NewReader(configFileStr)
|
||||
|
||||
expectedMap := make(map[string]string)
|
||||
expectedMap["foo"] = "bar"
|
||||
expectedMap["asdf"] = "1234"
|
||||
expectedMap["wat.wat"] = "thing"
|
||||
expectedMap["foo.wat"] = "stuff"
|
||||
expectedMap["immaempty"] = ""
|
||||
expectedMap["technically valid"] = "haha hmm..."
|
||||
|
||||
parsedMap, err := parse(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed while parsing the file")
|
||||
}
|
||||
|
||||
validateMap(t, parsedMap, expectedMap)
|
||||
}
|
||||
|
||||
func TestParseWithExtraEquals(t *testing.T) {
|
||||
var err error
|
||||
configFileStr := `
|
||||
foo=bar=true
|
||||
`
|
||||
configFile := strings.NewReader(configFileStr)
|
||||
|
||||
_, err = parse(configFile)
|
||||
if err == nil {
|
||||
t.Fatalf("parse did not thrown an error when it was supposed to")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWithNoEquals(t *testing.T) {
|
||||
var err error
|
||||
configFileStr := `
|
||||
Something here with no equals to split on
|
||||
`
|
||||
configFile := strings.NewReader(configFileStr)
|
||||
|
||||
_, err = parse(configFile)
|
||||
if err == nil {
|
||||
t.Fatalf("parse did not thrown an error when it was supposed to")
|
||||
}
|
||||
}
|
||||
|
||||
func validateMap(t *testing.T, given map[string]string, expected map[string]string) {
|
||||
if given == nil {
|
||||
t.Fatalf("given map was nil")
|
||||
}
|
||||
|
||||
if expected == nil {
|
||||
t.Fatalf("expected map was nil")
|
||||
}
|
||||
|
||||
expectedLen := len(expected)
|
||||
givenLen := len(given)
|
||||
if expectedLen != givenLen {
|
||||
t.Fatalf("size mismatch on maps - expected %v, given %v", expectedLen, givenLen)
|
||||
}
|
||||
|
||||
for k, v := range expected {
|
||||
if v != given[k] {
|
||||
t.Fatalf("incorrect value for key %v - expected %v, given %v", k, expected[k], given[k])
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue