aspec_rb 0.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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/README.md +40 -0
- data/Rakefile +13 -0
- data/aspec_rb.gemspec +34 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/lib/aspec_rb/version.rb +6 -0
- data/lib/aspec_rb.rb +14 -0
- data/lib/extensions/autoxrefs.rb +183 -0
- data/lib/extensions/definition_block.rb +49 -0
- data/lib/extensions/inline_callout_macro.rb +14 -0
- data/lib/extensions/inline_cwiki_macro.rb +31 -0
- data/lib/extensions/inline_repo_macro.rb +96 -0
- data/lib/extensions/inline_task_macro.rb +47 -0
- data/lib/extensions/postprocessor.rb +18 -0
- data/lib/extensions/req_refs.rb +191 -0
- data/lib/extensions/requirement_appendix.rb +108 -0
- data/lib/extensions/requirement_block.rb +50 -0
- data/lib/extensions/todo_block.rb +16 -0
- data/lib/extensions/utils/block.rb +36 -0
- data/lib/extensions/utils/labels.rb +18 -0
- data/lib/extensions/utils/scanner.rb +20 -0
- data/lib/html_chunker.rb +107 -0
- data/lib/postprocessors/fulltext_search.rb +48 -0
- data/lib/postprocessors/generate_toc.rb +104 -0
- metadata +133 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'asciidoctor/extensions'
|
2
|
+
require_relative 'utils/scanner'
|
3
|
+
|
4
|
+
# test using ruby benchmark
|
5
|
+
|
6
|
+
include ::Asciidoctor
|
7
|
+
|
8
|
+
# Retrieve $srcdir from config file:
|
9
|
+
$srcdir = 'chapters'
|
10
|
+
$reqs = []
|
11
|
+
$doclinks = []
|
12
|
+
|
13
|
+
adoc_files = Dir.glob("#{$srcdir}/**/*.adoc")
|
14
|
+
|
15
|
+
# Find a nicer way to initialize some arrays:
|
16
|
+
ssincs, ni_includes, includes, doclinksfix, docs, titles, mainchaps, indexincludes, xrefs = Array.new(10) { [] }
|
17
|
+
$reqfixes = []
|
18
|
+
|
19
|
+
# From the index, create an array of the main chapters
|
20
|
+
File.read('index.adoc').each_line do |li|
|
21
|
+
if li[IncludeDirectiveRx]
|
22
|
+
doc = li.match(/(?<=^include::).+?\.adoc(?=\[\])/).to_s
|
23
|
+
doc = doc.sub(/^\{find\}/, '')
|
24
|
+
indexincludes.push(doc) unless doc == 'config'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
adoc_files.sort!
|
29
|
+
adoc_files.each do |filename|
|
30
|
+
main = false
|
31
|
+
chapter = Sform.trim(filename).match(/.+?(?=\/)/).to_s
|
32
|
+
|
33
|
+
# Add a switch if the current document is an include within the index.adoc
|
34
|
+
indexincludes.each do |inc|
|
35
|
+
main = true if inc == filename
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create an array of chapters and contained docs
|
39
|
+
docs.push([filename.sub(/^#{$srcdir}\//, ''), chapter])
|
40
|
+
|
41
|
+
# Main iterator to check for requirements, anchors and xrefs line-by-line
|
42
|
+
File.read(filename).each_line do |li|
|
43
|
+
# Match Requirement Blocks "[req,ABC-123,version=n]"
|
44
|
+
if li[/\[\s*req\s*,\s*id\s*=\s*(\w+-?[0-9]+)\s*,.*/]
|
45
|
+
rid = li.chop.match(/id\s*=\s*(\w+-?[0-9]+)/i).captures[0]
|
46
|
+
path = filename.sub(/^#{$srcdir}\//, '')
|
47
|
+
$reqs.push([rid, li.chop, Sform.trim(path), filename, main, chapter])
|
48
|
+
|
49
|
+
# Match block and section titles
|
50
|
+
elsif li[/^(\=+\s+?\S+.+)/]
|
51
|
+
# Keep track of level 1 sections (Document Titles)
|
52
|
+
h1 = true if li[/^=\s+?\S+.+/]
|
53
|
+
title = li.chop.match(/(?!=+\s)(\S+.+?)$/i).captures[0]
|
54
|
+
title.sub!(/\.(?=\w+?)/, '') if title[/\.(?=\w+?)/]
|
55
|
+
title = title.strip
|
56
|
+
titles.push([title, Sform.trim(filename), filename, Sform.underscorify(title).strip, chapter, main, h1])
|
57
|
+
|
58
|
+
# Match for sub includes
|
59
|
+
elsif li[IncludeDirectiveRx]
|
60
|
+
child = li.match(/(?<=^include::).+?\.adoc(?=\[\])/).to_s
|
61
|
+
child = child.sub(/^\{find\}/, '')
|
62
|
+
childpath = "#{filename.sub(/[^\/]+?\.adoc/, '')}#{child}"
|
63
|
+
includes.push([filename, child, childpath])
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# TODO: - remove unused items in array (prefixed with undserscore)
|
70
|
+
# For each main include, store
|
71
|
+
titles.each do |_anchor, full, _filename, text, chapter, main, h1|
|
72
|
+
next unless main
|
73
|
+
doc = full.gsub(/^#{chapter}\//, '')
|
74
|
+
mainchaps.push([chapter, doc, text, h1])
|
75
|
+
end
|
76
|
+
|
77
|
+
# H1 title is used by html-chunker.rb to generate
|
78
|
+
# a target document name, e.g. for "= Document Title" the generated chapter
|
79
|
+
# will have a filename of "_document_title.html", all requirements
|
80
|
+
# within this chapter and its includes need to point to this H1
|
81
|
+
# NOTE: this will fail if the h1 title is overridden, this is resolved
|
82
|
+
# by the next block
|
83
|
+
mainchaps.each do |mchapter, doc, link, h1|
|
84
|
+
next unless h1
|
85
|
+
$doclinks.push([doc, link, mchapter])
|
86
|
+
end
|
87
|
+
|
88
|
+
# For edge cases - slurp the first 10 lines of each top level document and
|
89
|
+
# search for an overridden H1 - the previous line will be an anchor
|
90
|
+
$doclinks.delete_if do |doc, _link, mchapter|
|
91
|
+
topleveldoc = "#{$srcdir}/#{mchapter}/#{doc}.adoc"
|
92
|
+
lines = File.foreach(topleveldoc).first(10).join
|
93
|
+
next unless lines[/(?x)\[\[.+?\]\]\n=\s{1,}.+$/]
|
94
|
+
overriding_anchor = lines.match(/(?x)\[\[(.+?)\]\]\n=\s{1,}.+$/).captures[0].to_s
|
95
|
+
doclinksfix.push([doc, overriding_anchor, mchapter])
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
# TODO: - see if editing the array in-place is better using map!
|
100
|
+
$doclinks += doclinksfix
|
101
|
+
|
102
|
+
# Create array of non-indexed includes
|
103
|
+
adoc_files.each do |filename|
|
104
|
+
includes.each do |parent, child, childpath|
|
105
|
+
next unless childpath == filename
|
106
|
+
ni_includes.push([parent, child, filename])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# TODO: - see if editing the array in-place is better using map!
|
111
|
+
includes += ni_includes
|
112
|
+
|
113
|
+
# Edit the array of Requirements to point to the parent document *if* it is included.
|
114
|
+
# TODO use a while loop, repeat until no changes made
|
115
|
+
3.times do
|
116
|
+
# initialize an array
|
117
|
+
tempreqs = []
|
118
|
+
|
119
|
+
# Loop through all includes, if the requirement is contained in an include,
|
120
|
+
# edit the $reqs array to point to its parent instead
|
121
|
+
includes.each do |parent, _child, childpath|
|
122
|
+
# Delete the child req if matched
|
123
|
+
$reqs.delete_if do |rid, line, path, _filename, main, chapter|
|
124
|
+
next unless Sform.trim(childpath) == Sform.trim(path)
|
125
|
+
tempreqs.push([rid, line, Sform.trim(parent), parent, main, chapter])
|
126
|
+
true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# TODO: - see if editing the array in-place is better using map!
|
131
|
+
$reqs += tempreqs
|
132
|
+
$reqs.uniq!
|
133
|
+
end
|
134
|
+
|
135
|
+
# Sort in-place by numberic ID
|
136
|
+
$reqs.sort_by!(&:first)
|
137
|
+
|
138
|
+
# For all requirements, check which chapter they should finally be in with includes catered for
|
139
|
+
# match with a main document name - should be a main chapter title
|
140
|
+
$reqs.each do |rid, _line, path, _filename, _main, _chapter|
|
141
|
+
$doclinks.each do |_doc, link, chapter|
|
142
|
+
next unless path == "#{chapter}/#{_doc}"
|
143
|
+
$reqfixes.push([rid, link])
|
144
|
+
break
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# RexRx = /(?<=(?<!\\)<<)(Req-\w+-?.+?)(?=>>)/
|
149
|
+
RexRx = /(?<=<<)(Req-\w+-?.+?)(?=>>)/
|
150
|
+
|
151
|
+
# TODO: consider a more performant way of matching the requirements here
|
152
|
+
Extensions.register do
|
153
|
+
inline_macro do
|
154
|
+
named :reqlink
|
155
|
+
|
156
|
+
match RexRx
|
157
|
+
process do |parent, target|
|
158
|
+
id = target.sub(/^Req-/, '')
|
159
|
+
msg = ''
|
160
|
+
fix = ''
|
161
|
+
|
162
|
+
if target[/,/]
|
163
|
+
msg = target.match(/(?<=,).+/).to_s.strip
|
164
|
+
id = target.sub(/^Req-/, '').sub(/,.+/, '')
|
165
|
+
icon = '<i class="fa fa-info-circle" aria-hidden="true"></i>'
|
166
|
+
end
|
167
|
+
|
168
|
+
displaytext = id
|
169
|
+
|
170
|
+
$reqfixes.each do |fixid, file|
|
171
|
+
next unless fixid == id
|
172
|
+
fix = file
|
173
|
+
break
|
174
|
+
end
|
175
|
+
|
176
|
+
tooltip = if msg != ''
|
177
|
+
"data-toggle=\"tooltip\" data-placement=\"top\" title=\"#{msg}\""
|
178
|
+
else
|
179
|
+
''
|
180
|
+
end
|
181
|
+
|
182
|
+
fix = fix.sub(/^_/, '') if fix[/^_/]
|
183
|
+
link = id.sub(/^Req-/, '')
|
184
|
+
uri = "#{fix}.html##{link}"
|
185
|
+
uri = "##{link}" if fix == ''
|
186
|
+
o = "<span class=\"label label-info\" #{tooltip}>"
|
187
|
+
c = '</span>'
|
188
|
+
(create_anchor parent, %(#{o} #{icon} #{displaytext}#{c}), type: :link, target: uri).convert
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'asciidoctor'
|
2
|
+
require 'asciidoctor/extensions'
|
3
|
+
|
4
|
+
exts = "(\.adoc|\.md|\.html)"
|
5
|
+
docsdir = 'chapters'
|
6
|
+
|
7
|
+
title = nil
|
8
|
+
chapter = nil
|
9
|
+
doctitle = nil
|
10
|
+
incs = []
|
11
|
+
inc_reqs = []
|
12
|
+
rows = []
|
13
|
+
coms = []
|
14
|
+
reqs = []
|
15
|
+
reqfixes = []
|
16
|
+
|
17
|
+
CommentBlockRx = %r(^\/{4,}$)
|
18
|
+
CommentLineRx = %r{^//(?=[^/]|$)}
|
19
|
+
|
20
|
+
def trim(s)
|
21
|
+
s.gsub!(/chapters\//, '')
|
22
|
+
s.gsub!(/(\.adoc|\.md|\.html)/, '')
|
23
|
+
end
|
24
|
+
|
25
|
+
adoc_files = Dir.glob('**/*.adoc')
|
26
|
+
adoc_files.sort!
|
27
|
+
|
28
|
+
adoc_files.each do |f|
|
29
|
+
inc = false
|
30
|
+
commented = false
|
31
|
+
i = 0
|
32
|
+
File.read(f).each_line do |li|
|
33
|
+
i += 1
|
34
|
+
incommentblock ^= true if li[CommentBlockRx]
|
35
|
+
commented = true if li[CommentLineRx]
|
36
|
+
|
37
|
+
if li[/\[\s*req\s*,\s*id\s*=\s*\w+-?[0-9]+\s*,.*/]
|
38
|
+
title.sub!(/^\./, '')
|
39
|
+
req = [li.chop, f, title]
|
40
|
+
|
41
|
+
if commented || incommentblock
|
42
|
+
coms.push(req)
|
43
|
+
elsif inc
|
44
|
+
inc_reqs.push(req)
|
45
|
+
else
|
46
|
+
reqs.push(req)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Collect all includes
|
50
|
+
elsif li[/^include::.+.adoc\[\]/]
|
51
|
+
|
52
|
+
inc_file = li.chop.match(/(?<=^include::).+.adoc(?=\[\])/i).to_s
|
53
|
+
inc_file = inc_file.sub(/^\{find\}/, '')
|
54
|
+
path = inc_file.sub(/^#{docsdir}\//, '')
|
55
|
+
path = path.sub(/#{exts}/, '')
|
56
|
+
parent = f
|
57
|
+
item = [inc_file, path, parent]
|
58
|
+
incs.push item
|
59
|
+
|
60
|
+
end
|
61
|
+
title = li
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
i = 0
|
66
|
+
reqs.each do |req, f, title, chapter, doctitle|
|
67
|
+
i += 1
|
68
|
+
link = ''
|
69
|
+
# TODO: - find better solution for sanitized titles:
|
70
|
+
title = title.delete('`').delete("'").delete('*')
|
71
|
+
|
72
|
+
rid = /[^,]*\s*id\s*=\s*(\w+-?[0-9]+)\s*,.*/.match(req)[1]
|
73
|
+
version = /(?<=version=)\d+/.match(req)
|
74
|
+
|
75
|
+
f.gsub!(/^chapters\//, '')
|
76
|
+
f.gsub!(/.adoc$/, '')
|
77
|
+
|
78
|
+
$reqfixes.each do |id, fix|
|
79
|
+
next unless id == rid
|
80
|
+
link = "#{fix}.html##{rid}"
|
81
|
+
break
|
82
|
+
end
|
83
|
+
|
84
|
+
f = f.sub(/^chapters\//, '')
|
85
|
+
ref = "<a class=\"link\" href=\"#{link}\"><emphasis role=\"strong\">#{title}</emphasis> </a>"
|
86
|
+
breadcrumb = "<a href=\"#{f}\">#{chapter} / #{doctitle}</a>"
|
87
|
+
row = "<tr> <th scope=\"row\">#{i}</th> <td style=\"white-space:pre;\">#{rid}</td><td>#{version}</td> <td>#{ref}</td> <td>#{f}</td> </tr>"
|
88
|
+
|
89
|
+
rows.push(row)
|
90
|
+
end
|
91
|
+
|
92
|
+
Asciidoctor::Extensions.register do
|
93
|
+
block_macro :requirements do
|
94
|
+
process do |parent, _target, _attrs|
|
95
|
+
content = %(<h2 id="requirements"><a class="anchor" href="#requirements"></a><a class="link" href="#requirements">Requirements</a></h2>
|
96
|
+
<div class="panel panel-default"> <div class="panel-heading"><h4>Requirements</h4></div>
|
97
|
+
<table class="table"> <thead> <tr>
|
98
|
+
<th>#</th> <th>ID</th><th>Version</th> <th>Title</th> <th>Source Document</th>
|
99
|
+
</tr> </thead>
|
100
|
+
<tbody>
|
101
|
+
#{rows.join}
|
102
|
+
</tbody>
|
103
|
+
</table> </div>)
|
104
|
+
|
105
|
+
create_pass_block parent, content, {}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
|
2
|
+
|
3
|
+
include ::Asciidoctor
|
4
|
+
|
5
|
+
Extensions.register do
|
6
|
+
block do
|
7
|
+
named :req
|
8
|
+
on_contexts :open, :paragraph, :example, :listing, :sidebar, :pass
|
9
|
+
name_positional_attributes 'number', 'version'
|
10
|
+
|
11
|
+
process do |parent, reader, attrs|
|
12
|
+
# Add pass characters here to prevent html character replacements for < > tags
|
13
|
+
pass = '++++'
|
14
|
+
attrs['name'] = 'requirement'
|
15
|
+
attrs['caption'] = 'Requirement: '
|
16
|
+
id = attrs['id']
|
17
|
+
nl = ''
|
18
|
+
|
19
|
+
begin
|
20
|
+
# downcase the title and replace spaces with underscores.
|
21
|
+
# Also replacing special HTML entities:
|
22
|
+
# " = "
|
23
|
+
# & = &
|
24
|
+
downcased_title = attrs['title'].downcase.tr(' ', '_').gsub('"', '"')
|
25
|
+
san_title = attrs['title'].gsub(/&/, '&').delete('`').delete("'").delete('*')
|
26
|
+
rescue Exception => msg
|
27
|
+
puts msg
|
28
|
+
# If no title exists on the Req block, throw an exception
|
29
|
+
puts '[ERROR] Requirement block title missing'
|
30
|
+
end
|
31
|
+
|
32
|
+
alt = %(<div class=\"panel panel-primary\">
|
33
|
+
<div class=\"panel-heading\">
|
34
|
+
<h3 class=\"panel-title\" data-toc-skip>
|
35
|
+
<a class=\"anchor\" href=\"##{id}\"></a>
|
36
|
+
<a class=\"link\" href=\"##{id}\"><emphasis role=\"strong\">Requirement: #{id}:</emphasis> #{san_title} </a> (ver. #{attrs['version']})
|
37
|
+
</h3>
|
38
|
+
</div>
|
39
|
+
<div class=\"panel-body\">)
|
40
|
+
|
41
|
+
close = '</div></div>'
|
42
|
+
|
43
|
+
# concatenate all generated lines and prepend before the original content
|
44
|
+
concat_lines = reader.lines.unshift(pass, alt, pass, nl)
|
45
|
+
concat_lines.push(nl, pass, close, pass)
|
46
|
+
|
47
|
+
create_block parent, :open, concat_lines, attrs, content_model: :compound
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
|
2
|
+
|
3
|
+
include ::Asciidoctor
|
4
|
+
|
5
|
+
Extensions.register do
|
6
|
+
block do
|
7
|
+
named :TODO
|
8
|
+
on_contexts :open, :paragraph, :example, :listing, :sidebar, :pass
|
9
|
+
|
10
|
+
process do |parent, reader, attrs|
|
11
|
+
attrs['name'] = 'todo'
|
12
|
+
attrs['caption'] = 'Todo'
|
13
|
+
create_block parent, :admonition, reader.lines, attrs, content_model: :compound
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Helper methods handling whether to output inline content or a block.
|
2
|
+
# Will read the attributes of the current macro and output a HTML string that is either
|
3
|
+
# inline or a block (float-right).
|
4
|
+
module Context
|
5
|
+
# @param attributes [Array] attributes passed by the inline macro
|
6
|
+
# @param target [String] the target text
|
7
|
+
# @param pattern [String] the target url
|
8
|
+
# @param label [String] an optional status label, used to display if a task/issue is open or closed
|
9
|
+
# @return [String] the raw HTML to be included in the target document
|
10
|
+
def self.format(attributes, target, pattern, label)
|
11
|
+
block = false
|
12
|
+
block = true if attributes.key? 'block'
|
13
|
+
|
14
|
+
if target[0] == "\:"
|
15
|
+
block = true
|
16
|
+
target[0] = ''
|
17
|
+
end
|
18
|
+
|
19
|
+
url = "#{pattern}/#{target}"
|
20
|
+
|
21
|
+
html = if block
|
22
|
+
if pattern == 'unknown'
|
23
|
+
"<div style=\"float:right;padding-left:0.1em;\"><span class=\"label label-#{label}\" data-toggle=\"tooltip\" title=\"Missing config\">#{target}</span></div>"
|
24
|
+
else
|
25
|
+
"<div style=\"float:right;padding-left:0.1em;\"><a href=\"#{url}\"><span class=\"label label-#{label}\">#{target}</span></a></div>"
|
26
|
+
end
|
27
|
+
else
|
28
|
+
if pattern == 'unknown'
|
29
|
+
"<span class=\"label label-#{label}\" data-toggle=\"tooltip\" title=\"Missing config\">#{target}</span>"
|
30
|
+
else
|
31
|
+
"<a href=\"#{url}\"><span class=\"label label-#{label}\">#{target}</span></a>"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
html
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# A simple helper method handles the status of the target text.
|
2
|
+
# This is used to display whether a GitHub issue or a Jira ticket
|
3
|
+
# is open or closed etc.
|
4
|
+
module Labels
|
5
|
+
# @param attrs [Array] attributes passed by the inline macro
|
6
|
+
# @return [String] the status and/or label to be displayed
|
7
|
+
def self.getstatus(attrs)
|
8
|
+
status = attrs['status']
|
9
|
+
if status == ('done' || 'closed')
|
10
|
+
label = 'success'
|
11
|
+
elsif status == 'open'
|
12
|
+
label = 'warning'
|
13
|
+
else
|
14
|
+
status = 'unknown'
|
15
|
+
label = 'default'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sform
|
2
|
+
def self.trim(s)
|
3
|
+
s = s.gsub(/^#{$srcdir}\//, '') unless s.nil?
|
4
|
+
s = s.gsub(/(\.adoc)/, '') unless s.nil?
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.underscorify(t)
|
8
|
+
t = t.downcase.gsub(/(\s|-)/, '_')
|
9
|
+
# document attribute idprefix must be seet to empty, if not
|
10
|
+
# the default value is an underscore and the following line is required
|
11
|
+
# t = t.prepend('_') unless t.match(/^_/)
|
12
|
+
t = t.gsub(/___/, '_').delete('`')
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.titleify(t)
|
16
|
+
t = t.tr('_', ' ')
|
17
|
+
t = t.lstrip
|
18
|
+
t = t.split.map(&:capitalize).join(' ')
|
19
|
+
end
|
20
|
+
end
|
data/lib/html_chunker.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'asciidoctor'
|
2
|
+
|
3
|
+
# Chunks the HTML output generated by the HTML5 converter by chapter.
|
4
|
+
#
|
5
|
+
class MultipageHtml5Converter
|
6
|
+
include Asciidoctor::Converter
|
7
|
+
include Asciidoctor::Writer
|
8
|
+
|
9
|
+
register_for 'multipage'
|
10
|
+
EOL = "\n".freeze
|
11
|
+
|
12
|
+
def initialize(backend, opts)
|
13
|
+
super
|
14
|
+
basebackend 'html'
|
15
|
+
@documents = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def convert(node, transform = nil)
|
19
|
+
transform ||= node.node_name
|
20
|
+
send transform, node if respond_to? transform
|
21
|
+
end
|
22
|
+
|
23
|
+
def document(node)
|
24
|
+
indexconfigs = {
|
25
|
+
'stylesheet' => 'styles/main.css',
|
26
|
+
'find' => '',
|
27
|
+
'docinfodir' => 'headers',
|
28
|
+
'docinfo1' => 'true'
|
29
|
+
}
|
30
|
+
node.blocks.each(&:convert)
|
31
|
+
node.blocks.clear
|
32
|
+
master_content = []
|
33
|
+
master_content << %(= #{node.doctitle})
|
34
|
+
master_content << (node.attr 'author') if node.attr? 'author'
|
35
|
+
master_content << ''
|
36
|
+
# @documents.each do |doc|
|
37
|
+
# sect = doc.blocks[0]
|
38
|
+
# sectnum = sect.numbered && !sect.caption ? %(#{sect.sectnum} ) : nil
|
39
|
+
# filename = doc.attr 'docname'
|
40
|
+
# filename = filename.sub(/^_/, '')
|
41
|
+
# master_content << %(== <<#{filename}#,#{sectnum}#{sect.captioned_title}>> +)
|
42
|
+
# end
|
43
|
+
master_content << ''
|
44
|
+
master_content << 'requirements::[]'
|
45
|
+
Asciidoctor.convert master_content, doctype: node.doctype, header_footer: true, safe: node.safe, attributes: indexconfigs
|
46
|
+
end
|
47
|
+
|
48
|
+
def section(node)
|
49
|
+
doc = node.document
|
50
|
+
node.id.gsub!(/_2$/, '') if node.id[/_2$/]
|
51
|
+
configs = doc.attributes.clone
|
52
|
+
configs['noheader'] = ''
|
53
|
+
configs['doctitle'] = node.title
|
54
|
+
configs['backend'] = 'html'
|
55
|
+
page = Asciidoctor::Document.new [], header_footer: true, doctype: doc.doctype, safe: doc.safe, parse: true, attributes: configs
|
56
|
+
|
57
|
+
page.set_attr 'docname', node.id
|
58
|
+
reparent node, page
|
59
|
+
|
60
|
+
page.blocks << node
|
61
|
+
@documents << page
|
62
|
+
''
|
63
|
+
end
|
64
|
+
|
65
|
+
def reparent(node, parent)
|
66
|
+
node.parent = parent
|
67
|
+
node.blocks.each do |block|
|
68
|
+
reparent block, node unless block.context == :dlist
|
69
|
+
if block.context == :table
|
70
|
+
block.columns.each do |col|
|
71
|
+
col.parent = col.parent
|
72
|
+
end
|
73
|
+
block.rows.body.each do |row|
|
74
|
+
row.each do |cell|
|
75
|
+
cell.parent = cell.parent
|
76
|
+
end
|
77
|
+
end
|
78
|
+
elsif block.context == :dlist
|
79
|
+
block.parent = parent
|
80
|
+
block.items.each do |i|
|
81
|
+
reparent i[1], parent if i[1].respond_to? 'parent'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def write(output, target)
|
88
|
+
outdir = ::File.dirname target
|
89
|
+
puts '[CHUNKED HTML] Generating chapters:'
|
90
|
+
@documents.each do |doc|
|
91
|
+
filename = doc.attr 'docname'
|
92
|
+
filename = filename.sub(/^_/, '')
|
93
|
+
outfile = ::File.join outdir, %(#{filename}.html)
|
94
|
+
puts "[CHAPTER] #{outfile}"
|
95
|
+
::File.open(outfile, 'w') do |f|
|
96
|
+
f.write doc.convert
|
97
|
+
end
|
98
|
+
end
|
99
|
+
chunked_target = target.gsub(/(\.[^.]+)$/, '-chunked\1')
|
100
|
+
puts "[HTML INDEX] Generating index at #{chunked_target}"
|
101
|
+
::File.open(chunked_target, 'w') do |f|
|
102
|
+
f.write output
|
103
|
+
end
|
104
|
+
load "postprocessors/generate_toc.rb"
|
105
|
+
load "postprocessors/fulltext_search.rb"
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
json = ''
|
6
|
+
gendir = 'generated-docs' # TODO: - do not hardcode
|
7
|
+
replacements = /"|\n|«|»
|\s\s/
|
8
|
+
|
9
|
+
html_files = Dir.glob("#{gendir}/**/*.html")
|
10
|
+
|
11
|
+
html_files.each do |file|
|
12
|
+
# Skip the search results and index pages
|
13
|
+
next if file == "#{gendir}/search.html" || file[%r{^#{gendir}\/index}]
|
14
|
+
|
15
|
+
page = Nokogiri::HTML(open(file))
|
16
|
+
file.sub!(%r{^#{gendir}\/}, '')
|
17
|
+
slug = file.sub(/\.html$/, '')
|
18
|
+
|
19
|
+
h2 = page.css('h2').text
|
20
|
+
text = page.css('p').text.gsub(replacements, ' ')
|
21
|
+
|
22
|
+
content = %(
|
23
|
+
"#{slug}": {
|
24
|
+
"id": "#{slug}",
|
25
|
+
"title": "#{h2}",
|
26
|
+
"url": "#{file}",
|
27
|
+
"content": "#{text}"
|
28
|
+
},\n)
|
29
|
+
json += content
|
30
|
+
end
|
31
|
+
|
32
|
+
jsonindex = %(<script>
|
33
|
+
window.data = {
|
34
|
+
|
35
|
+
#{json}
|
36
|
+
|
37
|
+
};
|
38
|
+
</script>)
|
39
|
+
|
40
|
+
marker = '{{searchdata}}'
|
41
|
+
searchpage = "#{gendir}/search.html"
|
42
|
+
|
43
|
+
data = File.read(searchpage)
|
44
|
+
filtered_data = data.sub(marker, jsonindex)
|
45
|
+
|
46
|
+
File.open(searchpage, 'w') do |f|
|
47
|
+
f.write(filtered_data)
|
48
|
+
end
|