attire 0.1.4 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e5925a4cfc382fbdb58206a32e9b2c48952b5ce
4
- data.tar.gz: 7ae88651bbceccaf71b924e657d186ce762e581b
3
+ metadata.gz: b1e1fc1f22a2213695656d10b09d2de54e340910
4
+ data.tar.gz: 0e10d06cf3402de661391eb894c5127f6a9f56fe
5
5
  SHA512:
6
- metadata.gz: 6c22d2e332413f498157b1309dd24ac30902cf5e852a98249f011f55853728d76201a77d250b4073efd9abb30f37f9ac4acaba292aec9ab3c964fc98ff8457e1
7
- data.tar.gz: 5e7fe15ae4b53d3db1064d82885000d1cfc07a96e63263c831968da1bf2244d97bef02ca352823997c1601d85d30d5cae7614160ef4cbb05b0c1ff0a049687d1
6
+ metadata.gz: 8878fced254c26d7f4520bb99547d88c96b5355b79200b8e3ace0c47e03c3c1e26c64b4ecfc435184c209138ffecb1f11c38876f249f33417a03abce178f7def
7
+ data.tar.gz: 76646d629df750287941eaad2e57a3884f236be29a0c77bc66eb7bd0bcc08730def009c0dbccefb66b9c0e32350cab641c07d176ee7b7e294b276f93dd427421
data/README.md CHANGED
@@ -1,93 +1,107 @@
1
- # Attire
2
-
3
- [![Build Status](https://travis-ci.org/mushishi78/attire.svg?branch=master)](https://travis-ci.org/mushishi78/attire)
4
- [![Gem Version](https://badge.fury.io/rb/attire.svg)](http://badge.fury.io/rb/attire)
5
-
6
- Convenience methods to remove some boiler plate in defining classes. Inspired by [attr_extras](https://github.com/barsoom/attr_extras).
7
-
8
- ## Usage
9
-
10
- ### `attr_init :foo, :bar, fizz: 15, pop: nil`
11
-
12
- Defines the following:
13
-
14
- ``` ruby
15
- def initialize(foo, bar, opts = {})
16
- @foo = foo
17
- @bar = bar
18
- @fizz = opts[:fizz] || 15
19
- @pop = opts[:pop]
20
- end
21
-
22
- private
23
-
24
- attr_reader :foo, :bar, :fizz, :pop
25
- ```
26
-
27
- Optional, splat and blocks arguments can also be defined:
28
-
29
- ``` ruby
30
- attr_init :'opts = {}', :'*args', :'&block'
31
- ```
32
-
33
- If a block is provided, it will be evaluated after initialization:
34
-
35
- ``` ruby
36
- attr_init :foo do
37
- @foo = foo ** 2
38
- end
39
- ```
40
-
41
- ### `attr_method :select, :bar`
42
-
43
- Defines the following:
44
-
45
- ``` ruby
46
- def self.select(*args, &block)
47
- new(*args, &block).select
48
- end
49
-
50
- attr_init :bar
51
- ```
52
-
53
- This is useful for method objects/use cases:
54
-
55
- ``` ruby
56
- class CheeseSpreader
57
- attr_method :spread, :cheese, crackers_class: Jacobs
58
-
59
- def spread
60
- cracker = crackers_class.new
61
- cracker.spreads << cheese
62
- cracker
63
- end
64
- end
65
-
66
- CheeseSpreader.spread(roquefort)
67
- ```
68
-
69
- ### `attr_query :foo?`
70
-
71
- Defines query `#foo?`, which is true if `foo` is truthy.
72
-
73
- ## Installation
74
-
75
- Add to Gemfile:
76
-
77
- ```ruby
78
- gem 'attire'
79
- ```
80
-
81
- Require library:
82
-
83
- ``` ruby
84
- require 'attire'
85
- ```
86
-
87
- ## Contributing
88
-
89
- 1. Fork it ( https://github.com/[my-github-username]/attire/fork )
90
- 2. Create your feature branch (`git checkout -b my-new-feature`)
91
- 3. Commit your changes (`git commit -am 'Add some feature'`)
92
- 4. Push to the branch (`git push origin my-new-feature`)
93
- 5. Create a new Pull Request
1
+ # Attire
2
+
3
+ [![Build Status](https://travis-ci.org/mushishi78/attire.svg?branch=master)](https://travis-ci.org/mushishi78/attire)
4
+ [![Gem Version](https://badge.fury.io/rb/attire.svg)](http://badge.fury.io/rb/attire)
5
+
6
+ Convenience methods to remove some boiler plate in defining classes. Inspired by [attr_extras](https://github.com/barsoom/attr_extras).
7
+
8
+ ## attr_init
9
+
10
+ ``` ruby
11
+ attr_init :foo, :bar, fizz: 15, pop: nil
12
+ ```
13
+
14
+ Defines the following:
15
+
16
+ ``` ruby
17
+ def initialize(foo, bar, opts = {})
18
+ @foo = foo
19
+ @bar = bar
20
+ @fizz = opts[:fizz]
21
+ @pop = opts[:pop]
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :foo, :bar, :pop
27
+
28
+ def fizz
29
+ @fizz ||= 15
30
+ end
31
+ ```
32
+
33
+ Optional, splat and blocks arguments can also be defined:
34
+
35
+ ``` ruby
36
+ attr_init :'opts = {}', :'*args', :'&block'
37
+ ```
38
+
39
+ If a block is provided, it will be evaluated after initialization:
40
+
41
+ ``` ruby
42
+ attr_init :foo do
43
+ @foo = foo ** 2
44
+ end
45
+ ```
46
+
47
+ ## attr_method
48
+
49
+ ``` ruby
50
+ attr_method :select, :bar
51
+ ```
52
+
53
+ Defines the following:
54
+
55
+ ``` ruby
56
+ def self.select(*args, &block)
57
+ new(*args, &block).select
58
+ end
59
+
60
+ attr_init :bar
61
+ ```
62
+
63
+ This is useful for method objects/use cases:
64
+
65
+ ``` ruby
66
+ class CheeseSpreader
67
+ attr_method :spread, :cheese, crackers_class: Jacobs
68
+
69
+ def spread
70
+ cracker = crackers_class.new
71
+ cracker.spreads << cheese
72
+ cracker
73
+ end
74
+ end
75
+
76
+ CheeseSpreader.spread(roquefort)
77
+ ```
78
+
79
+ ## attr_query
80
+
81
+ ``` ruby
82
+ attr_query :foo?
83
+ ```
84
+
85
+ Defines query `#foo?`, which is true if `foo` is truthy.
86
+
87
+ ## Installation
88
+
89
+ Add to Gemfile:
90
+
91
+ ```ruby
92
+ gem 'attire'
93
+ ```
94
+
95
+ Require library:
96
+
97
+ ``` ruby
98
+ require 'attire'
99
+ ```
100
+
101
+ ## Contributing
102
+
103
+ 1. Fork it ( https://github.com/[my-github-username]/attire/fork )
104
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
105
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
106
+ 4. Push to the branch (`git push origin my-new-feature`)
107
+ 5. Create a new Pull Request
@@ -0,0 +1,57 @@
1
+ require 'attire/core_ext/duplicable'
2
+ require_relative 'arguments_checker'
3
+
4
+ module Attire
5
+ module AttrInit
6
+ class Arguments
7
+ def initialize(arguments)
8
+ ArgumentsChecker.check(arguments)
9
+ @block = last_argument_with_prefix(arguments, '&')
10
+ @splat = last_argument_with_prefix(arguments, '*')
11
+ @names, @defaults = [], {}
12
+ arguments.each { |arg| extract_argument(arg) }
13
+ @arity_range = (min_arity..max_arity)
14
+ @getter_names = (names.flatten + [splat, block]).compact
15
+ end
16
+
17
+ attr_reader :names, :splat, :block, :defaults, :arity_range, :getter_names
18
+
19
+ private
20
+
21
+ def last_argument_with_prefix(arguments, prefix)
22
+ return unless arguments.last.to_s.start_with?(prefix)
23
+ arguments.pop.to_s[1..-1]
24
+ end
25
+
26
+ def extract_argument(arg)
27
+ return extract_hash(arg) if arg.is_a?(Hash)
28
+ return extract_optional(arg) if arg.to_s.include?('=')
29
+ extract_required(arg)
30
+ end
31
+
32
+ def extract_hash(hash)
33
+ names << hash.keys
34
+ defaults.merge!(hash)
35
+ end
36
+
37
+ def extract_optional(arg)
38
+ name, default = arg.to_s.split('=').map(&:strip)
39
+ names << name
40
+ defaults[name] = eval(default)
41
+ end
42
+
43
+ def extract_required(arg)
44
+ names << arg
45
+ @min_arity = names.length
46
+ end
47
+
48
+ def min_arity
49
+ @min_arity ||= 0
50
+ end
51
+
52
+ def max_arity
53
+ splat ? Float::INFINITY : names.length
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,66 @@
1
+ module Attire
2
+ module AttrInit
3
+ class ArgumentsChecker
4
+ def self.check(arguments)
5
+ new.check(arguments)
6
+ end
7
+
8
+ def check(arguments)
9
+ arguments.reverse.each_with_index do |arg, i|
10
+ @arg, @i = arg, i
11
+ type_check
12
+ block_check
13
+ splat_check
14
+ required_after_optional_check
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :arg, :i
21
+
22
+ TYPES = [Symbol, String, Hash]
23
+
24
+ def type_check
25
+ return if TYPES.include?(arg.class)
26
+ fail ArgumentError, 'Must be Symbol, String or Hash.'
27
+ end
28
+
29
+ def block_check
30
+ return unless block?
31
+ fail ArgumentError, 'Block arguments must be last' unless i == 0
32
+ @has_block = true
33
+ end
34
+
35
+ def splat_check
36
+ return unless splat?
37
+ return if i == (@has_block ? 1 : 0)
38
+ fail ArgumentError, \
39
+ 'Splat arguments must come after required and optional arguments'
40
+ end
41
+
42
+ def required_after_optional_check
43
+ return if block? || splat? || hash?
44
+ return @has_requireds = true unless optional?
45
+ return unless @has_requireds
46
+ fail ArgumentError, 'Required arguments must come before optional'
47
+ end
48
+
49
+ def block?
50
+ arg.to_s.start_with?('&')
51
+ end
52
+
53
+ def splat?
54
+ arg.to_s.start_with?('*')
55
+ end
56
+
57
+ def hash?
58
+ arg.is_a?(Hash)
59
+ end
60
+
61
+ def optional?
62
+ arg.to_s.include?('=')
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'values_matcher'
2
+
3
+ module Attire
4
+ module AttrInit
5
+ class Initializer
6
+ def initialize(arguments, after_initialize, opts = {})
7
+ @values_matcher = opts[:values_matcher] || ValuesMatcher.new(arguments)
8
+ @arity_range = arguments.arity_range
9
+ @after_initialize = after_initialize
10
+ end
11
+
12
+ def instance_initialize(instance, values, value_block)
13
+ arity_check(values)
14
+ values_matcher.match(values, value_block).each do |k, v|
15
+ instance.instance_variable_set("@#{k}", v)
16
+ end
17
+ instance.instance_exec(&after_initialize) if after_initialize
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :values_matcher, :arity_range, :after_initialize
23
+
24
+ def arity_check(values)
25
+ return if arity_range.include?(values.length)
26
+ fail ArgumentError, "wrong number of arguments (#{values.length} for #{arity_range})"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ require 'forwardable'
2
+
3
+ module Attire
4
+ module AttrInit
5
+ class ValuesMatcher
6
+ extend Forwardable
7
+
8
+ def initialize(arguments)
9
+ @arguments = arguments
10
+ end
11
+
12
+ def match(values, value_block)
13
+ @matched = {}
14
+ names.zip(values) { |n, v| n.is_a?(Array) ? set_from_hash(v) : set(n, v) }
15
+ set_splat(values)
16
+ set_block(value_block)
17
+ matched
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :arguments, :matched
23
+ def_delegators :arguments, :names, :splat, :block
24
+
25
+ def set(name, value)
26
+ matched[name] = value
27
+ end
28
+
29
+ def set_from_hash(values)
30
+ matched.merge!(values || {})
31
+ end
32
+
33
+ def set_splat(values)
34
+ matched[splat] = values[names.length..-1] || [] if splat
35
+ end
36
+
37
+ def set_block(value_block)
38
+ matched[block] = value_block if block
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,94 +1,38 @@
1
- require 'attire/initializer'
1
+ require 'attire/attr_init/arguments'
2
+ require 'attire/attr_init/initializer'
2
3
 
3
4
  module Attire
4
- class AttrInit
5
- def self.apply(*args)
6
- new(*args).apply
7
- end
8
-
9
- def initialize(klass, names, after_initialize)
10
- @klass, @names, @after_initialize = klass, names, after_initialize
11
- end
12
-
13
- def apply
14
- type_check
15
- extract_splat_and_block_names
16
- optional_arguments_check
17
- instance_initialize = initializer.method(:instance_initialize)
18
-
19
- klass.send(:define_method, :initialize) do |*values, &value_block|
20
- instance_initialize.call(self, values, value_block)
5
+ module AttrInit
6
+ class << self
7
+ def apply(klass, args, after_initialize)
8
+ arguments = Arguments.new(args)
9
+ initializer = Initializer.new(arguments, after_initialize)
10
+ define_initializer(klass, initializer)
11
+ define_getters(klass, arguments)
21
12
  end
22
13
 
23
- define_getters
24
- end
25
-
26
- private
27
-
28
- attr_reader :klass, :names, :splat_name, :block_name, :after_initialize
14
+ private
29
15
 
30
- def type_check
31
- return if names.all? { |n| [Symbol, String, Hash].include?(n.class) }
32
- fail ArgumentError, 'Must be Symbol, String or Hash.'
33
- end
34
-
35
- def extract_splat_and_block_names
36
- @block_name = last_name_with_prefix('&')
37
- @splat_name = last_name_with_prefix('*')
38
- excess_splat_and_block_names_check
39
- end
40
-
41
- def initializer
42
- Initializer.new(names, splat_name, block_name, after_initialize)
43
- end
44
-
45
- def define_getters
46
- getter_names.each do |name|
47
- klass.send(:define_method, name) { instance_variable_get("@#{name}") }
16
+ def define_initializer(klass, initializer)
17
+ klass.send(:define_method, :initialize) do |*values, &value_block|
18
+ initializer.instance_initialize(self, values, value_block)
19
+ end
48
20
  end
49
- klass.send(:private, *getter_names)
50
- end
51
21
 
52
- def getter_names
53
- getter_names = names.map do |arg|
54
- next arg.keys if arg.respond_to?(:keys)
55
- next optional_name(arg) if optional?(arg)
56
- arg
22
+ def define_getters(klass, arguments)
23
+ names = arguments.getter_names
24
+ names.each { |n| define_getter(klass, n, arguments.defaults[n]) }
25
+ klass.send(:private, *names)
57
26
  end
58
- getter_names += [splat_name, block_name]
59
- getter_names.flatten.compact
60
- end
61
-
62
- def last_name_with_prefix(prefix)
63
- without_prefix(names.pop) if last_name_prefix?(prefix)
64
- end
65
-
66
- def without_prefix(symbol)
67
- symbol.to_s[1..-1].to_sym
68
- end
69
-
70
- def last_name_prefix?(prefix)
71
- names.last.to_s.start_with?(prefix)
72
- end
73
27
 
74
- def excess_splat_and_block_names_check
75
- return if names.none? { |n| n.to_s.start_with?('&', '*') }
76
- fail ArgumentError, 'Splat and Block arguments must be last'
77
- end
78
-
79
- def optional_arguments_check
80
- start = names.find_index { |n| optional?(n) }
81
- return unless start
82
- return if names[start..-1].all? { |n| n.is_a?(Hash) || optional?(n) }
83
- fail ArgumentError, 'Required arguments must come before optional'
84
- end
85
-
86
- def optional?(name)
87
- name.to_s.include?('=')
88
- end
89
-
90
- def optional_name(name)
91
- name.to_s.split('=')[0].strip
28
+ def define_getter(klass, name, default)
29
+ klass.send(:define_method, name) do
30
+ value = instance_variable_get("@#{name}")
31
+ return value unless value.nil? && !default.nil?
32
+ default = default.duplicable? ? default.dup : default
33
+ instance_variable_set("@#{name}", default)
34
+ end
35
+ end
92
36
  end
93
37
  end
94
38
  end
data/lib/attire.rb CHANGED
@@ -1,24 +1,24 @@
1
- require 'attire/attr_init'
2
-
3
- module Attire
4
- def attr_query(*names)
5
- names.each do |name|
6
- name = name.to_s
7
- fail ArgumentError, "`#{name}?`, not `#{name}`." unless name.end_with?('?')
8
- define_method(name) { !!send(name.chop) }
9
- end
10
- end
11
-
12
- def attr_init(*names, &block)
13
- AttrInit.apply(self, names, block)
14
- end
15
-
16
- def attr_method(verb, *names, &block)
17
- define_singleton_method(verb) { |*a, &b| new(*a, &b).send(verb) }
18
- attr_init(*names, &block)
19
- end
20
- end
21
-
22
- class Module
23
- include Attire
24
- end
1
+ require 'attire/attr_init'
2
+
3
+ module Attire
4
+ def attr_query(*names)
5
+ names.each do |name|
6
+ name = name.to_s
7
+ fail ArgumentError, "`#{name}?`, not `#{name}`." unless name.end_with?('?')
8
+ define_method(name) { !!send(name.chop) }
9
+ end
10
+ end
11
+
12
+ def attr_init(*args, &block)
13
+ AttrInit.apply(self, args, block)
14
+ end
15
+
16
+ def attr_method(verb, *args, &block)
17
+ define_singleton_method(verb) { |*a, &b| new(*a, &b).send(verb) }
18
+ attr_init(*args, &block)
19
+ end
20
+ end
21
+
22
+ class Module
23
+ include Attire
24
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max White
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-28 00:00:00.000000000 Z
11
+ date: 2015-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -40,9 +40,12 @@ files:
40
40
  - README.md
41
41
  - lib/attire.rb
42
42
  - lib/attire/attr_init.rb
43
+ - lib/attire/attr_init/arguments.rb
44
+ - lib/attire/attr_init/arguments_checker.rb
45
+ - lib/attire/attr_init/initializer.rb
46
+ - lib/attire/attr_init/values_matcher.rb
43
47
  - lib/attire/core_ext/duplicable.rb
44
- - lib/attire/initializer.rb
45
- homepage:
48
+ homepage: https://github.com/mushishi78/attire
46
49
  licenses:
47
50
  - MIT
48
51
  metadata: {}
@@ -1,92 +0,0 @@
1
- require 'attire/core_ext/duplicable'
2
-
3
- module Attire
4
- class Initializer
5
- def initialize(names, splat_name, block_name, after_initialize)
6
- @names = names
7
- @splat_name = splat_name
8
- @block_name = block_name
9
- @after_initialize = after_initialize
10
- end
11
-
12
- def instance_initialize(instance, values, value_block)
13
- @instance, @values, @value_block = instance, values, value_block
14
- arity_check
15
- set_ivars
16
- instance.instance_eval(&after_initialize) if after_initialize
17
- end
18
-
19
- private
20
-
21
- attr_reader :names, :splat_name, :block_name, :after_initialize,
22
- :instance, :values, :value_block
23
-
24
- def set_ivars
25
- names.zip(values).each do |name, value|
26
- next set_hash(name, value) if name.is_a?(Hash)
27
- next set_optional(name, value) if optional?(name)
28
- set_ivar(name, value)
29
- end
30
- set_splat
31
- set_block
32
- end
33
-
34
- def set_hash(defaults, values)
35
- values ||= {}
36
- hash_check(values)
37
- defaults.each do |name, default|
38
- next set_ivar(name, values[name]) unless values[name].nil?
39
- set_ivar(name, default.duplicable? ? default.dup : default)
40
- end
41
- end
42
-
43
- def set_optional(name, value)
44
- name, default = name.to_s.split('=').map(&:strip)
45
- value = instance.instance_eval(default) if value.nil?
46
- set_ivar(name, value)
47
- end
48
-
49
- def set_splat
50
- return unless splat_name
51
- value_splat = values[names.length..values.length] || []
52
- set_ivar(splat_name, value_splat)
53
- end
54
-
55
- def set_block
56
- set_ivar(block_name, value_block) if block_name
57
- end
58
-
59
- def set_ivar(name, value)
60
- instance.instance_variable_set("@#{name}", value)
61
- end
62
-
63
- def arity_check
64
- return if arity_range.include?(values.length)
65
- fail ArgumentError, "wrong number of arguments (#{values.length} for #{arity_range})"
66
- end
67
-
68
- def hash_check(value)
69
- return if value.is_a?(Hash)
70
- fail ArgumentError, "#{value} should be Hash."
71
- end
72
-
73
- def min_arity
74
- last_required = names.reverse_each.find_index do |n|
75
- !(optional?(n) || n.is_a?(Hash))
76
- end
77
- last_required ? names.length - last_required : 0
78
- end
79
-
80
- def max_arity
81
- splat_name ? Float::INFINITY : names.length
82
- end
83
-
84
- def arity_range
85
- @arity_range ||= (min_arity..max_arity)
86
- end
87
-
88
- def optional?(name)
89
- name.to_s.include?('=')
90
- end
91
- end
92
- end