Go the third-party libraries ini

Article from

Quick Start

my.ini
# possible values : production, development
app_mode = development

[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
data = /home/git/grafana

[server]
# Protocol (http or https)
protocol = http

# The http port  to use
http_port = 9999

# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
enforce_domain = true

 

Resolve

main Package 

Import ( 
    "FMT" 
    "OS" 

    "gopkg.in/ini.v1" 
) 

FUNC main () { 
    CFG, ERR: = ini.Load ( "the my.ini") 
    ! = nil {IF ERR 
        fmt.Printf ( "read the Fail to File:% V", ERR) 
        os.Exit (. 1) 
    } 

    // typical read operation, the default partition can use an empty string indicates 
    fmt.Println ( "App Mode:", cfg.Section ( "" ) .Key ( "app_mode") String ()). 
    fmt.Println ( "the Data Path:."., cfg.Section ( "Paths") Key ( "the Data") String ()) 

    // we can do some candidates value limiting operation 
    fmt.Println ( "Protocol Server:", 
        cfg.Section ( "Server") Key ( "Protocol") the in ( "HTTP",..[] {String "HTTP", "HTTPS"})) 
    // if the read value is not within the candidate list, the default value will fallback using the provided 
    fmt.Println ( "Email Protocol:",
        cfg.Section ( "Server"). Key ( "Protocol"). the In ( "SMTP", [] {String "IMAP", "SMTP"})) 

    // try automatic conversion 
    fmt.Printf ( "Port Number The: (% [. 1] T)% [. 1] D \ n-", cfg.Section (" Server ") (Http_port") MustInt (9999)). Key. " 
    fmt.Printf (" the Enforce the Domain: (% [ 1] T)% [1] v \ the n-", cfg.Section (" Server "). Key (" enforce_domain "). MustBool (false)) 
    
    // about it, change certain values and then save 
    cfg.Section ( "") .Key ( "app_mode"). the SetValue ( "Production") 
    cfg.SaveTo ( "my.ini.local") 
}

 

result  

$ go run main.go
App Mode: development
Data Path: /home/git/grafana
Server Protocol: http
Email Protocol: smtp
Port Number: (int) 9999
Enforce Domain: (bool) true

$ cat my.ini.local
# possible values : production, development
app_mode = production

[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
data = /home/git/grafana
...

 

 

Loaded from the data source

As said before, load the configuration data from multiple sources is a basic operation.

So, in the end what is the data source it?

Data source can be a  []byte type of raw data string type or file path  io.ReadCloser. You can load any number of data sources. If you pass other types of data sources, it will directly return an error.

CFG, ERR: = ini.Load ( 
    [] byte ( "RAW Data"), original data // 
    "filename", // File Path 
    ioutil.NopCloser (bytes.NewReader ([] byte ( "some other data")) ), 
)

Or start with a blank document:

cfg: = ini.Empty ()

When you can not decide at the outset what data source to load, you can still use the Append () to load them when needed.

err := cfg.Append("other file", []byte("other raw data"))

When you want to load a series of documents, but can not determine which file does not exist, you can ignore them by calling the function LooseLoad ().

cfg, err := ini.LooseLoad("filename", "filename_404")

More Niubi, when those documents did not previously exist suddenly appeared at the time to recall Reload () method, then they will be properly loaded.

 

Data overwritten

When loading a plurality of data sources, if a certain key appears in one or more data sources, the data will overwrite occurs. A previous value of the key source data will be read the next data source overwritten.

For example, if the two loading profiles  my.ini and  my.ini.local(  started with  the file input and output), app_mode the value is  production not  development.

 
cfg, err := ini.Load("my.ini", "my.ini.local")
...

cfg.Section("").Key("app_mode").String() // production

Only overwrite the data does not trigger in one case, that the use ShadowLoad load data source.

In some cases, your configuration file may contain a non-key-value pairs of data lines, the parser will complain default and terminate resolution. If you want to be able to ignore the parser and parse them to complete the remaining content can be achieved by the following method:
cfg, err := ini.LoadSources(ini.LoadOptions{
    SkipUnrecognizableLines: true,
}, "other.ini")

 

Save arrangement

And finally to this moment, it is time to save the configuration.

More primitive approach is to configure the output to a file:

// ...
err = cfg.SaveTo("my.ini")
err = cfg.SaveToIndent("my.ini", "\t")

Another more advanced approach is to write to implement any  io.Writer object interface in:

// ...
cfg.WriteTo(writer)
cfg.WriteToIndent(writer, "\t")

By default, the spaces between the equal sign will be used to align the key to beautify the output, the following code can disable the feature:

ini.PrettyFormat = false

 

Partition Operation (Section)

Gets the specified partition:

sec, err := cfg.GetSection("section name")

If you want to get the default partition, you can replace with an empty string partition name:

sec, err := cfg.GetSection("")

Correspondingly, it can also be used  ini.DEFAULT_SECTION to get the default partition:

sec, err := cfg.GetSection(ini.DEFAULT_SECTION)

When you are pretty sure a partition exists, you can use the following simple method:

sec := cfg.Section("section name")

If you accidentally misjudged, to get the partition in fact does not exist, what would happen then? Never mind, it will automatically create and return a corresponding partition object to you.

Create a partition:

err := cfg.NewSection("new section")

Get all partitioned object or name:

secs := cfg.Sections()
names := cfg.SectionStrings()

 

Father and son reading a partition

You can use the partition name  . to represent the parent-child relationship between two or more partitions. If a key does not exist in a child partition, it will go looking for parent partition again until no parent partition

NAME = ini
VERSION = v1
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s

[package]
CLONE_URL = https://%(IMPORT_PATH)s

[package.sub]

 

cfg.Section("package.sub").Key("CLONE_URL").String()    // https://gopkg.in/ini.v1

  

Unable to resolve partition

If you have some special partition, they do not contain common key-value pairs, but there is no fixed format, plain text, you can use  LoadOptions.UnparsableSections the following conditions:

cfg, err := ini.LoadSources(ini.LoadOptions{
    UnparseableSections: []string{"COMMENTS"},
}, `[COMMENTS]
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)

body := cfg.Section("COMMENTS").Body()

/* --- start ---
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
------  end  --- */

  

Operation key (Key)

Get key under a partition:

key, err := cfg.Section("").GetKey("key name")

And partitions as you can get direct bond while ignoring error handling:

key := cfg.Section("").Key("key name")

Determine if a key is present:

yes := cfg.Section("").HasKey("key name")

Get all keys or key names in the partition:

keys := cfg.Section("").Keys()
names := cfg.Section("").KeyStrings()

Get all clones of key-value pairs in the partition:

hash := cfg.Section("").KeysHash()

Ignore case key names

Sometimes the name of the partition and keys in mixed case is very annoying, this time by InsensitiveLoad all partitions and key names in the cast read in lowercase

cfg, ERR: = ini.InsensitiveLoad ( "filename") 
// ... 

// SEC1 and sec2 point to the same partition object 
SEC1, ERR: = cfg.GetSection ( "Section") 
sec2, ERR: = cfg.GetSection ( "sectionTop") 

// key1 and key2 objects point to the same key 
key1, ERR: = sec1.GetKey ( "key") 
key2, ERR: = sec2.GetKey ( "kEY")

MySQL configuration similar to Boolean key

MySQL configuration file boolean key no specific values ​​occur:

[mysqld]
...
skip-host-cache
skip-name-resolve

By default, this is considered to be missing values ​​can not be completed resolve, but they can be processed by advanced loading options:

cfg, err := ini.LoadSources(ini.LoadOptions{
    AllowBooleanKeys: true,
}, "my.cnf")

Their values are forever  true, and when you save the file will only output the key name.

If you want to generate such key through the program, you can use  NewBooleanKey:

key, err := sec.NewBooleanKey("skip-host-cache")

 

With a key name that contains multiple values

Do you also been plagued by the following configuration file?

[remote "origin"]
url = https://github.com/Antergone/test1.git
url = https://github.com/Antergone/test2.git
fetch = +refs/heads/*:refs/remotes/origin/*

 

Yes! By default, the value of only the last occurrence is saved in the  url middle, but I just want to keep the value of all how do ah? It does not matter, with ShadowLoad easily solve your troubles:

 
cfg, err := ini.ShadowLoad(".gitconfig")
// ...

f.Section(`remote "origin"`).Key("url").String() 
// Result: https://github.com/Antergone/test1.git

f.Section(`remote "origin"`).Key("url").ValueWithShadows()
// Result:  []string{
//              "https://github.com/Antergone/test1.git",
//              "https://github.com/Antergone/test2.git",
//          }

 

Read increment keys

If the data source named in the bond  -, is considered key to use a special syntax increment keys. Counter starts, and between the partitions are independent.

[features]
-: Support read/write comments of keys and sections
-: Support auto-increment of key names
-: Support load multiple files to overwrite key values

 

cfg.Section("features").KeyStrings()    // []{"#1", "#2", "#3"}

 

Get all the keys under the superior parent partition

cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]

 

Operation keys (Value)

 

Obtaining a value of type string (string) of:

val := cfg.Section("").Key("key name").String()

 

Get value while custom function verification process:

val := cfg.Section("").Key("key name").Validate(func(in string) string {
    if len(in) == 0 {
        return "default"
    }
    return in
})

 

If you do not need any automatic change function values ​​(such as recursive read), you can get the original value (in this way best performance) directly:

val := cfg.Section("").Key("key name").Value()

  

Determine whether there is an original value:

yes := cfg.Section("").HasValue("test value")

  

Other types of acquired values:

// 布尔值的规则:
// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
v, err = cfg.Section("").Key("BOOL").Bool()
v, err = cfg.Section("").Key("FLOAT64").Float64()
v, err = cfg.Section("").Key("INT").Int()
v, err = cfg.Section("").Key("INT64").Int64()
v, err = cfg.Section("").Key("UINT").Uint()
v, err = cfg.Section("").Key("UINT64").Uint64()
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
v, err = cfg.Section("").Key("TIME").Time() // RFC3339

v = cfg.Section("").Key("BOOL").MustBool()
v = cfg.Section("").Key("FLOAT64").MustFloat64()
v = cfg.Section("").Key("INT").MustInt()
cfg.Section = V ( ""). Key ( "INT64"). MustInt64 ()
cfg.Section = V ( ""). Key ( "UINT"). MustUint () 
V = cfg.Section ( ""). Key ( "UINT64"). MustUint64 () 
V = cfg.Section ( ""). Key ( "the TIME"). MustTimeFormat (time.RFC3339) 
V = cfg.Section ( ""). Key ( "the TIME"). MustTime () // RFC3339 

// method name preceded by a Must allow the receiving of the same type parameter as the default value, 
when the key is not present or when // conversion fails, it will directly return to the default values. 
// However, MustString method must pass a default value. 

cfg.Section = V ( ""). Key ( "String"). MustString ( "default") 
V = cfg.Section ( ""). Key ( "BOOL"). MustBool (to true) 
V = cfg.Section ( "") .Key ( "float64"). MustFloat64 (1.25) 
V = cfg.Section ( ""). Key ( "the INT"). MustInt (10) 
V = cfg.Section ( ""). Key ( "INT64 ").
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339

 

I have a lot of value if the line how to do?

[advance]
ADDRESS = """404 road,
NotFound, State, 5000
Earth"""

 

Ah ha? Small case!

cfg.Section("advance").Key("ADDRESS").String()

/* --- start ---
404 road,
NotFound, State, 5000
Earth
------  end  --- */

 

Chan burst! What if I write content that is less than one line written to the second line of how you want to do?

[advance]
two_lines = how about \
    continuation lines?
lots_of_lines = 1 \
    2 \
    3 \
    4

  

It is simply a piece of cake!

cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4

  

But I sometimes feel special boring two lines together, how can we not automatically connect the two lines do?

cfg, err := ini.LoadSources(ini.LoadOptions{
    IgnoreContinuation: true,
}, "filename")

  

Holy crap to force ah!

Note that the value of both sides of the single quotation marks are automatically excluded:

foo = "some value" // foo: some value
bar = 'some value' // bar: some value

 

Sometimes you get value like Crowdin files downloaded from the Web site as a special format (values ​​using double quotes inside of double quotes are escaped):

create_repo="created repository <a href=\"%s\">%s</a>"

So, how such values ​​are automatically deal with it?

cfg, err := ini.LoadSources(ini.LoadOptions{UnescapeValueDoubleQuotes: true}, "en-US.ini"))
cfg.Section("<name of your section>").Key("create_repo").String()
// You got: created repository <a href="%s">%s</a>

That's all? Haha, of course not.

 

 

The method of operation of the auxiliary keys

Setting the candidate values ​​acquired key:

v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339

 

If any value is not a candidate to get the value of a will return the default value and the default value need not be a candidate in one.

Verification of the acquired value is within the specified range:

vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
Automatic segmentation key to slice (Slice)

When there is an invalid input, instead of a zero value:

// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
vals = cfg.Section("").Key("STRINGS").Strings(",")
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
vals = cfg.Section("").Key("INTS").Ints(",")
vals = cfg.Section("").Key("INT64S").Int64s(",")
vals = cfg.Section("").Key("UINTS").Uints(",")
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
vals = cfg.Section("").Key("TIMES").Times(",")

 

Excluding results from an invalid input sections:

// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
// Input: how, 2.2, are, you -> [2.2]
vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
vals = cfg.Section("").Key("INTS").ValidInts(",")
vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
vals = cfg.Section("").Key("UINTS").ValidUints(",")
vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
vals = cfg.Section("").Key("TIMES").ValidTimes(",")

  

When there is an invalid entry, an error is returned directly to:

// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
// Input: how, 2.2, are, you -> error
vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
vals = cfg.Section("").Key("INTS").StrictInts(",")
vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
vals = cfg.Section("").Key("UINTS").StrictUints(",")
vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
vals = cfg.Section("").Key("TIMES").StrictTimes(",")

  

Recursive read key

In the process of obtaining all keys, special syntax  %(<name>)s can be applied, which  <name> can be the same partition or default key name under the partition. String  %(<name>)s will be replaced by the corresponding key, if the specified key does not exist, it will be replaced with an empty string. You can use up to 99 layers of nested recursive.

NAME = ini

[author]
NAME = Unknwon
GITHUB = https://github.com/%(NAME)s

[package]
FULL_NAME = github.com/go-ini/%(NAME)s

  

cfg.Section("author").Key("GITHUB").String()        // https://github.com/Unknwon
cfg.Section("package").Key("FULL_NAME").String()    // github.com/go-ini/ini

  

 

Python multi-line value

If you are new to the service migrated from Python, you may experience some of the old syntax of the configuration file, do not panic!

cfg, err := ini.LoadSources(ini.LoadOptions{
    AllowPythonMultilineValues: true,
}, []byte(`
[long]
long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
   foo
   bar
   foobar
   barfoo
   -----END RSA PRIVATE KEY-----
`)

/*
-----BEGIN RSA PRIVATE KEY-----
foo
bar
foobar
barfoo 
-----END RSA PRIVATE KEY-----
*/

  

Note operation (it Comment)

 

The content of the following situations will be treated as a comment:

  1. All at  # or  ; lines that start with
  2. All  # or  ; after the contents
  3. Text (that is, after the partition label  [分区名] after the contents)

If you want to use the included  # or  ; value, use  ` or  """ be coated.

In addition, you can also completely ignored within LoadOptions line comments:

cfg, err := ini.LoadSources(ini.LoadOptions{
    IgnoreInlineComment: true,
}, "app.ini")

 

Or before the comment symbol must be requested with a space:

cfg, err := ini.LoadSources(ini.LoadOptions{
    SpaceBeforeInlineComment: true,
}, "app.ini")

  

 

Mapped to the structure

I want to use a more object-oriented way Fun INI do? good idea.

Name = Unknwon
age = 21
Male = true
Born = 1993-01-01T20:17:05Z

[Note]
Content = Hi is a good man!
Cities = HangZhou, Boston

  

Note {struct type 
    the Content String 
    Cities [] String 
} 

type struct {the Person 
    the Name String 
    Age int `INI:" Age "` 
    for a Man BOOL 
    Born time.time 
    Note 
    the Created time.time `INI:" - "` 
} 

FUNC main () { 
    cfg, ERR: ini.Load = ( "path / to / the INI") 
    // ... 
    the p-: = new new (the Person) 
    ERR = cfg.MapTo (the p-) 
    // ... 

    // everything could be so simple . 
    = ini.MapTo ERR (the p-, "path / to / the INI") 
    // ... 

    // ah ha? You only need to map a partition? 
    n-: = new new (Note) 
    . cfg.Section ERR = ( "Note") MapTo (n-) 
    // ... 
}

  

Field structure of how to set the default value of it? Very simple, as long as the assignment of the specified field before mapping it. If the key is not found or the wrong type, change the value it does not happen.

INI play this really cool ah! However, if you can not give me back the original configuration file, what with eggs? 

 

From the structure of the reflective

type Embeded struct {
    Dates  []time.Time `delim:"|" comment:"Time data"`
    Places []string    `ini:"places,omitempty"`
    None   []int       `ini:",omitempty"`
}

type Author struct {
    Name      string `ini:"NAME"`
    Male      bool
    Age       int `comment:"Author's age"`
    GPA       float64
    NeverMind string `ini:"-"`
    *Embeded `comment:"Embeded section"`
}

func main() {
    a := &Author{"Unknwon", true, 21, 2.8, "",
        &Embeded{
            []time.Time{time.Now(), time.Now()},
            []string{"HangZhou", "Boston"},
            []int{},
        }}
    cfg: = ini.Empty () 
    err = ini.ReflectFrom (cfg, a) 
    // ... 
}

  

NAME = Unknwon
Male = true
; Author's age
Age = 21
GPA = 2.8

; Embeded section
[Embeded]
; Time data
Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
places = HangZhou,Boston

  

With ShadowLoad map

If you would like to cooperate ShadowLoad map a partition to the structure, you need to specify  allowshadow the label.

Suppose you have the following profile:

[IP]
value = 192.168.31.201
value = 192.168.31.211
value = 192.168.31.221

  

You should define the structure corresponding to the following manner:

type IP struct {
   Value    []string `ini:"value,omitempty,allowshadow"`
}

If you do not need two before labeling rules can be used  ini:",,allowshadow" be abbreviated.

Mappings / Other information reflected

Any embedded default configuration will be recognized as a different partition, and does not automatically associate a so-called partition Sons:

type Child struct {
    Age string
}

type Parent struct {
    Name string
    Child
}

type Config struct {
    City string
    Parent
}

  

Sample configuration file:

City = Boston

[Parent]
Name = Unknwon

[Child]
Age = 21

  

Good, but I just want to embed structure is also the same partition. Well, your father is Li Gang!

type Child struct {
    Age string
}

type Parent struct {
    Name string
    Child `ini:"Parent"`
}

type Config struct {
    City string
    Parent
}

  

Sample configuration file:

City = Boston

[Parent]
Name = Unknwon
Age = 21

  

Key Name Mapper (Name Mapper)

 

To save you time and simplify the code, this type of support for the library  NameMapper name mapper, mapping between the mapping field names with the structure responsible for the partition name and key name.

Currently it has 2 built-in mapper:

  • AllCapsUnderscore: The mapper to convert the format field names  ALL_CAPS_UNDERSCORE and then to match the partition name and key name.
  • TitleUnderscore: The mapper to convert the format field names  title_underscore and then to match the partition name and key name.

Instructions:

type Info struct {
    PackageName string
}

func main() {
    err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
    // ...

    cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
    // ...
    info := new(Info)
    cfg.NameMapper = ini.AllCapsUnderscore
    err = cfg.MapTo(info)
    // ...
}

 

Use functions  ini.ReflectFromWithMapper may also be applied the same rule.

 

Key mapper (Value Mapper)

Value mapper allows the use of a custom function values ​​automatically expand specific content, such as access to the runtime environment variables:

type Env struct {
    Foo string `ini:"foo"`
}

func main() {
    cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n"))
    cfg.ValueMapper = os.ExpandEnv
    // ...
    env := &Env{}
    err = cfg.Section("env").MapTo(env)
}

 

In the present embodiment, env.Foo it will be acquired by the environment variable runtime  MY_VAR values.

 

 

 

  

 

  

 

 

 
 
 

  

  

  

 

 

  

 

  

  
 

Guess you like

Origin www.cnblogs.com/binHome/p/12173176.html