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.
- 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
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# Definitions
|
|
4
|
+
# =======================================================================
|
|
5
|
+
|
|
6
|
+
module NRSER
|
|
7
|
+
# @!group Object Functions
|
|
8
|
+
|
|
9
|
+
# Constants
|
|
10
|
+
# ============================================================================
|
|
11
|
+
|
|
12
|
+
# Down-cased versions of strings that are considered to communicate true
|
|
13
|
+
# in things like ENV vars, CLI options, etc.
|
|
14
|
+
#
|
|
15
|
+
# @return [Set<String>]
|
|
16
|
+
#
|
|
5
17
|
TRUTHY_STRINGS = Set.new [
|
|
6
18
|
'true',
|
|
7
19
|
't',
|
|
@@ -9,9 +21,14 @@ module NRSER
|
|
|
9
21
|
'y',
|
|
10
22
|
'on',
|
|
11
23
|
'1',
|
|
12
|
-
]
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
13
26
|
|
|
14
|
-
# Down-cased versions of strings that are considered to communicate false
|
|
27
|
+
# Down-cased versions of strings that are considered to communicate false
|
|
28
|
+
# in things like ENV vars, CLI options, etc.
|
|
29
|
+
#
|
|
30
|
+
# @return [Set<String>]
|
|
31
|
+
#
|
|
15
32
|
FALSY_STRINGS = Set.new [
|
|
16
33
|
'false',
|
|
17
34
|
'f',
|
|
@@ -20,17 +37,30 @@ module NRSER
|
|
|
20
37
|
'off',
|
|
21
38
|
'0',
|
|
22
39
|
'',
|
|
23
|
-
]
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Functions
|
|
44
|
+
# ============================================================================
|
|
24
45
|
|
|
25
46
|
# Evaluate an object (that probably came from outside Ruby, like an
|
|
26
47
|
# environment variable) to see if it's meant to represent true or false.
|
|
27
48
|
#
|
|
28
|
-
# @
|
|
49
|
+
# @pure Return value depends only on parameters.
|
|
50
|
+
#
|
|
51
|
+
# @param [nil | String | Boolean] object
|
|
29
52
|
# Value to test.
|
|
30
53
|
#
|
|
31
54
|
# @return [Boolean]
|
|
32
55
|
# `true` if the object is "truthy".
|
|
33
56
|
#
|
|
57
|
+
# @raise [ArgumentError]
|
|
58
|
+
# When a string is received that is not in {NRSER::TRUTHY_STRINGS} or
|
|
59
|
+
# {NRSER::FALSY_STRINGS} (case insensitive).
|
|
60
|
+
#
|
|
61
|
+
# @raise [TypeError]
|
|
62
|
+
# When `object` is not the right type.
|
|
63
|
+
#
|
|
34
64
|
def self.truthy? object
|
|
35
65
|
case object
|
|
36
66
|
when nil
|
|
@@ -60,11 +90,20 @@ module NRSER
|
|
|
60
90
|
|
|
61
91
|
# Opposite of {NRSER.truthy?}.
|
|
62
92
|
#
|
|
93
|
+
# @pure Return value depends only on parameters.
|
|
94
|
+
#
|
|
63
95
|
# @param object (see .truthy?)
|
|
64
96
|
#
|
|
65
97
|
# @return [Boolean]
|
|
66
98
|
# The negation of {NRSER.truthy?}.
|
|
67
99
|
#
|
|
100
|
+
# @raise [ArgumentError]
|
|
101
|
+
# When a string is received that is not in {NRSER::TRUTHY_STRINGS} or
|
|
102
|
+
# {NRSER::FALSY_STRINGS} (case insensitive).
|
|
103
|
+
#
|
|
104
|
+
# @raise [TypeError]
|
|
105
|
+
# When `object` is not the right type.
|
|
106
|
+
#
|
|
68
107
|
def self.falsy? object
|
|
69
108
|
! truthy?(object)
|
|
70
109
|
end # .falsy?
|
|
File without changes
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
module NRSER
|
|
2
|
+
# @!group Path Functions
|
|
3
|
+
|
|
4
|
+
# @return [Pathname]
|
|
5
|
+
#
|
|
6
|
+
def self.pn_from path
|
|
7
|
+
if path.is_a? Pathname
|
|
8
|
+
path
|
|
9
|
+
else
|
|
10
|
+
Pathname.new path
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# @todo Document glob? method.
|
|
16
|
+
#
|
|
17
|
+
# @param [type] arg_name
|
|
18
|
+
# @todo Add name param description.
|
|
19
|
+
#
|
|
20
|
+
# @return [return_type]
|
|
21
|
+
# @todo Document return value.
|
|
22
|
+
#
|
|
23
|
+
def self.looks_globish? path
|
|
24
|
+
%w|* ? [ {|.any? &path.to_s.method( :include? )
|
|
25
|
+
end # .glob?
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Ascend the directory tree starting at `from` (defaults to working
|
|
29
|
+
# directory) looking for a relative path.
|
|
30
|
+
#
|
|
31
|
+
# How it works and what it returns is dependent on the sent options.
|
|
32
|
+
#
|
|
33
|
+
# In the simplest / default case:
|
|
34
|
+
#
|
|
35
|
+
# 1.
|
|
36
|
+
#
|
|
37
|
+
# @param [String | Pathname] rel_path
|
|
38
|
+
# Relative path to search for. Can contains glob patterns; see the `glob`
|
|
39
|
+
# keyword.
|
|
40
|
+
#
|
|
41
|
+
# @param [String | Pathname] from:
|
|
42
|
+
# Where to start the search. This is the first directory checked.
|
|
43
|
+
#
|
|
44
|
+
# @param [Boolean | :guess] glob:
|
|
45
|
+
# Controls file-glob behavior with respect to `rel_path`:
|
|
46
|
+
#
|
|
47
|
+
# - `:guess` (default) - boolean value is computed by passing `rel_path`
|
|
48
|
+
# to {.looks_globish?}.
|
|
49
|
+
#
|
|
50
|
+
# - `true` - {Pathname.glob} is used to search for `rel_path` in each
|
|
51
|
+
# directory, and the first glob result that passes the test is
|
|
52
|
+
# considered the match.
|
|
53
|
+
#
|
|
54
|
+
# - `false` - `rel_path` is used as a literal file path (if it has a `*`
|
|
55
|
+
# character it will only match paths with a literal `*` character,
|
|
56
|
+
# etc.)
|
|
57
|
+
#
|
|
58
|
+
# **Be mindful that glob searches can easily consume significant resources
|
|
59
|
+
# when using broad patterns and/or large file trees.**
|
|
60
|
+
#
|
|
61
|
+
# Basically, you probably don't *ever* want to use `**` - we walk all the
|
|
62
|
+
# way up to the file system root, so it would be equivalent to searching
|
|
63
|
+
# *the entire filesystem*.
|
|
64
|
+
#
|
|
65
|
+
# @todo
|
|
66
|
+
# There should be a way to cut the search off early or detect `**` in
|
|
67
|
+
# the `rel_path` and error out or something to prevent full FS search.
|
|
68
|
+
#
|
|
69
|
+
# @param [Symbol] test:
|
|
70
|
+
# The test to perform on pathnames to see if they match. Defaults to
|
|
71
|
+
# `:exist?` - which calls {Pathname#exist?} - but could be `:directory?`
|
|
72
|
+
# or anything else that makes sense.
|
|
73
|
+
#
|
|
74
|
+
# @param [Symbol] result:
|
|
75
|
+
# What information to return:
|
|
76
|
+
#
|
|
77
|
+
# - `:common_root` (default) - return the directory that the match was
|
|
78
|
+
# relative to, so the return value is `from` or a ancestor of it.
|
|
79
|
+
#
|
|
80
|
+
# - `:path` - return the full path that was matched.
|
|
81
|
+
#
|
|
82
|
+
# - `:pair` - return the `:common_root` value followed by the `:path`
|
|
83
|
+
# value in a two-element {Array}.
|
|
84
|
+
#
|
|
85
|
+
# @return [nil]
|
|
86
|
+
# When no match is found.
|
|
87
|
+
#
|
|
88
|
+
# @return [Pathname]
|
|
89
|
+
# When a match is found and `result` keyword is
|
|
90
|
+
#
|
|
91
|
+
# - `:common_root` - the directory in `from.ascend` the match was made
|
|
92
|
+
# from.
|
|
93
|
+
#
|
|
94
|
+
# - `:path` - the path to the matched file.
|
|
95
|
+
#
|
|
96
|
+
# @return [Array<(Pathname, Pathname)>]
|
|
97
|
+
# When a match is found and `result` keyword is `:pair`, the directory
|
|
98
|
+
# the match was relative to followed by the matched path.
|
|
99
|
+
#
|
|
100
|
+
def self.find_up(
|
|
101
|
+
rel_path,
|
|
102
|
+
from: Pathname.pwd,
|
|
103
|
+
glob: :guess,
|
|
104
|
+
test: :exist?,
|
|
105
|
+
result: :common_root
|
|
106
|
+
)
|
|
107
|
+
# If `glob` is `:guess`, override `glob` with the result of
|
|
108
|
+
# {.looks_globish?}
|
|
109
|
+
#
|
|
110
|
+
glob = looks_globish?( rel_path ) if glob == :guess
|
|
111
|
+
|
|
112
|
+
found = find_map( pn_from( from ).ascend ) { |dir|
|
|
113
|
+
path = dir / rel_path
|
|
114
|
+
|
|
115
|
+
found_path = if glob
|
|
116
|
+
Pathname.glob( path ).find { |match_path|
|
|
117
|
+
match_path.public_send test
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
path.public_send test
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
unless found_path.nil?
|
|
124
|
+
[dir, found_path]
|
|
125
|
+
end
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return nil if found.nil?
|
|
129
|
+
|
|
130
|
+
dir, path = found
|
|
131
|
+
|
|
132
|
+
Types.match result,
|
|
133
|
+
:common_root, dir,
|
|
134
|
+
:pair, found,
|
|
135
|
+
:path, path
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Exactly like {NRSER.find_up} but raises if nothing is found.
|
|
140
|
+
#
|
|
141
|
+
def self.find_up! *args
|
|
142
|
+
find_up( *args ).tap { |result|
|
|
143
|
+
if result.nil?
|
|
144
|
+
raise "HERE! #{ args.inspect }"
|
|
145
|
+
end
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
end # module NRSER
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
##
|
|
2
|
-
# Methods that make useful {Proc} instances.
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
# Requirements
|
|
6
|
-
# =======================================================================
|
|
7
|
-
|
|
8
|
-
# Stdlib
|
|
9
|
-
# -----------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
# Deps
|
|
12
|
-
# -----------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
# Project / Package
|
|
15
|
-
# -----------------------------------------------------------------------
|
|
16
|
-
require_relative './message'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# Definitions
|
|
20
|
-
# =======================================================================
|
|
21
|
-
|
|
22
1
|
module NRSER
|
|
23
2
|
|
|
24
3
|
# Creates a new {NRSER::Message} from the array.
|
|
@@ -95,7 +74,7 @@ module NRSER
|
|
|
95
74
|
#
|
|
96
75
|
# @note
|
|
97
76
|
# `mappable`` entries are mapped into messages when {#to_chain} is called,
|
|
98
|
-
# meaning subsequent changes to `mappable` **will not** affect the
|
|
77
|
+
# meaning subsequent changes to `mappable` **will not** affect the
|
|
99
78
|
# returned proc.
|
|
100
79
|
#
|
|
101
80
|
# @example Equivalent of `Time.now.to_i`
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative './string/looks_like'
|
|
4
|
+
|
|
5
|
+
module NRSER
|
|
6
|
+
|
|
7
|
+
# @!group String Functions
|
|
8
|
+
|
|
9
|
+
WHITESPACE_RE = /\A[[:space:]]*\z/
|
|
10
|
+
|
|
11
|
+
UNICODE_ELLIPSIS = '…'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def self.whitespace? string
|
|
15
|
+
string =~ WHITESPACE_RE
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# turn a multi-line string into a single line, collapsing whitespace
|
|
20
|
+
# to a single space.
|
|
21
|
+
#
|
|
22
|
+
# same as ActiveSupport's String.squish, adapted from there.
|
|
23
|
+
def self.squish str
|
|
24
|
+
str.gsub(/[[:space:]]+/, ' ').strip
|
|
25
|
+
end # squish
|
|
26
|
+
|
|
27
|
+
singleton_class.send :alias_method, :unblock, :squish
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def self.common_prefix strings
|
|
31
|
+
raise ArgumentError.new("argument can't be empty") if strings.empty?
|
|
32
|
+
|
|
33
|
+
sorted = strings.sort
|
|
34
|
+
|
|
35
|
+
i = 0
|
|
36
|
+
|
|
37
|
+
while sorted.first[i] == sorted.last[i] &&
|
|
38
|
+
i < [sorted.first.length, sorted.last.length].min
|
|
39
|
+
i = i + 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sorted.first[0...i]
|
|
43
|
+
end # .common_prefix
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def self.filter_repeated_blank_lines str, remove_leading: false
|
|
47
|
+
out = []
|
|
48
|
+
lines = str.lines
|
|
49
|
+
skipping = remove_leading
|
|
50
|
+
str.lines.each do |line|
|
|
51
|
+
if line =~ /^\s*$/
|
|
52
|
+
unless skipping
|
|
53
|
+
out << line
|
|
54
|
+
end
|
|
55
|
+
skipping = true
|
|
56
|
+
else
|
|
57
|
+
skipping = false
|
|
58
|
+
out << line
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
out.join
|
|
62
|
+
end # .filter_repeated_blank_lines
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def self.lazy_filter_repeated_blank_lines source, remove_leading: false
|
|
66
|
+
skipping = remove_leading
|
|
67
|
+
|
|
68
|
+
source = source.each_line if source.is_a? String
|
|
69
|
+
|
|
70
|
+
Enumerator::Lazy.new source do |yielder, line|
|
|
71
|
+
if line =~ /^\s*$/
|
|
72
|
+
unless skipping
|
|
73
|
+
yielder << line
|
|
74
|
+
end
|
|
75
|
+
skipping = true
|
|
76
|
+
else
|
|
77
|
+
skipping = false
|
|
78
|
+
yielder << line
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end # .lazy_filter_repeated_blank_lines
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
|
|
86
|
+
#
|
|
87
|
+
# 'Once upon a time in a world far far away'.truncate(27)
|
|
88
|
+
# # => "Once upon a time in a wo..."
|
|
89
|
+
#
|
|
90
|
+
# Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
|
|
91
|
+
#
|
|
92
|
+
# 'Once upon a time in a world far far away'.truncate(27, separator: ' ')
|
|
93
|
+
# # => "Once upon a time in a..."
|
|
94
|
+
#
|
|
95
|
+
# 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
|
|
96
|
+
# # => "Once upon a time in a..."
|
|
97
|
+
#
|
|
98
|
+
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
|
|
99
|
+
# for a total length not exceeding <tt>length</tt>:
|
|
100
|
+
#
|
|
101
|
+
# 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
|
|
102
|
+
# # => "And they f... (continued)"
|
|
103
|
+
#
|
|
104
|
+
# adapted from
|
|
105
|
+
#
|
|
106
|
+
# <https://github.com/rails/rails/blob/7847a19f476fb9bee287681586d872ea43785e53/activesupport/lib/active_support/core_ext/string/filters.rb#L46>
|
|
107
|
+
#
|
|
108
|
+
def self.truncate(str, truncate_at, options = {})
|
|
109
|
+
return str.dup unless str.length > truncate_at
|
|
110
|
+
|
|
111
|
+
omission = options[:omission] || '...'
|
|
112
|
+
length_with_room_for_omission = truncate_at - omission.length
|
|
113
|
+
stop = \
|
|
114
|
+
if options[:separator]
|
|
115
|
+
str.rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
|
|
116
|
+
else
|
|
117
|
+
length_with_room_for_omission
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
"#{str[0, stop]}#{omission}"
|
|
121
|
+
end # .truncate
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Cut the middle out of a string and stick an ellipsis in there instead.
|
|
125
|
+
#
|
|
126
|
+
# @param [String] string
|
|
127
|
+
# Source string.
|
|
128
|
+
#
|
|
129
|
+
# @param [Fixnum] max
|
|
130
|
+
# Max length to allow for the output string.
|
|
131
|
+
#
|
|
132
|
+
# @param [String] omission:
|
|
133
|
+
# The string to stick in the middle where original contents were
|
|
134
|
+
# removed. Defaults to the unicode ellipsis since I'm targeting the CLI
|
|
135
|
+
# at the moment and it saves precious characters.
|
|
136
|
+
#
|
|
137
|
+
# @return [String]
|
|
138
|
+
# String of at most `max` length with the middle chopped out if needed
|
|
139
|
+
# to do so.
|
|
140
|
+
def self.ellipsis string, max, omission: UNICODE_ELLIPSIS
|
|
141
|
+
return string unless string.length > max
|
|
142
|
+
|
|
143
|
+
trim_to = max - omission.length
|
|
144
|
+
|
|
145
|
+
start = string[0, (trim_to / 2) + (trim_to % 2)]
|
|
146
|
+
finish = string[-( (trim_to / 2) - (trim_to % 2) )..-1]
|
|
147
|
+
|
|
148
|
+
start + omission + finish
|
|
149
|
+
end # .ellipsis
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Try to do "smart" job adding ellipsis to the middle of strings by
|
|
153
|
+
# splitting them by a separator `split` - that defaults to `, ` - then
|
|
154
|
+
# building the result up by bouncing back and forth between tokens at the
|
|
155
|
+
# beginning and end of the string until we reach the `max` length limit.
|
|
156
|
+
#
|
|
157
|
+
# Intended to be used with possibly long single-line strings like
|
|
158
|
+
# `#inspect` returns for complex objects, where tokens are commonly
|
|
159
|
+
# separated by `, `, and producing a reasonably nice result that will fit
|
|
160
|
+
# in a reasonable amount of space, like `rspec` output (which was the
|
|
161
|
+
# motivation).
|
|
162
|
+
#
|
|
163
|
+
# If `string` is already less than `max` then it is just returned.
|
|
164
|
+
#
|
|
165
|
+
# If `string` doesn't contain `split` or just the first and last tokens
|
|
166
|
+
# alone would push the result over `max` then falls back to
|
|
167
|
+
# {NRSER.ellipsis}.
|
|
168
|
+
#
|
|
169
|
+
# If `max` is too small it's going to fall back nearly always... around
|
|
170
|
+
# `64` has seemed like a decent place to start from screwing around on
|
|
171
|
+
# the REPL a bit.
|
|
172
|
+
#
|
|
173
|
+
# @pure
|
|
174
|
+
# Return value depends only on parameters.
|
|
175
|
+
#
|
|
176
|
+
# @status
|
|
177
|
+
# Experimental
|
|
178
|
+
#
|
|
179
|
+
# @param [String] string
|
|
180
|
+
# Source string.
|
|
181
|
+
#
|
|
182
|
+
# @param [Fixnum] max
|
|
183
|
+
# Max length to allow for the output string. Result will usually be
|
|
184
|
+
# *less* than this unless the fallback to {NRSER.ellipsis} kicks in.
|
|
185
|
+
#
|
|
186
|
+
# @param [String] omission:
|
|
187
|
+
# The string to stick in the middle where original contents were
|
|
188
|
+
# removed. Defaults to the unicode ellipsis since I'm targeting the CLI
|
|
189
|
+
# at the moment and it saves precious characters.
|
|
190
|
+
#
|
|
191
|
+
# @param [String] split:
|
|
192
|
+
# The string to tokenize the `string` parameter by. If you pass a
|
|
193
|
+
# {Regexp} here it might work, it might loop out, maybe.
|
|
194
|
+
#
|
|
195
|
+
# @return [String]
|
|
196
|
+
# String of at most `max` length with the middle chopped out if needed
|
|
197
|
+
# to do so.
|
|
198
|
+
#
|
|
199
|
+
def self.smart_ellipsis string, max, omission: UNICODE_ELLIPSIS, split: ', '
|
|
200
|
+
return string unless string.length > max
|
|
201
|
+
|
|
202
|
+
unless string.include? split
|
|
203
|
+
return ellipsis string, max, omission: omission
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
tokens = string.split split
|
|
207
|
+
|
|
208
|
+
char_budget = max - omission.length
|
|
209
|
+
start = tokens[0] + split
|
|
210
|
+
finish = tokens[tokens.length - 1]
|
|
211
|
+
|
|
212
|
+
if start.length + finish.length > char_budget
|
|
213
|
+
return ellipsis string, max, omission: omission
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
next_start_index = 1
|
|
217
|
+
next_finish_index = tokens.length - 2
|
|
218
|
+
next_index_is = :start
|
|
219
|
+
next_index = next_start_index
|
|
220
|
+
|
|
221
|
+
while (
|
|
222
|
+
start.length +
|
|
223
|
+
finish.length +
|
|
224
|
+
tokens[next_index].length +
|
|
225
|
+
split.length
|
|
226
|
+
) <= char_budget do
|
|
227
|
+
if next_index_is == :start
|
|
228
|
+
start += tokens[next_index] + split
|
|
229
|
+
next_start_index += 1
|
|
230
|
+
next_index = next_finish_index
|
|
231
|
+
next_index_is = :finish
|
|
232
|
+
else # == :finish
|
|
233
|
+
finish = tokens[next_index] + split + finish
|
|
234
|
+
next_finish_index -= 1
|
|
235
|
+
next_index = next_start_index
|
|
236
|
+
next_index_is = :start
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
start + omission + finish
|
|
241
|
+
|
|
242
|
+
end # .smart_ellipsis
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# Get the constant identified by a string.
|
|
246
|
+
#
|
|
247
|
+
# @pure Return value depends only on parameters.
|
|
248
|
+
#
|
|
249
|
+
# @example
|
|
250
|
+
#
|
|
251
|
+
# SomeClass == NRSER.constantize(SomeClass.name)
|
|
252
|
+
#
|
|
253
|
+
# Lifted from ActiveSupport.
|
|
254
|
+
#
|
|
255
|
+
# @param [String] camel_cased_word
|
|
256
|
+
# The constant's camel-cased, double-colon-separated "name",
|
|
257
|
+
# like "NRSER::Types::Array".
|
|
258
|
+
#
|
|
259
|
+
# @return [Object]
|
|
260
|
+
#
|
|
261
|
+
# @raise [NameError]
|
|
262
|
+
# When the name is not in CamelCase or is not initialized.
|
|
263
|
+
#
|
|
264
|
+
def self.constantize(camel_cased_word)
|
|
265
|
+
names = camel_cased_word.split('::')
|
|
266
|
+
|
|
267
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
|
268
|
+
Object.const_get(camel_cased_word) if names.empty?
|
|
269
|
+
|
|
270
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
|
271
|
+
names.shift if names.size > 1 && names.first.empty?
|
|
272
|
+
|
|
273
|
+
names.inject(Object) do |constant, name|
|
|
274
|
+
if constant == Object
|
|
275
|
+
constant.const_get(name)
|
|
276
|
+
else
|
|
277
|
+
candidate = constant.const_get(name)
|
|
278
|
+
next candidate if constant.const_defined?(name, false)
|
|
279
|
+
next candidate unless Object.const_defined?(name)
|
|
280
|
+
|
|
281
|
+
# Go down the ancestors to check if it is owned directly. The check
|
|
282
|
+
# stops when we reach Object or the end of ancestors tree.
|
|
283
|
+
constant = constant.ancestors.inject do |const, ancestor|
|
|
284
|
+
break const if ancestor == Object
|
|
285
|
+
break ancestor if ancestor.const_defined?(name, false)
|
|
286
|
+
const
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# owner is in Object, so raise
|
|
290
|
+
constant.const_get(name, false)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end # .constantize
|
|
294
|
+
|
|
295
|
+
singleton_class.send :alias_method, :to_const, :constantize
|
|
296
|
+
|
|
297
|
+
end # module NRSER
|