opengraph_transporter 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aa0283f8d7638544d60cda64f99807201bb1a5e5
4
+ data.tar.gz: 8fcd12be282e33dbdb62fef653c3ee621b4df58c
5
+ SHA512:
6
+ metadata.gz: d7dee52e5207f8e2ffcd78a56c4885373d7bc5d4bfd3d1d0e06caf97e3a93c6e4a12f5a70aa41ecb7309fd28e7b83fbca7c96b09cdbd004a9bed43b4d19c85d9
7
+ data.tar.gz: 8fd34a80d0b543ad24e37c5a33a6e3d779698a5953fe0fdc3dfb479b7b72bd17b8472a4a8880c9f480d788af067a953a1827c013c629307a54ed564c779b724d
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in opengraph_transporter.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Barry Quigley
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # OpengraphTransporter
2
+
3
+ opengraph_transporter is a Ruby console app that exports Facebook OpenGraph translations between Developer Apps.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'opengraph_transporter'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install opengraph_transporter
18
+
19
+
20
+ ## Requirements
21
+
22
+ * Ruby 1.9.3 or higher
23
+ * Firefox 22.0.0 or higher
24
+
25
+ ## Usage
26
+
27
+ 1. Run opengraph_transporter
28
+ 2. Enter Source Facebook Developer App ID and App Secret
29
+ 3. Enter Destination Facebook Developer App ID and App Secret
30
+ 4. Enter App translations locale.
31
+ 5. Follow the prompts.
32
+
33
+
34
+ ```
35
+ $ Please Enter Source Application Id
36
+ ```
37
+
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'lib/opengraph_transporter'
8
+ t.test_files = FileList['test/lib/opengraph_transporter/*_test.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ## $VERBOSE
4
+ ## -W0 NO Warnings nil
5
+ ## -W1 Quiet false
6
+ ## -W2 Verbose true
7
+ BEGIN { $VERBOSE = false }
8
+
9
+ begin
10
+ require 'opengraph_transporter'
11
+ rescue LoadError
12
+ require 'rubygems'
13
+ require 'opengraph_transporter'
14
+ end
15
+
16
+ # -----------------------------------------------------------------------
17
+ # Main
18
+ # -----------------------------------------------------------------------
19
+
20
+ OpengraphTransporter::Base.run
21
+
data/lib/.DS_Store ADDED
Binary file
Binary file
@@ -0,0 +1,214 @@
1
+ module OpengraphTransporter
2
+ class Base
3
+
4
+ class << self
5
+
6
+ @@translation = nil
7
+ @@user = {}
8
+
9
+ def run
10
+ setup
11
+ say("<%= color('Preparing Translations....', YELLOW, BOLD) %>")
12
+ prepare
13
+ Common.show_translations_info(translation)
14
+ say("<%= color('Process existing Destination application Translations (#{Base.translation[:dst_app_name]} native stubs) ....', YELLOW, BOLD) %>")
15
+ run_export
16
+ end
17
+
18
+ def translation
19
+ @@translation
20
+ end
21
+
22
+ def translation=(value)
23
+ @@translation = value
24
+ end
25
+
26
+ def user
27
+ @@user
28
+ end
29
+
30
+ def user=(value)
31
+ @@user = value
32
+ end
33
+
34
+ def inspect
35
+ "<#{self.name} user: #{user} translation: #{translation.inspect} >"
36
+ end
37
+
38
+ def to_s
39
+ inspect
40
+ end
41
+
42
+ private
43
+
44
+ def setup
45
+ display_app_splash
46
+ get_apps_details
47
+ self
48
+ end
49
+
50
+ def run_export
51
+ translation[:dst_translation_arr] = Scraper.ingest_app_translations(translation[:destination_application_id], translation[:app_locale])
52
+ if translation[:dst_translation_arr].length == 0
53
+ say("No destination app <%= color('(#{Base.translation[:dst_app_name]})', RED, BOLD) %> Open Graph Translations found, please check that Open Graph stories exist and localization.")
54
+ else
55
+ translation[:src_translation_arr] = Scraper.update_display_names!(translation[:src_translation_arr], translation[:src_app_name].to_s, translation[:dst_app_name].to_s)
56
+ translations_cleanup
57
+ say("<%= color('Export Translations....', YELLOW, BOLD) %>")
58
+ choose do |menu|
59
+ menu.prompt = "This action will overwrite existing #{translation[:dst_app_name]} translations. Are you sure you want to export translations?"
60
+ menu.choice(:Yes) {
61
+ say("....starting Export!")
62
+ Browser.export
63
+ }
64
+ menu.choices(:No) { say("...exiting Exporter!") }
65
+ end
66
+ end
67
+ end
68
+
69
+ def prepare
70
+ src_translation_arr = Common.get_application_translations(translation[:source_app_token], translation[:app_locale], false)
71
+ dst_translation_arr = Common.get_application_translations(translation[:destination_app_token], translation[:app_locale], true)
72
+ translation[:src_translation_arr] = src_translation_arr
73
+ translation[:dst_translation_arr] = dst_translation_arr
74
+
75
+ if translation[:src_translation_arr].nil?
76
+ Common.show_arguments_info(translation)
77
+ say("<%= color('Invalid application data, please recheck application and locale settings.', RED, BOLD) %>")
78
+ exit
79
+ end
80
+ end
81
+
82
+ def get_apps_details(error_keys = [])
83
+ invalid_entries = false
84
+ initialize_translation
85
+
86
+ translation.each do |key, val|
87
+ if val.empty? || error_keys.include?(key.to_s)
88
+ invalid_entries = true
89
+ case key
90
+ when :source_application_id then translation[key] = ask_for_app_id(key).to_s
91
+ when :source_application_secret then translation[key] = ask_for_app_secret(key)
92
+ when :destination_application_id then translation[key] = ask_for_app_id(key).to_s
93
+ when :destination_application_secret then translation[key] = ask_for_app_secret(key)
94
+ else translation[key] = ask("\nPlease Enter <%= color('#{capitalize(key)}', GREEN, BOLD) %>", String)
95
+ end
96
+ end
97
+ end
98
+
99
+ if invalid_entries
100
+ validate_applications_data
101
+ end
102
+ end
103
+
104
+ def validate_applications_data
105
+ locales = generate_locales
106
+ error_keys = []
107
+
108
+ if translation[:source_application_id].empty?
109
+ error_keys << "source_application_id"
110
+ end
111
+ if translation[:source_application_secret].empty?
112
+ error_keys << "source_application_secret"
113
+ end
114
+ if translation[:destination_application_id].empty?
115
+ error_keys << "destination_application_token"
116
+ end
117
+ if translation[:destination_application_secret].empty?
118
+ error_keys << "destination_application_secret"
119
+ end
120
+ if translation[:source_application_id].eql?(translation[:destination_application_id])
121
+ say("<%= color('Duplicated selection: ', RED, BOLD) %> please selection different source and destination applications. \n")
122
+ error_keys << "destination_application_id" << "destination_application_secret"
123
+ end
124
+ if translation[:app_locale].empty?
125
+ error_keys << "app_locale"
126
+ else
127
+ if !locales.include?(translation[:app_locale])
128
+ error_keys << "app_locale"
129
+ end
130
+ end
131
+
132
+ if !error_keys.empty?
133
+ say("<%= color('Invalid Data: ', RED, BOLD) %> #{error_keys.join(' ')} \n")
134
+ choose do |menu|
135
+ menu.prompt = "Re-enter app details?"
136
+ menu.choice(:Yes) {
137
+ get_apps_details(error_keys)
138
+ }
139
+ menu.choices(:No) {
140
+ say("...exiting Exporter!")
141
+ exit
142
+ }
143
+ end
144
+ else
145
+ get_app_specfics
146
+ end
147
+ end
148
+
149
+ def generate_locales
150
+ locales_arr = []
151
+ app_root = File.expand_path(File.dirname(__FILE__))
152
+ fb_locales_doc = File.open(File.join(app_root, "/resources/FacebookLocales.xml"))
153
+ locales = Nokogiri::XML(fb_locales_doc)
154
+
155
+ locales.xpath("//representation").each do |locale|
156
+ locales_arr << locale.text
157
+ end
158
+
159
+ return locales_arr
160
+ end
161
+
162
+ def get_app_specfics
163
+ say("\n.....retrieving app tokens")
164
+ translation[:source_app_token] = Common.get_app_token(translation[:source_application_id], translation[:source_application_secret])
165
+ translation[:destination_app_token] = Common.get_app_token(translation[:destination_application_id], translation[:destination_application_secret])
166
+
167
+ translation[:src_app_name] = Common.get_application_name(translation[:source_app_token], translation[:source_application_id])
168
+ translation[:dst_app_name] = Common.get_application_name(translation[:destination_app_token], translation[:destination_application_id])
169
+ end
170
+
171
+ def initialize_translation
172
+ @@translation ||= {:source_application_id => '', :source_application_secret => '', :destination_application_id => '', :destination_application_secret => '', :app_locale => '' }
173
+ end
174
+
175
+ def translations_cleanup
176
+ Common.update_destination_translations(translation)
177
+ Common.remove_empty_translations(translation)
178
+ Common.show_translations_info(translation)
179
+ end
180
+
181
+ def capitalize(sym)
182
+ sym.to_s.split('_').map(&:capitalize).join(' ')
183
+ end
184
+
185
+ def ask_for_app_id(translation_key)
186
+ ask("\nPlease Enter <%= color('#{capitalize(translation_key)}', GREEN, BOLD) %>", lambda { |id| id.to_s.strip } ) do |q|
187
+ q.validate = lambda { |p| (p =~ /^\d{15}$/) != nil }
188
+ q.responses[:not_valid] = "Please enter a 15 digit App Id."
189
+ end
190
+ end
191
+
192
+ def ask_for_app_secret(translation_key)
193
+ ask("\nPlease Enter <%= color('#{capitalize(translation_key)}', GREEN, BOLD) %>", lambda { |id| id.to_s.strip } ) do |q|
194
+ q.validate = lambda { |p| (p =~ /^[a-zA-Z0-9]{32}$/) != nil }
195
+ q.responses[:not_valid] = "Please enter a 32 character alphanumeric App Secret."
196
+ end
197
+ end
198
+
199
+ def display_app_splash
200
+ puts "\n"
201
+ splash = ConsoleSplash.new(6, 88)
202
+ splash.write_horizontal_pattern("||")
203
+ splash.write_vertical_pattern("||")
204
+ splash.write_center(-4, "Open Graph Translations Exporter")
205
+ splash.write_center(-3, "Version: #{OpengraphTransporter::VERSION}")
206
+ splash.splash
207
+ puts "\n"
208
+ end
209
+
210
+ end
211
+
212
+
213
+ end
214
+ end
@@ -0,0 +1,99 @@
1
+ module OpengraphTransporter
2
+ class Browser
3
+
4
+ MAX_TRANSLATION_PAGE_LIMIT = 30
5
+
6
+ class << self
7
+
8
+ def export
9
+ begin
10
+ Watir.driver = :webdriver
11
+ @browser = Watir::Browser.new :firefox
12
+ @page_index_counter = 0
13
+ @translation_count_index = 0
14
+ @translation = Base.translation
15
+ @translation_arr = @translation [:dst_translation_arr].clone
16
+ fb_login
17
+
18
+ developer_translations_home_uri = "https://www.facebook.com/translations/admin/browse.php?search=&sloc=en_US&aloc=#{@translation[:app_locale]}&app=#{@translation[:destination_application_id]}"
19
+ @browser.goto developer_translations_home_uri
20
+ GracefulQuit.enable
21
+ parse_translation_rows
22
+
23
+ rescue Exception => e
24
+ say("An error occurred when generating automated browser, do you have FireFox installed? \n\n error: #{e}.")
25
+ exit
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def parse_translation_rows
32
+ @translation_arr.each_with_index do |translation, index|
33
+ row_hash = "variant:" << translation[:native_hash]
34
+ translation_row = @browser.tr(:id, row_hash)
35
+ if translation_row.exists? && !translation[:translation].empty?
36
+ say("updating translation native hash: #{translation[:native_hash]} : #{translation[:translation]}")
37
+ translation_row.td(:class, /s_trans/).click
38
+ translation_row.textarea(:class, /uiTextareaAutogrow/).set translation[:translation]
39
+ translation_row.button(:id, /submit:/).click
40
+ sleep(0.25)
41
+ @translation_count_index += 1
42
+ end
43
+ GracefulQuit.check
44
+ end
45
+ process_next_page
46
+ end
47
+
48
+ def process_next_page
49
+ next_button = @browser.div(:class => "pagerpro_container").link(:text => /Next/)
50
+ if next_button.exists? && @page_index_counter < MAX_TRANSLATION_PAGE_LIMIT
51
+ begin
52
+ translations_page = @page_index_counter + 1
53
+ say("processing translation page.... #{translations_page + 1}")
54
+ @page_index_counter += 1
55
+ next_button.click
56
+ sleep(2.5)
57
+ parse_translation_rows
58
+ rescue Exception => e
59
+ say("Translation error: #{e}, carry on.")
60
+ parse_translation_rows
61
+ end
62
+ else
63
+ complete_translations_process
64
+ end
65
+ end
66
+
67
+ def complete_translations_process
68
+ fb_logout
69
+ open_graph_translations_stats
70
+ end
71
+
72
+ def fb_login
73
+ say("\n.....logging into Facebook")
74
+ @browser.goto "https://www.facebook.com"
75
+ @browser.text_field(id: "email").set Base.user[:fb_username]
76
+ @browser.text_field(id: "pass").set Base.user[:fb_password]
77
+ @browser.label(id: "loginbutton").click
78
+ end
79
+
80
+ def fb_logout
81
+ say(".....logging out of Facebook \n")
82
+ @browser.div(id: "userNavigationLabel").click
83
+ @browser.label(class: "uiLinkButton navSubmenu").click
84
+ @browser.close
85
+ end
86
+
87
+ def open_graph_translations_stats
88
+ say("<%= color('\n***********************************************************************************************************', GREEN, BOLD) %>")
89
+ say("Source Application: <%= color('#{@translation[:src_app_name]}', GREEN, BOLD) %>")
90
+ say("Destination Application: <%= color('#{@translation[:dst_app_name]}', GREEN, BOLD) %>")
91
+ say("Export completed: <%= color('#{@translation[:dst_translation_arr].length} Open Graph translations processed', GREEN, BOLD) %>")
92
+ say("<%= color('***********************************************************************************************************\n', GREEN, BOLD) %>")
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
99
+
@@ -0,0 +1,159 @@
1
+ module OpengraphTransporter
2
+ class Common
3
+
4
+ FB_GRAPH_HOST = "https://graph.facebook.com"
5
+
6
+ class << self
7
+
8
+ def update_destination_translations(translation)
9
+ say(".....updating translation text")
10
+ translation[:dst_translation_arr].each do |dst_translation|
11
+ translation[:src_translation_arr].each do |src_translation|
12
+ if dst_translation[:native].eql?(src_translation['native'])
13
+ dst_translation[:translation] = src_translation['translation']
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def remove_empty_translations(translation)
20
+ say(".....remove empty translations")
21
+ translation[:dst_translation_arr].each_with_index do |dst_translation, index|
22
+ if dst_translation[:translation].empty?
23
+ translation[:dst_translation_arr].delete_at(index)
24
+ end
25
+ end
26
+ end
27
+
28
+ def get_application_translations(app_token, app_locale, include_hash)
29
+ if include_hash
30
+ fql = %Q{select native_hash, native_string, best_string FROM translation WHERE locale = "#{app_locale}"}
31
+ mappings = {"native_hash" => "native_hash", "native_string" => "native", "best_string" => "translation"}
32
+ else
33
+ fql = %Q{select native_string, best_string FROM translation WHERE locale = "#{app_locale}"}
34
+ mappings = {"native_string" => "native", "best_string" => "translation"}
35
+ end
36
+
37
+ params = {:access_token => app_token, :q => fql}
38
+ request = 'fql'
39
+ app_translations = fb_graph_call(request, params)['data']
40
+ @app_translations = Array.new
41
+
42
+ app_translations.each do |translation|
43
+ @app_translations.push(Hash[translation.map {|k, v| [mappings[k], v] }])
44
+ end
45
+
46
+ if !include_hash
47
+ @app_translations = @app_translations.uniq!
48
+ end
49
+ return @app_translations
50
+ end
51
+
52
+ def get_application_details(app_token, app_id)
53
+ fql = %Q{select display_name, description, link, namespace from application where app_id = #{app_id}}
54
+ params = {:access_token => app_token, :q => fql}
55
+ request = 'fql'
56
+ response = fb_graph_call(request, params)
57
+ return response['data'][0]
58
+ end
59
+
60
+ def show_translations_info(translation)
61
+ say("<%= color('\n***********************************************************************************************************', YELLOW, BOLD) %>")
62
+ say("Source Application ID: <%= color('#{translation[:source_application_id]}', BOLD) %> Existing translations: <%= color('#{translation[:src_translation_arr].length}', YELLOW, BOLD) %> Application Name: <%= color('#{translation[:src_app_name]}', BOLD) %>")
63
+ say("Destination Application ID: <%= color('#{translation[:destination_application_id]}', BOLD) %> Existing translations: <%= color('#{translation[:dst_translation_arr].length}', YELLOW, BOLD) %> Application Name: <%= color('#{translation[:dst_app_name]}', BOLD) %>")
64
+ say("<%= color('***********************************************************************************************************\n', YELLOW, BOLD) %>")
65
+ end
66
+
67
+
68
+ def show_arguments_info(translation)
69
+ say("<%= color('\n***********************************************************************************************************', YELLOW, BOLD) %>")
70
+ say("Source Application ID: <%= color('#{Base.translation[:source_application_id]}', YELLOW, BOLD) %> Application Name: <%= color('#{Base.translation[:src_app_name]}', YELLOW, BOLD) %>")
71
+ say("Destination Application ID: <%= color('#{Base.translation[:destination_application_id]}', YELLOW, BOLD) %> Application Name: <%= color('#{Base.translation[:dst_app_name]}', YELLOW, BOLD) %>")
72
+ say("Selected Locale: <%= color('#{Base.translation[:app_locale]}', YELLOW, BOLD) %>")
73
+ say("<%= color('***********************************************************************************************************\n', YELLOW, BOLD) %>")
74
+ end
75
+
76
+ def get_application_name(app_token, app_id)
77
+ params = {:access_token => app_token}
78
+ request = "#{app_id}"
79
+ response = fb_graph_call(request, params)
80
+ if response.nil?
81
+ show_arguments_info(translation)
82
+ say("<%= color('Invalid application data, please recheck application and locale settings.', RED, BOLD) %>")
83
+ exit
84
+ end
85
+ return response['name']
86
+ end
87
+
88
+ def get_app_token(app_id, app_secret)
89
+ params = {
90
+ :client_id => app_id,
91
+ :client_secret => app_secret,
92
+ :grant_type => 'client_credentials'
93
+ }
94
+ path = "/oauth/access_token"
95
+ response = fb_graph_call(path, params, {:format => :text})
96
+
97
+ if response.nil?
98
+ say("<%= color('Invalid Data:', RED, BOLD) %> please recheck source and destination app_id and app_secret.")
99
+ exit
100
+ end
101
+
102
+ dummy = Addressable::URI.new
103
+ dummy.query = response
104
+ access_token = dummy.query_values["access_token"]
105
+
106
+ return access_token
107
+ end
108
+
109
+
110
+ private
111
+
112
+ def fb_graph_call(path, params = {}, options = {})
113
+ clnt = HTTPClient.new
114
+ uri = Addressable::URI.parse(FB_GRAPH_HOST)
115
+ uri.path = path
116
+ query = params
117
+ # default to JSON response
118
+ options = {:format => :json}.merge(options)
119
+ begin
120
+ response = clnt.get(uri, query)
121
+ if response.status == 200
122
+ case options[:format]
123
+ when :text then response.body
124
+ else JSON.parse(response.content)
125
+ end
126
+ end
127
+ rescue StandardError => e
128
+ say("Graph Call error: #{e}")
129
+ say("...exiting Exporter!")
130
+ exit
131
+ # raise e
132
+ end
133
+ end
134
+
135
+ # unused
136
+ def write_csv(translations_arr)
137
+ unless translations_arr.length == 0
138
+ csv_file_name = "appid_#{@translation[:source_application_id]}.csv"
139
+ csv_tmp_file = Tempfile.new(csv_file_name)
140
+ headers_arr = Array.new
141
+ translations_arr[0].each_key {|key| headers_arr.push(key) }
142
+
143
+ CSV.open(csv_tmp_file, "w") do |csv|
144
+ csv << headers_arr
145
+ translations_arr.each do |el|
146
+ csv << el.values
147
+ end
148
+ end
149
+
150
+ send_file csv_tmp_file, :type => 'text/csv', :disposition => 'download', :filename => csv_file_name
151
+ csv_tmp_file.close
152
+ csv_tmp_file.unlink
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+ end
@@ -0,0 +1,33 @@
1
+ require "singleton"
2
+ module OpengraphTransporter
3
+
4
+ class GracefulQuit
5
+ include Singleton
6
+
7
+ attr_accessor :breaker
8
+
9
+ def initialize
10
+ self.breaker = false
11
+ end
12
+
13
+ class << self
14
+
15
+ def enable
16
+ trap('INT') {
17
+ yield if block_given?
18
+ self.instance.breaker = true
19
+ }
20
+ end
21
+
22
+ def check(message = "Quitting Exporter")
23
+ if self.instance.breaker
24
+ yield if block_given?
25
+ puts message
26
+ exit
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end