nrser 0.0.26 → 0.0.27
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 +1 -0
- data/lib/nrser/array.rb +15 -0
- data/lib/nrser/binding.rb +7 -1
- data/lib/nrser/enumerable.rb +21 -1
- data/lib/nrser/errors.rb +56 -6
- data/lib/nrser/hash/deep_merge.rb +1 -1
- data/lib/nrser/message.rb +33 -0
- data/lib/nrser/meta/props.rb +77 -15
- data/lib/nrser/meta/props/prop.rb +276 -44
- data/lib/nrser/proc.rb +7 -3
- data/lib/nrser/refinements/array.rb +5 -0
- data/lib/nrser/refinements/enumerable.rb +5 -0
- data/lib/nrser/refinements/hash.rb +8 -0
- data/lib/nrser/refinements/object.rb +11 -1
- data/lib/nrser/refinements/string.rb +17 -3
- data/lib/nrser/refinements/symbol.rb +8 -0
- data/lib/nrser/refinements/tree.rb +22 -0
- data/lib/nrser/rspex.rb +312 -70
- data/lib/nrser/rspex/shared_examples.rb +116 -0
- data/lib/nrser/string.rb +159 -27
- data/lib/nrser/temp/unicode_math.rb +48 -0
- data/lib/nrser/text.rb +3 -0
- data/lib/nrser/text/indentation.rb +210 -0
- data/lib/nrser/text/lines.rb +52 -0
- data/lib/nrser/text/word_wrap.rb +29 -0
- data/lib/nrser/tree.rb +4 -78
- data/lib/nrser/tree/each_branch.rb +76 -0
- data/lib/nrser/tree/map_branches.rb +91 -0
- data/lib/nrser/tree/map_tree.rb +97 -0
- data/lib/nrser/tree/transform.rb +56 -13
- data/lib/nrser/types.rb +1 -0
- data/lib/nrser/types/array.rb +15 -3
- data/lib/nrser/types/is_a.rb +40 -1
- data/lib/nrser/types/nil.rb +17 -0
- data/lib/nrser/types/paths.rb +17 -2
- data/lib/nrser/types/strings.rb +57 -22
- data/lib/nrser/types/tuples.rb +5 -0
- data/lib/nrser/types/type.rb +47 -6
- data/lib/nrser/version.rb +1 -1
- data/spec/nrser/errors/abstract_method_error_spec.rb +46 -0
- data/spec/nrser/meta/props/to_and_from_data_spec.rb +74 -0
- data/spec/nrser/meta/props_spec.rb +6 -2
- data/spec/nrser/refinements/erb_spec.rb +100 -1
- data/spec/nrser/{common_prefix_spec.rb → string/common_prefix_spec.rb} +9 -0
- data/spec/nrser/text/dedent_spec.rb +80 -0
- data/spec/nrser/tree/map_branch_spec.rb +83 -0
- data/spec/nrser/tree/map_tree_spec.rb +123 -0
- data/spec/nrser/tree/transform_spec.rb +26 -29
- data/spec/nrser/tree/transformer_spec.rb +179 -0
- data/spec/nrser/types/paths_spec.rb +73 -45
- data/spec/spec_helper.rb +10 -0
- metadata +27 -7
- data/spec/nrser/dedent_spec.rb +0 -36
@@ -0,0 +1,116 @@
|
|
1
|
+
# Declarations
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
module NRSER; end
|
5
|
+
module NRSER::RSpex; end
|
6
|
+
|
7
|
+
|
8
|
+
# Definitions
|
9
|
+
# =======================================================================
|
10
|
+
|
11
|
+
# Just a namespace module where I stuck shared examples, with lil' utils to
|
12
|
+
# alias them to multiple string and symbol names if you like.
|
13
|
+
#
|
14
|
+
module NRSER::RSpex::SharedExamples
|
15
|
+
|
16
|
+
# Module (Class/self) Methods (Helpers)
|
17
|
+
# =====================================================================
|
18
|
+
|
19
|
+
# Shitty but simple conversion of natural string names to more symbol-y
|
20
|
+
# ones.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# name_to_sym 'expect subject'
|
24
|
+
# # => :expect_subject
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# name_to_sym 'function'
|
28
|
+
# # => :function
|
29
|
+
#
|
30
|
+
# Doesn't do anything fancy-pants under the hood.
|
31
|
+
#
|
32
|
+
# @param [String | Symbol] name
|
33
|
+
#
|
34
|
+
# @return [Symbol]
|
35
|
+
#
|
36
|
+
def self.name_to_sym name
|
37
|
+
name.
|
38
|
+
to_s.
|
39
|
+
gsub( /\s+/, '_' ).
|
40
|
+
gsub( /[^a-zA-Z0-9_]/, '' ).
|
41
|
+
downcase.
|
42
|
+
to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Bind a proc as an RSpec shared example to string and symbol names
|
47
|
+
def self.bind_names proc, name, prefix: nil
|
48
|
+
names = [name.to_s, name_to_sym( name )]
|
49
|
+
|
50
|
+
unless prefix.nil?
|
51
|
+
names << "#{ prefix } #{ name}"
|
52
|
+
names << name_to_sym( "#{ prefix }_#{ name}" )
|
53
|
+
end
|
54
|
+
|
55
|
+
names.each do |name|
|
56
|
+
shared_examples name, &proc
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Shared Example Blocks and Binding
|
62
|
+
# =====================================================================
|
63
|
+
|
64
|
+
EXPECT_SUBJECT = ->( *expectations ) do
|
65
|
+
merge_expectations( *expectations ).each { |state, specs|
|
66
|
+
specs.each { |verb, noun|
|
67
|
+
it {
|
68
|
+
# like: is_expected.to(include(noun))
|
69
|
+
is_expected.send state, self.send(verb, noun)
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
end # is expected
|
74
|
+
|
75
|
+
bind_names EXPECT_SUBJECT, "expect subject"
|
76
|
+
|
77
|
+
|
78
|
+
# Shared example for a functional method that compares input and output pairs.
|
79
|
+
#
|
80
|
+
FUNCTION = ->( mapping: {}, raising: {} ) do
|
81
|
+
mapping.each { |args, expected|
|
82
|
+
# args = NRSER.as_array args
|
83
|
+
|
84
|
+
# context "called with #{ args.map( &NRSER::RSpex.method( :short_s ) ).join ', ' }" do
|
85
|
+
# subject { super().call *args }
|
86
|
+
describe_called_with *args do
|
87
|
+
|
88
|
+
it {
|
89
|
+
expected = unwrap expected, context: self
|
90
|
+
|
91
|
+
matcher = if expected.respond_to?( :matches? )
|
92
|
+
expected
|
93
|
+
elsif expected.is_a? NRSER::Message
|
94
|
+
expected.send_to self
|
95
|
+
else
|
96
|
+
eq expected
|
97
|
+
end
|
98
|
+
|
99
|
+
is_expected.to matcher
|
100
|
+
}
|
101
|
+
end
|
102
|
+
}
|
103
|
+
|
104
|
+
raising.each { |args, error|
|
105
|
+
args = NRSER.as_array args
|
106
|
+
|
107
|
+
context "called with #{ args.map( &NRSER::RSpex.method( :short_s ) ).join ', ' }" do
|
108
|
+
# it "rejects #{ args.map( &:inspect ).join ', ' }" do
|
109
|
+
it { expect { subject.call *args }.to raise_error( *error ) }
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
bind_names FUNCTION, 'function', prefix: 'a'
|
115
|
+
|
116
|
+
end # module NRSER::RSpex::SharedExamples
|
data/lib/nrser/string.rb
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
require_relative './string/looks_like'
|
2
2
|
|
3
3
|
module NRSER
|
4
|
+
# @!group String Functions
|
5
|
+
|
6
|
+
WHITESPACE_RE = /\A[[:space:]]*\z/
|
7
|
+
|
8
|
+
UNICODE_ELLIPSIS = '…'
|
9
|
+
|
10
|
+
|
11
|
+
def self.whitespace? string
|
12
|
+
string =~ WHITESPACE_RE
|
13
|
+
end
|
14
|
+
|
15
|
+
|
4
16
|
class << self
|
17
|
+
|
5
18
|
# Functions the operate on strings.
|
6
19
|
|
7
20
|
# turn a multi-line string into a single line, collapsing whitespace
|
@@ -17,34 +30,24 @@ module NRSER
|
|
17
30
|
|
18
31
|
def common_prefix strings
|
19
32
|
raise ArgumentError.new("argument can't be empty") if strings.empty?
|
20
|
-
|
33
|
+
|
34
|
+
sorted = strings.sort
|
35
|
+
|
21
36
|
i = 0
|
37
|
+
|
22
38
|
while sorted.first[i] == sorted.last[i] &&
|
23
39
|
i < [sorted.first.length, sorted.last.length].min
|
24
40
|
i = i + 1
|
25
41
|
end
|
26
|
-
|
42
|
+
|
43
|
+
sorted.first[0...i]
|
27
44
|
end # common_prefix
|
28
45
|
|
29
46
|
|
30
|
-
def
|
31
|
-
return str if str.empty?
|
32
|
-
lines = str.lines
|
33
|
-
indent = common_prefix(lines).match(/^\s*/)[0]
|
34
|
-
return str if indent.empty?
|
35
|
-
lines.map {|line|
|
36
|
-
line = line[indent.length..line.length] if line.start_with? indent
|
37
|
-
}.join
|
38
|
-
end # dedent
|
39
|
-
|
40
|
-
# I like dedent better, but other libs seems to call it deindent
|
41
|
-
alias_method :deindent, :dedent
|
42
|
-
|
43
|
-
|
44
|
-
def filter_repeated_blank_lines str
|
47
|
+
def filter_repeated_blank_lines str, remove_leading: false
|
45
48
|
out = []
|
46
49
|
lines = str.lines
|
47
|
-
skipping =
|
50
|
+
skipping = remove_leading
|
48
51
|
str.lines.each do |line|
|
49
52
|
if line =~ /^\s*$/
|
50
53
|
unless skipping
|
@@ -60,16 +63,26 @@ module NRSER
|
|
60
63
|
end # filter_repeated_blank_lines
|
61
64
|
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
66
|
+
def lazy_filter_repeated_blank_lines source, remove_leading: false
|
67
|
+
skipping = remove_leading
|
68
|
+
|
69
|
+
source = source.each_line if source.is_a? String
|
70
|
+
|
71
|
+
Enumerator::Lazy.new source do |yielder, line|
|
72
|
+
if line =~ /^\s*$/
|
73
|
+
unless skipping
|
74
|
+
yielder << line
|
75
|
+
end
|
76
|
+
skipping = true
|
77
|
+
else
|
78
|
+
skipping = false
|
79
|
+
yielder << line
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end # filter_repeated_blank_lines
|
72
84
|
|
85
|
+
|
73
86
|
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
|
74
87
|
#
|
75
88
|
# 'Once upon a time in a world far far away'.truncate(27)
|
@@ -108,6 +121,125 @@ module NRSER
|
|
108
121
|
"#{str[0, stop]}#{omission}"
|
109
122
|
end
|
110
123
|
|
124
|
+
|
125
|
+
# Cut the middle out of a string and stick an ellipsis in there instead.
|
126
|
+
#
|
127
|
+
# @param [String] string
|
128
|
+
# Source string.
|
129
|
+
#
|
130
|
+
# @param [Fixnum] max
|
131
|
+
# Max length to allow for the output string.
|
132
|
+
#
|
133
|
+
# @param [String] omission:
|
134
|
+
# The string to stick in the middle where original contents were
|
135
|
+
# removed. Defaults to the unicode ellipsis since I'm targeting the CLI
|
136
|
+
# at the moment and it saves precious characters.
|
137
|
+
#
|
138
|
+
# @return [String]
|
139
|
+
# String of at most `max` length with the middle chopped out if needed
|
140
|
+
# to do so.
|
141
|
+
def ellipsis string, max, omission: UNICODE_ELLIPSIS
|
142
|
+
return string unless string.length > max
|
143
|
+
|
144
|
+
trim_to = max - omission.length
|
145
|
+
|
146
|
+
start = string[0, (trim_to / 2) + (trim_to % 2)]
|
147
|
+
finish = string[-( (trim_to / 2) - (trim_to % 2) )..-1]
|
148
|
+
|
149
|
+
start + omission + finish
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# **EXPERIMENTAL!**
|
154
|
+
#
|
155
|
+
# Try to do "smart" job adding ellipsis to the middle of strings by
|
156
|
+
# splitting them by a separator `split` - that defaults to `, ` - then
|
157
|
+
# building the result up by bouncing back and forth between tokens at the
|
158
|
+
# beginning and end of the string until we reach the `max` length limit.
|
159
|
+
#
|
160
|
+
# Intended to be used with possibly long single-line strings like
|
161
|
+
# `#inspect` returns for complex objects, where tokens are commonly
|
162
|
+
# separated by `, `, and producing a reasonably nice result that will fit
|
163
|
+
# in a reasonable amount of space, like `rspec` output (which was the
|
164
|
+
# motivation).
|
165
|
+
#
|
166
|
+
# If `string` is already less than `max` then it is just returned.
|
167
|
+
#
|
168
|
+
# If `string` doesn't contain `split` or just the first and last tokens
|
169
|
+
# alone would push the result over `max` then falls back to
|
170
|
+
# {NRSER.ellipsis}.
|
171
|
+
#
|
172
|
+
# If `max` is too small it's going to fall back nearly always... around
|
173
|
+
# `64` has seemed like a decent place to start from screwing around on
|
174
|
+
# the REPL a bit.
|
175
|
+
#
|
176
|
+
# @param [String] string
|
177
|
+
# Source string.
|
178
|
+
#
|
179
|
+
# @param [Fixnum] max
|
180
|
+
# Max length to allow for the output string. Result will usually be
|
181
|
+
# *less* than this unless the fallback to {NRSER.ellipsis} kicks in.
|
182
|
+
#
|
183
|
+
# @param [String] omission:
|
184
|
+
# The string to stick in the middle where original contents were
|
185
|
+
# removed. Defaults to the unicode ellipsis since I'm targeting the CLI
|
186
|
+
# at the moment and it saves precious characters.
|
187
|
+
#
|
188
|
+
# @param [String] split:
|
189
|
+
# The string to tokenize the `string` parameter by. If you pass a
|
190
|
+
# {Regexp} here it might work, it might loop out, maybe.
|
191
|
+
#
|
192
|
+
# @return [String]
|
193
|
+
# String of at most `max` length with the middle chopped out if needed
|
194
|
+
# to do so.
|
195
|
+
#
|
196
|
+
def smart_ellipsis string, max, omission: UNICODE_ELLIPSIS, split: ', '
|
197
|
+
return string unless string.length > max
|
198
|
+
|
199
|
+
unless string.include? split
|
200
|
+
return ellipsis string, max, omission: omission
|
201
|
+
end
|
202
|
+
|
203
|
+
tokens = string.split split
|
204
|
+
|
205
|
+
char_budget = max - omission.length
|
206
|
+
start = tokens[0] + split
|
207
|
+
finish = tokens[tokens.length - 1]
|
208
|
+
|
209
|
+
if start.length + finish.length > char_budget
|
210
|
+
return ellipsis string, max, omission: omission
|
211
|
+
end
|
212
|
+
|
213
|
+
next_start_index = 1
|
214
|
+
next_finish_index = tokens.length - 2
|
215
|
+
next_index_is = :start
|
216
|
+
next_index = next_start_index
|
217
|
+
|
218
|
+
while (
|
219
|
+
start.length +
|
220
|
+
finish.length +
|
221
|
+
tokens[next_index].length +
|
222
|
+
split.length
|
223
|
+
) <= char_budget do
|
224
|
+
if next_index_is == :start
|
225
|
+
start += tokens[next_index] + split
|
226
|
+
next_start_index += 1
|
227
|
+
next_index = next_finish_index
|
228
|
+
next_index_is = :finish
|
229
|
+
else # == :finish
|
230
|
+
finish = tokens[next_index] + split + finish
|
231
|
+
next_finish_index -= 1
|
232
|
+
next_index = next_start_index
|
233
|
+
next_index_is = :start
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
start + omission + finish
|
238
|
+
|
239
|
+
end # #method_name
|
240
|
+
|
241
|
+
|
242
|
+
|
111
243
|
# Get the constant identified by a string.
|
112
244
|
#
|
113
245
|
# @example
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module NRSER
|
2
|
+
module UnicodeMath
|
3
|
+
SET_STARTS = {
|
4
|
+
bold: {
|
5
|
+
upper: '1D400',
|
6
|
+
lower: '1D41A',
|
7
|
+
},
|
8
|
+
|
9
|
+
bold_script: {
|
10
|
+
upper: '1D4D0',
|
11
|
+
lower: '1D4EA',
|
12
|
+
},
|
13
|
+
}
|
14
|
+
|
15
|
+
class CharacterTranslator
|
16
|
+
def initialize name, upper_start, lower_start
|
17
|
+
@name = name
|
18
|
+
@upper_start = upper_start
|
19
|
+
@lower_start = lower_start
|
20
|
+
end
|
21
|
+
|
22
|
+
def translate_char char
|
23
|
+
upper_offset = char.ord - 'A'.ord
|
24
|
+
lower_offset = char.ord - 'a'.ord
|
25
|
+
|
26
|
+
if upper_offset >= 0 && upper_offset < 26
|
27
|
+
[ @upper_start.hex + upper_offset ].pack "U"
|
28
|
+
elsif lower_offset >= 0 && lower_offset < 26
|
29
|
+
[ @lower_start.hex + lower_offset ].pack "U"
|
30
|
+
else
|
31
|
+
char
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def translate string
|
36
|
+
string.each_char.map( &method( :translate_char ) ).join
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :[], :translate
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.[] name
|
43
|
+
name = name.to_sym
|
44
|
+
starts = SET_STARTS.fetch name
|
45
|
+
CharacterTranslator.new name, starts[:upper], starts[:lower]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/nrser/text.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# Requirements
|
2
|
+
# =======================================================================
|
3
|
+
|
4
|
+
# Stdlib
|
5
|
+
# -----------------------------------------------------------------------
|
6
|
+
|
7
|
+
# Deps
|
8
|
+
# -----------------------------------------------------------------------
|
9
|
+
|
10
|
+
# Project / Package
|
11
|
+
# -----------------------------------------------------------------------
|
12
|
+
require_relative './lines'
|
13
|
+
|
14
|
+
|
15
|
+
module NRSER
|
16
|
+
# @!group Text
|
17
|
+
|
18
|
+
|
19
|
+
# Constants
|
20
|
+
# =====================================================================
|
21
|
+
|
22
|
+
INDENT_RE = /\A[\ \t]*/
|
23
|
+
|
24
|
+
INDENT_TAG_MARKER = "\x1E"
|
25
|
+
INDENT_TAG_SEPARATOR = "\x1F"
|
26
|
+
|
27
|
+
|
28
|
+
# Functions
|
29
|
+
# =====================================================================
|
30
|
+
|
31
|
+
def self.find_indent text
|
32
|
+
common_prefix lines( text ).map { |line| line[INDENT_RE] }
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def self.indented? text
|
37
|
+
!( find_indent( text ).empty? )
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# adapted from active_support 4.2.0
|
42
|
+
#
|
43
|
+
# <https://github.com/rails/rails/blob/7847a19f476fb9bee287681586d872ea43785e53/activesupport/lib/active_support/core_ext/string/indent.rb>
|
44
|
+
#
|
45
|
+
def self.indent text,
|
46
|
+
amount = 2,
|
47
|
+
indent_string: nil,
|
48
|
+
indent_empty_lines: false,
|
49
|
+
skip_first_line: false
|
50
|
+
if skip_first_line
|
51
|
+
lines = self.lines text
|
52
|
+
|
53
|
+
lines.first + indent(
|
54
|
+
rest( lines ).join,
|
55
|
+
amount,
|
56
|
+
indent_string: indent_string,
|
57
|
+
skip_first_line: false
|
58
|
+
)
|
59
|
+
|
60
|
+
else
|
61
|
+
indent_string = indent_string || text[/^[ \t]/] || ' '
|
62
|
+
re = indent_empty_lines ? /^/ : /^(?!$)/
|
63
|
+
text.gsub re, indent_string * amount
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
def self.dedent text, ignore_whitespace_lines: true
|
70
|
+
return text if text.empty?
|
71
|
+
|
72
|
+
all_lines = text.lines
|
73
|
+
|
74
|
+
indent_significant_lines = if ignore_whitespace_lines
|
75
|
+
all_lines.reject { |line| whitespace? line }
|
76
|
+
else
|
77
|
+
all_lines
|
78
|
+
end
|
79
|
+
|
80
|
+
indent = find_indent indent_significant_lines
|
81
|
+
|
82
|
+
return text if indent.empty?
|
83
|
+
|
84
|
+
all_lines.map { |line|
|
85
|
+
if line.start_with? indent
|
86
|
+
line[indent.length..-1]
|
87
|
+
elsif line.end_with? "\n"
|
88
|
+
"\n"
|
89
|
+
else
|
90
|
+
""
|
91
|
+
end
|
92
|
+
}.join
|
93
|
+
end # .dedent
|
94
|
+
|
95
|
+
# I like dedent better, but other libs seems to call it deindent
|
96
|
+
singleton_class.send :alias_method, :deindent, :dedent
|
97
|
+
|
98
|
+
|
99
|
+
# Tag each line of `text` with special marker characters around it's leading
|
100
|
+
# indent so that the resulting text string can be fed through an
|
101
|
+
# interpolation process like ERB that may inject multiline strings and the
|
102
|
+
# result can then be fed through {NRSER.indent_untag} to apply the correct
|
103
|
+
# indentation to the interpolated lines.
|
104
|
+
#
|
105
|
+
# Each line of `text` is re-formatted like:
|
106
|
+
#
|
107
|
+
# "<marker><leading_indent><separator><line_without_leading_indent>"
|
108
|
+
#
|
109
|
+
# `marker` and `separator` can be configured via keyword arguments, but they
|
110
|
+
# default to:
|
111
|
+
#
|
112
|
+
# - `marker` - {NRSER::INDENT_TAG_MARKER}, the no-printable ASCII
|
113
|
+
# *record separator* (ASCII character 30, "\x1E" / "\u001E").
|
114
|
+
#
|
115
|
+
# - `separator` - {NRSER::INDENT_TAG_SEPARATOR}, the non-printable ASCII
|
116
|
+
# *unit separator* (ASCII character 31, "\x1F" / "\u001F")
|
117
|
+
#
|
118
|
+
# @example With default marker and separator
|
119
|
+
# NRSER.indent_tag " hey there!"
|
120
|
+
# # => "\x1E \x1Fhey there!"
|
121
|
+
#
|
122
|
+
# @param [String] text
|
123
|
+
# String text to indent tag.
|
124
|
+
#
|
125
|
+
# @param [String] marker:
|
126
|
+
# Special string to mark the start of tagged lines. If interpolated text
|
127
|
+
# lines start with this string you're going to have a bad time.
|
128
|
+
#
|
129
|
+
# @param [String] separator:
|
130
|
+
# Special string to separate the leading indent from the rest of the line.
|
131
|
+
#
|
132
|
+
# @return [String]
|
133
|
+
# Tagged text.
|
134
|
+
#
|
135
|
+
def self.indent_tag text,
|
136
|
+
marker: INDENT_TAG_MARKER,
|
137
|
+
separator: INDENT_TAG_SEPARATOR
|
138
|
+
text.lines.map { |line|
|
139
|
+
indent = if match = INDENT_RE.match( line )
|
140
|
+
match[0]
|
141
|
+
else
|
142
|
+
''
|
143
|
+
end
|
144
|
+
|
145
|
+
"#{ marker }#{ indent }#{ separator }#{ line[indent.length..-1] }"
|
146
|
+
}.join
|
147
|
+
end # .indent_tag
|
148
|
+
|
149
|
+
|
150
|
+
# Reverse indent tagging that was done via {NRSER.indent_tag}, indenting
|
151
|
+
# any untagged lines to the same level as the one above them.
|
152
|
+
#
|
153
|
+
# @param [String] text
|
154
|
+
# Tagged text string.
|
155
|
+
#
|
156
|
+
# @param [String] marker:
|
157
|
+
# Must be the marker used to tag the text.
|
158
|
+
#
|
159
|
+
# @param [String] separator:
|
160
|
+
# Must be the separator used to tag the text.
|
161
|
+
#
|
162
|
+
# @return [String]
|
163
|
+
# Final text with interpolation and indent correction.
|
164
|
+
#
|
165
|
+
def self.indent_untag text,
|
166
|
+
marker: INDENT_TAG_MARKER,
|
167
|
+
separator: INDENT_TAG_SEPARATOR
|
168
|
+
|
169
|
+
current_indent = ''
|
170
|
+
|
171
|
+
text.lines.map { |line|
|
172
|
+
if line.start_with? marker
|
173
|
+
current_indent, line = line[marker.length..-1].split( separator, 2 )
|
174
|
+
end
|
175
|
+
|
176
|
+
current_indent + line
|
177
|
+
|
178
|
+
}.join
|
179
|
+
|
180
|
+
end # .indent_untag
|
181
|
+
|
182
|
+
|
183
|
+
|
184
|
+
# Indent tag a some text via {NRSER.indent_tag}, call the block with it,
|
185
|
+
# then pass the result through {NRSER.indent_untag} and return that.
|
186
|
+
#
|
187
|
+
# @param [String] marker:
|
188
|
+
# Special string to mark the start of tagged lines. If interpolated text
|
189
|
+
# lines start with this string you're going to have a bad time.
|
190
|
+
#
|
191
|
+
# @param [String] separator:
|
192
|
+
# Must be the separator used to tag the text.
|
193
|
+
#
|
194
|
+
# @return [String]
|
195
|
+
# Final text with interpolation and indent correction.
|
196
|
+
#
|
197
|
+
def self.with_indent_tagged text,
|
198
|
+
marker: INDENT_TAG_MARKER,
|
199
|
+
separator: INDENT_TAG_SEPARATOR,
|
200
|
+
&interpolate_block
|
201
|
+
indent_untag(
|
202
|
+
interpolate_block.call(
|
203
|
+
indent_tag text, marker: marker, separator: separator
|
204
|
+
),
|
205
|
+
marker: marker,
|
206
|
+
separator: separator,
|
207
|
+
)
|
208
|
+
end # .with_indent_tagged
|
209
|
+
|
210
|
+
end # module NRSER
|