├── .gitignore ├── LICENSE ├── README.md ├── config.go ├── configuration.go ├── configuration_test.go ├── hocon ├── array.go ├── element.go ├── literal.go ├── object.go ├── parser.go ├── root.go ├── stack.go ├── substitution.go ├── token.go ├── tokenizer.go └── value.go ├── pigeon.conf └── tests ├── configs.conf ├── t1.conf └── t2.conf /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HOCON (Human-Optimized Config Object Notation) 2 | ===== 3 | [![GoDoc](https://godoc.org/github.com/go-akka/configuration?status.svg)](https://godoc.org/github.com/go-akka/configuration) 4 | [HOCON Docs](https://github.com/typesafehub/config/blob/master/HOCON.md). 5 | 6 | > Currently, some features are not implemented, the API might be a little changed in the future. 7 | 8 | 9 | `example.go` 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "github.com/go-akka/configuration" 17 | ) 18 | 19 | var configText = ` 20 | #################################### 21 | # Typesafe HOCON # 22 | #################################### 23 | 24 | config { 25 | # Comment 26 | version = "0.0.1" 27 | one-second = 1s 28 | one-day = 1day 29 | array = ["one", "two", "three"] #comment 30 | bar = "bar" 31 | foo = foo.${config.bar} 32 | number = 1 33 | object { 34 | a = "a" 35 | b = "b" 36 | c = { 37 | d = ${config.object.a} //comment 38 | } 39 | } 40 | } 41 | // fallback 42 | config.object.a="newA" 43 | config.object.c.f="valueF" 44 | 45 | // self reference 46 | self-ref=1 47 | self-ref=[${self-ref}][2] 48 | 49 | // byte size 50 | byte-size=10MiB 51 | 52 | // system envs 53 | home:${HOME} 54 | 55 | plus-equal=foo 56 | plus-equal+=bar 57 | 58 | plus-equal-array=[foo] 59 | plus-equal-array+=[bar, ${HOME}] 60 | ` 61 | 62 | func main() { 63 | conf := configuration.ParseString(configText) 64 | 65 | fmt.Println("config.one-second:", conf.GetTimeDuration("config.one-second")) 66 | fmt.Println("config.one-day:", conf.GetTimeDuration("config.one-day")) 67 | fmt.Println("config.array:", conf.GetStringList("config.array")) 68 | fmt.Println("config.bar:", conf.GetString("config.bar")) 69 | fmt.Println("config.foo:", conf.GetString("config.foo")) 70 | fmt.Println("config.number:", conf.GetInt64("config.number")) 71 | fmt.Println("config.object.a:", conf.GetString("config.object.a")) 72 | fmt.Println("config.object.c.d:", conf.GetString("config.object.c.d")) 73 | fmt.Println("config.object.c.f:", conf.GetString("config.object.c.f")) 74 | fmt.Println("self-ref:", conf.GetInt64List("self-ref")) 75 | fmt.Println("byte-size:", conf.GetByteSize("byte-size")) 76 | fmt.Println("home:", conf.GetString("home")) 77 | fmt.Println("default:", conf.GetString("none", "default-value")) 78 | fmt.Println("plus-equal:", conf.GetString("plus-equal")) 79 | fmt.Println("plus-equal-array:", conf.GetStringList("plus-equal-array")) 80 | } 81 | 82 | ``` 83 | 84 | ```bash 85 | > go run example.go 86 | config.one-second: 1s 87 | config.one-day: 24h0m0s 88 | config.array: [one two three] 89 | config.bar: bar 90 | config.foo: foo.bar 91 | config.number: 1 92 | config.object.a: newA 93 | config.object.c.d: a 94 | config.object.c.f: valueF 95 | self-ref: [1 2] 96 | byte-size: 10485760 97 | home: /Users/zeal 98 | default: default-value 99 | plus-equal: foobar 100 | plus-equal-array: [foo bar /Users/zeal] 101 | ``` 102 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | "time" 7 | 8 | "github.com/go-akka/configuration/hocon" 9 | ) 10 | 11 | type Config struct { 12 | root *hocon.HoconValue 13 | substitutions []*hocon.HoconSubstitution 14 | fallback *Config 15 | } 16 | 17 | func NewConfigFromRoot(root *hocon.HoconRoot) *Config { 18 | if root.Value() == nil { 19 | panic("The root value cannot be null.") 20 | } 21 | 22 | return &Config{ 23 | root: root.Value(), 24 | substitutions: root.Substitutions(), 25 | } 26 | } 27 | 28 | func NewConfigFromConfig(source, fallback *Config) *Config { 29 | if source == nil { 30 | panic("The source configuration cannot be null.") 31 | } 32 | 33 | return &Config{ 34 | root: source.root, 35 | fallback: fallback, 36 | } 37 | } 38 | 39 | func (p *Config) IsEmpty() bool { 40 | return p == nil || p.root == nil || p.root.IsEmpty() 41 | } 42 | 43 | func (p *Config) Root() *hocon.HoconValue { 44 | return p.root 45 | } 46 | 47 | func (p *Config) Copy(fallback ...*Config) *Config { 48 | 49 | var fb *Config 50 | 51 | if p.fallback != nil { 52 | fb = p.fallback.Copy() 53 | } else { 54 | if len(fallback) > 0 { 55 | fb = fallback[0] 56 | } 57 | } 58 | return &Config{ 59 | fallback: fb, 60 | root: p.root, 61 | substitutions: p.substitutions, 62 | } 63 | } 64 | 65 | func (p *Config) GetNode(path string) *hocon.HoconValue { 66 | if p == nil { 67 | return nil 68 | } 69 | 70 | elements := splitDottedPathHonouringQuotes(path) 71 | currentNode := p.root 72 | 73 | if currentNode == nil { 74 | panic("Current node should not be null") 75 | } 76 | 77 | for _, key := range elements { 78 | currentNode = currentNode.GetChildObject(key) 79 | if currentNode == nil { 80 | if p.fallback != nil { 81 | return p.fallback.GetNode(path) 82 | } 83 | return nil 84 | } 85 | } 86 | return currentNode 87 | } 88 | 89 | func (p *Config) GetBoolean(path string, defaultVal ...bool) bool { 90 | obj := p.GetNode(path) 91 | if obj == nil { 92 | if len(defaultVal) > 0 { 93 | return defaultVal[0] 94 | } 95 | return false 96 | } 97 | return obj.GetBoolean() 98 | } 99 | 100 | func (p *Config) GetByteSize(path string) *big.Int { 101 | obj := p.GetNode(path) 102 | if obj == nil { 103 | return big.NewInt(-1) 104 | } 105 | return obj.GetByteSize() 106 | } 107 | 108 | func (p *Config) GetInt32(path string, defaultVal ...int32) int32 { 109 | obj := p.GetNode(path) 110 | if obj == nil { 111 | if len(defaultVal) > 0 { 112 | return defaultVal[0] 113 | } 114 | return 0 115 | } 116 | return obj.GetInt32() 117 | } 118 | 119 | func (p *Config) GetInt64(path string, defaultVal ...int64) int64 { 120 | obj := p.GetNode(path) 121 | if obj == nil { 122 | if len(defaultVal) > 0 { 123 | return defaultVal[0] 124 | } 125 | return 0 126 | } 127 | return obj.GetInt64() 128 | } 129 | 130 | func (p *Config) GetString(path string, defaultVal ...string) string { 131 | obj := p.GetNode(path) 132 | if obj == nil { 133 | if len(defaultVal) > 0 { 134 | return defaultVal[0] 135 | } 136 | return "" 137 | } 138 | return obj.GetString() 139 | } 140 | 141 | func (p *Config) GetFloat32(path string, defaultVal ...float32) float32 { 142 | obj := p.GetNode(path) 143 | if obj == nil { 144 | if len(defaultVal) > 0 { 145 | return defaultVal[0] 146 | } 147 | } 148 | return obj.GetFloat32() 149 | } 150 | 151 | func (p *Config) GetFloat64(path string, defaultVal ...float64) float64 { 152 | obj := p.GetNode(path) 153 | if obj == nil { 154 | if len(defaultVal) > 0 { 155 | return defaultVal[0] 156 | } 157 | return 0 158 | } 159 | return obj.GetFloat64() 160 | } 161 | 162 | func (p *Config) GetTimeDuration(path string, defaultVal ...time.Duration) time.Duration { 163 | obj := p.GetNode(path) 164 | if obj == nil { 165 | if len(defaultVal) > 0 { 166 | return defaultVal[0] 167 | } 168 | return 0 169 | } 170 | return obj.GetTimeDuration(true) 171 | } 172 | 173 | func (p *Config) GetTimeDurationInfiniteNotAllowed(path string, defaultVal ...time.Duration) time.Duration { 174 | obj := p.GetNode(path) 175 | if obj == nil { 176 | if len(defaultVal) > 0 { 177 | return defaultVal[0] 178 | } 179 | return 0 180 | } 181 | return obj.GetTimeDuration(false) 182 | } 183 | 184 | func (p *Config) GetBooleanList(path string) []bool { 185 | obj := p.GetNode(path) 186 | if obj == nil { 187 | return nil 188 | } 189 | return obj.GetBooleanList() 190 | } 191 | 192 | func (p *Config) GetFloat32List(path string) []float32 { 193 | obj := p.GetNode(path) 194 | if obj == nil { 195 | return nil 196 | } 197 | return obj.GetFloat32List() 198 | } 199 | 200 | func (p *Config) GetFloat64List(path string) []float64 { 201 | obj := p.GetNode(path) 202 | if obj == nil { 203 | return nil 204 | } 205 | return obj.GetFloat64List() 206 | } 207 | 208 | func (p *Config) GetInt32List(path string) []int32 { 209 | obj := p.GetNode(path) 210 | if obj == nil { 211 | return nil 212 | } 213 | return obj.GetInt32List() 214 | } 215 | 216 | func (p *Config) GetInt64List(path string) []int64 { 217 | obj := p.GetNode(path) 218 | if obj == nil { 219 | return nil 220 | } 221 | return obj.GetInt64List() 222 | } 223 | 224 | func (p *Config) GetByteList(path string) []byte { 225 | obj := p.GetNode(path) 226 | if obj == nil { 227 | return nil 228 | } 229 | return obj.GetByteList() 230 | } 231 | 232 | func (p *Config) GetStringList(path string) []string { 233 | obj := p.GetNode(path) 234 | if obj == nil { 235 | return nil 236 | } 237 | return obj.GetStringList() 238 | } 239 | 240 | func (p *Config) GetConfig(path string) *Config { 241 | if p == nil { 242 | return nil 243 | } 244 | 245 | value := p.GetNode(path) 246 | if p.fallback != nil { 247 | f := p.fallback.GetConfig(path) 248 | if value == nil && f == nil { 249 | return nil 250 | } 251 | if value == nil { 252 | return f 253 | } 254 | return NewConfigFromRoot(hocon.NewHoconRoot(value)).WithFallback(f) 255 | } 256 | 257 | if value == nil { 258 | return nil 259 | } 260 | return NewConfigFromRoot(hocon.NewHoconRoot(value)) 261 | } 262 | 263 | func (p *Config) GetValue(path string) *hocon.HoconValue { 264 | return p.GetNode(path) 265 | } 266 | 267 | func (p *Config) WithFallback(fallback *Config) *Config { 268 | if fallback == p { 269 | panic("Config can not have itself as fallback") 270 | } 271 | 272 | if fallback == nil { 273 | return p 274 | } 275 | 276 | mergedRoot := p.root.GetObject().MergeImmutable(fallback.root.GetObject()) 277 | newRoot := hocon.NewHoconValue() 278 | 279 | newRoot.AppendValue(mergedRoot) 280 | 281 | mergedConfig := p.Copy(fallback) 282 | 283 | mergedConfig.root = newRoot 284 | 285 | return mergedConfig 286 | } 287 | 288 | func (p *Config) HasPath(path string) bool { 289 | return p.GetNode(path) != nil 290 | } 291 | 292 | func (p *Config) IsObject(path string) bool { 293 | node := p.GetNode(path) 294 | if node == nil { 295 | return false 296 | } 297 | 298 | return node.IsObject() 299 | } 300 | 301 | func (p *Config) IsArray(path string) bool { 302 | node := p.GetNode(path) 303 | if node == nil { 304 | return false 305 | } 306 | 307 | return node.IsArray() 308 | } 309 | 310 | func (p *Config) AddConfig(textConfig string, fallbackConfig *Config) *Config { 311 | root := hocon.Parse(textConfig, nil) 312 | config := NewConfigFromRoot(root) 313 | return config.WithFallback(fallbackConfig) 314 | } 315 | 316 | func (p *Config) AddConfigWithTextFallback(config *Config, textFallback string) *Config { 317 | fallbackRoot := hocon.Parse(textFallback, nil) 318 | fallbackConfig := NewConfigFromRoot(fallbackRoot) 319 | return config.WithFallback(fallbackConfig) 320 | } 321 | 322 | func (p Config) String() string { 323 | return p.root.String() 324 | } 325 | 326 | func splitDottedPathHonouringQuotes(path string) []string { 327 | tmp1 := strings.Split(path, "\"") 328 | var values []string 329 | for i := 0; i < len(tmp1); i++ { 330 | tmp2 := strings.Split(tmp1[i], ".") 331 | for j := 0; j < len(tmp2); j++ { 332 | if len(tmp2[j]) > 0 { 333 | values = append(values, tmp2[j]) 334 | } 335 | } 336 | } 337 | return values 338 | } 339 | -------------------------------------------------------------------------------- /configuration.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | 7 | "github.com/go-akka/configuration/hocon" 8 | ) 9 | 10 | func ParseString(text string, includeCallback ...hocon.IncludeCallback) *Config { 11 | var callback hocon.IncludeCallback 12 | if len(includeCallback) > 0 { 13 | callback = includeCallback[0] 14 | } else { 15 | callback = defaultIncludeCallback 16 | } 17 | root := hocon.Parse(text, callback) 18 | return NewConfigFromRoot(root) 19 | } 20 | 21 | func LoadConfig(filename string) *Config { 22 | data, err := ioutil.ReadFile(filename) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | return ParseString(string(data), defaultIncludeCallback) 28 | } 29 | 30 | func FromObject(obj interface{}) *Config { 31 | data, err := json.Marshal(obj) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | return ParseString(string(data), defaultIncludeCallback) 37 | } 38 | 39 | func defaultIncludeCallback(filename string) *hocon.HoconRoot { 40 | data, err := ioutil.ReadFile(filename) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | return hocon.Parse(string(data), defaultIncludeCallback) 46 | } 47 | -------------------------------------------------------------------------------- /configuration_test.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "testing" 8 | ) 9 | 10 | func TestParseKeyOrder(t *testing.T) { 11 | 12 | wg := &sync.WaitGroup{} 13 | 14 | fn := func() { 15 | 16 | defer func() { 17 | wg.Done() 18 | }() 19 | 20 | for i := 0; i < 100000; i++ { 21 | conf := LoadConfig("tests/configs.conf") 22 | for g := 1; g < 3; g++ { 23 | for i := 1; i < 4; i++ { 24 | key := fmt.Sprintf("test.out.a.b.c.d.groups.g%d.o%d.order", g, i) 25 | order := conf.GetInt32(key, -1) 26 | 27 | if order != int32(i) { 28 | fmt.Println(conf) 29 | t.Fatalf("order not match,group %d, except: %d, real order: %d", g, i, order) 30 | return 31 | } 32 | } 33 | } 34 | conf = nil 35 | runtime.Gosched() 36 | } 37 | } 38 | 39 | wg.Add(2) 40 | 41 | go fn() 42 | go fn() 43 | 44 | wg.Wait() 45 | } 46 | -------------------------------------------------------------------------------- /hocon/array.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type HoconArray struct { 8 | values []*HoconValue 9 | } 10 | 11 | func NewHoconArray() *HoconArray { 12 | return &HoconArray{} 13 | } 14 | 15 | func (p *HoconArray) IsString() bool { 16 | return false 17 | } 18 | 19 | func (p *HoconArray) GetString() string { 20 | panic("This element is an array and not a string.") 21 | } 22 | 23 | func (p *HoconArray) IsArray() bool { 24 | return true 25 | } 26 | 27 | func (p *HoconArray) GetArray() []*HoconValue { 28 | return p.values 29 | } 30 | 31 | func (p *HoconArray) String() string { 32 | var strs []string 33 | for _, v := range p.values { 34 | strs = append(strs, v.String()) 35 | } 36 | return "[" + strings.Join(strs, ",") + "]" 37 | } 38 | -------------------------------------------------------------------------------- /hocon/element.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | type MightBeAHoconObject interface { 4 | IsObject() bool 5 | GetObject() *HoconObject 6 | } 7 | 8 | type HoconElement interface { 9 | IsString() bool 10 | GetString() string 11 | IsArray() bool 12 | GetArray() []*HoconValue 13 | } 14 | -------------------------------------------------------------------------------- /hocon/literal.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | type HoconLiteral struct { 4 | value string 5 | } 6 | 7 | func NewHoconLiteral(value string) *HoconLiteral { 8 | return &HoconLiteral{value: value} 9 | } 10 | 11 | func (p *HoconLiteral) IsString() bool { 12 | return true 13 | } 14 | 15 | func (p *HoconLiteral) GetString() string { 16 | return p.value 17 | } 18 | 19 | func (p *HoconLiteral) IsArray() bool { 20 | return false 21 | } 22 | 23 | func (p *HoconLiteral) GetArray() []*HoconValue { 24 | panic("This element is a string literal and not an array.") 25 | } 26 | 27 | func (p *HoconLiteral) String() string { 28 | return p.value 29 | } 30 | -------------------------------------------------------------------------------- /hocon/object.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type HoconObject struct { 10 | items map[string]*HoconValue 11 | keys []string 12 | } 13 | 14 | func NewHoconObject() *HoconObject { 15 | return &HoconObject{ 16 | items: make(map[string]*HoconValue), 17 | } 18 | } 19 | 20 | func (p *HoconObject) GetString() string { 21 | panic("This element is an object and not a string.") 22 | } 23 | 24 | func (p *HoconObject) IsArray() bool { 25 | return false 26 | } 27 | 28 | func (p *HoconObject) GetArray() []*HoconValue { 29 | panic("This element is an object and not an array.") 30 | } 31 | 32 | func (p *HoconObject) GetKeys() []string { 33 | return p.keys 34 | } 35 | 36 | func (p *HoconObject) Unwrapped() map[string]interface{} { 37 | if len(p.items) == 0 { 38 | return nil 39 | } 40 | 41 | dics := map[string]interface{}{} 42 | 43 | for _, k := range p.keys { 44 | v := p.items[k] 45 | 46 | obj := v.GetObject() 47 | if obj != nil { 48 | dics[k] = obj.Unwrapped() 49 | } else { 50 | dics[k] = v 51 | } 52 | } 53 | 54 | return dics 55 | } 56 | 57 | func (p *HoconObject) Items() map[string]*HoconValue { 58 | return p.items 59 | } 60 | 61 | func (p *HoconObject) GetKey(key string) *HoconValue { 62 | value, _ := p.items[key] 63 | return value 64 | } 65 | 66 | func (p *HoconObject) GetOrCreateKey(key string) *HoconValue { 67 | if value, exist := p.items[key]; exist { 68 | child := NewHoconValue() 69 | child.oldValue = value 70 | p.items[key] = child 71 | return child 72 | } 73 | 74 | child := NewHoconValue() 75 | p.items[key] = child 76 | p.keys = append(p.keys, key) 77 | return child 78 | } 79 | 80 | func (p *HoconObject) IsString() bool { 81 | return false 82 | } 83 | 84 | func (p *HoconObject) String() string { 85 | return p.ToString(0) 86 | } 87 | 88 | func (p *HoconObject) ToString(indent int) string { 89 | tmp := strings.Repeat(" ", indent*2) 90 | buf := bytes.NewBuffer(nil) 91 | for _, k := range p.keys { 92 | key := p.quoteIfNeeded(k) 93 | v := p.items[key] 94 | buf.WriteString(fmt.Sprintf("%s%s : %s\r\n", tmp, key, v.ToString(indent))) 95 | } 96 | return buf.String() 97 | } 98 | 99 | func (p *HoconObject) Merge(other *HoconObject) { 100 | thisValues := p.items 101 | otherItems := other.items 102 | 103 | otherKeys := other.keys 104 | 105 | for _, otherkey := range otherKeys { 106 | otherValue := otherItems[otherkey] 107 | 108 | if thisValue, exist := thisValues[otherkey]; exist { 109 | if thisValue.IsObject() && otherValue.IsObject() { 110 | thisValue.GetObject().Merge(otherValue.GetObject()) 111 | } 112 | } else { 113 | p.items[otherkey] = otherValue 114 | p.keys = append(p.keys, otherkey) 115 | } 116 | } 117 | } 118 | 119 | func (p *HoconObject) MergeImmutable(other *HoconObject) *HoconObject { 120 | thisValues := map[string]*HoconValue{} 121 | otherKeys := other.keys 122 | 123 | var thisKeys []string 124 | 125 | otherItems := other.items 126 | 127 | for _, otherkey := range otherKeys { 128 | otherValue := otherItems[otherkey] 129 | 130 | if thisValue, exist := thisValues[otherkey]; exist { 131 | 132 | if thisValue.IsObject() && otherValue.IsObject() { 133 | 134 | mergedObject := thisValue.GetObject().MergeImmutable(otherValue.GetObject()) 135 | mergedValue := NewHoconValue() 136 | 137 | mergedValue.AppendValue(mergedObject) 138 | thisValues[otherkey] = mergedValue 139 | } 140 | } else { 141 | thisValues[otherkey] = &HoconValue{values: otherValue.values} 142 | thisKeys = append(thisKeys, otherkey) 143 | } 144 | } 145 | 146 | return &HoconObject{items: thisValues, keys: thisKeys} 147 | } 148 | 149 | func (p *HoconObject) quoteIfNeeded(text string) string { 150 | if strings.IndexByte(text, ' ') >= 0 || 151 | strings.IndexByte(text, '\t') >= 0 { 152 | return "\"" + text + "\"" 153 | } 154 | return text 155 | } 156 | -------------------------------------------------------------------------------- /hocon/parser.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | type IncludeCallback func(filename string) *HoconRoot 9 | 10 | type Parser struct { 11 | reader *HoconTokenizer 12 | root *HoconValue 13 | callback IncludeCallback 14 | 15 | substitutions []*HoconSubstitution 16 | } 17 | 18 | func Parse(text string, callback IncludeCallback) *HoconRoot { 19 | return new(Parser).parseText(text, callback) 20 | } 21 | 22 | func (p *Parser) parseText(text string, callback IncludeCallback) *HoconRoot { 23 | p.callback = callback 24 | p.root = NewHoconValue() 25 | p.reader = NewHoconTokenizer(text) 26 | p.reader.PullWhitespaceAndComments() 27 | p.parseObject(p.root, true, "") 28 | 29 | root := NewHoconRoot(p.root) 30 | 31 | cRoot := root.Value() 32 | 33 | for _, sub := range p.substitutions { 34 | res := getNode(cRoot, sub.Path) 35 | if res == nil { 36 | envVal, exist := os.LookupEnv(sub.OrignialPath) 37 | if !exist { 38 | if !sub.IsOptional { 39 | panic("Unresolved substitution:" + sub.Path) 40 | } 41 | } else { 42 | hv := NewHoconValue() 43 | hv.AppendValue(NewHoconLiteral(envVal)) 44 | sub.ResolvedValue = hv 45 | } 46 | } else { 47 | sub.ResolvedValue = res 48 | } 49 | } 50 | 51 | return NewHoconRoot(p.root, p.substitutions...) 52 | } 53 | 54 | func (p *Parser) parseObject(owner *HoconValue, root bool, currentPath string) { 55 | if !owner.IsObject() { 56 | owner.NewValue(NewHoconObject()) 57 | } 58 | 59 | if owner.IsObject() { 60 | rootObj := owner 61 | for rootObj.oldValue != nil { 62 | oldObj := rootObj.oldValue.GetObject() 63 | obj := rootObj.GetObject() 64 | 65 | if oldObj == nil || obj == nil { 66 | break 67 | } 68 | obj.Merge(oldObj) 69 | rootObj = rootObj.oldValue 70 | } 71 | } 72 | 73 | currentObject := owner.GetObject() 74 | 75 | for !p.reader.EOF() { 76 | t := p.reader.PullNext() 77 | 78 | switch t.tokenType { 79 | case TokenTypeInclude: 80 | included := p.callback(t.value) 81 | substitutions := included.substitutions 82 | for _, substitution := range substitutions { 83 | substitution.Path = currentPath + "." + substitution.Path 84 | } 85 | p.substitutions = append(p.substitutions, substitutions...) 86 | otherObj := included.value.GetObject() 87 | owner.GetObject().Merge(otherObj) 88 | case TokenTypeEoF: 89 | case TokenTypeKey: 90 | value := currentObject.GetOrCreateKey(t.value) 91 | nextPath := t.value 92 | if len(currentPath) > 0 { 93 | nextPath = currentPath + "." + t.value 94 | } 95 | p.parseKeyContent(value, nextPath) 96 | if !root { 97 | return 98 | } 99 | case TokenTypeObjectEnd: 100 | return 101 | } 102 | } 103 | } 104 | 105 | func (p *Parser) parseKeyContent(value *HoconValue, currentPath string) { 106 | for !p.reader.EOF() { 107 | t := p.reader.PullNext() 108 | switch t.tokenType { 109 | case TokenTypeDot: 110 | p.parseObject(value, false, currentPath) 111 | return 112 | case TokenTypeAssign: 113 | { 114 | if !value.IsObject() { 115 | value.Clear() 116 | } 117 | } 118 | p.ParseValue(value, false, currentPath) 119 | return 120 | case TokenTypePlusAssign: 121 | { 122 | if !value.IsObject() { 123 | value.Clear() 124 | } 125 | } 126 | p.ParseValue(value, true, currentPath) 127 | return 128 | case TokenTypeObjectStart: 129 | p.parseObject(value, true, currentPath) 130 | return 131 | } 132 | } 133 | } 134 | 135 | func (p *Parser) ParseValue(owner *HoconValue, isEqualPlus bool, currentPath string) { 136 | if p.reader.EOF() { 137 | panic("End of file reached while trying to read a value") 138 | } 139 | 140 | p.reader.PullWhitespaceAndComments() 141 | for p.reader.isValue() { 142 | t := p.reader.PullValue() 143 | 144 | if isEqualPlus { 145 | sub := p.ParseSubstitution(currentPath, false) 146 | p.substitutions = append(p.substitutions, sub) 147 | owner.AppendValue(sub) 148 | } 149 | 150 | switch t.tokenType { 151 | case TokenTypeEoF: 152 | case TokenTypeLiteralValue: 153 | if owner.IsObject() { 154 | owner.Clear() 155 | } 156 | lit := NewHoconLiteral(t.value) 157 | owner.AppendValue(lit) 158 | case TokenTypeObjectStart: 159 | p.parseObject(owner, true, currentPath) 160 | case TokenTypeArrayStart: 161 | arr := p.ParseArray(currentPath) 162 | owner.AppendValue(&arr) 163 | case TokenTypeSubstitute: 164 | sub := p.ParseSubstitution(t.value, t.isOptional) 165 | p.substitutions = append(p.substitutions, sub) 166 | owner.AppendValue(sub) 167 | } 168 | 169 | if p.reader.IsSpaceOrTab() { 170 | p.ParseTrailingWhitespace(owner) 171 | } 172 | } 173 | p.ignoreComma() 174 | p.ignoreNewline() 175 | } 176 | 177 | func (p *Parser) ParseTrailingWhitespace(owner *HoconValue) { 178 | ws := p.reader.PullSpaceOrTab() 179 | if len(ws.value) > 0 { 180 | wsList := NewHoconLiteral(ws.value) 181 | owner.AppendValue(wsList) 182 | } 183 | } 184 | 185 | func (p *Parser) ParseSubstitution(value string, isOptional bool) *HoconSubstitution { 186 | return NewHoconSubstitution(value, isOptional) 187 | } 188 | 189 | func (p *Parser) ParseArray(currentPath string) HoconArray { 190 | arr := NewHoconArray() 191 | for !p.reader.EOF() && !p.reader.IsArrayEnd() { 192 | v := NewHoconValue() 193 | p.ParseValue(v, false, currentPath) 194 | arr.values = append(arr.values, v) 195 | p.reader.PullWhitespaceAndComments() 196 | } 197 | p.reader.PullArrayEnd() 198 | return *arr 199 | } 200 | 201 | func (p *Parser) ignoreComma() { 202 | if p.reader.IsComma() { 203 | p.reader.PullComma() 204 | } 205 | } 206 | 207 | func (p *Parser) ignoreNewline() { 208 | if p.reader.IsNewline() { 209 | p.reader.PullNewline() 210 | } 211 | } 212 | 213 | func getNode(root *HoconValue, path string) *HoconValue { 214 | elements := splitDottedPathHonouringQuotes(path) 215 | currentNode := root 216 | 217 | if currentNode == nil { 218 | panic("Current node should not be null") 219 | } 220 | 221 | for _, key := range elements { 222 | currentNode = currentNode.GetChildObject(key) 223 | if currentNode == nil { 224 | return nil 225 | } 226 | } 227 | return currentNode 228 | } 229 | 230 | func splitDottedPathHonouringQuotes(path string) []string { 231 | tmp1 := strings.Split(path, "\"") 232 | var values []string 233 | for i := 0; i < len(tmp1); i++ { 234 | tmp2 := strings.Split(tmp1[i], ".") 235 | for j := 0; j < len(tmp2); j++ { 236 | if len(tmp2[j]) > 0 { 237 | values = append(values, tmp2[j]) 238 | } 239 | } 240 | } 241 | return values 242 | } 243 | -------------------------------------------------------------------------------- /hocon/root.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | type HoconRoot struct { 4 | value *HoconValue 5 | substitutions []*HoconSubstitution 6 | } 7 | 8 | func NewHoconRoot(value *HoconValue, substitutions ...*HoconSubstitution) *HoconRoot { 9 | return &HoconRoot{ 10 | value: value, 11 | substitutions: substitutions, 12 | } 13 | } 14 | 15 | func (p HoconRoot) Value() *HoconValue { 16 | return p.value 17 | } 18 | 19 | func (p HoconRoot) Substitutions() []*HoconSubstitution { 20 | return p.substitutions 21 | } 22 | -------------------------------------------------------------------------------- /hocon/stack.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | type Stack struct { 9 | lock sync.Mutex 10 | values []int 11 | } 12 | 13 | func NewStack() *Stack { 14 | return &Stack{sync.Mutex{}, make([]int, 0)} 15 | } 16 | 17 | func (p *Stack) Push(v int) { 18 | p.lock.Lock() 19 | defer p.lock.Unlock() 20 | 21 | p.values = append(p.values, v) 22 | } 23 | 24 | func (p *Stack) Pop() (int, error) { 25 | p.lock.Lock() 26 | defer p.lock.Unlock() 27 | 28 | l := len(p.values) 29 | if l == 0 { 30 | return 0, errors.New("Empty Stack") 31 | } 32 | 33 | res := p.values[l-1] 34 | p.values = p.values[:l-1] 35 | return res, nil 36 | } 37 | -------------------------------------------------------------------------------- /hocon/substitution.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type HoconSubstitution struct { 8 | Path string 9 | ResolvedValue *HoconValue 10 | IsOptional bool 11 | OrignialPath string 12 | } 13 | 14 | func NewHoconSubstitution(path string, isOptional bool) *HoconSubstitution { 15 | return &HoconSubstitution{Path: path, OrignialPath: path, IsOptional: isOptional} 16 | } 17 | 18 | func (p *HoconSubstitution) IsString() bool { 19 | if p.ResolvedValue == nil { 20 | return false 21 | } 22 | p.checkCycleRef() 23 | return p.ResolvedValue.IsString() 24 | } 25 | 26 | func (p *HoconSubstitution) GetString() string { 27 | if p.ResolvedValue == nil { 28 | return "" 29 | } 30 | p.checkCycleRef() 31 | return p.ResolvedValue.GetString() 32 | } 33 | 34 | func (p *HoconSubstitution) IsArray() bool { 35 | if p.ResolvedValue == nil { 36 | return false 37 | } 38 | p.checkCycleRef() 39 | return p.ResolvedValue.IsArray() 40 | } 41 | func (p *HoconSubstitution) GetArray() []*HoconValue { 42 | if p.ResolvedValue == nil { 43 | return nil 44 | } 45 | return p.ResolvedValue.GetArray() 46 | } 47 | 48 | func (p *HoconSubstitution) IsObject() bool { 49 | if p.ResolvedValue == nil { 50 | return false 51 | } 52 | p.checkCycleRef() 53 | return p.ResolvedValue.IsObject() 54 | } 55 | 56 | func (p *HoconSubstitution) GetObject() *HoconObject { 57 | if p.ResolvedValue == nil { 58 | return nil 59 | } 60 | p.checkCycleRef() 61 | return p.ResolvedValue.GetObject() 62 | } 63 | 64 | func (p *HoconSubstitution) checkCycleRef() { 65 | if p.hasCycleRef(map[HoconElement]int{}, 1, p.ResolvedValue) { 66 | panic(fmt.Sprintf("cycle reference in path of %s", p.Path)) 67 | } 68 | } 69 | 70 | // Temporary solution 71 | func (p *HoconSubstitution) hasCycleRef(dup map[HoconElement]int, level int, v interface{}) bool { 72 | if v == nil { 73 | return false 74 | } 75 | 76 | val, ok := v.(*HoconValue) 77 | if val == nil { 78 | return false 79 | } 80 | 81 | if !ok { 82 | return false 83 | } 84 | 85 | if lvl, exist := dup[val]; exist { 86 | if lvl != level { 87 | return true 88 | } 89 | } 90 | dup[val] = level 91 | 92 | for _, subV := range val.values { 93 | if sub, ok := subV.(*HoconSubstitution); ok { 94 | 95 | if sub.ResolvedValue != nil { 96 | return p.hasCycleRef(dup, level+1, sub.ResolvedValue) 97 | } 98 | } 99 | } 100 | 101 | return false 102 | } 103 | -------------------------------------------------------------------------------- /hocon/token.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | type TokenType int 4 | 5 | const ( 6 | TokenTypeNone TokenType = iota 7 | TokenTypeComment 8 | TokenTypeKey 9 | TokenTypeLiteralValue 10 | TokenTypeAssign 11 | TokenTypePlusAssign 12 | TokenTypeObjectStart 13 | TokenTypeObjectEnd 14 | TokenTypeDot 15 | TokenTypeNewline 16 | TokenTypeEoF 17 | TokenTypeArrayStart 18 | TokenTypeArrayEnd 19 | TokenTypeComma 20 | TokenTypeSubstitute 21 | TokenTypeInclude 22 | ) 23 | 24 | var ( 25 | DefaultToken = Token{} 26 | ) 27 | 28 | type Token struct { 29 | tokenType TokenType 30 | value string 31 | isOptional bool 32 | } 33 | 34 | func NewToken(v interface{}) *Token { 35 | 36 | switch value := v.(type) { 37 | case string: 38 | { 39 | return &Token{tokenType: TokenTypeLiteralValue, value: value} 40 | } 41 | case TokenType: 42 | { 43 | return &Token{tokenType: value} 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (p *Token) Key(key string) *Token { 51 | return &Token{tokenType: TokenTypeKey, value: key} 52 | } 53 | 54 | func (p *Token) Substitution(path string, isOptional bool) *Token { 55 | return &Token{tokenType: TokenTypeSubstitute, value: path, isOptional: isOptional} 56 | } 57 | 58 | func (p *Token) LiteralValue(value string) *Token { 59 | return &Token{tokenType: TokenTypeLiteralValue, value: value} 60 | } 61 | 62 | func (p *Token) Include(path string) *Token { 63 | return &Token{tokenType: TokenTypeInclude, value: path} 64 | } 65 | 66 | func StringTokenType(tokenType TokenType) string { 67 | switch tokenType { 68 | case TokenTypeNone: 69 | return "TokenTypeNone" 70 | case TokenTypeComment: 71 | return "TokenTypeComment" 72 | case TokenTypeKey: 73 | return "TokenTypeKey" 74 | case TokenTypeLiteralValue: 75 | return "TokenTypeLiteralValue" 76 | case TokenTypeAssign: 77 | return "TokenTypeAssign" 78 | case TokenTypePlusAssign: 79 | return "TokenTypePlusAssign" 80 | case TokenTypeObjectStart: 81 | return "TokenTypeObjectStart" 82 | case TokenTypeObjectEnd: 83 | return "TokenTypeObjectEnd" 84 | case TokenTypeDot: 85 | return "TokenTypeDot" 86 | case TokenTypeNewline: 87 | return "TokenTypeNewline" 88 | case TokenTypeEoF: 89 | return "TokenTypeEoF" 90 | case TokenTypeArrayStart: 91 | return "TokenTypeArrayStart" 92 | case TokenTypeArrayEnd: 93 | return "TokenTypeArrayEnd" 94 | case TokenTypeComma: 95 | return "TokenTypeComma" 96 | case TokenTypeSubstitute: 97 | return "TokenTypeSubstitute" 98 | case TokenTypeInclude: 99 | return "TokenTypeInclude" 100 | } 101 | return "<>" 102 | } 103 | -------------------------------------------------------------------------------- /hocon/tokenizer.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | HoconNotInUnquotedKey = "$\"{}[]:=+,#`^?!@*&\\." 11 | HoconNotInUnquotedText = "$\"{}[]:=+,#`^?!@*&\\" 12 | ) 13 | 14 | type Tokenizer struct { 15 | text string 16 | index int 17 | indexStack *Stack 18 | } 19 | 20 | func NewTokenizer(text string) *Tokenizer { 21 | return &Tokenizer{ 22 | indexStack: NewStack(), 23 | text: text, 24 | } 25 | } 26 | 27 | func (p *Tokenizer) Push() { 28 | p.indexStack.Push(p.index) 29 | } 30 | 31 | func (p *Tokenizer) Pop() { 32 | index, err := p.indexStack.Pop() 33 | if err != nil { 34 | panic(err) 35 | } 36 | p.index = index 37 | } 38 | 39 | func (p *Tokenizer) EOF() bool { 40 | return p.index >= len(p.text) 41 | } 42 | 43 | func (p *Tokenizer) Matches(pattern string) bool { 44 | 45 | if len(pattern)+p.index > len(p.text) { 46 | return false 47 | } 48 | 49 | selected := string(p.text[p.index : p.index+len(pattern)]) 50 | 51 | if selected == pattern { 52 | return true 53 | } 54 | 55 | return false 56 | } 57 | 58 | func (p *Tokenizer) MatchesMore(patterns []string) bool { 59 | for _, pattern := range patterns { 60 | if len(pattern)+p.index >= len(p.text) { 61 | continue 62 | } 63 | 64 | if string(p.text[p.index:p.index+len(pattern)]) == pattern { 65 | return true 66 | } 67 | } 68 | return false 69 | } 70 | 71 | func (p *Tokenizer) Take(length int) string { 72 | if p.index+length > len(p.text) { 73 | return "" 74 | } 75 | 76 | str := string(p.text[p.index : p.index+length]) 77 | p.index += length 78 | return str 79 | } 80 | 81 | func (p *Tokenizer) Peek() byte { 82 | if p.EOF() { 83 | return 0 84 | } 85 | 86 | return p.text[p.index] 87 | } 88 | 89 | func (p *Tokenizer) TakeOne() byte { 90 | if p.EOF() { 91 | return 0 92 | } 93 | 94 | b := p.text[p.index] 95 | p.index += 1 96 | return b 97 | } 98 | 99 | func (p *Tokenizer) PullWhitespace() { 100 | for !p.EOF() && isWhitespace(p.Peek()) { 101 | p.TakeOne() 102 | } 103 | } 104 | 105 | type HoconTokenizer struct { 106 | *Tokenizer 107 | } 108 | 109 | func NewHoconTokenizer(text string) *HoconTokenizer { 110 | return &HoconTokenizer{NewTokenizer(text)} 111 | } 112 | 113 | func (p *HoconTokenizer) PullWhitespaceAndComments() { 114 | for { 115 | p.PullWhitespace() 116 | for p.IsStartOfComment() { 117 | p.PullComment() 118 | } 119 | 120 | if !p.IsWhitespace() { 121 | break 122 | } 123 | } 124 | } 125 | 126 | func (p *HoconTokenizer) PullRestOfLine() string { 127 | buf := bytes.NewBuffer(nil) 128 | 129 | for !p.EOF() { 130 | c := p.TakeOne() 131 | if c == '\n' { 132 | break 133 | } 134 | 135 | if c == '\r' { 136 | continue 137 | } 138 | if err := buf.WriteByte(c); err != nil { 139 | panic(err) 140 | } 141 | } 142 | 143 | return strings.TrimSpace(buf.String()) 144 | } 145 | 146 | func (p *HoconTokenizer) PullNext() (token *Token) { 147 | 148 | p.PullWhitespaceAndComments() 149 | if p.IsDot() { 150 | token = p.PullDot() 151 | } else if p.IsObjectStart() { 152 | token = p.PullStartOfObject() 153 | } else if p.IsEndOfObject() { 154 | token = p.PullEndOfObject() 155 | } else if p.IsAssignment() { 156 | token = p.PullAssignment() 157 | } else if p.IsPlusAssignment() { 158 | token = p.PullPlusAssignment() 159 | } else if p.IsInclude() { 160 | token = p.PullInclude() 161 | } else if p.isStartOfQuotedKey() { 162 | token = p.PullQuotedKey() 163 | } else if p.IsUnquotedKeyStart() { 164 | token = p.PullUnquotedKey() 165 | } else if p.IsArrayStart() { 166 | token = p.PullArrayStart() 167 | } else if p.IsArrayEnd() { 168 | token = p.PullArrayEnd() 169 | } else if p.EOF() { 170 | token = NewToken(TokenTypeEoF) 171 | } 172 | 173 | if token != nil { 174 | return 175 | } 176 | 177 | panic(fmt.Errorf("unknown token, offset: %d", p.index)) 178 | } 179 | 180 | func (p *HoconTokenizer) isStartOfQuotedKey() bool { 181 | return p.Matches("\"") 182 | } 183 | 184 | func (p *HoconTokenizer) PullArrayEnd() *Token { 185 | p.TakeOne() 186 | return NewToken(TokenTypeArrayEnd) 187 | } 188 | 189 | func (p *HoconTokenizer) IsArrayEnd() bool { 190 | return p.Matches("]") 191 | } 192 | 193 | func (p *HoconTokenizer) IsArrayStart() bool { 194 | return p.Matches("[") 195 | } 196 | 197 | func (p *HoconTokenizer) PullArrayStart() *Token { 198 | p.TakeOne() 199 | return NewToken(TokenTypeArrayStart) 200 | } 201 | 202 | func (p *HoconTokenizer) PullDot() *Token { 203 | p.TakeOne() 204 | return NewToken(TokenTypeDot) 205 | } 206 | 207 | func (p *HoconTokenizer) PullComma() *Token { 208 | p.TakeOne() 209 | return NewToken(TokenTypeComma) 210 | } 211 | 212 | func (p *HoconTokenizer) PullNewline() *Token { 213 | p.Take(2) 214 | return NewToken(TokenTypeNewline) 215 | } 216 | 217 | func (p *HoconTokenizer) PullStartOfObject() *Token { 218 | p.TakeOne() 219 | return NewToken(TokenTypeObjectStart) 220 | } 221 | 222 | func (p *HoconTokenizer) PullEndOfObject() *Token { 223 | p.TakeOne() 224 | return NewToken(TokenTypeObjectEnd) 225 | } 226 | 227 | func (p *HoconTokenizer) PullAssignment() *Token { 228 | p.TakeOne() 229 | return NewToken(TokenTypeAssign) 230 | } 231 | 232 | func (p *HoconTokenizer) PullPlusAssignment() *Token { 233 | p.Take(2) 234 | return NewToken(TokenTypePlusAssign) 235 | } 236 | 237 | func (p *HoconTokenizer) IsComma() bool { 238 | return p.Matches(",") 239 | } 240 | 241 | func (p *HoconTokenizer) IsNewline() bool { 242 | return p.Matches(`\n`) 243 | } 244 | 245 | func (p *HoconTokenizer) IsDot() bool { 246 | return p.Matches(".") 247 | } 248 | 249 | func (p *HoconTokenizer) IsObjectStart() bool { 250 | return p.Matches("{") 251 | } 252 | 253 | func (p *HoconTokenizer) IsEndOfObject() bool { 254 | return p.Matches("}") 255 | } 256 | 257 | func (p *HoconTokenizer) IsAssignment() bool { 258 | return p.MatchesMore([]string{"=", ":"}) 259 | } 260 | 261 | func (p *HoconTokenizer) IsPlusAssignment() bool { 262 | return p.Matches("+=") 263 | } 264 | 265 | func (p *HoconTokenizer) IsStartOfQuotedText() bool { 266 | return p.Matches("\"") 267 | } 268 | 269 | func (p *HoconTokenizer) IsStartOfTripleQuotedText() bool { 270 | return p.Matches("\"\"\"") 271 | } 272 | 273 | func (p *HoconTokenizer) PullComment() *Token { 274 | p.PullRestOfLine() 275 | return NewToken(TokenTypeComment) 276 | } 277 | 278 | func (p *HoconTokenizer) PullUnquotedKey() *Token { 279 | buf := bytes.NewBuffer(nil) 280 | for !p.EOF() && p.IsUnquotedKey() { 281 | if err := buf.WriteByte(p.TakeOne()); err != nil { 282 | panic(err) 283 | } 284 | } 285 | 286 | return DefaultToken.Key(strings.TrimSpace(buf.String())) 287 | } 288 | 289 | func (p *HoconTokenizer) IsUnquotedKey() bool { 290 | return !p.EOF() && !p.IsStartOfComment() && (strings.IndexByte(HoconNotInUnquotedKey, p.Peek()) == -1) 291 | } 292 | 293 | func (p *HoconTokenizer) IsUnquotedKeyStart() bool { 294 | return !p.EOF() && !p.IsWhitespace() && !p.IsStartOfComment() && (strings.IndexByte(HoconNotInUnquotedKey, p.Peek()) == -1) 295 | } 296 | 297 | func (p *HoconTokenizer) IsWhitespace() bool { 298 | return isWhitespace(p.Peek()) 299 | } 300 | 301 | func (p *HoconTokenizer) IsWhitespaceOrComment() bool { 302 | return p.IsWhitespace() || p.IsStartOfComment() 303 | } 304 | 305 | func (p *HoconTokenizer) PullTripleQuotedText() *Token { 306 | buf := bytes.NewBuffer(nil) 307 | p.Take(3) 308 | for !p.EOF() && !p.Matches("\"\"\"") { 309 | if err := buf.WriteByte(p.Peek()); err != nil { 310 | panic(err) 311 | } 312 | p.TakeOne() 313 | } 314 | p.Take(3) 315 | return DefaultToken.LiteralValue(buf.String()) 316 | } 317 | 318 | func (p *HoconTokenizer) PullQuotedText() *Token { 319 | buf := bytes.NewBuffer(nil) 320 | p.TakeOne() 321 | for !p.EOF() && !p.Matches("\"") { 322 | if p.Matches("\\") { 323 | if _, err := buf.WriteString(p.pullEscapeSequence()); err != nil { 324 | panic(err) 325 | } 326 | } else { 327 | if err := buf.WriteByte(p.Peek()); err != nil { 328 | panic(err) 329 | } 330 | p.TakeOne() 331 | } 332 | } 333 | p.TakeOne() 334 | return DefaultToken.LiteralValue(buf.String()) 335 | } 336 | 337 | func (p *HoconTokenizer) PullQuotedKey() *Token { 338 | buf := bytes.NewBuffer(nil) 339 | p.TakeOne() 340 | for !p.EOF() && !p.Matches("\"") { 341 | if p.Matches("\\") { 342 | if _, err := buf.WriteString(p.pullEscapeSequence()); err != nil { 343 | panic(err) 344 | } 345 | } else { 346 | if err := buf.WriteByte(p.Peek()); err != nil { 347 | panic(err) 348 | } 349 | p.TakeOne() 350 | } 351 | } 352 | p.TakeOne() 353 | return DefaultToken.Key(buf.String()) 354 | } 355 | 356 | func (p *HoconTokenizer) PullInclude() *Token { 357 | p.Take(len("include")) 358 | p.PullWhitespaceAndComments() 359 | rest := p.PullQuotedText() 360 | unQuote := rest.value 361 | return DefaultToken.Include(unQuote) 362 | } 363 | 364 | func (p *HoconTokenizer) pullEscapeSequence() string { 365 | p.TakeOne() 366 | escaped := p.TakeOne() 367 | switch escaped { 368 | case '"': 369 | return ("\"") 370 | case '\\': 371 | return ("\\") 372 | case '/': 373 | return ("/") 374 | case 'b': 375 | return ("\b") 376 | case 'f': 377 | return ("\f") 378 | case 'n': 379 | return ("\n") 380 | case 'r': 381 | return ("\r") 382 | case 't': 383 | return ("\t") 384 | case 'u': 385 | utf8Code := "\\u" + strings.ToLower(p.Take(4)) 386 | utf8Str := "" 387 | if _, err := fmt.Sscanf(utf8Code, "%s", &utf8Str); err != nil { 388 | panic(err) 389 | } 390 | return utf8Str 391 | default: 392 | panic(fmt.Errorf("Unknown escape code: %v", escaped)) 393 | } 394 | } 395 | 396 | func (p *HoconTokenizer) IsStartOfComment() bool { 397 | return p.MatchesMore([]string{"#", "//"}) 398 | } 399 | 400 | func (p *HoconTokenizer) PullValue() *Token { 401 | if p.IsObjectStart() { 402 | return p.PullStartOfObject() 403 | } 404 | 405 | if p.IsStartOfTripleQuotedText() { 406 | return p.PullTripleQuotedText() 407 | } 408 | 409 | if p.IsStartOfQuotedText() { 410 | return p.PullQuotedText() 411 | } 412 | 413 | if p.isUnquotedText() { 414 | return p.pullUnquotedText() 415 | } 416 | 417 | if p.IsArrayStart() { 418 | return p.PullArrayStart() 419 | } 420 | 421 | if p.IsArrayEnd() { 422 | return p.PullArrayEnd() 423 | } 424 | 425 | if p.IsSubstitutionStart() { 426 | return p.pullSubstitution() 427 | } 428 | 429 | panic(fmt.Errorf("Expected value: Null literal, Array, Quoted Text, Unquoted Text, Triple quoted Text, Object or End of array")) 430 | } 431 | 432 | func (p *HoconTokenizer) IsSubstitutionStart() bool { 433 | return p.MatchesMore([]string{"${", "${?"}) 434 | } 435 | 436 | func (p *HoconTokenizer) IsInclude() bool { 437 | p.Push() 438 | defer func() { 439 | recover() 440 | p.Pop() 441 | }() 442 | if p.Matches("include") { 443 | p.Take(len("include")) 444 | if p.IsWhitespaceOrComment() { 445 | p.PullWhitespaceAndComments() 446 | if p.IsStartOfQuotedText() { 447 | p.PullQuotedText() 448 | return true 449 | } 450 | } 451 | } 452 | 453 | return false 454 | } 455 | 456 | func (p *HoconTokenizer) pullSubstitution() *Token { 457 | buf := bytes.NewBuffer(nil) 458 | p.Take(2) 459 | isOptional := false 460 | if p.Peek() == '?' { 461 | p.TakeOne() 462 | isOptional = true 463 | } 464 | 465 | for !p.EOF() && p.isUnquotedText() { 466 | if err := buf.WriteByte(p.TakeOne()); err != nil { 467 | panic(err) 468 | } 469 | } 470 | p.TakeOne() 471 | return DefaultToken.Substitution(buf.String(), isOptional) 472 | } 473 | 474 | func (p *HoconTokenizer) IsSpaceOrTab() bool { 475 | return p.MatchesMore([]string{" ", "\t"}) 476 | } 477 | 478 | func (p *HoconTokenizer) IsStartSimpleValue() bool { 479 | if p.IsSpaceOrTab() { 480 | return true 481 | } 482 | 483 | if p.isUnquotedText() { 484 | return true 485 | } 486 | 487 | return false 488 | } 489 | 490 | func (p *HoconTokenizer) PullSpaceOrTab() *Token { 491 | buf := bytes.NewBuffer(nil) 492 | for p.IsSpaceOrTab() { 493 | if err := buf.WriteByte(p.TakeOne()); err != nil { 494 | panic(err) 495 | } 496 | } 497 | return DefaultToken.LiteralValue(buf.String()) 498 | } 499 | 500 | func (p *HoconTokenizer) pullUnquotedText() *Token { 501 | buf := bytes.NewBuffer(nil) 502 | for !p.EOF() && p.isUnquotedText() { 503 | if err := buf.WriteByte(p.TakeOne()); err != nil { 504 | panic(err) 505 | } 506 | } 507 | return DefaultToken.LiteralValue(buf.String()) 508 | } 509 | 510 | func (p *HoconTokenizer) isUnquotedText() bool { 511 | return !p.EOF() && !p.IsWhitespace() && !p.IsStartOfComment() && strings.IndexByte(HoconNotInUnquotedText, p.Peek()) == -1 512 | } 513 | 514 | func (p *HoconTokenizer) PullSimpleValue() *Token { 515 | if p.IsSpaceOrTab() { 516 | return p.PullSpaceOrTab() 517 | } 518 | 519 | if p.isUnquotedText() { 520 | return p.pullUnquotedText() 521 | } 522 | panic("No simple value found") 523 | } 524 | 525 | func (p *HoconTokenizer) isValue() bool { 526 | 527 | if p.IsArrayStart() || 528 | p.IsObjectStart() || 529 | p.IsStartOfTripleQuotedText() || 530 | p.IsSubstitutionStart() || 531 | p.IsStartOfQuotedText() || 532 | p.isUnquotedText() { 533 | return true 534 | } 535 | return false 536 | } 537 | 538 | /* 539 | SPACE (\u0020) 540 | NO-BREAK SPACE (\u00A0) 541 | OGHAM SPACE MARK (\u1680) 542 | EN QUAD (\u2000) 543 | EM QUAD (\u2001) 544 | EN SPACE (\u2002) 545 | EM SPACE (\u2003) 546 | THREE-PER-EM SPACE (\u2004) 547 | FOUR-PER-EM SPACE (\u2005) 548 | SIX-PER-EM SPACE (\u2006) 549 | FIGURE SPACE (\u2007) 550 | PUNCTUATION SPACE (\u2008) 551 | THIN SPACE (\u2009) 552 | HAIR SPACE (\u200A) 553 | NARROW NO-BREAK SPACE (\u202F) 554 | MEDIUM MATHEMATICAL SPACE (\u205F) 555 | and IDEOGRAPHIC SPACE (\u3000) 556 | Byte Order Mark (\uFEFF) 557 | */ 558 | func isWhitespace(c byte) bool { 559 | str := string(c) 560 | 561 | switch str { 562 | case " ", "\t", "\n", "\u000B", "\u000C", 563 | "\u000D", "\u00A0", "\u1680", "\u2000", 564 | "\u2001", "\u2002", "\u2003", "\u2004", 565 | "\u2005", "\u2006", "\u2007", "\u2008", 566 | "\u2009", "\u200A", "\u202F", "\u205F", 567 | "\u2060", "\u3000", "\uFEFF": 568 | return true 569 | } 570 | return false 571 | } 572 | -------------------------------------------------------------------------------- /hocon/value.go: -------------------------------------------------------------------------------- 1 | package hocon 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | var ( 13 | _Num1000 = big.NewInt(1000) 14 | _Num1024 = big.NewInt(1024) 15 | ) 16 | 17 | var ( 18 | _IByte = big.NewInt(1) 19 | _KByte = (&big.Int{}).Mul(_IByte, _Num1000) 20 | _MByte = (&big.Int{}).Mul(_KByte, _Num1000) 21 | _GByte = (&big.Int{}).Mul(_MByte, _Num1000) 22 | _TByte = (&big.Int{}).Mul(_GByte, _Num1000) 23 | _PByte = (&big.Int{}).Mul(_TByte, _Num1000) 24 | _EByte = (&big.Int{}).Mul(_PByte, _Num1000) 25 | _ZByte = (&big.Int{}).Mul(_EByte, _Num1000) 26 | _YByte = (&big.Int{}).Mul(_ZByte, _Num1000) 27 | ) 28 | 29 | var ( 30 | _Byte = big.NewInt(1) 31 | _KiByte = (&big.Int{}).Mul(_Byte, _Num1024) 32 | _MiByte = (&big.Int{}).Mul(_KiByte, _Num1024) 33 | _GiByte = (&big.Int{}).Mul(_MiByte, _Num1024) 34 | _TiByte = (&big.Int{}).Mul(_GiByte, _Num1024) 35 | _PiByte = (&big.Int{}).Mul(_TiByte, _Num1024) 36 | _EiByte = (&big.Int{}).Mul(_PiByte, _Num1024) 37 | _ZiByte = (&big.Int{}).Mul(_EiByte, _Num1024) 38 | _YiByte = (&big.Int{}).Mul(_ZiByte, _Num1024) 39 | ) 40 | 41 | type HoconValue struct { 42 | values []HoconElement 43 | oldValue *HoconValue 44 | } 45 | 46 | func NewHoconValue() *HoconValue { 47 | return &HoconValue{} 48 | } 49 | 50 | func (p *HoconValue) IsEmpty() bool { 51 | if len(p.values) == 0 { 52 | return true 53 | } 54 | 55 | if first, ok := p.values[0].(*HoconObject); ok { 56 | if len(first.items) == 0 { 57 | return true 58 | } 59 | } 60 | return false 61 | } 62 | 63 | func (p *HoconValue) AtKey(key string) *HoconRoot { 64 | obj := NewHoconObject() 65 | obj.GetOrCreateKey(key) 66 | obj.items[key] = p 67 | r := NewHoconValue() 68 | r.AppendValue(obj) 69 | return NewHoconRoot(r) 70 | } 71 | 72 | func (p *HoconValue) IsString() bool { 73 | strCount := 0 74 | 75 | for _, v := range p.values { 76 | v = p.topValueOfSub(v) 77 | if v.IsString() { 78 | strCount += 1 79 | } 80 | } 81 | 82 | if strCount > 0 && strCount == len(p.values) { 83 | return true 84 | } 85 | 86 | return false 87 | } 88 | 89 | func (p *HoconValue) topValueOfSub(v interface{}) HoconElement { 90 | if v == nil { 91 | return nil 92 | } 93 | 94 | if sub, ok := v.(*HoconSubstitution); ok { 95 | if sub.ResolvedValue != nil && sub.ResolvedValue.oldValue != nil { 96 | return sub.ResolvedValue.oldValue 97 | } 98 | if sub.ResolvedValue == nil && p.oldValue != nil { 99 | return p.oldValue 100 | } 101 | } 102 | 103 | return v.(HoconElement) 104 | } 105 | 106 | func (p *HoconValue) concatString() string { 107 | concat := "" 108 | for _, v := range p.values { 109 | v = p.topValueOfSub(v) 110 | concat += v.GetString() 111 | } 112 | 113 | if concat == "null" { 114 | concat = "" 115 | } 116 | 117 | return strings.TrimSpace(concat) 118 | } 119 | 120 | func (p *HoconValue) GetByteSize() *big.Int { 121 | res := p.GetString() 122 | groups, matched := findStringSubmatchMap(res, `^(?P([0-9]+(\.[0-9]+)?))\s*(?P(B|b|byte|bytes|kB|kilobyte|kilobytes|MB|megabyte|megabytes|GB|gigabyte|gigabytes|TB|terabyte|terabytes|PB|petabyte|petabytes|EB|exabyte|exabytes|ZB|zettabyte|zettabytes|YB|yottabyte|yottabytes|K|k|Ki|KiB|kibibyte|kibibytes|M|m|Mi|MiB|mebibyte|mebibytes|G|g|Gi|GiB|gibibyte|gibibytes|T|t|Ti|TiB|tebibyte|tebibytes|P|p|Pi|PiB|pebibyte|pebibytes|E|e|Ei|EiB|exbibyte|exbibytes|Z|z|Zi|ZiB|zebibyte|zebibytes|Y|y|Yi|YiB|yobibyte|yobibytes))$`) 123 | 124 | if matched { 125 | u := groups["unit"] 126 | strV := groups["value"] 127 | 128 | v := int64(parsePositiveValue(strV)) 129 | 130 | bigInt := big.NewInt(v) 131 | 132 | switch u { 133 | case "B", "b", "byte", "bytes": 134 | return (&big.Int{}).Mul(bigInt, _IByte) 135 | case "kB", "kilobyte", "kilobytes": 136 | return (&big.Int{}).Mul(bigInt, _KByte) 137 | case "MB", "megabyte", "megabytes": 138 | return (&big.Int{}).Mul(bigInt, _MByte) 139 | case "GB", "gigabyte", "gigabytes": 140 | return (&big.Int{}).Mul(bigInt, _GByte) 141 | case "TB", "terabyte", "terabytes": 142 | return (&big.Int{}).Mul(bigInt, _TByte) 143 | case "PB", "petabyte", "petabytes": 144 | return (&big.Int{}).Mul(bigInt, _PByte) 145 | case "EB", "exabyte", "exabytes": 146 | return (&big.Int{}).Mul(bigInt, _EByte) 147 | case "ZB", "zettabyte", "zettabytes": 148 | return (&big.Int{}).Mul(bigInt, _ZByte) 149 | case "YB", "yottabyte", "yottabytes": 150 | return (&big.Int{}).Mul(bigInt, _YByte) 151 | case "K", "k", "Ki", "KiB", "kibibyte", "kibibytes": 152 | return (&big.Int{}).Mul(bigInt, _KiByte) 153 | case "M", "m", "Mi", "MiB", "mebibyte", "mebibytes": 154 | return (&big.Int{}).Mul(bigInt, _MiByte) 155 | case "G", "g", "Gi", "GiB", "gibibyte", "gibibytes": 156 | return (&big.Int{}).Mul(bigInt, _GiByte) 157 | case "T", "t", "Ti", "TiB", "tebibyte", "tebibytes": 158 | return (&big.Int{}).Mul(bigInt, _TiByte) 159 | case "P", "p", "Pi", "PiB", "pebibyte", "pebibytes": 160 | return (&big.Int{}).Mul(bigInt, _PiByte) 161 | case "E", "e", "Ei", "EiB", "exbibyte", "exbibytes": 162 | return (&big.Int{}).Mul(bigInt, _EiByte) 163 | case "Z", "z", "Zi", "ZiB", "zebibyte", "zebibytes": 164 | return (&big.Int{}).Mul(bigInt, _ZiByte) 165 | case "Y", "y", "Yi", "YiB", "yobibyte", "yobibytes": 166 | return (&big.Int{}).Mul(bigInt, _YiByte) 167 | } 168 | } 169 | 170 | panic("unknown byte size unit") 171 | 172 | return nil 173 | } 174 | 175 | func (p *HoconValue) String() string { 176 | return p.ToString(0) 177 | } 178 | 179 | func (p *HoconValue) ToString(indent int) string { 180 | if p.IsString() { 181 | return p.quoteIfNeeded(p.GetString()) 182 | } 183 | 184 | if p.IsObject() { 185 | tmp := strings.Repeat(" ", indent*2) 186 | return fmt.Sprintf("{\r\n%s%s}", p.GetObject().ToString(indent+1), tmp) 187 | } 188 | 189 | if p.IsArray() { 190 | var strs []string 191 | for _, item := range p.GetArray() { 192 | strs = append(strs, item.ToString(indent+1)) 193 | } 194 | return "[" + strings.Join(strs, ",") + "]" 195 | } 196 | 197 | return "<>" 198 | } 199 | 200 | func (p *HoconValue) GetObject() *HoconObject { 201 | if len(p.values) == 0 { 202 | return nil 203 | } 204 | 205 | raw := p.values[0] 206 | if o, ok := raw.(*HoconObject); ok { 207 | return o 208 | } 209 | 210 | raw = p.topValueOfSub(raw) 211 | 212 | if s, ok := raw.(*HoconSubstitution); ok { 213 | if s.ResolvedValue == nil { 214 | return nil 215 | } 216 | } 217 | 218 | if sub, ok := raw.(MightBeAHoconObject); ok { 219 | if sub != nil && sub.IsObject() { 220 | 221 | return sub.GetObject() 222 | } 223 | } 224 | 225 | return nil 226 | } 227 | 228 | func (p *HoconValue) IsObject() bool { 229 | return p.GetObject() != nil 230 | } 231 | 232 | func (p *HoconValue) AppendValue(value HoconElement) { 233 | p.values = append(p.values, value) 234 | } 235 | 236 | func (p *HoconValue) Clear() { 237 | p.values = []HoconElement{} 238 | } 239 | 240 | func (p *HoconValue) NewValue(value HoconElement) { 241 | p.values = []HoconElement{} 242 | p.values = append(p.values, value) 243 | } 244 | 245 | func (p *HoconValue) GetBoolean() bool { 246 | v := strings.ToLower(p.GetString()) 247 | switch v { 248 | case "on", "true", "yes": 249 | return true 250 | case "off", "false", "no": 251 | return false 252 | default: 253 | panic("Unknown boolean format: " + v) 254 | } 255 | } 256 | 257 | func (p *HoconValue) GetString() string { 258 | if p.IsString() { 259 | return p.concatString() 260 | } 261 | return "" 262 | } 263 | 264 | func (p *HoconValue) GetFloat64() float64 { 265 | val, err := strconv.ParseFloat(p.GetString(), 64) 266 | if err != nil { 267 | panic(err) 268 | } 269 | return val 270 | } 271 | 272 | func (p *HoconValue) GetFloat32() float32 { 273 | val, err := strconv.ParseFloat(p.GetString(), 32) 274 | if err != nil { 275 | panic(err) 276 | } 277 | return float32(val) 278 | } 279 | 280 | func (p *HoconValue) GetInt64() int64 { 281 | val, err := strconv.ParseInt(p.GetString(), 10, 64) 282 | if err != nil { 283 | panic(err) 284 | } 285 | return val 286 | } 287 | 288 | func (p *HoconValue) GetInt32() int32 { 289 | val, err := strconv.ParseInt(p.GetString(), 10, 32) 290 | if err != nil { 291 | panic(err) 292 | } 293 | return int32(val) 294 | } 295 | 296 | func (p *HoconValue) GetByte() byte { 297 | val, err := strconv.ParseUint(p.GetString(), 10, 8) 298 | if err != nil { 299 | panic(err) 300 | } 301 | return byte(val) 302 | } 303 | 304 | func (p *HoconValue) GetByteList() []byte { 305 | arrs := p.GetArray() 306 | var items []byte 307 | for _, v := range arrs { 308 | items = append(items, v.GetByte()) 309 | } 310 | return items 311 | } 312 | 313 | func (p *HoconValue) GetInt32List() []int32 { 314 | arrs := p.GetArray() 315 | var items []int32 316 | for _, v := range arrs { 317 | items = append(items, v.GetInt32()) 318 | } 319 | return items 320 | } 321 | 322 | func (p *HoconValue) GetInt64List() []int64 { 323 | arrs := p.GetArray() 324 | var items []int64 325 | for _, v := range arrs { 326 | items = append(items, v.GetInt64()) 327 | } 328 | return items 329 | } 330 | 331 | func (p *HoconValue) GetBooleanList() []bool { 332 | arrs := p.GetArray() 333 | var items []bool 334 | for _, v := range arrs { 335 | items = append(items, v.GetBoolean()) 336 | } 337 | return items 338 | } 339 | 340 | func (p *HoconValue) GetFloat32List() []float32 { 341 | arrs := p.GetArray() 342 | var items []float32 343 | for _, v := range arrs { 344 | items = append(items, v.GetFloat32()) 345 | } 346 | return items 347 | } 348 | 349 | func (p *HoconValue) GetFloat64List() []float64 { 350 | arrs := p.GetArray() 351 | var items []float64 352 | for _, v := range arrs { 353 | items = append(items, v.GetFloat64()) 354 | } 355 | return items 356 | } 357 | 358 | func (p *HoconValue) GetStringList() []string { 359 | arrs := p.GetArray() 360 | var items []string 361 | for _, v := range arrs { 362 | items = append(items, v.GetString()) 363 | } 364 | return items 365 | } 366 | 367 | func (p *HoconValue) GetArray() []*HoconValue { 368 | var arrs []*HoconValue 369 | 370 | if len(p.values) == 0 { 371 | return arrs 372 | } 373 | 374 | for _, v := range p.values { 375 | v = p.topValueOfSub(v) 376 | if v.IsArray() { 377 | arrs = append(arrs, v.GetArray()...) 378 | } 379 | } 380 | 381 | return arrs 382 | } 383 | 384 | func (p *HoconValue) GetChildObject(key string) *HoconValue { 385 | obj := p.GetObject() 386 | if obj == nil { 387 | return nil 388 | } 389 | return obj.GetKey(key) 390 | } 391 | 392 | func (p *HoconValue) IsArray() bool { 393 | return p.GetArray() != nil 394 | } 395 | 396 | func (p *HoconValue) GetTimeDuration(allowInfinite bool) time.Duration { 397 | res := p.GetString() 398 | groups, matched := findStringSubmatchMap(res, `^(?P([0-9]+(\.[0-9]+)?))\s*(?P(nanoseconds|nanosecond|nanos|nano|ns|microseconds|microsecond|micros|micro|us|milliseconds|millisecond|millis|milli|ms|seconds|second|s|minutes|minute|m|hours|hour|h|days|day|d))$`) 399 | 400 | if matched { 401 | u := groups["unit"] 402 | strV := groups["value"] 403 | v := parsePositiveValue(strV) 404 | 405 | switch u { 406 | case "nanoseconds", "nanosecond", "nanos", "nano", "ns": 407 | return time.Duration(float64(time.Nanosecond) * v) 408 | case "microseconds", "microsecond", "micros", "micro": 409 | return time.Duration(float64(time.Microsecond) * v) 410 | case "milliseconds", "millisecond", "millis", "milli", "ms": 411 | return time.Duration(float64(time.Millisecond) * v) 412 | case "seconds", "second", "s": 413 | return time.Duration(float64(time.Second) * v) 414 | case "minutes", "minute", "m": 415 | return time.Duration(float64(time.Minute) * v) 416 | case "hours", "hour", "h": 417 | return time.Duration(float64(time.Hour) * v) 418 | case "days", "day", "d": 419 | return time.Duration(float64(time.Hour*24) * v) 420 | } 421 | } 422 | 423 | if strings.ToLower(res) == "infinite" { 424 | if allowInfinite { 425 | return time.Duration(-1) 426 | } 427 | panic("infinite time duration not allowed") 428 | } 429 | 430 | return time.Duration(float64(time.Millisecond) * parsePositiveValue(res)) 431 | } 432 | 433 | func (p *HoconValue) quoteIfNeeded(text string) string { 434 | if len(text) == 0 { 435 | return "\"\"" 436 | } 437 | 438 | if strings.IndexByte(text, ' ') >= 0 || 439 | strings.IndexByte(text, '\t') >= 0 { 440 | return "\"" + text + "\"" 441 | } 442 | 443 | return text 444 | } 445 | 446 | func findStringSubmatchMap(s, exp string) (map[string]string, bool) { 447 | reg := regexp.MustCompile(exp) 448 | captures := make(map[string]string) 449 | 450 | match := reg.FindStringSubmatch(s) 451 | if match == nil { 452 | return captures, false 453 | } 454 | 455 | for i, name := range reg.SubexpNames() { 456 | if i == 0 || name == "" { 457 | continue 458 | } 459 | captures[name] = match[i] 460 | } 461 | return captures, true 462 | } 463 | 464 | func parsePositiveValue(v string) float64 { 465 | value, err := strconv.ParseFloat(v, 64) 466 | if err != nil { 467 | panic(err) 468 | } 469 | if value < 0 { 470 | panic("Expected a positive value instead of " + v) 471 | } 472 | return value 473 | } 474 | -------------------------------------------------------------------------------- /pigeon.conf: -------------------------------------------------------------------------------- 1 | 2 | #################################### 3 | # Akka Actor Reference Config File # 4 | #################################### 5 | 6 | # This is the reference config file that contains all the default settings. 7 | # Make your edits/overrides in your application.conf. 8 | 9 | akka { 10 | # Akka version, checked against the runtime version of Akka. 11 | version = "0.0.1 Akka" 12 | 13 | # Home directory of Akka, modules in the deploy directory will be loaded 14 | home = "" 15 | 16 | # Loggers to register at boot time (akka.event.Logging$DefaultLogger logs 17 | # to STDOUT) 18 | loggers = ["Akka.Event.DefaultLogger"] 19 | 20 | # Specifies the default loggers dispatcher 21 | loggers-dispatcher = "akka.actor.default-dispatcher" 22 | 23 | # Loggers are created and registered synchronously during ActorSystem 24 | # start-up, and since they are actors, this timeout is used to bound the 25 | # waiting time 26 | logger-startup-timeout = 5s 27 | 28 | # Log level used by the configured loggers (see "loggers") as soon 29 | # as they have been started; before that, see "stdout-loglevel" 30 | # Options: OFF, ERROR, WARNING, INFO, DEBUG 31 | loglevel = "INFO" 32 | 33 | # Suppresses warning about usage of the default (JSON.NET) serializer 34 | # which is going to be obsoleted at v1.5 35 | suppress-json-serializer-warning = off 36 | 37 | # Log level for the very basic logger activated during AkkaApplication startup 38 | # Options: OFF, ERROR, WARNING, INFO, DEBUG 39 | stdout-loglevel = "WARNING" 40 | 41 | # Log the complete configuration at INFO level when the actor system is started. 42 | # This is useful when you are uncertain of what configuration is used. 43 | log-config-on-start = off 44 | 45 | # Log at info level when messages are sent to dead letters. 46 | # Possible values: 47 | # on: all dead letters are logged 48 | # off: no logging of dead letters 49 | # n: positive integer, number of dead letters that will be logged 50 | log-dead-letters = 10 51 | 52 | # Possibility to turn off logging of dead letters while the actor system 53 | # is shutting down. Logging is only done when enabled by 'log-dead-letters' 54 | # setting. 55 | log-dead-letters-during-shutdown = on 56 | 57 | # List FQCN of extensions which shall be loaded at actor system startup. 58 | # Should be on the format: 'extensions = ["foo", "bar"]' etc. 59 | # See the Akka Documentation for more info about Extensions 60 | extensions = [] 61 | 62 | # Toggles whether threads created by this ActorSystem should be daemons or not 63 | daemonic = off 64 | 65 | # THIS DOES NOT APPLY TO .NET 66 | # 67 | # JVM shutdown, System.exit(-1), in case of a fatal error, 68 | # such as OutOfMemoryError 69 | #jvm-exit-on-fatal-error = on 70 | 71 | actor { 72 | 73 | # FQCN of the ActorRefProvider to be used; the below is the built-in default, 74 | # another one is akka.remote.RemoteActorRefProvider in the akka-remote bundle. 75 | provider = "Akka.Actor.LocalActorRefProvider" 76 | 77 | # The guardian "/user" will use this class to obtain its supervisorStrategy. 78 | # It needs to be a subclass of Akka.Actor.SupervisorStrategyConfigurator. 79 | # In addition to the default there is Akka.Actor.StoppingSupervisorStrategy. 80 | guardian-supervisor-strategy = "Akka.Actor.DefaultSupervisorStrategy" 81 | 82 | # Timeout for ActorSystem.actorOf 83 | creation-timeout = 20s 84 | 85 | # Frequency with which stopping actors are prodded in case they had to be 86 | # removed from their parents 87 | reaper-interval = 5 88 | 89 | # Serializes and deserializes (non-primitive) messages to ensure immutability, 90 | # this is only intended for testing. 91 | serialize-messages = off 92 | 93 | # Serializes and deserializes creators (in Props) to ensure that they can be 94 | # sent over the network, this is only intended for testing. Purely local deployments 95 | # as marked with deploy.scope == LocalScope are exempt from verification. 96 | serialize-creators = off 97 | 98 | # Timeout for send operations to top-level actors which are in the process 99 | # of being started. This is only relevant if using a bounded mailbox or the 100 | # CallingThreadDispatcher for a top-level actor. 101 | unstarted-push-timeout = 10s 102 | 103 | # Default timeout for IActorRef.Ask. 104 | ask-timeout = infinite 105 | 106 | 107 | # THIS DOES NOT APPLY TO .NET 108 | # 109 | typed { 110 | # Default timeout for typed actor methods with non-void return type 111 | timeout = 5 112 | } 113 | 114 | inbox { 115 | inbox-size = 1000, 116 | default-timeout = 5s 117 | } 118 | 119 | # Mapping between ´deployment.router' short names to fully qualified class names 120 | router.type-mapping { 121 | from-code = "Akka.Routing.NoRouter" 122 | round-robin-pool = "Akka.Routing.RoundRobinPool" 123 | round-robin-group = "Akka.Routing.RoundRobinGroup" 124 | random-pool = "Akka.Routing.RandomPool" 125 | random-group = "Akka.Routing.RandomGroup" 126 | balancing-pool = "Akka.Routing.BalancingPool" 127 | smallest-mailbox-pool = "Akka.Routing.SmallestMailboxPool" 128 | broadcast-pool = "Akka.Routing.BroadcastPool" 129 | broadcast-group = "Akka.Routing.BroadcastGroup" 130 | scatter-gather-pool = "Akka.Routing.ScatterGatherFirstCompletedPool" 131 | scatter-gather-group = "Akka.Routing.ScatterGatherFirstCompletedGroup" 132 | consistent-hashing-pool = "Akka.Routing.ConsistentHashingPool" 133 | consistent-hashing-group = "Akka.Routing.ConsistentHashingGroup" 134 | } 135 | 136 | deployment { 137 | 138 | # deployment id pattern - on the format: /parent/child etc. 139 | default { 140 | 141 | # The id of the dispatcher to use for this actor. 142 | # If undefined or empty the dispatcher specified in code 143 | # (Props.withDispatcher) is used, or default-dispatcher if not 144 | # specified at all. 145 | dispatcher = "" 146 | 147 | # The id of the mailbox to use for this actor. 148 | # If undefined or empty the default mailbox of the configured dispatcher 149 | # is used or if there is no mailbox configuration the mailbox specified 150 | # in code (Props.withMailbox) is used. 151 | # If there is a mailbox defined in the configured dispatcher then that 152 | # overrides this setting. 153 | mailbox = "" 154 | 155 | # routing (load-balance) scheme to use 156 | # - available: "from-code", "round-robin", "random", "smallest-mailbox", 157 | # "scatter-gather", "broadcast" 158 | # - or: Fully qualified class name of the router class. 159 | # The class must extend akka.routing.CustomRouterConfig and 160 | # have a public constructor with com.typesafe.config.Config 161 | # and optional akka.actor.DynamicAccess parameter. 162 | # - default is "from-code"; 163 | # Whether or not an actor is transformed to a Router is decided in code 164 | # only (Props.withRouter). The type of router can be overridden in the 165 | # configuration; specifying "from-code" means that the values specified 166 | # in the code shall be used. 167 | # In case of routing, the actors to be routed to can be specified 168 | # in several ways: 169 | # - nr-of-instances: will create that many children 170 | # - routees.paths: will route messages to these paths using ActorSelection, 171 | # i.e. will not create children 172 | # - resizer: dynamically resizable number of routees as specified in 173 | # resizer below 174 | router = "from-code" 175 | 176 | # number of children to create in case of a router; 177 | # this setting is ignored if routees.paths is given 178 | nr-of-instances = 1 179 | 180 | # within is the timeout used for routers containing future calls 181 | within = 5 s 182 | 183 | # number of virtual nodes per node for consistent-hashing router 184 | virtual-nodes-factor = 10 185 | 186 | routees { 187 | # Alternatively to giving nr-of-instances you can specify the full 188 | # paths of those actors which should be routed to. This setting takes 189 | # precedence over nr-of-instances 190 | paths = [] 191 | } 192 | 193 | # To use a dedicated dispatcher for the routees of the pool you can 194 | # define the dispatcher configuration inline with the property name 195 | # 'pool-dispatcher' in the deployment section of the router. 196 | # For example: 197 | # pool-dispatcher { 198 | # fork-join-executor.parallelism-min = 5 199 | # fork-join-executor.parallelism-max = 5 200 | # } 201 | 202 | # Routers with dynamically resizable number of routees; this feature is 203 | # enabled by including (parts of) this section in the deployment 204 | resizer { 205 | 206 | enabled = off 207 | 208 | # The fewest number of routees the router should ever have. 209 | lower-bound = 1 210 | 211 | # The most number of routees the router should ever have. 212 | # Must be greater than or equal to lower-bound. 213 | upper-bound = 10 214 | 215 | # Threshold used to evaluate if a routee is considered to be busy 216 | # (under pressure). Implementation depends on this value (default is 1). 217 | # 0: number of routees currently processing a message. 218 | # 1: number of routees currently processing a message has 219 | # some messages in mailbox. 220 | # > 1: number of routees with at least the configured pressure-threshold 221 | # messages in their mailbox. Note that estimating mailbox size of 222 | # default UnboundedMailbox is O(N) operation. 223 | pressure-threshold = 1 224 | 225 | # Percentage to increase capacity whenever all routees are busy. 226 | # For example, 0.2 would increase 20% (rounded up), i.e. if current 227 | # capacity is 6 it will request an increase of 2 more routees. 228 | rampup-rate = 0.2 229 | 230 | # Minimum fraction of busy routees before backing off. 231 | # For example, if this is 0.3, then we'll remove some routees only when 232 | # less than 30% of routees are busy, i.e. if current capacity is 10 and 233 | # 3 are busy then the capacity is unchanged, but if 2 or less are busy 234 | # the capacity is decreased. 235 | # Use 0.0 or negative to avoid removal of routees. 236 | backoff-threshold = 0.3 237 | 238 | # Fraction of routees to be removed when the resizer reaches the 239 | # backoffThreshold. 240 | # For example, 0.1 would decrease 10% (rounded up), i.e. if current 241 | # capacity is 9 it will request an decrease of 1 routee. 242 | backoff-rate = 0.1 243 | 244 | # Number of messages between resize operation. 245 | # Use 1 to resize before each message. 246 | messages-per-resize = 10 247 | } 248 | } 249 | } 250 | 251 | #used for GUI applications 252 | synchronized-dispatcher { 253 | type = "SynchronizedDispatcher" 254 | executor = "current-context-executor" 255 | throughput = 10 256 | } 257 | 258 | task-dispatcher { 259 | type = "TaskDispatcher" 260 | executor = "task-executor" 261 | throughput = 30 262 | } 263 | 264 | default-fork-join-dispatcher{ 265 | type = ForkJoinDispatcher 266 | executor = fork-join-executor 267 | throughput = 30 268 | dedicated-thread-pool{ #settings for Helios.DedicatedThreadPool 269 | thread-count = 3 #number of threads 270 | #deadlock-timeout = 3s #optional timeout for deadlock detection 271 | threadtype = background #values can be "background" or "foreground" 272 | } 273 | } 274 | 275 | default-dispatcher { 276 | # Must be one of the following 277 | # Dispatcher, PinnedDispatcher, or a FQCN to a class inheriting 278 | # MessageDispatcherConfigurator with a public constructor with 279 | # both com.typesafe.config.Config parameter and 280 | # akka.dispatch.DispatcherPrerequisites parameters. 281 | # PinnedDispatcher must be used together with executor=fork-join-executor. 282 | type = "Dispatcher" 283 | 284 | # Which kind of ExecutorService to use for this dispatcher 285 | # Valid options: 286 | # - "default-executor" requires a "default-executor" section 287 | # - "fork-join-executor" requires a "fork-join-executor" section 288 | # - "thread-pool-executor" requires a "thread-pool-executor" section 289 | # - "current-context-executor" requires a "current-context-executor" section 290 | # - "task-executor" requires a "task-executor" section 291 | # - A FQCN of a class extending ExecutorServiceConfigurator 292 | executor = "default-executor" 293 | 294 | # This will be used if you have set "executor = "default-executor"". 295 | # Uses the default .NET threadpool 296 | default-executor { 297 | 298 | } 299 | 300 | # Same as default executor 301 | thread-pool-executor{ 302 | } 303 | 304 | # This will be used if you have set "executor = "fork-join-executor"" 305 | # Underlying thread pool implementation is scala.concurrent.forkjoin.ForkJoinPool 306 | fork-join-executor { 307 | dedicated-thread-pool{ #settings for Helios.DedicatedThreadPool 308 | thread-count = 3 #number of threads 309 | #deadlock-timeout = 3s #optional timeout for deadlock detection 310 | threadtype = background #values can be "background" or "foreground" 311 | } 312 | } 313 | 314 | # For running in current synchronization contexts 315 | current-context-executor{} 316 | 317 | # How long time the dispatcher will wait for new actors until it shuts down 318 | shutdown-timeout = 1s 319 | 320 | # Throughput defines the number of messages that are processed in a batch 321 | # before the thread is returned to the pool. Set to 1 for as fair as possible. 322 | throughput = 30 323 | 324 | # Throughput deadline for Dispatcher, set to 0 or negative for no deadline 325 | throughput-deadline-time = 0ms 326 | 327 | # For BalancingDispatcher: If the balancing dispatcher should attempt to 328 | # schedule idle actors using the same dispatcher when a message comes in, 329 | # and the dispatchers ExecutorService is not fully busy already. 330 | attempt-teamwork = on 331 | 332 | # If this dispatcher requires a specific type of mailbox, specify the 333 | # fully-qualified class name here; the actually created mailbox will 334 | # be a subtype of this type. The empty string signifies no requirement. 335 | mailbox-requirement = "" 336 | } 337 | 338 | default-mailbox { 339 | # FQCN of the MailboxType. The Class of the FQCN must have a public 340 | # constructor with 341 | # (akka.actor.ActorSystem.Settings, com.typesafe.config.Config) parameters. 342 | mailbox-type = "Akka.Dispatch.UnboundedMailbox" 343 | 344 | # If the mailbox is bounded then it uses this setting to determine its 345 | # capacity. The provided value must be positive. 346 | # NOTICE: 347 | # Up to version 2.1 the mailbox type was determined based on this setting; 348 | # this is no longer the case, the type must explicitly be a bounded mailbox. 349 | mailbox-capacity = 1000 350 | 351 | # If the mailbox is bounded then this is the timeout for enqueueing 352 | # in case the mailbox is full. Negative values signify infinite 353 | # timeout, which should be avoided as it bears the risk of dead-lock. 354 | mailbox-push-timeout-time = 10s 355 | 356 | # For Actor with Stash: The default capacity of the stash. 357 | # If negative (or zero) then an unbounded stash is used (default) 358 | # If positive then a bounded stash is used and the capacity is set using 359 | # the property 360 | stash-capacity = -1 361 | } 362 | 363 | mailbox { 364 | # Mapping between message queue semantics and mailbox configurations. 365 | # Used by akka.dispatch.RequiresMessageQueue[T] to enforce different 366 | # mailbox types on actors. 367 | # If your Actor implements RequiresMessageQueue[T], then when you create 368 | # an instance of that actor its mailbox type will be decided by looking 369 | # up a mailbox configuration via T in this mapping 370 | requirements { 371 | "Akka.Dispatch.IUnboundedMessageQueueSemantics" = akka.actor.mailbox.unbounded-queue-based 372 | "Akka.Dispatch.IBoundedMessageQueueSemantics" = akka.actor.mailbox.bounded-queue-based 373 | "Akka.Dispatch.IDequeBasedMessageQueueSemantics" = akka.actor.mailbox.unbounded-deque-based 374 | "Akka.Dispatch.IUnboundedDequeBasedMessageQueueSemantics" = akka.actor.mailbox.unbounded-deque-based 375 | "Akka.Dispatch.IBoundedDequeBasedMessageQueueSemantics" = akka.actor.mailbox.bounded-deque-based 376 | "Akka.Dispatch.IMultipleConsumerSemantics" = akka.actor.mailbox.unbounded-queue-based 377 | "Akka.Event.ILoggerMessageQueueSemantics" = akka.actor.mailbox.logger-queue 378 | } 379 | 380 | unbounded-queue-based { 381 | # FQCN of the MailboxType, The Class of the FQCN must have a public 382 | # constructor with (akka.actor.ActorSystem.Settings, 383 | # com.typesafe.config.Config) parameters. 384 | mailbox-type = "Akka.Dispatch.UnboundedMailbox" 385 | } 386 | 387 | bounded-queue-based { 388 | # FQCN of the MailboxType, The Class of the FQCN must have a public 389 | # constructor with (akka.actor.ActorSystem.Settings, 390 | # com.typesafe.config.Config) parameters. 391 | mailbox-type = "Akka.Dispatch.BoundedMailbox" 392 | } 393 | 394 | unbounded-deque-based { 395 | # FQCN of the MailboxType, The Class of the FQCN must have a public 396 | # constructor with (akka.actor.ActorSystem.Settings, 397 | # com.typesafe.config.Config) parameters. 398 | mailbox-type = "Akka.Dispatch.UnboundedDequeBasedMailbox" 399 | } 400 | 401 | bounded-deque-based { 402 | # FQCN of the MailboxType, The Class of the FQCN must have a public 403 | # constructor with (akka.actor.ActorSystem.Settings, 404 | # com.typesafe.config.Config) parameters. 405 | mailbox-type = "Akka.Dispatch.BoundedDequeBasedMailbox" 406 | } 407 | 408 | # The LoggerMailbox will drain all messages in the mailbox 409 | # when the system is shutdown and deliver them to the StandardOutLogger. 410 | # Do not change this unless you know what you are doing. 411 | logger-queue { 412 | mailbox-type = "Akka.Event.LoggerMailboxType" 413 | } 414 | } 415 | 416 | debug { 417 | # enable function of Actor.loggable(), which is to log any received message 418 | # at DEBUG level, see the “Testing Actor Systems” section of the Akka 419 | # Documentation at http://akka.io/docs 420 | receive = off 421 | 422 | # enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill et.c.) 423 | autoreceive = off 424 | 425 | # enable DEBUG logging of actor lifecycle changes 426 | lifecycle = off 427 | 428 | # enable DEBUG logging of all LoggingFSMs for events, transitions and timers 429 | fsm = off 430 | 431 | # enable DEBUG logging of subscription changes on the eventStream 432 | event-stream = off 433 | 434 | # enable DEBUG logging of unhandled messages 435 | unhandled = off 436 | 437 | # enable WARN logging of misconfigured routers 438 | router-misconfiguration = off 439 | } 440 | 441 | # Entries for pluggable serializers and their bindings. 442 | 443 | serializers { 444 | json = "Akka.Serialization.NewtonSoftJsonSerializer" 445 | java = "Akka.Serialization.JavaSerializer" # not used, reserves java serializer identifier 446 | bytes = "Akka.Serialization.ByteArraySerializer" 447 | } 448 | 449 | # Class to Serializer binding. You only need to specify the name of an 450 | # interface or abstract base class of the messages. In case of ambiguity it 451 | # is using the most specific configured class, or giving a warning and 452 | # choosing the “first” one. 453 | # 454 | # To disable one of the default serializers, assign its class to "none", like 455 | # "java.io.Serializable" = none 456 | serialization-bindings { 457 | "System.Byte[]" = bytes 458 | "System.Object" = json 459 | } 460 | } 461 | 462 | # Used to set the behavior of the scheduler. 463 | # Changing the default values may change the system behavior drastically so make 464 | # sure you know what you're doing! See the Scheduler section of the Akka 465 | # Documentation for more details. 466 | scheduler { 467 | # The LightArrayRevolverScheduler is used as the default scheduler in the 468 | # system. It does not execute the scheduled tasks on exact time, but on every 469 | # tick, it will run everything that is (over)due. You can increase or decrease 470 | # the accuracy of the execution timing by specifying smaller or larger tick 471 | # duration. If you are scheduling a lot of tasks you should consider increasing 472 | # the ticks per wheel. 473 | # Note that it might take up to 1 tick to stop the Timer, so setting the 474 | # tick-duration to a high value will make shutting down the actor system 475 | # take longer. 476 | tick-duration = 10ms 477 | 478 | # The timer uses a circular wheel of buckets to store the timer tasks. 479 | # This should be set such that the majority of scheduled timeouts (for high 480 | # scheduling frequency) will be shorter than one rotation of the wheel 481 | # (ticks-per-wheel * ticks-duration) 482 | # THIS MUST BE A POWER OF TWO! 483 | ticks-per-wheel = 512 484 | 485 | # This setting selects the timer implementation which shall be loaded at 486 | # system start-up. 487 | # The class given here must implement the akka.actor.Scheduler interface 488 | # and offer a public constructor which takes three arguments: 489 | # 1) com.typesafe.config.Config 490 | # 2) akka.event.LoggingAdapter 491 | # 3) java.util.concurrent.ThreadFactory 492 | implementation = "Akka.Actor.HashedWheelTimerScheduler" 493 | 494 | # When shutting down the scheduler, there will typically be a thread which 495 | # needs to be stopped, and this timeout determines how long to wait for 496 | # that to happen. In case of timeout the shutdown of the actor system will 497 | # proceed without running possibly still enqueued tasks. 498 | shutdown-timeout = 5s 499 | } 500 | 501 | io { 502 | 503 | # By default the select loops run on dedicated threads, hence using a 504 | # PinnedDispatcher 505 | pinned-dispatcher { 506 | type = "PinnedDispatcher" 507 | executor = "fork-join-executor" 508 | } 509 | 510 | tcp { 511 | 512 | # The number of selectors to stripe the served channels over; each of 513 | # these will use one select loop on the selector-dispatcher. 514 | nr-of-selectors = 1 515 | 516 | # Maximum number of open channels supported by this TCP module; there is 517 | # no intrinsic general limit, this setting is meant to enable DoS 518 | # protection by limiting the number of concurrently connected clients. 519 | # Also note that this is a "soft" limit; in certain cases the implementation 520 | # will accept a few connections more or a few less than the number configured 521 | # here. Must be an integer > 0 or "unlimited". 522 | max-channels = 256000 523 | 524 | # When trying to assign a new connection to a selector and the chosen 525 | # selector is at full capacity, retry selector choosing and assignment 526 | # this many times before giving up 527 | selector-association-retries = 10 528 | 529 | # The maximum number of connection that are accepted in one go, 530 | # higher numbers decrease latency, lower numbers increase fairness on 531 | # the worker-dispatcher 532 | batch-accept-limit = 10 533 | 534 | # The number of bytes per direct buffer in the pool used to read or write 535 | # network data from the kernel. 536 | direct-buffer-size = 18432 # 128 KiB 537 | 538 | # The maximal number of direct buffers kept in the direct buffer pool for 539 | # reuse. 540 | direct-buffer-pool-limit = 1000 541 | 542 | # The duration a connection actor waits for a `Register` message from 543 | # its commander before aborting the connection. 544 | register-timeout = 5s 545 | 546 | # The maximum number of bytes delivered by a `Received` message. Before 547 | # more data is read from the network the connection actor will try to 548 | # do other work. 549 | # The purpose of this setting is to impose a smaller limit than the 550 | # configured receive buffer size. When using value 'unlimited' it will 551 | # try to read all from the receive buffer. 552 | max-received-message-size = unlimited 553 | 554 | # Enable fine grained logging of what goes on inside the implementation. 555 | # Be aware that this may log more than once per message sent to the actors 556 | # of the tcp implementation. 557 | trace-logging = off 558 | 559 | # Fully qualified config path which holds the dispatcher configuration 560 | # to be used for running the select() calls in the selectors 561 | selector-dispatcher = "akka.io.pinned-dispatcher" 562 | 563 | # Fully qualified config path which holds the dispatcher configuration 564 | # for the read/write worker actors 565 | worker-dispatcher = "akka.actor.default-dispatcher" 566 | 567 | # Fully qualified config path which holds the dispatcher configuration 568 | # for the selector management actors 569 | management-dispatcher = "akka.actor.default-dispatcher" 570 | 571 | # Fully qualified config path which holds the dispatcher configuration 572 | # on which file IO tasks are scheduled 573 | file-io-dispatcher = "akka.actor.default-dispatcher" 574 | 575 | # The maximum number of bytes (or "unlimited") to transfer in one batch 576 | # when using `WriteFile` command which uses `FileChannel.transferTo` to 577 | # pipe files to a TCP socket. On some OS like Linux `FileChannel.transferTo` 578 | # may block for a long time when network IO is faster than file IO. 579 | # Decreasing the value may improve fairness while increasing may improve 580 | # throughput. 581 | file-io-transferTo-limit = 524288 # 512 KiB 582 | 583 | # The number of times to retry the `finishConnect` call after being notified about 584 | # OP_CONNECT. Retries are needed if the OP_CONNECT notification doesn't imply that 585 | # `finishConnect` will succeed, which is the case on Android. 586 | finish-connect-retries = 5 587 | 588 | # On Windows connection aborts are not reliably detected unless an OP_READ is 589 | # registered on the selector _after_ the connection has been reset. This 590 | # workaround enables an OP_CONNECT which forces the abort to be visible on Windows. 591 | # Enabling this setting on other platforms than Windows will cause various failures 592 | # and undefined behavior. 593 | # Possible values of this key are on, off and auto where auto will enable the 594 | # workaround if Windows is detected automatically. 595 | windows-connection-abort-workaround-enabled = off 596 | } 597 | 598 | udp { 599 | 600 | # The number of selectors to stripe the served channels over; each of 601 | # these will use one select loop on the selector-dispatcher. 602 | nr-of-selectors = 1 603 | 604 | # Maximum number of open channels supported by this UDP module Generally 605 | # UDP does not require a large number of channels, therefore it is 606 | # recommended to keep this setting low. 607 | max-channels = 4096 608 | 609 | # The select loop can be used in two modes: 610 | # - setting "infinite" will select without a timeout, hogging a thread 611 | # - setting a positive timeout will do a bounded select call, 612 | # enabling sharing of a single thread between multiple selectors 613 | # (in this case you will have to use a different configuration for the 614 | # selector-dispatcher, e.g. using "type=Dispatcher" with size 1) 615 | # - setting it to zero means polling, i.e. calling selectNow() 616 | select-timeout = infinite 617 | 618 | # When trying to assign a new connection to a selector and the chosen 619 | # selector is at full capacity, retry selector choosing and assignment 620 | # this many times before giving up 621 | selector-association-retries = 10 622 | 623 | # The maximum number of datagrams that are read in one go, 624 | # higher numbers decrease latency, lower numbers increase fairness on 625 | # the worker-dispatcher 626 | receive-throughput = 3 627 | 628 | # The number of bytes per direct buffer in the pool used to read or write 629 | # network data from the kernel. 630 | direct-buffer-size = 18432 # 128 KiB 631 | 632 | # The maximal number of direct buffers kept in the direct buffer pool for 633 | # reuse. 634 | direct-buffer-pool-limit = 1000 635 | 636 | # The maximum number of bytes delivered by a `Received` message. Before 637 | # more data is read from the network the connection actor will try to 638 | # do other work. 639 | received-message-size-limit = unlimited 640 | 641 | # Enable fine grained logging of what goes on inside the implementation. 642 | # Be aware that this may log more than once per message sent to the actors 643 | # of the tcp implementation. 644 | trace-logging = off 645 | 646 | # Fully qualified config path which holds the dispatcher configuration 647 | # to be used for running the select() calls in the selectors 648 | selector-dispatcher = "akka.io.pinned-dispatcher" 649 | 650 | # Fully qualified config path which holds the dispatcher configuration 651 | # for the read/write worker actors 652 | worker-dispatcher = "akka.actor.default-dispatcher" 653 | 654 | # Fully qualified config path which holds the dispatcher configuration 655 | # for the selector management actors 656 | management-dispatcher = "akka.actor.default-dispatcher" 657 | } 658 | 659 | udp-connected { 660 | 661 | # The number of selectors to stripe the served channels over; each of 662 | # these will use one select loop on the selector-dispatcher. 663 | nr-of-selectors = 1 664 | 665 | # Maximum number of open channels supported by this UDP module Generally 666 | # UDP does not require a large number of channels, therefore it is 667 | # recommended to keep this setting low. 668 | max-channels = 4096 669 | 670 | # The select loop can be used in two modes: 671 | # - setting "infinite" will select without a timeout, hogging a thread 672 | # - setting a positive timeout will do a bounded select call, 673 | # enabling sharing of a single thread between multiple selectors 674 | # (in this case you will have to use a different configuration for the 675 | # selector-dispatcher, e.g. using "type=Dispatcher" with size 1) 676 | # - setting it to zero means polling, i.e. calling selectNow() 677 | select-timeout = infinite 678 | 679 | # When trying to assign a new connection to a selector and the chosen 680 | # selector is at full capacity, retry selector choosing and assignment 681 | # this many times before giving up 682 | selector-association-retries = 10 683 | 684 | # The maximum number of datagrams that are read in one go, 685 | # higher numbers decrease latency, lower numbers increase fairness on 686 | # the worker-dispatcher 687 | receive-throughput = 3 688 | 689 | # The number of bytes per direct buffer in the pool used to read or write 690 | # network data from the kernel. 691 | direct-buffer-size = 18432 # 128 KiB 692 | 693 | # The maximal number of direct buffers kept in the direct buffer pool for 694 | # reuse. 695 | direct-buffer-pool-limit = 1000 696 | 697 | # The maximum number of bytes delivered by a `Received` message. Before 698 | # more data is read from the network the connection actor will try to 699 | # do other work. 700 | received-message-size-limit = unlimited 701 | 702 | # Enable fine grained logging of what goes on inside the implementation. 703 | # Be aware that this may log more than once per message sent to the actors 704 | # of the tcp implementation. 705 | trace-logging = off 706 | 707 | # Fully qualified config path which holds the dispatcher configuration 708 | # to be used for running the select() calls in the selectors 709 | selector-dispatcher = "akka.io.pinned-dispatcher" 710 | 711 | # Fully qualified config path which holds the dispatcher configuration 712 | # for the read/write worker actors 713 | worker-dispatcher = "akka.actor.default-dispatcher" 714 | 715 | # Fully qualified config path which holds the dispatcher configuration 716 | # for the selector management actors 717 | management-dispatcher = "akka.actor.default-dispatcher" 718 | } 719 | 720 | dns { 721 | # Fully qualified config path which holds the dispatcher configuration 722 | # for the manager and resolver router actors. 723 | # For actual router configuration see akka.actor.deployment./IO-DNS/* 724 | dispatcher = "akka.actor.default-dispatcher" 725 | 726 | # Name of the subconfig at path akka.io.dns, see inet-address below 727 | resolver = "inet-address" 728 | 729 | inet-address { 730 | # Must implement akka.io.DnsProvider 731 | provider-object = "Akka.IO.InetAddressDnsProvider" 732 | 733 | # These TTLs are set to default java 6 values 734 | positive-ttl = 30s 735 | negative-ttl = 10s 736 | 737 | # How often to sweep out expired cache entries. 738 | # Note that this interval has nothing to do with TTLs 739 | cache-cleanup-interval = 120s 740 | } 741 | } 742 | } 743 | } 744 | -------------------------------------------------------------------------------- /tests/configs.conf: -------------------------------------------------------------------------------- 1 | include "tests/t1.conf" 2 | include "tests/t2.conf" -------------------------------------------------------------------------------- /tests/t1.conf: -------------------------------------------------------------------------------- 1 | sender { 2 | t1 = "t1" 3 | t2 = "t2" 4 | t3 = "t3" 5 | } 6 | 7 | 8 | test.out.a.b.c.d.groups={ 9 | g2 { 10 | o1 { 11 | order = 1 12 | } 13 | 14 | o2 { 15 | order = 2 16 | } 17 | } 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/t2.conf: -------------------------------------------------------------------------------- 1 | test.out.a.b.c.d.groups = { 2 | g1 { 3 | o1 { 4 | order = 1 5 | } 6 | 7 | o2 { 8 | order = 2 9 | } 10 | 11 | o3 { 12 | order = 3 13 | } 14 | } 15 | 16 | g2 { 17 | o3 { 18 | order = 3 19 | } 20 | } 21 | 22 | } 23 | 24 | --------------------------------------------------------------------------------