nrser 0.0.30 → 0.1.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.
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