dim-toolkit 2.1.0
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 +7 -0
- data/bin/dim +9 -0
- data/lib/dim/commands/check.rb +13 -0
- data/lib/dim/commands/export.rb +145 -0
- data/lib/dim/commands/format.rb +198 -0
- data/lib/dim/commands/stats.rb +64 -0
- data/lib/dim/consistency.rb +187 -0
- data/lib/dim/dimmain.rb +28 -0
- data/lib/dim/encoding.rb +4 -0
- data/lib/dim/exit_helper.rb +23 -0
- data/lib/dim/exporter/csv.rb +25 -0
- data/lib/dim/exporter/exporterInterface.rb +37 -0
- data/lib/dim/exporter/json.rb +32 -0
- data/lib/dim/exporter/rst.rb +142 -0
- data/lib/dim/ext/psych.rb +63 -0
- data/lib/dim/ext/string.rb +85 -0
- data/lib/dim/globals.rb +12 -0
- data/lib/dim/helpers/attribute_helper.rb +126 -0
- data/lib/dim/helpers/file_helper.rb +25 -0
- data/lib/dim/loader.rb +581 -0
- data/lib/dim/options.rb +116 -0
- data/lib/dim/requirement.rb +236 -0
- data/lib/dim/ver.rb +7 -0
- data/lib/dim.rb +1 -0
- data/license.txt +205 -0
- data/version.txt +1 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2782516b43e4c6b75d4e810cb13dd1c110ab7f5f8f1eecbf29bfb530ec8d3a6b
|
4
|
+
data.tar.gz: 73c4f999afb166a82d584101f72aba0d6f170dc5981dff903deb77490c96abb8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5674b913de138615cd8902896f24bea48d66df18a71c47af7ba282a39cd46907944447b7964537fc48d100364e972cca8aabede2754dd1e478f08f0698afc7f8
|
7
|
+
data.tar.gz: 1b693a54304ccbb093185444b32389e53788b87c3446812cd5fa49453898d3c53a60b4d327614671787f5bce5bc31ccf94d1dbbdb3ec1ebd0d27f7cf2f098637
|
data/bin/dim
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative '../globals'
|
2
|
+
|
3
|
+
module Dim
|
4
|
+
class Check
|
5
|
+
SUBCOMMANDS['check'] = self
|
6
|
+
|
7
|
+
def initialize(_loader); end
|
8
|
+
|
9
|
+
def execute(silent: true)
|
10
|
+
# checks are always done after loading, so this is just a placeholder for command line option
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
require_relative '../globals'
|
5
|
+
require_relative '../exporter/exporterInterface'
|
6
|
+
require_relative '../exporter/rst'
|
7
|
+
require_relative '../exporter/csv'
|
8
|
+
require_relative '../exporter/json'
|
9
|
+
require_relative 'check'
|
10
|
+
|
11
|
+
module Dim
|
12
|
+
class Export
|
13
|
+
SUBCOMMANDS['export'] = self
|
14
|
+
|
15
|
+
attr_accessor :file_list, :export_dir
|
16
|
+
|
17
|
+
def initialize(loader)
|
18
|
+
@loader = loader
|
19
|
+
@file_list = []
|
20
|
+
@export_dir = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute(silent: true)
|
24
|
+
@silent = silent
|
25
|
+
@loader.filter(OPTIONS[:filter]) unless OPTIONS[:filter].empty?
|
26
|
+
|
27
|
+
puts 'Exporting...' unless @silent
|
28
|
+
export
|
29
|
+
clean_destination
|
30
|
+
puts 'Done.' unless @silent
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy_files(mod)
|
34
|
+
@loader.module_data[mod][:files].each do |f, list|
|
35
|
+
d = File.dirname(f)
|
36
|
+
|
37
|
+
list.uniq.each do |l|
|
38
|
+
src_pattern = File.join(d, l)
|
39
|
+
Dir.glob(src_pattern).each do |src|
|
40
|
+
src = Pathname.new(src).cleanpath.to_s
|
41
|
+
dst = File.join(OPTIONS[:folder], mod.sanitize, Pathname.new(src).relative_path_from(Pathname(d)))
|
42
|
+
@file_list << dst
|
43
|
+
FileUtils.mkdir_p(File.dirname(dst))
|
44
|
+
|
45
|
+
if File.exist?(dst)
|
46
|
+
md5_src = Digest::MD5.file(src)
|
47
|
+
md5_dst = Digest::MD5.file(dst)
|
48
|
+
next if md5_src == md5_dst
|
49
|
+
end
|
50
|
+
|
51
|
+
puts "Copying #{src} to #{dst}..." unless @silent
|
52
|
+
FileUtils.cp_r(src, dst)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_content(filename, content)
|
59
|
+
old_content = (File.exist?(filename) ? File.read(filename) : nil)
|
60
|
+
if content != old_content
|
61
|
+
File.write(filename, content)
|
62
|
+
puts ' done' unless @silent
|
63
|
+
else
|
64
|
+
puts ' skipped' unless @silent
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_index(requirements_by_module)
|
69
|
+
return unless @exporter.hasIndex
|
70
|
+
|
71
|
+
files = {}
|
72
|
+
@loader.module_data.each do |mod, data|
|
73
|
+
next if requirements_by_module[mod].empty?
|
74
|
+
|
75
|
+
c = data[:category]
|
76
|
+
o = data[:origin]
|
77
|
+
files[c] ||= {}
|
78
|
+
files[c][o] ||= []
|
79
|
+
files[c][o] << mod
|
80
|
+
end
|
81
|
+
ind = 0
|
82
|
+
files.each do |category, data|
|
83
|
+
data.each do |origin, modules|
|
84
|
+
ind += 1
|
85
|
+
filename = "index_#{format('%03d', ind)}_#{category.downcase}_#{origin.downcase.sanitize}"
|
86
|
+
filepath = "#{OPTIONS[:folder]}/#{filename}.#{OPTIONS[:type]}"
|
87
|
+
file_list << filepath
|
88
|
+
new_content = StringIO.new
|
89
|
+
print "Creating #{filepath}..." unless @silent
|
90
|
+
@exporter.index(new_content, category, origin, modules)
|
91
|
+
write_content(filepath, new_content.string)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def export
|
97
|
+
requirements_by_module = {}
|
98
|
+
module_keys = @loader.module_data.keys
|
99
|
+
module_keys.each { |um| requirements_by_module[um] = [] }
|
100
|
+
@loader.requirements.each { |_id, r| requirements_by_module[r.document] << r }
|
101
|
+
|
102
|
+
@exporter = EXPORTER[OPTIONS[:type]].new(@loader)
|
103
|
+
requirements_by_module.each do |doc, reqs|
|
104
|
+
next if reqs.empty?
|
105
|
+
|
106
|
+
@export_dir = File.join(OPTIONS[:folder])
|
107
|
+
filename = File.join(export_dir, doc.sanitize, "Requirements.#{OPTIONS[:type]}")
|
108
|
+
file_list << filename
|
109
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
110
|
+
copy_files(doc)
|
111
|
+
|
112
|
+
new_content = StringIO.new
|
113
|
+
print "Creating #{filename}..." unless @silent
|
114
|
+
@exporter.header(new_content)
|
115
|
+
@exporter.document(new_content, doc)
|
116
|
+
meta = @loader.metadata[doc]
|
117
|
+
@exporter.metadata(new_content, meta) if meta != ''
|
118
|
+
reqs.each do |r|
|
119
|
+
@exporter.requirement(new_content, r)
|
120
|
+
end
|
121
|
+
@exporter.footer(new_content)
|
122
|
+
write_content(filename, new_content.string)
|
123
|
+
end
|
124
|
+
|
125
|
+
create_index(requirements_by_module)
|
126
|
+
end
|
127
|
+
|
128
|
+
def clean_destination(dir_path = export_dir)
|
129
|
+
Dir.new(dir_path).children.each do |file|
|
130
|
+
dst = File.join(dir_path, file)
|
131
|
+
path = Pathname.new(dst)
|
132
|
+
|
133
|
+
clean_destination(dst) if path.directory?
|
134
|
+
|
135
|
+
next if file_list.include? dst
|
136
|
+
|
137
|
+
if path.directory?
|
138
|
+
path.rmdir if path.empty?
|
139
|
+
else
|
140
|
+
path.delete
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require_relative '../globals'
|
4
|
+
|
5
|
+
module Dim
|
6
|
+
class Format
|
7
|
+
SUBCOMMANDS['format'] = self
|
8
|
+
TARGET_WIDTH = 120
|
9
|
+
TARGET_WIDTH_ONE_LINER = 150
|
10
|
+
WIDTH_MIN = 50 # used when ID in one-liner or language-attr is very long
|
11
|
+
|
12
|
+
def initialize(loader)
|
13
|
+
@loader = loader
|
14
|
+
end
|
15
|
+
|
16
|
+
def short_type(type)
|
17
|
+
return "h#{type[8..]}" if /\Aheading_(\d+)\z/ === type
|
18
|
+
|
19
|
+
'info' # type == "information"
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_string_array(val, line_width, line_width_first)
|
23
|
+
input = val.strip
|
24
|
+
return ['|'] + input.split("\n") if input.include?("\n")
|
25
|
+
|
26
|
+
if "#,[]{}&*!|>\)\"%@`'?:-".include?(input[0]) || input =~ /(:(\s|\Z)|\s#)/ || (input.length > line_width_first && input =~ /\s\s/)
|
27
|
+
return ['|', input] if input.length <= line_width_first
|
28
|
+
|
29
|
+
res = ['>']
|
30
|
+
else
|
31
|
+
res = []
|
32
|
+
end
|
33
|
+
until input.empty?
|
34
|
+
width = (res.empty? ? line_width_first : line_width)
|
35
|
+
if input.length <= width
|
36
|
+
res << input
|
37
|
+
break
|
38
|
+
end
|
39
|
+
pos = input[0, width + 2].rindex(/ \w/)
|
40
|
+
if pos
|
41
|
+
res << input[0, pos]
|
42
|
+
input = input[pos + 1..]
|
43
|
+
else
|
44
|
+
pos = input.index(/ \w/)
|
45
|
+
if pos
|
46
|
+
res << input[0, pos]
|
47
|
+
input = input[pos + 1..]
|
48
|
+
else
|
49
|
+
res << input
|
50
|
+
break
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
res
|
55
|
+
end
|
56
|
+
|
57
|
+
def dump(attr, val, shift, target_width = TARGET_WIDTH)
|
58
|
+
shift_first = [shift - attr.length, 0].max
|
59
|
+
|
60
|
+
line_width = [target_width - shift, WIDTH_MIN].max
|
61
|
+
line_width_first = [target_width - [shift, attr.length].max, WIDTH_MIN].max
|
62
|
+
|
63
|
+
array = create_string_array(val, line_width, line_width_first)
|
64
|
+
array[0] = attr + ' ' * shift_first + array[0]
|
65
|
+
(1..array.length - 1).each do |i|
|
66
|
+
array[i] = ' ' * shift + array[i]
|
67
|
+
end
|
68
|
+
array
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_file(data)
|
72
|
+
io = StringIO.new
|
73
|
+
|
74
|
+
io.puts "document: #{data['document']}\n\n"
|
75
|
+
|
76
|
+
if data.key?('metadata') && !data['metadata'].empty?
|
77
|
+
io.puts dump('metadata: ', data['metadata'], 10)
|
78
|
+
io.puts "\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
data.each do |id, req|
|
82
|
+
next if %w[module enclosed document].include? id
|
83
|
+
|
84
|
+
# do not write default values
|
85
|
+
next unless req.is_a?(Hash)
|
86
|
+
|
87
|
+
req.delete_if { |_key, value| value.nil? }
|
88
|
+
|
89
|
+
req.delete('test_setups')
|
90
|
+
|
91
|
+
%w[tags verification_methods refs].each do |e|
|
92
|
+
req[e] = req[e].cleanUniqString if req.key?(e) && req[e].is_a?(String)
|
93
|
+
end
|
94
|
+
|
95
|
+
# use oneliner style for headings and information if reasonable
|
96
|
+
if req.keys.length == 2 &&
|
97
|
+
%w[type text].all? { |e| req.key?(e) } &&
|
98
|
+
(req['type'] =~ /\Aheading_\d+\Z/ ||
|
99
|
+
req['type'] =~ /\Ainformation\Z/)
|
100
|
+
stripped = req['text'].strip
|
101
|
+
unless stripped.include?("\n")
|
102
|
+
one_liner = "#{short_type(req['type'])} #{req['text']}"
|
103
|
+
str = dump("#{id}: ", one_liner, Requirement::SYNTAX['text'][:format_shift], TARGET_WIDTH_ONE_LINER)
|
104
|
+
if str.length == 1
|
105
|
+
io.puts str
|
106
|
+
io.puts "\n"
|
107
|
+
next
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
io.puts "#{id}:"
|
113
|
+
@loader.all_attributes.each_key do |k|
|
114
|
+
next unless req.key?(k)
|
115
|
+
|
116
|
+
if req[k] && req[k].empty? || req[k].nil?
|
117
|
+
io.puts " #{k}:"
|
118
|
+
next
|
119
|
+
end
|
120
|
+
shift = [@loader.all_attributes[k][:format_shift], k.length + 4].max
|
121
|
+
case @loader.all_attributes[k][:format_style]
|
122
|
+
when :single
|
123
|
+
shift_str = ' ' * [shift - (k.length + 4), 0].max
|
124
|
+
io.puts " #{k}: #{shift_str}#{req[k].strip}"
|
125
|
+
when :multi
|
126
|
+
shift_str = ' ' * [shift - (k.length + 4), 0].max
|
127
|
+
io.puts " #{k}: #{shift_str}#{req[k].cleanUniqString}"
|
128
|
+
when :list
|
129
|
+
io.puts dump(" #{k}: ", req[k].cleanUniqString, shift)
|
130
|
+
when :text
|
131
|
+
io.puts dump(" #{k}: ", req[k], shift)
|
132
|
+
when :split
|
133
|
+
arr = req[k].cleanUniqArray
|
134
|
+
|
135
|
+
if arr.length == 1
|
136
|
+
io.puts " #{k}: #{shift_str}#{arr[0]}"
|
137
|
+
else
|
138
|
+
io.puts " #{k}: #{shift_str}>"
|
139
|
+
shift_str = ' ' * shift
|
140
|
+
arr.each_with_index do |a, i|
|
141
|
+
io.puts "#{shift_str}#{a}#{i < arr.length - 1 ? ',' : ''}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
else
|
145
|
+
Dim::ExitHelper.exit(code: 1, msg: 'Invalid format style')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
io.puts "\n"
|
150
|
+
end
|
151
|
+
|
152
|
+
if data.key?('enclosed')
|
153
|
+
a = Array(data['enclosed'])
|
154
|
+
if a.length == 1
|
155
|
+
io.puts "enclosed: #{a[0]}\n\n"
|
156
|
+
else
|
157
|
+
io.puts "enclosed:\n"
|
158
|
+
Array(data['enclosed']).each do |e|
|
159
|
+
io.puts " - #{e}"
|
160
|
+
end
|
161
|
+
io.puts "\n"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
io.truncate(io.length - 1) # do not print two line breaks at the end
|
166
|
+
|
167
|
+
io
|
168
|
+
end
|
169
|
+
|
170
|
+
def execute(silent: true)
|
171
|
+
puts OPTIONS[:output_format] == 'check-only' ? 'Checking format...' : 'Formatting...' unless silent
|
172
|
+
|
173
|
+
incorrect_files = []
|
174
|
+
@loader.original_data.each do |filename, data|
|
175
|
+
formatted_data = format_file(data).string
|
176
|
+
if OPTIONS[:output_format] == 'check-only'
|
177
|
+
original_data = File.read(filename).universal_newline
|
178
|
+
incorrect_files << filename if original_data != formatted_data
|
179
|
+
elsif OPTIONS[:output_format] == 'stdout'
|
180
|
+
puts formatted_data
|
181
|
+
next
|
182
|
+
else
|
183
|
+
output_filename = OPTIONS[:output_format] == 'extra' ? "#{filename}.formatted" : filename
|
184
|
+
File.write(output_filename, formatted_data)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
unless incorrect_files.empty?
|
189
|
+
io = StringIO.new
|
190
|
+
io.puts 'The following files are not formatted correctly:'
|
191
|
+
io.puts(incorrect_files.map { |f| "- #{f}" })
|
192
|
+
Dim::ExitHelper.exit(code: 1, msg: io.string)
|
193
|
+
end
|
194
|
+
|
195
|
+
puts 'Done.' unless silent
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Dim
|
2
|
+
class Stats
|
3
|
+
SUBCOMMANDS['stats'] = self
|
4
|
+
|
5
|
+
def initialize(loader)
|
6
|
+
@loader = loader
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute(silent: true)
|
10
|
+
print_stats
|
11
|
+
end
|
12
|
+
|
13
|
+
def print_owner(type, module_name = nil)
|
14
|
+
if type == :all
|
15
|
+
top = 'ALL'
|
16
|
+
is_owner = proc { |_x| true }
|
17
|
+
else # :module
|
18
|
+
is_owner = proc { |x| x.document == module_name }
|
19
|
+
top = "DOCUMENT: #{module_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "\n#{top}\n#{'-' * top.length}"
|
23
|
+
all_reqs = @loader.requirements.values
|
24
|
+
reqs_owner = all_reqs.select { |r| is_owner.call(r) }
|
25
|
+
|
26
|
+
num_files = if type == :module
|
27
|
+
@loader.module_data[module_name][:files].length
|
28
|
+
else
|
29
|
+
@loader.module_data.values.map { |e| e[:files].keys }.flatten.length
|
30
|
+
end
|
31
|
+
puts "Number of files: #{num_files}"
|
32
|
+
|
33
|
+
if type != :module
|
34
|
+
mods = reqs_owner.map(&:document).uniq
|
35
|
+
puts "Number of modules: #{mods.count}"
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "Number of entries: #{reqs_owner.length}"
|
39
|
+
real_reqs_owner = reqs_owner.select { |r| r.data['type'] == 'requirement' }
|
40
|
+
puts "Requirements: #{real_reqs_owner.length}"
|
41
|
+
valid_reqs = real_reqs_owner.select { |r| r.data['status'] == 'valid' }
|
42
|
+
puts "Valid requirements: #{valid_reqs.length}"
|
43
|
+
accepted = valid_reqs.select { |r| r.data['review_status'] == 'accepted' }
|
44
|
+
puts " Accepted: #{accepted.length}"
|
45
|
+
covered = accepted.select { |r| r.data['tags'].include?('covered') }
|
46
|
+
puts " Covered: #{covered.length}"
|
47
|
+
not_covered_num = accepted.length - covered.length
|
48
|
+
puts " Not covered: #{not_covered_num}"
|
49
|
+
rejected = valid_reqs.select { |r| r.data['review_status'] == 'rejected' }
|
50
|
+
puts " Rejected: #{rejected.length}"
|
51
|
+
unclear = valid_reqs.select { |r| r.data['review_status'] == 'unclear' }
|
52
|
+
puts " Unclear: #{unclear.length}"
|
53
|
+
not_reviewed = valid_reqs.select { |r| r.data['review_status'] == 'not_reviewed' }
|
54
|
+
puts " Not reviewed: #{not_reviewed.length}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def print_stats
|
58
|
+
@loader.requirements.values.map(&:document).uniq.each do |mod|
|
59
|
+
print_owner(:module, mod)
|
60
|
+
end
|
61
|
+
print_owner(:all)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require_relative 'globals'
|
2
|
+
require_relative 'exit_helper'
|
3
|
+
|
4
|
+
module Dim
|
5
|
+
class Consistency
|
6
|
+
def initialize(loader)
|
7
|
+
@loader = loader
|
8
|
+
end
|
9
|
+
|
10
|
+
def cyclic_check(ref, previous_ref, checked_ids = {}, list = [])
|
11
|
+
return if checked_ids[ref.id]
|
12
|
+
|
13
|
+
# Circular dependency can occur only on same category level
|
14
|
+
return if previous_ref && (ref.category_level != previous_ref.category_level)
|
15
|
+
|
16
|
+
if list.include?(ref.id)
|
17
|
+
Dim::ExitHelper.exit(
|
18
|
+
code: 1,
|
19
|
+
filename: ref.filename,
|
20
|
+
msg: "\"#{ref.id}\" is cyclically referenced: #{list.join(' -> ')} -> #{ref.id}"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
list << ref.id
|
25
|
+
|
26
|
+
ref.existingRefs.each do |ref_id|
|
27
|
+
cyclic_check(@loader.requirements[ref_id], ref, checked_ids, list)
|
28
|
+
end
|
29
|
+
|
30
|
+
list.pop
|
31
|
+
|
32
|
+
# Any value other than nil and false will work, as this is being used to check
|
33
|
+
# boolean condition on line#11
|
34
|
+
checked_ids[ref.id] = 1 if list.empty?
|
35
|
+
end
|
36
|
+
private :cyclic_check
|
37
|
+
|
38
|
+
def insert_default_values(req)
|
39
|
+
req.all_attributes.each do |key, config|
|
40
|
+
next if req.data.key?(key)
|
41
|
+
|
42
|
+
req.data[key] = config[:default]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def insert_property_file_definitions(ref)
|
47
|
+
@loader.property_table.fetch(ref.document, {}).each do |attr, value|
|
48
|
+
ref.data[attr] = value if ref.data[attr].nil?
|
49
|
+
|
50
|
+
if attr == 'test_setups' && (ref.data['verification_methods'].nil? || ref.data['verification_methods'] == '')
|
51
|
+
ref.data['verification_methods'] = value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def merge_test_setups_and_verification_methods(ref)
|
57
|
+
return if ref.data.key?('verification_methods') && !ref.data['verification_methods'].nil?
|
58
|
+
|
59
|
+
ref.data['verification_methods'] = ref.data['test_setups'].clone
|
60
|
+
end
|
61
|
+
|
62
|
+
def calculate_test_setups(ref)
|
63
|
+
return unless ref.data['test_setups'].nil?
|
64
|
+
|
65
|
+
tags = ref.data['tags'].cleanArray
|
66
|
+
|
67
|
+
ref.data['test_setups'] = if ref.data['type'] != 'requirement' || tags.include?('process')
|
68
|
+
'none'
|
69
|
+
elsif ref.category == 'input' || ref.category == 'unspecified'
|
70
|
+
'none'
|
71
|
+
elsif ref.category == 'module'
|
72
|
+
'off_target'
|
73
|
+
elsif tags.include?('tool')
|
74
|
+
'off_target'
|
75
|
+
else
|
76
|
+
'on_target'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def calculate_verification_methods(ref)
|
81
|
+
return unless ref.data['verification_methods'].nil?
|
82
|
+
|
83
|
+
tags = ref.data['tags'].cleanArray
|
84
|
+
|
85
|
+
ref.data['verification_methods'] = if ref.data['type'] != 'requirement' || tags.include?('process')
|
86
|
+
'none'
|
87
|
+
elsif ref.category == 'input' || ref.category == 'unspecified'
|
88
|
+
'none'
|
89
|
+
elsif ref.category == 'module'
|
90
|
+
'off_target'
|
91
|
+
elsif tags.include?('tool')
|
92
|
+
'off_target'
|
93
|
+
else
|
94
|
+
'on_target'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def calculate_review_status(ref)
|
99
|
+
return unless ref.data['review_status'].nil?
|
100
|
+
|
101
|
+
ref.data['review_status'] = if ref.category == 'input' || ref.category == 'unspecified'
|
102
|
+
'not_reviewed'
|
103
|
+
else
|
104
|
+
'accepted'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def clean_comma_separated(ref)
|
109
|
+
%w[tags developer tester refs test_setups verification_methods].each do |var|
|
110
|
+
ref.data[var] = ref.data[var].cleanString
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def calculate_developer_tester(ref)
|
115
|
+
%w[developer tester].each do |t|
|
116
|
+
next unless ref.data[t].nil?
|
117
|
+
|
118
|
+
ref.data[t] = if ref.data['type'] != 'requirement'
|
119
|
+
''
|
120
|
+
elsif ref.data['tags'].cleanArray.include?('process') && t == 'tester'
|
121
|
+
''
|
122
|
+
elsif ref.category == 'input' || ref.category == 'unspecified'
|
123
|
+
''
|
124
|
+
else
|
125
|
+
ref.origin
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def calculate_status(ref)
|
131
|
+
return unless ref.data['status'].nil?
|
132
|
+
|
133
|
+
ref.data['status'] = %w[requirement information].include?(ref.data['type']) ? 'draft' : 'valid'
|
134
|
+
end
|
135
|
+
|
136
|
+
def check(allow_missing:)
|
137
|
+
requirements_by_module = {}
|
138
|
+
@loader.module_data.keys.each { |um| requirements_by_module[um] = [] }
|
139
|
+
@loader.requirements.each { |_id, r| requirements_by_module[r.document] << r }
|
140
|
+
|
141
|
+
@loader.requirements.each do |_id, r|
|
142
|
+
insert_property_file_definitions(r)
|
143
|
+
insert_default_values(r)
|
144
|
+
merge_test_setups_and_verification_methods(r)
|
145
|
+
calculate_test_setups(r)
|
146
|
+
calculate_verification_methods(r)
|
147
|
+
calculate_review_status(r)
|
148
|
+
calculate_developer_tester(r)
|
149
|
+
calculate_status(r)
|
150
|
+
end
|
151
|
+
|
152
|
+
@loader.requirements.each do |_id, r|
|
153
|
+
clean_comma_separated(r)
|
154
|
+
end
|
155
|
+
|
156
|
+
@loader.requirements.each do |id, r|
|
157
|
+
r.data['refs'].cleanArray.each do |ref|
|
158
|
+
if !@loader.requirements.has_key?(ref)
|
159
|
+
unless allow_missing
|
160
|
+
Dim::ExitHelper.exit(
|
161
|
+
code: 1,
|
162
|
+
filename: r.filename,
|
163
|
+
msg: "\"#{id}\" refers to non-existing \"#{ref}\""
|
164
|
+
)
|
165
|
+
end
|
166
|
+
else
|
167
|
+
# Generate upstream and downstream refs based on category level
|
168
|
+
if @loader.requirements[id].category_level <= @loader.requirements[ref].category_level
|
169
|
+
@loader.requirements[id].downstreamRefs |= [ref]
|
170
|
+
@loader.requirements[ref].upstreamRefs |= [id]
|
171
|
+
else
|
172
|
+
@loader.requirements[id].upstreamRefs |= [ref]
|
173
|
+
@loader.requirements[ref].downstreamRefs |= [id]
|
174
|
+
end
|
175
|
+
@loader.requirements[ref].backwardRefs << id
|
176
|
+
r.existingRefs << ref
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
@checked_ids = {}
|
182
|
+
@loader.requirements.each do |_id, r|
|
183
|
+
cyclic_check(r, nil, @checked_ids)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/lib/dim/dimmain.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$stdout.sync = true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require_relative 'globals'
|
6
|
+
require_relative 'exit_helper'
|
7
|
+
require_relative 'ext/string'
|
8
|
+
require_relative 'loader'
|
9
|
+
require_relative 'ver'
|
10
|
+
require_relative 'requirement'
|
11
|
+
require_relative 'commands/stats'
|
12
|
+
require_relative 'commands/check'
|
13
|
+
require_relative 'commands/export'
|
14
|
+
require_relative 'commands/format'
|
15
|
+
require_relative 'options'
|
16
|
+
|
17
|
+
module Dim
|
18
|
+
def self.main(args = ARGV)
|
19
|
+
Dim::Options.parse(args)
|
20
|
+
loader = Dim::Loader.new
|
21
|
+
loader.load(file: OPTIONS[:input],
|
22
|
+
attributes_file: OPTIONS[:attributes],
|
23
|
+
allow_missing: OPTIONS[:allow_missing],
|
24
|
+
no_check_enclosed: OPTIONS[:no_check_enclosed] || false,
|
25
|
+
silent: OPTIONS[:silent] || false)
|
26
|
+
SUBCOMMANDS[OPTIONS[:subcommand]].new(loader).execute(silent: OPTIONS[:silent] || false)
|
27
|
+
end
|
28
|
+
end
|
data/lib/dim/encoding.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Dim
|
2
|
+
class ExitHelper
|
3
|
+
@exit_code = 0
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def exit_code
|
7
|
+
@exit_code
|
8
|
+
end
|
9
|
+
|
10
|
+
def reset_exit_code
|
11
|
+
@exit_code = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def exit(msg:, code: 0, filename: nil)
|
15
|
+
@exit_code = code
|
16
|
+
inf = filename ? "in #{filename}: " : ''
|
17
|
+
pre = code.positive? ? 'Error: ' : ''
|
18
|
+
warn("#{pre}#{inf}#{msg}")
|
19
|
+
Kernel.exit(code)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|