nrser 0.0.26 → 0.0.27
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 +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
|