attire 0.1.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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