asciisourcerer 0.3.1 → 0.5.0
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/BILL_OF_MATERIALS.adoc +17 -0
- data/README.adoc +107 -26
- data/lib/sourcerer/_docs/partials/liquid-filters-by-kind.adoc +3227 -0
- data/lib/sourcerer/_docs/partials/liquid-filters-by-source.adoc +3155 -0
- data/lib/sourcerer/asciidoc.rb +52 -10
- data/lib/sourcerer/builder.rb +1 -1
- data/lib/sourcerer/jekyll/bootstrapper.rb +1 -1
- data/lib/sourcerer/jekyll/liquid/filters.rb +295 -0
- data/lib/sourcerer/jekyll.rb +7 -2
- data/lib/sourcerer/mark_down_grade.rb +321 -33
- data/lib/sourcerer/rendering.rb +10 -4
- data/lib/sourcerer/source_skim/config.rb +8 -2
- data/lib/sourcerer/source_skim/ruby_skimmer.rb +83 -0
- data/lib/sourcerer/source_skim/skimmer.rb +94 -1
- data/lib/sourcerer/source_skim.rb +11 -2
- data/lib/sourcerer/util/gem_uri.rb +34 -0
- data/lib/sourcerer/version.rb +1 -1
- data/lib/sourcerer.rb +0 -1
- data/specs/data/liquid-filters.yml +1314 -0
- metadata +22 -3
- data/specs/docs/frontmatter-reader_prd.adoc +0 -47
|
@@ -16,6 +16,23 @@ module Sourcerer
|
|
|
16
16
|
# raw text (i.e., were not resolved by the parser).
|
|
17
17
|
INCLUDE_DIRECTIVE_PATTERN = /include::[^\[]+\[[^\]]*\]/
|
|
18
18
|
|
|
19
|
+
# Matches a real (non-code-block) include directive at the start of a line,
|
|
20
|
+
# capturing the target path and its attribute list.
|
|
21
|
+
INCLUDE_LINE_PATTERN = /\A\s*include::([^\[]+)\[([^\]]*)\]\s*\z/
|
|
22
|
+
|
|
23
|
+
# Toggled by classic listing (----) / literal (....) block delimiters, so
|
|
24
|
+
# #detect_general_includes can skip include directives already captured
|
|
25
|
+
# (as literal example text) by the code_blocks/literal_blocks categories.
|
|
26
|
+
FENCE_DELIMITER_PATTERN = /\A(?:-{4,}|\.{4,})\s*\z/
|
|
27
|
+
|
|
28
|
+
# A document with a real title line can still end up with no `title`
|
|
29
|
+
# document attribute if a preceding, unresolved include directive
|
|
30
|
+
# disrupts Asciidoctor's header parsing (a known Asciidoctor quirk, not
|
|
31
|
+
# something this document did wrong). When that happens, Document#doctitle
|
|
32
|
+
# silently falls back to the first section's title. Detect that specific
|
|
33
|
+
# failure mode and recover the real title from the raw source instead.
|
|
34
|
+
RAW_TITLE_LINE_PATTERN = /\A=\s+(\S.*)\z/
|
|
35
|
+
|
|
19
36
|
def process document, config: Config.new
|
|
20
37
|
@config = config
|
|
21
38
|
@main_file = document.attr('docfile')
|
|
@@ -36,7 +53,7 @@ module Sourcerer
|
|
|
36
53
|
assign_line_ends(tree, doc_end)
|
|
37
54
|
|
|
38
55
|
result = {
|
|
39
|
-
title: document
|
|
56
|
+
title: doctitle_for(document),
|
|
40
57
|
lines: doc_end
|
|
41
58
|
}
|
|
42
59
|
|
|
@@ -61,12 +78,88 @@ module Sourcerer
|
|
|
61
78
|
result[:admonitions] = @admonitions if @config.include?(:admonitions)
|
|
62
79
|
result[:quotes] = @quotes if @config.include?(:quotes)
|
|
63
80
|
result[:images] = @images if @config.include?(:images)
|
|
81
|
+
result[:includes] = detect_general_includes(document) if @config.include?(:includes)
|
|
64
82
|
|
|
65
83
|
result
|
|
66
84
|
end
|
|
67
85
|
|
|
68
86
|
private
|
|
69
87
|
|
|
88
|
+
# Prefer Asciidoctor's own doctitle, but recover from the header-parsing
|
|
89
|
+
# failure mode described at RAW_TITLE_LINE_PATTERN: if the `title`
|
|
90
|
+
# attribute never got set (the real signal that header parsing broke,
|
|
91
|
+
# as opposed to a document that's genuinely untitled), look for an
|
|
92
|
+
# explicit `= Title` line in the raw source before trusting the
|
|
93
|
+
# first-section fallback.
|
|
94
|
+
def doctitle_for document
|
|
95
|
+
return document.doctitle if document.attr('title')
|
|
96
|
+
|
|
97
|
+
raw_title = raw_doctitle(document)
|
|
98
|
+
raw_title || document.doctitle
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def raw_doctitle document
|
|
102
|
+
lines = document.source_lines
|
|
103
|
+
return nil unless lines
|
|
104
|
+
|
|
105
|
+
lines.each do |line|
|
|
106
|
+
return ::Regexp.last_match(1) if line =~ RAW_TITLE_LINE_PATTERN
|
|
107
|
+
# A section heading appearing before any `=` title line means the
|
|
108
|
+
# document genuinely has no title; stop looking.
|
|
109
|
+
break if line =~ /\A==+\s+\S/
|
|
110
|
+
end
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Scan the raw source (outside of listing/literal delimited blocks, which
|
|
115
|
+
# are already covered by the code_blocks/literal_blocks `includes` field)
|
|
116
|
+
# for include directives, whether or not they were resolved by the parser.
|
|
117
|
+
def detect_general_includes document
|
|
118
|
+
lines = document.source_lines
|
|
119
|
+
return [] unless lines
|
|
120
|
+
|
|
121
|
+
offset = source_line_offset(document)
|
|
122
|
+
includes = []
|
|
123
|
+
in_fence = false
|
|
124
|
+
lines.each_with_index do |line, idx|
|
|
125
|
+
if line =~ FENCE_DELIMITER_PATTERN
|
|
126
|
+
in_fence = !in_fence
|
|
127
|
+
next
|
|
128
|
+
end
|
|
129
|
+
next if in_fence
|
|
130
|
+
|
|
131
|
+
next unless (m = line.match(INCLUDE_LINE_PATTERN))
|
|
132
|
+
|
|
133
|
+
entry = { target: m[1].strip, starts_at: idx + 1 + offset }
|
|
134
|
+
entry.merge!(parse_include_attrs(m[2]))
|
|
135
|
+
includes << entry
|
|
136
|
+
end
|
|
137
|
+
includes
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# document.source_lines reflects content *after* skip-front-matter
|
|
141
|
+
# stripping, while every other category's starts_at comes from
|
|
142
|
+
# Asciidoctor's own line-number tracking, which counts the stripped
|
|
143
|
+
# front matter lines too. Compute that offset so line numbers stay
|
|
144
|
+
# consistent across the whole skim.
|
|
145
|
+
def source_line_offset document
|
|
146
|
+
front_matter = document.attr('front-matter')
|
|
147
|
+
return 0 unless front_matter && !front_matter.empty?
|
|
148
|
+
|
|
149
|
+
front_matter.count("\n") + 1 + 2 # captured lines + both '---' delimiters
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def parse_include_attrs attrs_str
|
|
153
|
+
attrs = {}
|
|
154
|
+
if (m = attrs_str.match(/\btags?=("[^"]*"|'[^']*'|\S+)/))
|
|
155
|
+
attrs[:tags] = m[1].delete('"\'')
|
|
156
|
+
end
|
|
157
|
+
if (m = attrs_str.match(/\bleveloffset=("[^"]*"|'[^']*'|\S+)/))
|
|
158
|
+
attrs[:leveloffset] = m[1].delete('"\'')
|
|
159
|
+
end
|
|
160
|
+
attrs
|
|
161
|
+
end
|
|
162
|
+
|
|
70
163
|
def line_count_for file_path
|
|
71
164
|
return nil unless file_path && File.exist?(file_path)
|
|
72
165
|
|
|
@@ -6,6 +6,7 @@ require_relative 'yaml_frontmatter'
|
|
|
6
6
|
require_relative 'source_skim/config'
|
|
7
7
|
require_relative 'source_skim/skimmer'
|
|
8
8
|
require_relative 'source_skim/markdown_skimmer'
|
|
9
|
+
require_relative 'source_skim/ruby_skimmer'
|
|
9
10
|
|
|
10
11
|
module Sourcerer
|
|
11
12
|
# SourceSkim produces machine-oriented skims of markup source documents.
|
|
@@ -57,11 +58,15 @@ module Sourcerer
|
|
|
57
58
|
# @param attributes [Hash{String => String}] AsciiDoc only. Asciidoctor
|
|
58
59
|
# attribute overrides. Silently ignored for Markdown.
|
|
59
60
|
# @return [Hash] JSON-ready skim
|
|
60
|
-
def self.skim_file file_path, forms: nil, format: nil, categories: nil, attributes: {}
|
|
61
|
+
def self.skim_file file_path, forms: nil, format: nil, categories: nil, attributes: {}, descriptions: false
|
|
61
62
|
fmt = format || detect_format(file_path)
|
|
62
63
|
if fmt == :markdown
|
|
63
64
|
config = Config.new(forms: forms || [:flat])
|
|
64
65
|
MarkdownSkimmer.new.process(File.read(file_path), config: config)
|
|
66
|
+
elsif fmt == :ruby
|
|
67
|
+
RubySkimmer.new.process(
|
|
68
|
+
File.read(file_path),
|
|
69
|
+
config: Config.new(forms: forms || [:flat], descriptions: descriptions))
|
|
65
70
|
else
|
|
66
71
|
attrs = LOAD_OPTS[:attributes].merge(attributes)
|
|
67
72
|
opts = LOAD_OPTS.merge(attributes: attrs)
|
|
@@ -82,10 +87,12 @@ module Sourcerer
|
|
|
82
87
|
# @param categories [Array<Symbol>, nil] AsciiDoc only
|
|
83
88
|
# @param attributes [Hash{String => String}] AsciiDoc only
|
|
84
89
|
# @return [Hash] JSON-ready skim
|
|
85
|
-
def self.skim_string content, format: :asciidoc, forms: nil, categories: nil, attributes: {}
|
|
90
|
+
def self.skim_string content, format: :asciidoc, forms: nil, categories: nil, attributes: {}, descriptions: false
|
|
86
91
|
if format == :markdown
|
|
87
92
|
config = Config.new(forms: forms || [:flat])
|
|
88
93
|
MarkdownSkimmer.new.process(content, config: config)
|
|
94
|
+
elsif format == :ruby
|
|
95
|
+
RubySkimmer.new.process(content, config: Config.new(forms: forms || [:flat], descriptions: descriptions))
|
|
89
96
|
else
|
|
90
97
|
attrs = LOAD_OPTS[:attributes].merge(attributes)
|
|
91
98
|
opts = LOAD_OPTS.merge(attributes: attrs)
|
|
@@ -113,6 +120,8 @@ module Sourcerer
|
|
|
113
120
|
ext = File.extname(file_path).downcase
|
|
114
121
|
if Sourcerer::MARKDOWN_EXTS.include?(ext)
|
|
115
122
|
:markdown
|
|
123
|
+
elsif ext == '.rb'
|
|
124
|
+
:ruby
|
|
116
125
|
else
|
|
117
126
|
:asciidoc
|
|
118
127
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sourcerer
|
|
4
|
+
module Util
|
|
5
|
+
# Resolve <tt>gem://</tt> URIs to absolute filesystem paths.
|
|
6
|
+
#
|
|
7
|
+
# Not required internally; callers must require this file explicitly.
|
|
8
|
+
module GemUri
|
|
9
|
+
GEM_URI_PATTERN = %r{\Agem://([^/]+)/(.+)\z}
|
|
10
|
+
|
|
11
|
+
# Resolve a +gem://+ URI to an absolute path within the named gem's directory.
|
|
12
|
+
# Returns +path+ unchanged if it is not a +gem://+ URI.
|
|
13
|
+
#
|
|
14
|
+
# URI format: <tt>gem://<gem-name>/<path-within-gem></tt>
|
|
15
|
+
# Example: <tt>gem://schemagraphy/lib/schemagraphy/cfgyml/templates/foo.liquid</tt>
|
|
16
|
+
#
|
|
17
|
+
# @param path [String] A gem:// URI or any other string.
|
|
18
|
+
# @return [String] The resolved absolute path, or the original string if not a gem:// URI.
|
|
19
|
+
# @raise [ArgumentError] If the gem:// URI is malformed.
|
|
20
|
+
# @raise [LoadError] If the referenced gem is not loaded.
|
|
21
|
+
def self.resolve path
|
|
22
|
+
return path unless path.is_a?(String) && path.start_with?('gem://')
|
|
23
|
+
|
|
24
|
+
match = GEM_URI_PATTERN.match(path)
|
|
25
|
+
raise ArgumentError, "Invalid gem:// URI: #{path}" unless match
|
|
26
|
+
|
|
27
|
+
spec = Gem.loaded_specs[match[1]]
|
|
28
|
+
raise LoadError, "Gem '#{match[1]}' not loaded (referenced in gem:// URI: #{path})" unless spec
|
|
29
|
+
|
|
30
|
+
File.join(spec.gem_dir, match[2])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/sourcerer/version.rb
CHANGED
data/lib/sourcerer.rb
CHANGED
|
@@ -23,7 +23,6 @@ module Sourcerer
|
|
|
23
23
|
# File extensions recognised as AsciiDoc source files.
|
|
24
24
|
ASCIIDOC_EXTS = %w[.adoc .asciidoc .asc .ad].freeze
|
|
25
25
|
|
|
26
|
-
autoload :AttributesFilter, 'sourcerer/attributes_filter'
|
|
27
26
|
autoload :YamlFrontmatter, 'sourcerer/yaml_frontmatter'
|
|
28
27
|
autoload :Jekyll, 'sourcerer/jekyll'
|
|
29
28
|
autoload :MarkDownGrade, 'sourcerer/mark_down_grade'
|