class Asciidoctor::PreprocessorReader
def preprocess_include_directive target, attrlist
Returns a [Boolean] indicating whether the line under the cursor was changed. To skip over the
include directive.
attrlist - An attribute list String, which is the text between the square brackets of the
target slot of the include directive.
target - The unsubstituted String name of the target document to include as specified in the
If none of the above apply, emit the include directive line verbatim.
of the Array of source data.
stack size, normalize the target path and read the lines onto the beginning
Otherwise, if the max depth is greater than 0, and is not exceeded by the
attributes to that processor and expect an Array of String lines in return.
Otherwise, if an include processor is specified pass the target and
directive line is emitted verbatim.
If SafeMode is SECURE or greater, the directive is ignore and the include
are as follows:
Preprocess the directive to include the target document. The scenarios
Internal: Preprocess the directive to include lines from another document.
def preprocess_include_directive target, attrlist doc = @document if ((expanded_target = target).include? ATTR_REF_HEAD) && (expanded_target = doc.sub_attributes target, attribute_missing: ((attr_missing = doc.attributes['attribute-missing'] || Compliance.attribute_missing) == 'warn' ? 'drop-line' : attr_missing)).empty? if attr_missing == 'drop-line' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty? logger.info { message_with_context %(include dropped due to missing attribute: include::#{target}[#{attrlist}]), source_location: cursor } shift true elsif (doc.parse_attributes attrlist, [], sub_input: true)['optional-option'] logger.info { message_with_context %(optional include dropped #{attr_missing == 'warn' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty? ? 'due to missing attribute' : 'because resolved target is blank'}: include::#{target}[#{attrlist}]), source_location: cursor } shift true else logger.warn message_with_context %(include dropped #{attr_missing == 'warn' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty? ? 'due to missing attribute' : 'because resolved target is blank'}: include::#{target}[#{attrlist}]), source_location: cursor # QUESTION should this line include target or expanded_target (or escaped target?) replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{attrlist}]) end elsif include_processors? && (ext = @include_processor_extensions.find {|candidate| candidate.instance.handles? expanded_target }) shift # FIXME parse attributes only if requested by extension ext.process_method[doc, self, expanded_target, (doc.parse_attributes attrlist, [], sub_input: true)] true # if running in SafeMode::SECURE or greater, don't process this directive # however, be friendly and at least make it a link to the source document elsif doc.safe >= SafeMode::SECURE # FIXME we don't want to use a link macro if we are in a verbatim context replace_next_line %(link:#{expanded_target}[]) elsif @maxdepth if @include_stack.size >= @maxdepth[:curr] logger.error message_with_context %(maximum include depth of #{@maxdepth[:rel]} exceeded), source_location: cursor return end parsed_attrs = doc.parse_attributes attrlist, [], sub_input: true inc_path, target_type, relpath = resolve_include_path expanded_target, attrlist, parsed_attrs if target_type == :file reader = ::File.method :open read_mode = FILE_READ_MODE elsif target_type == :uri reader = ::OpenURI.method :open_uri read_mode = URI_READ_MODE else # NOTE if target_type is not set, inc_path is a boolean to skip over (false) or reevaluate (true) the current line return inc_path end inc_linenos = inc_tags = nil if attrlist if parsed_attrs.key? 'lines' inc_linenos = [] (split_delimited_value parsed_attrs['lines']).each do |linedef| if linedef.include? '..' from, _, to = linedef.partition '..' inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, 1.0/0.0] : (from.to_i..to).to_a else inc_linenos << linedef.to_i end end inc_linenos = inc_linenos.empty? ? nil : inc_linenos.sort.uniq elsif parsed_attrs.key? 'tag' unless (tag = parsed_attrs['tag']).empty? || tag == '!' inc_tags = (tag.start_with? '!') ? { (tag.slice 1, tag.length) => false } : { tag => true } end elsif parsed_attrs.key? 'tags' inc_tags = {} (split_delimited_value parsed_attrs['tags']).each do |tagdef| if tagdef.start_with? '!' inc_tags[tagdef.slice 1, tagdef.length] = false else inc_tags[tagdef] = true end unless tagdef.empty? || tagdef == '!' end inc_tags = nil if inc_tags.empty? end end if inc_linenos inc_lines, inc_offset, inc_lineno = [], nil, 0 begin reader.call inc_path, read_mode do |f| select_remaining = nil f.each_line do |l| inc_lineno += 1 if select_remaining || (::Float === (select = inc_linenos[0]) && (select_remaining = select.infinite?)) # NOTE record line where we started selecting inc_offset ||= inc_lineno inc_lines << l else if select == inc_lineno # NOTE record line where we started selecting inc_offset ||= inc_lineno inc_lines << l inc_linenos.shift end break if inc_linenos.empty? end end end rescue logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), source_location: cursor return replace_next_line %(Unresolved directive in #{@path} - include::#{expanded_target}[#{attrlist}]) end shift # FIXME not accounting for skipped lines in reader line numbering if inc_offset parsed_attrs['partial-option'] = '' push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs end elsif inc_tags inc_lines, inc_offset, inc_lineno, tag_stack, tags_used, active_tag = [], nil, 0, [], ::Set.new, nil if inc_tags.key? '**' if inc_tags.key? '*' select = base_select = inc_tags.delete '**' wildcard = inc_tags.delete '*' else select = base_select = wildcard = inc_tags.delete '**' end else select = base_select = !(inc_tags.value? true) wildcard = inc_tags.delete '*' end begin reader.call inc_path, read_mode do |f| dbl_co, dbl_sb = '::', '[]' f.each_line do |l| inc_lineno += 1 if (l.include? dbl_co) && (l.include? dbl_sb) && TagDirectiveRx =~ l this_tag = $2 if $1 # end tag if this_tag == active_tag tag_stack.pop active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1] elsif inc_tags.key? this_tag include_cursor = create_include_cursor inc_path, expanded_target, inc_lineno if (idx = tag_stack.rindex {|key, _| key == this_tag }) idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx) logger.warn message_with_context %(mismatched end tag (expected '#{active_tag}' but found '#{this_tag}') at line #{inc_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: include_cursor else logger.warn message_with_context %(unexpected end tag '#{this_tag}' at line #{inc_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: include_cursor end end elsif inc_tags.key? this_tag tags_used << this_tag # QUESTION should we prevent tag from being selected when enclosing tag is excluded? tag_stack << [(active_tag = this_tag), (select = inc_tags[this_tag]), inc_lineno] elsif !wildcard.nil? select = active_tag && !select ? false : wildcard tag_stack << [(active_tag = this_tag), select, inc_lineno] end elsif select # NOTE record the line where we started selecting inc_offset ||= inc_lineno inc_lines << l end end end rescue logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), source_location: cursor return replace_next_line %(Unresolved directive in #{@path} - include::#{expanded_target}[#{attrlist}]) end unless tag_stack.empty? tag_stack.each do |tag_name, _, tag_lineno| 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) end end unless (missing_tags = inc_tags.keys - tags_used.to_a).empty? 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 end shift if inc_offset parsed_attrs['partial-option'] = '' unless base_select && wildcard && inc_tags.empty? # FIXME not accounting for skipped lines in reader line numbering push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs end else begin # NOTE read content before shift so cursor is only advanced if IO operation succeeds inc_content = reader.call(inc_path, read_mode) {|f| f.read } shift push_include inc_content, inc_path, relpath, 1, parsed_attrs rescue logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), source_location: cursor return replace_next_line %(Unresolved directive in #{@path} - include::#{expanded_target}[#{attrlist}]) end end true end end