pax_global_header00006660000000000000000000000064125223164510014513gustar00rootroot0000000000000052 comment=da64c6b17b73aecb144ef4b7f42917901f76627e golang-github-rakyll-globalconf-0.0~git20140819/000077500000000000000000000000001252231645100212345ustar00rootroot00000000000000golang-github-rakyll-globalconf-0.0~git20140819/.travis.yml000066400000000000000000000000251252231645100233420ustar00rootroot00000000000000language: go go: 1.2 golang-github-rakyll-globalconf-0.0~git20140819/README.md000066400000000000000000000104721252231645100225170ustar00rootroot00000000000000# globalconf [![Build Status](https://travis-ci.org/rakyll/globalconf.png?branch=master)](https://travis-ci.org/rakyll/globalconf) Effortlessly persist/retrieve flags of your Golang programs. If you need global configuration instead of requiring user always to set command line flags, you are looking at the right package. `globalconf` allows your users to not only provide flags, but config files and environment variables as well. ## Usage ~~~ go import "github.com/rakyll/globalconf" ~~~ ### Loading a config file By default, globalconf provides you a config file under `~/.config//config.ini`. ~~~ go globalconf.New("appname") // loads from ~/.config//config.ini ~~~ If you don't prefer the default location you can load from a specified path as well. ~~~ go globalconf.NewWithOptions(&globalconf.Options{ Filename: "/path/to/config/file", }) ~~~ You may like to override configuration with env variables. See "Environment variables" header to see how to it works. ~~~ go globalconf.NewWithOptions(&globalconf.Options{ Filename: "/path/to/config/file", EnvPrefix: "APPCONF_", }) ~~~ ### Parsing flag values `globalconf` populates flags with data in the config file if they are not already set. ~~~ go var ( flagName = flag.String("name", "", "Name of the person.") flagAddress = flag.String("addr", "", "Address of the person.") ) ~~~ Assume the configuration file to be loaded contains the following lines. name = Burcu addr = Brandschenkestrasse 110, 8002 And your program is being started, `$ myapp -name=Jane` ~~~ go conf, err := globalconf.New("myapp") conf.ParseAll() ~~~ `*flagName` is going to be equal to `Jane`, whereas `*flagAddress` is `Brandschenkestrasse 110, 8002`, what is provided in the configuration file. ### Custom flag sets Custom flagsets are supported, but required registration before parse is done. The default flagset `flag.CommandLine` is automatically registered. ~~~ go globalconf.Register("termopts", termOptsFlagSet) conf.ParseAll() // parses command line and all registered flag sets ~~~ Custom flagset values should be provided in their own segment. Getting back to the sample ini config file, termopts values will have their own segment. name = Burcu addr = Brandschenkestrasse 110, 8002 [termopts] color = true background = ff0000 ### Environment variables If an EnvPrefix is provided, environment variables will take precedence over values in the configuration file. Set the `EnvPrefix` option when calling `globalconf.NewWithOptions`. An `EnvPrefix` will only be used if it is a non-empty string. Command line flags will override the environment variables. ~~~ go opts := globalconf.Options{ EnvPrefix: "MYAPP_", Filename: "/path/to/config", } conf, err := globalconf.NewWithOptions(&opts) conf.ParseAll() ~~~ With environment variables: APPCONF_NAME = Burcu and configuration: name = Jane addr = Brandschenkestrasse 110, 8002 `name` will be set to "burcu" and `addr` will be set to "Brandschenkestrasse 110, 8002". ### Modifying stored flags Modifications are persisted as long as you set a new flag and your GlobalConf object was configured with a filename. ~~~ go f := &flag.Flag{Name: "name", Value: val} conf.Set("", f) // if you are modifying a command line flag f := &flag.Flag{Name: "color", Value: val} conf.Set("termopts", color) // if you are modifying a custom flag set flag ~~~ ### Deleting stored flags Like Set, Deletions are persisted as long as you delete a flag's value and your GlobalConf object was configured with a filename. ~~~ go conf.Delete("", "name") // removes command line flag "name"s value from config conf.Delete("termopts", "color") // removes "color"s value from the custom flag set ~~~ ## License Copyright 2014 Google Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ![Analytics](https://ga-beacon.appspot.com/UA-46881978-1/globalconf?pixel) golang-github-rakyll-globalconf-0.0~git20140819/globalconf.go000066400000000000000000000103071252231645100236720ustar00rootroot00000000000000package globalconf import ( "flag" "io/ioutil" "os" "os/user" "path" "strings" ini "github.com/rakyll/goini" ) const ( defaultConfigFileName = "config.ini" ) var flags map[string]*flag.FlagSet = make(map[string]*flag.FlagSet) // Represents a GlobalConf context. type GlobalConf struct { Filename string EnvPrefix string dict *ini.Dict } type Options struct { Filename string EnvPrefix string } // NewWithOptions creates a GlobalConf from the provided // Options. The caller is responsible for creating any // referenced config files. func NewWithOptions(opts *Options) (g *GlobalConf, err error) { Register("", flag.CommandLine) var dict ini.Dict if opts.Filename != "" { dict, err = ini.Load(opts.Filename) if err != nil { return nil, err } } else { dict = make(ini.Dict, 0) } return &GlobalConf{ Filename: opts.Filename, EnvPrefix: opts.EnvPrefix, dict: &dict, }, nil } // Opens/creates a config file for the specified appName. // The path to config file is ~/.config/appName/config.ini. func New(appName string) (g *GlobalConf, err error) { var u *user.User if u, err = user.Current(); u == nil { return } // Create config file's directory. dirPath := path.Join(u.HomeDir, ".config", appName) if err = os.MkdirAll(dirPath, 0755); err != nil { return } // Touch a config file if it doesn't exit. filePath := path.Join(dirPath, defaultConfigFileName) if _, err = os.Stat(filePath); err != nil { if !os.IsNotExist(err) { return } // create file if err = ioutil.WriteFile(filePath, []byte{}, 0644); err != nil { return } } opts := Options{Filename: filePath} return NewWithOptions(&opts) } // Sets a flag's value and persists the changes to the disk. func (g *GlobalConf) Set(flagSetName string, f *flag.Flag) error { g.dict.SetString(flagSetName, f.Name, f.Value.String()) if g.Filename != "" { return ini.Write(g.Filename, g.dict) } return nil } // Deletes a flag from config file and persists the changes // to the disk. func (g *GlobalConf) Delete(flagSetName, flagName string) error { g.dict.Delete(flagSetName, flagName) if g.Filename != "" { return ini.Write(g.Filename, g.dict) } return nil } // Parses the config file for the provided flag set. // If the flags are already set, values are overwritten // by the values in the config file. Defaults are not set // if the flag is not in the file. func (g *GlobalConf) ParseSet(flagSetName string, set *flag.FlagSet) { set.VisitAll(func(f *flag.Flag) { val := getEnv(g.EnvPrefix, flagSetName, f.Name) if val != "" { set.Set(f.Name, val) return } val, found := g.dict.GetString(flagSetName, f.Name) if found { set.Set(f.Name, val) } }) } // Parses all the registered flag sets, including the command // line set and sets values from the config file if they are // not already set. func (g *GlobalConf) Parse() { for name, set := range flags { alreadySet := make(map[string]bool) set.Visit(func(f *flag.Flag) { alreadySet[f.Name] = true }) set.VisitAll(func(f *flag.Flag) { // if not already set, set it from dict if exists if alreadySet[f.Name] { return } val := getEnv(g.EnvPrefix, name, f.Name) if val != "" { set.Set(f.Name, val) return } val, found := g.dict.GetString(name, f.Name) if found { set.Set(f.Name, val) } }) } } // Parses command line flags and then, all of the registered // flag sets with the values provided in the config file. func (g *GlobalConf) ParseAll() { if !flag.Parsed() { flag.Parse() } g.Parse() } // Looks up variable in environment func getEnv(envPrefix, flagSetName, flagName string) string { // If we haven't set an EnvPrefix, don't lookup vals in the ENV if envPrefix == "" { return "" } // Append a _ to flagSetName if it exists. if flagSetName != "" { flagSetName += "_" } flagName = strings.Replace(flagName, ".", "_", -1) flagName = strings.Replace(flagName, "-", "_", -1) envKey := strings.ToUpper(envPrefix + flagSetName + flagName) return os.Getenv(envKey) } // Registers a flag set to be parsed. Register all flag sets // before calling this function. flag.CommandLine is automatically // registered. func Register(flagSetName string, set *flag.FlagSet) { flags[flagSetName] = set } golang-github-rakyll-globalconf-0.0~git20140819/globalconf_test.go000066400000000000000000000141471252231645100247370ustar00rootroot00000000000000package globalconf import ( "flag" "io/ioutil" "os" "testing" ) const envTestPrefix = "CONFTEST_" func TestNewWithOptionsNoFilename(t *testing.T) { opts := Options{EnvPrefix: envTestPrefix} os.Setenv(envTestPrefix+"D", "EnvD") flagD := flag.String("d", "default", "") flagE := flag.Bool("e", true, "") conf, err := NewWithOptions(&opts) if err != nil { t.Fatal(err) } conf.ParseAll() if *flagD != "EnvD" { t.Errorf("flagD found %v, expected 'EnvD'", *flagD) } if !*flagE { t.Errorf("flagE found %v, expected true", *flagE) } } func TestParse_Global(t *testing.T) { resetForTesting("") os.Setenv(envTestPrefix+"D", "EnvD") os.Setenv(envTestPrefix+"E", "true") os.Setenv(envTestPrefix+"F", "5.5") flagA := flag.Bool("a", false, "") flagB := flag.Float64("b", 0.0, "") flagC := flag.String("c", "", "") flagD := flag.String("d", "", "") flagE := flag.Bool("e", false, "") flagF := flag.Float64("f", 0.0, "") parse(t, "./testdata/global.ini", envTestPrefix) if !*flagA { t.Errorf("flagA found %v, expected true", *flagA) } if *flagB != 5.6 { t.Errorf("flagB found %v, expected 5.6", *flagB) } if *flagC != "Hello world" { t.Errorf("flagC found %v, expected 'Hello world'", *flagC) } if *flagD != "EnvD" { t.Errorf("flagD found %v, expected 'EnvD'", *flagD) } if !*flagE { t.Errorf("flagE found %v, expected true", *flagE) } if *flagF != 5.5 { t.Errorf("flagF found %v, expected 5.5", *flagF) } } func TestParse_DashConversion(t *testing.T) { resetForTesting("") flagFooBar := flag.String("foo-bar", "", "") os.Setenv("PREFIX_FOO_BAR", "baz") opts := Options{EnvPrefix: "PREFIX_"} conf, err := NewWithOptions(&opts) if err != nil { t.Fatal(err) } conf.ParseAll() if *flagFooBar != "baz" { t.Errorf("flagFooBar found %v, expected 5.5", *flagFooBar) } } func TestParse_GlobalWithDottedFlagname(t *testing.T) { resetForTesting("") os.Setenv(envTestPrefix+"SOME_VALUE", "some-value") flagSomeValue := flag.String("some.value", "", "") parse(t, "./testdata/global.ini", envTestPrefix) if *flagSomeValue != "some-value" { t.Errorf("flagSomeValue found %v, some-value expected", *flagSomeValue) } } func TestParse_GlobalOverwrite(t *testing.T) { resetForTesting("-b=7.6") flagB := flag.Float64("b", 0.0, "") parse(t, "./testdata/global.ini", "") if *flagB != 7.6 { t.Errorf("flagB found %v, expected 7.6", *flagB) } } func TestParse_Custom(t *testing.T) { resetForTesting("") os.Setenv(envTestPrefix+"CUSTOM_E", "Hello Env") flagB := flag.Float64("b", 5.0, "") name := "custom" custom := flag.NewFlagSet(name, flag.ExitOnError) flagD := custom.String("d", "dd", "") flagE := custom.String("e", "ee", "") Register(name, custom) parse(t, "./testdata/custom.ini", envTestPrefix) if *flagB != 5.0 { t.Errorf("flagB found %v, expected 5.0", *flagB) } if *flagD != "Hello d" { t.Errorf("flagD found %v, expected 'Hello d'", *flagD) } if *flagE != "Hello Env" { t.Errorf("flagE found %v, expected 'Hello Env'", *flagE) } } func TestParse_CustomOverwrite(t *testing.T) { resetForTesting("-b=6") flagB := flag.Float64("b", 5.0, "") name := "custom" custom := flag.NewFlagSet(name, flag.ExitOnError) flagD := custom.String("d", "dd", "") Register(name, custom) parse(t, "./testdata/custom.ini", "") if *flagB != 6.0 { t.Errorf("flagB found %v, expected 6.0", *flagB) } if *flagD != "Hello d" { t.Errorf("flagD found %v, expected 'Hello d'", *flagD) } } func TestParse_GlobalAndCustom(t *testing.T) { resetForTesting("") flagA := flag.Bool("a", false, "") flagB := flag.Float64("b", 0.0, "") flagC := flag.String("c", "", "") name := "custom" custom := flag.NewFlagSet(name, flag.ExitOnError) flagD := custom.String("d", "", "") Register(name, custom) parse(t, "./testdata/globalandcustom.ini", "") if !*flagA { t.Errorf("flagA found %v, expected true", *flagA) } if *flagB != 5.6 { t.Errorf("flagB found %v, expected 5.6", *flagB) } if *flagC != "Hello world" { t.Errorf("flagC found %v, expected 'Hello world'", *flagC) } if *flagD != "Hello d" { t.Errorf("flagD found %v, expected 'Hello d'", *flagD) } } func TestParse_GlobalAndCustomOverwrite(t *testing.T) { resetForTesting("-a=true", "-b=5", "-c=Hello") flagA := flag.Bool("a", false, "") flagB := flag.Float64("b", 0.0, "") flagC := flag.String("c", "", "") name := "custom" custom := flag.NewFlagSet(name, flag.ExitOnError) flagD := custom.String("d", "", "") Register(name, custom) parse(t, "./testdata/globalandcustom.ini", "") if !*flagA { t.Errorf("flagA found %v, expected true", *flagA) } if *flagB != 5.0 { t.Errorf("flagB found %v, expected 5.0", *flagB) } if *flagC != "Hello" { t.Errorf("flagC found %v, expected 'Hello'", *flagC) } if *flagD != "Hello d" { t.Errorf("flagD found %v, expected 'Hello d'", *flagD) } } func TestSet(t *testing.T) { resetForTesting() file, _ := ioutil.TempFile("", "") conf := parse(t, file.Name(), "") conf.Set("", &flag.Flag{Name: "a", Value: newFlagValue("test")}) flagA := flag.String("a", "", "") parse(t, file.Name(), "") if *flagA != "test" { t.Errorf("flagA found %v, expected 'test'", *flagA) } } func TestDelete(t *testing.T) { resetForTesting() file, _ := ioutil.TempFile("", "") conf := parse(t, file.Name(), "") conf.Set("", &flag.Flag{Name: "a", Value: newFlagValue("test")}) conf.Delete("", "a") flagA := flag.String("a", "", "") parse(t, file.Name(), "") if *flagA != "" { t.Errorf("flagNewA found %v, expected ''", *flagA) } } func parse(t *testing.T, filename, envPrefix string) *GlobalConf { opts := Options{ Filename: filename, EnvPrefix: envPrefix, } conf, err := NewWithOptions(&opts) if err != nil { t.Error(err) } conf.ParseAll() return conf } // Resets os.Args and the default flag set. func resetForTesting(args ...string) { os.Clearenv() os.Args = append([]string{"cmd"}, args...) flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) } type flagValue struct { str string } func (f *flagValue) String() string { return f.str } func (f *flagValue) Set(value string) error { f.str = value return nil } func newFlagValue(val string) *flagValue { return &flagValue{str: val} } golang-github-rakyll-globalconf-0.0~git20140819/testdata/000077500000000000000000000000001252231645100230455ustar00rootroot00000000000000golang-github-rakyll-globalconf-0.0~git20140819/testdata/custom.ini000066400000000000000000000000251252231645100250550ustar00rootroot00000000000000[custom] d = Hello d golang-github-rakyll-globalconf-0.0~git20140819/testdata/global.ini000066400000000000000000000000411252231645100250010ustar00rootroot00000000000000a = true b = 5.6 c = Hello world golang-github-rakyll-globalconf-0.0~git20140819/testdata/globalandcustom.ini000066400000000000000000000000671252231645100267270ustar00rootroot00000000000000a = true b = 5.6 c = Hello world [custom] d = Hello d