Almirah 0.4.2 → 0.4.3
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 +7 -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 +2 -2
- 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 +14 -8
- data/lib/almirah/doc_types/base_document.rb +21 -5
- 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/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 +8 -2
- data/lib/almirah/project/work_item_scheduler.rb +166 -0
- data/lib/almirah/project/working_calendar.rb +112 -0
- data/lib/almirah/project.rb +146 -9
- data/lib/almirah/project_configuration.rb +126 -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 +160 -0
- data/lib/almirah/templates/scripts/main.js +3 -1
- data/lib/almirah.rb +1 -2
- metadata +11 -2
data/lib/almirah/project.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'fileutils'
|
|
4
4
|
require_relative 'doc_fabric'
|
|
5
|
+
require_relative 'doc_items/work_item'
|
|
5
6
|
require_relative 'navigation_pane'
|
|
6
7
|
require_relative 'doc_types/traceability'
|
|
7
8
|
require_relative 'doc_types/index'
|
|
@@ -12,7 +13,7 @@ require_relative 'project/project_data'
|
|
|
12
13
|
require_relative 'console_reporter'
|
|
13
14
|
require_relative 'relative_url'
|
|
14
15
|
|
|
15
|
-
class Project
|
|
16
|
+
class Project
|
|
16
17
|
attr_accessor :index, :project, :configuration, :project_data
|
|
17
18
|
|
|
18
19
|
def initialize(configuration)
|
|
@@ -39,7 +40,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
39
40
|
FileUtils.copy_entry(src_folder, dst_folder)
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
def specifications_and_protocols
|
|
43
|
+
def specifications_and_protocols
|
|
43
44
|
parse_all_specifications
|
|
44
45
|
parse_all_protocols
|
|
45
46
|
parse_all_source_files
|
|
@@ -50,6 +51,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
50
51
|
link_all_decisions
|
|
51
52
|
check_wrong_specification_referenced
|
|
52
53
|
build_link_registry
|
|
54
|
+
link_work_items
|
|
53
55
|
create_index
|
|
54
56
|
render_all_specifications(@project_data.specifications)
|
|
55
57
|
render_all_specifications(@project_data.traceability_matrices)
|
|
@@ -58,14 +60,16 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
58
60
|
render_all_source_files
|
|
59
61
|
render_all_specifications(@project_data.implementation_matrices) # intentionally after source file rendering
|
|
60
62
|
render_decisions_overview
|
|
63
|
+
render_critical_chain_page
|
|
61
64
|
render_all_decisions
|
|
62
65
|
render_index
|
|
63
66
|
create_search_data
|
|
64
67
|
report_broken_links
|
|
68
|
+
report_kit_violations
|
|
65
69
|
report_rendered
|
|
66
70
|
end
|
|
67
71
|
|
|
68
|
-
def specifications_and_results(test_run)
|
|
72
|
+
def specifications_and_results(test_run)
|
|
69
73
|
parse_all_specifications
|
|
70
74
|
parse_test_run test_run
|
|
71
75
|
parse_all_source_files
|
|
@@ -76,6 +80,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
76
80
|
link_all_decisions
|
|
77
81
|
check_wrong_specification_referenced
|
|
78
82
|
build_link_registry
|
|
83
|
+
link_work_items
|
|
79
84
|
create_index
|
|
80
85
|
render_all_specifications(@project_data.specifications)
|
|
81
86
|
render_all_specifications(@project_data.traceability_matrices)
|
|
@@ -84,10 +89,12 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
84
89
|
render_all_source_files
|
|
85
90
|
render_all_specifications(@project_data.implementation_matrices) # intentionally after source file rendering
|
|
86
91
|
render_decisions_overview
|
|
92
|
+
render_critical_chain_page
|
|
87
93
|
render_all_decisions
|
|
88
94
|
render_index
|
|
89
95
|
create_search_data
|
|
90
96
|
report_broken_links
|
|
97
|
+
report_kit_violations
|
|
91
98
|
report_rendered
|
|
92
99
|
end
|
|
93
100
|
|
|
@@ -107,10 +114,115 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
107
114
|
broken.each { |b| puts ConsoleReporter.warn_detail(" #{b[:document] || '?'}: #{b[:target]}") }
|
|
108
115
|
end
|
|
109
116
|
|
|
117
|
+
# Builds the per-row WorkItem dependency network (ADR-194): registers every
|
|
118
|
+
# Scope-row work item, fills the intra-record step-order edges, then resolves
|
|
119
|
+
# each Depends On reference globally (LinkRegistry) to the activity-type-aligned
|
|
120
|
+
# work item of the target record, tagging each cross-record edge in-group or
|
|
121
|
+
# cross-group against the decision_groups boundary (ADR-197). Unresolved
|
|
122
|
+
# references are collected for report_kit_violations. Runs after
|
|
123
|
+
# build_link_registry (so every record resolves) and before rendering (so the
|
|
124
|
+
# overview Kit column is ready).
|
|
125
|
+
def link_work_items
|
|
126
|
+
@kit_unresolved = []
|
|
127
|
+
@project_data.decisions.each do |d|
|
|
128
|
+
d.scope_work_items.each { |wi| @project_data.work_items[wi.id] = wi }
|
|
129
|
+
end
|
|
130
|
+
link_intra_record_steps
|
|
131
|
+
link_cross_record_dependencies
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Each row's lower-numbered same-record steps are its predecessors; equal step
|
|
135
|
+
# numbers are concurrent (no edge). These edges are in-group by definition.
|
|
136
|
+
def link_intra_record_steps
|
|
137
|
+
@project_data.decisions.each do |d|
|
|
138
|
+
items = d.scope_work_items
|
|
139
|
+
items.each do |wi|
|
|
140
|
+
items.each do |other|
|
|
141
|
+
next if other.equal?(wi) || other.step >= wi.step
|
|
142
|
+
|
|
143
|
+
wi.add_predecessor(other, cross_group: false)
|
|
144
|
+
other.add_successor(wi)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def link_cross_record_dependencies
|
|
151
|
+
@project_data.decisions.each do |d|
|
|
152
|
+
d.scope_work_items.each do |wi|
|
|
153
|
+
wi.depends_on_refs.each { |ref| link_dependency(d, wi, ref) }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def link_dependency(record, work_item, ref)
|
|
159
|
+
target = @project_data.link_registry.find_by_id(ref)
|
|
160
|
+
unless target.is_a?(Decision)
|
|
161
|
+
@kit_unresolved << { record: record.id, target: ref }
|
|
162
|
+
return
|
|
163
|
+
end
|
|
164
|
+
prereq = aligned_work_item(target, work_item.activity)
|
|
165
|
+
return if prereq.nil? || prereq.equal?(work_item)
|
|
166
|
+
|
|
167
|
+
cross = decision_group_name(record) != decision_group_name(target)
|
|
168
|
+
work_item.add_predecessor(prereq, cross_group: cross)
|
|
169
|
+
prereq.add_successor(work_item)
|
|
170
|
+
anchor = target.scope_table&.step_column? ? prereq.row_anchor : nil
|
|
171
|
+
work_item.add_resolved_dependency(ref, target, anchor, prereq.id)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# The target record's work item whose activity (Item) matches `activity`,
|
|
175
|
+
# falling back to the nearest earlier activity by canonical phase order, then
|
|
176
|
+
# (when the target has only later activities) to its earliest row. nil only
|
|
177
|
+
# when the target has no Scope rows.
|
|
178
|
+
def aligned_work_item(target, activity)
|
|
179
|
+
items = target.scope_work_items
|
|
180
|
+
return nil if items.empty?
|
|
181
|
+
|
|
182
|
+
exact = items.select { |t| t.activity == activity }.min_by(&:step)
|
|
183
|
+
return exact if exact
|
|
184
|
+
|
|
185
|
+
rank = WorkItem::ACTIVITY_ORDER.index(activity) || WorkItem::ACTIVITY_ORDER.length
|
|
186
|
+
earlier = items.select { |t| t.activity_rank <= rank }
|
|
187
|
+
return items.min_by { |t| [t.activity_rank, t.step] } if earlier.empty?
|
|
188
|
+
|
|
189
|
+
earlier.min_by { |t| [-t.activity_rank, t.step] }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# The planning-group name (first-level decisions/ folder) a record belongs to,
|
|
193
|
+
# read from the decision_groups collection (ADR-197).
|
|
194
|
+
def decision_group_name(doc)
|
|
195
|
+
group = @project_data.decision_groups.find { |g| g.values.first.include?(doc) }
|
|
196
|
+
group&.keys&.first
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Reports the two kit gates and unresolved Depends On references (ADR-194), all
|
|
200
|
+
# as non-failing console warnings alongside report_broken_links.
|
|
201
|
+
def report_kit_violations
|
|
202
|
+
@kit_unresolved ||= []
|
|
203
|
+
phase = @project_data.work_items.each_value.select(&:phase_order_violation?)
|
|
204
|
+
cross = @project_data.work_items.each_value.select(&:cross_record_violation?)
|
|
205
|
+
total = phase.length + cross.length + @kit_unresolved.length
|
|
206
|
+
return if total.zero?
|
|
207
|
+
|
|
208
|
+
ConsoleReporter.warn('kit violations', total)
|
|
209
|
+
phase.each do |wi|
|
|
210
|
+
blocking = wi.intra_record_predecessors.reject(&:done?).map(&:id).join(', ')
|
|
211
|
+
puts ConsoleReporter.warn_detail(" phase order: #{wi.id} started before #{blocking}")
|
|
212
|
+
end
|
|
213
|
+
cross.each do |wi|
|
|
214
|
+
blocking = wi.cross_record_predecessors.reject(&:done?).map(&:id).join(', ')
|
|
215
|
+
puts ConsoleReporter.warn_detail(" not kitted: #{wi.id} needs #{blocking}")
|
|
216
|
+
end
|
|
217
|
+
@kit_unresolved.each do |u|
|
|
218
|
+
puts ConsoleReporter.warn_detail(" unresolved Depends On: #{u[:record]} -> #{u[:target]}")
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
110
222
|
# Assigns each document its generated output path (relative to the build root)
|
|
111
223
|
# and registers it for cross-document link resolution (ADR-186). Runs after all
|
|
112
224
|
# documents are parsed and before any rendering, so link targets are known.
|
|
113
|
-
def build_link_registry
|
|
225
|
+
def build_link_registry
|
|
114
226
|
reg = @project_data.link_registry
|
|
115
227
|
TextLine.link_registry = reg
|
|
116
228
|
TextLine.reset_broken_links
|
|
@@ -177,11 +289,26 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
177
289
|
rel_dir = File.dirname(f.sub("#{decisions_root}/", ''))
|
|
178
290
|
doc.html_rel_path = rel_dir == '.' ? "#{doc.id}.html" : "#{rel_dir}/#{doc.id}.html"
|
|
179
291
|
@project_data.decisions.append(doc)
|
|
292
|
+
add_to_decision_group(doc, rel_dir)
|
|
180
293
|
end
|
|
181
294
|
BaseDocument.show_decisions_link = @project_data.decisions.any?
|
|
182
295
|
ConsoleReporter.count('parsing decisions', @project_data.decisions.length)
|
|
183
296
|
end
|
|
184
297
|
|
|
298
|
+
# Add a decision record to its planning group, keyed on the first-level folder
|
|
299
|
+
# under decisions/ (a record directly under decisions/ has rel_dir '.', kept as
|
|
300
|
+
# its own '.' group rather than dropped). Groups are single-key hashes appended
|
|
301
|
+
# in folder-encounter order; the matching one is reused. See ADR-197.
|
|
302
|
+
def add_to_decision_group(doc, rel_dir)
|
|
303
|
+
group_name = rel_dir.split('/').first
|
|
304
|
+
group = @project_data.decision_groups.find { |g| g.key?(group_name) }
|
|
305
|
+
if group.nil?
|
|
306
|
+
@project_data.decision_groups.append({ group_name => [doc] })
|
|
307
|
+
else
|
|
308
|
+
group[group_name].append(doc)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
185
312
|
def parse_test_run(test_run)
|
|
186
313
|
path = @configuration.project_root_directory
|
|
187
314
|
Dir.glob("#{path}/tests/runs/#{test_run}/**/*.md").each do |f|
|
|
@@ -190,7 +317,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
190
317
|
end
|
|
191
318
|
end
|
|
192
319
|
|
|
193
|
-
def link_all_specifications
|
|
320
|
+
def link_all_specifications
|
|
194
321
|
comb_list = @project_data.specifications.combination(2)
|
|
195
322
|
comb_list.each do |c|
|
|
196
323
|
link_two_specifications(c[0], c[1])
|
|
@@ -209,7 +336,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
209
336
|
ConsoleReporter.count('traceability matrices', @project_data.traceability_matrices.length)
|
|
210
337
|
end
|
|
211
338
|
|
|
212
|
-
def link_all_protocols
|
|
339
|
+
def link_all_protocols
|
|
213
340
|
@project_data.protocols.each do |p|
|
|
214
341
|
@project_data.specifications.each do |s|
|
|
215
342
|
if p.up_link_docs.key?(s.id.to_s)
|
|
@@ -250,7 +377,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
250
377
|
ConsoleReporter.count('implementation matrices', @project_data.implementation_matrices.length)
|
|
251
378
|
end
|
|
252
379
|
|
|
253
|
-
def check_wrong_specification_referenced
|
|
380
|
+
def check_wrong_specification_referenced
|
|
254
381
|
available_specification_ids = {}
|
|
255
382
|
|
|
256
383
|
@project_data.specifications.each do |s|
|
|
@@ -280,7 +407,7 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
280
407
|
end
|
|
281
408
|
end
|
|
282
409
|
|
|
283
|
-
def link_two_specifications(doc_a, doc_b)
|
|
410
|
+
def link_two_specifications(doc_a, doc_b)
|
|
284
411
|
if doc_b.up_link_docs.key?(doc_a.id.to_s)
|
|
285
412
|
top_document = doc_a
|
|
286
413
|
bottom_document = doc_b
|
|
@@ -383,7 +510,17 @@ class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
|
|
383
510
|
doc.to_html("#{path}/build/decisions/")
|
|
384
511
|
end
|
|
385
512
|
|
|
386
|
-
def
|
|
513
|
+
def render_critical_chain_page
|
|
514
|
+
return if @project_data.decisions.empty?
|
|
515
|
+
|
|
516
|
+
path = @configuration.project_root_directory
|
|
517
|
+
FileUtils.mkdir_p("#{path}/build/decisions")
|
|
518
|
+
|
|
519
|
+
doc = DocFabric.create_critical_chain_page(@project)
|
|
520
|
+
doc.to_html("#{path}/build/decisions/")
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def render_all_decisions
|
|
387
524
|
return if @project_data.decisions.empty?
|
|
388
525
|
|
|
389
526
|
build_decisions_root = "#{@configuration.project_root_directory}/build/decisions"
|
|
@@ -1,42 +1,139 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
|
+
require 'date'
|
|
2
3
|
|
|
3
4
|
class ProjectConfiguration
|
|
4
|
-
|
|
5
|
+
DEFAULT_WIP_LIMIT = 2
|
|
6
|
+
DEFAULT_BUFFER_RATIO = 0.5
|
|
7
|
+
DEFAULT_HOURS_PER_DAY = 8
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
@project_root_directory = File.expand_path(path)
|
|
8
|
-
@parameters = {}
|
|
9
|
-
load_project_file
|
|
10
|
-
end
|
|
9
|
+
attr_accessor :project_root_directory, :parameters
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
puts 'Project file not found: project.yml'
|
|
18
|
-
end
|
|
11
|
+
def initialize(path)
|
|
12
|
+
@project_root_directory = File.expand_path(path)
|
|
13
|
+
@parameters = {}
|
|
14
|
+
load_project_file
|
|
15
|
+
end
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
def load_project_file
|
|
18
|
+
@parameters = YAML.load_file(@project_root_directory + '/project.yml')
|
|
19
|
+
rescue Psych::SyntaxError => e
|
|
20
|
+
puts "YAML syntax error: #{e.message}"
|
|
21
|
+
rescue Errno::ENOENT
|
|
22
|
+
puts 'Project file not found: project.yml'
|
|
23
|
+
end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
def get_design_inputs
|
|
26
|
+
return @parameters['specifications']['input'] if (@parameters.key? 'specifications') and (@parameters['specifications'].key? 'input')
|
|
27
|
+
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_repositories
|
|
32
|
+
return @parameters['repositories'] if @parameters.key? 'repositories'
|
|
33
|
+
|
|
34
|
+
[]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get_wip_limit
|
|
38
|
+
return DEFAULT_WIP_LIMIT unless @parameters.is_a?(Hash)
|
|
39
|
+
|
|
40
|
+
planning = @parameters['planning']
|
|
41
|
+
return DEFAULT_WIP_LIMIT unless planning.is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
value = planning['wip_limit']
|
|
44
|
+
return DEFAULT_WIP_LIMIT unless value.is_a?(Integer) && value.positive?
|
|
45
|
+
|
|
46
|
+
value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The CCPM project-buffer ratio (ADR-195): a fraction in (0, 1] cutting the
|
|
50
|
+
# aggregated chain safety. Absent or out-of-range falls back to the 0.5 default.
|
|
51
|
+
def get_buffer_ratio
|
|
52
|
+
return DEFAULT_BUFFER_RATIO unless @parameters.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
planning = @parameters['planning']
|
|
55
|
+
return DEFAULT_BUFFER_RATIO unless planning.is_a?(Hash)
|
|
56
|
+
|
|
57
|
+
value = planning['buffer_ratio']
|
|
58
|
+
return DEFAULT_BUFFER_RATIO unless value.is_a?(Numeric) && value.positive? && value <= 1
|
|
27
59
|
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
value
|
|
61
|
+
end
|
|
30
62
|
|
|
31
|
-
|
|
63
|
+
# The calendar anchor for working day 1 (ADR-205), a Date parsed from a
|
|
64
|
+
# DD-MM-YYYY planning.start_date. Absent or unparseable falls back to today,
|
|
65
|
+
# so an unconfigured project still renders (a moving anchor).
|
|
66
|
+
def get_start_date
|
|
67
|
+
date = parse_planning_date(planning_value('start_date'))
|
|
68
|
+
date || Date.today
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Per-group planning start dates (ADR-211): a map from a decision group's
|
|
72
|
+
# first-level folder name (under decisions/) to its start Date, read from
|
|
73
|
+
# planning.groups as DD-MM-YYYY entries. Non-string or unparseable values are
|
|
74
|
+
# dropped; empty when unset. A group absent from the map sequences after the
|
|
75
|
+
# previous group rather than carrying a declared start.
|
|
76
|
+
def get_group_start_dates
|
|
77
|
+
value = planning_value('groups')
|
|
78
|
+
return {} unless value.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
value.each_with_object({}) do |(name, raw), acc|
|
|
81
|
+
date = parse_planning_date(raw)
|
|
82
|
+
acc[name.to_s] = date if date
|
|
32
83
|
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The non-working holiday dates (ADR-205) from planning.holidays, each a
|
|
87
|
+
# DD-MM-YYYY entry; unparseable entries are dropped. Empty when unset.
|
|
88
|
+
def get_holidays
|
|
89
|
+
value = planning_value('holidays')
|
|
90
|
+
return [] unless value.is_a?(Array)
|
|
91
|
+
|
|
92
|
+
value.filter_map { |entry| parse_planning_date(entry) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Working hours per day (ADR-196): converts logged effort hours into the
|
|
96
|
+
# working-day unit the estimates use. Absent or non-positive falls back to 8.
|
|
97
|
+
def get_hours_per_day
|
|
98
|
+
return DEFAULT_HOURS_PER_DAY unless @parameters.is_a?(Hash)
|
|
99
|
+
|
|
100
|
+
planning = @parameters['planning']
|
|
101
|
+
return DEFAULT_HOURS_PER_DAY unless planning.is_a?(Hash)
|
|
102
|
+
|
|
103
|
+
value = planning['hours_per_day']
|
|
104
|
+
return DEFAULT_HOURS_PER_DAY unless value.is_a?(Numeric) && value.positive?
|
|
33
105
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
106
|
+
value
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def is_spec_db_shall_be_created
|
|
110
|
+
if @parameters.key? 'output'
|
|
111
|
+
@parameters['output'].each do |p|
|
|
112
|
+
return true if p == 'specifications_db'
|
|
113
|
+
end
|
|
41
114
|
end
|
|
115
|
+
false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# A value under the planning: key, or nil when planning is absent.
|
|
119
|
+
def planning_value(key)
|
|
120
|
+
return nil unless @parameters.is_a?(Hash)
|
|
121
|
+
|
|
122
|
+
planning = @parameters['planning']
|
|
123
|
+
planning.is_a?(Hash) ? planning[key] : nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Parse a planning date that may already be a Date (YAML ISO form) or a
|
|
127
|
+
# DD-MM-YYYY string; nil when neither.
|
|
128
|
+
def parse_planning_date(value)
|
|
129
|
+
return value if value.is_a?(Date)
|
|
130
|
+
return nil unless value.is_a?(String)
|
|
131
|
+
|
|
132
|
+
match = /\A(\d{2})-(\d{2})-(\d{4})\z/.match(value.strip)
|
|
133
|
+
return nil unless match
|
|
134
|
+
|
|
135
|
+
Date.new(match[3].to_i, match[2].to_i, match[1].to_i)
|
|
136
|
+
rescue ArgumentError
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
42
139
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'fileutils'
|
|
4
4
|
|
|
5
|
-
class ProjectTemplate
|
|
5
|
+
class ProjectTemplate
|
|
6
6
|
attr_accessor :project_root
|
|
7
7
|
|
|
8
8
|
def initialize(project_name)
|
|
@@ -324,11 +324,11 @@ class ProjectTemplate # rubocop:disable Style/Documentation
|
|
|
324
324
|
|
|
325
325
|
# Scope
|
|
326
326
|
|
|
327
|
-
| Item | Status | Start Date | Target Date | Description |
|
|
328
|
-
|
|
329
|
-
| Requirements | To Do | | | |
|
|
330
|
-
| Code | To Do | | | |
|
|
331
|
-
| Tests | To Do | | | |
|
|
327
|
+
| # | Item | Owner | Depends On | Est (focused) | Est (safe) | Status | Start Date | Target Date | Description |
|
|
328
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
329
|
+
| 1 | Requirements | BA | | 2 | 3 | To Do | | | |
|
|
330
|
+
| 2 | Code | DEV | | 3 | 5 | To Do | | | |
|
|
331
|
+
| 3 | Tests | TEST | | 2 | 4 | To Do | | | |
|
|
332
332
|
|
|
333
333
|
# Out of Scope
|
|
334
334
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'fileutils'
|
|
4
4
|
|
|
5
|
-
class ProjectUtility
|
|
5
|
+
class ProjectUtility
|
|
6
6
|
attr_accessor :configuration
|
|
7
7
|
|
|
8
8
|
def initialize(configuration)
|
|
@@ -19,7 +19,7 @@ class ProjectUtility # rubocop:disable Style/Documentation
|
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
def combine(run_id)
|
|
22
|
+
def combine(run_id)
|
|
23
23
|
path = @configuration.project_root_directory
|
|
24
24
|
dst_folder = "#{@configuration.project_root_directory}/build"
|
|
25
25
|
FileUtils.mkdir_p(dst_folder)
|
|
@@ -29,9 +29,7 @@ class ProjectUtility # rubocop:disable Style/Documentation
|
|
|
29
29
|
dst_f = File.open(dst_file, 'a')
|
|
30
30
|
|
|
31
31
|
src_path = if run_id
|
|
32
|
-
unless Dir.exist? "#{path}/tests/runs/#{run_id}"
|
|
33
|
-
puts "\e[1m\e[31m Run #{run_id} folder does not exists"
|
|
34
|
-
end
|
|
32
|
+
puts "\e[1m\e[31m Run #{run_id} folder does not exists" unless Dir.exist? "#{path}/tests/runs/#{run_id}"
|
|
35
33
|
"#{path}/tests/runs/#{run_id}/**/*.md"
|
|
36
34
|
else
|
|
37
35
|
"#{path}/tests/protocols/**/*.md"
|
|
@@ -12,7 +12,7 @@ class SpecificationsDb
|
|
|
12
12
|
create_data
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def create_data
|
|
15
|
+
def create_data
|
|
16
16
|
@specifications.each do |sp|
|
|
17
17
|
sp.items.each do |i|
|
|
18
18
|
if (i.instance_of? Paragraph) or (i.instance_of? ControlledParagraph)
|
|
@@ -31,7 +31,7 @@ class SpecificationsDb
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def add_markdown_list_item_to_db(data, item_for_reference, item_to_process)
|
|
34
|
+
def add_markdown_list_item_to_db(data, item_for_reference, item_to_process)
|
|
35
35
|
e = nil
|
|
36
36
|
item_to_process.rows.each do |r|
|
|
37
37
|
if r.is_a?(MarkdownList)
|
|
@@ -2,7 +2,7 @@ require_relative 'doc_items/doc_item'
|
|
|
2
2
|
require_relative 'doc_items/heading'
|
|
3
3
|
require_relative 'doc_items/source_code_paragraph'
|
|
4
4
|
|
|
5
|
-
class SourceFileParser
|
|
5
|
+
class SourceFileParser
|
|
6
6
|
def self.parse(doc, file_lines)
|
|
7
7
|
# restart section numbering for each new document
|
|
8
8
|
Heading.reset_global_section_number
|
|
@@ -19,7 +19,7 @@ class SourceFileParser # rubocop:disable Style/Documentation
|
|
|
19
19
|
doc.headings.append(item)
|
|
20
20
|
|
|
21
21
|
# main loop
|
|
22
|
-
file_lines.each do |s|
|
|
22
|
+
file_lines.each do |s|
|
|
23
23
|
res = %r{<REQ>(.*)</REQ>}.match(s) # Document Referece Item
|
|
24
24
|
next unless res
|
|
25
25
|
|
|
@@ -36,7 +36,6 @@ class SourceFileParser # rubocop:disable Style/Documentation
|
|
|
36
36
|
up_links = []
|
|
37
37
|
tmp.each do |ul|
|
|
38
38
|
lnk = ul[0]
|
|
39
|
-
#
|
|
40
39
|
doc_id = /([a-zA-Z]+)-\d+/.match(lnk) # SRS
|
|
41
40
|
up_links << lnk.upcase if doc_id
|
|
42
41
|
# try to find the real end of text
|