TOML parser and encoder library for Golang

TOML parser and encoder library for Golang Build Status

TOML parser and encoder library for Golang.

This library is compatible with TOML version v0.4.0.

Installation

go get -u github.com/naoina/toml

Usage

The following TOML save as example.toml.

# This is a TOML document. Boom.

title = "TOML Example"

[owner]
name = "Lance Uppercut"
dob = 1979-05-27T07:32:00-08:00 # First class dates? Why not?

[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true

[servers]

  # You can indent as you please. Tabs or spaces. TOML don't care.
  [servers.alpha]
  ip = "10.0.0.1"
  dc = "eqdc10"

  [servers.beta]
  ip = "10.0.0.2"
  dc = "eqdc10"

[clients]
data = [ ["gamma", "delta"], [1, 2] ]

# Line breaks are OK when inside arrays
hosts = [
  "alpha",
  "omega"
]

Then above TOML will mapping to tomlConfig struct using toml.Unmarshal.

package main

import (
    "os"
    "time"

    "github.com/naoina/toml"
)

type tomlConfig struct {
    Title string
    Owner struct {
        Name string
        Dob  time.Time
    }
    Database struct {
        Server        string
        Ports         []int
        ConnectionMax uint
        Enabled       bool
    }
    Servers map[string]ServerInfo
    Clients struct {
        Data  [][]interface{}
        Hosts []string
    }
}

type ServerInfo struct {
    IP net.IP
    DC string
}

func main() {
    f, err := os.Open("example.toml")
    if err != nil {
        panic(err)
    }
    defer f.Close()
    var config tomlConfig
    if err := toml.NewDecoder(f).Decode(&config); err != nil {
        panic(err)
    }

    // then to use the unmarshaled config...
    fmt.Println("IP of server 'alpha':", config.Servers["alpha"].IP)
}

Mappings

A key and value of TOML will map to the corresponding field. The fields of struct for mapping must be exported.

The rules of the mapping of key are following:

Exact matching

timeout_seconds = 256
type Config struct {
	Timeout_seconds int
}

Camelcase matching

server_name = "srv1"
type Config struct {
	ServerName string
}

Uppercase matching

ip = "10.0.0.1"
type Config struct {
	IP string
}

See the following examples for the value mappings.

String

val = "string"
type Config struct {
	Val string
}

Integer

val = 100
type Config struct {
	Val int
}

All types that can be used are following:

  • int8 (from -128 to 127)
  • int16 (from -32768 to 32767)
  • int32 (from -2147483648 to 2147483647)
  • int64 (from -9223372036854775808 to 9223372036854775807)
  • int (same as int32 on 32bit environment, or int64 on 64bit environment)
  • uint8 (from 0 to 255)
  • uint16 (from 0 to 65535)
  • uint32 (from 0 to 4294967295)
  • uint64 (from 0 to 18446744073709551615)
  • uint (same as uint on 32bit environment, or uint64 on 64bit environment)

Float

val = 3.1415
type Config struct {
	Val float32
}

All types that can be used are following:

  • float32
  • float64

Boolean

val = true
type Config struct {
	Val bool
}

Datetime

val = 2014-09-28T21:27:39Z
type Config struct {
	Val time.Time
}

Array

val = ["a", "b", "c"]
type Config struct {
	Val []string
}

Also following examples all can be mapped:

val1 = [1, 2, 3]
val2 = [["a", "b"], ["c", "d"]]
val3 = [[1, 2, 3], ["a", "b", "c"]]
val4 = [[1, 2, 3], [["a", "b"], [true, false]]]
type Config struct {
	Val1 []int
	Val2 [][]string
	Val3 [][]interface{}
	Val4 [][]interface{}
}

Table

[server]
type = "app"

  [server.development]
  ip = "10.0.0.1"

  [server.production]
  ip = "10.0.0.2"
type Config struct {
	Server map[string]Server
}

type Server struct {
	IP string
}

You can also use the following struct instead of map of struct.

type Config struct {
	Server struct {
		Development Server
		Production Server
	}
}

type Server struct {
	IP string
}

Array of Tables

[[fruit]]
  name = "apple"

  [fruit.physical]
    color = "red"
    shape = "round"

  [[fruit.variety]]
    name = "red delicious"

  [[fruit.variety]]
    name = "granny smith"

[[fruit]]
  name = "banana"

  [[fruit.variety]]
    name = "plantain"
type Config struct {
	Fruit []struct {
		Name string
		Physical struct {
			Color string
			Shape string
		}
		Variety []struct {
			Name string
		}
	}
}

Using the encoding.TextUnmarshaler interface

Package toml supports encoding.TextUnmarshaler (and encoding.TextMarshaler). You can use it to apply custom marshaling rules for certain types. The UnmarshalText method is called with the value text found in the TOML input. TOML strings are passed unquoted.

duration = "10s"
import time

type Duration time.Duration

// UnmarshalText implements encoding.TextUnmarshaler
func (d *Duration) UnmarshalText(data []byte) error {
    duration, err := time.ParseDuration(string(data))
    if err == nil {
        *d = Duration(duration)
    }
    return err
}

// MarshalText implements encoding.TextMarshaler
func (d Duration) MarshalText() ([]byte, error) {
    return []byte(time.Duration(d).String()), nil
}

type ConfigWithDuration struct {
    Duration Duration
}

Using the toml.UnmarshalerRec interface

You can also override marshaling rules specifically for TOML using the UnmarshalerRec and MarshalerRec interfaces. These are useful if you want to control how structs or arrays are handled. You can apply additional validation or set unexported struct fields.

Note: encoding.TextUnmarshaler and encoding.TextMarshaler should be preferred for simple (scalar) values because they're also compatible with other formats like JSON or YAML.

See the UnmarshalerRec example.

Using the toml.Unmarshaler interface

If you want to deal with raw TOML syntax, use the Unmarshaler and Marshaler interfaces. Their input and output is raw TOML syntax. As such, these interfaces are useful if you want to handle TOML at the syntax level.

See the Unmarshaler example.

API documentation

See Godoc.

License

MIT

Owner
Comments
  • unmarshalTOML function not called

    unmarshalTOML function not called

    Hello, Thanks for your great library.

    I would like to setup default values for my structure

    // A Row is a dashboard Row it can contains multiple panels
    type Row struct {
        Collapse bool    `json:"collapse"`
        Editable bool    `json:"editable"`
        Height   string  `json:"height"`
        Panels   []Panel `json:"panels" toml:"panel"`
        Title    string  `json:"title"`
    }
    

    So I defined this method :

    func (row *Row) UnmarshalTOML(b interface{}) error {
        fmt.Printf("toto\n") //for debug
    
        r := NewRow()
        err := toml.Unmarshal(b.([]byte), &r)
        *row = r
        return err
    }
    

    But the method is not called when I do a toml.Unmarshal call.

    Do you have any ideas about what I could do wrong here ?

    Thanks,

    Alain

  • No documentation on the PEG implementation used

    No documentation on the PEG implementation used

    Please document that the PEG source requires the https://github.com/pointlander/peg project to build. I had to dig through git history to see that this was the project from an old .travis.yml entry. :)

  • [Servers] should be a map, not a struct

    [Servers] should be a map, not a struct

    Hi,

    I really like your toml parser very much, as it is really simple to use. I have just this issue that bothers me:

    In https://github.com/toml-lang/toml, it is stated:

    "Tables (also known as hash tables or dictionaries) are collections of key/value pairs."
    

    Many times, the keys of the map can be represented as fields in a struct.

    type tomlConfig struct {
        ...
        Servers struct {  <------ in your example, Servers is a struct
            Alpha Server
            Beta  Server
        }
        ...
    }
    
    type Server struct {
        IP string
        DC string
    }
    

    You consider that "Servers" is a struct, with "Alpha" and "Beta" as field names.

    But in this case, how do you add new servers in the TOML config files ? If you add more servers, like [servers.gamma], it means that you must change the definition of the struct in your program, and you must know the names of all servers beforehand:

    type tomlConfig struct {
        ...
        Servers struct {
            Alpha Server
            Beta  Server
            Gamma Server
        }
        ...
    }
    

    I think that "Alpha" and "Beta" are not field names of a struct, but keys of a map.

    type tomlConfig struct {
        ...
        Servers map[string]Server     <-------- I think that Servers should be a map, and not a struct
        ...
    }
    
    type Server struct {
        IP string
        DC string
    }
    

    It would be good if the parser could write in map, as well as in struct.

    What is your optinion ?

  • Parsing Inline Tables

    Parsing Inline Tables

    I'm trying to use TOML Inline tables and running into an issue. Given the following program:

    package main
    
    import (
        "fmt"
    
        "github.com/naoina/toml"
    )
    
    const good = `
    [heading]
    key1 = "value1"
    
    [[heading.array]]
    key2 = "value2"
    
    [[heading.array]]
    key2 = "value3"
      [heading.array.map]
      key3 = "value4"
    
    [[heading.array]]
    key2 = "value5"
      [heading.array.map]
      key3 = "value6"
    `
    
    const bad = `
    [heading]
    key1 = "value1"
    
    [[heading.array]]
    key2 = "value2"
    
    [[heading.array]]
    key2 = "value3"
    map = { key3 = "value4" }
    
    [[heading.array]]
    key2 = "value5"
    map = { key3 = "value6" }
    `
    
    func main() {
        if _, err := toml.Parse([]byte(good)); err != nil {
            fmt.Println(err)
        }
        if _, err := toml.Parse([]byte(bad)); err != nil {
            fmt.Println(err)
        }
    }
    

    Yields this error:

    toml: line 14: table 'map' is in conflict with normal table in line 10

    In the bad config, why does the second definition of map interfere with the first one? I can change the bad config to the following and it works fine:

    const bad = `
    [heading]
    key1 = "value1"
    
    [[heading.array]]
    key2 = "value2"
    
    [[heading.array]]
    key2 = "value3"
    map = { key3 = "value4" }
    
    [[heading.array]]
    key2 = "value5"
      [heading.array.map]
      key3 = "value6"
    `
    
  • Improve PEG usage

    Improve PEG usage

    This documents the program used to generate the output file, updates the code to cope with changes in the newly generated output, and removes it from the repo so that it gets always generated and this kind of problems get spotted earlier.

  • Add support for struct pointer traversal

    Add support for struct pointer traversal

    This deals with two cases: where we have been provided with a pointer to a struct in the call to Marshal(), and also where we encounter a pointer while encoding a struct. The former case resulted in a panic (see #13), while the latter reported that Pointer types were not supported. The solution uses reflection to continually walk across pointers to get to the underlying value.

    Fixes #13

  • Expose the AST for more flexible usages

    Expose the AST for more flexible usages

    This is easily the best TOML parser and so I wanted to be able to expose the ast to be used in a more flexible way that having to be decoded into a struct or map.

    This exposes that API by doing a couple things:

    1. Move table and keyValue to the ast package and expose them
    2. Change the ast value to hold their original source data to allow for "detached" usage of setUnmarshaller
    3. Decouple the decoding functions from decodeState so they can be called when values are already decoded.
    4. Add Parse and UnmarshalTable to allow the user to retrieve and coerce parts into values.
  • Fix #44: Properly counting lines on Windows

    Fix #44: Properly counting lines on Windows

    Fix of issue #44: Fixed by a small changes in the grammar file and regenerated using: peg -inline -switch parser.peg.

    Made working following failed tests, as shown in the issue:

    --- PASS: TestUnmarshal_WithTable (0.00s)
    --- PASS: TestUnmarshal_WithArrayTable (0.00s)
    
  • Wrong lines counting on Windows

    Wrong lines counting on Windows

    Altough there are other problems on Windows resulting mainly from line ending difference (see the tests result bellow) one specific annoying issue is wrong lines counting occurring mainly in error strings, like:

           got:  line 15: key `b' is in conflict with table in line 7
           want: line 8: key `b' is in conflict with table in line 4
    

    All tests passes on linux. But on Windows:

    === RUN   TestConfigNormField
    --- PASS: TestConfigNormField (0.00s)
    === RUN   TestConfigFieldToKey
    --- PASS: TestConfigFieldToKey (0.00s)
    === RUN   TestConfigMissingField
    --- PASS: TestConfigMissingField (0.00s)
    === RUN   TestUnmarshal
    --- FAIL: TestUnmarshal (0.00s)
        decode_test.go:321: Unmarshal value mismatch for input:
            # test.toml
            
            ################################################################################
            ## Comment
            
            # Speak your mind with the hash symbol. They go from the symbol to the end of
            # the line.
            
            
            ################################################################################
            ## Table
            
            # Tables (also known as hash tables or dictionaries) are collections of
            # key/value pairs. They appear in square brackets on a line by themselves.
            
            [table]
            
            key = "value" # Yeah, you can do this.
            
            # Nested tables are denoted by table names with dots in them. Name your tables
            # whatever crap you please, just don't use #, ., [ or ].
            
            [table.subtable]
            
            key = "another value"
            
            # You don't need to specify all the super-tables if you don't want to. TOML
            # knows how to do it for you.
            
            # [x] you
            # [x.y] don't
            # [x.y.z] need these
            [x.y.z.w] # for this to work
            
            
            ################################################################################
            ## Inline Table
            
            # Inline tables provide a more compact syntax for expressing tables. They are
            # especially useful for grouped data that can otherwise quickly become verbose.
            # Inline tables are enclosed in curly braces `{` and `}`. No newlines are
            # allowed between the curly braces unless they are valid within a value.
            
            [table.inline]
            
            name = { first = "Tom", last = "Preston-Werner" }
            point = { x = 1, y = 2 }
            
            
            ################################################################################
            ## String
            
            # There are four ways to express strings: basic, multi-line basic, literal, and
            # multi-line literal. All strings must contain only valid UTF-8 characters.
            
            [string.basic]
            
            basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
            
            [string.multiline]
            
            # The following strings are byte-for-byte equivalent:
            key1 = "One\nTwo"
            key2 = """One\nTwo"""
            key3 = """
            One
            Two"""
            
            [string.multiline.continued]
            
            # The following strings are byte-for-byte equivalent:
            key1 = "The quick brown fox jumps over the lazy dog."
            
            key2 = """
            The quick brown \
            
            
              fox jumps over \
                the lazy dog."""
            
            key3 = """\
                   The quick brown \
                   fox jumps over \
                   the lazy dog.\
                   """
            
            [string.literal]
            
            # What you see is what you get.
            winpath  = 'C:\Users\nodejs\templates'
            winpath2 = '\\ServerX\admin$\system32\'
            quoted   = 'Tom "Dubs" Preston-Werner'
            regex    = '<\i\c*\s*>'
            
            
            [string.literal.multiline]
            
            regex2 = '''I [dw]on't need \d{2} apples'''
            lines  = '''
            The first newline is
            trimmed in raw strings.
               All other whitespace
               is preserved.
            '''
            
            
            ################################################################################
            ## Integer
            
            # Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
            # Negative numbers are prefixed with a minus sign.
            
            [integer]
            
            key1 = +99
            key2 = 42
            key3 = 0
            key4 = -17
            
            [integer.underscores]
            
            # For large numbers, you may use underscores to enhance readability. Each
            # underscore must be surrounded by at least one digit.
            key1 = 1_000
            key2 = 5_349_221
            key3 = 1_2_3_4_5     # valid but inadvisable
            
            
            ################################################################################
            ## Float
            
            # A float consists of an integer part (which may be prefixed with a plus or
            # minus sign) followed by a fractional part and/or an exponent part.
            
            [float.fractional]
            
            key1 = +1.0
            key2 = 3.1415
            key3 = -0.01
            
            [float.exponent]
            
            key1 = 5e+22
            key2 = 1e6
            key3 = -2E-2
            
            [float.both]
            
            key = 6.626e-34
            
            [float.underscores]
            
            key1 = 9_224_617.445_991_228_313
            key2 = 1e1_00
            
            
            ################################################################################
            ## Boolean
            
            # Booleans are just the tokens you're used to. Always lowercase.
            
            [boolean]
            
            True = true
            False = false
            
            
            ################################################################################
            ## Datetime
            
            # Datetimes are RFC 3339 dates.
            
            [datetime]
            
            key1 = 1979-05-27T07:32:00Z
            key2 = 1979-05-27T00:32:00-07:00
            key3 = 1979-05-27T00:32:00.999999-07:00
            
            
            ################################################################################
            ## Array
            
            # Arrays are square brackets with other primitives inside. Whitespace is
            # ignored. Elements are separated by commas. Data types may not be mixed.
            
            [array]
            
            key1 = [ 1, 2, 3 ]
            key2 = [ "red", "yellow", "green" ]
            key3 = [ [ 1, 2 ], [3, 4, 5] ]
            key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
            
            # Arrays can also be multiline. So in addition to ignoring whitespace, arrays
            # also ignore newlines between the brackets.  Terminating commas are ok before
            # the closing bracket.
            
            key5 = [
              1, 2, 3
            ]
            key6 = [
              1,
              2, # this is ok
            ]
            
            
            ################################################################################
            ## Array of Tables
            
            # These can be expressed by using a table name in double brackets. Each table
            # with the same double bracketed name will be an element in the array. The
            # tables are inserted in the order encountered.
            
            [[products]]
            
            name = "Hammer"
            sku = 738594937
            
            [[products]]
            
            [[products]]
            
            name = "Nail"
            sku = 284758393
            color = "gray"
            
            
            # You can create nested arrays of tables as well.
            
            [[fruit]]
              name = "apple"
            
              [fruit.physical]
                color = "red"
                shape = "round"
            
              [[fruit.variety]]
                name = "red delicious"
            
              [[fruit.variety]]
                name = "granny smith"
            
            [[fruit]]
              name = "banana"
            
              [[fruit.variety]]
                name = "plantain"
            
            diff:
             {
              Table: {
               Key: "value",
               Subtable: {
                Key: "another value",
               },
               Inline: {
                Name: {
                 First: "Tom",
                 Last: "Preston-Werner",
                },
                Point: {
                 x: 1,
                 y: 2,
                },
               },
              },
              X: {
               Y: {
                Z: {
                 W: {
                 },
                },
               },
              },
              String: {
               Basic: {
                Basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF.",
               },
               Multiline: {
                Key1: "One\nTwo",
                Key2: "One\nTwo",
            -   Key3: "One\r\nTwo",
            +   Key3: "One\nTwo",
                Continued: {
                 Key1: "The quick brown fox jumps over the lazy dog.",
                 Key2: "The quick brown fox jumps over the lazy dog.",
                 Key3: "The quick brown fox jumps over the lazy dog.",
                },
               },
               Literal: {
                Winpath: "C:\\Users\\nodejs\\templates",
                Winpath2: "\\\\ServerX\\admin$\\system32\\",
                Quoted: "Tom \"Dubs\" Preston-Werner",
                Regex: "<\\i\\c*\\s*>",
                Multiline: {
                 Regex2: "I [dw]on't need \\d{2} apples",
            -    Lines: "The first newline is\r\ntrimmed in raw strings.\r\n   All other whitespace\r\n   is preserved.\r\n",
            +    Lines: "The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n",
                },
               },
              },
              Integer: {
               Key1: 99,
               Key2: 42,
               Key3: 0,
               Key4: -17,
               Underscores: {
                Key1: 1000,
                Key2: 5349221,
                Key3: 12345,
               },
              },
              Float: {
               Fractional: {
                Key1: 1,
                Key2: 3.1415,
                Key3: -0.01,
               },
               Exponent: {
                Key1: 5e+22,
                Key2: 1e+06,
                Key3: -0.02,
               },
               Both: {
                Key: 6.626e-34,
               },
               Underscores: {
                Key1: 9.224617445991227e+06,
                Key2: 1e+100,
               },
              },
              Boolean: {
               True: true,
               False: false,
              },
              Datetime: {
               Key1: 1979-05-27 07:32:00 +0000 UTC,
               Key2: 1979-05-27 00:32:00 -0700 -0700,
               Key3: 1979-05-27 00:32:00.999999 -0700 -0700,
              },
              Array: {
               Key1: [1,2,3],
               Key2: ["red","yellow","green"],
               Key3: [[1,2],[3,4,5]],
               Key4: [[1,2],["a","b","c"]],
               Key5: [1,2,3],
               Key6: [1,2],
              },
              Products: [
               {
                Name: "Hammer",
                Sku: 738594937,
                Color: "",
               },
               {
                Name: "",
                Sku: 0,
                Color: "",
               },
               {
                Name: "Nail",
                Sku: 284758393,
                Color: "gray",
               },
              ],
              Fruit: [
               {
                Name: "apple",
                Physical: {
                 Color: "red",
                 Shape: "round",
                },
                Variety: [{Name:"red delicious"},{Name:"granny smith"}],
               },
               {
                Name: "banana",
                Physical: {
                 Color: "",
                 Shape: "",
                },
                Variety: [{Name:"plantain"}],
               },
              ],
             }
    === RUN   TestUnmarshal_WithString
    --- FAIL: TestUnmarshal_WithString (0.00s)
        decode_test.go:321: Unmarshal value mismatch for input:
            # unmarshal-string-1.toml
            
            key1 = "One\nTwo"
            key2 = """One\nTwo"""
            key3 = """
            One
            Two"""
            
            diff:
             {
              Str: "",
              Key1: "One\nTwo",
              Key2: "One\nTwo",
            - Key3: "One\r\nTwo",
            + Key3: "One\nTwo",
              Winpath: "",
              Winpath2: "",
              Quoted: "",
              Regex: "",
              Regex2: "",
              Lines: "",
             }
        decode_test.go:321: Unmarshal value mismatch for input:
            # unmarshal-string-2.toml
            
            regex2 = '''I [dw]on't need \d{2} apples'''
            lines  = '''
            The first newline is
            trimmed in raw strings.
               All other whitespace
               is preserved.
            '''
            diff:
             {
              Str: "",
              Key1: "",
              Key2: "",
              Key3: "",
              Winpath: "",
              Winpath2: "",
              Quoted: "",
              Regex: "",
              Regex2: "I [dw]on't need \\d{2} apples",
            - Lines: "The first newline is\r\ntrimmed in raw strings.\r\n   All other whitespace\r\n   is preserved.\r\n",
            + Lines: "The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n",
             }
    === RUN   TestUnmarshal_WithInteger
    --- PASS: TestUnmarshal_WithInteger (0.00s)
    === RUN   TestUnmarshal_WithFloat
    --- PASS: TestUnmarshal_WithFloat (0.00s)
    === RUN   TestUnmarshal_WithBoolean
    --- PASS: TestUnmarshal_WithBoolean (0.00s)
    === RUN   TestUnmarshal_WithDatetime
    --- PASS: TestUnmarshal_WithDatetime (0.00s)
    === RUN   TestUnmarshal_WithArray
    --- PASS: TestUnmarshal_WithArray (0.00s)
    === RUN   TestUnmarshal_WithTable
    --- FAIL: TestUnmarshal_WithTable (0.00s)
        decode_test.go:318: Error mismatch for input:
            # unmarshal-table-conflict-1.toml
            # Table a is defined twice.
            
            [a]
            b = 1
            
            [a]
            c = 2
            
            got:  line 13: table `a' is in conflict with table in line 7
            want: line 7: table `a' is in conflict with table in line 4
        decode_test.go:318: Error mismatch for input:
            # unmarshal-table-conflict-2.toml
            # Key b conflicts with table a.b.
            
            [a]
            b = 1
            
            [a.b]
            c = 2
            
            got:  line 13: table `a.b' is in conflict with line 9
            want: line 7: table `a.b' is in conflict with line 5
        decode_test.go:318: Error mismatch for input:
            # unmarshal-table-conflict-3.toml
            # Key b conflicts with table a.b
            
            [a.b]
            c = 2
            
            [a]
            b = 1
            
            got:  line 15: key `b' is in conflict with table in line 7
            want: line 8: key `b' is in conflict with table in line 4
    === RUN   TestUnmarshal_WithEmbeddedStruct
    --- PASS: TestUnmarshal_WithEmbeddedStruct (0.00s)
    === RUN   TestUnmarshal_WithArrayTable
    --- FAIL: TestUnmarshal_WithArrayTable (0.00s)
        decode_test.go:318: Error mismatch for input:
            # unmarshal-arraytable-conflict-1.toml
            
            [[fruit]]
            name = "apple"
            
            [[fruit.variety]]
            name = "red delicious"
            
            # This table conflicts with the previous table.
            [fruit.variety]
            name = "granny smith"
            
            got:  line 19: table `fruit.variety' is in conflict with array table in line 11
            want: line 10: table `fruit.variety' is in conflict with array table in line 6
        decode_test.go:318: Error mismatch for input:
            # unmarshal-arraytable-conflict-2.toml
            
            [[fruit]]
            name = "apple"
            
            [fruit.variety]
            name = "granny smith"
            
            # This table conflicts with the previous table.
            [[fruit.variety]]
            name = "red delicious"
            
            got:  line 19: array table `fruit.variety' is in conflict with table in line 11
            want: line 10: array table `fruit.variety' is in conflict with table in line 6
        decode_test.go:318: Error mismatch for input:
            # unmarshal-arraytable-conflict-3.toml
            
            [[fruit]]
            name = "apple"
            variety = { name = "granny smith" }
            
            # conflicts with inline table above
            [[fruit.variety]]
            
            got:  line 15: array table `fruit.variety' is in conflict with table in line 9
            want: line 8: array table `fruit.variety' is in conflict with table in line 5
    === RUN   TestUnmarshal_WithUnmarshaler
    --- FAIL: TestUnmarshal_WithUnmarshaler (0.00s)
        decode_test.go:998: toml.Unmarshal(data, &v); v => toml.testStruct{Title:"Unmarshaled: \"testtitle\"", MaxConn:"Unmarshaled: 777", Ports:"Unmarshaled: [8080, 8081, 8082]", Servers:"Unmarshaled: [1, 2, 3]", Table:"Unmarshaled: [table]\r\nname = \"alice\"", Arraytable:"Unmarshaled: [[arraytable]]\r\nname = \"alice\"\n[[arraytable]]\r\nname = \"bob\"", ArrayOfStruct:[]toml.testUnmarshalerStruct{toml.testUnmarshalerStruct{Title:"Unmarshaled: [[array_of_struct]]\r\ntitle = \"Alice's Adventures in Wonderland\"\r\nauthor = \"Lewis Carroll\"", Author:""}}}; want toml.testStruct{Title:"Unmarshaled: \"testtitle\"", MaxConn:"Unmarshaled: 777", Ports:"Unmarshaled: [8080, 8081, 8082]", Servers:"Unmarshaled: [1, 2, 3]", Table:"Unmarshaled: [table]\nname = \"alice\"", Arraytable:"Unmarshaled: [[arraytable]]\nname = \"alice\"\n[[arraytable]]\nname = \"bob\"", ArrayOfStruct:[]toml.testUnmarshalerStruct{toml.testUnmarshalerStruct{Title:"Unmarshaled: [[array_of_struct]]\ntitle = \"Alice's Adventures in Wonderland\"\nauthor = \"Lewis Carroll\"", Author:""}}}
    === RUN   TestUnmarshal_WithUnmarshalerForTopLevelStruct
    --- PASS: TestUnmarshal_WithUnmarshalerForTopLevelStruct (0.00s)
    === RUN   TestUnmarshal_WithTextUnmarshaler
    --- PASS: TestUnmarshal_WithTextUnmarshaler (0.00s)
    === RUN   TestUnmarshal_WithUnmarshalerRec
    --- PASS: TestUnmarshal_WithUnmarshalerRec (0.00s)
    === RUN   TestUnmarshal_WithMultibyteString
    --- PASS: TestUnmarshal_WithMultibyteString (0.00s)
    === RUN   TestUnmarshal_WithPointers
    --- PASS: TestUnmarshal_WithPointers (0.00s)
    === RUN   TestUnmarshalMap
    --- PASS: TestUnmarshalMap (0.00s)
    === RUN   TestUnmarshal_WithQuotedKeyValue
    --- PASS: TestUnmarshal_WithQuotedKeyValue (0.00s)
    === RUN   TestUnmarshal_WithCustomPrimitiveType
    --- PASS: TestUnmarshal_WithCustomPrimitiveType (0.00s)
    === RUN   TestUnmarshal_WithInterface
    --- PASS: TestUnmarshal_WithInterface (0.00s)
    === RUN   TestMarshal
    --- FAIL: TestMarshal (0.00s)
        encode_test.go:185: Output mismatch:
            Value:
            {Table:    {Key:      "value",
                        Subtable: {Key: "another value"},
                        Inline:   {Name:  {First: "Tom",
                                           Last:  "Preston-Werner"},
                                   Point: {x: 1,
                                           y: 2}}},
             X:        {Y: {Z: {W: {}}}},
             String:   {Basic:     {Basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."},
                        Multiline: {Key1:      "One\nTwo",
                                    Key2:      "One\nTwo",
                                    Key3:      "One\nTwo",
                                    Continued: {Key1: "The quick brown fox jumps over the lazy dog.",
                                                Key2: "The quick brown fox jumps over the lazy dog.",
                                                Key3: "The quick brown fox jumps over the lazy dog."}},
                        Literal:   {Winpath:   "C:\\Users\\nodejs\\templates",
                                    Winpath2:  "\\\\ServerX\\admin$\\system32\\",
                                    Quoted:    "Tom \"Dubs\" Preston-Werner",
                                    Regex:     "<\\i\\c*\\s*>",
                                    Multiline: {Regex2: "I [dw]on't need \\d{2} apples",
                                                Lines:  "The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n"}}},
             Integer:  {Key1:        99,
                        Key2:        42,
                        Key3:        0,
                        Key4:        -17,
                        Underscores: {Key1: 1000,
                                      Key2: 5349221,
                                      Key3: 12345}},
             Float:    {Fractional:  {Key1: 1,
                                      Key2: 3.1415,
                                      Key3: -0.01},
                        Exponent:    {Key1: 5e+22,
                                      Key2: 1e+06,
                                      Key3: -0.02},
                        Both:        {Key: 6.626e-34},
                        Underscores: {Key1: 9.224617445991227e+06,
                                      Key2: 1e+100}},
             Boolean:  {True:  true,
                        False: false},
             Datetime: {Key1: 1979-05-27 07:32:00 +0000 UTC,
                        Key2: 1979-05-27 00:32:00 -0700 -0700,
                        Key3: 1979-05-27 00:32:00.999999 -0700 -0700},
             Array:    {Key1: [1,2,3],
                        Key2: ["red","yellow","green"],
                        Key3: [[1,2],[3,4,5]],
                        Key4: [[1,2],["a","b","c"]],
                        Key5: [1,2,3],
                        Key6: [1,2]},
             Products: [{Name:  "Hammer",
                         Sku:   738594937,
                         Color: ""},
                        {Name:  "",
                         Sku:   0,
                         Color: ""},
                        {Name:  "Nail",
                         Sku:   284758393,
                         Color: "gray"}],
             Fruit:    [{Name:     "apple",
                         Physical: {Color: "red",
                                    Shape: "round"},
                         Variety:  [{Name:"red delicious"},{Name:"granny smith"}]},
                        {Name:     "banana",
                         Physical: {Color: "",
                                    Shape: ""},
                         Variety:  [{Name:"plantain"}]}]}
            Diff:
            -[table]
            -key = "value"
            +[table]
            +key = "value"
            +
            +[table.subtable]
            +key = "another value"
            +
            +[table.inline.name]
            +first = "Tom"
            +last = "Preston-Werner"
            +
            +[table.inline.point]
            +x = 1
            +y = 2
            +
            +[string.basic]
            +basic = "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."
            +
            +[string.multiline]
            +key1 = "One\nTwo"
            +key2 = "One\nTwo"
            +key3 = "One\nTwo"
            +
            +[string.multiline.continued]
            +key1 = "The quick brown fox jumps over the lazy dog."
            +key2 = "The quick brown fox jumps over the lazy dog."
            +key3 = "The quick brown fox jumps over the lazy dog."
            +
            +[string.literal]
            +winpath = "C:\\Users\\nodejs\\templates"
            +winpath2 = "\\\\ServerX\\admin$\\system32\\"
            +quoted = "Tom \"Dubs\" Preston-Werner"
            +regex = "<\\i\\c*\\s*>"
            +
            +[string.literal.multiline]
            +regex2 = "I [dw]on't need \\d{2} apples"
            +lines = "The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n"
            +
            +[integer]
            +key1 = 99
            +key2 = 42
            +key3 = 0
            +key4 = -17
            +
            +[integer.underscores]
            +key1 = 1000
            +key2 = 5349221
            +key3 = 12345
            +
            +[float.fractional]
            +key1 = 1e+00
            +key2 = 3.1415e+00
            +key3 = -1e-02
            +
            +[float.exponent]
            +key1 = 5e+22
            +key2 = 1e+06
            +key3 = -2e-02
            +
            +[float.both]
            +key = 6.626e-34
            +
            +[float.underscores]
            +key1 = 9.224617445991227e+06
            +key2 = 1e+100
            +
            +[boolean]
            +true = true
            +false = false
            +
            +[datetime]
            +key1 = 1979-05-27T07:32:00Z
            +key2 = 1979-05-27T00:32:00-07:00
            +key3 = 1979-05-27T00:32:00.999999-07:00
            +
            +[array]
            +key1 = [1, 2, 3]
            +key2 = ["red", "yellow", "green"]
            +key3 = [[1, 2], [3, 4, 5]]
            +key4 = [[1, 2], ["a", "b", "c"]]
            +key5 = [1, 2, 3]
            +key6 = [1, 2]
            +
            +[[products]]
            +name = "Hammer"
            +sku = 738594937
            +
            +[[products]]
            +
            +[[products]]
            +name = "Nail"
            +sku = 284758393
            +color = "gray"
            +
            +[[fruit]]
            +name = "apple"
            +
            +[fruit.physical]
            +color = "red"
            +shape = "round"
            +
            +[[fruit.variety]]
            +name = "red delicious"
            +
            +[[fruit.variety]]
            +name = "granny smith"
            +
            +[[fruit]]
            +name = "banana"
            +
            +[fruit.physical]
            +color = ""
            +shape = ""
            +
            +[[fruit.variety]]
            +name = "plantain"
             
            -[table.subtable]
            -key = "another value"
            -
            -[table.inline.name]
            -first = "Tom"
            -last = "Preston-Werner"
            -
            -[table.inline.point]
            -x = 1
            -y = 2
            -
            -[string.basic]
            -basic = "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."
            -
            -[string.multiline]
            -key1 = "One\nTwo"
            -key2 = "One\nTwo"
            -key3 = "One\nTwo"
            -
            -[string.multiline.continued]
            -key1 = "The quick brown fox jumps over the lazy dog."
            -key2 = "The quick brown fox jumps over the lazy dog."
            -key3 = "The quick brown fox jumps over the lazy dog."
            -
            -[string.literal]
            -winpath = "C:\\Users\\nodejs\\templates"
            -winpath2 = "\\\\ServerX\\admin$\\system32\\"
            -quoted = "Tom \"Dubs\" Preston-Werner"
            -regex = "<\\i\\c*\\s*>"
            -
            -[string.literal.multiline]
            -regex2 = "I [dw]on't need \\d{2} apples"
            -lines = "The first newline is\ntrimmed in raw strings.\n   All other whitespace\n   is preserved.\n"
            -
            -[integer]
            -key1 = 99
            -key2 = 42
            -key3 = 0
            -key4 = -17
            -
            -[integer.underscores]
            -key1 = 1000
            -key2 = 5349221
            -key3 = 12345
            -
            -[float.fractional]
            -key1 = 1e+00
            -key2 = 3.1415e+00
            -key3 = -1e-02
            -
            -[float.exponent]
            -key1 = 5e+22
            -key2 = 1e+06
            -key3 = -2e-02
            -
            -[float.both]
            -key = 6.626e-34
            -
            -[float.underscores]
            -key1 = 9.224617445991227e+06
            -key2 = 1e+100
            -
            -[boolean]
            -true = true
            -false = false
            -
            -[datetime]
            -key1 = 1979-05-27T07:32:00Z
            -key2 = 1979-05-27T00:32:00-07:00
            -key3 = 1979-05-27T00:32:00.999999-07:00
            -
            -[array]
            -key1 = [1, 2, 3]
            -key2 = ["red", "yellow", "green"]
            -key3 = [[1, 2], [3, 4, 5]]
            -key4 = [[1, 2], ["a", "b", "c"]]
            -key5 = [1, 2, 3]
            -key6 = [1, 2]
            -
            -[[products]]
            -name = "Hammer"
            -sku = 738594937
            -
            -[[products]]
            -
            -[[products]]
            -name = "Nail"
            -sku = 284758393
            -color = "gray"
            -
            -[[fruit]]
            -name = "apple"
            -
            -[fruit.physical]
            -color = "red"
            -shape = "round"
            -
            -[[fruit.variety]]
            -name = "red delicious"
            -
            -[[fruit.variety]]
            -name = "granny smith"
            -
            -[[fruit]]
            -name = "banana"
            -
            -[fruit.physical]
            -color = ""
            -shape = ""
            -
            -[[fruit.variety]]
            -name = "plantain"
            -
        encode_test.go:185: Output mismatch:
            Value:
            {intKeys:       {1: 1,
                             2: 2,
                             3: 3},
             marshalerKeys: {0001-01-01 00:00:00 +0000 UTC: 1}}
            Diff:
            -[intKeys]
            -1 = 1
            -2 = 2
            -3 = 3
            +[intKeys]
            +1 = 1
            +2 = 2
            +3 = 3
            +
            +[marshalerKeys]
            +"0001-01-01T00:00:00Z" = 1
             
            -[marshalerKeys]
            -"0001-01-01T00:00:00Z" = 1
            -
        encode_test.go:185: Output mismatch:
            Value:
            {m1: {S: "1"},
             m2: {S: "2"},
             m3: {S: "3"}}
            Diff:
            -m1 = 1
            -m2 = 2
            -m3 = 3
            +m1 = 1
            +m2 = 2
            +m3 = 3
             
        encode_test.go:185: Output mismatch:
            Value:
            {m1:  {},
             m2:  {},
             m3:  {},
             sub: {}}
            Diff:
            -m1 = 1
            -m2 = 2
            -m3 = 3
            +m1 = 1
            +m2 = 2
            +m3 = 3
            +
            +[sub]
            +key = 1
             
            -[sub]
            -key = 1
            -
        encode_test.go:185: Output mismatch:
            Value:
            {:                    "empty",
              :                   "space",
             -:                   "dash (not quoted)",
             1:                   "number (not quoted)",
             subtable with space: {depth:                  1,
                                   subsubtable with space: {depth: 2}},
             ʎǝʞ:              "reverse"}
            Diff:
            -"" = "empty"
            -" " = "space"
            -- = "dash (not quoted)"
            -1 = "number (not quoted)"
            -"ʎǝʞ" = "reverse"
            +"" = "empty"
            +" " = "space"
            +- = "dash (not quoted)"
            +1 = "number (not quoted)"
            +"ʎǝʞ" = "reverse"
            +
            +["subtable with space"]
            +depth = 1
            +
            +["subtable with space"."subsubtable with space"]
            +depth = 2
             
            -["subtable with space"]
            -depth = 1
            -
            -["subtable with space"."subsubtable with space"]
            -depth = 2
            -
    === RUN   TestMarshalRoundTrip
    --- PASS: TestMarshalRoundTrip (0.00s)
    === RUN   TestMarshalArrayTableEmptyParent
    --- FAIL: TestMarshalArrayTableEmptyParent (0.00s)
        encode_test.go:222: Output mismatch:
            -[[bars]]
            -[bars.baz]
            -key = 1
            +[[bars]]
            +[bars.baz]
            +key = 1
            +
            +[[bars]]
            +[bars.baz]
            +key = 2
             
            -[[bars]]
            -[bars.baz]
            -key = 2
            -
    === RUN   TestMarshalPointerError
    --- PASS: TestMarshalPointerError (0.00s)
    === RUN   TestMarshalNonStruct
    --- PASS: TestMarshalNonStruct (0.00s)
    === RUN   TestMarshalOmitempty
    --- PASS: TestMarshalOmitempty (0.00s)
    === RUN   Example
    --- PASS: Example (0.00s)
    === RUN   ExampleUnmarshalerRec
    --- PASS: ExampleUnmarshalerRec (0.00s)
    === RUN   ExampleUnmarshaler
    --- PASS: ExampleUnmarshaler (0.00s)
    === RUN   Example_textUnmarshaler
    --- PASS: Example_textUnmarshaler (0.00s)
    === RUN   Example_textUnmarshalerError
    --- PASS: Example_textUnmarshalerError (0.00s)
    FAIL
    FAIL	github.com/naoina/toml	0.283s
    ?   	github.com/naoina/toml/ast	[no test files]
    
  • How modifying a key?

    How modifying a key?

    Does the library incorporate the possibility of modifying a key? Eg: [server] addr = "127.0.0.1" port = 8080 Update server set addr = ":: 1" Update server set port = 3000

    Given

    [server] addr = "::1" port = 3000

  • Marshal doesn't handle multiple struct tag fields properly

    Marshal doesn't handle multiple struct tag fields properly

    If you have multiple struct field tags, and the first one isn't "toml" then it's ignored and the default logic kicks in for naming the fields in the serialized output.

    Example:

    
    package main
    
    import (
            "fmt"
            "github.com/naoina/toml"
    )
    
    type cfg struct {
            MySetting string `json:"JSONMySetting",toml:"toml_my_setting"` // Does not work
            //MySetting string `toml:"toml_my_setting",json:"JSONMySetting"` // Works
    }
    
    func main() {
            c1 := cfg{"foo"}
    
            data, err := toml.Marshal(&c1)
            if err != nil {
                    panic(err)
            }
            fmt.Printf("Serialized: %s", string(data))
    
            var c2 cfg
                    
            if err := toml.Unmarshal(data, &c2); err != nil {
                    panic(err)
            }
            fmt.Printf("Unmarshaled: %#v\n", c2)
    }
    

    Expected output:

    Serialized: toml_my_setting="foo"
    Unmarshaled: main.cfg{MySetting:"foo"}
    

    Actual output:

    Serialized: my_setting="foo"
    Unmarshaled: main.cfg{MySetting:"foo"}
    
  • Behavior change between 0.1.0 and 0.1.1

    Behavior change between 0.1.0 and 0.1.1

    If this were a major version change, I'd accept that "things change" but since it was a minor revision, I feel compelled to point out my now-broken use-case with this library. Maybe someone can correct my flaws and show me a valid way to do this. Or we can identify this as some sort of regression.

    type tomlConfig struct {
    	IncludeDir string
    	Services []*Config
    }
    
    type Config struct {
    	Name string
    	Paths []string
    }
    
    func loadConfigFile(configFile string) (tomlConfig, error) {
    	var config tomlConfig
    	if buf, err := ioutil.ReadFile(configFile); err != nil {
    		return config, err
    	} else if err := toml.Unmarshal(buf, &config); err != nil {
    		return config, err
    	} else if config.IncludeDir == "" {
    		return config, nil
    	}
    
    	// include_dir is not empty, read in more config files from a conf.d folder.
    	includedConfigFiles, err := ioutil.ReadDir(config.IncludeDir)
    	if err != nil {
    		return config, err
    	}
    
    	for _, includeFile := range includedConfigFiles {
    		filePath := filepath.Join(config.IncludeDir, includeFile.Name())
    		if !strings.HasSuffix(filePath, ".conf") {
    			continue // Only read files that end with .conf.
    		} else if buf, err := ioutil.ReadFile(filePath); err != nil {
    			return config, err
    		} else if err := toml.Unmarshal(buf, &config); err != nil {
    			return config, err
    		}
    	}
    	return config, nil
    }
    

    The above code works great with toml library 0.1.0. The Services slice is appended to for each [[services]] found in the included config files. In version 0.1.1 the slice only contains the data from the last config file Unmarshal()'d. Thoughts?

  • Decoding into embedded struct

    Decoding into embedded struct

    At some point this library was changed so it can't decode into embedded structs any more

    package main
    
    import (
    	"fmt"
    
    	"github.com/naoina/toml"
    )
    
    type DBConfig struct {
    	User string `toml:"user"`
    }
    
    type DBClusterConfig struct {
    	DBConfig
    }
    
    func main() {
    	doc := []byte(`
    		user = "test_user"
    	`)
    
    	config := new(DBClusterConfig)
    	if err := toml.Unmarshal(doc, &config); err != nil {
    		panic(err)
    	}
    	fmt.Println("user", config.User)
    }
    

    Old revisions (such as 7a3d4a6210ea3f8a66f704c5603ff27fb91fea8e) work just fine.

  • Support AST manipulation

    Support AST manipulation

    Say I wanted to load a TOML file from a program, change a value, and write it back to disk.

    The current configuration makes that difficult. Say I wanted to change the value in testdata/example.toml. I would call toml.Parse() and eventually get an ast.Value, but there's no way to modify that, and updating the Value would not update the ast.Table.Data. In addition, it doesn't seem like there's a way to convert an *ast.Table back into a []byte.

  • support field validation

    support field validation

    It would be awesome if the library could support some simple validations, such as marking which keys are required vs optional and perhaps having some regexp validations for strings and some ranges for ints. This could cover 80%-90% of use-cases and avoid having to write validations manually in main.

Related tags
Library providing routines to merge and validate JSON, YAML and/or TOML files
Library providing routines to merge and validate JSON, YAML and/or TOML files

CONFLATE Library providing routines to merge and validate JSON, YAML, TOML files and/or structs (godoc) Typical use case: Make your application config

Sep 26, 2022
Light weight, extensible configuration management library for Go. Built in support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.
Light weight, extensible configuration management library for Go. Built in support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

koanf (pronounced conf; a play on the Japanese Koan) is a library for reading configuration from different sources in different formats in Go applicat

Jan 8, 2023
Go library for the TOML language

go-toml Go library for the TOML format. This library supports TOML version v1.0.0-rc.3 Features Go-toml provides the following features for using data

Dec 27, 2022
Golang Configuration tool that support YAML, JSON, TOML, Shell Environment

Configor Golang Configuration tool that support YAML, JSON, TOML, Shell Environment (Supports Go 1.10+) Usage package main import ( "fmt" "github.c

Dec 29, 2022
Generic templating tool with support of JSON, YAML and TOML data

gotempl Small binary used to generate files from Go Templates and data files. The following formats are supported: JSON YAML TOML Usage usage: gotempl

Jun 15, 2022
Tmpl - A tool to apply variables from cli, env, JSON/TOML/YAML files to templates

tmpl allows to apply variables from JSON/TOML/YAML files, environment variables or CLI arguments to template files using Golang text/template and functions from the Sprig project.

Nov 14, 2022
🔎🪲 Malleable C2 profiles parser and assembler written in golang

goMalleable ?? ?? Malleable C2 profiles parser and assembler written in golang Table of Contents Introduction Installation Usage Examples Introduction

Oct 24, 2022
ini parser for golang
ini parser for golang

INI Parser & Write Library ini parser and write library for Golang,easy-use,fast Features Can be read by []byte Can be read by file Supports file moni

Dec 3, 2022
Nginx Configuration Golang Parser

Nginx Configuration Golang Parser

Oct 21, 2022
A simple INI file parser and builder.

ini A simple INI file parser and builder. What Read INI files, or build them section by section. Datatypes are kept very simple. The main fuctions are

Nov 20, 2021
Go-config - Config parser for go that supports environment vars and multiple yaml files

go-multiconfig This package is able to parse yaml config files. It supports gett

Jun 23, 2022
parser for configuration files

config-loader config-loader supports to load config files and convert to map values. Supported format json Usage import ( "fmt" "github.com/tharun2

Nov 27, 2021
Genv is a library for Go (golang) that makes it easy to read and use environment variables in your projects. It also allows environment variables to be loaded from the .env file.

genv Genv is a library for Go (golang) that makes it easy to read and use environment variables in your projects. It also allows environment variables

Dec 21, 2022
Golang library for reading properties from configuration files in JSON and YAML format or from environment variables.

go-config Golang library for reading properties from configuration files in JSON and YAML format or from environment variables. Usage Create config in

Aug 22, 2022
Golang library for managing configuration data from environment variables

envconfig import "github.com/kelseyhightower/envconfig" Documentation See godoc Usage Set some environment variables: export MYAPP_DEBUG=false export

Dec 26, 2022
Cfginterpolator is an interpolate library in golang allowing to include data from external sources in your configuration

cfginterpolator cfginterpolator is an interpolate library in golang allowing to include data from external sources in your configuration cfginterpolat

Dec 14, 2021
go-up! A simple configuration library with recursive placeholders resolution and no magic.

go-up! A simple configuration library with placeholders resolution and no magic. go-up provides a simple way to configure an application from multiple

Nov 23, 2022
🛠 A configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP
🛠 A configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP

config A small configuration library for Go that parses environment variables, JSON files, and reloads automatically on SIGHUP. Example func main() {

Dec 11, 2022