aspec_rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = /(?<=(?<!\\)&lt;&lt;)(Req-\w+-?.+?)(?=&gt;&gt;)/
149
+ RexRx = /(?<=&lt;&lt;)(Req-\w+-?.+?)(?=&gt;&gt;)/
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
+ # &quot; = "
23
+ # &amp; = &
24
+ downcased_title = attrs['title'].downcase.tr(' ', '_').gsub('"', '&quot;')
25
+ san_title = attrs['title'].gsub(/&/, '&amp;').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
@@ -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