dim-toolkit 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/dim/loader.rb
ADDED
@@ -0,0 +1,581 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
require_relative 'encoding'
|
5
|
+
require_relative 'globals'
|
6
|
+
require_relative 'ext/psych'
|
7
|
+
require_relative 'requirement'
|
8
|
+
require_relative 'consistency'
|
9
|
+
require_relative 'exit_helper'
|
10
|
+
require_relative 'helpers/attribute_helper'
|
11
|
+
require_relative 'helpers/file_helper'
|
12
|
+
|
13
|
+
using Dim::Refinements
|
14
|
+
|
15
|
+
module Dim
|
16
|
+
class Loader
|
17
|
+
include Helpers::AttributeHelper
|
18
|
+
|
19
|
+
attr_reader :requirements, :config, :property_table, :properties, :original_data, :module_data, :metadata, :dim_file, :all_attributes, :custom_attributes
|
20
|
+
|
21
|
+
# YAML standard:
|
22
|
+
# invalid C0 control characters: 0x00 - 0x1F (except TAB 0x09, LF 0x0A and CR 0x0D)
|
23
|
+
# invalid control character DEL 0x7F
|
24
|
+
# invalid C1 control characters: 0x80 - 0x9F (except NEL 0x85)
|
25
|
+
#
|
26
|
+
# in addition NEL will be also replaced which seems to be misused in CRS documents
|
27
|
+
@@invalid_ccs = {
|
28
|
+
"\x00" => '[NUL]', "\x01" => '[SOH]', "\x02" => '[STX]', "\x03" => '[ETX]',
|
29
|
+
"\x04" => '[EOT]', "\x05" => '[ENQ]', "\x06" => '[ACK]', "\x07" => '[BEL]',
|
30
|
+
"\x08" => '[BS]', "\x0B" => '[VT]',
|
31
|
+
"\x0C" => '[FF]', "\x0E" => '[SO]', "\x0F" => '[SI]',
|
32
|
+
"\x10" => '[DLE]', "\x11" => '[DC1]', "\x12" => '[DC2]', "\x13" => '[DC3]',
|
33
|
+
"\x14" => '[DC4]', "\x15" => '[NAK]', "\x16" => '[SYN]', "\x17" => '[ETB]',
|
34
|
+
"\x18" => '[CAN]', "\x19" => '[EM]', "\x1A" => '[SUB]', "\x1B" => '[ESC]',
|
35
|
+
"\x1C" => '[FS]', "\x1D" => '[GS]', "\x1E" => '[RS]', "\x1F" => '[US]',
|
36
|
+
"\x7F" => '[DEL]',
|
37
|
+
"\u0080" => '[PAD]', "\u0081" => '[HOP]', "\u0082" => '[BPH]', "\u0083" => '[NBH]',
|
38
|
+
"\u0084" => '[IND]', "\u0085" => '[NEL]', "\u0086" => '[SSA]', "\u0087" => '[ESA]',
|
39
|
+
"\u0088" => '[HTS]', "\u0089" => '[HTJ]', "\u008A" => '[VTS]', "\u008B" => '[PLD]',
|
40
|
+
"\u008C" => '[PLU]', "\u008D" => '[RI]', "\u008E" => '[SS2]', "\u008F" => '[SS3]',
|
41
|
+
"\u0090" => '[DCS]', "\u0091" => '[PU1]', "\u0092" => '[PU2]', "\u0093" => '[STS]',
|
42
|
+
"\u0094" => '[CCH]', "\u0095" => '[MW]', "\u0096" => '[SPA]', "\u0097" => '[EPA]',
|
43
|
+
"\u0098" => '[SOS]', "\u0099" => '[SGCI]', "\u009A" => '[SCI]', "\u009B" => '[CSI]',
|
44
|
+
"\u009C" => '[ST]', "\u009D" => '[OSC]', "\u009E" => '[PM]', "\u009F" => '[APC]'
|
45
|
+
}
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@requirements = {}
|
49
|
+
@module_data = {}
|
50
|
+
@metadata = {}
|
51
|
+
@config = {}
|
52
|
+
@properties = {}
|
53
|
+
@property_table = {}
|
54
|
+
@original_data = {}
|
55
|
+
@all_attributes = Requirement::SYNTAX.dup
|
56
|
+
@custom_attributes = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def filter(str)
|
60
|
+
@requirements.keep_if { |_id, r| r.filter(str) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def load(file: nil, attributes_file: nil, allow_missing: false, no_check_enclosed: false, silent: true, input_filenames: [])
|
64
|
+
::Psych::Nodes::Scalar.add_patch
|
65
|
+
::Psych::Visitors::ToRuby.add_patch
|
66
|
+
|
67
|
+
input_filenames = *input_filenames
|
68
|
+
# If output format is vim then we do not need to read the file,
|
69
|
+
# content will be read from the stdin and printed out to stdout. This is to enhance the
|
70
|
+
# formatting in the vim.
|
71
|
+
unless OPTIONS[:output_format] == 'stdout'
|
72
|
+
if input_filenames.length > 0 and file
|
73
|
+
Dim::ExitHelper.exit(code: 1, msg: 'use either file or input_filenames argument (deprecated) for load method')
|
74
|
+
end
|
75
|
+
if input_filenames.length > 1
|
76
|
+
Dim::ExitHelper.exit(code: 1,
|
77
|
+
msg: 'input_filenames argument (deprecated) of load method must have at maximum one entry')
|
78
|
+
end
|
79
|
+
file = input_filenames[0] if input_filenames.length == 1
|
80
|
+
unless file
|
81
|
+
Dim::ExitHelper.exit(code: 1,
|
82
|
+
msg: 'neither file nor input_filenames argument (deprecated) argument specified for load method')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
unless silent
|
87
|
+
if allow_missing && OPTIONS[:subcommand] != 'format'
|
88
|
+
puts "Warning: Using 'allow-missing' might influence metrics when some references are ignored!\n"
|
89
|
+
end
|
90
|
+
puts 'Start Loading...'
|
91
|
+
end
|
92
|
+
|
93
|
+
Dim::ExitHelper.exit(code: 1, filename: file, msg: 'does not exist') unless File.exist?(file.to_s) || OPTIONS[:output_format] == 'stdout'
|
94
|
+
|
95
|
+
@dim_file = OPTIONS[:output_format] == 'stdout' ? $stdin.read.chomp : File.binread(file).chomp
|
96
|
+
|
97
|
+
if attributes_file
|
98
|
+
fetch_attributes!(folder: File.dirname(attributes_file), filename: attributes_file.split('/').last, silent: silent)
|
99
|
+
elsif !dim_file.match(/^Config:/)
|
100
|
+
folder = search_attributes_file(file.to_s)
|
101
|
+
fetch_attributes!(folder: folder, filename: 'attributes.dim', silent: silent) if folder
|
102
|
+
end
|
103
|
+
|
104
|
+
if dim_file.match(/^Config:/)
|
105
|
+
load_config(config_filename: file.to_s, silent: silent)
|
106
|
+
else
|
107
|
+
load_pattern(
|
108
|
+
config_filename: nil,
|
109
|
+
pattern: file.to_s,
|
110
|
+
origin: '',
|
111
|
+
silent: silent,
|
112
|
+
category: 'unspecified',
|
113
|
+
disable_naming_convention_check: false,
|
114
|
+
no_check_enclosed: no_check_enclosed
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
puts 'Checking consistency...' unless silent
|
119
|
+
checker = Dim::Consistency.new(self)
|
120
|
+
checker.check(allow_missing: allow_missing)
|
121
|
+
puts 'Done.' unless silent
|
122
|
+
ensure
|
123
|
+
::Psych::Nodes::Scalar.revert_patch
|
124
|
+
::Psych::Visitors::ToRuby.revert_patch
|
125
|
+
end
|
126
|
+
|
127
|
+
def load_config(config_filename:, silent: true, no_check_enclosed: false)
|
128
|
+
puts "Loading [config] #{config_filename}..." unless silent
|
129
|
+
@config = open_yml_file(config_filename, '')
|
130
|
+
|
131
|
+
allowed_attributes = %w[Config Properties Attributes]
|
132
|
+
|
133
|
+
@config.each_key do |k|
|
134
|
+
next unless allowed_attributes.none? { |a| a == k }
|
135
|
+
|
136
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename, msg: "top level key in config file must be #{allowed_attributes.map do |a|
|
137
|
+
"\"#{a}\""
|
138
|
+
end.join(', ')}, found \"#{k}\"")
|
139
|
+
end
|
140
|
+
|
141
|
+
if @config.key?('Attributes')
|
142
|
+
if @config['Attributes'].is_a?(String)
|
143
|
+
fetch_attributes!(folder: File.dirname(config_filename), filename: @config['Attributes'], silent: silent)
|
144
|
+
else
|
145
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename, msg: "'Attributes' must be a string")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if @config.key?('Properties')
|
150
|
+
if @config['Properties'].is_a?(String)
|
151
|
+
resolve_properties(folder: File.dirname(config_filename), properties_filename: @config['Properties'])
|
152
|
+
else
|
153
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename, msg: "'Properties' must be a string")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# COP: Move to constants
|
158
|
+
allowed_keys = %w[files category originator disable_naming_convention_check]
|
159
|
+
allowed_categories = ALLOWED_CATEGORIES.values
|
160
|
+
|
161
|
+
if @config['Config'].is_a?(Array)
|
162
|
+
config_values = @config['Config']
|
163
|
+
else
|
164
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename, msg: "'Config' must be an array")
|
165
|
+
end
|
166
|
+
|
167
|
+
config_values.each do |value|
|
168
|
+
validate_and_load_config(value, config_filename)
|
169
|
+
|
170
|
+
unless value.is_a?(Hash) && value.keys.sort_by(&:length).eql?(allowed_keys.sort_by(&:length))
|
171
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename,
|
172
|
+
msg: "each hash in 'Config' array must have key/value pairs for #{allowed_keys.join(', ')}.")
|
173
|
+
end
|
174
|
+
|
175
|
+
if value['category'].is_a?(String)
|
176
|
+
value['category'].strip!
|
177
|
+
unless allowed_categories.include?(value['category'])
|
178
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename,
|
179
|
+
msg: "attribute \"category\" of '#{value['originator']}' reqs is '#{value['category']}' but must be one of #{allowed_categories.join(', ')}.")
|
180
|
+
end
|
181
|
+
else
|
182
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename,
|
183
|
+
msg: "attribute \"category\" of '#{value['originator']}' reqs must be a string")
|
184
|
+
end
|
185
|
+
|
186
|
+
if value['files'].is_a?(Array) && value['files'].all? { |a| a.is_a?(String) } || value['files'].is_a?(String)
|
187
|
+
value['files'] = *value['files']
|
188
|
+
else
|
189
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename,
|
190
|
+
msg: "attribute \"files\" of '#{value['originator']}' reqs must be a string or an array of strings.")
|
191
|
+
end
|
192
|
+
value['files'].each do |pattern|
|
193
|
+
pattern.gsub!(%r{\A\./}, '')
|
194
|
+
if pattern.empty?
|
195
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename,
|
196
|
+
msg: "attribute \"files\" of '#{value['originator']}' must not have an empty string")
|
197
|
+
end
|
198
|
+
p = Pathname.new(pattern)
|
199
|
+
if p.absolute?
|
200
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename,
|
201
|
+
msg: "'#{pattern}' must not be an absolute path")
|
202
|
+
end
|
203
|
+
if p.each_filename.any? { |e| e == '..' }
|
204
|
+
Dim::ExitHelper.exit(code: 1, filename: config_filename,
|
205
|
+
msg: "'#{pattern}' must not include '..'")
|
206
|
+
end
|
207
|
+
load_pattern(
|
208
|
+
config_filename: config_filename,
|
209
|
+
pattern: pattern,
|
210
|
+
origin: value['originator'],
|
211
|
+
silent: silent,
|
212
|
+
category: value['category'],
|
213
|
+
disable_naming_convention_check: value['disable_naming_convention_check'],
|
214
|
+
no_check_enclosed: no_check_enclosed
|
215
|
+
)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
puts 'Done.' unless silent
|
219
|
+
end
|
220
|
+
|
221
|
+
def extract_type(attr)
|
222
|
+
return {} if attr.empty?
|
223
|
+
|
224
|
+
hscan = attr.scan(/\Ah(\d+)\s.+/)
|
225
|
+
if hscan.length == 1
|
226
|
+
level = hscan[0][0].to_i
|
227
|
+
return { 'type' => "heading_#{level}", 'text' => attr[2 + hscan[0][0].length..-1].strip }
|
228
|
+
else
|
229
|
+
iscan = attr.scan(/\Ainfo\s.+/)
|
230
|
+
return { 'type' => 'information', 'text' => attr[5..-1].strip } if iscan.length == 1
|
231
|
+
end
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
def load_file(filename:, origin:, silent:, category:, disable_naming_convention_check: false, no_check_enclosed: false)
|
236
|
+
puts "Loading [#{origin.empty? ? "unknown" : origin}] #{filename}..." unless silent
|
237
|
+
binary_data = OPTIONS[:output_format] == 'stdout' ? @dim_file : File.binread(filename).force_encoding('UTF-8')
|
238
|
+
|
239
|
+
# this looks expensive but measurement showed it's close to zero
|
240
|
+
@@invalid_ccs.each { |k, v| binary_data = binary_data.gsub(k, v) }
|
241
|
+
|
242
|
+
if binary_data.valid_encoding?
|
243
|
+
begin
|
244
|
+
psych_doc = YAML.parse(binary_data)
|
245
|
+
rescue Psych::SyntaxError => e
|
246
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: e.message)
|
247
|
+
end
|
248
|
+
else
|
249
|
+
begin
|
250
|
+
psych_doc = YAML.parse(binary_data.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?'),
|
251
|
+
filename: filename)
|
252
|
+
rescue Psych::SyntaxError => e
|
253
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: e.message)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
unless psych_doc
|
257
|
+
puts "Warning: empty file detected; skipped loading of #{filename}"
|
258
|
+
|
259
|
+
return
|
260
|
+
end
|
261
|
+
reqs = psych_doc.to_ruby
|
262
|
+
|
263
|
+
unless reqs.is_a?(Hash)
|
264
|
+
Dim::ExitHelper.exit(code: 1, filename: filename,
|
265
|
+
msg: 'top level must be a hash with keys "module", "enclosed", "metadata" and/or unique ids'
|
266
|
+
)
|
267
|
+
end
|
268
|
+
|
269
|
+
# TODO: Remove module backward compatibility in future version
|
270
|
+
if reqs.key?('document') && reqs.key?('module')
|
271
|
+
Dim::ExitHelper.exit(
|
272
|
+
code: 1,
|
273
|
+
filename: filename,
|
274
|
+
msg: 'module and document found in the file; please rename module to document'
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
if reqs.key?('document')
|
279
|
+
if !reqs['document'].is_a?(String) || reqs['document'].empty?
|
280
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: 'document must be a non-empty string')
|
281
|
+
end
|
282
|
+
document = reqs['document']
|
283
|
+
# TODO: Remove module backward compatibility in future version
|
284
|
+
reqs['module'] = document
|
285
|
+
elsif reqs.key?('module')
|
286
|
+
if !(reqs['module'].is_a? String) || reqs['module'].empty?
|
287
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: 'module name must be a non-empty string')
|
288
|
+
end
|
289
|
+
document = reqs['module']
|
290
|
+
reqs['document'] = document
|
291
|
+
else
|
292
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: 'Document name is missing; please add document name')
|
293
|
+
end
|
294
|
+
|
295
|
+
validate_srs_name(document, disable_naming_convention_check, category, filename)
|
296
|
+
|
297
|
+
if @module_data.key?(document)
|
298
|
+
if @module_data[document][:origin] != origin
|
299
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg:
|
300
|
+
"files of the same module must have the same owner:\n" +
|
301
|
+
"- #{@module_data[document][:files].first[0]} (#{@module_data[document][:origin]})\n" +
|
302
|
+
"- #{filename} (#{origin})")
|
303
|
+
elsif @module_data[document][:category] != category
|
304
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg:
|
305
|
+
"files of the same module must have the same category:\n" +
|
306
|
+
"- #{@module_data[document][:files].first[0]} (#{@module_data[document][:category]})\n" +
|
307
|
+
"- #{filename} (#{category})")
|
308
|
+
end
|
309
|
+
else
|
310
|
+
@module_data[document] = { origin: origin, category: category, files: {} }
|
311
|
+
end
|
312
|
+
|
313
|
+
if @module_data[document][:files].key?(filename)
|
314
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: "file #{filename} already loaded")
|
315
|
+
end
|
316
|
+
|
317
|
+
@module_data[document][:files][filename] = []
|
318
|
+
@metadata[document] ||= ''
|
319
|
+
|
320
|
+
if reqs.key?('metadata')
|
321
|
+
unless reqs['metadata'].is_a?(String)
|
322
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: 'metadata must be a string')
|
323
|
+
end
|
324
|
+
unless reqs['metadata'].empty?
|
325
|
+
unless @metadata[document].empty?
|
326
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: 'only one metadata per module allowed')
|
327
|
+
end
|
328
|
+
@metadata[document] = reqs['metadata'].strip
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
if reqs.key?('enclosed') && !no_check_enclosed
|
333
|
+
ecl = reqs['enclosed']
|
334
|
+
ecl = [ecl] if ecl.is_a?(String)
|
335
|
+
if !ecl.is_a?(Array) || ecl.empty? || ecl.any? { |s| !s.is_a?(String) || s.empty? }
|
336
|
+
Dim::ExitHelper.exit(code: 1, filename: filename,
|
337
|
+
msg: '"enclosed" must be a non-empty string or an array of non-empty strings')
|
338
|
+
end
|
339
|
+
# Remove superfluous ./ from path
|
340
|
+
ecl = ecl.map do |path|
|
341
|
+
if path.match?(/\\/)
|
342
|
+
puts "Warning: Backward slashes detected in filepath #{path}. Use '/' over '\\' in filepath"
|
343
|
+
path.gsub!('\\', '/')
|
344
|
+
end
|
345
|
+
Pathname.new(path).cleanpath.to_s
|
346
|
+
end
|
347
|
+
dir_file = File.dirname(filename)
|
348
|
+
ecl.each do |l|
|
349
|
+
p = Pathname.new(l)
|
350
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: "'#{l}' must not be an absolute path") if p.absolute?
|
351
|
+
if p.each_filename.any? do |name|
|
352
|
+
name == '..'
|
353
|
+
end
|
354
|
+
Dim::ExitHelper.exit(code: 1, filename: filename,
|
355
|
+
msg: "'#{l}' must not include '..'")
|
356
|
+
end
|
357
|
+
|
358
|
+
src = File.join(dir_file, l)
|
359
|
+
src_globbed = Dir.glob(src)
|
360
|
+
if src_globbed.empty?
|
361
|
+
Dim::ExitHelper.exit(code: 1, filename: filename,
|
362
|
+
msg: "\"#{l}\" in \"enclosed\" does not refer to any existing file")
|
363
|
+
end
|
364
|
+
end
|
365
|
+
@module_data[document][:files][filename] += ecl
|
366
|
+
end
|
367
|
+
|
368
|
+
line_numbers = psych_doc.line_numbers
|
369
|
+
|
370
|
+
reqs.each do |id, attr|
|
371
|
+
next if %w[module enclosed metadata document].include? id
|
372
|
+
|
373
|
+
if @requirements.key?(id)
|
374
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: "id \"#{id}\" found more than once")
|
375
|
+
end
|
376
|
+
|
377
|
+
if id.include?(',')
|
378
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: "Disallowed ',' found in id \"#{id}\"")
|
379
|
+
end
|
380
|
+
|
381
|
+
validate_srs_name(id, disable_naming_convention_check, category, filename, 'ID')
|
382
|
+
|
383
|
+
if attr.is_a?(String)
|
384
|
+
attr.strip!
|
385
|
+
res = extract_type(attr)
|
386
|
+
if res
|
387
|
+
attr = res
|
388
|
+
else
|
389
|
+
Dim::ExitHelper.exit(code: 1, filename: filename,
|
390
|
+
msg: "Invalid short-form in requirement \"#{id}\", valid forms are \"h<level> <text>\" or \"info <text>\"")
|
391
|
+
end
|
392
|
+
elsif attr.is_a?(Hash)
|
393
|
+
attr.keys.select do |k|
|
394
|
+
@all_attributes.key?(k) && %i[multi split].include?(@all_attributes[k][:format_style])
|
395
|
+
end.each do |k|
|
396
|
+
attr[k] = attr[k].cleanUniqString if attr[k].is_a?(String)
|
397
|
+
end
|
398
|
+
attr['tags'] = attr['tags'].cleanUniqString if attr['tags'].is_a?(String)
|
399
|
+
else
|
400
|
+
Dim::ExitHelper.exit(code: 1, filename: filename, msg: "attributes for id \"#{id}\" must be key-value pairs")
|
401
|
+
end
|
402
|
+
unless attr.key?('verification_methods')
|
403
|
+
attr['verification_methods'] = attr['test_setups'] if attr.key?('test_setups')
|
404
|
+
end
|
405
|
+
attr.each do |key, value|
|
406
|
+
unless value.is_a?(String)
|
407
|
+
Dim::ExitHelper.exit(code: 1, filename: filename,
|
408
|
+
msg: "value of attribute \"#{key}\" must be String not #{value.class}")
|
409
|
+
end
|
410
|
+
attr[key].gsub!("\u00A0", ' ')
|
411
|
+
attr[key].strip!
|
412
|
+
end
|
413
|
+
|
414
|
+
r = Requirement.new(id, document, filename, attr, origin, self, category, line_numbers[id], @all_attributes)
|
415
|
+
reqs[id] = attr
|
416
|
+
@requirements[id] = r
|
417
|
+
end
|
418
|
+
|
419
|
+
@original_data[filename] = Marshal.load(Marshal.dump(reqs))
|
420
|
+
end
|
421
|
+
|
422
|
+
def load_pattern(config_filename:, pattern:, origin:, silent:, category:, disable_naming_convention_check: false, no_check_enclosed: false)
|
423
|
+
if pattern.match?(/\\/)
|
424
|
+
puts "Warning: Backward slashes detected in pattern #{pattern}. Use '/' over '\\'"
|
425
|
+
pattern.gsub!('\\', '/')
|
426
|
+
end
|
427
|
+
pattern_search = config_filename ? File.join(File.dirname(config_filename), pattern) : pattern
|
428
|
+
fs = Dir.glob(pattern_search).sort
|
429
|
+
if fs.empty? && !silent
|
430
|
+
puts "Info: no matches for \"#{pattern}\" in \"#{config_filename}\""
|
431
|
+
end
|
432
|
+
fs.each do |f|
|
433
|
+
load_file(filename: f, origin: origin, silent: silent, category: category, disable_naming_convention_check: disable_naming_convention_check, no_check_enclosed: no_check_enclosed)
|
434
|
+
end
|
435
|
+
return unless OPTIONS[:output_format] == 'stdout'
|
436
|
+
|
437
|
+
load_file(filename: '', origin: origin, silent: silent, category: category, disable_naming_convention_check: disable_naming_convention_check, no_check_enclosed: no_check_enclosed)
|
438
|
+
end
|
439
|
+
|
440
|
+
def resolve_properties(folder:, properties_filename:)
|
441
|
+
@properties = open_yml_file(folder, properties_filename, allow_empty_file: true)
|
442
|
+
unless @properties
|
443
|
+
puts "Warning: empty file detected; skipped loading of #{properties_filename}"
|
444
|
+
return
|
445
|
+
end
|
446
|
+
|
447
|
+
@properties.each do |document, value|
|
448
|
+
value.each do |attr, property_value|
|
449
|
+
next unless @all_attributes.key?(attr)
|
450
|
+
|
451
|
+
unless property_value.is_a?(String)
|
452
|
+
Dim::ExitHelper.exit(code: 1, filename: properties_filename,
|
453
|
+
msg: "The value for key #{attr} in properties files must be a string")
|
454
|
+
end
|
455
|
+
|
456
|
+
if @all_attributes.dig(attr, :allowed).nil? ||
|
457
|
+
!property_value.cleanArray.select do |val|
|
458
|
+
!@all_attributes[attr][:allowed].include?(val)
|
459
|
+
end.any?
|
460
|
+
@property_table[document] ||= {}
|
461
|
+
@property_table[document][attr] = property_value.strip
|
462
|
+
else
|
463
|
+
Dim::ExitHelper.exit(code: 1, filename: properties_filename,
|
464
|
+
msg: "The properties file includes an invalid #{attr} value '#{property_value}' for module: #{document}.")
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def fetch_attributes!(folder:, filename:, silent:)
|
471
|
+
puts "Loading [attributes] #{File.join(folder, filename)}" unless silent
|
472
|
+
|
473
|
+
@custom_attributes.merge!(resolve_attributes(folder: folder, filename: filename))
|
474
|
+
@all_attributes.merge!(@custom_attributes)
|
475
|
+
end
|
476
|
+
|
477
|
+
def search_attributes_file(file)
|
478
|
+
path = Pathname.new(file).parent.realpath
|
479
|
+
if path.root?
|
480
|
+
nil
|
481
|
+
else
|
482
|
+
return path if Dir.new(path).children.include?('attributes.dim')
|
483
|
+
search_attributes_file(path)
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def validate_and_load_config(value, config_filename)
|
488
|
+
disable_naming_convention_check = value['disable_naming_convention_check']
|
489
|
+
unless disable_naming_convention_check
|
490
|
+
value['disable_naming_convention_check'] = false
|
491
|
+
return
|
492
|
+
end
|
493
|
+
|
494
|
+
if value['category'] != ALLOWED_CATEGORIES[:software]
|
495
|
+
warn("Warning: disable_naming_convention_check attribute will only take effect when category is software")
|
496
|
+
end
|
497
|
+
|
498
|
+
if disable_naming_convention_check == 'yes'
|
499
|
+
value['disable_naming_convention_check'] = true
|
500
|
+
return
|
501
|
+
elsif disable_naming_convention_check == 'no'
|
502
|
+
value['disable_naming_convention_check'] = false
|
503
|
+
return
|
504
|
+
end
|
505
|
+
|
506
|
+
Dim::ExitHelper.exit(
|
507
|
+
code: 1,
|
508
|
+
filename: config_filename,
|
509
|
+
msg: 'disable_naming_convention_check in config must be either boolean value or a string "yes" or "no"'
|
510
|
+
)
|
511
|
+
end
|
512
|
+
|
513
|
+
def validate_srs_name(name, disable_naming_convention_check, category, filename, attr = 'module')
|
514
|
+
return if category != ALLOWED_CATEGORIES[:software] || disable_naming_convention_check
|
515
|
+
|
516
|
+
# raise error if not starting with SRS_
|
517
|
+
unless name.match?(/^(SRS_)/)
|
518
|
+
Dim::ExitHelper.exit(
|
519
|
+
code: 1,
|
520
|
+
filename: filename,
|
521
|
+
msg: "#{attr} #{name} in software requirement must start with \"SRS_\""
|
522
|
+
)
|
523
|
+
end
|
524
|
+
|
525
|
+
_srs, feature, aspect, *rest = name.split('_')
|
526
|
+
|
527
|
+
# Raise error if more than two _ detected
|
528
|
+
unless rest.empty?
|
529
|
+
Dim::ExitHelper.exit(
|
530
|
+
code: 1,
|
531
|
+
filename: filename,
|
532
|
+
msg: "#{attr} #{name} in software requirement must contain exactly two \"_\""
|
533
|
+
)
|
534
|
+
end
|
535
|
+
|
536
|
+
if feature.to_s.empty?
|
537
|
+
Dim::ExitHelper.exit(
|
538
|
+
code: 1,
|
539
|
+
filename: filename,
|
540
|
+
msg: "invalid #{attr} #{name} in software requirement; missing feature after \"SRS_\""
|
541
|
+
)
|
542
|
+
end
|
543
|
+
|
544
|
+
# Raise error if feature or aspect is non alphanumeric
|
545
|
+
if feature.match?(SRS_NAME_REGEX)
|
546
|
+
Dim::ExitHelper.exit(
|
547
|
+
code: 1,
|
548
|
+
filename: filename,
|
549
|
+
msg: "feature in #{attr} #{name} in software requirement contains non-alphanumeric characters"
|
550
|
+
)
|
551
|
+
end
|
552
|
+
|
553
|
+
if attr == 'module' && !aspect.to_s.empty?
|
554
|
+
Dim::ExitHelper.exit(
|
555
|
+
code: 1,
|
556
|
+
filename: filename,
|
557
|
+
msg: "invalid module #{name} in software requirement; must contain exactly one \"_\""
|
558
|
+
)
|
559
|
+
end
|
560
|
+
|
561
|
+
# Check naming convention for aspect only in case of IDs
|
562
|
+
return if attr == 'module'
|
563
|
+
|
564
|
+
if aspect.to_s.empty?
|
565
|
+
Dim::ExitHelper.exit(
|
566
|
+
code: 1,
|
567
|
+
filename: filename,
|
568
|
+
msg: "invalid ID #{name} in software requirement; missing aspect/ratio after \"SRS_feature\""
|
569
|
+
)
|
570
|
+
end
|
571
|
+
|
572
|
+
if aspect.match?(SRS_NAME_REGEX)
|
573
|
+
Dim::ExitHelper.exit(
|
574
|
+
code: 1,
|
575
|
+
filename: filename,
|
576
|
+
msg: "aspect in ID #{name} in software requirement contains non-alphanumeric characters"
|
577
|
+
)
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|