nrser 0.0.30 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nrser.rb +56 -12
  3. data/lib/nrser/collection.rb +4 -7
  4. data/lib/nrser/ext.rb +5 -0
  5. data/lib/nrser/{refinements → ext}/enumerable.rb +11 -9
  6. data/lib/nrser/ext/pathname.rb +74 -0
  7. data/lib/nrser/{refinements → ext}/tree.rb +2 -26
  8. data/lib/nrser/functions.rb +18 -0
  9. data/lib/nrser/{array.rb → functions/array.rb} +2 -3
  10. data/lib/nrser/{binding.rb → functions/binding.rb} +0 -2
  11. data/lib/nrser/functions/enumerable.rb +355 -0
  12. data/lib/nrser/functions/enumerable/find_all_map.rb +33 -0
  13. data/lib/nrser/functions/enumerable/find_map.rb +53 -0
  14. data/lib/nrser/functions/exception.rb +17 -0
  15. data/lib/nrser/{hash.rb → functions/hash.rb} +0 -0
  16. data/lib/nrser/functions/hash/bury.rb +147 -0
  17. data/lib/nrser/{hash → functions/hash}/deep_merge.rb +5 -5
  18. data/lib/nrser/{hash → functions/hash}/except_keys.rb +2 -0
  19. data/lib/nrser/{hash → functions/hash}/guess_label_key_type.rb +3 -1
  20. data/lib/nrser/{hash → functions/hash}/slice_keys.rb +3 -1
  21. data/lib/nrser/{hash → functions/hash}/stringify_keys.rb +2 -0
  22. data/lib/nrser/{hash → functions/hash}/symbolize_keys.rb +3 -1
  23. data/lib/nrser/{hash → functions/hash}/transform_keys.rb +3 -1
  24. data/lib/nrser/functions/merge_by.rb +29 -0
  25. data/lib/nrser/{object.rb → functions/object.rb} +0 -0
  26. data/lib/nrser/{object → functions/object}/as_array.rb +2 -0
  27. data/lib/nrser/{object → functions/object}/as_hash.rb +7 -5
  28. data/lib/nrser/{object → functions/object}/truthy.rb +46 -7
  29. data/lib/nrser/{open_struct.rb → functions/open_struct.rb} +0 -0
  30. data/lib/nrser/functions/path.rb +150 -0
  31. data/lib/nrser/{proc.rb → functions/proc.rb} +1 -22
  32. data/lib/nrser/functions/string.rb +297 -0
  33. data/lib/nrser/functions/string/looks_like.rb +44 -0
  34. data/lib/nrser/{text.rb → functions/text.rb} +0 -0
  35. data/lib/nrser/{text → functions/text}/indentation.rb +2 -16
  36. data/lib/nrser/{text → functions/text}/lines.rb +1 -2
  37. data/lib/nrser/{text → functions/text}/word_wrap.rb +2 -4
  38. data/lib/nrser/{tree.rb → functions/tree.rb} +0 -0
  39. data/lib/nrser/{tree → functions/tree}/each_branch.rb +6 -7
  40. data/lib/nrser/functions/tree/leaves.rb +92 -0
  41. data/lib/nrser/{tree → functions/tree}/map_branches.rb +31 -32
  42. data/lib/nrser/functions/tree/map_leaves.rb +56 -0
  43. data/lib/nrser/{tree → functions/tree}/map_tree.rb +9 -20
  44. data/lib/nrser/{tree → functions/tree}/transform.rb +0 -10
  45. data/lib/nrser/logger.rb +9 -10
  46. data/lib/nrser/message.rb +3 -7
  47. data/lib/nrser/meta.rb +2 -0
  48. data/lib/nrser/meta/class_attrs.rb +3 -9
  49. data/lib/nrser/meta/props.rb +19 -19
  50. data/lib/nrser/meta/props/base.rb +4 -10
  51. data/lib/nrser/meta/props/prop.rb +12 -28
  52. data/lib/nrser/no_arg.rb +1 -3
  53. data/lib/nrser/refinements.rb +5 -0
  54. data/lib/nrser/refinements/array.rb +5 -17
  55. data/lib/nrser/refinements/enumerator.rb +1 -3
  56. data/lib/nrser/refinements/hash.rb +3 -15
  57. data/lib/nrser/refinements/object.rb +2 -2
  58. data/lib/nrser/refinements/open_struct.rb +0 -2
  59. data/lib/nrser/refinements/pathname.rb +3 -46
  60. data/lib/nrser/refinements/set.rb +2 -6
  61. data/lib/nrser/refinements/string.rb +2 -2
  62. data/lib/nrser/rspex.rb +16 -13
  63. data/lib/nrser/types.rb +6 -20
  64. data/lib/nrser/types/any.rb +0 -1
  65. data/lib/nrser/types/booleans.rb +1 -1
  66. data/lib/nrser/types/combinators.rb +5 -5
  67. data/lib/nrser/types/in.rb +0 -21
  68. data/lib/nrser/types/responds.rb +1 -0
  69. data/lib/nrser/types/trees.rb +1 -0
  70. data/lib/nrser/version.rb +2 -3
  71. data/spec/nrser/{template_spec.rb → functions/binding/template_spec.rb} +0 -0
  72. data/spec/nrser/functions/enumerable/find_all_map_spec.rb +28 -0
  73. data/spec/nrser/functions/enumerable/find_bounded_spec.rb +70 -0
  74. data/spec/nrser/functions/enumerable/find_map_spec.rb +38 -0
  75. data/spec/nrser/functions/enumerable/find_only_spec.rb +25 -0
  76. data/spec/nrser/functions/enumerable/to_h_by_spec.rb +28 -0
  77. data/spec/nrser/{format_exception_spec.rb → functions/exception/format_exception_spec.rb} +0 -0
  78. data/spec/nrser/{hash → functions/hash}/bury_spec.rb +0 -0
  79. data/spec/nrser/{hash → functions/hash}/guess_label_key_type_spec.rb +0 -0
  80. data/spec/nrser/{hash_spec.rb → functions/hash_spec.rb} +0 -7
  81. data/spec/nrser/{merge_by_spec.rb → functions/merge_by_spec.rb} +0 -0
  82. data/spec/nrser/{truthy_spec.rb → functions/object/truthy_spec.rb} +0 -0
  83. data/spec/nrser/{open_struct_spec.rb → functions/open_struct_spec.rb} +0 -0
  84. data/spec/nrser/{string → functions/string}/common_prefix_spec.rb +0 -0
  85. data/spec/nrser/{string → functions/string}/looks_like_spec.rb +0 -0
  86. data/spec/nrser/{truncate_spec.rb → functions/string/truncate_spec.rb} +0 -0
  87. data/spec/nrser/{text → functions/text}/dedent/gotchas_spec.rb +0 -0
  88. data/spec/nrser/{text → functions/text}/dedent_spec.rb +0 -0
  89. data/spec/nrser/{indent_spec.rb → functions/text/indent_spec.rb} +0 -0
  90. data/spec/nrser/{tree → functions/tree}/each_branch_spec.rb +0 -0
  91. data/spec/nrser/{tree → functions/tree}/leaves_spec.rb +0 -0
  92. data/spec/nrser/{tree → functions/tree}/map_branch_spec.rb +0 -0
  93. data/spec/nrser/{tree → functions/tree}/map_tree_spec.rb +0 -0
  94. data/spec/nrser/{tree → functions/tree}/transform_spec.rb +0 -0
  95. data/spec/nrser/{tree → functions/tree}/transformer_spec.rb +0 -0
  96. data/spec/nrser/meta/class_attrs_spec.rb +12 -14
  97. data/spec/spec_helper.rb +2 -3
  98. metadata +136 -110
  99. data/lib/nrser/enumerable.rb +0 -288
  100. data/lib/nrser/exception.rb +0 -7
  101. data/lib/nrser/hash/bury.rb +0 -154
  102. data/lib/nrser/merge_by.rb +0 -26
  103. data/lib/nrser/string.rb +0 -294
  104. data/lib/nrser/string/looks_like.rb +0 -51
  105. data/lib/nrser/tree/leaves.rb +0 -92
  106. data/lib/nrser/tree/map_leaves.rb +0 -63
  107. data/spec/nrser/enumerable_spec.rb +0 -111
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e894a45a17099589d7b23f53b7b2c416d448b6d3
4
- data.tar.gz: 9e66a57ccee2dba38b1bf16e45a55197fa80ccac
3
+ metadata.gz: b087fa91e207a8859fdcf2335e50bc2dc8661fb8
4
+ data.tar.gz: 6a0edb30a2236bde01044c9de2189edc7f42bac3
5
5
  SHA512:
6
- metadata.gz: 1132214b192658affc169b261f8d9840cbae0d2f3c33e2a7f48c64e2bfb6876411b50e5a3aa0b22a5245d82aa5dae5b48ef3c16fee9fa4b1ab295793b9ff7413
7
- data.tar.gz: 1676e9d23b8182bc24447008410498aeace1b57a5c0400f02da39cb1e2e64cb2f24099253dc0ad3815c9fb00e9de24e899ee5a5c6389cffc219aa1e711211a6e
6
+ metadata.gz: a8f5e75698179ea8beac5a4e40fe6f52f006817646fc6c47cc04c6b505e144e0b07066023ad547c49d0d9c3eea52bda2520816b9b9ed2bd15373cd55cd92aad0
7
+ data.tar.gz: 8a2e9a4b5deee46c2c9e5f34142618b1148ed5ae23ba1cdceb8e573e7ec5575c7e96c24ba62fe2ad3188be3618aa0922cbebb4810c2344dd9ce99c619a72ed8b
data/lib/nrser.rb CHANGED
@@ -1,24 +1,68 @@
1
+ # Requirements
2
+ # =======================================================================
3
+ #
4
+ # I'm moving to "just require everything" after abandoning the idea of
5
+ # supporting old rubies (2.3 is now min), so all the require expressions
6
+ # should just go here, making life much simpler.
7
+ #
8
+
9
+ # Stdlib
10
+ # -----------------------------------------------------------------------
1
11
  require 'pathname'
12
+ require 'set'
13
+ require 'pp'
14
+ require 'ostruct'
15
+ require 'json'
16
+ require 'yaml'
17
+ require 'logger'
18
+ require 'singleton'
19
+
20
+
21
+ # Deps
22
+ # -----------------------------------------------------------------------
23
+
2
24
 
25
+ # Hi there!
26
+ #
27
+ # This is [my][me] Ruby stuff.
28
+ #
29
+ # [me]: https://github.com/nrser
30
+ #
31
+ # This module has a lot of static methods (`class` or `self.` methods), which
32
+ # are functional (pure).
33
+ #
34
+ # They're all just directly attached to the {NRSER} object to make them
35
+ # convenient to call, but they're grouped here by what they operate on.
36
+ #
37
+ # You can go ahead and use them like that, but many (most?) of them are also
38
+ # refined in to the classes of their first arguments in `nrser/refinements`,
39
+ # which is how I mostly use them, unless I'm targeting an older Ruby that
40
+ # doesn't support refinements.
41
+ #
42
+ # There are also various classes and sub-modules as well. Most of the
43
+ # sub-modules require refinements because though it's nice to have the core
44
+ # functionality available in a pinch for earlier Rubies, I'm not targeting
45
+ # versions that far back in any serious or complicated code.
46
+ #
47
+ # Enjoy!
48
+ #
3
49
  module NRSER
50
+
51
+ # Absolute, expanded path to the gem's root directory.
52
+ #
53
+ # @return [Pathname]
54
+ #
4
55
  ROOT = ( Pathname.new(__FILE__).dirname / '..' ).expand_path
56
+
5
57
  end
6
58
 
59
+ require_relative './nrser/ext'
60
+ require_relative './nrser/errors'
7
61
  require_relative './nrser/version'
8
62
  require_relative './nrser/no_arg'
9
63
  require_relative './nrser/message'
10
- require_relative './nrser/proc'
11
64
  require_relative './nrser/collection'
12
- require_relative './nrser/object'
13
- require_relative './nrser/string'
14
- require_relative './nrser/text'
15
- require_relative './nrser/binding'
16
- require_relative './nrser/exception'
17
- require_relative './nrser/enumerable'
18
- require_relative './nrser/hash'
19
- require_relative './nrser/array'
65
+ require_relative './nrser/functions'
20
66
  require_relative './nrser/types'
67
+ require_relative './nrser/refinements'
21
68
  require_relative './nrser/meta'
22
- require_relative './nrser/open_struct'
23
- require_relative './nrser/merge_by'
24
- require_relative './nrser/tree'
@@ -1,6 +1,3 @@
1
- require 'set'
2
- require 'ostruct'
3
-
4
1
  module NRSER
5
2
  # include this module in any custom classes to have them treated as
6
3
  # collections instead of individual objects by the methods in this file
@@ -36,7 +33,7 @@ module NRSER
36
33
  #
37
34
  # **NOTE** Implemented for our idea of a collection instead of testing
38
35
  # for response to `#each` (or similar) to avoid catching things
39
- # like {IO} instances, which include {Enumerable} but are
36
+ # like {IO} instances, which include {Enumerable} but are
40
37
  # probably not what is desired when using {NRSER.each}
41
38
  # (more likely that you mean "I expect one or more files" than
42
39
  # "I expect one or more strings which may be represented by
@@ -54,9 +51,9 @@ module NRSER
54
51
  def each object, &block
55
52
  if collection? object
56
53
  # We need to test for response because {OpenStruct} *will* respond to
57
- # #each because *it will respond to anything* (which sucks), but it
54
+ # #each because *it will respond to anything* (which sucks), but it
58
55
  # will return `false` for `respond_to? :each` and the like, and this
59
- # behavior could be shared by other collection objects, so it seems
56
+ # behavior could be shared by other collection objects, so it seems
60
57
  # like a decent idea.
61
58
  if object.respond_to? :each_pair
62
59
  object.each_pair &block
@@ -98,4 +95,4 @@ module NRSER
98
95
  end # #map
99
96
 
100
97
  end # class << self
101
- end # module NRSER
98
+ end # module NRSER
data/lib/nrser/ext.rb ADDED
@@ -0,0 +1,5 @@
1
+ module NRSER::Ext; end
2
+
3
+ require_relative './ext/enumerable'
4
+ require_relative './ext/tree'
5
+ require_relative './ext/pathname'
@@ -1,13 +1,9 @@
1
- module NRSER; end
2
- module NRSER::Refinements; end
3
-
4
- # Instance methods that are mixed in to the refinements of many classes that
5
- # include {Enumerable}, including {Array}, {Set}, {Hash} and {OpenStruct}.
1
+ # Instance methods to extend {Enumerable} objects.
6
2
  #
7
- # All of these just proxy to a {NRSER} module (static) method, so the
8
- # functionality can be used on older Rubies that can't refine.
3
+ # Refined into many of them, including {Array}, {Set}, {Hash} and {OpenStruct},
4
+ # and may be independently used as well.
9
5
  #
10
- module NRSER::Refinements::Enumerable
6
+ module NRSER::Ext::Enumerable
11
7
 
12
8
  # See {NRSER.map_values}
13
9
  def map_values &block
@@ -62,4 +58,10 @@ module NRSER::Refinements::Enumerable
62
58
  NRSER.try_find self, &block
63
59
  end
64
60
 
65
- end # module NRSER::Refinements::Enumerable
61
+
62
+ # See {NRSER.find_map}
63
+ def find_map *args, &block
64
+ NRSER.find_map self, *args, &block
65
+ end
66
+
67
+ end # module NRSER::Ext::Enumerable
@@ -0,0 +1,74 @@
1
+
2
+ # @todo document NRSER::Ext::Pathname module.
3
+ module NRSER::Ext::Pathname
4
+
5
+ # override to accept Pathname instances.
6
+ #
7
+ # @param [String] *prefixes
8
+ # the prefixes to see if the Pathname starts with.
9
+ #
10
+ # @return [Boolean]
11
+ # true if the Pathname starts with any of the prefixes.
12
+ #
13
+ def start_with? *prefixes
14
+ to_s.start_with? *prefixes.map(&:to_s)
15
+ end
16
+
17
+
18
+ # override sub to support Pathname instances as patterns.
19
+ #
20
+ # @param [String | Regexp | Pathname] pattern
21
+ # thing to replace.
22
+ #
23
+ # @param [String | Hash] replacement
24
+ # thing to replace it with.
25
+ #
26
+ # @return [Pathname]
27
+ # new Pathname.
28
+ #
29
+ def sub pattern, replacement
30
+ case pattern
31
+ when Pathname
32
+ super pattern.to_s, replacement
33
+ else
34
+ super pattern, replacement
35
+ end
36
+ end
37
+
38
+
39
+ # Just returns `self`. Implemented to match the {String#to_pn} API so it
40
+ # can be called on an argument that may be either one.
41
+ #
42
+ # @return [Pathname]
43
+ #
44
+ def to_pn
45
+ self
46
+ end
47
+
48
+
49
+ # @todo Document find_root method.
50
+ #
51
+ # @param [type] arg_name
52
+ # @todo Add name param description.
53
+ #
54
+ # @return [return_type]
55
+ # @todo Document return value.
56
+ #
57
+ def find_up rel_path, **kwds
58
+ NRSER.find_up rel_path, **kwds, from: self
59
+ end # #find_root
60
+
61
+
62
+ # @todo Document find_root method.
63
+ #
64
+ # @param [type] arg_name
65
+ # @todo Add name param description.
66
+ #
67
+ # @return [return_type]
68
+ # @todo Document return value.
69
+ #
70
+ def find_up! rel_path, **kwds
71
+ NRSER.find_up! rel_path, **kwds, from: self
72
+ end # #find_root
73
+
74
+ end # module NRSER::Ext::Pathname
@@ -1,30 +1,7 @@
1
- # Requirements
2
- # =======================================================================
3
-
4
- # Stdlib
5
- # -----------------------------------------------------------------------
6
-
7
- # Deps
8
- # -----------------------------------------------------------------------
9
-
10
- # Project / Package
11
- # -----------------------------------------------------------------------
12
-
13
-
14
- # Declarations
15
- # =======================================================================
16
-
17
- module NRSER; end
18
- module NRSER::Refinements; end
19
-
20
-
21
- # Definitions
22
- # =======================================================================
23
-
24
1
  # Instance methods that are refined in to the Ruby built-ins that we consider
25
2
  # trees: {Array}, {Hash} and {OpenStruct}.
26
3
  #
27
- module NRSER::Refinements::Tree
4
+ module NRSER::Ext::Tree
28
5
 
29
6
  # Sends `self` to {NRSER.leaves}.
30
7
  def leaves
@@ -59,5 +36,4 @@ module NRSER::Refinements::Tree
59
36
  NRSER.map_tree self, **options, &block
60
37
  end
61
38
 
62
- end # module NRSER::Refinements::Tree
63
-
39
+ end # module NRSER::Ext::Tree
@@ -0,0 +1,18 @@
1
+ ##
2
+ # Require everything in `//lib/nrser/functions` - the core functional
3
+ # interfaces that underlie all the refinements.
4
+ ##
5
+
6
+ require_relative './functions/proc'
7
+ require_relative './functions/object'
8
+ require_relative './functions/string'
9
+ require_relative './functions/text'
10
+ require_relative './functions/binding'
11
+ require_relative './functions/exception'
12
+ require_relative './functions/enumerable'
13
+ require_relative './functions/hash'
14
+ require_relative './functions/array'
15
+ require_relative './functions/open_struct'
16
+ require_relative './functions/merge_by'
17
+ require_relative './functions/tree'
18
+ require_relative './functions/path'
@@ -1,4 +1,4 @@
1
- module NRSER
1
+ module NRSER
2
2
 
3
3
  # Functional implementation of "rest" for arrays. Used when refining `#rest`
4
4
  # into {Array}.
@@ -26,5 +26,4 @@ module NRSER
26
26
  extracted
27
27
  end
28
28
 
29
-
30
- end # module NRSER
29
+ end # module NRSER
@@ -1,5 +1,3 @@
1
- require_relative './string'
2
-
3
1
  module NRSER
4
2
  class << self
5
3
 
@@ -0,0 +1,355 @@
1
+ require_relative './enumerable/find_map'
2
+ require_relative './enumerable/find_all_map'
3
+
4
+ module NRSER
5
+
6
+ # @!group Enumerable Functions
7
+
8
+ # Test if an object is "array-like" - is it an Enumerable and does it respond
9
+ # to `#each_index`?
10
+ #
11
+ # @param [Object] object
12
+ # Any old thing.
13
+ #
14
+ # @return [Boolean]
15
+ # `true` if `object` is "array-like" for our purposes.
16
+ #
17
+ def self.array_like? object
18
+ object.is_a?( ::Enumerable ) &&
19
+ object.respond_to?( :each_index )
20
+ end # .array_like?
21
+
22
+
23
+ # Test if an object is "hash-like" - is it an Enumerable and does it respond
24
+ # to `#each_pair`?
25
+ #
26
+ # @param [Object] object
27
+ # Any old thing.
28
+ #
29
+ # @return [Boolean]
30
+ # `true` if `object` is "hash-like" for our purposes.
31
+ #
32
+ def self.hash_like? object
33
+ object.is_a?( ::Enumerable ) &&
34
+ object.respond_to?( :each_pair )
35
+ end # .hash_like?
36
+
37
+
38
+ # Maps an enumerable object to a *new* hash with the same keys and values
39
+ # obtained by calling `block` with the current key and value.
40
+ #
41
+ # If `enumerable` *does not* respond to `#to_pairs` then it's
42
+ # treated as a hash where the elements iterated by `#each` are it's keys
43
+ # and all it's values are `nil`.
44
+ #
45
+ # In this way, {NRSER.map_values} handles Hash, Array, Set, OpenStruct,
46
+ # and probably pretty much anything else reasonable you may throw at it.
47
+ #
48
+ # @param [#each_pair, #each] enum
49
+ #
50
+ # @yieldparam [Object] key
51
+ # The key that will be used for whatever value the block returns in the
52
+ # new hash.
53
+ #
54
+ # @yieldparam [nil, Object] value
55
+ # If `enumerable` responds to `#each_pair`, the second parameter it yielded
56
+ # along with `key`. Otherwise `nil`.
57
+ #
58
+ # @yieldreturn [Object]
59
+ # Value for the new hash.
60
+ #
61
+ # @return [Hash]
62
+ #
63
+ # @raise [TypeError]
64
+ # If `enumerable` does not respond to `#each_pair` or `#each`.
65
+ #
66
+ def self.map_values enum, &block
67
+ result = {}
68
+
69
+ if enum.respond_to? :each_pair
70
+ enum.each_pair { |key, value|
71
+ result[key] = block.call key, value
72
+ }
73
+ elsif enum.respond_to? :each
74
+ enum.each { |key|
75
+ result[key] = block.call key, nil
76
+ }
77
+ else
78
+ raise ArgumentError.new erb binding, <<-END
79
+ First argument to {NRSER.map_values} must respond to #each_pair or #each
80
+
81
+ Received
82
+
83
+ <%= enum.pretty_inspect %>
84
+
85
+ of class <%= enum.class %>
86
+ END
87
+ end
88
+
89
+ result
90
+ end # .map_values
91
+
92
+
93
+ # Find all entries in an {Enumerable} for which `&block` returns a truthy
94
+ # value, then check the amount of results found against the
95
+ # {NRSER::Types.length} created from `bounds`, raising a {TypeError} if
96
+ # the results' length doesn't satisfy the bounds type.
97
+ #
98
+ # @param [Enumerable<E>] enum
99
+ # The entries to search and check.
100
+ #
101
+ # @param [Integer | Hash] bounds
102
+ # Passed as only argument to {NRSER::Types.length} to create the length
103
+ # type the results are checked against.
104
+ #
105
+ # @param [Proc] &block
106
+ # `#find`/`#find_all`-style block that will be called with each entry
107
+ # from `enum`. Truthy responses mean the entry matched.
108
+ #
109
+ # @return [Array<E>]
110
+ # Found entries from `enum`.
111
+ #
112
+ # @raise [TypeError]
113
+ # If the results of `enum.find_all &block` don't satisfy `bounds`.
114
+ #
115
+ def self.find_bounded enum, bounds, &block
116
+ NRSER::Types.
117
+ length(bounds).
118
+ check(enum.find_all &block) { |type:, value:|
119
+ erb binding, <<-END
120
+
121
+ Length of found elements (<%= value.length %>) FAILED to
122
+ satisfy <%= type.to_s %>.
123
+
124
+ Found entries:
125
+
126
+ <%= value.pretty_inspect %>
127
+
128
+ from enumerable:
129
+
130
+ <%= enum.pretty_inspect %>
131
+
132
+ END
133
+ }
134
+ end # .find_bounded
135
+
136
+
137
+ # Find the only entry in `enum` for which `&block` responds truthy, raising
138
+ # if either no entries or more than one are found.
139
+ #
140
+ # Returns the entry itself, not an array of length 1.
141
+ #
142
+ # Just calls {NRSER.find_bounded} with `bounds = 1`.
143
+ #
144
+ # @param enum (see NRSER.find_bounded)
145
+ # @param &block (see NRSER.find_bounded)
146
+ #
147
+ # @return [E]
148
+ # Only entry in `enum` that `&block` matched.
149
+ #
150
+ # @raise [TypeError]
151
+ # If `&block` matched more or less than one entry.
152
+ #
153
+ def self.find_only enum, &block
154
+ find_bounded(enum, 1, &block).first
155
+ end # .find_only
156
+
157
+
158
+ # Return the first entry if the enumerable has `#count` one.
159
+ #
160
+ # Otherwise, return `default` (which defaults to `nil`).
161
+ #
162
+ # @param [Enumerable<E>] enum
163
+ # Enumerable in question (really, anything that responds to `#first` and
164
+ # `#count`).
165
+ #
166
+ # @param [D] default:
167
+ # Value to return if `enum` does not have only one entry.
168
+ #
169
+ # @return [E]
170
+ # When `enum` has `#count == 1`.
171
+ #
172
+ # @return [D]
173
+ # When `enum` does not have `#count == 1`.
174
+ #
175
+ def self.only enum, default: nil
176
+ if enum.count == 1
177
+ enum.first
178
+ else
179
+ default
180
+ end
181
+ end # .only
182
+
183
+
184
+ # Return the only entry if the enumerable has `#count` one. Otherwise
185
+ # raise an error.
186
+ #
187
+ # @param enum (see NRSER.only)
188
+ #
189
+ # @return [E]
190
+ # First element of `enum`.
191
+ #
192
+ # @raise [ArgumentError]
193
+ # If `enum` does not have `#count == 1`.
194
+ #
195
+ def self.only! enum
196
+ unless enum.count == 1
197
+ raise ArgumentError.new erb binding, <<-END
198
+ Expected enumerable to have #count == 1 but it has
199
+
200
+ #count = <%= enum.count %>
201
+
202
+ Enumerable (class: <%= enum.class %>):
203
+
204
+ <%= enum.pretty_inspect %>
205
+
206
+ END
207
+ end
208
+
209
+ enum.first
210
+ end # .only!
211
+
212
+
213
+ # Convert an enumerable to a hash by passing each entry through `&block` to
214
+ # get it's key, raising an error if multiple entries map to the same key.
215
+ #
216
+ # @param [Enumerable<V>] enum
217
+ # Enumerable containing the values for the hash.
218
+ #
219
+ # @param [Proc<(V)=>K>] &block
220
+ # Block that maps `enum` values to their hash keys.
221
+ #
222
+ # @return [Hash<K, V>]
223
+ #
224
+ # @raise [NRSER::ConflictError]
225
+ # If two values map to the same key.
226
+ #
227
+ def self.to_h_by enum, &block
228
+ enum.each_with_object( {} ) { |element, result|
229
+ key = block.call element
230
+
231
+ if result.key? key
232
+ raise NRSER::ConflictError.new erb binding, <<-END
233
+ Key <%= key.inspect %> is already in results with value:
234
+
235
+ <%= result[key].pretty_inspect %>
236
+
237
+ END
238
+ end
239
+
240
+ result[key] = element
241
+ }
242
+ end # .to_h_by
243
+
244
+
245
+ # Create an {Enumerator} that iterates over the "values" of an
246
+ # {Enumerable} `enum`. If `enum` responds to `#each_value` than we return
247
+ # that. Otherwise, we return `#each_entry`.
248
+ #
249
+ # @param [Enumerable] enum
250
+ #
251
+ # @return [Enumerator]
252
+ #
253
+ # @raise [ArgumentError]
254
+ # If `enum` doesn't respond to `#each_value` or `#each_entry`.
255
+ #
256
+ def self.enumerate_as_values enum
257
+ # NRSER.match enum,
258
+ # t.respond_to(:each_value), :each_value.to_proc,
259
+ # t.respond_to(:each_entry), :each_entry.to_proc
260
+ #
261
+ if enum.respond_to? :each_value
262
+ enum.each_value
263
+ elsif enum.respond_to? :each_entry
264
+ enum.each_entry
265
+ else
266
+ raise ArgumentError.new erb binding, <<-END
267
+ Expected `enum` arg to respond to :each_value or :each_entry, found:
268
+
269
+ <%= enum.inspect %>
270
+
271
+ END
272
+ end
273
+ end # .enumerate_as_values
274
+
275
+
276
+ # Count entries in an {Enumerable} by the value returned when they are
277
+ # passed to the block.
278
+ #
279
+ # @example Count array entries by class
280
+ #
281
+ # [1, 2, :three, 'four', 5, :six].count_by &:class
282
+ # # => {Fixnum=>3, Symbol=>2, String=>1}
283
+ #
284
+ # @param [Enumerable<E>] enum
285
+ # {Enumerable} (or other object with compatible `#each_with_object` and
286
+ # `#to_enum` methods) you want to count.
287
+ #
288
+ # @param [Proc<(E)=>C>] &block
289
+ # Block mapping entries in `enum` to the group to count them in.
290
+ #
291
+ # @return [Hash{C=>Integer}]
292
+ # Hash mapping groups to positive integer counts.
293
+ #
294
+ def self.count_by enum, &block
295
+ enum.each_with_object( Hash.new 0 ) do |entry, hash|
296
+ hash[block.call entry] += 1
297
+ end
298
+ end # .count_by
299
+
300
+
301
+ # Like `Enumerable#find`, but wraps each call to `&block` in a
302
+ # `begin` / `rescue`, returning the result of the first call that doesn't
303
+ # raise an error.
304
+ #
305
+ # If no calls succeed, raises a {NRSER::MultipleErrors} containing the
306
+ # errors from the block calls.
307
+ #
308
+ # @param [Enumerable<E>] enum
309
+ # Values to call `&block` with.
310
+ #
311
+ # @param [Proc<E=>V>] &block
312
+ # Block to call, which is expected to raise an error if it fails.
313
+ #
314
+ # @return [V]
315
+ # Result of first call to `&block` that doesn't raise.
316
+ #
317
+ # @raise [ArgumentError]
318
+ # If `enum` was empty (`enum#each` never yielded).
319
+ #
320
+ # @raise [NRSER::MultipleErrors]
321
+ # If all calls to `&block` failed.
322
+ #
323
+ def self.try_find enum, &block
324
+ errors = []
325
+
326
+ enum.each do |*args|
327
+ begin
328
+ result = block.call *args
329
+ rescue Exception => error
330
+ errors << error
331
+ else
332
+ return result
333
+ end
334
+ end
335
+
336
+ if errors.empty?
337
+ raise ArgumentError,
338
+ "Appears that enumerable was empty: #{ enum.inspect }"
339
+ else
340
+ raise NRSER::MultipleErrors.new errors
341
+ end
342
+ end # .try_find
343
+
344
+
345
+ # TODO It would be nice for this to work...
346
+ #
347
+ # def to_enum object, meth, *args
348
+ # unless object.respond_to?( meth )
349
+ # object = NRSER::Enumerable.new object
350
+ # end
351
+ #
352
+ # object.to_enum meth, *args
353
+ # end
354
+
355
+ end # module NRSER