tax_generator 0.0.1
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/.gitignore +21 -0
- data/.inch.yml +10 -0
- data/.reek +10 -0
- data/.rspec +1 -0
- data/.rubocop.yml +72 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +154 -0
- data/LICENSE +20 -0
- data/README.md +131 -0
- data/Rakefile +26 -0
- data/bin/tax_generator +7 -0
- data/data/input/.gitignore +25 -0
- data/data/input/destinations.xml +1073 -0
- data/data/input/taxonomy.xml +78 -0
- data/data/output/.gitignore +4 -0
- data/init.rb +1 -0
- data/lib/tax_generator/all.rb +26 -0
- data/lib/tax_generator/application.rb +125 -0
- data/lib/tax_generator/classes/destination.rb +103 -0
- data/lib/tax_generator/classes/file_creator.rb +100 -0
- data/lib/tax_generator/classes/processor.rb +270 -0
- data/lib/tax_generator/classes/taxonomy_tree.rb +97 -0
- data/lib/tax_generator/cli.rb +14 -0
- data/lib/tax_generator/helpers/application_helper.rb +154 -0
- data/lib/tax_generator/version.rb +27 -0
- data/lib/tax_generator.rb +1 -0
- data/spec/lib/tax_generator/application_spec.rb +0 -0
- data/spec/lib/tax_generator/classes/destination_spec.rb +62 -0
- data/spec/lib/tax_generator/classes/file_creator_spec.rb +96 -0
- data/spec/lib/tax_generator/classes/processor_spec.rb +30 -0
- data/spec/lib/tax_generator/classes/taxonomy_tree_spec.rb +0 -0
- data/spec/lib/tax_generator/cli_spec.rb +0 -0
- data/spec/lib/tax_generator/helpers/application_helper_spec.rb +0 -0
- data/spec/spec_helper.rb +60 -0
- data/tax_generator.gemspec +40 -0
- data/templates/static/all.css +586 -0
- data/templates/template.html.erb +91 -0
- metadata +452 -0
@@ -0,0 +1,270 @@
|
|
1
|
+
require_relative '../helpers/application_helper'
|
2
|
+
module TaxGenerator
|
3
|
+
# class used to process xml files and create html files
|
4
|
+
#
|
5
|
+
# @!attribute options
|
6
|
+
# @return [Hash] the options that can determine the input and output files and folders
|
7
|
+
#
|
8
|
+
# @!attribute worker_supervisor
|
9
|
+
# @return [Celluloid::SupervisionGroup] the supervision group that supervises workers
|
10
|
+
# @!attribute workers
|
11
|
+
# @return [Celluloid::Actor] the actors that will work on the jobs
|
12
|
+
# @!attribute taxonomy
|
13
|
+
# @return [TaxGenerator::TaxonomyTree] the taxonomy tree that holds the nodes from the taxonomy xml document
|
14
|
+
# @!attribute jobs
|
15
|
+
# @return [Hash] each key from the job list is the job id, and the value is the job itself
|
16
|
+
# @!attribute job_to_worker
|
17
|
+
# @return [Hash] each key from the list is the job id, and the value is the worker that will handle the job
|
18
|
+
# @!attribute worker_to_job
|
19
|
+
# @return [Hash] each key from the list is the workers mailbox address, and the value is the job being handled by the worker
|
20
|
+
# @!attribute condition
|
21
|
+
# @return [Celluloid::Condition] the supervision group that supervises workers
|
22
|
+
class Processor
|
23
|
+
include Celluloid
|
24
|
+
include Celluloid::Logger
|
25
|
+
include Celluloid::Notifications
|
26
|
+
include TaxGenerator::ApplicationHelper
|
27
|
+
|
28
|
+
attr_reader :options, :worker_supervisor, :workers, :taxonomy, :jobs, :job_to_worker, :worker_to_job, :condition
|
29
|
+
|
30
|
+
trap_exit :worker_died
|
31
|
+
|
32
|
+
# receives a list of options that are used to determine the input files and output and input folders
|
33
|
+
#
|
34
|
+
# @param [Hash] options the options that can determine the input and output files and folders
|
35
|
+
# @option options [String] :input_dir The input directory
|
36
|
+
# @option options [String]:output_dir The output directory
|
37
|
+
# @option options [String] :taxonomy_file_name The taxonomy file name
|
38
|
+
# @option options [String] :destinations_file_name The destinations file name
|
39
|
+
#
|
40
|
+
# @see #work
|
41
|
+
#
|
42
|
+
# @return [void]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def initialize(options = {})
|
46
|
+
Celluloid.boot
|
47
|
+
@options = options.is_a?(Hash) ? options.symbolize_keys : {}
|
48
|
+
@worker_supervisor = Celluloid::SupervisionGroup.run!
|
49
|
+
@workers = @worker_supervisor.pool(TaxGenerator::FileCreator, as: :workers, size: 50)
|
50
|
+
Actor.current.link @workers
|
51
|
+
@jobs = {}
|
52
|
+
@job_to_worker = {}
|
53
|
+
@worker_to_job = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
# returns the input folder from the options list
|
57
|
+
# otherwise the default path
|
58
|
+
#
|
59
|
+
# @return [String]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def input_folder
|
63
|
+
@options.fetch(:input_dir, "#{root}/data/input")
|
64
|
+
end
|
65
|
+
|
66
|
+
# returns the taxonomy filename from the option list
|
67
|
+
# otherwise the default filename
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def taxonomy_file_name
|
73
|
+
@options.fetch(:taxonomy_filename, 'taxonomy.xml')
|
74
|
+
end
|
75
|
+
|
76
|
+
# returns the destinations filename from the option list
|
77
|
+
# otherwise the default filename
|
78
|
+
#
|
79
|
+
# @return [String]
|
80
|
+
#
|
81
|
+
# @api public
|
82
|
+
def destinations_file_name
|
83
|
+
@options.fetch(:destinations_filename, 'destinations.xml')
|
84
|
+
end
|
85
|
+
|
86
|
+
# returns the output folder path from the option list
|
87
|
+
# otherwise the default path
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
#
|
91
|
+
# @api public
|
92
|
+
def output_folder
|
93
|
+
@options.fetch(:output_dir, "#{root}/data/output")
|
94
|
+
end
|
95
|
+
|
96
|
+
# returns the full path to the taxonomy file
|
97
|
+
#
|
98
|
+
# @return [String]
|
99
|
+
#
|
100
|
+
# @api public
|
101
|
+
def taxonomy_file_path
|
102
|
+
File.join(input_folder, taxonomy_file_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
# returns the full path to the destinations file
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
def destinations_file_path
|
111
|
+
File.join(input_folder, destinations_file_name)
|
112
|
+
end
|
113
|
+
|
114
|
+
# returns the full path to the static folder
|
115
|
+
#
|
116
|
+
# @return [String]
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def static_output_dir
|
120
|
+
File.join(output_folder, 'static')
|
121
|
+
end
|
122
|
+
|
123
|
+
# cleans the output folder and re-creates it and the static folder
|
124
|
+
#
|
125
|
+
# @return [void]
|
126
|
+
#
|
127
|
+
# @api public
|
128
|
+
def prepare_output_dirs
|
129
|
+
FileUtils.rm_rf Dir["#{output_folder}/**/*"]
|
130
|
+
create_directories(output_folder, static_output_dir)
|
131
|
+
FileUtils.cp_r(Dir["#{File.join(root, 'templates', 'static')}/*"], static_output_dir)
|
132
|
+
end
|
133
|
+
|
134
|
+
# checks if all workers finished and returns true or false
|
135
|
+
#
|
136
|
+
# @return [Boolean]
|
137
|
+
#
|
138
|
+
# @api public
|
139
|
+
def all_workers_finished?
|
140
|
+
@jobs.all? { |_job_id, job| job['status'] == 'finished' }
|
141
|
+
end
|
142
|
+
|
143
|
+
# registers all the jobs so that the managers can have access to them at any time
|
144
|
+
#
|
145
|
+
# @param [Array] jobs the jobs that will be registered
|
146
|
+
#
|
147
|
+
# @return [void]
|
148
|
+
#
|
149
|
+
# @api public
|
150
|
+
def register_jobs(*jobs)
|
151
|
+
jobs.pmap do |job|
|
152
|
+
job = job.stringify_keys
|
153
|
+
@jobs[job['atlas_id']] = job
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# registers all the jobs, and then delegates them to workers
|
158
|
+
# @see #register_jobs
|
159
|
+
# @see TaxGenerator::FileCreator#work
|
160
|
+
#
|
161
|
+
# @param [Array] jobs the jobs that will be delegated to the workers
|
162
|
+
#
|
163
|
+
# @return [void]
|
164
|
+
#
|
165
|
+
# @api public
|
166
|
+
def delegate_job(*jobs)
|
167
|
+
# jobs need to be added into the manager before starting task to avoid adding new key while iterating
|
168
|
+
register_jobs(*jobs)
|
169
|
+
current_actor = Actor.current
|
170
|
+
@jobs.pmap do |_job_id, job|
|
171
|
+
@workers.async.work(job, current_actor) if @workers.alive?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# parses the destinations xml document, gets each destination and adds a new job for that
|
176
|
+
# destination in the job list and then returns it
|
177
|
+
# @see #nokogiri_xml
|
178
|
+
#
|
179
|
+
# @return [Array<Hash>]
|
180
|
+
#
|
181
|
+
# @api public
|
182
|
+
def fetch_file_jobs
|
183
|
+
jobs = [{ atlas_id: 0, taxonomy: @taxonomy, destination: nil, output_folder: output_folder }]
|
184
|
+
nokogiri_xml(destinations_file_path).xpath('//destination').pmap do |destination|
|
185
|
+
atlas_id = destination.attributes['atlas_id']
|
186
|
+
jobs << { atlas_id: atlas_id.value, taxonomy: @taxonomy, destination: destination, output_folder: output_folder }
|
187
|
+
end
|
188
|
+
jobs
|
189
|
+
end
|
190
|
+
|
191
|
+
# fetches the jobs for file generation, then delegates the jobs to workers and waits untill workers finish
|
192
|
+
# @see #fetch_file_jobs
|
193
|
+
# @see #delegate_job
|
194
|
+
# @see #wait_jobs_termination
|
195
|
+
#
|
196
|
+
# @return [void]
|
197
|
+
#
|
198
|
+
# @api public
|
199
|
+
def generate_files
|
200
|
+
@condition = Celluloid::Condition.new
|
201
|
+
jobs = fetch_file_jobs
|
202
|
+
delegate_job(*jobs)
|
203
|
+
wait_jobs_termination
|
204
|
+
end
|
205
|
+
|
206
|
+
# retrieves the information about the node from the tree and generates for each destination a new File
|
207
|
+
# @see #create_file
|
208
|
+
#
|
209
|
+
# @param [TaxGenerator::TaxonomyTree] taxonomy the taxonomy tree that will be used for fetching node information
|
210
|
+
#
|
211
|
+
# @return [void]
|
212
|
+
#
|
213
|
+
# @api public
|
214
|
+
def wait_jobs_termination
|
215
|
+
result = @condition.wait
|
216
|
+
return unless result.present?
|
217
|
+
terminate
|
218
|
+
end
|
219
|
+
|
220
|
+
# registers the worker so that the current actor has access to it at any given time and starts the worker
|
221
|
+
# @see TaxGenerator::FileCreator#start_work
|
222
|
+
#
|
223
|
+
# @param [Hash] job the job that the worker will work
|
224
|
+
# @param [TaxGenerator::FileCreator] worker the worker that will create the file
|
225
|
+
#
|
226
|
+
# @return [void]
|
227
|
+
#
|
228
|
+
# @api public
|
229
|
+
def register_worker_for_job(job, worker)
|
230
|
+
@job_to_worker[job['atlas_id']] = worker
|
231
|
+
@worker_to_job[worker.mailbox.address] = job
|
232
|
+
log_message("worker #{worker.job_id} registed into manager")
|
233
|
+
Actor.current.link worker
|
234
|
+
worker.async.start_work
|
235
|
+
end
|
236
|
+
|
237
|
+
# generates the taxonomy tree , prints it and generates the files
|
238
|
+
# @see TaxGenerator::TaxonomyTree#new
|
239
|
+
# @see Tree::TreeNode#print_tree
|
240
|
+
# @see #generate_files
|
241
|
+
#
|
242
|
+
# @return [void]
|
243
|
+
#
|
244
|
+
# @api public
|
245
|
+
def work
|
246
|
+
prepare_output_dirs
|
247
|
+
if File.directory?(input_folder) && File.file?(taxonomy_file_path) && File.file?(destinations_file_path)
|
248
|
+
@taxonomy = TaxGenerator::TaxonomyTree.new(taxonomy_file_path)
|
249
|
+
@taxonomy.print_tree
|
250
|
+
generate_files
|
251
|
+
else
|
252
|
+
log_message('Please provide valid options', log_method: 'fatal')
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# logs the message about working being dead if a worker crashes
|
257
|
+
# @param [TaxGenerator::FileCreator] worker the worker that died
|
258
|
+
# @param [String] reason the reason for which the worker died
|
259
|
+
#
|
260
|
+
# @return [void]
|
261
|
+
#
|
262
|
+
# @api public
|
263
|
+
def worker_died(worker, reason)
|
264
|
+
mailbox_address = worker.mailbox.address
|
265
|
+
job = @worker_to_job.delete(mailbox_address)
|
266
|
+
return if reason.blank? || job.blank?
|
267
|
+
log_message("worker job #{job['atlas_id']} with mailbox #{mailbox_address.inspect} died for reason: #{log_error(reason)}", log_method: 'fatal')
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative '../helpers/application_helper'
|
2
|
+
module TaxGenerator
|
3
|
+
# class used to create the Taxonomy Tree
|
4
|
+
#
|
5
|
+
# @!attribute root_node
|
6
|
+
# @return [Tree::TreeNode] the root node of the tree
|
7
|
+
#
|
8
|
+
# @!attribute document
|
9
|
+
# @return [Nokogiri::XML] the xml document used to build the tree
|
10
|
+
class TaxonomyTree
|
11
|
+
include TaxGenerator::ApplicationHelper
|
12
|
+
attr_reader :root_node, :document
|
13
|
+
|
14
|
+
# receives a file path that will be parsed and used to build the tree
|
15
|
+
# @see Tree::TreeNode#new
|
16
|
+
# @see #add_node
|
17
|
+
#
|
18
|
+
# @param [String] file_path the path to the xml file that will be parsed and used to build the tree
|
19
|
+
#
|
20
|
+
# @return [void]
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
def initialize(file_path)
|
24
|
+
@document = nokogiri_xml(file_path)
|
25
|
+
taxonomy_root = @document.at_xpath('//taxonomy_name')
|
26
|
+
@root_node = Tree::TreeNode.new(taxonomy_root.content, nil)
|
27
|
+
@document.xpath('//node').pmap do |taxonomy_node|
|
28
|
+
add_node(taxonomy_node, @root_node)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# gets the atlas_id from the nokogiri element and then searches first child whose name is 'node_name'
|
33
|
+
# and uses this to insert the node
|
34
|
+
# @see #insert_node
|
35
|
+
#
|
36
|
+
# @param [Nokogiri::Element] taxonomy_node the nokogiri element that wants to be added to the tree
|
37
|
+
# @param [Tree::TreeNode] node the parent node to which the element needs to be added
|
38
|
+
#
|
39
|
+
# @return [void]
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def add_taxonomy_node(taxonomy_node, node)
|
43
|
+
atlas_node_id = taxonomy_node.attributes['atlas_node_id']
|
44
|
+
node_name = taxonomy_node.children.find { |child| child.name == 'node_name' }
|
45
|
+
insert_node(atlas_node_id, node_name, node)
|
46
|
+
end
|
47
|
+
|
48
|
+
# inserts a new node in the tree by checking first if atlas_id and node_name are present
|
49
|
+
# and then adds the node as child to the node passed as third argument
|
50
|
+
# @see Tree::TreeNode#new
|
51
|
+
#
|
52
|
+
# @param [Nokogiri::Element] atlas_node_id the element that holds the value of the atlas_id attribute
|
53
|
+
# @param [Nokogiri::Element] node_name the the element that holds the node name of the element
|
54
|
+
# @param [Tree::TreeNode] node the parent node to which the element needs to be added
|
55
|
+
#
|
56
|
+
# @return [void]
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def insert_node(atlas_node_id, node_name, node)
|
60
|
+
return if atlas_node_id.blank? || node_name.blank?
|
61
|
+
current_node = Tree::TreeNode.new(atlas_node_id.value, node_name.content)
|
62
|
+
node << current_node
|
63
|
+
current_node
|
64
|
+
end
|
65
|
+
|
66
|
+
# checks to see if the nokogiri element has any childrens, if it has , will add it to the tree and iterates over the
|
67
|
+
# children and adds them as child to the newly added node
|
68
|
+
# @see #add_taxonomy_node
|
69
|
+
#
|
70
|
+
# @param [Nokogiri::Element] taxonomy_node the nokogiri element that wants to be added to the tree
|
71
|
+
# @param [Tree::TreeNode] node the parent node to which the element needs to be added
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def add_node(taxonomy_node, node)
|
77
|
+
return unless taxonomy_node.children.any?
|
78
|
+
tax_node = add_taxonomy_node(taxonomy_node, node)
|
79
|
+
taxonomy_node.xpath('./node').pmap do |child_node|
|
80
|
+
add_taxonomy_node(child_node, tax_node) if tax_node.present?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# receives a file path that will be parsed and used to build the tree
|
85
|
+
#
|
86
|
+
# @param [String] name the name of the method that is invoked against the tree
|
87
|
+
# @param [Array] args the arguments to the method
|
88
|
+
# @param [Proc] block the block that will be passed to the method
|
89
|
+
#
|
90
|
+
# @return [void]
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def method_missing(name, *args, &block)
|
94
|
+
@root_node.send name, *args, &block
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative './all'
|
2
|
+
module TaxGenerator
|
3
|
+
# this is the class that will be invoked from terminal , and willl use the invoke task as the primary function.
|
4
|
+
class CLI
|
5
|
+
# method used to start
|
6
|
+
#
|
7
|
+
# @return [void]
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
def self.start
|
11
|
+
TaxGenerator::Application.new.run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module TaxGenerator
|
2
|
+
# class that holds the helper methods used in the classes
|
3
|
+
module ApplicationHelper
|
4
|
+
delegate :app_logger,
|
5
|
+
to: :'TaxGenerator::Application'
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# returns the text from a nokogiri element by rejecting blank elements
|
10
|
+
#
|
11
|
+
# @param [Nokogiri::Element] element the nokogiri element that will select only children with content and returns their text
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def elements_with_content(element)
|
17
|
+
if element.present?
|
18
|
+
element.select { |elem| elem.content.present? }.join(&:text)
|
19
|
+
else
|
20
|
+
element
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# returns the root path of the gem
|
25
|
+
#
|
26
|
+
# @return [void]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def root
|
30
|
+
File.expand_path(File.dirname(File.dirname(File.dirname(__dir__))))
|
31
|
+
end
|
32
|
+
|
33
|
+
# returns a Nokogiri XML document from a file
|
34
|
+
#
|
35
|
+
# @param [String] file_path the path to the xml file that will be parsed
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def nokogiri_xml(file_path)
|
41
|
+
Nokogiri::XML(File.open(file_path), nil, 'UTF-8')
|
42
|
+
end
|
43
|
+
|
44
|
+
# creates directories from a list of arguments
|
45
|
+
#
|
46
|
+
# @param [Array] args the arguments that will be used as directory names and will create them
|
47
|
+
#
|
48
|
+
# @return [void]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def create_directories(*args)
|
52
|
+
args.pmap do |argument|
|
53
|
+
FileUtils.mkdir_p(argument) unless File.directory?(argument)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# sets the exception handler for celluloid actors
|
58
|
+
#
|
59
|
+
#
|
60
|
+
# @return [void]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def set_celluloid_exception_handling
|
64
|
+
Celluloid.logger = app_logger
|
65
|
+
Celluloid.task_class = Celluloid::TaskThread
|
66
|
+
Celluloid.exception_handler do |ex|
|
67
|
+
unless ex.is_a?(Interrupt)
|
68
|
+
log_error(ex)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Reads a file and interpretes it as ERB
|
74
|
+
#
|
75
|
+
# @param [String] file_path the file that will be read and interpreted as ERB
|
76
|
+
#
|
77
|
+
# @return [String]
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def erb_template(file_path)
|
81
|
+
template = ERB.new(File.read(file_path))
|
82
|
+
template.filename = file_path
|
83
|
+
template
|
84
|
+
end
|
85
|
+
|
86
|
+
# Displays a error with fatal log level
|
87
|
+
# @see #format_error
|
88
|
+
# @see #log_message
|
89
|
+
#
|
90
|
+
# @param [Exception] exception the exception that will be formatted and printed on screen
|
91
|
+
#
|
92
|
+
# @return [String]
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def log_error(exception)
|
96
|
+
message = format_error(exception)
|
97
|
+
log_message(message, log_method: 'fatal')
|
98
|
+
end
|
99
|
+
|
100
|
+
# formats a exception to be displayed on screen
|
101
|
+
#
|
102
|
+
# @param [String] message the message that will be printed to the log file
|
103
|
+
# @param [Hash] options the options used to determine how to log the message
|
104
|
+
# @option options [String] :log_method The log method , by default debug
|
105
|
+
#
|
106
|
+
# @return [String]
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
def log_message(message, options = {})
|
110
|
+
app_logger.send(options.fetch(:log_method, 'debug'), message)
|
111
|
+
end
|
112
|
+
|
113
|
+
# formats a exception to be displayed on screen
|
114
|
+
#
|
115
|
+
# @param [Exception] exception the exception that will be formatted and printed on screen
|
116
|
+
#
|
117
|
+
# @return [String]
|
118
|
+
#
|
119
|
+
# @api public
|
120
|
+
def format_error(exception)
|
121
|
+
message = "#{exception.class} (#{exception.respond_to?(:message) ? exception.message : exception.inspect}):\n"
|
122
|
+
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
|
123
|
+
message << ' ' << exception.backtrace.join("\n ") if exception.respond_to?(:backtrace)
|
124
|
+
message
|
125
|
+
end
|
126
|
+
|
127
|
+
# wrapper to execute a block and rescue from exception
|
128
|
+
# @see #set_celluloid_exception_handling
|
129
|
+
# @see #rescue_interrupt
|
130
|
+
# @see #log_error
|
131
|
+
#
|
132
|
+
# @return [void]
|
133
|
+
#
|
134
|
+
# @api public
|
135
|
+
def execute_with_rescue
|
136
|
+
set_celluloid_exception_handling
|
137
|
+
yield if block_given?
|
138
|
+
rescue Interrupt
|
139
|
+
rescue_interrupt
|
140
|
+
rescue => error
|
141
|
+
log_error(error)
|
142
|
+
end
|
143
|
+
|
144
|
+
# rescues from a interrupt error and shows a message
|
145
|
+
#
|
146
|
+
# @return [void]
|
147
|
+
#
|
148
|
+
# @api public
|
149
|
+
def rescue_interrupt
|
150
|
+
`stty icanon echo`
|
151
|
+
puts "\n Command was cancelled due to an Interrupt error."
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Returns the version of the gem as a <tt>Gem::Version</tt>
|
2
|
+
module TaxGenerator
|
3
|
+
# it prints the gem version as a string
|
4
|
+
#
|
5
|
+
# @return [String]
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
def self.gem_version
|
9
|
+
Gem::Version.new VERSION::STRING
|
10
|
+
end
|
11
|
+
|
12
|
+
# module used to generate the version string
|
13
|
+
# provides a easy way of getting the major, minor and tiny
|
14
|
+
module VERSION
|
15
|
+
# major release version
|
16
|
+
MAJOR = 0
|
17
|
+
# minor release version
|
18
|
+
MINOR = 0
|
19
|
+
# tiny release version
|
20
|
+
TINY = 1
|
21
|
+
# prelease version ( set this only if it is a prelease)
|
22
|
+
PRE = nil
|
23
|
+
|
24
|
+
# generates the version string
|
25
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'tax_generator/all'
|
File without changes
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding:utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
describe TaxGenerator::Destination do
|
4
|
+
let(:default_processor) {TaxGenerator::Processor.new}
|
5
|
+
let(:str) { default_processor.destinations_file_path }
|
6
|
+
|
7
|
+
let(:destination_xml) { @xml ||= Nokogiri::XML(File.open(str), nil, 'UTF-8') }
|
8
|
+
let(:info_base) { './/practical_information/health_and_safety' }
|
9
|
+
before(:each) do
|
10
|
+
@destination = TaxGenerator::Destination.new(destination_xml)
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'xpaths' do
|
14
|
+
it 'fetches the introduction' do
|
15
|
+
destination_xml.expects(:xpath).with('.//introductory/introduction/overview').returns(true)
|
16
|
+
@destination.introduction
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'fetches the history' do
|
20
|
+
destination_xml.expects(:xpath).with('./history/history/history').returns(true)
|
21
|
+
@destination.history
|
22
|
+
end
|
23
|
+
|
24
|
+
['dangers_and_annoyances', 'while_youre_there', 'before_you_go', 'money_and_costs/money'].each do |name|
|
25
|
+
it "fetches the practical_information with #{name}" do
|
26
|
+
destination_xml.stubs(:xpath).returns([])
|
27
|
+
destination_xml.expects(:xpath).with(".//practical_information/health_and_safety/#{name}").returns([])
|
28
|
+
@destination.practical_information
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'fetches the transport' do
|
33
|
+
destination_xml.expects(:xpath).with('.//transport/getting_around').returns(true)
|
34
|
+
@destination.transport
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'fetches the weather' do
|
38
|
+
destination_xml.expects(:xpath).with('.//weather').returns(true)
|
39
|
+
@destination.weather
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'fetches the work_live_study' do
|
43
|
+
destination_xml.expects(:xpath).with('.//work_live_study').returns(true)
|
44
|
+
@destination.work_live_study
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns the hash' do
|
49
|
+
expect(@destination.to_hash).to eq(
|
50
|
+
{
|
51
|
+
introduction: @destination.introduction,
|
52
|
+
history: @destination.history,
|
53
|
+
practical_information: @destination.practical_information,
|
54
|
+
transport: @destination.transport,
|
55
|
+
weather: @destination.weather,
|
56
|
+
work_live_study: @destination.work_live_study
|
57
|
+
}.each_with_object({}) do |(key, value), hsh|
|
58
|
+
hsh[key] = elements_with_content(value)
|
59
|
+
hsh
|
60
|
+
end)
|
61
|
+
end
|
62
|
+
end
|