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
@@ -1,7 +1,19 @@
1
- require 'set'
1
+ # frozen_string_literal: true
2
2
 
3
- module NRSER
4
- # Down-cased versions of strings that are considered to communicate truth.
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
- # @param [Nil, String] object
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?
@@ -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