tax_generator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|