opto 1.8.7 → 1.9.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.
- checksums.yaml +5 -5
- data/.travis.yml +11 -8
- data/README.md +75 -8
- data/lib/opto/group.rb +55 -20
- data/lib/opto/option.rb +53 -14
- data/lib/opto/resolver.rb +0 -22
- data/lib/opto/resolvers/condition.rb +0 -4
- data/lib/opto/resolvers/evaluate.rb +1 -5
- data/lib/opto/resolvers/interpolate.rb +8 -12
- data/lib/opto/resolvers/yaml.rb +45 -0
- data/lib/opto/types/group.rb +28 -0
- data/lib/opto/version.rb +1 -1
- data/opto.gemspec +2 -2
- metadata +8 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 8d5546d31ee73e05adce64402dca900e55ef7159fcb277327fedf8bee0db2d00
         | 
| 4 | 
            +
              data.tar.gz: 8dcaaf5040b306b4997f01124f2597a500c230f0a32d2ca5071c9d80e2bb536c
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 683dd71495e9ea808933e58416d81fb8ebc815ec0b34834bf14e4d4a7595ce478417287369184928033b6b309ff89797a39a4da0418f5f324a314b09308e7fe0
         | 
| 7 | 
            +
              data.tar.gz: b4012a61a1a01b1a0ba7ef545956435939d329cd60400f37966b700a4ccc7e0f10c7223b19dd1a985a6383f5ce1b2d399c9914a411cdeb2d15b22597e4a383c9
         | 
    
        data/.travis.yml
    CHANGED
    
    | @@ -1,18 +1,21 @@ | |
| 1 1 | 
             
            language: ruby
         | 
| 2 2 | 
             
            rvm:
         | 
| 3 | 
            -
              - 2. | 
| 4 | 
            -
              - 2. | 
| 5 | 
            -
              - 2. | 
| 6 | 
            -
              - 2.4.1
         | 
| 3 | 
            +
              - 2.3
         | 
| 4 | 
            +
              - 2.4
         | 
| 5 | 
            +
              - 2.5
         | 
| 7 6 | 
             
            env:
         | 
| 8 7 | 
             
              - secure: "bCu1bVzr6SP+gAbMo88GvcHHbhO3LcnJtWp+YuSZ5qW4EHhdBw+DOQeOlzoKHtYhNhptmb/65pZZctRTE6jOOSZzpOUHaeqaqHZpELldkCMu7rE8bXuCV4OfhG0CKWvURS0x5p5F5onEx1a1sjiu+MVEUqktAPDAcdTBNBW1irRwvnSifgjcZRLzfm1aRs1fqzBJpHaAXyD++2GQaWyXDtQsEsc/Eyhmnkac5Y2cTeQiYlDluqBeB885q9K16ruLp2NWx/rnR8RGMP1LSnrye/GEo2mRWOWy1MyIEwNDcVaA6MVDY2GBIQUoXg4PBrZAleVIsO3LzwR4oorFCaSgWxzqYI67g5Kb2zn8fW9Yu5lDcFEJKIHQAwBrlq3n3Mi5it/IxGQDWXQ/2fjBxCwuilvI0WTzOH0m4g8Uf6jHaQLFC6ahdWDjfs6aCzdPpbJls26/r39NIB5k/6ZO29NZkxGBsQ7E7m4wurr7a9ksLODgPOyPiB8/4Txu35Llp+IuJpbshrlkRIMwEIFVWiLi6MZhRBkxaRzlkJQ0bS3RD9W4z9UJyoBujmgXMrv0eHNTGCOC+/4tvrA+pXzJwmFQJA6TnenoDl9hc6o03gyfH/YvmMgrtJLbJ8rGoLRaY8HsQwHWgimpCl1o043kO8/UkhhS7J4wyQXYwqGnA2PC/j0="
         | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 8 | 
            +
            bundler_args: --with test development --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
         | 
| 9 | 
            +
            before_install:
         | 
| 10 | 
            +
              - gem update --system
         | 
| 11 | 
            +
              - gem install bundler
         | 
| 12 | 
            +
            script: bundle exec rspec spec/
         | 
| 13 | 
            +
            cache:
         | 
| 14 | 
            +
              bundler: true
         | 
| 12 15 | 
             
            deploy:
         | 
| 13 16 | 
             
              provider: rubygems
         | 
| 14 17 | 
             
              api_key: $GEM_TOKEN
         | 
| 15 18 | 
             
              gem: opto
         | 
| 16 19 | 
             
              on:
         | 
| 17 20 | 
             
                tags: true
         | 
| 18 | 
            -
                rvm: 2. | 
| 21 | 
            +
                rvm: 2.5
         | 
    
        data/README.md
    CHANGED
    
    | @@ -45,10 +45,12 @@ require 'opto' | |
| 45 45 | 
             
              foo_username:
         | 
| 46 46 | 
             
                type: string
         | 
| 47 47 | 
             
                required: true
         | 
| 48 | 
            -
                 | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
                 | 
| 48 | 
            +
                validate:
         | 
| 49 | 
            +
                  min_length: 1
         | 
| 50 | 
            +
                  max_length: 30
         | 
| 51 | 
            +
                transform:
         | 
| 52 | 
            +
                  - strip # remove leading / trailing whitespace
         | 
| 53 | 
            +
                  - upcase # make UPCASE
         | 
| 52 54 | 
             
                from:
         | 
| 53 55 | 
             
                  env: FOO_USER # read value from ENV variable FOO_USER
         | 
| 54 56 | 
             
            ```
         | 
| @@ -72,16 +74,18 @@ require 'opto' | |
| 72 74 | 
             
              foo_instances:
         | 
| 73 75 | 
             
                type: integer
         | 
| 74 76 | 
             
                default: 1
         | 
| 75 | 
            -
                 | 
| 76 | 
            -
             | 
| 77 | 
            +
                validate:
         | 
| 78 | 
            +
                  min: 1
         | 
| 79 | 
            +
                  max: 30
         | 
| 77 80 | 
             
            ```
         | 
| 78 81 |  | 
| 79 82 | 
             
            ```yaml
         | 
| 80 83 | 
             
            # Uri validator
         | 
| 81 84 | 
             
              host_url:
         | 
| 82 85 | 
             
                type: uri
         | 
| 83 | 
            -
                 | 
| 84 | 
            -
                   | 
| 86 | 
            +
                validate:
         | 
| 87 | 
            +
                  schemes:
         | 
| 88 | 
            +
                    - file # only allow file:/// uris
         | 
| 85 89 | 
             
            ```
         | 
| 86 90 |  | 
| 87 91 | 
             
            ## Resolvers
         | 
| @@ -343,6 +347,14 @@ end | |
| 343 347 | 
             
              from: prompter
         | 
| 344 348 | 
             
            ```
         | 
| 345 349 |  | 
| 350 | 
            +
            You can also use procs:
         | 
| 351 | 
            +
             | 
| 352 | 
            +
            ```ruby
         | 
| 353 | 
            +
            group = Opto::Group.new(
         | 
| 354 | 
            +
              resolvers: { prompt: proc { |hint, option| print "Enter #{hint} (default: #{option.default}): "; gets }
         | 
| 355 | 
            +
            )
         | 
| 356 | 
            +
            ```
         | 
| 357 | 
            +
             | 
| 346 358 | 
             
            ## Subclassing a predefined type handler, setter, etc
         | 
| 347 359 |  | 
| 348 360 | 
             
            ```ruby
         | 
| @@ -459,6 +471,23 @@ Global validations: | |
| 459 471 | 
             
            }
         | 
| 460 472 | 
             
            ```
         | 
| 461 473 |  | 
| 474 | 
            +
            ### group
         | 
| 475 | 
            +
             | 
| 476 | 
            +
            Allows nesting of Opto::Groups:
         | 
| 477 | 
            +
             | 
| 478 | 
            +
            ```yaml
         | 
| 479 | 
            +
            subgroup:
         | 
| 480 | 
            +
              type: group
         | 
| 481 | 
            +
              value:
         | 
| 482 | 
            +
                subvariable:
         | 
| 483 | 
            +
                  type: string
         | 
| 484 | 
            +
                  value: world
         | 
| 485 | 
            +
            greeting:
         | 
| 486 | 
            +
              type: string
         | 
| 487 | 
            +
              from:
         | 
| 488 | 
            +
                interpolate: hello, ${subgroup.subvariable} # becomes hello, world
         | 
| 489 | 
            +
            ```
         | 
| 490 | 
            +
             | 
| 462 491 | 
             
            ## Default resolvers
         | 
| 463 492 | 
             
            Hint is the value that gets passed to the resolver when doing for example: `env: FOO` (FOO is the hint)
         | 
| 464 493 |  | 
| @@ -598,6 +627,44 @@ When an "else" is not defined and none of the conditions match, a null value wil | |
| 598 627 |  | 
| 599 628 | 
             
            The syntax for conditionals and complex conditionals is documented above in the chapter about conditionals.
         | 
| 600 629 |  | 
| 630 | 
            +
            ### yaml
         | 
| 631 | 
            +
             | 
| 632 | 
            +
            Hint is a hash defining filename or variable containing YAML source and optionally a key
         | 
| 633 | 
            +
             | 
| 634 | 
            +
            Example:
         | 
| 635 | 
            +
            ```yaml
         | 
| 636 | 
            +
            # Read a string value from a key in YAML file
         | 
| 637 | 
            +
            str:
         | 
| 638 | 
            +
              type: string
         | 
| 639 | 
            +
              from:
         | 
| 640 | 
            +
                yaml:
         | 
| 641 | 
            +
                  file: .env
         | 
| 642 | 
            +
                  key: STR
         | 
| 643 | 
            +
             | 
| 644 | 
            +
            # Read an array from a nested key in a YAML file
         | 
| 645 | 
            +
            str2:
         | 
| 646 | 
            +
              type: array
         | 
| 647 | 
            +
              from:
         | 
| 648 | 
            +
                yaml:
         | 
| 649 | 
            +
                  file: variables.yml
         | 
| 650 | 
            +
                  key: variables.str2.value # assuming { variables: { str2: { value: ["abcd", "defg"] } } }
         | 
| 651 | 
            +
             | 
| 652 | 
            +
            # Read a YAML file into a string
         | 
| 653 | 
            +
            yaml_content:
         | 
| 654 | 
            +
              type: string
         | 
| 655 | 
            +
              from:
         | 
| 656 | 
            +
                file: /etc/config.yml
         | 
| 657 | 
            +
             | 
| 658 | 
            +
            # Read YAML content from a variable and fetch a key from it
         | 
| 659 | 
            +
            str3:
         | 
| 660 | 
            +
              type: string
         | 
| 661 | 
            +
              from:
         | 
| 662 | 
            +
                yaml:
         | 
| 663 | 
            +
                  variable: yaml_content
         | 
| 664 | 
            +
                  key: settings.cpu_arch
         | 
| 665 | 
            +
            ```
         | 
| 666 | 
            +
             | 
| 667 | 
            +
             | 
| 601 668 | 
             
            ## Default setters
         | 
| 602 669 |  | 
| 603 670 | 
             
            ### env
         | 
    
        data/lib/opto/group.rb
    CHANGED
    
    | @@ -11,35 +11,52 @@ module Opto | |
| 11 11 |  | 
| 12 12 | 
             
                using Opto::Extension::HashStringOrSymbolKey
         | 
| 13 13 |  | 
| 14 | 
            -
                attr_reader :options, :defaults
         | 
| 15 | 
            -
             | 
| 16 14 | 
             
                extend Forwardable
         | 
| 17 15 |  | 
| 18 16 | 
             
                # Initialize a new Option Group. You can also pass in :defaults.
         | 
| 19 17 | 
             
                #
         | 
| 20 18 | 
             
                # @param [Array<Hash,Opto::Option>,Hash,NilClass] opts An array of Option definition hashes or Option objects or a hash like { var_name: { opts } }.
         | 
| 21 19 | 
             
                # @return [Opto::Group]
         | 
| 22 | 
            -
                def initialize(* | 
| 23 | 
            -
                   | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
                     | 
| 27 | 
            -
                     | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
                         | 
| 35 | 
            -
             | 
| 36 | 
            -
                        raise TypeError, "Invalid type #{options.first.class} for Opto::Group.new"
         | 
| 20 | 
            +
                def initialize(*opts)
         | 
| 21 | 
            +
                  case opts.first
         | 
| 22 | 
            +
                  when NilClass
         | 
| 23 | 
            +
                  when Hash
         | 
| 24 | 
            +
                    defaults.merge!(opts.first.delete(:defaults)) if opts.first.key?(:defaults)
         | 
| 25 | 
            +
                    setters.merge!(opts.first.delete(:setters)) if opts.first.key?(:setters)
         | 
| 26 | 
            +
                    resolvers.merge!(opts.first.delete(:resolvers)) if opts.first.key?(:resolvers)
         | 
| 27 | 
            +
                    options.concat(opts.first.map {|k,v| Option.new({name: k.to_s, group: self}.merge(v))})
         | 
| 28 | 
            +
                  when ::Array
         | 
| 29 | 
            +
                    if opts.last.is_a?(Hash) && !opts.last.key?(:type)
         | 
| 30 | 
            +
                      opts.pop.tap do |settings|
         | 
| 31 | 
            +
                        defaults.merge!(settings[:defaults]) if settings.key?(:defaults)
         | 
| 32 | 
            +
                        setters.merge!(settings[:setters]) if settings.key?(:setters)
         | 
| 33 | 
            +
                        resolvers.merge!(settings[:resolvers]) if settings.key?(:resolvers)
         | 
| 37 34 | 
             
                      end
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                    if opts.first.kind_of?(Array)
         | 
| 37 | 
            +
                      options.concat(opts.first.map { |opt| opt.kind_of?(Opto::Option) ? opt : Option.new(opt.merge(group: self)) })
         | 
| 38 | 
            +
                    end
         | 
| 38 39 | 
             
                  else
         | 
| 39 | 
            -
                     | 
| 40 | 
            +
                    raise TypeError, "Invalid type #{opts.class} for Opto::Group.new"
         | 
| 40 41 | 
             
                  end
         | 
| 41 42 | 
             
                end
         | 
| 42 43 |  | 
| 44 | 
            +
                def options
         | 
| 45 | 
            +
                  @options ||= []
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def setters
         | 
| 49 | 
            +
                  @setters ||= {}
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def resolvers
         | 
| 53 | 
            +
                  @resolvers ||= {}
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def defaults
         | 
| 57 | 
            +
                  @defaults ||= {}
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 43 60 | 
             
                # Are all options valid? (Option value passes validation)
         | 
| 44 61 | 
             
                # @return [Boolean]
         | 
| 45 62 | 
             
                def valid?
         | 
| @@ -91,7 +108,25 @@ module Opto | |
| 91 108 | 
             
                # @param [String] option_name
         | 
| 92 109 | 
             
                # @return [Opto::Option]
         | 
| 93 110 | 
             
                def option(option_name)
         | 
| 94 | 
            -
                   | 
| 111 | 
            +
                  if option_name.to_s.include?('.')
         | 
| 112 | 
            +
                    parts = option_name.to_s.split('.')
         | 
| 113 | 
            +
                    var_name = parts.pop
         | 
| 114 | 
            +
                    group = parts.inject(self) do |base, part|
         | 
| 115 | 
            +
                      grp = base.option(part).value
         | 
| 116 | 
            +
                      if grp.nil?
         | 
| 117 | 
            +
                        raise NameError, "No such group: #{base.name}.#{part}"
         | 
| 118 | 
            +
                      elsif grp.kind_of?(Opto::Group)
         | 
| 119 | 
            +
                        grp
         | 
| 120 | 
            +
                      else
         | 
| 121 | 
            +
                        raise TypeError, "Is not a group: #{base.name}.#{part}"
         | 
| 122 | 
            +
                      end
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
                  else
         | 
| 125 | 
            +
                    group = self
         | 
| 126 | 
            +
                    var_name = option_name
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  group.options.find { |opt| opt.name == var_name }
         | 
| 95 130 | 
             
                end
         | 
| 96 131 |  | 
| 97 132 | 
             
                # Get a value of a member by option name
         | 
| @@ -180,6 +215,6 @@ module Opto | |
| 180 215 | 
             
                  end
         | 
| 181 216 | 
             
                end
         | 
| 182 217 |  | 
| 183 | 
            -
                def_delegators  | 
| 218 | 
            +
                def_delegators :options, *(::Array.instance_methods - [:__send__, :object_id, :to_h, :to_a, :is_a?, :kind_of?, :instance_of?, :self, :inspect, :nil?])
         | 
| 184 219 | 
             
              end
         | 
| 185 220 | 
             
            end
         | 
    
        data/lib/opto/option.rb
    CHANGED
    
    | @@ -86,12 +86,27 @@ module Opto | |
| 86 86 | 
             
                  @only_if       = opts.delete(:only_if)
         | 
| 87 87 | 
             
                  @from          = normalize_from_to(opts.delete(:from))
         | 
| 88 88 | 
             
                  @to            = normalize_from_to(opts.delete(:to))
         | 
| 89 | 
            -
                   | 
| 89 | 
            +
                  validations    = opts.delete(:validate).to_h
         | 
| 90 | 
            +
                  transforms     = opts.delete(:transform)
         | 
| 91 | 
            +
                  transforms     = case transforms
         | 
| 92 | 
            +
                                   when NilClass then {}
         | 
| 93 | 
            +
                                   when Hash then transforms
         | 
| 94 | 
            +
                                   when Array then
         | 
| 95 | 
            +
                                     transforms.each_with_object({}) { |t, hash| hash[t] = true }
         | 
| 96 | 
            +
                                   else
         | 
| 97 | 
            +
                                     raise TypeError, 'Transform has to be a hash or an array'
         | 
| 98 | 
            +
                                   end
         | 
| 99 | 
            +
                  @type_options  = opts.merge(validations).merge(transforms)
         | 
| 100 | 
            +
                  @tried_resolve = false
         | 
| 90 101 |  | 
| 91 102 | 
             
                  set_initial(val) if val
         | 
| 92 103 | 
             
                  deep_merge_defaults
         | 
| 93 104 | 
             
                end
         | 
| 94 105 |  | 
| 106 | 
            +
                def has_group?
         | 
| 107 | 
            +
                  !group.nil?
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 95 110 | 
             
                def deep_merge_defaults
         | 
| 96 111 | 
             
                  return nil unless group && group.defaults
         | 
| 97 112 | 
             
                  if group.defaults[:from]
         | 
| @@ -140,7 +155,7 @@ module Opto | |
| 140 155 | 
             
                # Returns true if this field should not be processed because of the conditionals
         | 
| 141 156 | 
             
                # @return [Boolean]
         | 
| 142 157 | 
             
                def skip?
         | 
| 143 | 
            -
                  return false  | 
| 158 | 
            +
                  return false unless has_group?
         | 
| 144 159 | 
             
                  return true if group.any_true?(skip_if)
         | 
| 145 160 | 
             
                  return true unless group.all_true?(only_if)
         | 
| 146 161 | 
             
                  false
         | 
| @@ -150,7 +165,8 @@ module Opto | |
| 150 165 | 
             
                # @param [String] option_name
         | 
| 151 166 | 
             
                def value_of(option_name)
         | 
| 152 167 | 
             
                  return value if option_name == self.name
         | 
| 153 | 
            -
                   | 
| 168 | 
            +
                  return nil unless has_group?
         | 
| 169 | 
            +
                  group.value_of(option_name)
         | 
| 154 170 | 
             
                end
         | 
| 155 171 |  | 
| 156 172 | 
             
                # Run validators
         | 
| @@ -181,11 +197,11 @@ module Opto | |
| 181 197 | 
             
                # Accessor to defined resolvers for this option.
         | 
| 182 198 | 
             
                # @return [Array<Opto::Resolver>]
         | 
| 183 199 | 
             
                def resolvers
         | 
| 184 | 
            -
                  @resolvers ||= from.merge(default: self).map { |origin, hint| Resolver.for(origin) | 
| 200 | 
            +
                  @resolvers ||= from.merge(default: self).map { |origin, hint| { origin: origin, hint: hint, resolver: ((has_group? && group.resolvers[origin]) || Resolver.for(origin)) } }
         | 
| 185 201 | 
             
                end
         | 
| 186 202 |  | 
| 187 203 | 
             
                def setters
         | 
| 188 | 
            -
                  @setters ||= to.map { |target, hint| Setter.for(target) | 
| 204 | 
            +
                  @setters ||= to.map { |target, hint| { target: target, hint: hint, setter: ((has_group? && group.setters[target]) || Setter.for(target)) } }
         | 
| 189 205 | 
             
                end
         | 
| 190 206 |  | 
| 191 207 | 
             
                # True if this field is defined as required: true
         | 
| @@ -194,32 +210,55 @@ module Opto | |
| 194 210 | 
             
                  handler.required?
         | 
| 195 211 | 
             
                end
         | 
| 196 212 |  | 
| 213 | 
            +
                def tried_resolve?
         | 
| 214 | 
            +
                  @tried_resolve
         | 
| 215 | 
            +
                end
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                def set_tried
         | 
| 218 | 
            +
                  @tried_resolve = true
         | 
| 219 | 
            +
                end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                def unset_tried!
         | 
| 222 | 
            +
                  @tried_resolve = false
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
             | 
| 197 225 | 
             
                # Run resolvers
         | 
| 198 226 | 
             
                # @raise [TypeError, ArgumentError]
         | 
| 199 227 | 
             
                def resolve
         | 
| 200 | 
            -
                   | 
| 228 | 
            +
                  return nil if tried_resolve?
         | 
| 229 | 
            +
                  resolvers.each do |resolver_config|
         | 
| 201 230 | 
             
                    begin
         | 
| 202 | 
            -
                       | 
| 231 | 
            +
                      resolver = resolver_config[:resolver]
         | 
| 232 | 
            +
                      if resolver.respond_to?(:call)
         | 
| 233 | 
            +
                        result = resolver.call(resolver_config[:hint], self)
         | 
| 234 | 
            +
                      else
         | 
| 235 | 
            +
                        result = resolver.new(resolver_config[:hint], self).resolve
         | 
| 236 | 
            +
                      end
         | 
| 203 237 | 
             
                    rescue StandardError => ex
         | 
| 204 | 
            -
                      raise ex, "Resolver '#{ | 
| 238 | 
            +
                      raise ex, "Resolver '#{resolver_config[:origin]}' for '#{name}' : #{ex.message}"
         | 
| 205 239 | 
             
                    end
         | 
| 206 240 | 
             
                    unless result.nil?
         | 
| 207 | 
            -
                      @origin =  | 
| 241 | 
            +
                      @origin = resolver_config[:origin]
         | 
| 208 242 | 
             
                      return result
         | 
| 209 243 | 
             
                    end
         | 
| 210 244 | 
             
                  end
         | 
| 211 245 | 
             
                  nil
         | 
| 246 | 
            +
                ensure
         | 
| 247 | 
            +
                  set_tried
         | 
| 212 248 | 
             
                end
         | 
| 213 249 |  | 
| 214 250 | 
             
                # Run setters
         | 
| 215 251 | 
             
                def output
         | 
| 216 | 
            -
                  setters.each do | | 
| 252 | 
            +
                  setters.each do |setter_config|
         | 
| 217 253 | 
             
                    begin
         | 
| 218 | 
            -
                      setter | 
| 219 | 
            -
                      setter. | 
| 220 | 
            -
             | 
| 254 | 
            +
                      setter = setter_config[:setter]
         | 
| 255 | 
            +
                      if setter.respond_to?(:call)
         | 
| 256 | 
            +
                        setter.call(setter_config[:hint], value, self)
         | 
| 257 | 
            +
                      else
         | 
| 258 | 
            +
                        setter.new(setter_config[:hint], self).set(value)
         | 
| 259 | 
            +
                      end
         | 
| 221 260 | 
             
                    rescue StandardError => ex
         | 
| 222 | 
            -
                      raise ex, "Setter '#{ | 
| 261 | 
            +
                      raise ex, "Setter '#{setter_config[:target]}' for '#{name}' : #{ex.message}"
         | 
| 223 262 | 
             
                    end
         | 
| 224 263 | 
             
                  end
         | 
| 225 264 | 
             
                end
         | 
    
        data/lib/opto/resolver.rb
    CHANGED
    
    | @@ -43,28 +43,6 @@ module Opto | |
| 43 43 | 
             
                def initialize(hint = nil, option = nil)
         | 
| 44 44 | 
             
                  @hint = hint
         | 
| 45 45 | 
             
                  @option = option
         | 
| 46 | 
            -
                  @tried = false
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                def tried?
         | 
| 50 | 
            -
                  !!@tried
         | 
| 51 | 
            -
                end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                def set_tried
         | 
| 54 | 
            -
                  @tried = true
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                def reset_tried
         | 
| 58 | 
            -
                  @tried = false
         | 
| 59 | 
            -
                end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                def try_resolve
         | 
| 62 | 
            -
                  return nil if tried?
         | 
| 63 | 
            -
                  set_tried
         | 
| 64 | 
            -
                  self.respond_to?(:before) && self.before
         | 
| 65 | 
            -
                  result = resolve
         | 
| 66 | 
            -
                  self.respond_to?(:after) && self.after
         | 
| 67 | 
            -
                  result
         | 
| 68 46 | 
             
                end
         | 
| 69 47 |  | 
| 70 48 | 
             
                # This is a "base" class, you're supposed to inherit from this in your resolver and define a #resolve method.
         | 
| @@ -10,7 +10,7 @@ module Opto | |
| 10 10 |  | 
| 11 11 | 
             
                  def resolve
         | 
| 12 12 | 
             
                    raise TypeError, "String required" unless hint.kind_of?(String)
         | 
| 13 | 
            -
                    interpolated_hint = hint.gsub(/(?<!\$)\$(?!\$)\{ | 
| 13 | 
            +
                    interpolated_hint = hint.gsub(/(?<!\$)\$(?!\$)\{?[\w\.]+\}?/) do |v|
         | 
| 14 14 | 
             
                      var = v.tr('${}', '')
         | 
| 15 15 | 
             
                      if option.group.nil? || option.group.option(var).nil?
         | 
| 16 16 | 
             
                        raise RuntimeError, "Variable #{var} not declared"
         | 
| @@ -27,10 +27,6 @@ module Opto | |
| 27 27 | 
             
                      raise TypeError, "Syntax error: '#{interpolated_hint}' does not look like a number or a calculation"
         | 
| 28 28 | 
             
                    end
         | 
| 29 29 | 
             
                  end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                  def after
         | 
| 32 | 
            -
                    reset_tried
         | 
| 33 | 
            -
                  end
         | 
| 34 30 | 
             
                end
         | 
| 35 31 | 
             
              end
         | 
| 36 32 | 
             
            end
         | 
| @@ -14,20 +14,16 @@ module Opto | |
| 14 14 |  | 
| 15 15 | 
             
                  def resolve
         | 
| 16 16 | 
             
                    raise TypeError, "String expected" unless hint.kind_of?(String)
         | 
| 17 | 
            -
                    hint.gsub(/(?<!\$)\$(?!\$)\{ | 
| 17 | 
            +
                    hint.gsub(/(?<!\$)\$(?!\$)\{?[\w\.]+\}?/) do |v|
         | 
| 18 18 | 
             
                      var = v.tr('${}', '')
         | 
| 19 | 
            -
                      if option.group.nil? || option.group.option(var).nil?
         | 
| 20 | 
            -
                        raise RuntimeError, "Variable #{var} not declared"
         | 
| 21 | 
            -
                      end
         | 
| 22 | 
            -
                      if option.value_of(var).nil?
         | 
| 23 | 
            -
                        raise RuntimeError, "No value for #{var}, note that the order is meaningful"
         | 
| 24 | 
            -
                      end
         | 
| 25 | 
            -
                      option.value_of(var)
         | 
| 26 | 
            -
                    end
         | 
| 27 | 
            -
                  end
         | 
| 28 19 |  | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 20 | 
            +
                      raise RuntimeError, "Variable #{var} not declared" if option.group.nil?
         | 
| 21 | 
            +
                      opt = option.group.option(var)
         | 
| 22 | 
            +
                      raise RuntimeError, "Variable #{var} not declared" if opt.nil?
         | 
| 23 | 
            +
                      value = opt.value
         | 
| 24 | 
            +
                      raise RuntimeError, "No value for #{var}, note that the order is meaningful" if value.nil?
         | 
| 25 | 
            +
                      value
         | 
| 26 | 
            +
                    end
         | 
| 31 27 | 
             
                  end
         | 
| 32 28 | 
             
                end
         | 
| 33 29 | 
             
              end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'opto/extensions/snake_case'
         | 
| 2 | 
            +
            require 'opto/extensions/hash_string_or_symbol_key'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Opto
         | 
| 5 | 
            +
              module Resolvers
         | 
| 6 | 
            +
                # Loads values from YAML files
         | 
| 7 | 
            +
                #
         | 
| 8 | 
            +
                # Example:
         | 
| 9 | 
            +
                # from:
         | 
| 10 | 
            +
                #   yaml:
         | 
| 11 | 
            +
                #     file: foofoo.yml
         | 
| 12 | 
            +
                #     key: foo
         | 
| 13 | 
            +
                class Yaml < Opto::Resolver
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  using Opto::Extension::HashStringOrSymbolKey
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def resolve
         | 
| 18 | 
            +
                    raise TypeError, "Hash expected" unless hint.kind_of?(Hash)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    require 'yaml' unless Kernel.const_defined?(:YAML)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    if hint[:file]
         | 
| 23 | 
            +
                      yaml = YAML.safe_load(::File.read(hint[:file]), [], [], true, hint[:file])
         | 
| 24 | 
            +
                    elsif hint[:variable]
         | 
| 25 | 
            +
                      raise TypeError, "Option not in a group" unless option.has_group?
         | 
| 26 | 
            +
                      other_opt = option.group.option(hint[:variable])
         | 
| 27 | 
            +
                      raise ArgumentError, "No such option: #{hint[:variable]}" if other_opt.nil?
         | 
| 28 | 
            +
                      yaml = YAML.safe_load(other_opt.value.to_s, [], [], true, hint[:variable])
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      raise TypeError, "Missing file/variable definition"
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                    if hint[:key]
         | 
| 33 | 
            +
                      raise TypeError, "Source is not a hash" unless yaml.kind_of?(Hash)
         | 
| 34 | 
            +
                      if yaml.key?(hint[:key])
         | 
| 35 | 
            +
                        yaml[hint[:key]]
         | 
| 36 | 
            +
                      elsif hint[:key].include?('.')
         | 
| 37 | 
            +
                        yaml.dig(*hint[:key].split('.'))
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
                    else
         | 
| 40 | 
            +
                      yaml
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            require_relative '../type'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Opto
         | 
| 4 | 
            +
              module Types
         | 
| 5 | 
            +
                # A subgroup
         | 
| 6 | 
            +
                class Group < Opto::Type
         | 
| 7 | 
            +
                  using Opto::Extension::HashStringOrSymbolKey
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  true_when do |value|
         | 
| 10 | 
            +
                    value.kind_of?(Opto::Group) && !value.empty?
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  sanitizer :init do |value|
         | 
| 14 | 
            +
                    if options[:variables]
         | 
| 15 | 
            +
                      Opto::Group.new(options[:variables])
         | 
| 16 | 
            +
                    elsif value.kind_of?(::Hash) || value.kind_of?(::Array)
         | 
| 17 | 
            +
                      Opto::Group.new(value)
         | 
| 18 | 
            +
                    elsif value.kind_of?(Opto::Group)
         | 
| 19 | 
            +
                      value
         | 
| 20 | 
            +
                    elsif value.nil?
         | 
| 21 | 
            +
                      Opto::Group.new
         | 
| 22 | 
            +
                    else
         | 
| 23 | 
            +
                      raise TypeError, "Invalid type #{value.class.name} for a group"
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
    
        data/lib/opto/version.rb
    CHANGED
    
    
    
        data/opto.gemspec
    CHANGED
    
    | @@ -18,9 +18,9 @@ Gem::Specification.new do |spec| | |
| 18 18 | 
             
              spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
         | 
| 19 19 | 
             
              spec.require_paths = ["lib"]
         | 
| 20 20 |  | 
| 21 | 
            -
              spec.required_ruby_version = ">= 2. | 
| 21 | 
            +
              spec.required_ruby_version = ">= 2.3.0"
         | 
| 22 22 |  | 
| 23 23 | 
             
              spec.add_development_dependency "bundler", "~> 1.12"
         | 
| 24 | 
            -
              spec.add_development_dependency "rake", " | 
| 24 | 
            +
              spec.add_development_dependency "rake", ">= 10.0"
         | 
| 25 25 | 
             
              spec.add_development_dependency "rspec", "~> 3.0"
         | 
| 26 26 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: opto
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.9.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Kimmo Lehto
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2018-05-22 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -28,14 +28,14 @@ dependencies: | |
| 28 28 | 
             
              name: rake
         | 
| 29 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 30 | 
             
                requirements:
         | 
| 31 | 
            -
                - - " | 
| 31 | 
            +
                - - ">="
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 33 | 
             
                    version: '10.0'
         | 
| 34 34 | 
             
              type: :development
         | 
| 35 35 | 
             
              prerelease: false
         | 
| 36 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 37 | 
             
                requirements:
         | 
| 38 | 
            -
                - - " | 
| 38 | 
            +
                - - ">="
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 40 | 
             
                    version: '10.0'
         | 
| 41 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| @@ -83,12 +83,14 @@ files: | |
| 83 83 | 
             
            - lib/opto/resolvers/random_string.rb
         | 
| 84 84 | 
             
            - lib/opto/resolvers/random_uuid.rb
         | 
| 85 85 | 
             
            - lib/opto/resolvers/variable.rb
         | 
| 86 | 
            +
            - lib/opto/resolvers/yaml.rb
         | 
| 86 87 | 
             
            - lib/opto/setter.rb
         | 
| 87 88 | 
             
            - lib/opto/setters/environment_variable.rb
         | 
| 88 89 | 
             
            - lib/opto/type.rb
         | 
| 89 90 | 
             
            - lib/opto/types/array.rb
         | 
| 90 91 | 
             
            - lib/opto/types/boolean.rb
         | 
| 91 92 | 
             
            - lib/opto/types/enum.rb
         | 
| 93 | 
            +
            - lib/opto/types/group.rb
         | 
| 92 94 | 
             
            - lib/opto/types/integer.rb
         | 
| 93 95 | 
             
            - lib/opto/types/string.rb
         | 
| 94 96 | 
             
            - lib/opto/types/uri.rb
         | 
| @@ -106,7 +108,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 106 108 | 
             
              requirements:
         | 
| 107 109 | 
             
              - - ">="
         | 
| 108 110 | 
             
                - !ruby/object:Gem::Version
         | 
| 109 | 
            -
                  version: 2. | 
| 111 | 
            +
                  version: 2.3.0
         | 
| 110 112 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 111 113 | 
             
              requirements:
         | 
| 112 114 | 
             
              - - ">="
         | 
| @@ -114,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 114 116 | 
             
                  version: '0'
         | 
| 115 117 | 
             
            requirements: []
         | 
| 116 118 | 
             
            rubyforge_project: 
         | 
| 117 | 
            -
            rubygems_version: 2. | 
| 119 | 
            +
            rubygems_version: 2.7.7
         | 
| 118 120 | 
             
            signing_key: 
         | 
| 119 121 | 
             
            specification_version: 4
         | 
| 120 122 | 
             
            summary: Option validator / resolver
         |