Almirah 0.2.3 → 0.2.5
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/lib/almirah/doc_fabric.rb +21 -2
- data/lib/almirah/doc_items/controlled_paragraph.rb +100 -79
- data/lib/almirah/doc_items/controlled_table.rb +180 -137
- data/lib/almirah/doc_items/controlled_table_row.rb +3 -3
- data/lib/almirah/doc_items/doc_item.rb +16 -14
- data/lib/almirah/doc_items/frontmatter.rb +9 -0
- data/lib/almirah/doc_items/markdown_list.rb +3 -5
- data/lib/almirah/doc_items/markdown_table.rb +2 -4
- data/lib/almirah/doc_items/paragraph.rb +18 -20
- data/lib/almirah/doc_parser.rb +77 -29
- data/lib/almirah/doc_types/base_document.rb +56 -61
- data/lib/almirah/doc_types/coverage.rb +65 -63
- data/lib/almirah/doc_types/index.rb +168 -156
- data/lib/almirah/doc_types/persistent_document.rb +14 -17
- data/lib/almirah/doc_types/traceability.rb +6 -2
- data/lib/almirah/dom/doc_section.rb +21 -24
- data/lib/almirah/dom/document.rb +72 -57
- data/lib/almirah/project.rb +240 -277
- data/lib/almirah/project_template.rb +298 -0
- data/lib/almirah/search/specifications_db.rb +66 -70
- data/lib/almirah/templates/page.html +3 -3
- data/lib/almirah/templates/scripts/main.js +34 -0
- data/lib/almirah.rb +32 -23
- metadata +27 -5
data/lib/almirah/project.rb
CHANGED
@@ -1,324 +1,287 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fileutils'
|
2
|
-
require_relative
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
4
|
+
require_relative 'doc_fabric'
|
5
|
+
require_relative 'navigation_pane'
|
6
|
+
require_relative 'doc_types/traceability'
|
7
|
+
require_relative 'doc_types/index'
|
8
|
+
require_relative 'search/specifications_db'
|
9
|
+
|
10
|
+
class Project # rubocop:disable Metrics/ClassLength,Style/Documentation
|
11
|
+
attr_accessor :specifications, :protocols, :traceability_matrices, :coverage_matrices, :specifications_dictionary,
|
12
|
+
:index, :project, :configuration
|
13
|
+
|
14
|
+
def initialize(configuration)
|
15
|
+
@configuration = configuration
|
16
|
+
@specifications = []
|
17
|
+
@protocols = []
|
18
|
+
@traceability_matrices = []
|
19
|
+
@coverage_matrices = []
|
20
|
+
@specifications_dictionary = {}
|
21
|
+
@covered_specifications_dictionary = {}
|
22
|
+
@index = nil
|
23
|
+
@project = self
|
24
|
+
FileUtils.remove_dir("#{@configuration.project_root_directory}/build", true)
|
25
|
+
copy_resources
|
26
|
+
end
|
27
|
+
|
28
|
+
def copy_resources
|
29
|
+
# scripts
|
30
|
+
gem_root = File.expand_path './../..', File.dirname(__FILE__)
|
31
|
+
src_folder = "#{gem_root}/lib/almirah/templates/scripts"
|
32
|
+
dst_folder = "#{@configuration.project_root_directory}/build/scripts"
|
33
|
+
FileUtils.mkdir_p(dst_folder)
|
34
|
+
FileUtils.copy_entry(src_folder, dst_folder)
|
35
|
+
# css
|
36
|
+
src_folder = "#{gem_root}/lib/almirah/templates/css"
|
37
|
+
dst_folder = "#{@configuration.project_root_directory}/build/css"
|
38
|
+
FileUtils.mkdir_p(dst_folder)
|
39
|
+
FileUtils.copy_entry(src_folder, dst_folder)
|
40
|
+
end
|
41
|
+
|
42
|
+
def specifications_and_protocols # rubocop:disable Metrics/MethodLength
|
43
|
+
parse_all_specifications
|
44
|
+
parse_all_protocols
|
45
|
+
link_all_specifications
|
46
|
+
link_all_protocols
|
47
|
+
check_wrong_specification_referenced
|
48
|
+
create_index
|
49
|
+
render_all_specifications(@specifications)
|
50
|
+
render_all_specifications(@traceability_matrices)
|
51
|
+
render_all_specifications(@coverage_matrices)
|
52
|
+
render_all_protocols
|
53
|
+
render_index
|
54
|
+
create_search_data
|
55
|
+
end
|
56
|
+
|
57
|
+
def specifications_and_results(test_run) # rubocop:disable Metrics/MethodLength
|
58
|
+
parse_all_specifications
|
59
|
+
parse_test_run test_run
|
60
|
+
link_all_specifications
|
61
|
+
link_all_protocols
|
62
|
+
check_wrong_specification_referenced
|
63
|
+
create_index
|
64
|
+
render_all_specifications(@specifications)
|
65
|
+
render_all_specifications(@traceability_matrices)
|
66
|
+
render_all_specifications(@coverage_matrices)
|
67
|
+
render_all_protocols
|
68
|
+
render_index
|
69
|
+
create_search_data
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_all_specifications
|
73
|
+
path = @configuration.project_root_directory
|
74
|
+
# do a lasy pass first to get the list of documents id
|
75
|
+
Dir.glob("#{path}/specifications/**/*.md").each do |f|
|
76
|
+
DocFabric.add_lazy_doc_id(f)
|
31
77
|
end
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
dst_folder = @configuration.project_root_directory + "/build/scripts"
|
38
|
-
FileUtils.mkdir_p(dst_folder)
|
39
|
-
FileUtils.copy_entry( src_folder, dst_folder )
|
40
|
-
# css
|
41
|
-
src_folder = gem_root + "/lib/almirah/templates/css"
|
42
|
-
dst_folder = @configuration.project_root_directory + "/build/css"
|
43
|
-
FileUtils.mkdir_p(dst_folder)
|
44
|
-
FileUtils.copy_entry( src_folder, dst_folder )
|
78
|
+
# parse documents in the second pass
|
79
|
+
Dir.glob("#{path}/specifications/**/*.md").each do |f| # rubocop:disable Style/CombinableLoops
|
80
|
+
doc = DocFabric.create_specification(f)
|
81
|
+
@specifications.append(doc)
|
82
|
+
@specifications_dictionary[doc.id.to_s.downcase] = doc
|
45
83
|
end
|
84
|
+
end
|
46
85
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
link_all_protocols
|
53
|
-
check_wrong_specification_referenced
|
54
|
-
create_index
|
55
|
-
render_all_specifications(@specifications)
|
56
|
-
render_all_specifications(@traceability_matrices)
|
57
|
-
render_all_specifications(@coverage_matrices)
|
58
|
-
render_all_protocols
|
59
|
-
render_index
|
60
|
-
create_search_data
|
86
|
+
def parse_all_protocols
|
87
|
+
path = @configuration.project_root_directory
|
88
|
+
Dir.glob("#{path}/tests/protocols/**/*.md").each do |f|
|
89
|
+
doc = DocFabric.create_protocol(f)
|
90
|
+
@protocols.append(doc)
|
61
91
|
end
|
92
|
+
end
|
62
93
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
link_all_protocols
|
69
|
-
check_wrong_specification_referenced
|
70
|
-
create_index
|
71
|
-
render_all_specifications(@specifications)
|
72
|
-
render_all_specifications(@traceability_matrices)
|
73
|
-
render_all_specifications(@coverage_matrices)
|
74
|
-
render_all_protocols
|
75
|
-
render_index
|
76
|
-
create_search_data
|
94
|
+
def parse_test_run(test_run)
|
95
|
+
path = @configuration.project_root_directory
|
96
|
+
Dir.glob("#{path}/tests/runs/#{test_run}/**/*.md").each do |f|
|
97
|
+
doc = DocFabric.create_protocol(f)
|
98
|
+
@protocols.append(doc)
|
77
99
|
end
|
100
|
+
end
|
78
101
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
path = @configuration.project_root_directory
|
86
|
-
|
87
|
-
# find all specifications
|
88
|
-
Dir.glob( "#{path}/specifications/**/*.md" ).each do |f|
|
89
|
-
puts f
|
90
|
-
# make a copy with another extention to preserve the content
|
91
|
-
f_directory = File.dirname(f)
|
92
|
-
f_name = File.basename(f, File.extname(f)).downcase + "._md"
|
93
|
-
FileUtils.copy_file( f, "#{f_directory}/#{f_name}")
|
94
|
-
# transform the original one
|
95
|
-
# but do nothing for now - TODO
|
96
|
-
end
|
102
|
+
def link_all_specifications # rubocop:disable Metrics/MethodLength
|
103
|
+
comb_list = @specifications.combination(2)
|
104
|
+
comb_list.each do |c|
|
105
|
+
link_two_specifications(c[0], c[1])
|
106
|
+
# puts "Link: #{c[0].id} - #{c[1].id}"
|
97
107
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
doc = DocFabric.create_specification(f)
|
108
|
-
@specifications.append(doc)
|
109
|
-
@specifications_dictionary[doc.id.to_s.downcase] = doc
|
110
|
-
end
|
108
|
+
# separatelly create design inputs treceability
|
109
|
+
@configuration.get_design_inputs.each do |i|
|
110
|
+
next unless @specifications_dictionary.key? i.to_s.downcase
|
111
|
+
|
112
|
+
document = @specifications_dictionary[i.to_s.downcase]
|
113
|
+
if document
|
114
|
+
doc = DocFabric.create_traceability_document(document, nil)
|
115
|
+
@traceability_matrices.append doc
|
116
|
+
end
|
111
117
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
118
|
+
end
|
119
|
+
|
120
|
+
def link_all_protocols # rubocop:disable Metrics/MethodLength
|
121
|
+
@protocols.each do |p|
|
122
|
+
@specifications.each do |s|
|
123
|
+
if p.up_link_docs.key?(s.id.to_s)
|
124
|
+
link_protocol_to_spec(p, s)
|
125
|
+
@covered_specifications_dictionary[s.id.to_s] = s
|
119
126
|
end
|
127
|
+
end
|
120
128
|
end
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
puts "Run: " + f
|
126
|
-
doc = DocFabric.create_protocol(f)
|
127
|
-
@protocols.append(doc)
|
128
|
-
end
|
129
|
+
# create coverage documents
|
130
|
+
@covered_specifications_dictionary.each do |_key, value|
|
131
|
+
doc = DocFabric.create_coverage_matrix(value)
|
132
|
+
@coverage_matrices.append doc
|
129
133
|
end
|
134
|
+
end
|
130
135
|
|
131
|
-
|
132
|
-
|
133
|
-
combList.each do |c|
|
134
|
-
link_two_specifications(c[0], c[1])
|
135
|
-
# puts "Link: #{c[0].id} - #{c[1].id}"
|
136
|
-
end
|
137
|
-
# separatelly create design inputs treceability
|
138
|
-
@configuration.get_design_inputs.each do |i|
|
139
|
-
if @specifications_dictionary.has_key? i.to_s.downcase
|
140
|
-
document = @specifications_dictionary[i.to_s.downcase]
|
141
|
-
if document
|
142
|
-
trx = Traceability.new document, nil, true
|
143
|
-
@traceability_matrices.append trx
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
136
|
+
def check_wrong_specification_referenced # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
137
|
+
available_specification_ids = {}
|
148
138
|
|
149
|
-
|
150
|
-
|
151
|
-
@specifications.each do |s|
|
152
|
-
if p.up_link_docs.has_key?(s.id.to_s)
|
153
|
-
link_protocol_to_spec(p,s)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
139
|
+
@specifications.each do |s|
|
140
|
+
available_specification_ids[s.id.to_s.downcase] = s
|
157
141
|
end
|
158
142
|
|
159
|
-
|
143
|
+
@specifications.each do |s| # rubocop:disable Style/CombinableLoops
|
144
|
+
s.up_link_docs.each do |key, _value|
|
145
|
+
next if available_specification_ids.key?(key)
|
160
146
|
|
161
|
-
|
147
|
+
# now key points to the doc_id that does not exist
|
148
|
+
wrong_doc_id = key
|
149
|
+
# find the item that reference to it
|
150
|
+
s.controlled_items.each do |item|
|
151
|
+
next if item.up_link_ids.nil?
|
162
152
|
|
163
|
-
|
164
|
-
|
165
|
-
end
|
153
|
+
item.up_link_ids.each do |up_link_id|
|
154
|
+
next unless tmp = /^([a-zA-Z]+)-\d+/.match(up_link_id) # SRS
|
166
155
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
# now key points to the doc_id that does not exist
|
171
|
-
wrong_doc_id = key
|
172
|
-
# find the item that reference to it
|
173
|
-
s.controlled_items.each do |item|
|
174
|
-
unless item.up_link_ids.nil?
|
175
|
-
item.up_link_ids.each do |up_link_id|
|
176
|
-
if tmp = /^([a-zA-Z]+)[-]\d+/.match(up_link_id) # SRS
|
177
|
-
if tmp[1].downcase == wrong_doc_id
|
178
|
-
# we got it finally!
|
179
|
-
s.wrong_links_hash[ up_link_id.to_s ] = item
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
156
|
+
if tmp[1].downcase == wrong_doc_id
|
157
|
+
# we got it finally!
|
158
|
+
s.wrong_links_hash[up_link_id.to_s] = item
|
186
159
|
end
|
160
|
+
end
|
187
161
|
end
|
162
|
+
end
|
188
163
|
end
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
bottom_document.wrong_links_hash[ up_lnk ] = item
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def link_two_specifications(doc_a, doc_b) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
167
|
+
if doc_b.up_link_docs.key?(doc_a.id.to_s)
|
168
|
+
top_document = doc_a
|
169
|
+
bottom_document = doc_b
|
170
|
+
elsif doc_a.up_link_docs.key?(doc_b.id.to_s)
|
171
|
+
top_document = doc_b
|
172
|
+
bottom_document = doc_a
|
173
|
+
else
|
174
|
+
return # no links
|
175
|
+
end
|
176
|
+
# puts "Link: #{doc_a.id} - #{doc_b.id}"
|
177
|
+
bottom_document.controlled_items.each do |item|
|
178
|
+
next unless item.up_link_ids
|
179
|
+
|
180
|
+
item.up_link_ids.each do |up_lnk|
|
181
|
+
if top_document.dictionary.key?(up_lnk.to_s)
|
182
|
+
|
183
|
+
top_item = top_document.dictionary[up_lnk.to_s]
|
184
|
+
|
185
|
+
unless top_item.down_links
|
186
|
+
top_item.down_links = []
|
187
|
+
top_document.items_with_downlinks_number += 1 # for statistics
|
188
|
+
end
|
189
|
+
top_item.down_links.append(item)
|
190
|
+
elsif tmp = /^([a-zA-Z]+)-\d+/.match(up_lnk)
|
191
|
+
# check if there is a non existing link with the right doc_id
|
192
|
+
if tmp[1].downcase == top_document.id.downcase
|
193
|
+
bottom_document.wrong_links_hash[up_lnk] = item
|
194
|
+
end # SRS
|
226
195
|
end
|
227
|
-
|
228
|
-
trx = Traceability.new top_document, bottom_document, false
|
229
|
-
@traceability_matrices.append trx
|
196
|
+
end
|
230
197
|
end
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
198
|
+
# create treceability document
|
199
|
+
doc = DocFabric.create_traceability_document(top_document, bottom_document)
|
200
|
+
@traceability_matrices.append doc
|
201
|
+
end
|
202
|
+
|
203
|
+
def link_protocol_to_spec(protocol, specification) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
204
|
+
top_document = specification
|
205
|
+
bottom_document = protocol
|
206
|
+
|
207
|
+
bottom_document.controlled_items.each do |item|
|
208
|
+
next unless item.up_link_ids
|
209
|
+
|
210
|
+
item.up_link_ids.each do |up_lnk|
|
211
|
+
if top_document.dictionary.key?(up_lnk.to_s)
|
212
|
+
|
213
|
+
top_item = top_document.dictionary[up_lnk.to_s]
|
214
|
+
|
215
|
+
unless top_item.coverage_links
|
216
|
+
top_item.coverage_links = []
|
217
|
+
top_document.items_with_coverage_number += 1 # for statistics
|
218
|
+
end
|
219
|
+
top_item.coverage_links.append(item)
|
220
|
+
elsif tmp = /^([a-zA-Z]+)-\d+/.match(up_lnk)
|
221
|
+
# check if there is a non existing link with the right doc_id
|
222
|
+
if tmp[1].downcase == top_document.id.downcase
|
223
|
+
bottom_document.wrong_links_hash[up_lnk] = item
|
224
|
+
end # SRS
|
249
225
|
end
|
250
|
-
|
251
|
-
trx = Coverage.new top_document
|
252
|
-
@coverage_matrices.append trx
|
226
|
+
end
|
253
227
|
end
|
228
|
+
end
|
254
229
|
|
255
|
-
|
256
|
-
|
257
|
-
|
230
|
+
def create_index
|
231
|
+
@index = Index.new(@project)
|
232
|
+
end
|
258
233
|
|
259
|
-
|
234
|
+
def render_all_specifications(spec_list) # rubocop:disable Metrics/MethodLength
|
235
|
+
path = @configuration.project_root_directory
|
260
236
|
|
261
|
-
|
237
|
+
FileUtils.mkdir_p("#{path}/build/specifications")
|
262
238
|
|
263
|
-
|
264
|
-
|
265
|
-
spec_list.each do |doc|
|
239
|
+
spec_list.each do |doc|
|
240
|
+
doc.to_console
|
266
241
|
|
267
|
-
|
242
|
+
img_src_dir = "#{path}/specifications/#{doc.id}/img"
|
243
|
+
img_dst_dir = "#{path}/build/specifications/#{doc.id}/img"
|
268
244
|
|
269
|
-
|
270
|
-
img_dst_dir = path + "/build/specifications/" + doc.id + "/img"
|
271
|
-
|
272
|
-
FileUtils.mkdir_p(img_dst_dir)
|
245
|
+
FileUtils.mkdir_p(img_dst_dir)
|
273
246
|
|
274
|
-
|
275
|
-
FileUtils.copy_entry( img_src_dir, img_dst_dir )
|
276
|
-
end
|
247
|
+
FileUtils.copy_entry(img_src_dir, img_dst_dir) if File.directory?(img_src_dir)
|
277
248
|
|
278
|
-
|
279
|
-
|
280
|
-
doc.to_html( nav_pane, "#{path}/build/specifications/" )
|
281
|
-
end
|
249
|
+
nav_pane = NavigationPane.new(doc)
|
250
|
+
doc.to_html(nav_pane, "#{path}/build/specifications/")
|
282
251
|
end
|
252
|
+
end
|
283
253
|
|
284
|
-
|
285
|
-
|
286
|
-
# create a sidebar first
|
287
|
-
# nav_pane = NavigationPane.new(@specifications)
|
254
|
+
def render_all_protocols
|
255
|
+
path = @configuration.project_root_directory
|
288
256
|
|
289
|
-
|
257
|
+
FileUtils.mkdir_p("#{path}/build/tests/protocols")
|
290
258
|
|
291
|
-
|
292
|
-
|
293
|
-
|
259
|
+
@protocols.each do |doc|
|
260
|
+
img_src_dir = "#{path}/tests/protocols/#{doc.id}/img"
|
261
|
+
img_dst_dir = "#{path}/build/tests/protocols/#{doc.id}/img"
|
294
262
|
|
295
|
-
|
296
|
-
img_dst_dir = path + "/build/tests/protocols/" + doc.id + "/img"
|
297
|
-
|
298
|
-
FileUtils.mkdir_p(img_dst_dir)
|
263
|
+
FileUtils.mkdir_p(img_dst_dir)
|
299
264
|
|
300
|
-
|
301
|
-
FileUtils.copy_entry( img_src_dir, img_dst_dir )
|
302
|
-
end
|
265
|
+
FileUtils.copy_entry(img_src_dir, img_dst_dir) if File.directory?(img_src_dir)
|
303
266
|
|
304
|
-
|
305
|
-
|
267
|
+
nav_pane = NavigationPane.new(doc)
|
268
|
+
doc.to_html(nav_pane, "#{path}/build/tests/protocols/")
|
306
269
|
end
|
270
|
+
end
|
307
271
|
|
308
|
-
|
309
|
-
|
310
|
-
path = @configuration.project_root_directory
|
272
|
+
def render_index
|
273
|
+
path = @configuration.project_root_directory
|
311
274
|
|
312
|
-
|
313
|
-
|
275
|
+
doc = @index
|
276
|
+
doc.to_console
|
314
277
|
|
315
|
-
|
316
|
-
|
278
|
+
doc.to_html("#{path}/build/")
|
279
|
+
end
|
317
280
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
end
|
281
|
+
def create_search_data
|
282
|
+
db = SpecificationsDb.new @specifications
|
283
|
+
data_path = "#{@configuration.project_root_directory}/build/data"
|
284
|
+
FileUtils.mkdir_p(data_path)
|
285
|
+
db.save(data_path)
|
286
|
+
end
|
287
|
+
end
|