dux 0.3.0 → 0.8.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: 8549a025dd5d4bd0d25700d7155a99ac46d222cc
4
- data.tar.gz: 187be7a1fcfa97857bf948f846218131889aa17e
3
+ metadata.gz: ed22ad66960e4fd9f7c8616f2541a0363a2ecf40
4
+ data.tar.gz: 40d5a91736ca7ca6a0e61e3508c1fa0fdf94b3b7
5
5
  SHA512:
6
- metadata.gz: a24dddfbe3465f64ac539c33a38c8369cb854761acaf72616b9c070c6d3b85c92c9b0effc7d022c0477ddd02fc733c9386ea886213a5568bf895fb63b062b9b2
7
- data.tar.gz: d5ab3bff61e513d04bef1b78acb5afa218ee8dbd96cf35817913301fd9ada0e8b1ec0be57819b7571bdf593c693855571f9213ec68aa347f92d4a49ce330f241
6
+ metadata.gz: 1cb5fb4869324b0d8d9a2cf225d78e24b6c2de3315bf8917fe07b62d6528d01f1245d9dd6f498fd8e9a5e0a7956bdb2c2ad5ecd00238bea69c51097f78f67a0f
7
+ data.tar.gz: f5ce999d6fb1ab0f9d440108762167f8b0a2cd6b1461c8ae3e8ba69ea75a0a24de62e7d4f8adb7b42c5251a3ddc4a3a1439f4be6dda8919b04f1b72e5f3dfc59
@@ -3,10 +3,10 @@ dist: trusty
3
3
  before_install: gem install bundler -v '~> 1.14'
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.1.0
7
- - 2.2.1
8
- - 2.3.0
9
- - 2.4.0
6
+ - 2.1
7
+ - 2.2
8
+ - 2.3
9
+ - 2.4
10
10
  - ruby-head
11
11
  - rbx-3.73
12
12
  - jruby-head
data/.yardopts CHANGED
@@ -1,3 +1,6 @@
1
+ --embed-mixin Dux::Attempt
2
+ --embed-mixin Dux::Blankness
3
+ --embed-mixin Dux::InspectID
1
4
  --private
2
5
  --protected
3
6
  -m markdown
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Dux [![Build Status](https://travis-ci.org/scryptmouse/dux.svg?branch=master)](https://travis-ci.org/scryptmouse/dux)
2
2
 
3
- A lazy duck-type matching gem that is particularly designed for use in case statements.
3
+ A swiss-army knife of duck-type and utility objects without monkey-patching.
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,27 +18,204 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install dux
20
20
 
21
- ## Usage
21
+ ## Dux[] / Predicates
22
22
 
23
- Usage is straightforward out of the box:
23
+ `Dux[]` can be used for creating predicate matchers for case equality.
24
+ It will check if whatever it is compared against can `respond_to?` the
25
+ symbol it's provided with.
26
+
27
+ Usage is straightforward:
24
28
 
25
29
  ```ruby
26
30
  case foo
27
- when Dux[:bar] then foo.bar
28
- when Dux[:baz] then foo.baz
29
- when Dux[:quux] then foo.quux
31
+ when Dux[:bar]
32
+ foo.bar
33
+ when Dux[:baz]
34
+ foo.baz
35
+ when Dux[:quux]
36
+ foo.quux
37
+ else
38
+ raise TypeError, "Dunno what do to do with #{foo.inspect}"
39
+ end
40
+ ```
41
+
42
+ Because it uses lambdas under the hood, it can also be used
43
+ as an enumerable predicate:
44
+
45
+ ```ruby
46
+ list_of_objects_that_should_conform.all?(&Dux[:some_method])
47
+ ```
48
+
49
+ That's not the intended usage, but it works.
50
+
51
+ ### Inheritance / Inclusion / Extension
52
+
53
+ There are also predicates for testing class structure:
54
+
55
+ ```ruby
56
+ class SomeClass < SomeParentClass
57
+ extend SomeDSLMethods
58
+ include SomeHelpers
59
+ prepend SomeOverrides
30
60
  end
61
+
62
+ Dux.inherits(SomeParentClass) === SomeClass # => true
63
+ Dux.inherits(SomeHelpers) === SomeClass # => true
64
+ Dux.inherits(SomeOverrides) === SomeClass # => true
65
+
66
+ # If you want to use Module#<= in the predicate, use include_self: true
67
+ Dux.inherits(SomeClass, include_self: true) === SomeClass # => true
68
+
69
+ # Testing for module extension is different.
70
+ Dux.extends(SomeDSLMethods) === SomeClass # => true
31
71
  ```
32
72
 
33
- It also has some methods for matching against a strict interface:
73
+ `Dux.inherits` is aliased to `Dux.prepends` and `Dux.includes`
74
+ for semantic clarity, but it does not currently check
75
+ if a module was included or prepended.
76
+
77
+ ### Interfaces
78
+
79
+ There are also some methods for matching against a strict / flexible interface:
34
80
 
35
81
  ```ruby
36
82
  case foo
37
- when Dux.all(:bar, :baz) then foo.bar && foo.baz
38
- when Dux.any(:quux, :bloop) then foo.try(:quux) || foo.try(:bloop)
83
+ when Dux.all(:bar, :baz)
84
+ foo.bar && foo.baz
85
+ when Dux.any(:quux, :bloop)
86
+ Dux.attempt(foo, :quux) || Dux.attempt(foo, :bloop)
87
+ end
88
+ ```
89
+
90
+ ### YARD Types
91
+
92
+ If you have a condition more easily expressed in [YARD types](http://yardoc.org/types),
93
+ you can do something like the following:
94
+
95
+ ```ruby
96
+ case some_overloaded_arg
97
+ when Dux.yard('(Symbol, Symbol)')
98
+ # Do something with a tuple of two symbols
99
+ when Dux.yard('{ Symbol => <Symbol> }')
100
+ # Do something with a symbol-keyed hash with
101
+ # values that are an array of symbols
102
+ when Dux.yard('<SomeClass>')
103
+ # Do something with an array of `SomeClass` members
104
+ end
105
+ ```
106
+
107
+ Performance of the underlying gem is not thoroughly tested,
108
+ but it should work just fine for non-intensive use cases.
109
+
110
+ ## Dux.comparable
111
+
112
+ Simplifies creating comparable objects when just want
113
+ to compare on a couple of attributes defined on objects.
114
+
115
+ ```ruby
116
+ class Person < Struct.new(:name)
117
+ include Dux.comparable :name
118
+ end
119
+
120
+ alice = Person.new 'Alice'
121
+ bob = Person.new 'Bob'
122
+ carol = Person.new 'Carol'
123
+
124
+ [bob, carol, alice].sort == [alice, bob, carol]
125
+
126
+ # You can also sort descending:
127
+
128
+ Person.include Dux.comparable :name, sort_order: :desc
129
+
130
+ [alice, carol, bob].sort == [carol, bob, alice]
131
+ ```
132
+
133
+ You can additionally specify multiple attributes with
134
+ individual sort ordering for each attribute:
135
+
136
+ ```ruby
137
+ class Person < Struct.new(:name, :salary)
138
+ include Dux.comparable [:salary, :desc], :name
139
+ end
140
+
141
+ alice = Person.new 'Alice', 100_000
142
+ bob = Person.new 'Bob', 75_000
143
+ carol = Person.new 'Carol', 100_000
144
+
145
+ [carol, bob, alice].sort == [alice, carol, bob]
146
+ ```
147
+
148
+ ## Dux.enum
149
+
150
+ Create an indifferent set of strings/symbols that can be used
151
+ to validate options.
152
+
153
+ ```ruby
154
+ ROLES = Dux.enum :author, :admin, :reader
155
+
156
+ ROLES[:author] # => :author
157
+ ROLES[:nonexistent] # raises Dux::Enum::NotFound
158
+
159
+ ROLES.fetch :nonexistent do |value|
160
+ raise YourErrorHere, "Invalid role: #{value}"
39
161
  end
40
162
  ```
41
163
 
164
+ If you want a specific fallback value instead of raising an error, you can do that too
165
+
166
+ ```ruby
167
+ ROLES = Dux.enum :author, :admin, :reader, default: :reader
168
+
169
+ ROLES[:nonexistent] # => :reader
170
+
171
+ # Override the fallback for a particular fetch
172
+ ROLES[:nonexistent, fallback: :author] # => :author
173
+ ```
174
+
175
+ ## Utilities
176
+
177
+ Small utility methods, many to replace needing ActiveSupport / monkey patching.
178
+
179
+ ### Dux.attempt
180
+
181
+ `Object#try` when you don't have/want ActiveSupport.
182
+
183
+ It will attempt to execute a provided method (with optional args and block)
184
+ with `public_send`, or simply return `nil` if it doesn't respond.
185
+
186
+ ```ruby
187
+ Dux.attempt(some_object, :method, *args, &block)
188
+ ```
189
+
190
+ ### Dux.blankish? / Dux.presentish?
191
+
192
+ `Object#blank?` & `Object#present?` when you don't have/want ActiveSupport.
193
+
194
+ Rather than being monkey patched across all `Object`s, you will need to
195
+ use `Dux` to check:
196
+
197
+ ```ruby
198
+ Dux.blankish? [nil] # => true
199
+ Dux.blankish? Hash.new # => true
200
+ Dux.blankish? "\t" # => true
201
+ Dux.blankish? Float::NAN
202
+ ```
203
+
204
+ ### Dux.inspect_id
205
+
206
+ If you are overriding the `#inspect` method on an object and want
207
+ to keep that unique hex `object_id`, this offers a shorthand:
208
+
209
+ ```ruby
210
+ def inspect
211
+ "#<YourClassHere:#{Dux.inspect_id(self)}>"
212
+ end
213
+ ```
214
+
215
+ ## Monkey patches / Experimental
216
+
217
+ These will probably end up getting removed for version 1.x.
218
+
42
219
  ### Core Extensions
43
220
 
44
221
  There are a few core extensions available that can be manually enabled.
@@ -75,7 +252,7 @@ when %i[quux bloop].duckify(type: :any) then foo.try(:quux) || foo.try(:bloop)
75
252
  end
76
253
  ```
77
254
 
78
- #### Shorthand
255
+ #### ~ Shorthand
79
256
 
80
257
  There is an experimental option that uses unary `~` as an alias for `#duckify`.
81
258
 
@@ -116,7 +293,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
116
293
 
117
294
  ## Supported Ruby Versions
118
295
 
119
- * MRI 2.1+
296
+ * MRI 2.2+
120
297
  * Rubinius 2.5+
121
298
  * jruby-head / 9.0+
122
299
 
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Alexa Grey"]
10
10
  spec.email = ["devel@mouse.vc"]
11
11
 
12
- spec.summary = %q{Lazy duck-type matching}
12
+ spec.summary = %q{Swiss-army knife gem for duck-type matching and utility methods}
13
13
  spec.homepage = "https://github.com/scryptmouse/dux"
14
14
  spec.license = "MIT"
15
15
 
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.metadata["yard.run"] = "yri"
22
22
 
23
- spec.add_dependency "lru_redux"
23
+ spec.add_dependency "yard_types"
24
24
 
25
25
  spec.add_development_dependency "bundler", "~> 1.9"
26
26
  spec.add_development_dependency "rspec", "~> 3.5"
data/lib/dux.rb CHANGED
@@ -1,183 +1,27 @@
1
1
  require 'delegate'
2
+ require 'strscan'
3
+ require 'yard_types'
2
4
 
3
5
  require 'dux/version'
6
+
4
7
  require 'dux/inspect_id'
5
8
  require 'dux/null_object'
6
9
  require 'dux/attempt'
7
10
  require 'dux/blankness'
8
- require 'dux/duckify'
9
- require 'dux/hacks_like_a_duck'
10
- require 'dux/flock_methods'
11
-
12
11
  require 'dux/indifferent_string'
13
- require 'dux/enum'
14
-
15
- # Super simple duck-type testing.
16
- module Dux
17
- # Methods for generating "interface" checks against an array of symbols
18
- FLOCK_TYPES = %i[all any none]
19
-
20
- module_function
21
-
22
- # @param [Symbol] symbol
23
- # @param [Boolean] include_all
24
- # @return [Proc]
25
- def dux(symbol, include_all: false)
26
- ->(obj) { obj.respond_to? symbol, include_all }
27
- end
28
-
29
- class << self
30
- alias_method :[], :dux
31
-
32
- # @param [:all, :any, :none] type
33
- # @param [<Symbol, String>] methods
34
- # @param [Boolean] include_all
35
- # @raise [ArgumentError] on invalid type
36
- # @return [<Proc>]
37
- def flock(type, *methods, include_all: false)
38
- raise ArgumentError, "Invalid flock type: #{type}" unless FLOCK_TYPES.include? type
39
-
40
- __send__ type, methods, include_all: include_all
41
- end
42
-
43
- # Creates a lambda that describes a restrictive interface,
44
- # wherein the provided object must `respond_to?` *all* of
45
- # the methods.
46
- #
47
- # @param [<Symbol, String>] methods
48
- # @param [Boolean] include_all
49
- # @return [Proc]
50
- def all(*methods, include_all: false)
51
- ducks = flockify methods, include_all: include_all
52
-
53
- ->(obj) { ducks.all? { |duck| duck.call obj } }
54
- end
55
-
56
- # Creates a lambda that describes a permissive interface,
57
- # wherein the provided object must `respond_to?` at least
58
- # one of the methods.
59
- #
60
- # @param [<Symbol>] methods
61
- # @param [Boolean] include_all
62
- # @return [Proc]
63
- def any(*methods, include_all: false)
64
- ducks = flockify methods, include_all: include_all
65
-
66
- ->(obj) { ducks.any? { |duck| duck.call obj } }
67
- end
68
-
69
- # Creates a lambda that describes a restrictive interface,
70
- # wherein the provided object must `respond_to?` *none* of
71
- # the methods.
72
- #
73
- # @param [<Symbol>] methods
74
- # @param [Boolean] include_all
75
- # @return [Proc]
76
- def none(*methods, include_all: false)
77
- ducks = flockify methods, include_all: include_all
78
-
79
- ->(obj) { ducks.none? { |duck| duck.call obj } }
80
- end
81
-
82
- # @!group Core extensions
83
-
84
- # Enhance `Array` with {Dux::FlockMethods#duckify}
85
- #
86
- # @return [void]
87
- def add_flock_methods!
88
- Array.__send__ :prepend, Dux::FlockMethods
89
12
 
90
- return nil
91
- end
92
-
93
- # Load all `Dux` core extensions.
94
- #
95
- # @param [Boolean] experimental whether to load experimental shorthand methods
96
- # @return [void]
97
- def extend_all!(experimental: false)
98
- add_flock_methods!
99
- extend_strings_and_symbols!
100
-
101
- return unless experimental
102
-
103
- array_shorthand!
104
- string_shorthand!
105
- symbol_shorthand!
106
- end
107
-
108
- # Enhance `String` with {Dux::Duckify}
109
- #
110
- # @return [void]
111
- def extend_strings!
112
- String.__send__ :prepend, Dux::Duckify
113
-
114
- return nil
115
- end
116
-
117
- # Enhance `Symbol` with {Dux::Duckify}
118
- #
119
- # @return [void]
120
- def extend_symbols!
121
- Symbol.__send__ :prepend, Dux::Duckify
122
-
123
- return nil
124
- end
125
-
126
- # Enhance `String` and `Symbol` classes with {Dux::Duckify#duckify}
127
- #
128
- # @return [void]
129
- def extend_strings_and_symbols!
130
- extend_strings!
131
- extend_symbols!
132
-
133
- return nil
134
- end
135
-
136
- # Experimental feature to add unary `~` to `Array`s
137
- # for quick usage in case statements.
138
- #
139
- # @return [void]
140
- def array_shorthand!
141
- add_flock_methods!
142
-
143
- Array.__send__ :prepend, Dux::HacksLikeADuck
144
-
145
- nil
146
- end
147
-
148
- # Experimental feature to add unary `~` to `String`s
149
- # for quick usage in case statements.
150
- #
151
- # @return [void]
152
- def string_shorthand!
153
- extend_strings!
154
- String.__send__ :prepend, Dux::HacksLikeADuck
155
-
156
- nil
157
- end
158
-
159
- # Experimental feature to add unary `~` to `Symbol`s
160
- # for quick usage in case statements.
161
- #
162
- # @return [void]
163
- def symbol_shorthand!
164
- extend_symbols!
13
+ require 'dux/enum'
165
14
 
166
- Symbol.__send__ :prepend, Dux::HacksLikeADuck
15
+ require 'dux/predicate'
167
16
 
168
- return nil
169
- end
17
+ require 'dux/comparable'
170
18
 
171
- # @!endgroup
19
+ # Experimental nonsense
20
+ require 'dux/monkey_patch'
21
+ require 'dux/duckify'
22
+ require 'dux/hacks_like_a_duck'
23
+ require 'dux/flock_methods'
172
24
 
173
- protected
174
- # @param [<Symbol>] methods
175
- # @param [Boolean] include_all
176
- # @return [<Proc>]
177
- def flockify(methods, include_all: false)
178
- methods.flatten.map do |sym|
179
- dux sym, include_all: include_all
180
- end
181
- end
182
- end
25
+ # Swiss-army duck type matching
26
+ module Dux
183
27
  end