pax_global_header00006660000000000000000000000064142207136760014522gustar00rootroot0000000000000052 comment=e5ab21fba4462be842efb87a3b99b9124445b0b7 jirutka-asciidoctor-include-ext-62206d9/000077500000000000000000000000001422071367600201715ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/.editorconfig000066400000000000000000000002551422071367600226500ustar00rootroot00000000000000; http://editorconfig.org root = true [*] charset = utf-8 end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true jirutka-asciidoctor-include-ext-62206d9/.github/000077500000000000000000000000001422071367600215315ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/.github/workflows/000077500000000000000000000000001422071367600235665ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/.github/workflows/ci.yml000066400000000000000000000035351422071367600247120ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: test: name: Test with Asciidoctor ${{ matrix.asciidoctor }} on Ruby ${{ matrix.ruby }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - '3.1' - '3.0' - '2.7' - '2.6' - head - jruby-head asciidoctor: [2.0.17] include: - { asciidoctor: 2.0.10 , ruby: 3.0 } - { asciidoctor: 2.0.0 , ruby: 2.6 } - { asciidoctor: 1.5.8 , ruby: 2.6 } - { asciidoctor: 1.5.7.1 , ruby: 2.6 } - { asciidoctor: 1.5.7 , ruby: 2.6 } - { asciidoctor: 1.5.6.2 , ruby: 2.6 } - { asciidoctor: 'git:main', ruby: 3.1 } env: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} ASCIIDOCTOR_VERSION: ${{ matrix.asciidoctor }} continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.asciidoctor == 'git:main' }} steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle exec rake spec - run: bundle exec rake rubocop if: matrix.ruby < 3.0 # FIXME: update rubocop publish: name: Publish to RubyGems needs: [test] if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.0 bundler-cache: true - name: Publish to RubyGems run: | install -D -m 0600 /dev/null $HOME/.gem/credentials printf -- '---\n:rubygems_api_key: %s\n' "$RUBYGEMS_API_KEY" > $HOME/.gem/credentials gem build *.gemspec gem push *.gem env: RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} jirutka-asciidoctor-include-ext-62206d9/.gitignore000066400000000000000000000001151422071367600221560ustar00rootroot00000000000000/.bundle/ /.yardoc/ /coverage/ /doc/ /pkg/ /tmp/ Gemfile.lock *.bundle *.gem jirutka-asciidoctor-include-ext-62206d9/.rubocop.yml000066400000000000000000000026631422071367600224520ustar00rootroot00000000000000AllCops: TargetRubyVersion: 2.3 # False positive... Bundler/DuplicatedGem: Enabled: false # I prefer no empty line between magic comment and requires. Layout/EmptyLineAfterMagicComment: Enabled: false Layout/EmptyLinesAroundClassBody: Enabled: false # I prefer to put two spaces before inline comment, Rubocop does not allow to # set exception for this. Layout/ExtraSpacing: Enabled: false Layout/MultilineMethodCallIndentation: EnforcedStyle: indented Lint/EndAlignment: EnforcedStyleAlignWith: variable Metrics/AbcSize: Max: 30 Metrics/CyclomaticComplexity: Max: 12 Metrics/LineLength: Max: 99 Metrics/MethodLength: Max: 35 Metrics/PerceivedComplexity: Max: 12 Metrics/ParameterLists: CountKeywordArgs: false Naming/FileName: Exclude: - 'lib/asciidoctor-include-ext.rb' Style/BlockDelimiters: EnforcedStyle: braces_for_chaining Style/ClassAndModuleChildren: Enabled: false Style/EmptyMethod: EnforcedStyle: expanded # I don't use guard clause when line would be too long. Style/GuardClause: Enabled: false Style/HashSyntax: Exclude: - Rakefile Style/FrozenStringLiteralComment: Exclude: - Gemfile* - Rakefile - '*.gemspec' Style/MultilineBlockChain: Enabled: false Style/NegatedIf: Enabled: false Style/RedundantFreeze: Enabled: false Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma Style/TrailingCommaInLiteral: EnforcedStyleForMultiline: comma jirutka-asciidoctor-include-ext-62206d9/.yardopts000066400000000000000000000001501422071367600220330ustar00rootroot00000000000000--title "Asciidoctor Rouge Documentation" --default-return void --protected --markup markdown - LICENSE jirutka-asciidoctor-include-ext-62206d9/Gemfile000066400000000000000000000004431422071367600214650ustar00rootroot00000000000000source 'https://rubygems.org' gemspec unless ENV.fetch('ASCIIDOCTOR_VERSION', '').empty? if (match = ENV['ASCIIDOCTOR_VERSION'].match(/^git:(\w+)/)) gem 'asciidoctor', github: 'asciidoctor/asciidoctor', ref: match[1] else gem 'asciidoctor', ENV['ASCIIDOCTOR_VERSION'] end end jirutka-asciidoctor-include-ext-62206d9/LICENSE000066400000000000000000000021121422071367600211720ustar00rootroot00000000000000The MIT License Copyright 2017-present Jakub Jirutka . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jirutka-asciidoctor-include-ext-62206d9/README.adoc000066400000000000000000000071041422071367600217600ustar00rootroot00000000000000= Asciidoctor Include Extension :source-language: shell // custom :gem-name: asciidoctor-include-ext :gh-name: jirutka/{gem-name} :gh-branch: master :codacy-id: 45320444129044688ef6553821b083f1 ifdef::env-github[] image:https://github.com/{gh-name}/workflows/CI/badge.svg[CI Status, link=https://github.com/{gh-name}/actions?query=workflow%3A%22CI%22] image:https://api.codacy.com/project/badge/Coverage/{codacy-id}["Test Coverage", link="https://www.codacy.com/app/{gh-name}"] image:https://api.codacy.com/project/badge/Grade/{codacy-id}["Codacy Code quality", link="https://www.codacy.com/app/{gh-name}"] image:https://img.shields.io/gem/v/{gem-name}.svg?style=flat[Gem Version, link="https://rubygems.org/gems/{gem-name}"] image:https://img.shields.io/badge/yard-docs-blue.svg[Yard Docs, link="http://www.rubydoc.info/github/{gh-name}/{gh-branch}"] endif::env-github[] This project is a reimplementation of the http://asciidoctor.org[Asciidoctor]’s built-in (pre)processor for the http://asciidoctor.org/docs/user-manual/#include-directive[include::[\]] directive in extensible and more clean way. It provides the same features, but you can easily adjust it or extend for your needs. For example, you can change how it loads included files or add another ways how to select portions of the document to include. == Why? You may ask why I _reimplemented_ something that is already in the Asciidoctor core. Well… Code for decision if the include is allowed, parsing attributes for partial selection, reading the file to be included, filtering its content according to `lines` or `tags` attribute, handling errors… all of this is implemented directly in a single 210 lines long method https://github.com/asciidoctor/asciidoctor/blob/911d0bd509f369e9da15d2bb71f81aecb7c45fec/lib/asciidoctor/reader.rb#L824-L1034[Asciidoctor::Reader#preprocess_include_directive] with really horrible perl-like spaghetti code. :spaghetti: :hankey: How can you adjust it or reuse outside of the Asciidoctor codebase? For example, what if you can’t read documents directly from file system? Then you’re out of luck. There’s no way how to do that without reimplementing this whole mess on your own (monkey-patching `Kernel.open` and `File.file?` is not a sensible option…). I wrote this extension to allow implementing a complete support of `include::[]` directive in GitLab. And also to open doors for adding some custom _selectors_, e.g. selecting lines using regular expression in addition to ranges of line numbers and tags. == Installation To install (or update to the latest version): [source, subs="+attributes"] gem install {gem-name} or to install the latest development version: [source, subs="+attributes"] gem install {gem-name} --pre WARNING: Versions *prior 0.4.0* are vulnerable for Command Injection (see https://github.com/{gh-name}/commit/c7ea001a597c7033575342c51483dab7b87ae155[c7ea001] for more information). If you use an older version, update to 0.4.0 immediately! == Usage Just `require '{gem-name}'`. If you invoke Asciidoctor from command-line, use option `-r` to load the extension: [source, subs="+attributes"] asciidoctor -r {gem-name} README.adoc If you don’t want the extension to be automatically registered in Asciidoctor, don’t _require_ `{gem-name}`, but `asciidoctor/include_ext/include_processor`. IMPORTANT: Bundler automatically _requires_ all the specified gems. To prevent it, use `gem '{gem-name}', require: false`. == License This project is licensed under http://opensource.org/licenses/MIT/[MIT License]. For the full text of the license, see the link:LICENSE[LICENSE] file. jirutka-asciidoctor-include-ext-62206d9/Rakefile000066400000000000000000000007031422071367600216360ustar00rootroot00000000000000require 'bundler/gem_tasks' begin require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task :test => :spec task :default => :spec rescue LoadError => e warn "#{e.path} is not available" end begin require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) do |t| t.options = ['--display-cop-names', '--fail-level', 'W'] end task :default => :rubocop rescue LoadError => e warn "#{e.path} is not available" end jirutka-asciidoctor-include-ext-62206d9/asciidoctor-include-ext.gemspec000066400000000000000000000025021422071367600262570ustar00rootroot00000000000000require File.expand_path('lib/asciidoctor/include_ext/version', __dir__) Gem::Specification.new do |s| s.name = 'asciidoctor-include-ext' s.version = Asciidoctor::IncludeExt::VERSION s.author = 'Jakub Jirutka' s.email = 'jakub@jirutka.cz' s.homepage = 'https://github.com/jirutka/asciidoctor-include-ext' s.license = 'MIT' s.summary = "Asciidoctor's standard include::[] processor reimplemented as an extension" s.description = <<~EOF This is a reimplementation of the Asciidoctor's built-in (pre)processor for the include::[] directive in extensible and more clean way. It provides the same features, but you can easily adjust it or extend for your needs. For example, you can change how it loads included files or add another ways how to select portions of the document to include. EOF s.files = Dir['lib/**/*', '*.gemspec', 'LICENSE*', 'README*'] s.required_ruby_version = '>= 2.3' s.add_runtime_dependency 'asciidoctor', '>= 1.5.6', '< 3.0.0' s.add_development_dependency 'kramdown', '~> 2.0' s.add_development_dependency 'rake', '~> 13.0' s.add_development_dependency 'rspec', '~> 3.7' s.add_development_dependency 'rubocop', '~> 0.51.0' s.add_development_dependency 'simplecov', '~> 0.15' s.add_development_dependency 'yard', '~> 0.9' end jirutka-asciidoctor-include-ext-62206d9/lib/000077500000000000000000000000001422071367600207375ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor-include-ext.rb000066400000000000000000000001001422071367600257750ustar00rootroot00000000000000# frozen_string_literal: true require 'asciidoctor/include_ext' jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/000077500000000000000000000000001422071367600232425ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext.rb000066400000000000000000000003321422071367600260700ustar00rootroot00000000000000# frozen_string_literal: true require 'asciidoctor/extensions' require 'asciidoctor/include_ext/include_processor' Asciidoctor::Extensions.register do include_processor Asciidoctor::IncludeExt::IncludeProcessor end jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext/000077500000000000000000000000001422071367600255455ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext/include_processor.rb000066400000000000000000000132161422071367600316170ustar00rootroot00000000000000# frozen_string_literal: true require 'logger' require 'open-uri' require 'uri' require 'asciidoctor/include_ext/version' require 'asciidoctor/include_ext/reader_ext' require 'asciidoctor/include_ext/lineno_lines_selector' require 'asciidoctor/include_ext/logging' require 'asciidoctor/include_ext/tag_lines_selector' require 'asciidoctor' require 'asciidoctor/extensions' module Asciidoctor::IncludeExt # Asciidoctor preprocessor for processing `include::[]` directives # in the source document. # # @see http://asciidoctor.org/docs/user-manual/#include-directive class IncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor # @param selectors [Array] an array of selectors that can filter # specified portions of the document to include # (see ). # @param logger [Logger] the logger to use for logging warning and errors # from this object and selectors. def initialize(selectors: [LinenoLinesSelector, TagLinesSelector], logger: Logging.default_logger, **) super @selectors = selectors.dup.freeze @logger = logger end # @param reader [Asciidoctor::Reader] # @param target [String] name of the source file to include as specified # in the target slot of the `include::[]` directive. # @param attributes [Hash] parsed attributes of the # `include::[]` directive. def process(_, reader, target, attributes) unless include_allowed? target, reader reader.unshift_line("link:#{target}[]") return end if (max_depth = reader.exceeded_max_depth?) logger.error "#{reader.line_info}: maximum include depth of #{max_depth} exceeded" return end unless (path = resolve_target_path(target, reader)) if attributes.key? 'optional-option' reader.shift else logger.error "#{reader.line_info}: include target not found: #{target}" unresolved_include!(target, reader) end return end selector = lines_selector_for(target, attributes) begin lines = read_lines(path, selector) rescue => e # rubocop:disable RescueWithoutErrorClass logger.error "#{reader.line_info}: failed to read include file: #{path}: #{e}" unresolved_include!(target, reader) return end if selector && selector.respond_to?(:first_included_lineno) incl_offset = selector.first_included_lineno end unless lines.empty? reader.push_include(lines, path, target, incl_offset || 1, attributes) end end protected attr_reader :logger # @param target (see #process) # @param reader (see #process) # @return [Boolean] `true` if it's allowed to include the *target*, # `false` otherwise. def include_allowed?(target, reader) doc = reader.document return false if doc.safe >= ::Asciidoctor::SafeMode::SECURE return false if doc.attributes.fetch('max-include-depth', 64).to_i < 1 return false if target_http?(target) && !doc.attributes.key?('allow-uri-read') true end # @param target (see #process) # @param reader (see #process) # @return [String, nil] file path or URI of the *target*, or `nil` if not found. def resolve_target_path(target, reader) return target if target_http? target # Include file is resolved relative to dir of the current include, # or base_dir if within original docfile. path = reader.document.normalize_system_path(target, reader.dir, nil, target_name: 'include file') path if ::File.file?(path) end # Reads the specified file as individual lines, filters them using the # *selector* (if provided) and returns those lines in an array. # # @param path [String] URL or path of the file to be read. # @param selector [#to_proc, nil] predicate to filter lines that should be # included in the output. It must accept two arguments: line and # the line number. If `nil` is given, all lines are passed. # @return [Array] an array of read lines. def read_lines(path, selector) # IO.open is deliberately not used directly to avoid potential security risks. # TODO: Get rid of 'open-uri' (URI.open). io = target_http?(path) ? URI : File io.open(path) do |f| if selector f.each.select.with_index(1, &selector) else f.read end end end # Finds and initializes a lines selector that can handle the specified include. # # @param target (see #process) # @param attributes (see #process) # @return [#to_proc, nil] an instance of lines selector, or `nil` if not found. def lines_selector_for(target, attributes) if (klass = @selectors.find { |s| s.handles? target, attributes }) klass.new(target, attributes, logger: logger) end end # Replaces the include directive in ouput with a notice that it has not # been resolved. # # @param target (see #process) # @param reader (see #process) def unresolved_include!(target, reader) reader.unshift_line("Unresolved directive in #{reader.path} - include::#{target}[]") end private # @param target (see #process) # @return [Boolean] `true` if the *target* is a valid HTTP(S) URI, `false` otherwise. def target_http?(target) # First do a fast test, then try to parse it. target.downcase.start_with?('http://', 'https://') \ && URI.parse(target).is_a?(URI::HTTP) rescue URI::InvalidURIError false end end end jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext/lineno_lines_selector.rb000066400000000000000000000053451422071367600324570ustar00rootroot00000000000000# frozen_string_literal: true require 'asciidoctor/include_ext/version' module Asciidoctor::IncludeExt # Lines selector that selects lines of the content to be included based on # the specified ranges of line numbers. # # @note Instance of this class can be used only once, as a predicate to # filter a single include directive. # # @example # include::some-file.adoc[lines=1;3..4;6..-1] # # @example # selector = LinenoLinesSelector.new("some-file.adoc", {"lines" => "1;3..4;6..-1"}) # IO.foreach(filename).select.with_index(1, &selector) # # @see http://asciidoctor.org/docs/user-manual#by-line-ranges class LinenoLinesSelector # @return [Integer, nil] 1-based line number of the first included line, # or `nil` if none. attr_reader :first_included_lineno # @param attributes [Hash] the attributes parsed from the # `include::[]`s attributes slot. # @return [Boolean] `true` if the *attributes* hash contains a key `"lines"`. def self.handles?(_, attributes) attributes.key? 'lines' end # @param attributes [Hash] the attributes parsed from the # `include::[]`s attributes slot. It must contain a key `"lines"`. def initialize(_, attributes, **) @ranges = parse_attribute(attributes['lines']) @first_included_lineno = @ranges.last.first unless @ranges.empty? end # Returns `true` if the given line should be included, `false` otherwise. # # @note This method modifies state of this object. It's supposed to be # called successively with each line of the content being included. # See {LinenoLinesSelector example}. # # @param line_num [Integer] 1-based *line* number. # @return [Boolean] `true` to select the *line*, or `false` to reject. def include?(_, line_num) return false if @ranges.empty? ranges = @ranges ranges.pop while !ranges.empty? && ranges.last.last < line_num ranges.last.cover?(line_num) if !ranges.empty? end # @return [Proc] {#include?} method as a Proc. def to_proc method(:include?).to_proc end protected # @param lines_def [String] a comma or semicolon separated numbers and # and ranges (e.g. `1..2`) specifying lines to be selected, or rejected # if prefixed with "!". # @return [Array] an array of ranges sorted by the range begin in # _descending_ order. def parse_attribute(lines_def) lines_def .split(/[,;]/) .map! { |line_def| from, to = line_def.split('..', 2).map(&:to_i) to ||= from to = ::Float::INFINITY if to == -1 (from..to) }.sort! do |a, b| b.first <=> a.first end end end end jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext/logging.rb000066400000000000000000000011151422071367600275160ustar00rootroot00000000000000# frozen_string_literal: true require 'logger' require 'asciidoctor' require 'asciidoctor/include_ext/version' module Asciidoctor::IncludeExt # Helper module for getting default Logger based on the Asciidoctor version. module Logging module_function # @return [Logger] the default `Asciidoctor::Logger` if using Asciidoctor # 1.5.7 or later, or Ruby's `Logger` that outputs to `STDERR`. def default_logger if defined? ::Asciidoctor::LoggerManager ::Asciidoctor::LoggerManager.logger else ::Logger.new(STDERR) end end end end jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext/reader_ext.rb000066400000000000000000000002231422071367600302110ustar00rootroot00000000000000# frozen_string_literal: true require 'asciidoctor' # Monkey-patch Reader to add #document. class Asciidoctor::Reader attr_reader :document end jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext/tag_lines_selector.rb000066400000000000000000000131621422071367600317420ustar00rootroot00000000000000# frozen_string_literal: true require 'logger' require 'set' require 'asciidoctor' require 'asciidoctor/include_ext/version' require 'asciidoctor/include_ext/logging' module Asciidoctor::IncludeExt # Lines selector that selects lines of the content based on the specified tags. # # @note Instance of this class can be used only once, as a predicate to # filter a single include directive. # # @example # include::some-file.adoc[tags=snippets;!snippet-b] # include::some-file.adoc[tag=snippets] # # @example # selector = TagLinesSelector.new("some-file.adoc", {"tag" => "snippets"}) # IO.foreach(filename).select.with_index(1, &selector) # # @see http://asciidoctor.org/docs/user-manual#by-tagged-regions class TagLinesSelector # @return [Integer, nil] 1-based line number of the first included line, # or `nil` if none. attr_reader :first_included_lineno # @param attributes [Hash] the attributes parsed from the # `include::[]`s attributes slot. # @return [Boolean] `true` if the *attributes* hash contains a key `"tag"` # or `"tags"`. def self.handles?(_, attributes) attributes.key?('tag') || attributes.key?('tags') end # @param target [String] name of the source file to include as specified # in the target slot of the `include::[]` directive. # @param attributes [Hash] the attributes parsed from the # `include::[]`s attributes slot. It must contain a key `"tag"` or `"tags"`. # @param logger [Logger] def initialize(target, attributes, logger: Logging.default_logger, **) tag_flags = if attributes.key? 'tag' parse_attribute(attributes['tag'], true) else parse_attribute(attributes['tags']) end wildcard = tag_flags.delete('*') if tag_flags.key? '**' default_state = tag_flags.delete('**') wildcard = default_state if wildcard.nil? else default_state = !tag_flags.value?(true) end # "immutable" @target = target @logger = logger @tag_flags = tag_flags.freeze @wildcard = wildcard @tag_directive_rx = /\b(?:tag|(end))::(\S+)\[\](?=$| )/.freeze # mutable (state variables) @stack = [[nil, default_state]] @state = default_state @used_tags = ::Set.new end # Returns `true` if the given line should be included, `false` otherwise. # # @note This method modifies state of this object. It's supposed to be # called successively with each line of the content being included. # See {TagLinesSelector example}. # # @param line [String] # @param line_num [Integer] 1-based *line* number. # @return [Boolean] `true` to select the *line*, `false` to reject. def include?(line, line_num) tag_type, tag_name = parse_tag_directive(line) case tag_type when :start enter_region!(tag_name, line_num) false when :end exit_region!(tag_name, line_num) false when nil if @state && @first_included_lineno.nil? @first_included_lineno = line_num end @state end end # @return [Proc] {#include?} method as a Proc. def to_proc method(:include?).to_proc end protected attr_reader :logger, :target # @return [String, nil] a name of the active tag (region), or `nil` if none. def active_tag @stack.last.first end # @param tag_name [String] # @param _line_num [Integer] def enter_region!(tag_name, _line_num) if @tag_flags.key? tag_name @used_tags << tag_name @state = @tag_flags[tag_name] @stack << [tag_name, @state] elsif !@wildcard.nil? @state = active_tag && !@state ? false : @wildcard @stack << [tag_name, @state] end end # @param tag_name [String] # @param line_num [Integer] def exit_region!(tag_name, line_num) # valid end tag if tag_name == active_tag @stack.pop @state = @stack.last[1] # mismatched/unexpected end tag elsif @tag_flags.key? tag_name log_prefix = "#{target}: line #{line_num}" if (idx = @stack.rindex { |key, _| key == tag_name }) @stack.delete_at(idx) logger.warn "#{log_prefix}: mismatched end tag include: expected #{active_tag}, found #{tag_name}" # rubocop:disable LineLength else logger.warn "#{log_prefix}: unexpected end tag in include: #{tag_name}" end end end # Parses `tag::[]` and `end::[]` in the given *line*. # # @param line [String] # @return [Array, nil] a tuple `[Symbol, String]` where the first item is # `:start` or `:end` and the second is a tag name. If no tag is matched, # then `nil` is returned. def parse_tag_directive(line) @tag_directive_rx.match(line) do |m| [m[1].nil? ? :start : :end, m[2]] end end # @param tags_def [String] a comma or semicolon separated names of tags to # be selected, or rejected if prefixed with "!". # @param single [Boolean] whether the *tags_def* should be parsed as # a single tag name (i.e. without splitting on comma/semicolon). # @return [Hash] a Hash with tag names as keys and boolean # flags as values. def parse_attribute(tags_def, single = false) atoms = single ? [tags_def] : tags_def.split(/[,;]/) atoms.each_with_object({}) do |atom, tags| if atom.start_with? '!' tags[atom[1..-1]] = false if atom != '!' elsif !atom.empty? tags[atom] = true end end end end end jirutka-asciidoctor-include-ext-62206d9/lib/asciidoctor/include_ext/version.rb000066400000000000000000000002371422071367600275610ustar00rootroot00000000000000# frozen_string_literal: true module Asciidoctor module IncludeExt # Version of the asciidoctor-include-ext gem. VERSION = '0.4.0'.freeze end end jirutka-asciidoctor-include-ext-62206d9/spec/000077500000000000000000000000001422071367600211235ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/spec/.rubocop.yml000066400000000000000000000017171422071367600234030ustar00rootroot00000000000000inherit_from: ../.rubocop.yml Layout/ClosingParenthesisIndentation: Enabled: false Layout/EmptyLines: Enabled: false Layout/EmptyLinesAroundBlockBody: Enabled: false Layout/LeadingCommentSpace: Enabled: false Layout/MultilineBlockLayout: Enabled: false Layout/SpaceBeforeComma: Enabled: false Layout/SpaceBeforeComment: Enabled: false Layout/SpaceInsideBlockBraces: Enabled: false Layout/SpaceInsideBrackets: Enabled: false Layout/SpaceInsideParens: Enabled: false Lint/AmbiguousRegexpLiteral: Enabled: false Metrics/ModuleLength: Enabled: false Metrics/BlockLength: Enabled: false Style/BlockDelimiters: IgnoredMethods: [ expect, let ] # I use it for purpose with Corefines, e.g. `using Corefines::String::unindent`. Style/ColonMethodCall: Enabled: false Style/FrozenStringLiteralComment: Enabled: false Style/NestedParenthesizedCalls: Enabled: false Style/SymbolArray: Enabled: false Style/WordArray: Enabled: false jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/000077500000000000000000000000001422071367600227745ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/a/000077500000000000000000000000001422071367600232145ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/a/b/000077500000000000000000000000001422071367600234355ustar00rootroot00000000000000jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/a/b/include-2b.adoc000066400000000000000000000001051422071367600262050ustar00rootroot00000000000000begin of include 2b include::../include-3.adoc[] end of include 2b jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/a/include-1.adoc000066400000000000000000000001421422071367600256220ustar00rootroot00000000000000begin of include 1 include::../include-2a.adoc[] include::b/include-2b.adoc[] end of include 1 jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/a/include-3.adoc000066400000000000000000000000121422071367600256200ustar00rootroot00000000000000include 3 jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/include-2a.adoc000066400000000000000000000000131422071367600255410ustar00rootroot00000000000000include 2a jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/include-file.adoc000066400000000000000000000006761422071367600261750ustar00rootroot00000000000000line 1 of included content line 2 of included content line 3 of included content line 4 of included content line 5 of included content line 6 of included content line 7 of included content line 8 of included content // tag::snippet[] // tag::snippet-a[] snippet-a content // end::snippet-a[] non-tagged content // tag::snippet-b[] snippet-b content // end::snippet-b[] // end::snippet[] more non-tagged content last line of included content jirutka-asciidoctor-include-ext-62206d9/spec/fixtures/include-file.ml000066400000000000000000000000751422071367600256700ustar00rootroot00000000000000(* tag::snippet[] *) let s = SS.empty;; (* end::snippet[] *) jirutka-asciidoctor-include-ext-62206d9/spec/integration_spec.rb000066400000000000000000000116741422071367600250160ustar00rootroot00000000000000require_relative 'spec_helper' require 'asciidoctor/include_ext/include_processor' require 'webrick' FIXTURES_DIR = File.expand_path('fixtures', __dir__) describe 'Integration tests' do subject(:output) { Asciidoctor.convert(input, options) } let(:input) { '' } # this is modified in #given let(:processor) { Asciidoctor::IncludeExt::IncludeProcessor.new } let(:options) { processor_ = processor { safe: :safe, header_footer: false, base_dir: FIXTURES_DIR, extensions: proc { include_processor processor_ }, } } before do # XXX: Ugly hack to get rid of rspec-mocks' warnings about resetting # frozen object; https://github.com/rspec/rspec-mocks/issues/1190. processor.define_singleton_method(:freeze) { self } # Make sure that Asciidoctor really calls our processor. expect(processor).to receive(:process).at_least(:once).and_call_original end describe 'include::[] directive' do it 'is replaced by a link when safe mode is default' do given 'include::include-file.adoc[]', safe: nil should match /]+href="include-file.adoc"/ should_not match /included content/ end it 'is resolved when safe mode is less than SECURE' do given 'include::include-file.adoc[]' should match /included content/ should_not match /]+href="include-file\.adoc"/ end it 'nested includes are resolved with relative paths' do given 'include::a/include-1.adoc[]' expect( output.scan(/[^>]*include \w+/) ).to eq [ 'begin of include 1', 'include 2a', 'begin of include 2b', 'include 3', 'end of include 2b', 'end of include 1' ] end it 'is replaced by a warning when target is not found' do given <<~ADOC include::no-such-file.adoc[] trailing content ADOC should match /unresolved/i should match /trailing content/ end it 'is skipped when target is not found and optional option is set' do given <<~ADOC include::no-such-file.adoc[opts=optional] trailing content ADOC should match /trailing content/ should_not match /unresolved/i end it 'is replaced by a link when target is an URI and attribute allow-uri-read is not set' do using_test_webserver do |host, port| target = "http://#{host}:#{port}/hello.json" given "include::#{target}[]" should match /]*href="#{target}"/ should_not match /\{"message": "Hello, world!"\}/ end end it 'retrieves content from URI target when allow-uri-read is set' do using_test_webserver do |host, port| given "include::http://#{host}:#{port}/hello.json[]", attributes: { 'allow-uri-read' => '' } should match /\{"message": "Hello, world!"\}/ should_not match /unresolved/i end end it 'supports line selection' do given 'include::include-file.adoc[lines=1;3..4;6..-1]' %w[1 3 4 6 7 8].each do |n| should match /line #{n} of included content/ end should match /last line/ should_not match /line 2/ should_not match /line 5/ end it 'supports tagged selection' do given 'include::include-file.adoc[tag=snippet-a]' should match /snippet-a content/ should_not match /snippet-b content/ should_not match /non-tagged content/ should_not match /included content/ end it 'supports multiple tagged selection' do given 'include::include-file.adoc[tags="snippet-a,snippet-b"]' should match /snippet-a content/ should match /snippet-b content/ should_not match /non-tagged content/ should_not match /included content/ end it 'supports tagged selection in language that uses circumfix comments' do given <<~ADOC [source, ml] ---- include::include-file.ml[tag=snippet] ---- ADOC should match /let s = SS.empty;;/ should_not match /(?:tag|end)::snippet\[\]/ end it 'does not allow execution of system command when allow-uri-read is set' do options.merge!(attributes: { 'allow-uri-read' => '' }) given <<~ADOC :app-name: |cat LICENSE # + \\ http://test.com include::{app-name}[] ADOC should match /unresolved/i should_not match /The MIT License/ end end #---------- Helpers ---------- def given(str, opts = {}) input.replace(str) options.merge!(opts) end def using_test_webserver started = false server = WEBrick::HTTPServer.new( BindAddress: '127.0.0.1', Port: 0, StartCallback: -> { started = true }, AccessLog: [], ) server.mount_proc '/hello.json' do |_, res| res.body = '{"message": "Hello, world!"}' end Thread.new { server.start } Timeout.timeout(1) { :wait until started } begin yield server.config[:BindAddress], server.config[:Port] ensure server.shutdown end end end jirutka-asciidoctor-include-ext-62206d9/spec/spec_helper.rb000066400000000000000000000003601422071367600237400ustar00rootroot00000000000000require 'rspec' RSpec.configure do |config| config.color = true end unless RUBY_ENGINE == 'jruby' require 'simplecov' SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter SimpleCov.start do add_filter '/spec/' end end