opto 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 01abb09f30890132075384eb8ff969899b0fb168
4
+ data.tar.gz: 51d608217ef7b48968d66a75ae2e8515c38c4d9d
5
+ SHA512:
6
+ metadata.gz: 66121b1275518c5023ee8e2a4633e7835f3cff613c6173bc18debe8a1b29905f0913f13a4b1579e83d46f8fd4f049ebb17a2936497660c3fda1d1c2471446a07
7
+ data.tar.gz: 89ecd251166c29beeae9bcb96962d7d873215d9f55225e7c6813050fa1b772b400497977289594549a182e51e0098e498887e0a8bd38c0a0b17e0c80c2d66494
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,18 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0-p648
4
+ - 2.1.8
5
+ - 2.2.3
6
+ - 2.3.1
7
+ env:
8
+ - secure: "bCu1bVzr6SP+gAbMo88GvcHHbhO3LcnJtWp+YuSZ5qW4EHhdBw+DOQeOlzoKHtYhNhptmb/65pZZctRTE6jOOSZzpOUHaeqaqHZpELldkCMu7rE8bXuCV4OfhG0CKWvURS0x5p5F5onEx1a1sjiu+MVEUqktAPDAcdTBNBW1irRwvnSifgjcZRLzfm1aRs1fqzBJpHaAXyD++2GQaWyXDtQsEsc/Eyhmnkac5Y2cTeQiYlDluqBeB885q9K16ruLp2NWx/rnR8RGMP1LSnrye/GEo2mRWOWy1MyIEwNDcVaA6MVDY2GBIQUoXg4PBrZAleVIsO3LzwR4oorFCaSgWxzqYI67g5Kb2zn8fW9Yu5lDcFEJKIHQAwBrlq3n3Mi5it/IxGQDWXQ/2fjBxCwuilvI0WTzOH0m4g8Uf6jHaQLFC6ahdWDjfs6aCzdPpbJls26/r39NIB5k/6ZO29NZkxGBsQ7E7m4wurr7a9ksLODgPOyPiB8/4Txu35Llp+IuJpbshrlkRIMwEIFVWiLi6MZhRBkxaRzlkJQ0bS3RD9W4z9UJyoBujmgXMrv0eHNTGCOC+/4tvrA+pXzJwmFQJA6TnenoDl9hc6o03gyfH/YvmMgrtJLbJ8rGoLRaY8HsQwHWgimpCl1o043kO8/UkhhS7J4wyQXYwqGnA2PC/j0="
9
+ before_install: gem install bundler -v 1.12.5
10
+ cache: bundler
11
+ script: bundle install && bundle exec rspec spec/
12
+ deploy:
13
+ provider: rubygems
14
+ api_key: $GEM_TOKEN
15
+ gem: opto
16
+ on:
17
+ tags: true
18
+ rvm: 2.3.1
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in opto.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'byebug', require: false
8
+ gem 'simplecov', require: false
9
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Kimmo Lehto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,407 @@
1
+ # Opto
2
+
3
+ [![Build Status](https://travis-ci.org/kontena/opto.svg?branch=master)](https://travis-ci.org/kontena/opto)
4
+
5
+ An option parser, built for generating options from YAML based [Kontena](https://github.com/kontena/kontena/) stack
6
+ definition files, but can be just as well used for other things, such as an API input validator.
7
+
8
+ The values for options can be resolved from files, env-variables, custom interactive prompts, random generators, etc.
9
+
10
+ The option type handlers can perform validations, such as defining a range or length requirements.
11
+
12
+ Transformations can be performed on values, such as upcasing strings or removing white space.
13
+
14
+ Options can have simple conditionals for determining if it needs to be processed or not, an option for defining a database
15
+ password can be processed only if a database has been selected.
16
+
17
+ Finally the value for the option can be placed to some destination, such as an environment variable or sent to a command.
18
+
19
+ ## Installation
20
+
21
+ ```ruby
22
+ # gem install opto
23
+
24
+ require 'opto'
25
+ ```
26
+
27
+ ## YAML definition examples:
28
+
29
+ ```yaml
30
+ # Enum type
31
+ remote_driver:
32
+ type: "enum"
33
+ required: true
34
+ label: "Remote Driver"
35
+ description: "Remote Git and Auth scheme"
36
+ options:
37
+ - github
38
+ - bitbucket
39
+ - gitlab
40
+ - gogs
41
+ ```
42
+
43
+ ```yaml
44
+ # String validation and transformation
45
+ foo_username:
46
+ type: string
47
+ required: true
48
+ min_length: 1
49
+ max_length: 30
50
+ strip: true # remove leading / trailing whitespace
51
+ upcase: true # make UPCASE
52
+ env: FOO_USER # read value from ENV variable FOO_USER
53
+ ```
54
+
55
+ ```yaml
56
+ # Enum with prettier item descriptions
57
+ name: foo_os
58
+ type: enum
59
+ can_be_other: true # otherwise value has to be one of the options to be valid.
60
+ options:
61
+ - value: coreos
62
+ label: CoreOS
63
+ description: CoreOS Stable
64
+ - value: ubuntu
65
+ label: Ubuntu
66
+ description: Ubuntu Bubuntu
67
+ ```
68
+
69
+ ```yaml
70
+ # Integer with default value and allowed range
71
+ foo_instances:
72
+ type: integer
73
+ default: 1
74
+ min: 1
75
+ max: 30
76
+ ```
77
+
78
+ ```yaml
79
+ # Uri validator
80
+ host_url:
81
+ type: uri
82
+ schemes:
83
+ - file # only allow file:/// uris
84
+ ```
85
+
86
+ ## Resolvers
87
+
88
+ Simple so far. Now let's mix in "resolvers" which can fetch the value from a number of sources or even generate new data:
89
+
90
+ ```yaml
91
+ # Generate random strings
92
+ vault_iv:
93
+ type: string
94
+ from:
95
+ random_string:
96
+ length: 64
97
+ charset: ascii_printable # Other charsets include hex, hex_upcase, alphanumeric, etc.
98
+ ```
99
+
100
+ ```yaml
101
+ # Try to get value from multiple sources
102
+ aws_secret:
103
+ type: string
104
+ strip: true # removes any leading / trailing whitespace from a string
105
+ upcase: true # turns the string to upcase
106
+ from:
107
+ env: 'FOOFOO'
108
+ file: /tmp/aws_secret.txt # if env is not set, try to read it from this file, raises if not readable
109
+
110
+ aws_secret:
111
+ type: string
112
+ strip: true # removes any leading / trailing whitespace from a string
113
+ upcase: true # turns the string to upcase
114
+ from:
115
+ env: FOOFOO
116
+ file: # if env is not set, try to read it from this file, returns nil if not readable
117
+ path: /tmp/aws_secret.txt
118
+ ignore_errors: true
119
+ random_string: 30 # not there either, generate a random string.
120
+ ```
121
+
122
+ ## Setters
123
+
124
+ Ok, so what to do with the values? There's setters for that.
125
+
126
+ ```yaml
127
+ aws_secret:
128
+ type: string
129
+ from:
130
+ env: AWS_TOKEN
131
+ to:
132
+ env: AWS_SECRET_TOKEN # once a valid value is set, set it to this variable.
133
+
134
+ # There aren't any more setters right now, but one could imagine setters such as
135
+ # output to a file, interpolate into a file, run a command, etc.
136
+ ```
137
+
138
+ ## Conditionals
139
+
140
+ There's also rudimentary conditional support:
141
+
142
+ ```yaml
143
+ - name: foo
144
+ type: string
145
+ value: 'hello'
146
+ - name: bar
147
+ type: integer
148
+ only_if: # only process if 'foo' has the value 'hello'
149
+ - foo: hello
150
+ ```
151
+
152
+ ```ruby
153
+ group.option('bar').skip?
154
+ => false
155
+ group.option('foo').value = 'world'
156
+ group.option('bar').skip?
157
+ => true
158
+ ```
159
+
160
+ ```yaml
161
+ - name: bar
162
+ type: integer
163
+ skip_if: # same but reverse, do not process if 'foo' has value 'hello'
164
+ - foo: 'hello'
165
+ ```
166
+
167
+ ```ruby
168
+ group.option('foo').value = 'world'
169
+ group.option('bar').skip?
170
+ => false
171
+ group.option('foo').value = 'hello'
172
+ group.option('bar').skip?
173
+ => true
174
+ ```
175
+
176
+ ```yaml
177
+ # These work too:
178
+
179
+ - name: bar
180
+ type: integer
181
+ skip_if:
182
+ - foo: hello # AND
183
+ - baz: world
184
+
185
+ - name: bar
186
+ type: integer
187
+ only_if: foo # process if foo is not null, false or 'false'
188
+
189
+ - name: bar
190
+ type: integer
191
+ only_if:
192
+ - foo # foo is not null
193
+ - baz # AND baz is not null
194
+ ```
195
+
196
+ Pretty nifty, right?
197
+
198
+ ## Examples
199
+
200
+ ```ruby
201
+ # Read definitions from 'options' key inside a YAML:
202
+ Opto.load('/tmp/stack.yml', :options)
203
+
204
+ # Read definitions from root of YAML
205
+ Opto.load('/tmp/stack.yml')
206
+
207
+ # Create an option group:
208
+ Opto.new( [ {name: 'foo', type: :string} ] )
209
+ # or
210
+ group = Opto::Group.new
211
+ group.build_option(name: 'foo', type: :string, value: "hello")
212
+ group.build_option(name: 'bar', type: :string, required: true)
213
+ group.first
214
+ => #<Opto::Option:xxx>
215
+ group.size
216
+ => 2
217
+ group.each { .. }
218
+ group.errors
219
+ => { 'bar' => { :presence => "Required value missing" } }
220
+ group.options_with_errors.each { ... }
221
+ group.valid?
222
+ => false
223
+ ```
224
+
225
+ ## Creating a custom resolver
226
+
227
+ Want to prompt for values? Try something like this:
228
+
229
+ ```ruby
230
+ # gem install tty-prompt
231
+ require 'tty-prompt'
232
+ class Prompter < Opto::Resolver
233
+ def resolve
234
+ # option = accessor to the option currently being resolved
235
+ # option.handler = accessor to the type handler
236
+ # hint = resolver options, for example the env variable name for env resolver, not used here.
237
+ return nil if option.skip?
238
+ if option.type == :enum
239
+ TTY::Prompt.new.select("Select #{option.label}") do |menu|
240
+ option.handler.options[:options].each do |opt| # quite ugly way to access the option's value list definition
241
+ menu.choice opt[:label], opt[:value]
242
+ end
243
+ end
244
+ else
245
+ TTY::Prompt.new.ask("Enter value for #{option.label}")
246
+ end
247
+ end
248
+ end
249
+
250
+ # And the option:
251
+ - name: foo
252
+ type: enum
253
+ options:
254
+ - foo: Foo
255
+ - bar: Bar
256
+ from: prompter
257
+ ```
258
+
259
+ ## Subclassing a predefined type handler, setter, etc
260
+
261
+ ```ruby
262
+ class VersionNumber < Opto::Types::String
263
+ Opto::Type.inherited(self) # need to call Opto::Type.inherited for registering the handler for now.
264
+
265
+ OPTIONS = Opto::Types::String::OPTIONS.merge(
266
+ min_version: nil,
267
+ max_version: nil
268
+ )
269
+
270
+ validate :min_version do |value|
271
+ if options[:min_version] && value < options[:min_version]
272
+ "Minimum version required: #{options[:min_version]}"
273
+ end
274
+ end
275
+
276
+ validate :max_version do |value|
277
+ if options[:max_version] && value > options[:max_version]
278
+ "Maximum version: #{options[:max_version]}, yours is #{value}"
279
+ end
280
+ end
281
+
282
+ sanitize :remove_build_info do |value|
283
+ value.split('+').first
284
+ end
285
+ end
286
+
287
+ # And to use:
288
+ > opt = Opto::Option.new(type: :version_number, name: 'foo', minimum_version: '1.0.0')
289
+ > opt.value = '0.1.0'
290
+ > opt.valid?
291
+ => false
292
+ > opt.errors
293
+ => { :validate_min_version => "Minimum version required: 1.0.0" }
294
+ ```
295
+
296
+ ## Default types
297
+
298
+ Global validations:
299
+
300
+ ```yaml
301
+ in: # only allow one of the following values
302
+ - a
303
+ - b
304
+ - c
305
+ ```
306
+
307
+ ### boolean
308
+
309
+ ```ruby
310
+ {
311
+ truthy: ['true', 'yes', '1', 'on', 'enabled', 'enable'], # These strings will be turned into true
312
+ nil_is: false, # If the value is null, set to false
313
+ blank_is: false, # If the value is a blank string, set to false
314
+ false: 'false', # When outputting, emit this value when value is false
315
+ true: 'true', # When outputting, emit this value when value is true
316
+ as: 'string' # Output a string, can be 'boolean' or 'integer'
317
+ }
318
+ ```
319
+
320
+ ### enum
321
+
322
+ ```ruby
323
+ {
324
+ options: [], # List of the possible option values
325
+ can_be_other: false # Or allow values outside the option list
326
+ }
327
+ ```
328
+
329
+ ### integer
330
+ ```ruby
331
+ {
332
+ min: 0, # minimum value, can be negative
333
+ max: nil, # maximum value
334
+ nil_is_zero: false # null value will be turned into zero
335
+ }
336
+ ```
337
+
338
+ ### string
339
+ ```ruby
340
+ {
341
+ min_length: nil, # minimum length
342
+ max_length: nil, # maximum length
343
+ empty_is_nil: true, # if string contains whitespace only, make value null
344
+ encode_64: false, # encode content to base64
345
+ decode_64: false, # decode content from base64
346
+ upcase: false, # convert to UPPERCASE
347
+ downcase: false, # convert to lowercase
348
+ strip: false, # remove leading/trailing whitespace,
349
+ chomp: false, # remove trailing linefeed
350
+ capitalize: false # convert to Capital case.
351
+ }
352
+ ```
353
+
354
+ ### uri
355
+ ```ruby
356
+ {
357
+ schemes: [ 'http', 'https' ] # only http and https urls are considered valid
358
+ }
359
+ ```
360
+
361
+ ## Default resolvers
362
+ Hint is the value that gets passed to the resolver when doing for example: `env: FOO` (FOO is the hint)
363
+
364
+ ### env
365
+ Hint is the environment variable name to read from. Defaults to the option's name.
366
+
367
+ ### file
368
+ Hint can be a string containing a path to the file, or a hash that defines `path: 'file_path', ignore_errors: true`
369
+
370
+ ### random_number
371
+ Hint must be a hash containing `min: minimum_number, max: maximum_number`
372
+
373
+ ### random_string
374
+ Hint can be a string/number that defines minimum length. Default charset is 'alphanumeric'
375
+ Hint can also be a hash that defines `length: length_of_generated_string, charset: 'charset_name'`
376
+
377
+ Defined charsets:
378
+ * numbers (0-9)
379
+ * letters (a-z + A-Z)
380
+ * downcase (a-z)
381
+ * upcase (A-Z)
382
+ * alphanumeric (0-9 + a-z + A-Z)
383
+ * hex (0-9 + a-f)
384
+ * hex_upcase (0-9 + A-F)
385
+ * base64 (base64 charset (length has to be divisible by four when using base64))
386
+ * ascii_printable (all printable ascii chars)
387
+ * or a set of characters, for example: { length: 8, charset: '01' } Will generate something like: 01001100
388
+
389
+ ### random_uuid
390
+ Ignores hint completely.
391
+
392
+ Output is a 'random' UUID generated by `SecureRandom.uuid`, such as `78b6decf-e312-45a1-ac8c-d562270036ba`
393
+
394
+ ## Default setters
395
+
396
+ ### env
397
+ Works exactly the same as env resolver, except in reverse.
398
+
399
+ ## Contributing
400
+
401
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kontena/opto. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
402
+
403
+
404
+ ## License
405
+
406
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
407
+