├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── args.go ├── args_test.go ├── doc.go ├── example_custom_test.go └── example_test.go /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - 1.4 6 | -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction ![Travis Build Status](https://api.travis-ci.org/soheilhy/args.svg?branch=master "Travis Build Status") 2 | args is a generic library for optional arguments. It is 3 | inspired by Dave Cheney's 4 | [functional options idea](http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis). 5 | It can also serve the purpose of Python "kwargs" for Go programs. 6 | 7 | # Usage 8 | Optional arguments are defined using `New` and its typed variants. 9 | These arguments are basically functions that return argument values 10 | of type `args.V`. To use these argument values, your function receives 11 | a variadic list of `args.V` and then gets the value of each argument: 12 | 13 | ```go 14 | var Port = args.NewInt() 15 | var RoundTripper = args.New(Default(http.DefaultTransport)) 16 | var Timeout = args.NewDuration(Flag("timeout", 10*time.Second, "timeout")) 17 | 18 | func MyServer(args ...args.V) { 19 | port := Port.Get(args) 20 | rt := RoundTripper.Get(args) 21 | to := Timeout.Get(args) 22 | ... 23 | } 24 | 25 | MyServer() 26 | MyServer(Timeout(1 * time.Second)) 27 | MyServer(Timeout(2 * time.Second), RoundTripper(MyTransport)) 28 | ``` 29 | 30 | `args` can load default values from flags as well as user-defined 31 | constants. 32 | 33 | To use user-defined types instead of the generic `args.V`, 34 | you need to write a few lines of boiler-plates: 35 | 36 | ```go 37 | var roundTripper = args.New(http.DefaultTransport) 38 | var timeout = args.NewDuration() 39 | 40 | type ServerOpt args.V 41 | func RoundTripper(r http.RoundTripper) ServerOpt { return ServerOpt(roundTripper(r)) } 42 | func Timeout(d time.Duration) ServerOpt { return ServerOpt(d) } 43 | 44 | func MyServer(opts ...ServerOpt) { 45 | rt := roundTripper.Get(opts).(http.RoundTripper) 46 | to := timeout.Get(opts) 47 | ... 48 | } 49 | ``` 50 | 51 | Note that, args is focused on easy-to-use APIs. It is not efficient 52 | and is wasteful if the function is frequently invoked. 53 | 54 | # API 55 | * [Go Doc](https://godoc.org/github.com/soheilhy/args) 56 | -------------------------------------------------------------------------------- /args.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | import ( 4 | "flag" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | // A represents an argument 10 | type A func(i interface{}) V 11 | 12 | // Get returns the value of the argument from the values. 13 | // nil is returned if no value and no default is set for this argument. 14 | func (a A) Get(vals ...interface{}) (val interface{}) { 15 | val = get(a(nil).arg(), vals) 16 | return 17 | } 18 | 19 | // IsSet returns whether the argument is set in the values. 20 | func (a A) IsSet(vals ...interface{}) bool { 21 | return isSet(a(nil).arg(), vals) 22 | } 23 | 24 | // New creates a new argument. 25 | func New(vals ...V) A { 26 | a := &arg{} 27 | 28 | d := Default.Get(vals) 29 | a.defv = d 30 | 31 | f := A(flagA).Get(vals) 32 | if f != nil { 33 | panic("cannot set flag for interface{}") 34 | } 35 | 36 | return a.getA() 37 | } 38 | 39 | // BoolA is a boolean argument. 40 | type BoolA func(b bool) V 41 | 42 | // Get returns the value of the int argument among vals. 43 | func (a BoolA) Get(vals ...interface{}) (val bool) { 44 | v := get(a(false).arg(), vals) 45 | switch v := v.(type) { 46 | case *bool: 47 | return *v 48 | case bool: 49 | return v 50 | } 51 | return 52 | } 53 | 54 | // IsSet returns whether the argument is set in the values. 55 | func (a BoolA) IsSet(vals ...interface{}) bool { 56 | return isSet(a(false).arg(), vals) 57 | } 58 | 59 | // NewBool creates a new integer argument. 60 | func NewBool(vals ...V) BoolA { 61 | a := &arg{} 62 | 63 | d := Default.Get(vals) 64 | a.defv = d 65 | 66 | f := A(flagA).Get(vals) 67 | if f != nil { 68 | f := f.(flagT) 69 | a.defv = flag.Bool(f.name, f.defv.(bool), f.usage) 70 | } 71 | 72 | ab := a.getA() 73 | return func(b bool) V { 74 | return ab(b) 75 | } 76 | } 77 | 78 | // IntA is an integer argument. 79 | type IntA func(i int) V 80 | 81 | // Get returns the value of the int argument among vals. 82 | func (a IntA) Get(vals ...interface{}) (val int) { 83 | v := get(a(0).arg(), vals) 84 | switch v := v.(type) { 85 | case *int: 86 | return *v 87 | case int: 88 | return v 89 | } 90 | return 91 | } 92 | 93 | // IsSet returns whether the argument is set in the values. 94 | func (a IntA) IsSet(vals ...interface{}) bool { 95 | return isSet(a(0).arg(), vals) 96 | } 97 | 98 | // NewInt creates a new integer argument. 99 | func NewInt(vals ...V) IntA { 100 | a := &arg{} 101 | 102 | d := Default.Get(vals) 103 | a.defv = d 104 | 105 | f := A(flagA).Get(vals) 106 | if f != nil { 107 | f := f.(flagT) 108 | a.defv = flag.Int(f.name, f.defv.(int), f.usage) 109 | } 110 | 111 | ai := a.getA() 112 | return func(i int) V { 113 | return ai(i) 114 | } 115 | } 116 | 117 | // UintA is an unsigned integer argument. 118 | type UintA func(i uint) V 119 | 120 | // Get returns the value of the int argument among vals. 121 | func (a UintA) Get(vals ...interface{}) (val uint) { 122 | v := get(a(0).arg(), vals) 123 | switch v := v.(type) { 124 | case *uint: 125 | return *v 126 | case uint: 127 | return v 128 | } 129 | return 130 | } 131 | 132 | // IsSet returns whether the argument is set in the values. 133 | func (a UintA) IsSet(vals ...interface{}) bool { 134 | return isSet(a(0).arg(), vals) 135 | } 136 | 137 | // NewUint creates a new integer argument. 138 | func NewUint(vals ...V) UintA { 139 | a := &arg{} 140 | 141 | d := Default.Get(vals) 142 | a.defv = d 143 | 144 | f := A(flagA).Get(vals) 145 | if f != nil { 146 | f := f.(flagT) 147 | a.defv = flag.Uint(f.name, f.defv.(uint), f.usage) 148 | } 149 | 150 | ai := a.getA() 151 | return func(i uint) V { 152 | return ai(i) 153 | } 154 | } 155 | 156 | // Int64A is a 64-bit integer argument. 157 | type Int64A func(i int64) V 158 | 159 | // Get returns the value of the int64 argument among vals. 160 | func (a Int64A) Get(vals ...interface{}) (val int64) { 161 | v := get(a(0).arg(), vals) 162 | switch v := v.(type) { 163 | case *int64: 164 | return *v 165 | case int64: 166 | return v 167 | } 168 | return 169 | } 170 | 171 | // IsSet returns whether the argument is set in the values. 172 | func (a Int64A) IsSet(vals ...interface{}) bool { 173 | return isSet(a(0).arg(), vals) 174 | } 175 | 176 | // NewInt64 creates a new integer argument. 177 | func NewInt64(vals ...V) Int64A { 178 | a := &arg{} 179 | 180 | d := Default.Get(vals) 181 | a.defv = d 182 | 183 | f := A(flagA).Get(vals) 184 | if f != nil { 185 | f := f.(flagT) 186 | a.defv = flag.Int64(f.name, f.defv.(int64), f.usage) 187 | } 188 | 189 | ai := a.getA() 190 | return func(i int64) V { 191 | return ai(i) 192 | } 193 | } 194 | 195 | // Uint64A is an unsigned 64-bit integer argument. 196 | type Uint64A func(i uint64) V 197 | 198 | // Get returns the value of the uint64 argument among vals. 199 | func (a Uint64A) Get(vals ...interface{}) (val uint64) { 200 | v := get(a(0).arg(), vals) 201 | switch v := v.(type) { 202 | case *uint64: 203 | return *v 204 | case uint64: 205 | return v 206 | } 207 | return 208 | } 209 | 210 | // IsSet returns whether the argument is set in the values. 211 | func (a Uint64A) IsSet(vals ...interface{}) bool { 212 | return isSet(a(0).arg(), vals) 213 | } 214 | 215 | // NewUint64 creates a new integer argument. 216 | func NewUint64(vals ...V) Uint64A { 217 | a := &arg{} 218 | 219 | d := Default.Get(vals) 220 | a.defv = d 221 | 222 | f := A(flagA).Get(vals) 223 | if f != nil { 224 | f := f.(flagT) 225 | a.defv = flag.Uint64(f.name, f.defv.(uint64), f.usage) 226 | } 227 | 228 | ai := a.getA() 229 | return func(i uint64) V { 230 | return ai(i) 231 | } 232 | } 233 | 234 | // Float64A is a 64-bit float argument. 235 | type Float64A func(i float64) V 236 | 237 | // Get returns the value of the float64 argument among vals. 238 | func (a Float64A) Get(vals ...interface{}) (val float64) { 239 | v := get(a(0).arg(), vals) 240 | switch v := v.(type) { 241 | case *float64: 242 | return *v 243 | case float64: 244 | return v 245 | } 246 | return 247 | } 248 | 249 | // IsSet returns whether the argument is set in the values. 250 | func (a Float64A) IsSet(vals ...interface{}) bool { 251 | return isSet(a(0).arg(), vals) 252 | } 253 | 254 | // NewFloat64 creates a new integer argument. 255 | func NewFloat64(vals ...V) Float64A { 256 | a := &arg{} 257 | 258 | d := Default.Get(vals) 259 | a.defv = d 260 | 261 | f := A(flagA).Get(vals) 262 | if f != nil { 263 | f := f.(flagT) 264 | a.defv = flag.Float64(f.name, f.defv.(float64), f.usage) 265 | } 266 | 267 | af := a.getA() 268 | return func(f float64) V { 269 | return af(f) 270 | } 271 | } 272 | 273 | // StringA is a string argument. 274 | type StringA func(s string) V 275 | 276 | // Get returns the value of the string argument among vals. 277 | func (a StringA) Get(vals ...interface{}) (val string) { 278 | v := get(a("").arg(), vals) 279 | switch v := v.(type) { 280 | case *string: 281 | return *v 282 | case string: 283 | return v 284 | } 285 | return 286 | } 287 | 288 | // IsSet returns whether the argument is set in the values. 289 | func (a StringA) IsSet(vals ...interface{}) bool { 290 | return isSet(a("").arg(), vals) 291 | } 292 | 293 | // NewString creates a new integer argument. 294 | func NewString(vals ...V) StringA { 295 | a := &arg{} 296 | 297 | d := Default.Get(vals) 298 | a.defv = d 299 | 300 | f := A(flagA).Get(vals) 301 | if f != nil { 302 | f := f.(flagT) 303 | a.defv = flag.String(f.name, f.defv.(string), f.usage) 304 | } 305 | 306 | as := a.getA() 307 | return func(s string) V { 308 | return as(s) 309 | } 310 | } 311 | 312 | // DurationA is a duration argument. 313 | type DurationA func(d time.Duration) V 314 | 315 | // Get returns the value of the time.Duration argument among vals. 316 | func (a DurationA) Get(vals ...interface{}) (val time.Duration) { 317 | v := get(a(0).arg(), vals) 318 | switch v := v.(type) { 319 | case *time.Duration: 320 | return *v 321 | case time.Duration: 322 | return v 323 | } 324 | return 325 | } 326 | 327 | // IsSet returns whether the argument is set in the values. 328 | func (a DurationA) IsSet(vals ...interface{}) bool { 329 | return isSet(a(0).arg(), vals) 330 | } 331 | 332 | // NewDuration creates a new integer argument. 333 | func NewDuration(vals ...V) DurationA { 334 | a := &arg{} 335 | 336 | d := Default.Get(vals) 337 | a.defv = d 338 | 339 | f := A(flagA).Get(vals) 340 | if f != nil { 341 | f := f.(flagT) 342 | a.defv = flag.Duration(f.name, f.defv.(time.Duration), f.usage) 343 | } 344 | 345 | ad := a.getA() 346 | return func(d time.Duration) V { 347 | return ad(d) 348 | } 349 | } 350 | 351 | // V represents the value of an argument. 352 | type V interface { 353 | val() interface{} 354 | arg() *arg 355 | } 356 | 357 | type argVal struct { 358 | value interface{} 359 | argument *arg 360 | } 361 | 362 | func (v argVal) val() interface{} { 363 | return v.value 364 | } 365 | 366 | func (v argVal) arg() *arg { 367 | return v.argument 368 | } 369 | 370 | func get(arg *arg, vals []interface{}) interface{} { 371 | for i := len(vals) - 1; 0 <= i; i-- { 372 | val := vals[i] 373 | switch reflect.TypeOf(val).Kind() { 374 | case reflect.Slice: 375 | s := reflect.ValueOf(val) 376 | for j := s.Len() - 1; 0 <= j; j-- { 377 | if v, ok := s.Index(j).Interface().(V); ok && arg == v.arg() { 378 | return v.val() 379 | } 380 | } 381 | 382 | default: 383 | if v, ok := arg.valueOf(val); ok { 384 | return v 385 | } 386 | } 387 | } 388 | return arg.defv 389 | } 390 | 391 | func isSet(arg *arg, vals []interface{}) bool { 392 | for i := len(vals) - 1; 0 <= i; i-- { 393 | val := vals[i] 394 | switch reflect.TypeOf(val).Kind() { 395 | case reflect.Slice: 396 | s := reflect.ValueOf(val) 397 | for j := s.Len() - 1; 0 <= j; j-- { 398 | if v, ok := s.Index(j).Interface().(V); ok && arg == v.arg() { 399 | return true 400 | } 401 | } 402 | 403 | default: 404 | _, ok := arg.valueOf(val) 405 | return ok 406 | } 407 | } 408 | return false 409 | } 410 | 411 | type arg struct { 412 | defv interface{} 413 | } 414 | 415 | func (a *arg) valueOf(i interface{}) (val interface{}, ok bool) { 416 | if v, ok := i.(V); ok && a == v.arg() { 417 | return v.val(), true 418 | } 419 | return 420 | } 421 | 422 | func (a *arg) getA() A { 423 | return func(i interface{}) V { 424 | return argVal{ 425 | value: i, 426 | argument: a, 427 | } 428 | } 429 | } 430 | 431 | // Default sets the default value of an argument. 432 | var Default = defArg.getA() 433 | var defArg = &arg{} 434 | 435 | // Flag sets the default value of an argument based on a flag. 436 | // 437 | // Note that Flag can only be used in typed New functions (e.g., NewInt(), ...). 438 | // New() will panic if it receives a Flag. 439 | // 440 | // Flag overwrites Default if both passed to an argument. 441 | func Flag(name string, defv interface{}, usage string) V { 442 | return flagA(flagT{ 443 | name: name, 444 | defv: defv, 445 | usage: usage, 446 | }) 447 | } 448 | 449 | var flagA = flagArg.getA() 450 | var flagArg = &arg{} 451 | 452 | type flagT struct { 453 | name string // name is the name of the flag. 454 | defv interface{} // defv is the default value of this flag. 455 | usage string // usage is the usage documentation of this flag. 456 | } 457 | -------------------------------------------------------------------------------- /args_test.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | import "testing" 4 | 5 | func TestNew(t *testing.T) { 6 | argv := "test" 7 | testNew1 := New() 8 | testNew2 := New() 9 | func(vals ...V) { 10 | v := []interface{}{testNew1.Get(vals), testNew2.Get(vals)} 11 | for i := range v { 12 | if v[i] != argv { 13 | t.Errorf("invalid default value: want=%v got=%v", argv, v[i]) 14 | } 15 | } 16 | }(testNew1(argv), testNew2(argv)) 17 | } 18 | 19 | func TestNewDefaults(t *testing.T) { 20 | testNewNil := New() 21 | testNewOne := New(Default(1)) 22 | testNewFlag := NewInt(Flag("testnewflag", 1, "usage")) 23 | func(vals ...V) { 24 | if v := testNewNil.Get(vals); v != nil { 25 | t.Errorf("invalid default value: want=nil got=%v", v) 26 | } 27 | 28 | if v := testNewOne.Get(vals); v != 1 { 29 | t.Errorf("invalid default value: want=1 got=%v", v) 30 | } 31 | 32 | if i := testNewFlag.Get(vals); i != 1 { 33 | t.Errorf("invalid default value: want=1 got=%v", i) 34 | } 35 | }() 36 | } 37 | 38 | func TestNewInt(t *testing.T) { 39 | argv := 1 40 | testNew1 := NewInt() 41 | testNew2 := NewInt() 42 | func(vals ...V) { 43 | v := []int{testNew1.Get(vals), testNew2.Get(vals)} 44 | for i := range v { 45 | if v[i] != argv { 46 | t.Errorf("invalid value: want=%d got=%d", argv, v[i]) 47 | } 48 | } 49 | }(testNew1(argv), testNew2(argv)) 50 | } 51 | 52 | func TestNewUint(t *testing.T) { 53 | argv := uint(1) 54 | testNew1 := NewUint() 55 | testNew2 := NewUint() 56 | func(vals ...V) { 57 | v := []uint{testNew1.Get(vals), testNew2.Get(vals)} 58 | for i := range v { 59 | if v[i] != argv { 60 | t.Errorf("invalid value: want=%d got=%d", argv, v[i]) 61 | } 62 | } 63 | }(testNew1(argv), testNew2(argv)) 64 | } 65 | 66 | func TestOrder(t *testing.T) { 67 | argv1 := "first" 68 | argv2 := "second" 69 | testStr := NewString() 70 | func(vals ...V) { 71 | v := testStr.Get(vals) 72 | if v != argv2 { 73 | t.Errorf("invalid value: want=%d got=%d", argv2, v) 74 | } 75 | }(testStr(argv1), testStr(argv2)) 76 | } 77 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // args is a generic library for optional arguments. It is 2 | // inspired by Dave Cheney's functional options idea 3 | // (http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis). 4 | // It can serve the purpose of Python "kwargs" for Go programs. 5 | // 6 | // Optional arguments are defined using New and its typed variants: 7 | // 8 | // 9 | // These arguments are basically functions that return argument values 10 | // of type args.V. To use these argument values the function receives 11 | // a variadic list of args.V and then get the value of each argument 12 | // from those values: 13 | // 14 | // var RoundTripper = args.New() 15 | // var Timeout = args.NewDuration() 16 | // func MyServer(args ...args.V) { 17 | // rt := RoundTripper.Get(args) 18 | // to := Timeout.Get(args) 19 | // ... 20 | // } 21 | // MyServer() 22 | // MyServer(Timeout(1 * time.Second)) 23 | // MyServer(Timeout(2 * time.Second), RoundTripper(MyTransport)) 24 | // 25 | // To use typed arguments, instead of the generic args.V, 26 | // you'll need to write a few lines of boiler-plates: 27 | // 28 | // var roundTripper = args.New() 29 | // var timeout = args.NewDuration() 30 | // 31 | // type ServerOpt args.V 32 | // func RoundTripper(r http.RoundTripper) ServerOpt { 33 | // return ServerOpt(roundTripper(r)) 34 | // } 35 | // func Timeout(d time.Duration) ServerOpt { 36 | // return ServerOpt(d) 37 | // } 38 | // func MyServer(opts ...ServerOpt) { 39 | // rt := roundTripper.Get(opts).(http.RoundTripper) 40 | // to := timeout.Get(opts) 41 | // ... 42 | // } 43 | // 44 | // Note that, args is focused on easy-to-use APIs. It is not efficient 45 | // and is wasteful if the function is frequently invoked. 46 | package args 47 | -------------------------------------------------------------------------------- /example_custom_test.go: -------------------------------------------------------------------------------- 1 | package args_test 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/soheilhy/args" 9 | ) 10 | 11 | // port is an integer argument that its default value is read from 12 | // the "-example.typed.port" flag. 13 | var port = args.NewInt(args.Flag("example.typed.port", 1234, "the port")) 14 | 15 | // roundTripper is a generic argument that its default value is 16 | // http.DefaultTransport. 17 | var roundTripper = args.New(args.Default(http.DefaultTransport)) 18 | 19 | // timeout is a duration argument. 20 | var timeout = args.NewDuration() 21 | 22 | type ServerOpt args.V 23 | 24 | // Port, RoundTripper, and Timeout respectively wrap port, roundTripper, 25 | // and timeout to return ServerOpt instead of args.V. 26 | func Port(p int) ServerOpt { return ServerOpt(port(p)) } 27 | func RoundTripper(r http.RoundTripper) ServerOpt { return ServerOpt(roundTripper(r)) } 28 | func Timeout(d time.Duration) ServerOpt { return ServerOpt(timeout(d)) } 29 | 30 | func MyServer(opts ...ServerOpt) { 31 | port := port.Get(opts) 32 | fmt.Printf("listening on port %v\n", port) 33 | 34 | rt := roundTripper.Get(opts).(http.RoundTripper) 35 | if rt == http.DefaultTransport { 36 | fmt.Println("using the default transport") 37 | } else { 38 | fmt.Println("using a user-provided round-tripper") 39 | } 40 | 41 | to := timeout.Get(opts) 42 | fmt.Printf("using a timeout of %v\n", to) 43 | } 44 | 45 | func Example_typed() { 46 | // If you run "go test -example.typed.port=2222" this test fails, 47 | // because the output is (correctly) different. 48 | MyServer(Timeout(1 * time.Second)) 49 | // Output: 50 | //listening on port 1234 51 | //using the default transport 52 | //using a timeout of 1s 53 | } 54 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package args_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/soheilhy/args" 7 | ) 8 | 9 | var ListenOn = args.NewInt(args.Default(8080)) 10 | var BufferSize = args.NewUint64(args.Default(uint64(1024 * 1024))) 11 | var StateDir = args.NewString(args.Flag("test.state.dir", "/tmp", "state dir")) 12 | 13 | func Server(opts ...args.V) { 14 | port := ListenOn.Get(opts) 15 | bufs := BufferSize.Get(opts) 16 | sdir := StateDir.Get(opts) 17 | 18 | fmt.Printf("port=%d buf=%d state=%s\n", port, bufs, sdir) 19 | } 20 | 21 | func Example() { 22 | Server() 23 | Server(ListenOn(80), BufferSize(2048*1024), StateDir("/tmp2")) 24 | // Output: 25 | //port=8080 buf=1048576 state=/tmp 26 | //port=80 buf=2097152 state=/tmp2 27 | } 28 | --------------------------------------------------------------------------------