nrser 0.0.30 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/nrser.rb +56 -12
- data/lib/nrser/collection.rb +4 -7
- data/lib/nrser/ext.rb +5 -0
- data/lib/nrser/{refinements → ext}/enumerable.rb +11 -9
- data/lib/nrser/ext/pathname.rb +74 -0
- data/lib/nrser/{refinements → ext}/tree.rb +2 -26
- data/lib/nrser/functions.rb +18 -0
- data/lib/nrser/{array.rb → functions/array.rb} +2 -3
- data/lib/nrser/{binding.rb → functions/binding.rb} +0 -2
- data/lib/nrser/functions/enumerable.rb +355 -0
- data/lib/nrser/functions/enumerable/find_all_map.rb +33 -0
- data/lib/nrser/functions/enumerable/find_map.rb +53 -0
- data/lib/nrser/functions/exception.rb +17 -0
- data/lib/nrser/{hash.rb → functions/hash.rb} +0 -0
- data/lib/nrser/functions/hash/bury.rb +147 -0
- data/lib/nrser/{hash → functions/hash}/deep_merge.rb +5 -5
- data/lib/nrser/{hash → functions/hash}/except_keys.rb +2 -0
- data/lib/nrser/{hash → functions/hash}/guess_label_key_type.rb +3 -1
- data/lib/nrser/{hash → functions/hash}/slice_keys.rb +3 -1
- data/lib/nrser/{hash → functions/hash}/stringify_keys.rb +2 -0
- data/lib/nrser/{hash → functions/hash}/symbolize_keys.rb +3 -1
- data/lib/nrser/{hash → functions/hash}/transform_keys.rb +3 -1
- data/lib/nrser/functions/merge_by.rb +29 -0
- data/lib/nrser/{object.rb → functions/object.rb} +0 -0
- data/lib/nrser/{object → functions/object}/as_array.rb +2 -0
- data/lib/nrser/{object → functions/object}/as_hash.rb +7 -5
- data/lib/nrser/{object → functions/object}/truthy.rb +46 -7
- data/lib/nrser/{open_struct.rb → functions/open_struct.rb} +0 -0
- data/lib/nrser/functions/path.rb +150 -0
- data/lib/nrser/{proc.rb → functions/proc.rb} +1 -22
- data/lib/nrser/functions/string.rb +297 -0
- data/lib/nrser/functions/string/looks_like.rb +44 -0
- data/lib/nrser/{text.rb → functions/text.rb} +0 -0
- data/lib/nrser/{text → functions/text}/indentation.rb +2 -16
- data/lib/nrser/{text → functions/text}/lines.rb +1 -2
- data/lib/nrser/{text → functions/text}/word_wrap.rb +2 -4
- data/lib/nrser/{tree.rb → functions/tree.rb} +0 -0
- data/lib/nrser/{tree → functions/tree}/each_branch.rb +6 -7
- data/lib/nrser/functions/tree/leaves.rb +92 -0
- data/lib/nrser/{tree → functions/tree}/map_branches.rb +31 -32
- data/lib/nrser/functions/tree/map_leaves.rb +56 -0
- data/lib/nrser/{tree → functions/tree}/map_tree.rb +9 -20
- data/lib/nrser/{tree → functions/tree}/transform.rb +0 -10
- data/lib/nrser/logger.rb +9 -10
- data/lib/nrser/message.rb +3 -7
- data/lib/nrser/meta.rb +2 -0
- data/lib/nrser/meta/class_attrs.rb +3 -9
- data/lib/nrser/meta/props.rb +19 -19
- data/lib/nrser/meta/props/base.rb +4 -10
- data/lib/nrser/meta/props/prop.rb +12 -28
- data/lib/nrser/no_arg.rb +1 -3
- data/lib/nrser/refinements.rb +5 -0
- data/lib/nrser/refinements/array.rb +5 -17
- data/lib/nrser/refinements/enumerator.rb +1 -3
- data/lib/nrser/refinements/hash.rb +3 -15
- data/lib/nrser/refinements/object.rb +2 -2
- data/lib/nrser/refinements/open_struct.rb +0 -2
- data/lib/nrser/refinements/pathname.rb +3 -46
- data/lib/nrser/refinements/set.rb +2 -6
- data/lib/nrser/refinements/string.rb +2 -2
- data/lib/nrser/rspex.rb +16 -13
- data/lib/nrser/types.rb +6 -20
- data/lib/nrser/types/any.rb +0 -1
- data/lib/nrser/types/booleans.rb +1 -1
- data/lib/nrser/types/combinators.rb +5 -5
- data/lib/nrser/types/in.rb +0 -21
- data/lib/nrser/types/responds.rb +1 -0
- data/lib/nrser/types/trees.rb +1 -0
- data/lib/nrser/version.rb +2 -3
- data/spec/nrser/{template_spec.rb → functions/binding/template_spec.rb} +0 -0
- data/spec/nrser/functions/enumerable/find_all_map_spec.rb +28 -0
- data/spec/nrser/functions/enumerable/find_bounded_spec.rb +70 -0
- data/spec/nrser/functions/enumerable/find_map_spec.rb +38 -0
- data/spec/nrser/functions/enumerable/find_only_spec.rb +25 -0
- data/spec/nrser/functions/enumerable/to_h_by_spec.rb +28 -0
- data/spec/nrser/{format_exception_spec.rb → functions/exception/format_exception_spec.rb} +0 -0
- data/spec/nrser/{hash → functions/hash}/bury_spec.rb +0 -0
- data/spec/nrser/{hash → functions/hash}/guess_label_key_type_spec.rb +0 -0
- data/spec/nrser/{hash_spec.rb → functions/hash_spec.rb} +0 -7
- data/spec/nrser/{merge_by_spec.rb → functions/merge_by_spec.rb} +0 -0
- data/spec/nrser/{truthy_spec.rb → functions/object/truthy_spec.rb} +0 -0
- data/spec/nrser/{open_struct_spec.rb → functions/open_struct_spec.rb} +0 -0
- data/spec/nrser/{string → functions/string}/common_prefix_spec.rb +0 -0
- data/spec/nrser/{string → functions/string}/looks_like_spec.rb +0 -0
- data/spec/nrser/{truncate_spec.rb → functions/string/truncate_spec.rb} +0 -0
- data/spec/nrser/{text → functions/text}/dedent/gotchas_spec.rb +0 -0
- data/spec/nrser/{text → functions/text}/dedent_spec.rb +0 -0
- data/spec/nrser/{indent_spec.rb → functions/text/indent_spec.rb} +0 -0
- data/spec/nrser/{tree → functions/tree}/each_branch_spec.rb +0 -0
- data/spec/nrser/{tree → functions/tree}/leaves_spec.rb +0 -0
- data/spec/nrser/{tree → functions/tree}/map_branch_spec.rb +0 -0
- data/spec/nrser/{tree → functions/tree}/map_tree_spec.rb +0 -0
- data/spec/nrser/{tree → functions/tree}/transform_spec.rb +0 -0
- data/spec/nrser/{tree → functions/tree}/transformer_spec.rb +0 -0
- data/spec/nrser/meta/class_attrs_spec.rb +12 -14
- data/spec/spec_helper.rb +2 -3
- metadata +136 -110
- data/lib/nrser/enumerable.rb +0 -288
- data/lib/nrser/exception.rb +0 -7
- data/lib/nrser/hash/bury.rb +0 -154
- data/lib/nrser/merge_by.rb +0 -26
- data/lib/nrser/string.rb +0 -294
- data/lib/nrser/string/looks_like.rb +0 -51
- data/lib/nrser/tree/leaves.rb +0 -92
- data/lib/nrser/tree/map_leaves.rb +0 -63
- data/spec/nrser/enumerable_spec.rb +0 -111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b087fa91e207a8859fdcf2335e50bc2dc8661fb8
|
4
|
+
data.tar.gz: 6a0edb30a2236bde01044c9de2189edc7f42bac3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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'
|
data/lib/nrser/collection.rb
CHANGED
@@ -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
@@ -1,13 +1,9 @@
|
|
1
|
-
|
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
|
-
#
|
8
|
-
#
|
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::
|
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
|
-
|
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::
|
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::
|
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'
|
@@ -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
|