plain_text 0.1

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.
@@ -0,0 +1,474 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module PlainText
4
+
5
+ # Class to describe rules to parse a String (and Array of them)
6
+ #
7
+ # An instance (say, +pr+) of this class describes how a String (or Array of them)
8
+ # is parsed to a structure, that is, an Array of String or maybe
9
+ # {PlainText::Part}, {PlainText::Part::Paragraph}, {PlainText::Part::Boundary}.
10
+ # Once +pr+ is created, a String +str+ is parsed as
11
+ # ary = pr.apply(str)
12
+ # which returns an Array (referred to as +ary+ hereafter).
13
+ #
14
+ # The returned array +ary+ may contain Strings at the basic level. In that case,
15
+ # any even elements are semantically {PlainText::Part::Boundary} and any odd elements are
16
+ # semantically {PlainText::Part::Paragraph} or {PlainText::Part}, which can be further parsed
17
+ # in the later processing.
18
+ #
19
+ # Alternatively, the returned array +ary+ may contain
20
+ # {PlainText::Part::Paragraph}, {PlainText::Part::Boundary}, or even {PlainText::Part},
21
+ # depending how the instance +pr+ is constructed.
22
+ #
23
+ # An instance +pr+ consists of an array of the rules (which can be retrieved by {#rules});
24
+ # each rule of it is either a Proc instance or Regexp.
25
+ # The rule is applied to either String (for the first-time application only)
26
+ # or Array (for any subsequent applications), the latter of which is
27
+ # (though it does not have to be) the result of the previous applications, and an Array is returned.
28
+ # Elements of {#rules} (particularly common for for {#rules}[ 0 ]) can be Regexp,
29
+ # in which case either the given String or every element of an even index (starting from 0;
30
+ # they all are semantically Paragraphs) of the given Array is String#split as defined in the rule
31
+ # to return an Array. This manipulation with String#split in general increases the number of the elements
32
+ # (Array#size) if an Array is given as the argument. For example, suppose the given Array has initially two elements,
33
+ # and suppose String#split is applied to the first element (only), and it may create 5 elements. Then, the resultant
34
+ # number of elements of the returned array is 6.
35
+ #
36
+ # For the second or later application, the element, Proc, must assume the argument is an Array
37
+ # (of String or even {PlainText}::SOMETHING objects) and process them accordingly.
38
+ #
39
+ # For example, the predefined constant {PlainText::ParseRule::RuleConsecutiveLbs}
40
+ # is one of the instances and it splits a String based on any consecutive linebreaks
41
+ # (it is typical to regard paragraphs as being separated by consecutive linebreaks).
42
+ # An example is like this:
43
+ #
44
+ # pr.rules[0] # => The rule is: PlainText::ParseRule::RuleConsecutiveLbs.rules[0]
45
+ # # Once applied, the returned Array is like
46
+ # # ["My story\n======\nHere is my report.",
47
+ # # "\n\n", "abc", "\n\n", "xyz"]
48
+ # pr.rules[1] # => /(\n={4,}\n)/
49
+ # # Once applied, the returned Array is like
50
+ # # ["My story", "\n======\n", "Here is my report.",
51
+ # # "\n\n", "abc", "\n\n", "xyz"]
52
+ #
53
+ # Or another example may be like this:
54
+ #
55
+ # pr.rules[0] # => The rule: PlainText::ParseRule::RuleConsecutiveLbs.rules[0]
56
+ # # Once applied, the returned Array is like
57
+ # # ["# Breaking! #\nBy Mary Smith\n======\nHere is my report.",
58
+ # # "\n\n", "abc", "\n\n", "xyz"]
59
+ # pr.rules[1] # => The rule: For the first element of the input argument (Array), if it has one "\n======\n",
60
+ # # it is regarded as a (the first) boundary, and the text before
61
+ # # is regarded as {PlainText::Part}. The returned Array is like
62
+ # # [Part("# Breaking! #\nBy Mary Smith"),
63
+ # # Boundary("\n======\n"),
64
+ # # Paragraph("Here is my report."),
65
+ # # "\n\n", "abc", "\n\n", "xyz"]
66
+ # pr.rules[2] # => The rule: For the first element of the input argument (Array), if it satisfies /# (.+) #/,
67
+ # # it is regarded as a title of a header. The returned Array is like
68
+ # # [Part::Header(Paragraph(""), Boundary("# "), Paragraph::Title("Breaking!"), Boundary(" #\n")),
69
+ # # Boundary(""),
70
+ # # Paragraph("By Mary Smith"),
71
+ # # Boundary("\n======\n"),
72
+ # # Paragraph("Here is my report."),
73
+ # # "\n\n", "abc", "\n\n", "xyz"]
74
+ #
75
+ # With this, a {PlainText::Part} instance can be created like:
76
+ #
77
+ # pt1 = PlainText::Part.parse(str, rule: pr)
78
+ #
79
+ # Then,
80
+ #
81
+ # pt1.parts[0].parts[1] # => Paragraph::Title("Breaking!")
82
+ # pt1.boundaries[1] # => Boundary("\n======\n")
83
+ #
84
+ # @author Masa Sakano (Wise Babel Ltd)
85
+ #
86
+ class ParseRule
87
+
88
+ # Main Array of rules (Proc or Regexp). Do not delete or add the contents, as it would have a knock-on effect, especially with {#names}!
89
+ # Use {#rule_at} to get a rule for the index/key.
90
+ # The private method {#rule_at}(-1) is the same as {#rules}[-1],
91
+ # but is more versatile and can be called like +#rules_at(:my_rule1, :my_rule2)+.
92
+ attr_reader :rules
93
+
94
+ # User-specified human-readable names Array, corresponding to each element of {#rules}.
95
+ # The elements of this array are either String or nil, though it can be referred to as,
96
+ # or set with {#set_name_at}, with Symbol. In other words, an element of {#rules}
97
+ # can be specified with a human-readable name, if set, as well as its index.
98
+ # Use {#rule_at} to get a rule for the index/key.
99
+ attr_reader :names
100
+
101
+ # Constructor
102
+ #
103
+ # The main argument is a single or an Array of Proc or Regexp.
104
+ # Alternatively, a block can be given.
105
+ # If Regexp(s) is given, it should include grouping
106
+ # (to enclose the entire Regexp usually). If not, grouping is added forcibly.
107
+ #
108
+ # Note that the method (private method {#add_grouping}) wrongly recognizes patterns like +/[(x]/+ to contain grouping.
109
+ # Also, it does not raise warning when more than one grouping is defined.
110
+ # In fact, multiple groupings might be useful in some cases, such as,
111
+ # /(\n{2,})([^\n]*\S)([[:blank:]]*={2,}\n{2,})/
112
+ # would produce, when applied, a series of
113
+ # [Paragraph, Boundary("\n\n"), Paragraph::Title, Boundary("==\n\n")]
114
+ # Just make sure the number of groupings is an odd number, though.
115
+ #
116
+ # Optionally, when a non-Array argument or block is given, a name can be specified as the human-readable name for the rule.
117
+ #
118
+ # @option rule [ParseRule, Array, Regexp, Proc]
119
+ # @param name: [String, Symbol]
120
+ #
121
+ # @yield [inprm] Block to register.
122
+ # @yieldparam [String, Array<Part, Paragraph, Boundary>, Part] inprm Input String/Part/Array to apply the rule to.
123
+ # @yieldreturn [Array]
124
+ def initialize(rule=nil, name: nil, &rule_block)
125
+ if defined?(rule.rules) && defined?(rule.names)
126
+ # ParseRule given
127
+ @rules = rule.rules.clone.map{|i| i.clone rescue i} # Deep copy
128
+ @names = rule.names.clone.map{|i| i.clone rescue i} # Deep copy
129
+ return
130
+ end
131
+
132
+ if defined? rule.to_ary
133
+ # Array given
134
+ @rules = rule
135
+ @names = Array.new(@rules.size)
136
+ return
137
+ end
138
+
139
+ @rules = []
140
+ @names = []
141
+ push(rule, name: name, &rule_block)
142
+ end
143
+
144
+
145
+ # If no grouping is specified in Regexp, this method encloses it with '()'
146
+ #
147
+ # Because otherwise Boundaries would not be recognized.
148
+ #
149
+ # Note that this wrongly recognizes patterns like +/[(x]/+ to contain grouping.
150
+ # Also, this does not raise warning when more than one grouping is defined.
151
+ # In fact, multiple groupings might be useful in some cases, such as,
152
+ # /(\n{2,})([^\n]*\S)([[:blank:]]*={2,}\n{2,})/
153
+ # would produce, when applied, a series of
154
+ # [Paragraph, Boundary("\n\n"), Paragraph::Title, Boundary("==\n\n")]
155
+ # Just make sure the number of groupings is an odd number, though.
156
+ #
157
+ # @param rule_re [Regexp]
158
+ # @return [Regexp]
159
+ # @see PlainText::Split.add_grouping
160
+ def add_grouping(rule_re)
161
+ re_src = rule_re.source
162
+ return rule_re if /(?<!\\)(?:(\\\\)*)\((?!\?:)/ =~ re_src
163
+
164
+ # No "explicit" grouping is specified. Hence adds it.
165
+ Regexp.new '('+re_src+')', rule_re.options
166
+ end
167
+ private :add_grouping
168
+
169
+
170
+ alias_method :clone_original_b4_parse_rule?, :clone if !method_defined? :clone_original_b4_parse_rule?
171
+
172
+ # Deeper clone
173
+ #
174
+ # Without this, if @rules or @names are modified in a cloned instance,
175
+ # even the original is affected.
176
+ #
177
+ # @return the same as self
178
+ def clone
179
+ ret = clone_original_b4_parse_rule?
180
+ begin
181
+ ret.instance_eval{ @rules = rules.clone }
182
+ ret.instance_eval{ @names = names.clone }
183
+ rescue FrozenError
184
+ warn "Instances in the original remain frozen after clone."
185
+ end
186
+ ret
187
+ end
188
+
189
+
190
+ alias_method :dup_original_b4_parse_rule?, :dup if !method_defined? :dup_original_b4_parse_rule?
191
+
192
+ # Deeper dup
193
+ #
194
+ # Without this, if @rules or @names are modified in a dupped instance,
195
+ # even the original is affected.
196
+ #
197
+ # @return the same as self
198
+ def dup
199
+ ret = dup_original_b4_parse_rule?
200
+ ret.instance_eval{ @rules = rules.dup }
201
+ ret.instance_eval{ @names = names.dup }
202
+ ret
203
+ end
204
+
205
+
206
+ # Add a rule(s)
207
+ #
208
+ # If Regexp is given, it should include grouping (to enclose the entire Regexp usually). If not, grouping is added forcibly.
209
+ # Or, Proc or block can be given.
210
+ # Consecutive rules can be given. Note if a rule(s) is given, a block is ignored even if present.
211
+ #
212
+ # Any given rules, except the very first one, where the Proc argument is a String, should assume the Proc argument is an Array.
213
+ # If Regexp is given for the second or later one, it will raise an Exception when {#apply}-ed.
214
+ #
215
+ # Optionally, providing non-Array argument or block is given, a name can be specified as the human-readable name for the rule.
216
+ #
217
+ # @option *rule [Regexp, Proc]
218
+ # @param name: [String, Symbol, NilClass, Array<String, Symbol, NilClass>] Array is not supported, yet.
219
+ # @return [self]
220
+ #
221
+ # @yield [inprm] Block to register.
222
+ # @yieldparam [String, Array<Part, Paragraph, Boundary>, Part] inprm Input String/Part/Array to apply the rule to.
223
+ # @yieldreturn [Array]
224
+ def push(*rule, name: nil, &rule_block)
225
+ #if rule.size > 1
226
+ # rule.each do |each_r|
227
+ # push each_r, rule_block
228
+ # end
229
+ # return self
230
+ #end
231
+
232
+ push_rule_core(*rule, &rule_block)
233
+ set_name_at(name, -1) if !rules.empty?
234
+ # rulesize = ((0 != rule.size) ? rule.size : (block_given? ? 1 : 0))
235
+ ### print "DEBUG-p: rulesize=#{rulesize}\n"
236
+ # arnames = (name ? [name].flatten : [])
237
+ # ((-rulesize)..-1).each_with_index do |i_rule, i_given|
238
+ # set_name_at(arnames[i_given], i_rule)
239
+ # end if !rule.empty?
240
+ self
241
+ end
242
+
243
+ # @option *rule [Regexp, Proc]
244
+ # @return [self]
245
+ #
246
+ # @yield [inprm] Block to register.
247
+ # @yieldparam [String, Array<Part, Paragraph, Boundary>, Part] inprm Input String/Part/Array to apply the rule to.
248
+ # @yieldreturn [Array]
249
+ def push_rule_core(*rule, &rule_block)
250
+ # If rule is given, it is guaranteed to be a single component.
251
+ rule0 = rule[0]
252
+ if rule0
253
+ raise ArgumentError, "Argument and block are not allowed to be given simultaneously." if block_given?
254
+ if defined?(rule0.source) && defined?(rule0.options)
255
+ # Regexp given
256
+ @rules.push add_grouping(rule0)
257
+ return self
258
+ end
259
+
260
+ if defined? rule0.lambda?
261
+ # Proc given
262
+ @rules.push rule0
263
+ return self
264
+ end
265
+
266
+ raise ArgumentError, "Invalid rule is given."
267
+ end
268
+
269
+ raise ArgumentError, "Neither an argument nor block is given." if !block_given?
270
+
271
+ @rules.push rule_block
272
+ self
273
+ end
274
+ private :push_rule_core
275
+
276
+ # Set (or reset) a human-readable name for {#rules} at a specified index
277
+ #
278
+ # @param name [NilClass, #to_s] nil to reset or a human-readable name, usually either String or Symbol
279
+ # @param index [Integer] Index for {#rules}. A negative index is allowed.
280
+ # @return [Integer] Non-negative index where name is set; i.e., if index=-1 is specified for {#rules} with a size of 3, the returned value is 2 (the last index of it).
281
+ def set_name_at(name, index_rules)
282
+ index = PlainText::Util.positive_array_index_checked(index_rules, @rules, accept_too_big: false, varname: 'rules')
283
+ if !name
284
+ @names[index] = nil
285
+ return index
286
+ end
287
+ ns = name.to_s
288
+ index_exist = @names.find_index(ns)
289
+ raise "Name #{ns} is already used for the index #{index}" if index_exist && (index_exist != index)
290
+ @names[index] = ns
291
+ index
292
+ end
293
+
294
+
295
+ # Get a rule for the specified index or human-readable key
296
+ #
297
+ # @param key [Integer, String, Symbol] Key for @rules
298
+ # @return [Proc, Regexp, NilClass] nil if the specified rule is not found.
299
+ def rule_at(key)
300
+ begin
301
+ ( defined?(key.to_int) ? @rules[key.to_int] : @rules[@names.find_index(key.to_s)] )
302
+ rescue TypeError # no implicit conversion from nil to integer
303
+ nil
304
+ # raise TypeError, "Specified key (#{key.inspect}) is not found for the rules among the registered names=#{@names.inspect}"
305
+ end
306
+ end
307
+
308
+
309
+ # Get an array of rules for the specified indices or human-readable keys
310
+ #
311
+ # If an Array or sequence of arguments is given, it can be a combination of Integer and String/Symbol,
312
+ # and the order of the elements in the returned Array corresponds to the input.
313
+ #
314
+ # @param keys [Array, Integer, Range, String, Symbol] Key for @rules
315
+ # @return [Proc, Regexp]
316
+ def rules_at(keys, *rest)
317
+ if defined?(keys.exclude_end?)
318
+ return @rules[keys]
319
+ end
320
+ ([keys]+rest).flatten.map{ |i| rule_at(i) }
321
+ end
322
+ private :rules_at
323
+
324
+
325
+ # Pop a rule(s)
326
+ #
327
+ # @option *rest [Integer]
328
+ # @return [Proc, Array<Proc>] if no argument is given, Proc is returned.
329
+ def pop(*rest)
330
+ if (rest.size == 0)
331
+ (@rules.size > 0) ? @names.slice!((@rules.size-1)..-1) : @names.clear
332
+ else
333
+ i_beg = @rules.size - rest[0]
334
+ i_beg = 0 if i_beg < 0
335
+ @names.slice!(i_beg..-1)
336
+ end
337
+ (rest.size == 0) ? @rules.pop : @rules.pop(*rest)
338
+ end
339
+
340
+
341
+ # Apply the rules to a given String
342
+ #
343
+ # In default, all the rules are applied in the registered sequence, unless an Option is specified
344
+ #
345
+ # This method receives either String (for the first-time application only)
346
+ # or Array (for any subsequent applications), the latter of which is
347
+ # (though not necessarily) the result of the previous applications,
348
+ # applies the {#rules} one by one sequentially, and returns an Array.
349
+ #
350
+ # Elements of {#rules} can be Regexp (particularly common for for {#rules}[0]).
351
+ # In that case, if the given argument is a String, String#split is simply applied.
352
+ # If it is an Array, String#split is applied to every element of an even index
353
+ # (starting from 0; n.b., all even-index elements are semantically Paragraphs).
354
+ # Importantly, this manipulation with String#split to Array unfolds the result
355
+ # of split on the spot, which means in general it increases
356
+ # the number of the elements (Array#size) from the given one. For example,
357
+ # suppose the given Array has initially two elements and then String#split
358
+ # is applied to the first element only (because it is the only even-index element).
359
+ # Suppose the application creates 3 elements. They are interpreted as
360
+ # a sequence of Paragraph, Boundary, and Paragraph. Then the returned array
361
+ # will contain 4 elements. Or, suppose the split application to the first element
362
+ # of the given array resulted in an array of 4 elements. Then, the last element
363
+ # of this array and the next element of the original array are both Boundary.
364
+ # In this case, the two Boundaries are merged so that the elements of
365
+ # the returned array are in the right order of Paragraphs and Boundaries.
366
+ #
367
+ # @example String input
368
+ # pr = PlainText::Part::ParseRule /(\n)/
369
+ # pr.rules #=> [/(\n)/]
370
+ # pr.apply(["abc==def==\n"])
371
+ # #=> ["abc==def==", "\n"])
372
+ #
373
+ # @example Array input
374
+ # pr.rules #=> [/(==)/]
375
+ # pr.apply(["abc==def==", "\n"])
376
+ # #=> ["abc", "==", "def", "==\n"])
377
+ #
378
+ # @example String input, sequential processing
379
+ # pr.rules #=> [/(\n)/, /(==)/]
380
+ # pr.apply(["abc==def==\n"])
381
+ # #=> ["abc", "==", "def", "==\n"])
382
+ #
383
+ # @example Regexp and Proc rules, applied one by one.
384
+ # pr = PlainText::Part::ParseRule /(==(?:\n)?)/, index: 'my_first'
385
+ # pr.push{ |i| i.map{|j| ("def"==j) ? PlainText::Part::Paragraph(j) : j}}
386
+ # pr.rules
387
+ # #=> [/(==(?:\n)?)/, Proc{ |i| i.map{|j| ("def"==j) ? i.upcase : j}}]
388
+ # ar0 = pr.apply(["abc==def==\n"], index: 'my_first')
389
+ # #=> ["abc", "==", "def", "==\n"])
390
+ # pr.apply ar0, index: 1
391
+ # #=> ["abc", "==", "DEF", "==\n"])
392
+ #
393
+ # @param inprm [String, Array, PlainText::Part]
394
+ # @param index: [Array, Range, Integer, String, Symbol] If given, the rule(s) at the given index (indices) or key(s) only are applied in the given order.
395
+ # @return [Array] array of String, Paragraph, Boundary, Array, Part, etc
396
+ def apply(inprm, index: nil, from_string: true, from_array: true)
397
+ allrules = (index ? rules_at(index) : @rules)
398
+
399
+ arret = (inprm.class.method_defined?(:to_ary) ? inprm : [inprm])
400
+ allrules.each do |each_r|
401
+ arret = (defined?(each_r.match) ? apply_split(arret, each_r) : each_r.call(arret))
402
+ end
403
+ arret
404
+ end
405
+
406
+ # Apply String#split with Regexp
407
+ #
408
+ # If an Array is given, Regexp is applied to each of even-number elements,
409
+ # which are supposed to be {Paragraph}, one by one and recursively.
410
+ #
411
+ # @param inprm [String, Array, PlainText::Part]
412
+ # @param re [Regexp]
413
+ # @return [Array]
414
+ def apply_split(inprm, re)
415
+ return inprm.split re if !defined? inprm.to_ary
416
+
417
+ hsflag = { concat_bd: false } # whether concatnate Boundary to the previous one as a String.
418
+ arret = []
419
+ inprm.each_with_index do |ea_e, i|
420
+ if i.odd?
421
+ if !hsflag[:concat_bd]
422
+ arret << ea_e
423
+ next
424
+ end
425
+ if defined? ea_e.to_ary
426
+ # The given argument (by the user) is wrong! Boundary is somehow an Array.
427
+ # Here, an empty string is added, emulating an empty Paragraph.
428
+ arret << "" << ea_e
429
+ else
430
+ # Boundary is concatnated with the previous one.
431
+ arret[-1] << ea_e
432
+ end
433
+ hsflag[:concat_bd] = false
434
+ next
435
+ end
436
+
437
+ ar = apply_split(ea_e, re)
438
+
439
+ if (defined? ea_e.to_ary)
440
+ # The processed Array(Part) simply replaces the existing one (no change of the size of the given array).
441
+ arret << ar
442
+ else
443
+ # String(Paragraph) is split further and concatnated on the spot.
444
+ ar = [""] if ar.empty?
445
+ arret.concat ar
446
+ hsflag[:concat_bd] = true if ar.size.even? # The next element (Boundary) should be appended to the last element as String.
447
+ end
448
+ end
449
+ arret
450
+ end
451
+ private :apply_split
452
+
453
+ # @return [Integer] The number of defined rules.
454
+ def size
455
+ si_rules = rules.size
456
+ si_names = names.size
457
+ if si_rules != si_names
458
+ warn "WARNING: Inconsistent sizes for between rules (#{si_rules}) and names (#{si_names})."
459
+ end
460
+ si_rules
461
+ end
462
+
463
+ def_lb_q = PlainText::DefLineBreaks.map{|i| Regexp.quote i}.join '|'
464
+
465
+ # {ParseRule} instance to
466
+ # split a String with 2 or more linebreaks (with potentially white-spaces in between).
467
+ # This instance can be dup-ped and used normally. However, if it is clone-d, the cloned instance would be unmodifiable.
468
+ RuleConsecutiveLbs = self.new(/((?:#{def_lb_q})(?:#{def_lb_q}|[[:blank:]])*(?:#{def_lb_q}))/, name: 'ConsecutiveLbs') # => /((?:\r\n|\n|\r){2,}/
469
+ RuleConsecutiveLbs.freeze
470
+ RuleConsecutiveLbs.rules.freeze
471
+ RuleConsecutiveLbs.names.freeze
472
+ end # class ParseRule
473
+ end # module PlainText
474
+
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module PlainText
4
+ class Part < Array
5
+
6
+ # Class to express a Boundary as String
7
+ #
8
+ class Boundary < String
9
+
10
+ # @return [String]
11
+ def inspect
12
+ # 'Boundary("\n\n\n")'
13
+ s = self.class.name
14
+ sprintf "%s(%s)", (s.split('::')[2..-1].join('::') rescue s), super
15
+ end
16
+
17
+ # Boundary sub-class name only
18
+ #
19
+ # Make sure your class is a child class of Boundary.
20
+ # Otherwise this method would not be inherited, obviously.
21
+ #
22
+ # @example
23
+ # class PlainText::Part::Boundary
24
+ # class SubBoundary < self
25
+ # class SubBoundary < self; end # It must be a child class!
26
+ # end
27
+ # end
28
+ # ss = PlainText::Part::SubBoundary::SubSubBoundary.new ["abc"]
29
+ # ss.subclass_name # => "SubBoundary::SubSubBoundary"
30
+ #
31
+ # @return [String]
32
+ # @see PlainText::Part#subclass_name
33
+ def subclass_name
34
+ printf "__method__=(%s)\n", __method__
35
+ self.class.name.split(/\A#{Regexp.quote method(__method__).owner.name}::/)[1] || ''
36
+ end
37
+
38
+ # Empty Boundary instance
39
+ Empty = self.new ""
40
+ Empty.freeze
41
+ end # class Boundary < String
42
+ end # class Part < Array
43
+ end # module PlainText
44
+
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module PlainText
4
+ class Part < Array
5
+
6
+ # Class to express a Paragraph as String
7
+ #
8
+ class Paragraph < String
9
+
10
+ # @return [String]
11
+ def inspect
12
+ # 'Paragraph("abc\ndef")' or like 'Paragraph::Title("My Title")'
13
+ s = self.class.name
14
+ sprintf "%s(%s)", (s.split('::')[2..-1].join('::') rescue s), super
15
+ end
16
+
17
+ # Paragraph sub-class name
18
+ #
19
+ # Make sure your class is a child class of Paragraph.
20
+ # Otherwise this method would not be inherited, obviously.
21
+ #
22
+ # @return [String]
23
+ # @see PlainText::Part#subclass_name
24
+ def subclass_name
25
+ printf "__method__=(%s)\n", __method__
26
+ self.class.name.split(/\A#{Regexp.quote method(__method__).owner.name}::/)[1] || ''
27
+ end
28
+
29
+ # Empty Paragraph instance
30
+ Empty = self.new ""
31
+ Empty.freeze
32
+ end # class Paragraph < String
33
+ end # class Part < Array
34
+ end # module PlainText
35
+