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 +4 -4
- data/.travis.yml +4 -4
- data/.yardopts +3 -0
- data/README.md +188 -11
- data/dux.gemspec +2 -2
- data/lib/dux.rb +13 -169
- data/lib/dux/blankness.rb +9 -2
- data/lib/dux/comparable.rb +241 -0
- data/lib/dux/duckify.rb +1 -13
- data/lib/dux/enum.rb +194 -11
- data/lib/dux/indifferent_string.rb +9 -1
- data/lib/dux/inspect_id.rb +6 -1
- data/lib/dux/monkey_patch.rb +97 -0
- data/lib/dux/null_object.rb +52 -5
- data/lib/dux/predicate.rb +139 -0
- data/lib/dux/version.rb +1 -1
- metadata +7 -5
- data/lib/dux/duck_type.rb +0 -0
@@ -1,9 +1,11 @@
|
|
1
1
|
module Dux
|
2
|
+
# A string value that is programmatically equivalent to its symbol representation.
|
2
3
|
class IndifferentString < DelegateClass(String)
|
4
|
+
# @param [String, Symbol, Dux::IndifferentString, #to_str, #acts_like_string?] stringish
|
3
5
|
def initialize(stringish)
|
4
6
|
stringified =
|
5
7
|
case stringish
|
6
|
-
when String then stringish
|
8
|
+
when String, Dux::IndifferentString then stringish
|
7
9
|
when Symbol then stringish.to_s
|
8
10
|
when Dux[:to_str] then stringish.to_str
|
9
11
|
else
|
@@ -17,6 +19,9 @@ module Dux
|
|
17
19
|
super(stringified)
|
18
20
|
end
|
19
21
|
|
22
|
+
# Test basic equality.
|
23
|
+
#
|
24
|
+
# @param [String, Symbol] other
|
20
25
|
def ==(other)
|
21
26
|
if other.kind_of?(Symbol)
|
22
27
|
self == other.to_s
|
@@ -27,6 +32,9 @@ module Dux
|
|
27
32
|
|
28
33
|
alias_method :eql?, :==
|
29
34
|
|
35
|
+
# Test case equality
|
36
|
+
#
|
37
|
+
# @param [String, Symbol, Regexp] other
|
30
38
|
def ===(other)
|
31
39
|
if other.kind_of?(Symbol)
|
32
40
|
self == other.to_s
|
data/lib/dux/inspect_id.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module Dux
|
2
|
+
# Calculate the object's inspection id
|
2
3
|
module InspectID
|
3
4
|
# Calculates the id shown in the default
|
4
5
|
# version of `#inspect` methods.
|
5
6
|
#
|
7
|
+
# @note This is currently limited to the implementation
|
8
|
+
# used in MRI. Rubinius and JRuby have their own
|
9
|
+
# implementations that are not currently served
|
10
|
+
# by this.
|
6
11
|
# @param [Object] object
|
7
12
|
# @return [String]
|
8
|
-
def inspect_id(object)
|
13
|
+
def inspect_id(object = self)
|
9
14
|
"0x%0.14x" % ( object.object_id << 1 )
|
10
15
|
end
|
11
16
|
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Dux
|
2
|
+
class << self
|
3
|
+
# @!group Core extensions
|
4
|
+
|
5
|
+
# Enhance `Array` with {Dux::FlockMethods#duckify}
|
6
|
+
#
|
7
|
+
# @return [void]
|
8
|
+
def add_flock_methods!
|
9
|
+
Array.__send__ :prepend, Dux::FlockMethods
|
10
|
+
|
11
|
+
return nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# Load all `Dux` core extensions.
|
15
|
+
#
|
16
|
+
# @param [Boolean] experimental whether to load experimental shorthand methods
|
17
|
+
# @return [void]
|
18
|
+
def extend_all!(experimental: false)
|
19
|
+
add_flock_methods!
|
20
|
+
|
21
|
+
extend_strings_and_symbols!
|
22
|
+
|
23
|
+
return unless experimental
|
24
|
+
|
25
|
+
array_shorthand!
|
26
|
+
string_shorthand!
|
27
|
+
symbol_shorthand!
|
28
|
+
end
|
29
|
+
|
30
|
+
# Enhance `String` with {Dux::Duckify}
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
def extend_strings!
|
34
|
+
String.__send__ :prepend, Dux::Duckify
|
35
|
+
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Enhance `Symbol` with {Dux::Duckify}
|
40
|
+
#
|
41
|
+
# @return [void]
|
42
|
+
def extend_symbols!
|
43
|
+
Symbol.__send__ :prepend, Dux::Duckify
|
44
|
+
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Enhance `String` and `Symbol` classes with {Dux::Duckify#duckify}
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
def extend_strings_and_symbols!
|
52
|
+
extend_strings!
|
53
|
+
extend_symbols!
|
54
|
+
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# Experimental feature to add unary `~` to `Array`s
|
59
|
+
# for quick usage in case statements.
|
60
|
+
#
|
61
|
+
# @return [void]
|
62
|
+
def array_shorthand!
|
63
|
+
add_flock_methods!
|
64
|
+
|
65
|
+
Array.__send__ :prepend, Dux::HacksLikeADuck
|
66
|
+
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Experimental feature to add unary `~` to `String`s
|
71
|
+
# for quick usage in case statements.
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
def string_shorthand!
|
75
|
+
extend_strings!
|
76
|
+
|
77
|
+
String.__send__ :prepend, Dux::HacksLikeADuck
|
78
|
+
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
# Experimental feature to add unary `~` to `Symbol`s
|
83
|
+
# for quick usage in case statements.
|
84
|
+
#
|
85
|
+
# @return [void]
|
86
|
+
def symbol_shorthand!
|
87
|
+
extend_symbols!
|
88
|
+
|
89
|
+
Symbol.__send__ :prepend, Dux::HacksLikeADuck
|
90
|
+
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# @!endgroup
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
data/lib/dux/null_object.rb
CHANGED
@@ -1,30 +1,77 @@
|
|
1
1
|
module Dux
|
2
|
+
# Null objects that can be used for smarter
|
3
|
+
# argument building.
|
4
|
+
#
|
5
|
+
# @example Usage
|
6
|
+
# module Foo
|
7
|
+
# class Bar
|
8
|
+
# NULL = Dux.null 'Foo::Bar::NULL'
|
9
|
+
#
|
10
|
+
# # Making it private is not required,
|
11
|
+
# # but recommended if it is something
|
12
|
+
# # that should only be available
|
13
|
+
# # internally.
|
14
|
+
# private_constant :NULL
|
15
|
+
#
|
16
|
+
# def initialize(some_option: NULL)
|
17
|
+
# if some_option == NULL
|
18
|
+
# # Do some default logic
|
19
|
+
# else
|
20
|
+
# @some_option = some_option
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
2
25
|
class NullObject
|
3
26
|
include Dux::InspectID
|
4
27
|
|
28
|
+
# @!attribute [r] name
|
29
|
+
# The name of this null object,
|
30
|
+
# for self-documenting / introspection.
|
31
|
+
#
|
32
|
+
# In practice, this should be the object path,
|
33
|
+
# e.g. `Foo::Bar::NULL`
|
34
|
+
# @return [String]
|
5
35
|
attr_reader :name
|
36
|
+
|
37
|
+
# @!attribute [r] purpose
|
38
|
+
# A purpose or description of this object,
|
39
|
+
# for self-documenting / introspection.
|
40
|
+
# @return [String]
|
6
41
|
attr_reader :purpose
|
7
42
|
|
8
43
|
# @param [String] name
|
9
44
|
# @param [String] purpose
|
10
|
-
def initialize(name, purpose: 'a null object')
|
11
|
-
@name = name
|
45
|
+
def initialize(name = nil, purpose: 'a null object')
|
46
|
+
@name = name || default_name
|
12
47
|
@purpose = purpose
|
48
|
+
|
49
|
+
freeze
|
13
50
|
end
|
14
51
|
|
15
52
|
private
|
53
|
+
|
54
|
+
# Generates a default name for this object.
|
55
|
+
#
|
56
|
+
# @return [String]
|
16
57
|
def default_name
|
17
|
-
|
58
|
+
"Dux::NullObject(#{inspect_id(self)})"
|
18
59
|
end
|
19
60
|
end
|
20
61
|
|
21
62
|
class << self
|
22
|
-
#
|
63
|
+
# @!group DSL
|
64
|
+
|
65
|
+
# Create {Dux::NullObject a null object} with the provided options.
|
66
|
+
#
|
67
|
+
# @see Dux::NullObject#initialize
|
23
68
|
# @return [Dux::NullObject]
|
24
|
-
def null_object(name, **options)
|
69
|
+
def null_object(name = nil, **options)
|
25
70
|
Dux::NullObject.new(name, **options)
|
26
71
|
end
|
27
72
|
|
28
73
|
alias_method :null, :null_object
|
74
|
+
|
75
|
+
# @!endgroup DSL
|
29
76
|
end
|
30
77
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Dux
|
2
|
+
# Methods for generating "interface" checks against an array of symbols
|
3
|
+
FLOCK_TYPES = Dux.enum :all, :any, :none
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# @!group Predicates
|
7
|
+
|
8
|
+
# Create a predicate that checks if the provided object
|
9
|
+
# responds to the `symbol`.
|
10
|
+
#
|
11
|
+
# @param [Symbol] symbol
|
12
|
+
# @param [Boolean] include_all
|
13
|
+
# @return [Proc]
|
14
|
+
def [](symbol, include_all: false)
|
15
|
+
->(obj) { obj.respond_to? symbol, include_all }
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :predicate, :[]
|
19
|
+
alias_method :dux, :[]
|
20
|
+
|
21
|
+
# Create a predicate that checks if the provided `Class` or `Module`
|
22
|
+
# inherits from a given class or module.
|
23
|
+
#
|
24
|
+
# @param [Class, Module] parent
|
25
|
+
# @param [Boolean] include_self
|
26
|
+
# @raise [TypeError] requires a class or module to serve as parent
|
27
|
+
# @return [Proc]
|
28
|
+
def inherits(parent, include_self: false)
|
29
|
+
raise TypeError, "Must be a class or module to check inheritance" unless inheritable?(parent)
|
30
|
+
|
31
|
+
if include_self
|
32
|
+
->(klass) { Dux.inheritable?(klass) && klass <= parent }
|
33
|
+
else
|
34
|
+
->(klass) { Dux.inheritable?(klass) && klass < parent }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :includes, :inherits
|
39
|
+
alias_method :prepends, :inherits
|
40
|
+
|
41
|
+
# Create a predicate that checks if the provided `Class` or `Module`
|
42
|
+
# extends a given `parent` module.
|
43
|
+
#
|
44
|
+
# @param [Module] parent
|
45
|
+
# @raise [TypeError] requires a module to check extension
|
46
|
+
# @return [Proc]
|
47
|
+
def extends(parent)
|
48
|
+
raise TypeError, "Must be a module to check if something extends it" unless parent.kind_of?(Module)
|
49
|
+
|
50
|
+
->(klass) { Dux.inheritable?(klass) && klass.singleton_class < parent }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create a predicate that checks against a specific YARD type description.
|
54
|
+
#
|
55
|
+
# @note Some limitations apply because of the underlying gem, e.g.
|
56
|
+
# symbol / string literals are not matched and will raise an error
|
57
|
+
# @see https://github.com/pd/yard_types the gem used for parsing types
|
58
|
+
# @raise [SyntaxError] when `YardTypes` fails to parse.
|
59
|
+
# @param [String] pattern
|
60
|
+
# @return [Proc]
|
61
|
+
def yard(pattern)
|
62
|
+
type = YardTypes.parse pattern
|
63
|
+
|
64
|
+
->(value) { !type.check(value).nil? }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if the provided thing is a class or a module.
|
68
|
+
#
|
69
|
+
# @param [Class, Module] klass_or_module
|
70
|
+
# @api private
|
71
|
+
def inheritable?(klass_or_module)
|
72
|
+
klass_or_module.kind_of?(Class) || klass_or_module.kind_of?(Module)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates a lambda that describes a restrictive interface,
|
76
|
+
# wherein the provided object must `respond_to?` *all* of
|
77
|
+
# the methods.
|
78
|
+
#
|
79
|
+
# @param [<Symbol, String>] methods
|
80
|
+
# @param [Boolean] include_all
|
81
|
+
# @return [Proc]
|
82
|
+
def all(*methods, include_all: false)
|
83
|
+
ducks = flockify methods, include_all: include_all
|
84
|
+
|
85
|
+
->(obj) { ducks.all? { |duck| duck.call obj } }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Creates a lambda that describes a permissive interface,
|
89
|
+
# wherein the provided object must `respond_to?` at least
|
90
|
+
# one of the methods.
|
91
|
+
#
|
92
|
+
# @param [<Symbol>] methods
|
93
|
+
# @param [Boolean] include_all
|
94
|
+
# @return [Proc]
|
95
|
+
def any(*methods, include_all: false)
|
96
|
+
ducks = flockify methods, include_all: include_all
|
97
|
+
|
98
|
+
->(obj) { ducks.any? { |duck| duck.call obj } }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Creates a lambda that describes a restrictive interface,
|
102
|
+
# wherein the provided object must `respond_to?` *none* of
|
103
|
+
# the methods.
|
104
|
+
#
|
105
|
+
# @param [<Symbol>] methods
|
106
|
+
# @param [Boolean] include_all
|
107
|
+
# @return [Proc]
|
108
|
+
def none(*methods, include_all: false)
|
109
|
+
ducks = flockify methods, include_all: include_all
|
110
|
+
|
111
|
+
->(obj) { ducks.none? { |duck| duck.call obj } }
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param [:all, :any, :none] type
|
115
|
+
# @param [<Symbol, String>] methods
|
116
|
+
# @param [Boolean] include_all
|
117
|
+
# @raise [ArgumentError] on invalid type
|
118
|
+
# @api private
|
119
|
+
# @return [<Proc>]
|
120
|
+
def flock(type, *methods, include_all: false)
|
121
|
+
type = FLOCK_TYPES.fetch(type) { raise ArgumentError, "Invalid flock type: #{type}" }
|
122
|
+
|
123
|
+
__send__ type, methods, include_all: include_all
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
|
128
|
+
# @param [<Symbol>] methods
|
129
|
+
# @param [Boolean] include_all
|
130
|
+
# @return [<Proc>]
|
131
|
+
def flockify(methods, include_all: false)
|
132
|
+
methods.flatten.map do |sym|
|
133
|
+
dux sym, include_all: include_all
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @!endgroup
|
138
|
+
end
|
139
|
+
end
|
data/lib/dux/version.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dux
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexa Grey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-04-
|
11
|
+
date: 2017-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: yard_types
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -132,14 +132,16 @@ files:
|
|
132
132
|
- lib/dux.rb
|
133
133
|
- lib/dux/attempt.rb
|
134
134
|
- lib/dux/blankness.rb
|
135
|
-
- lib/dux/
|
135
|
+
- lib/dux/comparable.rb
|
136
136
|
- lib/dux/duckify.rb
|
137
137
|
- lib/dux/enum.rb
|
138
138
|
- lib/dux/flock_methods.rb
|
139
139
|
- lib/dux/hacks_like_a_duck.rb
|
140
140
|
- lib/dux/indifferent_string.rb
|
141
141
|
- lib/dux/inspect_id.rb
|
142
|
+
- lib/dux/monkey_patch.rb
|
142
143
|
- lib/dux/null_object.rb
|
144
|
+
- lib/dux/predicate.rb
|
143
145
|
- lib/dux/version.rb
|
144
146
|
homepage: https://github.com/scryptmouse/dux
|
145
147
|
licenses:
|
@@ -165,5 +167,5 @@ rubyforge_project:
|
|
165
167
|
rubygems_version: 2.6.8
|
166
168
|
signing_key:
|
167
169
|
specification_version: 4
|
168
|
-
summary:
|
170
|
+
summary: Swiss-army knife gem for duck-type matching and utility methods
|
169
171
|
test_files: []
|
data/lib/dux/duck_type.rb
DELETED
File without changes
|