dux 0.3.0 → 0.8.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: 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