golang-robfig-config-0.0~git20141208/0000755000175000000620000000000012524011561016373 5ustar jenkinsstaffgolang-robfig-config-0.0~git20141208/all_test.go0000644000175000000620000002716412524011537020546 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config import ( "bufio" "os" "reflect" "strings" "testing" ) const ( tmpFilename = "testdata/__test.go" sourceFilename = "testdata/source.cfg" targetFilename = "testdata/target.cfg" ) func testGet(t *testing.T, c *Config, section string, option string, expected interface{}) { ok := false switch expected.(type) { case string: v, _ := c.String(section, option) if v == expected.(string) { ok = true } case int: v, _ := c.Int(section, option) if v == expected.(int) { ok = true } case bool: v, _ := c.Bool(section, option) if v == expected.(bool) { ok = true } default: t.Fatalf("Bad test case") } if !ok { v, _ := c.String(section, option) t.Errorf("Get failure: expected different value for %s %s (expected: [%#v] got: [%#v])", section, option, expected, v) } } // TestInMemory creates configuration representation and run multiple tests in-memory. func TestInMemory(t *testing.T) { c := NewDefault() // == Test empty structure // should be empty if len(c.Sections()) != 1 { t.Errorf("Sections failure: invalid length") } // test presence of missing section if c.HasSection("no-section") { t.Errorf("HasSection failure: invalid section") } // get options for missing section _, err := c.Options("no-section") if err == nil { t.Errorf("Options failure: invalid section") } // test presence of option for missing section if c.HasOption("no-section", "no-option") { t.Errorf("HasSection failure: invalid/section/option") } // get value from missing section/option _, err = c.String("no-section", "no-option") if err == nil { t.Errorf("String failure: got value for missing section/option") } // get value from missing section/option _, err = c.Int("no-section", "no-option") if err == nil { t.Errorf("Int failure: got value for missing section/option") } // remove missing section if c.RemoveSection("no-section") { t.Errorf("RemoveSection failure: removed missing section") } // remove missing section/option if c.RemoveOption("no-section", "no-option") { t.Errorf("RemoveOption failure: removed missing section/option") } // == Fill up structure // add section if !c.AddSection("section1") { t.Errorf("AddSection failure: false on first insert") } // re-add same section if c.AddSection("section1") { t.Errorf("AddSection failure: true on second insert") } // default section always exists if c.AddSection(DEFAULT_SECTION) { t.Errorf("AddSection failure: true on default section insert") } // add option/value if !c.AddOption("section1", "option1", "value1") { t.Errorf("AddOption failure: false on first insert") } testGet(t, c, "section1", "option1", "value1") // read it back // overwrite value if c.AddOption("section1", "option1", "value2") { t.Errorf("AddOption failure: true on second insert") } testGet(t, c, "section1", "option1", "value2") // read it back again // remove option/value if !c.RemoveOption("section1", "option1") { t.Errorf("RemoveOption failure: false on first remove") } // remove again if c.RemoveOption("section1", "option1") { t.Errorf("RemoveOption failure: true on second remove") } // read it back again _, err = c.String("section1", "option1") if err == nil { t.Errorf("String failure: got value for removed section/option") } // remove existing section if !c.RemoveSection("section1") { t.Errorf("RemoveSection failure: false on first remove") } // remove again if c.RemoveSection("section1") { t.Errorf("RemoveSection failure: true on second remove") } // == Test types // add section if !c.AddSection("section2") { t.Errorf("AddSection failure: false on first insert") } // add number if !c.AddOption("section2", "test-number", "666") { t.Errorf("AddOption failure: false on first insert") } testGet(t, c, "section2", "test-number", 666) // read it back // add 'yes' (bool) if !c.AddOption("section2", "test-yes", "yes") { t.Errorf("AddOption failure: false on first insert") } testGet(t, c, "section2", "test-yes", true) // read it back // add 'false' (bool) if !c.AddOption("section2", "test-false", "false") { t.Errorf("AddOption failure: false on first insert") } testGet(t, c, "section2", "test-false", false) // read it back // == Test cycle c.AddOption(DEFAULT_SECTION, "opt1", "%(opt2)s") c.AddOption(DEFAULT_SECTION, "opt2", "%(opt1)s") _, err = c.String(DEFAULT_SECTION, "opt1") if err == nil { t.Errorf("String failure: no error for cycle") } else if strings.Index(err.Error(), "cycle") < 0 { t.Errorf("String failure: incorrect error for cycle") } } // TestReadFile creates a 'tough' configuration file and test (read) parsing. func TestReadFile(t *testing.T) { file, err := os.Create(tmpFilename) if err != nil { t.Fatal("Test cannot run because cannot write temporary file: " + tmpFilename) } err = os.Setenv("GO_CONFIGFILE_TEST_ENV_VAR", "configvalue12345") if err != nil { t.Fatalf("Test cannot run because cannot set environment variable GO_CONFIGFILE_TEST_ENV_VAR: %#v", err) } buf := bufio.NewWriter(file) buf.WriteString("optionInDefaultSection=true\n") buf.WriteString("[section-1]\n") buf.WriteString("option1=value1 ; This is a comment\n") buf.WriteString("option2 : 2#Not a comment\t#Now this is a comment after a TAB\n") buf.WriteString(" # Let me put another comment\n") buf.WriteString("option3= line1\n line2: \n\tline3=v # Comment multiline with := in value\n") buf.WriteString("; Another comment\n") buf.WriteString("[" + DEFAULT_SECTION + "]\n") buf.WriteString("variable1=small\n") buf.WriteString("variable2=a_part_of_a_%(variable1)s_test\n") buf.WriteString("[secTION-2]\n") buf.WriteString("IS-flag-TRUE=Yes\n") buf.WriteString("[section-1] # comment on section header\n") // continue again [section-1] buf.WriteString("option4=this_is_%(variable2)s.\n") buf.WriteString("envoption1=this_uses_${GO_CONFIGFILE_TEST_ENV_VAR}_env\n") buf.WriteString("optionInDefaultSection=false") buf.Flush() file.Close() c, err := ReadDefault(tmpFilename) if err != nil { t.Fatalf("ReadDefault failure: %s", err) } // check number of sections if len(c.Sections()) != 3 { t.Errorf("Sections failure: wrong number of sections") } // check number of options 6 of [section-1] plus 2 of [default] opts, err := c.Options("section-1") if len(opts) != 8 { t.Errorf("Options failure: wrong number of options: %d", len(opts)) } testGet(t, c, "section-1", "option1", "value1") testGet(t, c, "section-1", "option2", "2#Not a comment") testGet(t, c, "section-1", "option3", "line1\nline2:\nline3=v") testGet(t, c, "section-1", "option4", "this_is_a_part_of_a_small_test.") testGet(t, c, "section-1", "envoption1", "this_uses_configvalue12345_env") testGet(t, c, "section-1", "optionInDefaultSection", false) testGet(t, c, "section-2", "optionInDefaultSection", true) testGet(t, c, "secTION-2", "IS-flag-TRUE", true) // case-sensitive } // TestWriteReadFile tests writing and reading back a configuration file. func TestWriteReadFile(t *testing.T) { cw := NewDefault() // write file; will test only read later on cw.AddSection("First-Section") cw.AddOption("First-Section", "option1", "value option1") cw.AddOption("First-Section", "option2", "2") cw.AddOption("", "host", "www.example.com") cw.AddOption(DEFAULT_SECTION, "protocol", "https://") cw.AddOption(DEFAULT_SECTION, "base-url", "%(protocol)s%(host)s") cw.AddOption("Another-Section", "useHTTPS", "y") cw.AddOption("Another-Section", "url", "%(base-url)s/some/path") cw.WriteFile(tmpFilename, 0644, "Test file for test-case") // read back file and test cr, err := ReadDefault(tmpFilename) if err != nil { t.Fatalf("ReadDefault failure: %s", err) } testGet(t, cr, "First-Section", "option1", "value option1") testGet(t, cr, "First-Section", "option2", 2) testGet(t, cr, "Another-Section", "useHTTPS", true) testGet(t, cr, "Another-Section", "url", "https://www.example.com/some/path") defer os.Remove(tmpFilename) } // TestSectionOptions tests read options in a section without default options. func TestSectionOptions(t *testing.T) { cw := NewDefault() // write file; will test only read later on cw.AddSection("First-Section") cw.AddOption("First-Section", "option1", "value option1") cw.AddOption("First-Section", "option2", "2") cw.AddOption("", "host", "www.example.com") cw.AddOption(DEFAULT_SECTION, "protocol", "https://") cw.AddOption(DEFAULT_SECTION, "base-url", "%(protocol)s%(host)s") cw.AddOption("Another-Section", "useHTTPS", "y") cw.AddOption("Another-Section", "url", "%(base-url)s/some/path") cw.WriteFile(tmpFilename, 0644, "Test file for test-case") // read back file and test cr, err := ReadDefault(tmpFilename) if err != nil { t.Fatalf("ReadDefault failure: %s", err) } options, err := cr.SectionOptions("First-Section") if err != nil { t.Fatalf("SectionOptions failure: %s", err) } if len(options) != 2 { t.Fatalf("SectionOptions reads wrong data: %v", options) } expected := map[string]bool{ "option1": true, "option2": true, } actual := map[string]bool{} for _, v := range options { actual[v] = true } if !reflect.DeepEqual(expected, actual) { t.Fatalf("SectionOptions reads wrong data: %v", options) } options, err = cr.SectionOptions(DEFAULT_SECTION) if err != nil { t.Fatalf("SectionOptions failure: %s", err) } expected = map[string]bool{ "host": true, "protocol": true, "base-url": true, } actual = map[string]bool{} for _, v := range options { actual[v] = true } if !reflect.DeepEqual(expected, actual) { t.Fatalf("SectionOptions reads wrong data: %v", options) } defer os.Remove(tmpFilename) } // TestMerge tests merging 2 configurations. func TestMerge(t *testing.T) { target, error := ReadDefault(targetFilename) if error != nil { t.Fatalf("Unable to read target config file '%s'", targetFilename) } source, error := ReadDefault(sourceFilename) if error != nil { t.Fatalf("Unable to read source config file '%s'", sourceFilename) } target.Merge(source) // Assert whether a regular option was merged from source -> target if result, _ := target.String(DEFAULT_SECTION, "one"); result != "source1" { t.Errorf("Expected 'one' to be '1' but instead it was '%s'", result) } // Assert that a non-existent option in source was not overwritten if result, _ := target.String(DEFAULT_SECTION, "five"); result != "5" { t.Errorf("Expected 'five' to be '5' but instead it was '%s'", result) } // Assert that a folded option was correctly unfolded if result, _ := target.String(DEFAULT_SECTION, "two_+_three"); result != "source2 + source3" { t.Errorf("Expected 'two_+_three' to be 'source2 + source3' but instead it was '%s'", result) } if result, _ := target.String(DEFAULT_SECTION, "four"); result != "4" { t.Errorf("Expected 'four' to be '4' but instead it was '%s'", result) } // Assert that a section option has been merged if result, _ := target.String("X", "x.one"); result != "sourcex1" { t.Errorf("Expected '[X] x.one' to be 'sourcex1' but instead it was '%s'", result) } if result, _ := target.String("X", "x.four"); result != "x4" { t.Errorf("Expected '[X] x.four' to be 'x4' but instead it was '%s'", result) } } golang-robfig-config-0.0~git20141208/config.go0000644000175000000620000000775512524011537020210 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config import ( "regexp" "strings" ) const ( // Default section name. DEFAULT_SECTION = "DEFAULT" // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 200 DEFAULT_COMMENT = "# " ALTERNATIVE_COMMENT = "; " DEFAULT_SEPARATOR = ":" ALTERNATIVE_SEPARATOR = "=" ) var ( // Strings accepted as boolean. boolString = map[string]bool{ "t": true, "true": true, "y": true, "yes": true, "on": true, "1": true, "f": false, "false": false, "n": false, "no": false, "off": false, "0": false, } varRegExp = regexp.MustCompile(`%\(([a-zA-Z0-9_.\-]+)\)s`) // %(variable)s envVarRegExp = regexp.MustCompile(`\${([a-zA-Z0-9_.\-]+)}`) // ${envvar} ) // Config is the representation of configuration settings. type Config struct { comment string separator string // Sections order lastIdSection int // Last section identifier idSection map[string]int // Section : position // The last option identifier used for each section. lastIdOption map[string]int // Section : last identifier // Section -> option : value data map[string]map[string]*tValue } // tValue holds the input position for a value. type tValue struct { position int // Option order v string // value } // New creates an empty configuration representation. // This representation can be filled with AddSection and AddOption and then // saved to a file using WriteFile. // // == Arguments // // comment: has to be `DEFAULT_COMMENT` or `ALTERNATIVE_COMMENT` // separator: has to be `DEFAULT_SEPARATOR` or `ALTERNATIVE_SEPARATOR` // preSpace: indicate if is inserted a space before of the separator // postSpace: indicate if is added a space after of the separator func New(comment, separator string, preSpace, postSpace bool) *Config { if comment != DEFAULT_COMMENT && comment != ALTERNATIVE_COMMENT { panic("comment character not valid") } if separator != DEFAULT_SEPARATOR && separator != ALTERNATIVE_SEPARATOR { panic("separator character not valid") } // == Get spaces around separator if preSpace { separator = " " + separator } if postSpace { separator += " " } //== c := new(Config) c.comment = comment c.separator = separator c.idSection = make(map[string]int) c.lastIdOption = make(map[string]int) c.data = make(map[string]map[string]*tValue) c.AddSection(DEFAULT_SECTION) // Default section always exists. return c } // NewDefault creates a configuration representation with values by default. func NewDefault() *Config { return New(DEFAULT_COMMENT, DEFAULT_SEPARATOR, false, true) } // Merge merges the given configuration "source" with this one ("target"). // // Merging means that any option (under any section) from source that is not in // target will be copied into target. When the target already has an option with // the same name and section then it is overwritten (i.o.w. the source wins). func (target *Config) Merge(source *Config) { if source == nil || source.data == nil || len(source.data) == 0 { return } for section, option := range source.data { for optionName, optionValue := range option { target.AddOption(section, optionName, optionValue.v) } } } // == Utility func stripComments(l string) string { // Comments are preceded by space or TAB for _, c := range []string{" ;", "\t;", " #", "\t#"} { if i := strings.Index(l, c); i != -1 { l = l[0:i] } } return l } golang-robfig-config-0.0~git20141208/Doc/0000755000175000000620000000000012524011537017103 5ustar jenkinsstaffgolang-robfig-config-0.0~git20141208/Doc/AUTHORS.md0000644000175000000620000000076712524011537020564 0ustar jenkinsstaff###### Notice *This is the official list of **config** authors for copyright purposes.* *This file is distinct from the CONTRIBUTORS file. See the latter for an explanation.* *Names should be added to this file as: `Organization`; `[Name](web address)` or `Name ` for individuals* *Please keep the list sorted.* * * * [Jonas mg](https://github.com/kless) [Miguel Branco](https://github.com/msbranco) [Rob Figueiredo](https://github.com/robfig) [Tom Bruggeman](https://github.com/tmbrggmn) golang-robfig-config-0.0~git20141208/Doc/CONTRIBUTORS.md0000644000175000000620000000126412524011537021365 0ustar jenkinsstaff###### Notice *This is the official list of people who can contribute (and typically have contributed) code to the **config** repository.* *The AUTHORS file lists the copyright holders; this file lists people. For example, the employees of an organization are listed here but not in AUTHORS, because the organization holds the copyright.* *Names should be added to this file as: `[Name](web address)` or `Name `* *Please keep the list sorted.* * * * ### Initial author [Miguel Branco](https://github.com/msbranco) ### Maintainer [Rob Figueiredo](https://github.com/robfig) ### Other authors [Jonas mg](https://github.com/kless) [Tom Bruggeman](https://github.com/tmbrggmn) golang-robfig-config-0.0~git20141208/Doc/LICENSE_Apache.txt0000644000175000000620000002613612524011537022177 0ustar jenkinsstaff Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. golang-robfig-config-0.0~git20141208/Doc/NEWS.md0000644000175000000620000000200312524011537020174 0ustar jenkinsstaff###### Notice *This file documents the changes in **config** versions that are listed below.* *Items should be added to this file as:* ### YYYY-MM-DD Release + Additional changes. + More changes. * * * ### 2011-??-?? v0.9.6 + Changed to line comments. ### 2010-09-15 v0.9.5 + Sections, options and values are all case-sensitive. + Changed API: Type *File* -> *Config* *NewFile()* -> *NewDefault* *ReadFile()* -> *ReadDefault* + Added functions, *New()*, *Read()*, which allow to choose the character of comment and separator, and the spaces around separator. + Better error handling. + Both sections and options are showed by its input order. ### 2010-08-22 v0.9 + The files has been splitted, formatted via *gomft*. + Methods use *self* to refer to its own type. + *Get* has been removed from the functions names. + Fixed some errors. All tests are passed. + At write the header in configuration file, it is added the comment character after of each new line. + Better documentation. golang-robfig-config-0.0~git20141208/error.go0000644000175000000620000000147712524011537020067 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config type SectionError string func (e SectionError) Error() string { return "section not found: " + string(e) } type OptionError string func (e OptionError) Error() string { return "option not found: " + string(e) } golang-robfig-config-0.0~git20141208/option.go0000644000175000000620000000645312524011537020245 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config import "errors" // AddOption adds a new option and value to the configuration. // // If the section is nil then uses the section by default; if it does not exist, // it is created in advance. // // It returns true if the option and value were inserted, and false if the value // was overwritten. func (c *Config) AddOption(section string, option string, value string) bool { c.AddSection(section) // Make sure section exists if section == "" { section = DEFAULT_SECTION } _, ok := c.data[section][option] c.data[section][option] = &tValue{c.lastIdOption[section], value} c.lastIdOption[section]++ return !ok } // RemoveOption removes a option and value from the configuration. // It returns true if the option and value were removed, and false otherwise, // including if the section did not exist. func (c *Config) RemoveOption(section string, option string) bool { if _, ok := c.data[section]; !ok { return false } _, ok := c.data[section][option] delete(c.data[section], option) return ok } // HasOption checks if the configuration has the given option in the section. // It returns false if either the option or section do not exist. func (c *Config) HasOption(section string, option string) bool { if _, ok := c.data[section]; !ok { return false } _, okd := c.data[DEFAULT_SECTION][option] _, oknd := c.data[section][option] return okd || oknd } // Options returns the list of options available in the given section. // It returns an error if the section does not exist and an empty list if the // section is empty. Options within the default section are also included. func (c *Config) Options(section string) (options []string, err error) { if _, ok := c.data[section]; !ok { return nil, errors.New(SectionError(section).Error()) } // Keep a map of option names we've seen to deduplicate. optionMap := make(map[string]struct{}, len(c.data[DEFAULT_SECTION])+len(c.data[section])) for s, _ := range c.data[DEFAULT_SECTION] { optionMap[s] = struct{}{} } for s, _ := range c.data[section] { optionMap[s] = struct{}{} } // Get the keys. i := 0 options = make([]string, len(optionMap)) for k, _ := range optionMap { options[i] = k i++ } return options, nil } // SectionOptions returns only the list of options available in the given section. // Unlike Options, SectionOptions doesn't return options in default section. // It returns an error if the section doesn't exist. func (c *Config) SectionOptions(section string) (options []string, err error) { if _, ok := c.data[section]; !ok { return nil, errors.New(SectionError(section).Error()) } options = make([]string, len(c.data[section])) i := 0 for s, _ := range c.data[section] { options[i] = s i++ } return options, nil } golang-robfig-config-0.0~git20141208/read.go0000644000175000000620000000552212524011537017644 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config import ( "bufio" "errors" "os" "strings" "unicode" ) // _read is the base to read a file and get the configuration representation. // That representation can be queried with GetString, etc. func _read(fname string, c *Config) (*Config, error) { file, err := os.Open(fname) if err != nil { return nil, err } if err = c.read(bufio.NewReader(file)); err != nil { return nil, err } if err = file.Close(); err != nil { return nil, err } return c, nil } // Read reads a configuration file and returns its representation. // All arguments, except `fname`, are related to `New()` func Read(fname string, comment, separator string, preSpace, postSpace bool) (*Config, error) { return _read(fname, New(comment, separator, preSpace, postSpace)) } // ReadDefault reads a configuration file and returns its representation. // It uses values by default. func ReadDefault(fname string) (*Config, error) { return _read(fname, NewDefault()) } // * * * func (c *Config) read(buf *bufio.Reader) (err error) { var section, option string var scanner = bufio.NewScanner(buf) for scanner.Scan() { l := strings.TrimRightFunc(stripComments(scanner.Text()), unicode.IsSpace) // Switch written for readability (not performance) switch { // Empty line and comments case len(l) == 0, l[0] == '#', l[0] == ';': continue // New section. The [ must be at the start of the line case l[0] == '[' && l[len(l)-1] == ']': option = "" // reset multi-line value section = strings.TrimSpace(l[1 : len(l)-1]) c.AddSection(section) // Continuation of multi-line value // starts with whitespace, we're in a section and working on an option case section != "" && option != "" && (l[0] == ' ' || l[0] == '\t'): prev, _ := c.RawString(section, option) value := strings.TrimSpace(l) c.AddOption(section, option, prev+"\n"+value) // Other alternatives default: i := strings.IndexAny(l, "=:") switch { // Option and value case i > 0 && l[0] != ' ' && l[0] != '\t': // found an =: and it's not a multiline continuation option = strings.TrimSpace(l[0:i]) value := strings.TrimSpace(l[i+1:]) c.AddOption(section, option, value) default: return errors.New("could not parse line: " + l) } } } return scanner.Err() } golang-robfig-config-0.0~git20141208/README.md0000644000175000000620000000472612524011537017666 0ustar jenkinsstaffconfig ====== This package implements a basic configuration file parser language which provides a structure similar to what you would find on Microsoft Windows INI files. The configuration file consists of sections, led by a "*[section]*" header and followed by "*name: value*" entries; "*name=value*" is also accepted. Note that leading whitespace is removed from values. The optional values can contain format strings which refer to other values in the same section, or values in a special *DEFAULT* section. Additional defaults can be provided on initialization and retrieval. Comments are indicated by ";" or "#"; a comment may begin anywhere on a line, including on the same line after parameters or section declarations. For example: [My Section] foodir: %(dir)s/whatever dir=foo would resolve the "*%(dir)s*" to the value of "*dir*" (*foo* in this case). All reference expansions are done on demand. The functionality and workflow is loosely based on the *configparser* package of the Python Standard Library. ## Installation go get github.com/robfig/config ## Operating instructions Given a sample configuration file: [DEFAULT] host: www.example.com protocol: http:// base-url: %(protocol)s%(host)s [service-1] url: %(base-url)s/some/path delegation: on maxclients: 200 # do not set this higher comments: This is a multi-line entry # And this is a comment To read this configuration file, do: c, _ := config.ReadDefault("config.cfg") c.String("service-1", "url") // result is string "http://www.example.com/some/path" c.Int("service-1", "maxclients") // result is int 200 c.Bool("service-1", "delegation") // result is bool true c.String("service-1", "comments") // result is string "This is a multi-line\nentry" Note the support for unfolding variables (such as *%(base-url)s*), which are read from the special (reserved) section name *[DEFAULT]*. A new configuration file can also be created with: c := config.NewDefault() c.AddSection("Section") c.AddOption("Section", "option", "value") c.WriteFile("config.cfg", 0644, "A header for this file") This results in the file: # A header for this file [Section] option: value Note that sections, options and values are all case-sensitive. ## License The source files are distributed under the [Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/), unless otherwise noted. Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license. golang-robfig-config-0.0~git20141208/section.go0000644000175000000620000000433412524011537020375 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config // AddSection adds a new section to the configuration. // // If the section is nil then uses the section by default which it's already // created. // // It returns true if the new section was inserted, and false if the section // already existed. func (c *Config) AddSection(section string) bool { // DEFAULT_SECTION if section == "" { return false } if _, ok := c.data[section]; ok { return false } c.data[section] = make(map[string]*tValue) // Section order c.idSection[section] = c.lastIdSection c.lastIdSection++ return true } // RemoveSection removes a section from the configuration. // It returns true if the section was removed, and false if section did not exist. func (c *Config) RemoveSection(section string) bool { _, ok := c.data[section] // Default section cannot be removed. if !ok || section == DEFAULT_SECTION { return false } for o, _ := range c.data[section] { delete(c.data[section], o) // *value } delete(c.data, section) delete(c.lastIdOption, section) delete(c.idSection, section) return true } // HasSection checks if the configuration has the given section. // (The default section always exists.) func (c *Config) HasSection(section string) bool { _, ok := c.data[section] return ok } // Sections returns the list of sections in the configuration. // (The default section always exists). func (c *Config) Sections() (sections []string) { sections = make([]string, len(c.idSection)) pos := 0 // Position in sections for i := 0; i < c.lastIdSection; i++ { for section, id := range c.idSection { if id == i { sections[pos] = section pos++ } } } return sections } golang-robfig-config-0.0~git20141208/testdata/0000755000175000000620000000000012524011537020207 5ustar jenkinsstaffgolang-robfig-config-0.0~git20141208/testdata/source.cfg0000644000175000000620000000026512524011537022173 0ustar jenkinsstaffone=source1 two=source2 three=source3 four=4 two_+_four=%(two)s + %(four)s [X] x.one=sourcex1 x.two=sourcex2 x.three=sourcex3 [Y] y.one=sourcey1 y.two=sourcey2 y.three=sourcey3 golang-robfig-config-0.0~git20141208/testdata/target.cfg0000644000175000000620000000022512524011537022155 0ustar jenkinsstaffone=1 two=2 three=3 five=5 two_+_three=%(two)s + %(three)s [X] x.one=x1 x.two=x2 x.three=x3 x.four=x4 [Y] y.one=y1 y.two=y2 y.three=y3 y.four=y4 golang-robfig-config-0.0~git20141208/type.go0000644000175000000620000001117312524011537017711 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config import ( "errors" "fmt" "os" "regexp" "strconv" "strings" ) // Substitutes values, calculated by callback, on matching regex func (c *Config) computeVar(beforeValue *string, regx *regexp.Regexp, headsz, tailsz int, withVar func(*string) string) (*string, error) { var i int computedVal := beforeValue for i = 0; i < _DEPTH_VALUES; i++ { // keep a sane depth vr := regx.FindStringSubmatchIndex(*computedVal) if len(vr) == 0 { break } varname := (*computedVal)[vr[headsz]:vr[headsz+1]] varVal := withVar(&varname) if varVal == "" { return &varVal, errors.New(fmt.Sprintf("Option not found: %s", varname)) } // substitute by new value and take off leading '%(' and trailing ')s' // %(foo)s => headsz=2, tailsz=2 // ${foo} => headsz=2, tailsz=1 newVal := (*computedVal)[0:vr[headsz]-headsz] + varVal + (*computedVal)[vr[headsz+1]+tailsz:] computedVal = &newVal } if i == _DEPTH_VALUES { retVal := "" return &retVal, fmt.Errorf("Possible cycle while unfolding variables: max depth of %d reached", _DEPTH_VALUES) } return computedVal, nil } // Bool has the same behaviour as String but converts the response to bool. // See "boolString" for string values converted to bool. func (c *Config) Bool(section string, option string) (value bool, err error) { sv, err := c.String(section, option) if err != nil { return false, err } value, ok := boolString[strings.ToLower(sv)] if !ok { return false, errors.New("could not parse bool value: " + sv) } return value, nil } // Float has the same behaviour as String but converts the response to float. func (c *Config) Float(section string, option string) (value float64, err error) { sv, err := c.String(section, option) if err == nil { value, err = strconv.ParseFloat(sv, 64) } return value, err } // Int has the same behaviour as String but converts the response to int. func (c *Config) Int(section string, option string) (value int, err error) { sv, err := c.String(section, option) if err == nil { value, err = strconv.Atoi(sv) } return value, err } // RawString gets the (raw) string value for the given option in the section. // The raw string value is not subjected to unfolding, which was illustrated in // the beginning of this documentation. // // It returns an error if either the section or the option do not exist. func (c *Config) RawString(section string, option string) (value string, err error) { if _, ok := c.data[section]; ok { if tValue, ok := c.data[section][option]; ok { return tValue.v, nil } } return c.RawStringDefault(option) } // RawStringDefault gets the (raw) string value for the given option from the // DEFAULT section. // // It returns an error if the option does not exist in the DEFAULT section. func (c *Config) RawStringDefault(option string) (value string, err error) { if tValue, ok := c.data[DEFAULT_SECTION][option]; ok { return tValue.v, nil } return "", OptionError(option) } // String gets the string value for the given option in the section. // If the value needs to be unfolded (see e.g. %(host)s example in the beginning // of this documentation), then String does this unfolding automatically, up to // _DEPTH_VALUES number of iterations. // // It returns an error if either the section or the option do not exist, or the // unfolding cycled. func (c *Config) String(section string, option string) (value string, err error) { value, err = c.RawString(section, option) if err != nil { return "", err } // % variables computedVal, err := c.computeVar(&value, varRegExp, 2, 2, func(varName *string) string { lowerVar := *varName // search variable in default section as well as current section varVal, _ := c.data[DEFAULT_SECTION][lowerVar] if _, ok := c.data[section][lowerVar]; ok { varVal = c.data[section][lowerVar] } return varVal.v }) value = *computedVal if err != nil { return value, err } // $ environment variables computedVal, err = c.computeVar(&value, envVarRegExp, 2, 1, func(varName *string) string { return os.Getenv(*varName) }) value = *computedVal return value, err } golang-robfig-config-0.0~git20141208/write.go0000644000175000000620000000445712524011537020071 0ustar jenkinsstaff// Copyright 2009 The "config" Authors // // 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. package config import ( "bufio" "fmt" "os" "strings" ) // WriteFile saves the configuration representation to a file. // The desired file permissions must be passed as in os.Open. The header is a // string that is saved as a comment in the first line of the file. func (c *Config) WriteFile(fname string, perm os.FileMode, header string) error { file, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } buf := bufio.NewWriter(file) if err = c.write(buf, header); err != nil { return err } buf.Flush() return file.Close() } func (c *Config) write(buf *bufio.Writer, header string) (err error) { if header != "" { // Add comment character after of each new line. if i := strings.Index(header, "\n"); i != -1 { header = strings.Replace(header, "\n", "\n"+c.comment, -1) } if _, err = buf.WriteString(c.comment + header + "\n"); err != nil { return err } } for _, orderedSection := range c.Sections() { for section, sectionMap := range c.data { if section == orderedSection { // Skip default section if empty. if section == DEFAULT_SECTION && len(sectionMap) == 0 { continue } if _, err = buf.WriteString("\n[" + section + "]\n"); err != nil { return err } // Follow the input order in options. for i := 0; i < c.lastIdOption[section]; i++ { for option, tValue := range sectionMap { if tValue.position == i { if _, err = buf.WriteString(fmt.Sprint( option, c.separator, tValue.v, "\n")); err != nil { return err } c.RemoveOption(section, option) break } } } } } } if _, err = buf.WriteString("\n"); err != nil { return err } return nil }