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 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