dim-toolkit 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib")
4
+
5
+ require 'dim/encoding'
6
+ require 'dim/ver'
7
+ require 'dim'
8
+
9
+ Dim.main
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ # This overwrites the configuration of local machines,
2
+ # Dim shall read, process and write only UTF_8.
3
+ Encoding.default_external = Encoding::UTF_8
4
+ Encoding.default_internal = Encoding::UTF_8
@@ -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