nrser 0.0.30 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nrser.rb +56 -12
  3. data/lib/nrser/collection.rb +4 -7
  4. data/lib/nrser/ext.rb +5 -0
  5. data/lib/nrser/{refinements → ext}/enumerable.rb +11 -9
  6. data/lib/nrser/ext/pathname.rb +74 -0
  7. data/lib/nrser/{refinements → ext}/tree.rb +2 -26
  8. data/lib/nrser/functions.rb +18 -0
  9. data/lib/nrser/{array.rb → functions/array.rb} +2 -3
  10. data/lib/nrser/{binding.rb → functions/binding.rb} +0 -2
  11. data/lib/nrser/functions/enumerable.rb +355 -0
  12. data/lib/nrser/functions/enumerable/find_all_map.rb +33 -0
  13. data/lib/nrser/functions/enumerable/find_map.rb +53 -0
  14. data/lib/nrser/functions/exception.rb +17 -0
  15. data/lib/nrser/{hash.rb → functions/hash.rb} +0 -0
  16. data/lib/nrser/functions/hash/bury.rb +147 -0
  17. data/lib/nrser/{hash → functions/hash}/deep_merge.rb +5 -5
  18. data/lib/nrser/{hash → functions/hash}/except_keys.rb +2 -0
  19. data/lib/nrser/{hash → functions/hash}/guess_label_key_type.rb +3 -1
  20. data/lib/nrser/{hash → functions/hash}/slice_keys.rb +3 -1
  21. data/lib/nrser/{hash → functions/hash}/stringify_keys.rb +2 -0
  22. data/lib/nrser/{hash → functions/hash}/symbolize_keys.rb +3 -1
  23. data/lib/nrser/{hash → functions/hash}/transform_keys.rb +3 -1
  24. data/lib/nrser/functions/merge_by.rb +29 -0
  25. data/lib/nrser/{object.rb → functions/object.rb} +0 -0
  26. data/lib/nrser/{object → functions/object}/as_array.rb +2 -0
  27. data/lib/nrser/{object → functions/object}/as_hash.rb +7 -5
  28. data/lib/nrser/{object → functions/object}/truthy.rb +46 -7
  29. data/lib/nrser/{open_struct.rb → functions/open_struct.rb} +0 -0
  30. data/lib/nrser/functions/path.rb +150 -0
  31. data/lib/nrser/{proc.rb → functions/proc.rb} +1 -22
  32. data/lib/nrser/functions/string.rb +297 -0
  33. data/lib/nrser/functions/string/looks_like.rb +44 -0
  34. data/lib/nrser/{text.rb → functions/text.rb} +0 -0
  35. data/lib/nrser/{text → functions/text}/indentation.rb +2 -16
  36. data/lib/nrser/{text → functions/text}/lines.rb +1 -2
  37. data/lib/nrser/{text → functions/text}/word_wrap.rb +2 -4
  38. data/lib/nrser/{tree.rb → functions/tree.rb} +0 -0
  39. data/lib/nrser/{tree → functions/tree}/each_branch.rb +6 -7
  40. data/lib/nrser/functions/tree/leaves.rb +92 -0
  41. data/lib/nrser/{tree → functions/tree}/map_branches.rb +31 -32
  42. data/lib/nrser/functions/tree/map_leaves.rb +56 -0
  43. data/lib/nrser/{tree → functions/tree}/map_tree.rb +9 -20
  44. data/lib/nrser/{tree → functions/tree}/transform.rb +0 -10
  45. data/lib/nrser/logger.rb +9 -10
  46. data/lib/nrser/message.rb +3 -7
  47. data/lib/nrser/meta.rb +2 -0
  48. data/lib/nrser/meta/class_attrs.rb +3 -9
  49. data/lib/nrser/meta/props.rb +19 -19
  50. data/lib/nrser/meta/props/base.rb +4 -10
  51. data/lib/nrser/meta/props/prop.rb +12 -28
  52. data/lib/nrser/no_arg.rb +1 -3
  53. data/lib/nrser/refinements.rb +5 -0
  54. data/lib/nrser/refinements/array.rb +5 -17
  55. data/lib/nrser/refinements/enumerator.rb +1 -3
  56. data/lib/nrser/refinements/hash.rb +3 -15
  57. data/lib/nrser/refinements/object.rb +2 -2
  58. data/lib/nrser/refinements/open_struct.rb +0 -2
  59. data/lib/nrser/refinements/pathname.rb +3 -46
  60. data/lib/nrser/refinements/set.rb +2 -6
  61. data/lib/nrser/refinements/string.rb +2 -2
  62. data/lib/nrser/rspex.rb +16 -13
  63. data/lib/nrser/types.rb +6 -20
  64. data/lib/nrser/types/any.rb +0 -1
  65. data/lib/nrser/types/booleans.rb +1 -1
  66. data/lib/nrser/types/combinators.rb +5 -5
  67. data/lib/nrser/types/in.rb +0 -21
  68. data/lib/nrser/types/responds.rb +1 -0
  69. data/lib/nrser/types/trees.rb +1 -0
  70. data/lib/nrser/version.rb +2 -3
  71. data/spec/nrser/{template_spec.rb → functions/binding/template_spec.rb} +0 -0
  72. data/spec/nrser/functions/enumerable/find_all_map_spec.rb +28 -0
  73. data/spec/nrser/functions/enumerable/find_bounded_spec.rb +70 -0
  74. data/spec/nrser/functions/enumerable/find_map_spec.rb +38 -0
  75. data/spec/nrser/functions/enumerable/find_only_spec.rb +25 -0
  76. data/spec/nrser/functions/enumerable/to_h_by_spec.rb +28 -0
  77. data/spec/nrser/{format_exception_spec.rb → functions/exception/format_exception_spec.rb} +0 -0
  78. data/spec/nrser/{hash → functions/hash}/bury_spec.rb +0 -0
  79. data/spec/nrser/{hash → functions/hash}/guess_label_key_type_spec.rb +0 -0
  80. data/spec/nrser/{hash_spec.rb → functions/hash_spec.rb} +0 -7
  81. data/spec/nrser/{merge_by_spec.rb → functions/merge_by_spec.rb} +0 -0
  82. data/spec/nrser/{truthy_spec.rb → functions/object/truthy_spec.rb} +0 -0
  83. data/spec/nrser/{open_struct_spec.rb → functions/open_struct_spec.rb} +0 -0
  84. data/spec/nrser/{string → functions/string}/common_prefix_spec.rb +0 -0
  85. data/spec/nrser/{string → functions/string}/looks_like_spec.rb +0 -0
  86. data/spec/nrser/{truncate_spec.rb → functions/string/truncate_spec.rb} +0 -0
  87. data/spec/nrser/{text → functions/text}/dedent/gotchas_spec.rb +0 -0
  88. data/spec/nrser/{text → functions/text}/dedent_spec.rb +0 -0
  89. data/spec/nrser/{indent_spec.rb → functions/text/indent_spec.rb} +0 -0
  90. data/spec/nrser/{tree → functions/tree}/each_branch_spec.rb +0 -0
  91. data/spec/nrser/{tree → functions/tree}/leaves_spec.rb +0 -0
  92. data/spec/nrser/{tree → functions/tree}/map_branch_spec.rb +0 -0
  93. data/spec/nrser/{tree → functions/tree}/map_tree_spec.rb +0 -0
  94. data/spec/nrser/{tree → functions/tree}/transform_spec.rb +0 -0
  95. data/spec/nrser/{tree → functions/tree}/transformer_spec.rb +0 -0
  96. data/spec/nrser/meta/class_attrs_spec.rb +12 -14
  97. data/spec/spec_helper.rb +2 -3
  98. metadata +136 -110
  99. data/lib/nrser/enumerable.rb +0 -288
  100. data/lib/nrser/exception.rb +0 -7
  101. data/lib/nrser/hash/bury.rb +0 -154
  102. data/lib/nrser/merge_by.rb +0 -26
  103. data/lib/nrser/string.rb +0 -294
  104. data/lib/nrser/string/looks_like.rb +0 -51
  105. data/lib/nrser/tree/leaves.rb +0 -92
  106. data/lib/nrser/tree/map_leaves.rb +0 -63
  107. data/spec/nrser/enumerable_spec.rb +0 -111
@@ -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