opto 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+