aspec_rb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|