Almirah 0.4.2 → 0.4.4
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/bin/almirah +2 -2
- data/lib/almirah/doc_fabric.rb +25 -0
- data/lib/almirah/doc_items/blockquote.rb +15 -16
- data/lib/almirah/doc_items/code_block.rb +19 -21
- data/lib/almirah/doc_items/controlled_paragraph.rb +4 -4
- data/lib/almirah/doc_items/controlled_table.rb +11 -11
- data/lib/almirah/doc_items/controlled_table_row.rb +15 -18
- data/lib/almirah/doc_items/doc_footer.rb +10 -13
- data/lib/almirah/doc_items/doc_item.rb +2 -2
- data/lib/almirah/doc_items/heading.rb +1 -1
- data/lib/almirah/doc_items/image.rb +25 -27
- data/lib/almirah/doc_items/markdown_list.rb +2 -2
- data/lib/almirah/doc_items/markdown_table.rb +9 -2
- data/lib/almirah/doc_items/scope_table.rb +188 -0
- data/lib/almirah/doc_items/text_line.rb +26 -22
- data/lib/almirah/doc_items/todo_block.rb +15 -16
- data/lib/almirah/doc_items/work_item.rb +129 -0
- data/lib/almirah/doc_parser.rb +15 -8
- data/lib/almirah/doc_types/base_document.rb +31 -7
- data/lib/almirah/doc_types/coverage.rb +3 -3
- data/lib/almirah/doc_types/critical_chain_page.rb +218 -0
- data/lib/almirah/doc_types/decision.rb +152 -7
- data/lib/almirah/doc_types/decision_grouping.rb +17 -0
- data/lib/almirah/doc_types/decisions_overview.rb +591 -31
- data/lib/almirah/doc_types/implementation.rb +98 -98
- data/lib/almirah/doc_types/index.rb +11 -12
- data/lib/almirah/doc_types/persistent_document.rb +1 -1
- data/lib/almirah/doc_types/planning_dates.rb +17 -0
- data/lib/almirah/doc_types/protocol.rb +16 -20
- data/lib/almirah/doc_types/risk_record.rb +77 -0
- data/lib/almirah/doc_types/risk_registry_page.rb +141 -0
- data/lib/almirah/doc_types/risks_overview.rb +102 -0
- data/lib/almirah/doc_types/rpn_rendering.rb +23 -0
- data/lib/almirah/doc_types/source_file.rb +1 -1
- data/lib/almirah/doc_types/traceability.rb +124 -133
- data/lib/almirah/dom/doc_section.rb +1 -1
- data/lib/almirah/navigation_pane.rb +9 -13
- data/lib/almirah/project/critical_chain.rb +117 -0
- data/lib/almirah/project/doc_linker.rb +4 -4
- data/lib/almirah/project/fever_chart.rb +94 -0
- data/lib/almirah/project/project_data.rb +17 -2
- data/lib/almirah/project/work_item_scheduler.rb +167 -0
- data/lib/almirah/project/working_calendar.rb +112 -0
- data/lib/almirah/project.rb +307 -9
- data/lib/almirah/project_configuration.rb +176 -29
- data/lib/almirah/project_template.rb +6 -6
- data/lib/almirah/project_utility.rb +3 -5
- data/lib/almirah/search/specifications_db.rb +2 -2
- data/lib/almirah/source_file_parser.rb +2 -3
- data/lib/almirah/templates/css/main.css +173 -1
- data/lib/almirah/templates/scripts/main.js +3 -1
- data/lib/almirah.rb +1 -2
- metadata +14 -1
|
@@ -1,104 +1,104 @@
|
|
|
1
1
|
require_relative 'base_document'
|
|
2
2
|
|
|
3
3
|
class Implementation < BaseDocument
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
4
|
+
attr_accessor :top_doc, :bottom_doc, :items, :is_agregated, :traced_items
|
|
5
|
+
|
|
6
|
+
def initialize(top_doc)
|
|
7
|
+
super()
|
|
8
|
+
@top_doc = top_doc
|
|
9
|
+
|
|
10
|
+
@is_agregated = true
|
|
11
|
+
@traced_items = {}
|
|
12
|
+
|
|
13
|
+
@id = if @is_agregated
|
|
14
|
+
top_doc.id + '-sources'
|
|
15
|
+
else
|
|
16
|
+
top_doc.id + '-' + bottom_doc.id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@title = 'Implementation Matrix: ' + @id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_console
|
|
23
|
+
puts "\e[35m" + 'Implementation: ' + @id + "\e[0m"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_html(nav_pane, output_file_path)
|
|
27
|
+
html_rows = []
|
|
28
|
+
|
|
29
|
+
html_rows.append('')
|
|
30
|
+
s = "<h1>#{@title}</h1>\n"
|
|
31
|
+
s += "<table class=\"controlled\">\n"
|
|
32
|
+
s += "\t<thead>"
|
|
33
|
+
s += "\t\t<th>#</th>"
|
|
34
|
+
s += "\t\t<th style='font-weight: bold;'>#{@top_doc.title}</th>"
|
|
35
|
+
s += "\t\t<th>#</th>"
|
|
36
|
+
s += "\t\t<th style='font-weight: bold;'>Repository</th>"
|
|
37
|
+
s += "\t\t<th style='font-weight: bold;'>File Name</th>"
|
|
38
|
+
s += "\t\t<th style='font-weight: bold;'>Comment</th>"
|
|
39
|
+
s += "\t</thead>\n"
|
|
40
|
+
html_rows.append s
|
|
41
|
+
|
|
42
|
+
sorted_items = @top_doc.controlled_items.sort_by { |w| w.id }
|
|
43
|
+
|
|
44
|
+
sorted_items.each do |top_item|
|
|
45
|
+
row = render_table_row top_item
|
|
46
|
+
html_rows.append row
|
|
20
47
|
end
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
s += "\t\t<td class=\"item_id\"><a href=\"#{html_source_file_relative_path}##{bottom_item.id}\" class=\"external\">#{bottom_item.id}</a></td>\n"
|
|
75
|
-
s += "\t\t<td class=\"item_text\" style='width: 16%;'>#{repository}</td>\n"
|
|
76
|
-
s += "\t\t<td class=\"item_text\" style='width: 16%;'>#{file_name}</td>\n"
|
|
77
|
-
s += "\t\t<td class=\"item_text\" style='width: 28%;'>#{bottom_f_text}</td>\n"
|
|
78
|
-
s += "\t</tr>\n"
|
|
79
|
-
top_item_rendered = true
|
|
80
|
-
@traced_items[top_item.id.to_s.downcase] = top_item
|
|
81
|
-
end
|
|
82
|
-
unless top_item_rendered
|
|
83
|
-
s += "\t<tr>\n"
|
|
84
|
-
s += "\t\t<td class=\"item_id\"><a href=\"./../#{top_item.parent_doc.id}/#{top_item.parent_doc.id}.html##{top_item.id}\" class=\"external\">#{top_item.id}</a></td>\n"
|
|
85
|
-
s += "\t\t<td class=\"item_text\" style='width: 28%;'>#{top_f_text}</td>\n"
|
|
86
|
-
s += "\t\t<td class=\"item_id\"></td>\n"
|
|
87
|
-
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
88
|
-
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
89
|
-
s += "\t\t<td class=\"item_text\" style='width: 28%;'></td>\n"
|
|
90
|
-
s += "\t</tr>\n"
|
|
91
|
-
end
|
|
92
|
-
else
|
|
93
|
-
s += "\t<tr>\n"
|
|
94
|
-
s += "\t\t<td class=\"item_id\"><a href=\"./../#{top_item.parent_doc.id}/#{top_item.parent_doc.id}.html##{top_item.id}\" class=\"external\">#{top_item.id}</a></td>\n"
|
|
95
|
-
s += "\t\t<td class=\"item_text\" style='width: 28%;'>#{top_f_text}</td>\n"
|
|
96
|
-
s += "\t\t<td class=\"item_id\"></td>\n"
|
|
97
|
-
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
98
|
-
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
99
|
-
s += "\t\t<td class=\"item_text\" style='width: 28%;'></td>\n"
|
|
100
|
-
s += "\t</tr>\n"
|
|
101
|
-
end
|
|
102
|
-
s
|
|
48
|
+
html_rows.append "</table>\n"
|
|
49
|
+
|
|
50
|
+
save_html_to_file(html_rows, nav_pane, output_file_path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def render_table_row(top_item)
|
|
54
|
+
s = ''
|
|
55
|
+
top_f_text = top_item.format_string(top_item.text)
|
|
56
|
+
id_color = ''
|
|
57
|
+
|
|
58
|
+
if top_item.source_code_links && top_item.source_code_links.length.positive?
|
|
59
|
+
|
|
60
|
+
top_item_rendered = false
|
|
61
|
+
|
|
62
|
+
top_item.source_code_links.each do |bottom_item|
|
|
63
|
+
id_color = "style='background-color: #cff4d2;'"
|
|
64
|
+
bottom_f_text = bottom_item.format_string(bottom_item.text)
|
|
65
|
+
file_name = bottom_item.parent_doc.id
|
|
66
|
+
repository = bottom_item.parent_doc.repository
|
|
67
|
+
|
|
68
|
+
p = bottom_item.parent_doc.html_file_path.split('/build/source_files/').last
|
|
69
|
+
html_source_file_relative_path = "./../../source_files/#{p}"
|
|
70
|
+
|
|
71
|
+
s += "\t<tr>\n"
|
|
72
|
+
s += "\t\t<td class=\"item_id\"><a href=\"./../#{top_item.parent_doc.id}/#{top_item.parent_doc.id}.html##{top_item.id}\" class=\"external\">#{top_item.id}</a></td>\n"
|
|
73
|
+
s += "\t\t<td class=\"item_text\" style='width: 28%;'>#{top_f_text}</td>\n"
|
|
74
|
+
s += "\t\t<td class=\"item_id\"><a href=\"#{html_source_file_relative_path}##{bottom_item.id}\" class=\"external\">#{bottom_item.id}</a></td>\n"
|
|
75
|
+
s += "\t\t<td class=\"item_text\" style='width: 16%;'>#{repository}</td>\n"
|
|
76
|
+
s += "\t\t<td class=\"item_text\" style='width: 16%;'>#{file_name}</td>\n"
|
|
77
|
+
s += "\t\t<td class=\"item_text\" style='width: 28%;'>#{bottom_f_text}</td>\n"
|
|
78
|
+
s += "\t</tr>\n"
|
|
79
|
+
top_item_rendered = true
|
|
80
|
+
@traced_items[top_item.id.to_s.downcase] = top_item
|
|
81
|
+
end
|
|
82
|
+
unless top_item_rendered
|
|
83
|
+
s += "\t<tr>\n"
|
|
84
|
+
s += "\t\t<td class=\"item_id\"><a href=\"./../#{top_item.parent_doc.id}/#{top_item.parent_doc.id}.html##{top_item.id}\" class=\"external\">#{top_item.id}</a></td>\n"
|
|
85
|
+
s += "\t\t<td class=\"item_text\" style='width: 28%;'>#{top_f_text}</td>\n"
|
|
86
|
+
s += "\t\t<td class=\"item_id\"></td>\n"
|
|
87
|
+
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
88
|
+
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
89
|
+
s += "\t\t<td class=\"item_text\" style='width: 28%;'></td>\n"
|
|
90
|
+
s += "\t</tr>\n"
|
|
91
|
+
end
|
|
92
|
+
else
|
|
93
|
+
s += "\t<tr>\n"
|
|
94
|
+
s += "\t\t<td class=\"item_id\"><a href=\"./../#{top_item.parent_doc.id}/#{top_item.parent_doc.id}.html##{top_item.id}\" class=\"external\">#{top_item.id}</a></td>\n"
|
|
95
|
+
s += "\t\t<td class=\"item_text\" style='width: 28%;'>#{top_f_text}</td>\n"
|
|
96
|
+
s += "\t\t<td class=\"item_id\"></td>\n"
|
|
97
|
+
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
98
|
+
s += "\t\t<td class=\"item_text\" style='width: 16%;'></td>\n"
|
|
99
|
+
s += "\t\t<td class=\"item_text\" style='width: 28%;'></td>\n"
|
|
100
|
+
s += "\t</tr>\n"
|
|
103
101
|
end
|
|
102
|
+
s
|
|
103
|
+
end
|
|
104
104
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base_document'
|
|
4
4
|
|
|
5
|
-
class Index < BaseDocument
|
|
5
|
+
class Index < BaseDocument
|
|
6
6
|
attr_accessor :project
|
|
7
7
|
|
|
8
8
|
def initialize(project)
|
|
@@ -16,11 +16,10 @@ class Index < BaseDocument # rubocop:disable Metrics/ClassLength,Style/Documenta
|
|
|
16
16
|
puts "\e[36mIndex: #{@id}\e[0m"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def to_html(output_file_path)
|
|
19
|
+
def to_html(output_file_path)
|
|
20
20
|
html_rows = []
|
|
21
21
|
|
|
22
22
|
html_rows.append('')
|
|
23
|
-
s = "<h1>#{@title}</h1>\n"
|
|
24
23
|
|
|
25
24
|
# Specifications
|
|
26
25
|
s = "<h2>Specifications</h2>\n"
|
|
@@ -41,7 +40,7 @@ class Index < BaseDocument # rubocop:disable Metrics/ClassLength,Style/Documenta
|
|
|
41
40
|
|
|
42
41
|
sorted_items = @project.project_data.specifications.sort_by(&:id)
|
|
43
42
|
|
|
44
|
-
sorted_items.each do |doc|
|
|
43
|
+
sorted_items.each do |doc|
|
|
45
44
|
s = "\t<tr>\n"
|
|
46
45
|
s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\"><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.color};'> </i> #{doc.title}</a></td>\n"
|
|
47
46
|
s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{doc.controlled_items.length}</td>\n"
|
|
@@ -121,11 +120,11 @@ class Index < BaseDocument # rubocop:disable Metrics/ClassLength,Style/Documenta
|
|
|
121
120
|
s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\">#{doc.title}</a></td>\n"
|
|
122
121
|
s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
|
|
123
122
|
s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.top_doc.color};'> </i> #{doc.top_doc.title}</td>\n"
|
|
124
|
-
if doc.bottom_doc
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
s += if doc.bottom_doc
|
|
124
|
+
"\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-file-text-o\" style='background-color: ##{doc.bottom_doc.color};'> </i> #{doc.bottom_doc.title}</td>\n"
|
|
125
|
+
else
|
|
126
|
+
"\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'><i class=\"fa fa-circle-o\"'> </i> All References</td>\n"
|
|
127
|
+
end
|
|
129
128
|
s += "</tr>\n"
|
|
130
129
|
html_rows.append s
|
|
131
130
|
end
|
|
@@ -158,7 +157,7 @@ class Index < BaseDocument # rubocop:disable Metrics/ClassLength,Style/Documenta
|
|
|
158
157
|
end
|
|
159
158
|
|
|
160
159
|
s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\">#{doc.title}</a></td>\n"
|
|
161
|
-
s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
|
|
160
|
+
s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
|
|
162
161
|
s += "\t\t<td class=\"item_id\" style='width: 7%; #{test_step_color}' title='pass/fail'> #{doc.passed_steps_number}/#{doc.failed_steps_number} </td>\n"
|
|
163
162
|
s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'>\
|
|
164
163
|
<i class=\"fa fa-file-text-o\" style='background-color: ##{doc.top_doc.color};'> </i>\
|
|
@@ -187,13 +186,13 @@ class Index < BaseDocument # rubocop:disable Metrics/ClassLength,Style/Documenta
|
|
|
187
186
|
|
|
188
187
|
implemented_items = 0
|
|
189
188
|
doc.top_doc.controlled_items.each do |i|
|
|
190
|
-
implemented_items += 1 if i.source_code_links && i.source_code_links.length.positive?
|
|
189
|
+
implemented_items += 1 if i.source_code_links && i.source_code_links.length.positive?
|
|
191
190
|
end
|
|
192
191
|
|
|
193
192
|
coverage = implemented_items.to_f / doc.top_doc.controlled_items.length * 100.0
|
|
194
193
|
|
|
195
194
|
s += "\t\t<td class=\"item_text\" style='padding: 5px;'><a href=\"./specifications/#{doc.id}/#{doc.id}.html\" class=\"external\">#{doc.title}</a></td>\n"
|
|
196
|
-
s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
|
|
195
|
+
s += "\t\t<td class=\"item_id\" style='width: 7%;'>#{'%.2f' % coverage}%</td>\n"
|
|
197
196
|
s += "\t\t<td class=\"item_text\" style='width: 25%; padding: 5px;'>\
|
|
198
197
|
<i class=\"fa fa-file-text-o\" style='background-color: ##{doc.top_doc.color};'> </i>\
|
|
199
198
|
#{doc.top_doc.title}</td>\n"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base_document'
|
|
4
4
|
|
|
5
|
-
class PersistentDocument < BaseDocument
|
|
5
|
+
class PersistentDocument < BaseDocument
|
|
6
6
|
attr_accessor :path, :items, :controlled_items, :headings, :up_link_docs, :frontmatter
|
|
7
7
|
|
|
8
8
|
def initialize(fele_path)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
# Shared date bucketing for the planning views (ADR-182): the recent Fridays a
|
|
6
|
+
# time-series trail is sampled on. Used by the overview's velocity chart and the
|
|
7
|
+
# Critical Chain page's fever-chart trail (ADR-196).
|
|
8
|
+
module PlanningDates
|
|
9
|
+
# `count` Fridays ending at the Friday on or before `reference_date`, oldest
|
|
10
|
+
# first.
|
|
11
|
+
def recent_fridays(reference_date, count)
|
|
12
|
+
friday_wday = 5
|
|
13
|
+
days_back = (reference_date.wday - friday_wday) % 7
|
|
14
|
+
most_recent = reference_date - days_back
|
|
15
|
+
(0...count).to_a.reverse.map { |i| most_recent - (7 * i) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,28 +1,24 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative 'persistent_document'
|
|
2
2
|
|
|
3
3
|
class Protocol < PersistentDocument
|
|
4
|
+
attr_accessor :specifications_path
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
def initialize(fele_path)
|
|
7
|
+
super
|
|
8
|
+
@id = File.basename(fele_path, File.extname(fele_path)).downcase
|
|
9
|
+
@specifications_path = './../../../specifications/'
|
|
10
|
+
end
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@id = File.basename(fele_path, File.extname(fele_path)).downcase
|
|
10
|
-
@specifications_path = './../../../specifications/'
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def to_html(nav_pane, output_file_path)
|
|
14
|
-
|
|
15
|
-
html_rows = Array.new
|
|
16
|
-
|
|
17
|
-
html_rows.append('')
|
|
12
|
+
def to_html(nav_pane, output_file_path)
|
|
13
|
+
html_rows = []
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
a = item.to_html
|
|
21
|
-
html_rows.append a
|
|
22
|
-
end
|
|
15
|
+
html_rows.append('')
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
@items.each do |item|
|
|
18
|
+
a = item.to_html
|
|
19
|
+
html_rows.append a
|
|
26
20
|
end
|
|
27
21
|
|
|
28
|
-
|
|
22
|
+
save_html_to_file(html_rows, nav_pane, output_file_path)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'decision'
|
|
4
|
+
require_relative '../doc_items/controlled_table'
|
|
5
|
+
|
|
6
|
+
# A risk record (ADR-215): one Markdown file per risk, collected from the
|
|
7
|
+
# first-level subfolders of the project's risks/ folder, each subfolder being
|
|
8
|
+
# a risk registry. The record format is the decision-record format — filename
|
|
9
|
+
# letters-digits id, frontmatter title, Status table with a "*" current-state
|
|
10
|
+
# marker — so the type reuses Decision wholesale.
|
|
11
|
+
class RiskRecord < Decision
|
|
12
|
+
# The first-level risks/ subfolder the record was collected from.
|
|
13
|
+
attr_accessor :registry
|
|
14
|
+
|
|
15
|
+
def to_console
|
|
16
|
+
puts "\e[36mRisk Record: #{@id}\e[0m"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The rendered HTML of the record section whose heading text equals
|
|
20
|
+
# `section_name` (ADR-216). Empty when there is no such section — the
|
|
21
|
+
# register renders it as an empty cell.
|
|
22
|
+
def section_html(section_name)
|
|
23
|
+
section_items(section_name).map(&:to_html).join
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The numeric value of the named section (ADR-217): its items' plain text
|
|
27
|
+
# parsed as a Float. nil when the section is missing, empty, or not numeric —
|
|
28
|
+
# the RPN cell renders blank rather than computing a broken record as safe.
|
|
29
|
+
def section_numeric(section_name)
|
|
30
|
+
texts = section_items(section_name).filter_map { |i| i.text if i.respond_to?(:text) }
|
|
31
|
+
Float(texts.join(' ').strip, exception: false)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The record's value for an RPN group (ADR-217): the product of its numeric
|
|
35
|
+
# input sections. nil when any input is missing or not numeric — such a
|
|
36
|
+
# record renders a blank cell and is ignored by the summary aggregates.
|
|
37
|
+
def rpn_value(group)
|
|
38
|
+
factors = group[:inputs].map { |input| section_numeric(input) }
|
|
39
|
+
return nil if factors.any?(&:nil?)
|
|
40
|
+
|
|
41
|
+
factors.reduce(:*)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The distinct controlled-paragraph IDs the record's Affected Documents
|
|
45
|
+
# Req-ID column links to (ADR-218), in the section's row order — the
|
|
46
|
+
# IDs-only content of the register cell. Empty when the record carries no
|
|
47
|
+
# Affected Documents section or its rows link nothing.
|
|
48
|
+
def affected_document_ids
|
|
49
|
+
table = section_items('Affected Documents').find { |i| i.is_a?(ControlledTable) }
|
|
50
|
+
return [] if table.nil?
|
|
51
|
+
|
|
52
|
+
table.rows.flat_map { |row| row.up_link_ids || [] }.uniq
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# The items between the heading whose text equals `section_name` and the
|
|
58
|
+
# next heading of the same or a higher level; empty when no heading matches.
|
|
59
|
+
def section_items(section_name)
|
|
60
|
+
in_section = false
|
|
61
|
+
section_level = nil
|
|
62
|
+
collected = []
|
|
63
|
+
@items.each do |item|
|
|
64
|
+
if item.is_a?(Heading) && !in_section
|
|
65
|
+
next unless item.text.strip == section_name
|
|
66
|
+
|
|
67
|
+
in_section = true
|
|
68
|
+
section_level = item.level
|
|
69
|
+
elsif in_section
|
|
70
|
+
break if item.is_a?(Heading) && item.level <= section_level
|
|
71
|
+
|
|
72
|
+
collected.append item
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
collected
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_document'
|
|
4
|
+
require_relative 'rpn_rendering'
|
|
5
|
+
require_relative '../doc_items/heading'
|
|
6
|
+
|
|
7
|
+
# The registry page (ADR-216): build/risks/<registry>/overview.html holding
|
|
8
|
+
# the rendered registry preface (overview.md), when present, followed by the
|
|
9
|
+
# register table — one row per risk record with the implicit linked-ID and
|
|
10
|
+
# Title columns first, then the configured columns filled from each record's
|
|
11
|
+
# section matched by heading text. The Status column is reserved: it is filled
|
|
12
|
+
# from the record's current lifecycle status, never from a section.
|
|
13
|
+
class RiskRegistryPage < BaseDocument
|
|
14
|
+
include RpnRendering
|
|
15
|
+
|
|
16
|
+
STATUS_COLUMN = 'Status'
|
|
17
|
+
# The column rendered as bare linked IDs from the record's Affected
|
|
18
|
+
# Documents section (ADR-218), never as the section's prose.
|
|
19
|
+
AFFECTED_DOCUMENTS_COLUMN = 'Affected Documents'
|
|
20
|
+
|
|
21
|
+
attr_accessor :registry, :records, :preface, :columns, :rpn_groups
|
|
22
|
+
|
|
23
|
+
# `columns` is the configured per-registry list (ADR-216); nil means the
|
|
24
|
+
# registry is not configured and gets the implicit columns plus Status only.
|
|
25
|
+
# `rpn_groups` (ADR-217) appends one computed "<Name> RPN" column per group
|
|
26
|
+
# after the configured columns; empty appends nothing.
|
|
27
|
+
def initialize(registry, records, preface, columns, rpn_groups = [])
|
|
28
|
+
super()
|
|
29
|
+
@registry = registry
|
|
30
|
+
@records = records
|
|
31
|
+
@preface = preface
|
|
32
|
+
@columns = columns.nil? ? [STATUS_COLUMN] : columns
|
|
33
|
+
@rpn_groups = rpn_groups
|
|
34
|
+
@id = 'overview'
|
|
35
|
+
@title = preface_title || "Risk Registry: #{registry}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_console
|
|
39
|
+
puts "\e[36mRisk Registry: #{@registry}\e[0m"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_html(output_file_path)
|
|
43
|
+
html_rows = ['']
|
|
44
|
+
html_rows.concat preface_html
|
|
45
|
+
html_rows.append render_register_table
|
|
46
|
+
save_html_to_file(html_rows, nil, output_file_path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# The preface frontmatter title names the page; without it the registry does.
|
|
52
|
+
def preface_title
|
|
53
|
+
params = @preface&.frontmatter&.parameters
|
|
54
|
+
params && params['title']
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The rendered overview.md items. The parser-injected level-0 title heading
|
|
58
|
+
# is not authored preface content and is skipped.
|
|
59
|
+
def preface_html
|
|
60
|
+
return [] if @preface.nil?
|
|
61
|
+
|
|
62
|
+
@preface.items.reject { |i| i.is_a?(Heading) && i.level.zero? }.map(&:to_html)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def render_register_table
|
|
66
|
+
s = "<table class=\"controlled risk_register\">\n"
|
|
67
|
+
s += "\t<thead>\n"
|
|
68
|
+
s += "\t\t<th>#</th>\n"
|
|
69
|
+
s += "\t\t<th>Title</th>\n"
|
|
70
|
+
@columns.each { |c| s += "\t\t<th>#{c}</th>\n" }
|
|
71
|
+
@rpn_groups.each { |g| s += "\t\t<th>#{g[:name]} RPN</th>\n" }
|
|
72
|
+
s += "</thead>\n"
|
|
73
|
+
@records.each { |doc| s += render_record_row(doc) }
|
|
74
|
+
s + "</table>\n"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def render_record_row(doc)
|
|
78
|
+
href = "./#{record_href(doc)}"
|
|
79
|
+
s = "\t<tr>\n"
|
|
80
|
+
s += "\t\t<td class=\"item_id\">\n"
|
|
81
|
+
s += "\t\t\t<a name=\"#{doc.id}\" id=\"#{doc.id}\" href=\"#{href}\" title=\"Risk Record ID\">#{doc.id.upcase}</a>"
|
|
82
|
+
s += "\t\t</td>\n"
|
|
83
|
+
s += "\t\t<td class=\"item_text\" style='padding: 5px;'>\
|
|
84
|
+
<a href=\"#{href}\" class=\"external\">#{record_title(doc)}</a></td>\n"
|
|
85
|
+
@columns.each { |c| s += render_column_cell(doc, c) }
|
|
86
|
+
@rpn_groups.each { |g| s += render_rpn_cell(doc, g) }
|
|
87
|
+
s + "\t</tr>\n"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# The Title cell (ENH-221): the frontmatter title with the record's own
|
|
91
|
+
# leading "ID:" prefix removed, case-insensitively — the ID column already
|
|
92
|
+
# carries it. A title not starting with the record's ID stays unchanged, and
|
|
93
|
+
# the record page keeps the full title. Display-only, like the uppercased ID:
|
|
94
|
+
# anchors and hrefs keep the canonical lowercase id.
|
|
95
|
+
def record_title(doc)
|
|
96
|
+
doc.title.to_s.sub(/\A#{Regexp.escape(doc.id)}\s*:\s*/i, '')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The record page path relative to the registry page, which sits at the
|
|
100
|
+
# registry root: the record's html_rel_path minus its registry segment.
|
|
101
|
+
def record_href(doc)
|
|
102
|
+
doc.html_rel_path.split('/', 2).last
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def render_column_cell(doc, column)
|
|
106
|
+
return "\t\t<td class=\"item_status\">#{doc.current_status}</td>\n" if column == STATUS_COLUMN
|
|
107
|
+
return render_affected_documents_cell(doc) if column == AFFECTED_DOCUMENTS_COLUMN
|
|
108
|
+
|
|
109
|
+
"\t\t<td class=\"item_text\">#{doc.section_html(column)}</td>\n"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# IDs only (ADR-218): each distinct linked controlled-paragraph ID as a
|
|
113
|
+
# clickable link in row order; a dangling ID renders in the existing
|
|
114
|
+
# broken-link style rather than being dropped. The Proposed Text stays on
|
|
115
|
+
# the record page.
|
|
116
|
+
def render_affected_documents_cell(doc)
|
|
117
|
+
links = doc.affected_document_ids.map { |id| affected_document_link(doc, id) }
|
|
118
|
+
"\t\t<td class=\"item_id\">#{links.join(', ')}</td>\n"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def affected_document_link(doc, item_id)
|
|
122
|
+
if doc.wrong_links_hash.key?(item_id)
|
|
123
|
+
%(<span class="broken_link" title="Unresolved reference">#{item_id}</span>)
|
|
124
|
+
else
|
|
125
|
+
spec = /^([a-zA-Z]+)-\d+/.match(item_id)&.[](1)&.downcase
|
|
126
|
+
href = "./../../specifications/#{spec}/#{spec}.html##{item_id}"
|
|
127
|
+
%(<a href="#{href}" class="external" title="Affected document">#{item_id}</a>)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The computed group cell (ADR-217): the record's group value, blank when
|
|
132
|
+
# any input is missing or not numeric, coloured by the group's thresholds
|
|
133
|
+
# when configured.
|
|
134
|
+
def render_rpn_cell(doc, group)
|
|
135
|
+
value = doc.rpn_value(group)
|
|
136
|
+
return "\t\t<td class=\"item_rpn\"></td>\n" if value.nil?
|
|
137
|
+
|
|
138
|
+
classes = ['item_rpn', rpn_threshold_class(value, group)].compact.join(' ')
|
|
139
|
+
"\t\t<td class=\"#{classes}\">#{format_rpn(value)}</td>\n"
|
|
140
|
+
end
|
|
141
|
+
end
|