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.
@@ -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