asciidoctor 2.0.13 → 2.0.17
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/CHANGELOG.adoc +151 -30
- data/LICENSE +1 -1
- data/README-de.adoc +9 -12
- data/README-fr.adoc +9 -12
- data/README-jp.adoc +10 -13
- data/README-zh_CN.adoc +9 -12
- data/README.adoc +40 -19
- data/asciidoctor.gemspec +2 -9
- data/data/locale/attributes-fr.adoc +2 -2
- data/data/locale/attributes-th.adoc +23 -0
- data/data/locale/attributes-vi.adoc +23 -0
- data/data/stylesheets/asciidoctor-default.css +54 -53
- data/data/stylesheets/coderay-asciidoctor.css +9 -9
- data/lib/asciidoctor/abstract_block.rb +11 -9
- data/lib/asciidoctor/abstract_node.rb +9 -8
- data/lib/asciidoctor/attribute_list.rb +1 -1
- data/lib/asciidoctor/block.rb +6 -6
- data/lib/asciidoctor/cli/invoker.rb +1 -2
- data/lib/asciidoctor/cli/options.rb +25 -25
- data/lib/asciidoctor/convert.rb +1 -0
- data/lib/asciidoctor/converter/docbook5.rb +45 -26
- data/lib/asciidoctor/converter/html5.rb +130 -102
- data/lib/asciidoctor/converter/manpage.rb +69 -64
- data/lib/asciidoctor/converter/template.rb +12 -13
- data/lib/asciidoctor/converter.rb +6 -4
- data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
- data/lib/asciidoctor/document.rb +61 -57
- data/lib/asciidoctor/extensions.rb +20 -12
- data/lib/asciidoctor/list.rb +2 -6
- data/lib/asciidoctor/load.rb +11 -9
- data/lib/asciidoctor/logging.rb +10 -8
- data/lib/asciidoctor/parser.rb +177 -193
- data/lib/asciidoctor/path_resolver.rb +3 -3
- data/lib/asciidoctor/reader.rb +73 -72
- data/lib/asciidoctor/rx.rb +5 -4
- data/lib/asciidoctor/section.rb +7 -0
- data/lib/asciidoctor/substitutors.rb +121 -121
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +16 -7
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +2 -1
- data/lib/asciidoctor/syntax_highlighter.rb +8 -11
- data/lib/asciidoctor/table.rb +18 -20
- data/lib/asciidoctor/timings.rb +3 -3
- data/lib/asciidoctor/version.rb +1 -1
- data/lib/asciidoctor.rb +10 -10
- data/man/asciidoctor.1 +26 -28
- data/man/asciidoctor.adoc +33 -27
- metadata +8 -62
@@ -109,7 +109,7 @@ class PathResolver
|
|
109
109
|
SLASH = '/'
|
110
110
|
BACKSLASH = '\\'
|
111
111
|
DOUBLE_SLASH = '//'
|
112
|
-
WindowsRootRx =
|
112
|
+
WindowsRootRx = %r(^(?:[a-zA-Z]:)?[\\/])
|
113
113
|
|
114
114
|
attr_accessor :file_separator
|
115
115
|
attr_accessor :working_dir
|
@@ -290,8 +290,8 @@ class PathResolver
|
|
290
290
|
# ex. ./sample/path
|
291
291
|
elsif posix_path.start_with? DOT_SLASH
|
292
292
|
root = DOT_SLASH
|
293
|
-
# else ex. sample/path
|
294
293
|
end
|
294
|
+
# otherwise ex. sample/path
|
295
295
|
elsif root? posix_path
|
296
296
|
# ex. //sample/path
|
297
297
|
if unc? posix_path
|
@@ -306,8 +306,8 @@ class PathResolver
|
|
306
306
|
# ex. ./sample/path
|
307
307
|
elsif posix_path.start_with? DOT_SLASH
|
308
308
|
root = DOT_SLASH
|
309
|
-
# else ex. sample/path
|
310
309
|
end
|
310
|
+
# otherwise ex. sample/path
|
311
311
|
|
312
312
|
path_segments = (root ? (posix_path.slice root.length, posix_path.length) : posix_path).split SLASH
|
313
313
|
# strip out all dot entries
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -59,8 +59,7 @@ class Reader
|
|
59
59
|
end
|
60
60
|
@lineno = cursor.lineno || 1
|
61
61
|
end
|
62
|
-
@lines = prepare_lines data, opts
|
63
|
-
@source_lines = @lines.drop 0
|
62
|
+
@lines = (@source_lines = prepare_lines data, opts).reverse
|
64
63
|
@mark = nil
|
65
64
|
@look_ahead = 0
|
66
65
|
@process_lines = true
|
@@ -127,7 +126,7 @@ class Reader
|
|
127
126
|
# Returns nothing if there is no more data.
|
128
127
|
def peek_line direct = false
|
129
128
|
if direct || @look_ahead > 0
|
130
|
-
@unescape_next_line ? ((line = @lines[
|
129
|
+
@unescape_next_line ? ((line = @lines[-1]).slice 1, line.length) : @lines[-1]
|
131
130
|
elsif @lines.empty?
|
132
131
|
@look_ahead = 0
|
133
132
|
nil
|
@@ -135,7 +134,7 @@ class Reader
|
|
135
134
|
# FIXME the problem with this approach is that we aren't
|
136
135
|
# retaining the modified line (hence the @unescape_next_line tweak)
|
137
136
|
# perhaps we need a stack of proxied lines
|
138
|
-
(
|
137
|
+
(process_line @lines[-1]) || peek_line
|
139
138
|
end
|
140
139
|
end
|
141
140
|
|
@@ -192,9 +191,7 @@ class Reader
|
|
192
191
|
def read_lines
|
193
192
|
lines = []
|
194
193
|
# has_more_lines? triggers preprocessor
|
195
|
-
while has_more_lines?
|
196
|
-
lines << shift
|
197
|
-
end
|
194
|
+
lines << shift while has_more_lines?
|
198
195
|
lines
|
199
196
|
end
|
200
197
|
alias readlines read_lines
|
@@ -241,7 +238,6 @@ class Reader
|
|
241
238
|
# Returns nothing.
|
242
239
|
def unshift_lines lines_to_restore
|
243
240
|
unshift_all lines_to_restore
|
244
|
-
nil
|
245
241
|
end
|
246
242
|
alias restore_lines unshift_lines
|
247
243
|
|
@@ -334,7 +330,7 @@ class Reader
|
|
334
330
|
comment_lines = []
|
335
331
|
# optimized code for shortest execution path
|
336
332
|
while (next_line = peek_line) && !next_line.empty?
|
337
|
-
if
|
333
|
+
if next_line.start_with? '//'
|
338
334
|
comment_lines << shift
|
339
335
|
else
|
340
336
|
break
|
@@ -407,34 +403,22 @@ class Reader
|
|
407
403
|
break_on_list_continuation = options[:break_on_list_continuation]
|
408
404
|
end
|
409
405
|
skip_comments = options[:skip_line_comments]
|
410
|
-
|
406
|
+
line_read = line_restored = nil
|
411
407
|
shift if options[:skip_first_line]
|
412
|
-
while
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
if break_on_list_continuation && line_read && line == LIST_CONTINUATION
|
418
|
-
options[:preserve_last_line] = true
|
419
|
-
break true
|
420
|
-
end
|
421
|
-
break true if block_given? && (yield line)
|
422
|
-
break false
|
423
|
-
end
|
424
|
-
if complete
|
425
|
-
if options[:read_last_line]
|
426
|
-
result << line
|
427
|
-
line_read = true
|
428
|
-
end
|
408
|
+
while (line = read_line)
|
409
|
+
if terminator ? line == terminator : ((break_on_blank_lines && line.empty?) ||
|
410
|
+
(break_on_list_continuation && line_read && line == LIST_CONTINUATION && (options[:preserve_last_line] = true)) ||
|
411
|
+
(block_given? && (yield line)))
|
412
|
+
result << line if options[:read_last_line]
|
429
413
|
if options[:preserve_last_line]
|
430
414
|
unshift line
|
431
415
|
line_restored = true
|
432
416
|
end
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
417
|
+
break
|
418
|
+
end
|
419
|
+
unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
|
420
|
+
result << line
|
421
|
+
line_read = true
|
438
422
|
end
|
439
423
|
end
|
440
424
|
if restore_process_lines
|
@@ -459,21 +443,37 @@ class Reader
|
|
459
443
|
def shift
|
460
444
|
@lineno += 1
|
461
445
|
@look_ahead -= 1 unless @look_ahead == 0
|
462
|
-
@lines.
|
446
|
+
@lines.pop
|
463
447
|
end
|
464
448
|
|
465
449
|
# Internal: Restore the line to the stack and decrement the lineno
|
466
450
|
def unshift line
|
467
451
|
@lineno -= 1
|
468
452
|
@look_ahead += 1
|
469
|
-
@lines.
|
453
|
+
@lines.push line
|
454
|
+
nil
|
470
455
|
end
|
471
456
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
457
|
+
if ::RUBY_ENGINE == 'jruby'
|
458
|
+
# Internal: Restore the lines to the stack and decrement the lineno
|
459
|
+
def unshift_all lines_to_restore
|
460
|
+
@lineno -= lines_to_restore.size
|
461
|
+
@look_ahead += lines_to_restore.size
|
462
|
+
if lines_to_restore.respond_to? :reverse
|
463
|
+
@lines.push(*lines_to_restore.reverse)
|
464
|
+
else
|
465
|
+
lines_to_restore.reverse_each {|it| @lines.push it }
|
466
|
+
end
|
467
|
+
nil
|
468
|
+
end
|
469
|
+
else
|
470
|
+
# Internal: Restore the lines to the stack and decrement the lineno
|
471
|
+
def unshift_all lines_to_restore
|
472
|
+
@lineno -= lines_to_restore.size
|
473
|
+
@look_ahead += lines_to_restore.size
|
474
|
+
@lines.push(*lines_to_restore.reverse)
|
475
|
+
nil
|
476
|
+
end
|
477
477
|
end
|
478
478
|
|
479
479
|
def cursor
|
@@ -516,12 +516,12 @@ class Reader
|
|
516
516
|
#
|
517
517
|
# Returns A copy of the String Array of lines remaining in this Reader
|
518
518
|
def lines
|
519
|
-
@lines.
|
519
|
+
@lines.reverse
|
520
520
|
end
|
521
521
|
|
522
522
|
# Public: Get a copy of the remaining lines managed by this Reader joined as a String
|
523
523
|
def string
|
524
|
-
@lines.join LF
|
524
|
+
@lines.reverse.join LF
|
525
525
|
end
|
526
526
|
|
527
527
|
# Public: Get the source lines for this Reader joined as a String
|
@@ -576,8 +576,7 @@ class Reader
|
|
576
576
|
# Returns A String Array of source lines. If the source data is an Array, this method returns a copy.
|
577
577
|
def prepare_lines data, opts = {}
|
578
578
|
if (normalize = opts[:normalize])
|
579
|
-
|
580
|
-
::Array === data ? (Helpers.prepare_source_array data, trim_end) : (Helpers.prepare_source_string data, trim_end)
|
579
|
+
::Array === data ? (Helpers.prepare_source_array data, normalize != :chomp) : (Helpers.prepare_source_string data, normalize != :chomp)
|
581
580
|
elsif ::Array === data
|
582
581
|
data.drop 0
|
583
582
|
elsif data
|
@@ -690,6 +689,7 @@ class PreprocessorReader < Reader
|
|
690
689
|
@path = (path ||= ::File.basename file)
|
691
690
|
# only process lines in AsciiDoc files
|
692
691
|
if (@process_lines = file.end_with?(*ASCIIDOC_EXTENSIONS.keys))
|
692
|
+
# NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
|
693
693
|
@includes[path.slice 0, (path.rindex '.')] = attributes['partial-option'] ? nil : true
|
694
694
|
end
|
695
695
|
else
|
@@ -697,6 +697,7 @@ class PreprocessorReader < Reader
|
|
697
697
|
# we don't know what file type we have, so assume AsciiDoc
|
698
698
|
@process_lines = true
|
699
699
|
if (@path = path)
|
700
|
+
# NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
|
700
701
|
@includes[Helpers.rootname path] = attributes['partial-option'] ? nil : true
|
701
702
|
else
|
702
703
|
@path = '<stdin>'
|
@@ -723,16 +724,11 @@ class PreprocessorReader < Reader
|
|
723
724
|
else
|
724
725
|
# FIXME we eventually want to handle leveloffset without affecting the lines
|
725
726
|
if attributes.key? 'leveloffset'
|
726
|
-
@lines.
|
727
|
-
|
728
|
-
@lines << ''
|
729
|
-
if (old_leveloffset = @document.attr 'leveloffset')
|
730
|
-
@lines << %(:leveloffset: #{old_leveloffset})
|
731
|
-
else
|
732
|
-
@lines << ':leveloffset!:'
|
733
|
-
end
|
734
|
-
# compensate for these extra lines
|
727
|
+
@lines = [((leveloffset = @document.attr 'leveloffset') ? %(:leveloffset: #{leveloffset}) : ':leveloffset!:'), ''] + @lines.reverse + ['', %(:leveloffset: #{attributes['leveloffset']})]
|
728
|
+
# compensate for these extra lines at the top
|
735
729
|
@lineno -= 2
|
730
|
+
else
|
731
|
+
@lines.reverse!
|
736
732
|
end
|
737
733
|
|
738
734
|
# FIXME kind of a hack
|
@@ -802,10 +798,8 @@ class PreprocessorReader < Reader
|
|
802
798
|
result = super
|
803
799
|
|
804
800
|
# QUESTION should this work for AsciiDoc table cell content? Currently it does not.
|
805
|
-
if @document && @document.attributes['skip-front-matter']
|
806
|
-
|
807
|
-
@document.attributes['front-matter'] = front_matter.join LF
|
808
|
-
end
|
801
|
+
if @document && @document.attributes['skip-front-matter'] && (front_matter = skip_front_matter! result)
|
802
|
+
@document.attributes['front-matter'] = front_matter.join LF
|
809
803
|
end
|
810
804
|
|
811
805
|
if opts.fetch :condense, true
|
@@ -955,11 +949,12 @@ class PreprocessorReader < Reader
|
|
955
949
|
if no_target
|
956
950
|
# the text in brackets must match a conditional expression
|
957
951
|
if text && EvalExpressionRx =~ text.strip
|
952
|
+
# NOTE assignments must happen before call to resolve_expr_val for compatibility with Opal
|
958
953
|
lhs = $1
|
954
|
+
# regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
|
959
955
|
op = $2
|
960
956
|
rhs = $3
|
961
|
-
|
962
|
-
skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true
|
957
|
+
skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true rescue true
|
963
958
|
else
|
964
959
|
logger.error message_with_context %(malformed preprocessor directive - #{text ? 'invalid expression' : 'missing expression'}: ifeval::[#{text}]), source_location: cursor
|
965
960
|
return true
|
@@ -1049,10 +1044,11 @@ class PreprocessorReader < Reader
|
|
1049
1044
|
|
1050
1045
|
parsed_attrs = doc.parse_attributes attrlist, [], sub_input: true
|
1051
1046
|
inc_path, target_type, relpath = resolve_include_path expanded_target, attrlist, parsed_attrs
|
1052
|
-
|
1047
|
+
case target_type
|
1048
|
+
when :file
|
1053
1049
|
reader = ::File.method :open
|
1054
1050
|
read_mode = FILE_READ_MODE
|
1055
|
-
|
1051
|
+
when :uri
|
1056
1052
|
reader = ::OpenURI.method :open_uri
|
1057
1053
|
read_mode = URI_READ_MODE
|
1058
1054
|
else
|
@@ -1073,7 +1069,7 @@ class PreprocessorReader < Reader
|
|
1073
1069
|
(split_delimited_value parsed_attrs['lines']).each do |linedef|
|
1074
1070
|
if linedef.include? '..'
|
1075
1071
|
from, _, to = linedef.partition '..'
|
1076
|
-
inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i,
|
1072
|
+
inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, ::Float::INFINITY] : (from.to_i..to).to_a
|
1077
1073
|
else
|
1078
1074
|
inc_linenos << linedef.to_i
|
1079
1075
|
end
|
@@ -1129,18 +1125,23 @@ class PreprocessorReader < Reader
|
|
1129
1125
|
push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
|
1130
1126
|
end
|
1131
1127
|
elsif inc_tags
|
1132
|
-
inc_lines, inc_offset, inc_lineno, tag_stack,
|
1128
|
+
inc_lines, inc_offset, inc_lineno, tag_stack, tags_selected, active_tag = [], nil, 0, [], ::Set.new, nil
|
1133
1129
|
if inc_tags.key? '**'
|
1130
|
+
select = base_select = inc_tags.delete '**'
|
1134
1131
|
if inc_tags.key? '*'
|
1135
|
-
select = base_select = inc_tags.delete '**'
|
1136
1132
|
wildcard = inc_tags.delete '*'
|
1137
|
-
|
1138
|
-
|
1133
|
+
elsif !select && inc_tags.values.first == false
|
1134
|
+
wildcard = true
|
1139
1135
|
end
|
1140
1136
|
elsif inc_tags.key? '*'
|
1141
|
-
|
1137
|
+
if inc_tags.keys.first == '*'
|
1138
|
+
select = base_select = !(wildcard = inc_tags.delete '*')
|
1139
|
+
else
|
1140
|
+
select = base_select = false
|
1141
|
+
wildcard = inc_tags.delete '*'
|
1142
|
+
end
|
1142
1143
|
else
|
1143
|
-
select = base_select =
|
1144
|
+
select = base_select = !(inc_tags.value? true)
|
1144
1145
|
end
|
1145
1146
|
begin
|
1146
1147
|
reader.call inc_path, read_mode do |f|
|
@@ -1163,9 +1164,9 @@ class PreprocessorReader < Reader
|
|
1163
1164
|
end
|
1164
1165
|
end
|
1165
1166
|
elsif inc_tags.key? this_tag
|
1166
|
-
|
1167
|
+
tags_selected << this_tag if (select = inc_tags[this_tag])
|
1167
1168
|
# QUESTION should we prevent tag from being selected when enclosing tag is excluded?
|
1168
|
-
tag_stack << [(active_tag = this_tag),
|
1169
|
+
tag_stack << [(active_tag = this_tag), select, inc_lineno]
|
1169
1170
|
elsif !wildcard.nil?
|
1170
1171
|
select = active_tag && !select ? false : wildcard
|
1171
1172
|
tag_stack << [(active_tag = this_tag), select, inc_lineno]
|
@@ -1186,12 +1187,12 @@ class PreprocessorReader < Reader
|
|
1186
1187
|
logger.warn message_with_context %(detected unclosed tag '#{tag_name}' starting at line #{tag_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: (create_include_cursor inc_path, expanded_target, tag_lineno)
|
1187
1188
|
end
|
1188
1189
|
end
|
1189
|
-
unless (missing_tags = inc_tags.keys -
|
1190
|
+
unless (missing_tags = inc_tags.keep_if {|_, v| v }.keys - tags_selected.to_a).empty?
|
1190
1191
|
logger.warn message_with_context %(tag#{missing_tags.size > 1 ? 's' : ''} '#{missing_tags.join ', '}' not found in include #{target_type}: #{inc_path}), source_location: cursor
|
1191
1192
|
end
|
1192
1193
|
shift
|
1193
1194
|
if inc_offset
|
1194
|
-
parsed_attrs['partial-option'] = '' unless base_select && wildcard && inc_tags.empty?
|
1195
|
+
parsed_attrs['partial-option'] = '' unless base_select && wildcard != false && inc_tags.empty?
|
1195
1196
|
# FIXME not accounting for skipped lines in reader line numbering
|
1196
1197
|
push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
|
1197
1198
|
end
|
@@ -1262,7 +1263,7 @@ class PreprocessorReader < Reader
|
|
1262
1263
|
end
|
1263
1264
|
|
1264
1265
|
def pop_include
|
1265
|
-
|
1266
|
+
unless @include_stack.empty?
|
1266
1267
|
@lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines = @include_stack.pop
|
1267
1268
|
# FIXME kind of a hack
|
1268
1269
|
#Document::AttributeEntry.new('infile', @file).save_to_next_block @document
|
data/lib/asciidoctor/rx.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Asciidoctor
|
2
3
|
# A collection of regular expression constants used by the parser. (For speed, these are not defined in the Rx module,
|
3
4
|
# but rather directly in the Asciidoctor module).
|
@@ -406,7 +407,7 @@ module Asciidoctor
|
|
406
407
|
# gist::123456[]
|
407
408
|
#
|
408
409
|
#--
|
409
|
-
# NOTE we've relaxed the match for target to
|
410
|
+
# NOTE we've relaxed the match for target to accommodate the short format (e.g., name::[attrlist])
|
410
411
|
CustomBlockMacroRx = /^(#{CG_WORD}[#{CC_WORD}-]*)::(|\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
|
411
412
|
|
412
413
|
# Matches an image, video or audio block macro.
|
@@ -469,7 +470,7 @@ module Asciidoctor
|
|
469
470
|
# footnoteref:[id,text] (legacy)
|
470
471
|
# footnoteref:[id] (legacy)
|
471
472
|
#
|
472
|
-
InlineFootnoteMacroRx =
|
473
|
+
InlineFootnoteMacroRx = %r(\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!</a>))m
|
473
474
|
|
474
475
|
# Matches an image or icon inline macro.
|
475
476
|
#
|
@@ -592,7 +593,7 @@ module Asciidoctor
|
|
592
593
|
# $$text$$
|
593
594
|
# pass:quotes[text]
|
594
595
|
#
|
595
|
-
# NOTE we have to support an empty pass:[] for compatibility with AsciiDoc
|
596
|
+
# NOTE we have to support an empty pass:[] for compatibility with AsciiDoc.py
|
596
597
|
InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
|
597
598
|
|
598
599
|
# Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
|
@@ -611,7 +612,7 @@ module Asciidoctor
|
|
611
612
|
# Matches a trailing + preceded by at least one space character,
|
612
613
|
# which forces a hard line break (<br> tag in HTML output).
|
613
614
|
#
|
614
|
-
# NOTE AsciiDoc
|
615
|
+
# NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not
|
615
616
|
#
|
616
617
|
# Examples
|
617
618
|
#
|
data/lib/asciidoctor/section.rb
CHANGED
@@ -64,6 +64,13 @@ class Section < AbstractBlock
|
|
64
64
|
Section.generate_id title, @document
|
65
65
|
end
|
66
66
|
|
67
|
+
# Public: Check whether this Section has any child Section objects.
|
68
|
+
#
|
69
|
+
# Returns A [Boolean] to indicate whether this Section has child Section objects
|
70
|
+
def sections?
|
71
|
+
@next_section_index > 0
|
72
|
+
end
|
73
|
+
|
67
74
|
# Public: Get the section number for the current Section
|
68
75
|
#
|
69
76
|
# The section number is a dot-separated String that uniquely describes the position of this
|