trinkets 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +3 -0
- data/doc/class/init.md +45 -16
- data/lib/explicit/trinkets/extend/class/init.rb +152 -71
- data/lib/trinkets/version.rb +1 -1
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2e815777ee25a04feffd792fc975e68256e346eb3ff77126bd3610b949deadb2
         | 
| 4 | 
            +
              data.tar.gz: 77a0c78f5dc7699e2d90e7db69333b246023145c66e408901ae328d29d377385
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e57c30e56c22137542e58d2128d9a8ddb31e78acca1756c3337fa54a906046790997fecb8737b2c442528920756cfc8ccab465ac47489bb44257c4cecb9c9d03
         | 
| 7 | 
            +
              data.tar.gz: 49ed92fdb96d888727ebc972cd71520f08fb603cad2345d9832cf9f8704d99e979cf02cd10c268dea5c12e8681f4bccdd49b8c9eca7a68a2025db002b349c74b
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        data/doc/class/init.md
    CHANGED
    
    | @@ -14,6 +14,9 @@ To use it, define a class and call `::init` like you would call `::attr` methods | |
| 14 14 | 
             
                * when it's a hash, like `{ default: <VALUE> }`, it's an optional keyword argument
         | 
| 15 15 | 
             
                * an empty hash `{}` is equivalent to `{ default: nil }`
         | 
| 16 16 | 
             
                * defaults to `false`
         | 
| 17 | 
            +
              * `super` : if the argument should be passed to `super()`
         | 
| 18 | 
            +
                * the super class can have a `initialize` or call `init`
         | 
| 19 | 
            +
                * default: `false`
         | 
| 17 20 |  | 
| 18 21 | 
             
            The same options can be used per individual argument.
         | 
| 19 22 |  | 
| @@ -44,8 +47,9 @@ end | |
| 44 47 | 
             
            class Test
         | 
| 45 48 | 
             
              attr_accessor :a, :b
         | 
| 46 49 | 
             
              def initialize(a, b)
         | 
| 47 | 
            -
                 | 
| 48 | 
            -
                @ | 
| 50 | 
            +
                super()
         | 
| 51 | 
            +
                @a = a unless instance_variable_defined?(:@a)
         | 
| 52 | 
            +
                @b = b unless instance_variable_defined?(:@b)
         | 
| 49 53 | 
             
              end
         | 
| 50 54 | 
             
            end
         | 
| 51 55 |  | 
| @@ -71,8 +75,9 @@ end | |
| 71 75 | 
             
            class TestAttr
         | 
| 72 76 | 
             
              attr_reader :a, :b
         | 
| 73 77 | 
             
              def initialize(a, b)
         | 
| 74 | 
            -
                 | 
| 75 | 
            -
                @ | 
| 78 | 
            +
                super()
         | 
| 79 | 
            +
                @a = a unless instance_variable_defined?(:@a)
         | 
| 80 | 
            +
                @b = b unless instance_variable_defined?(:@b)
         | 
| 76 81 | 
             
              end
         | 
| 77 82 | 
             
            end
         | 
| 78 83 |  | 
| @@ -98,8 +103,9 @@ end | |
| 98 103 | 
             
            class TestKW
         | 
| 99 104 | 
             
              attr_accessor :a, :b
         | 
| 100 105 | 
             
              def initialize(a:, b:)
         | 
| 101 | 
            -
                 | 
| 102 | 
            -
                @ | 
| 106 | 
            +
                super()
         | 
| 107 | 
            +
                @a = a unless instance_variable_defined?(:@a)
         | 
| 108 | 
            +
                @b = b unless instance_variable_defined?(:@b)
         | 
| 103 109 | 
             
              end
         | 
| 104 110 | 
             
            end
         | 
| 105 111 |  | 
| @@ -126,10 +132,11 @@ class TestAttrOptions | |
| 126 132 | 
             
              attr_reader :a
         | 
| 127 133 | 
             
              attr_accessor :b, :c
         | 
| 128 134 | 
             
              def initialize(b, d, a:, c:)
         | 
| 129 | 
            -
                 | 
| 130 | 
            -
                @ | 
| 131 | 
            -
                @ | 
| 132 | 
            -
                @ | 
| 135 | 
            +
                super()
         | 
| 136 | 
            +
                @a = a unless instance_variable_defined?(:@a)
         | 
| 137 | 
            +
                @b = b unless instance_variable_defined?(:@b)
         | 
| 138 | 
            +
                @c = c unless instance_variable_defined?(:@c)
         | 
| 139 | 
            +
                @d = d unless instance_variable_defined?(:@d)
         | 
| 133 140 | 
             
              end
         | 
| 134 141 | 
             
            end
         | 
| 135 142 |  | 
| @@ -152,7 +159,6 @@ test.a = 5 | |
| 152 159 | 
             
            ```
         | 
| 153 160 |  | 
| 154 161 | 
             
            ## Default values for keyword arguments
         | 
| 155 | 
            -
             | 
| 156 162 | 
             
            ```ruby
         | 
| 157 163 | 
             
            class TestDefaultKw
         | 
| 158 164 | 
             
              init [:a, kw: true],
         | 
| @@ -163,9 +169,10 @@ end | |
| 163 169 | 
             
            # would be the same as
         | 
| 164 170 | 
             
            class TestDefaultKw
         | 
| 165 171 | 
             
              attr_accessor :a, :b
         | 
| 166 | 
            -
              def initialize(a | 
| 167 | 
            -
                 | 
| 168 | 
            -
                @ | 
| 172 | 
            +
              def initialize(a:, b: 3)
         | 
| 173 | 
            +
                super()
         | 
| 174 | 
            +
                @a = a unless instance_variable_defined?(:@a)
         | 
| 175 | 
            +
                @b = b unless instance_variable_defined?(:@b)
         | 
| 169 176 | 
             
              end
         | 
| 170 177 | 
             
            end
         | 
| 171 178 |  | 
| @@ -178,6 +185,27 @@ test.b | |
| 178 185 | 
             
            # 3
         | 
| 179 186 | 
             
            ``` 
         | 
| 180 187 |  | 
| 188 | 
            +
            ## Super
         | 
| 189 | 
            +
            ```ruby
         | 
| 190 | 
            +
            class TestParent
         | 
| 191 | 
            +
              init :a # also works with a plain initialize()
         | 
| 192 | 
            +
            end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
            class TestChild
         | 
| 195 | 
            +
              init [:a, super: true], :b
         | 
| 196 | 
            +
            end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            # would be the same as
         | 
| 199 | 
            +
            class TestChild
         | 
| 200 | 
            +
              attr_accessor :a, :b
         | 
| 201 | 
            +
              def initialize(a, b)
         | 
| 202 | 
            +
                super(a)
         | 
| 203 | 
            +
                @a = a unless instance_variable_defined?(:@a)
         | 
| 204 | 
            +
                @b = b unless instance_variable_defined?(:@b)
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
            end
         | 
| 207 | 
            +
            ```
         | 
| 208 | 
            +
             | 
| 181 209 | 
             
            ## Mixed together
         | 
| 182 210 | 
             
            ```ruby
         | 
| 183 211 | 
             
            class TestMixed
         | 
| @@ -191,8 +219,9 @@ class TestMixed | |
| 191 219 | 
             
              attr_reader :a
         | 
| 192 220 | 
             
              attr_accessor :b
         | 
| 193 221 | 
             
              def initialize(a:, b:)
         | 
| 194 | 
            -
                 | 
| 195 | 
            -
                @ | 
| 222 | 
            +
                super()
         | 
| 223 | 
            +
                @a = a unless instance_variable_defined?(:@a)
         | 
| 224 | 
            +
                @b = b unless instance_variable_defined?(:@b)
         | 
| 196 225 | 
             
              end
         | 
| 197 226 | 
             
            end
         | 
| 198 227 |  | 
| @@ -5,107 +5,188 @@ module Trinkets | |
| 5 5 | 
             
                module Init
         | 
| 6 6 | 
             
                  ATTR = %i[accessor reader writer none].freeze
         | 
| 7 7 |  | 
| 8 | 
            -
                   | 
| 9 | 
            -
                     | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
                       | 
| 14 | 
            -
             | 
| 15 | 
            -
                      end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                    # even though options like `kw` aren't used, they serve here to validate the `attrs` options
         | 
| 18 | 
            -
                    attr_init = ->(name, attr: ATTR.first, kw: false) do
         | 
| 19 | 
            -
                      unless ATTR.include?(attr)
         | 
| 20 | 
            -
                        raise ArgumentError, "wrong `attr` type for `#{name.inspect}` (given #{attr.inspect}, expected :accessor (default), :reader, :writer or :none)"
         | 
| 21 | 
            -
                      end
         | 
| 22 | 
            -
                      attr_methods[attr].call(name) unless attr == :none
         | 
| 8 | 
            +
                  class Parameter
         | 
| 9 | 
            +
                    attr_reader :name, :attr, :kw, :super
         | 
| 10 | 
            +
                    def initialize(name:, attr:, kw:, super:)
         | 
| 11 | 
            +
                      @name  = name
         | 
| 12 | 
            +
                      @attr  = attr
         | 
| 13 | 
            +
                      @kw    = kw
         | 
| 14 | 
            +
                      @super = [super:].first[:super]
         | 
| 23 15 | 
             
                    end
         | 
| 16 | 
            +
                  end
         | 
| 24 17 |  | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
                    # hash with 3 keys: {
         | 
| 28 | 
            -
                    #   FalseClass => []  # positional args
         | 
| 29 | 
            -
                    #   TrueClass  => []  # mandatory kw args
         | 
| 30 | 
            -
                    #   Hash       => []  # optional kw args with default value
         | 
| 31 | 
            -
                    # }
         | 
| 32 | 
            -
                    grouped_params = attrs
         | 
| 33 | 
            -
                      .map { |name, opts| [name, opts[:kw] || false] }
         | 
| 34 | 
            -
                      .group_by { _1.last.class }
         | 
| 18 | 
            +
                  class BoundParameter < Parameter
         | 
| 35 19 |  | 
| 36 | 
            -
                     | 
| 37 | 
            -
                    kw_params     = [*grouped_params[TrueClass]].map(&:first)
         | 
| 38 | 
            -
                    opt_kw_params = [*grouped_params[Hash]].to_h
         | 
| 39 | 
            -
                      .transform_values! { _1[:default] }
         | 
| 20 | 
            +
                    attr_reader :value
         | 
| 40 21 |  | 
| 41 | 
            -
                     | 
| 42 | 
            -
             | 
| 22 | 
            +
                    def initialize(name:, attr:, kw:, super:, value:)
         | 
| 23 | 
            +
                      super(name:, attr:, kw:, super:)
         | 
| 24 | 
            +
                      @value = value
         | 
| 25 | 
            +
                    end
         | 
| 43 26 | 
             
                  end
         | 
| 44 27 |  | 
| 45 | 
            -
                   | 
| 46 | 
            -
             | 
| 28 | 
            +
                  # @!attribute r req
         | 
| 29 | 
            +
                  #   @return [Array[Parameter]]
         | 
| 30 | 
            +
                  # @!attribute r key_req
         | 
| 31 | 
            +
                  #   @return [Array[Parameter]]
         | 
| 32 | 
            +
                  # @!attribute r key_opt
         | 
| 33 | 
            +
                  #   @return [Array[Parameter]]
         | 
| 34 | 
            +
                  class Parameters < Struct.new(:req, :key_req, :key_opt, keyword_init: true)
         | 
| 47 35 |  | 
| 48 | 
            -
             | 
| 36 | 
            +
                    #@return [Parameters]
         | 
| 37 | 
            +
                    def self.build(params, **default_options)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      raise ArgumentError, 'At least 1 attribute is required.' if params.empty?
         | 
| 49 40 |  | 
| 50 41 | 
             
                      unless ::Trinkets::Class::Init::ATTR.include?(default_options[:attr])
         | 
| 51 42 | 
             
                        attr = default_options[:attr].inspect
         | 
| 52 43 | 
             
                        raise ArgumentError, "wrong `attr` type (given #{attr}, expected :accessor (default), :reader, :writer or :none)"
         | 
| 53 44 | 
             
                      end
         | 
| 54 45 |  | 
| 55 | 
            -
                      #  | 
| 56 | 
            -
                       | 
| 57 | 
            -
                      attrs = attrs.map do |a|
         | 
| 58 | 
            -
                        name, opts = [*a]
         | 
| 46 | 
            +
                      # @type [Array[Parameter]]
         | 
| 47 | 
            +
                      params = params.map do |name, opts|
         | 
| 59 48 | 
             
                        name = name.to_s.sub(/^@/, '').to_sym
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                         | 
| 49 | 
            +
             | 
| 50 | 
            +
                        opts ||= {}
         | 
| 51 | 
            +
                        opts.reject! { |_, v| v.nil? }
         | 
| 52 | 
            +
                        opts = default_options.merge(opts)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                        Parameter.new(name:, **opts)
         | 
| 62 55 | 
             
                      end
         | 
| 63 56 |  | 
| 64 | 
            -
                       | 
| 57 | 
            +
                      repeated_params = params.map(&:name)
         | 
| 65 58 | 
             
                        .tally
         | 
| 66 59 | 
             
                        .select { |_, count| count > 1 }
         | 
| 67 60 | 
             
                        .keys
         | 
| 68 61 |  | 
| 69 | 
            -
                      raise ArgumentError, "duplicated argument names: #{ | 
| 62 | 
            +
                      raise ArgumentError, "duplicated argument names: #{repeated_params.join(', ')}" if repeated_params.any?
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      # hash with 3 keys: {
         | 
| 65 | 
            +
                      #   FalseClass => { :name => Parameter }  # positional args
         | 
| 66 | 
            +
                      #   TrueClass  => { :name => Parameter }  # mandatory kw args
         | 
| 67 | 
            +
                      #   Hash       => { :name => Parameter }  # optional kw args with default value
         | 
| 68 | 
            +
                      # }
         | 
| 69 | 
            +
                      #@type [Hash[Class, Array[Parameter]]]
         | 
| 70 | 
            +
                      params = params.group_by { |param| param.kw.class }
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                      Parameters.new(
         | 
| 73 | 
            +
                        req:     [*params[FalseClass]],
         | 
| 74 | 
            +
                        key_req: [*params[TrueClass]],
         | 
| 75 | 
            +
                        key_opt: [*params[Hash]]
         | 
| 76 | 
            +
                      )
         | 
| 70 77 |  | 
| 71 | 
            -
                      attrs.to_h
         | 
| 72 78 | 
             
                    end
         | 
| 73 79 |  | 
| 74 | 
            -
                     | 
| 75 | 
            -
                     | 
| 76 | 
            -
                    # @param [Hash[Symbol, Object]] opt_kw_params
         | 
| 77 | 
            -
                    private def define_initialize(pos_params, kw_params, opt_kw_params)
         | 
| 78 | 
            -
                      ->(*values, **kw_values) do
         | 
| 80 | 
            +
                    #@return [Parameters]
         | 
| 81 | 
            +
                    def bind(values, kw_values)
         | 
| 79 82 |  | 
| 80 | 
            -
             | 
| 81 | 
            -
                          raise ArgumentError, "wrong number of arguments (given #{values.size}, expected #{pos_params.size})"
         | 
| 82 | 
            -
                        end
         | 
| 83 | 
            +
                      validate values, kw_values
         | 
| 83 84 |  | 
| 84 | 
            -
             | 
| 85 | 
            -
                         | 
| 86 | 
            -
                           | 
| 87 | 
            -
                           | 
| 88 | 
            -
             | 
| 85 | 
            +
                      req = self.req.zip(values).map do |(param, value)|
         | 
| 86 | 
            +
                        BoundParameter.new(
         | 
| 87 | 
            +
                          name: param.name,
         | 
| 88 | 
            +
                          attr: param.attr,
         | 
| 89 | 
            +
                          kw: param.kw,
         | 
| 90 | 
            +
                          super: param.super,
         | 
| 91 | 
            +
                          value:
         | 
| 92 | 
            +
                        )
         | 
| 93 | 
            +
                      end
         | 
| 89 94 |  | 
| 90 | 
            -
             | 
| 91 | 
            -
                         | 
| 92 | 
            -
                           | 
| 93 | 
            -
                           | 
| 94 | 
            -
             | 
| 95 | 
            +
                      key_req = self.key_req.map do |param|
         | 
| 96 | 
            +
                        BoundParameter.new(
         | 
| 97 | 
            +
                          name: param.name,
         | 
| 98 | 
            +
                          attr: param.attr,
         | 
| 99 | 
            +
                          kw: param.kw,
         | 
| 100 | 
            +
                          super: param.super,
         | 
| 101 | 
            +
                          value: kw_values[param.name]
         | 
| 102 | 
            +
                        )
         | 
| 103 | 
            +
                      end
         | 
| 95 104 |  | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
                         | 
| 105 | 
            +
                      key_opt = self.key_opt.map do |param|
         | 
| 106 | 
            +
                        key, value = kw_values.assoc(param.name)
         | 
| 107 | 
            +
                        BoundParameter.new(
         | 
| 108 | 
            +
                          name: param.name,
         | 
| 109 | 
            +
                          attr: param.attr,
         | 
| 110 | 
            +
                          kw: param.kw,
         | 
| 111 | 
            +
                          super: param.super,
         | 
| 112 | 
            +
                          value: key ? value : param.kw[:default]
         | 
| 113 | 
            +
                        )
         | 
| 114 | 
            +
                      end
         | 
| 99 115 |  | 
| 100 | 
            -
             | 
| 101 | 
            -
                          instance_variable_set "@#{name}", kw_values[name]
         | 
| 102 | 
            -
                        end
         | 
| 116 | 
            +
                      Parameters.new(req:, key_req:, key_opt:)
         | 
| 103 117 |  | 
| 104 | 
            -
             | 
| 105 | 
            -
                          value = kw_values.include?(name) ? kw_values[name] : default_value
         | 
| 106 | 
            -
                          instance_variable_set "@#{name}", value
         | 
| 107 | 
            -
                        end
         | 
| 118 | 
            +
                    end
         | 
| 108 119 |  | 
| 120 | 
            +
                    def all_params = req + all_key_params
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                    def all_key_params = key_req + key_opt
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    private def validate(values, kw_values)
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                      unless req.size == values.size
         | 
| 127 | 
            +
                        raise ArgumentError, "wrong number of arguments (given #{values.size}, expected #{req.size})"
         | 
| 128 | 
            +
                      end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                      missing_keys = key_req.map(&:name) - kw_values.keys
         | 
| 131 | 
            +
                      unless missing_keys.empty?
         | 
| 132 | 
            +
                        missing_keys = missing_keys.map(&:inspect).join(', ')
         | 
| 133 | 
            +
                        raise ArgumentError, "missing keywords: #{missing_keys}"
         | 
| 134 | 
            +
                      end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                      unknown_keywords = kw_values.except(*all_key_params.map(&:name))
         | 
| 137 | 
            +
                      unless unknown_keywords.empty?
         | 
| 138 | 
            +
                        unknown_keywords = unknown_keywords.keys.map(&:to_sym).map(&:inspect).join(', ')
         | 
| 139 | 
            +
                        raise ArgumentError, "unknown keywords: #{unknown_keywords}"
         | 
| 140 | 
            +
                      end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    end
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  def init(*params, attr: ATTR.first, kw: false, super: false)
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    params = Parameters.build(params, attr:, kw:, super:)
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                    # @type [Hash[Symbol, Method]]
         | 
| 150 | 
            +
                    attr_methods = (ATTR - [:none])
         | 
| 151 | 
            +
                      .each_with_object({}) do |name, h|
         | 
| 152 | 
            +
                        h[name] = method("attr_#{name}")
         | 
| 153 | 
            +
                      end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                    attr_init = ->(name, attr: ATTR.first) do
         | 
| 156 | 
            +
                      unless ATTR.include?(attr)
         | 
| 157 | 
            +
                        raise ArgumentError, "wrong `attr` type for `#{name.inspect}` (given #{attr.inspect}, expected :accessor (default), :reader, :writer or :none)"
         | 
| 158 | 
            +
                      end
         | 
| 159 | 
            +
                      attr_methods[attr].call(name) unless attr == :none
         | 
| 160 | 
            +
                    end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                    params.all_params.each do |param|
         | 
| 163 | 
            +
                      attr_init.call param.name, attr: param.attr
         | 
| 164 | 
            +
                    end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                    init_method = Init.send(:define_initialize, params)
         | 
| 167 | 
            +
                    define_method :initialize, init_method
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  class << self
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                    # @param [Parameters] params
         | 
| 173 | 
            +
                    private def define_initialize(params)
         | 
| 174 | 
            +
                      ->(*values, **kw_values) do
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                        params = params.bind(values, kw_values)
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                        super_req = params.req.select(&:super).map(&:value)
         | 
| 179 | 
            +
                        super_kws = params.all_key_params.select(&:super)
         | 
| 180 | 
            +
                          .each_with_object({}) do |param, h|
         | 
| 181 | 
            +
                            h[param.name] = param.value
         | 
| 182 | 
            +
                          end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                        super *super_req, **super_kws
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                        params.all_params.each do |param|
         | 
| 187 | 
            +
                          name = "@#{param.name}"
         | 
| 188 | 
            +
                          instance_variable_set(name, param.value) unless instance_variable_defined?(name)
         | 
| 189 | 
            +
                        end
         | 
| 109 190 | 
             
                      end
         | 
| 110 191 | 
             
                    end
         | 
| 111 192 | 
             
                  end
         | 
    
        data/lib/trinkets/version.rb
    CHANGED