fanforce-factory 0.4.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,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDAzOWRiYWJjYjJiODE4MDg1OGQyOTBiMTYyOTU5ODA1YmRlYWE4ZQ==
5
+ data.tar.gz: !binary |-
6
+ Mzc5YTBkM2ZiMmIyYjgxZjVmMmUxZDdkOTE1M2Y5Y2E5NDQwODgzYg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZTgyODM0M2ZmZmQ3YjUwNDA4Y2VmZDBlMDdkOTU3MDg0ZjgyNWFkZDFmNTNm
10
+ ZmExODU4NGFmODVmYTM1YWVlNzRiMTZkZDA0ZTg5YzkyYzIyZDUxMzllYzg1
11
+ YWNlOWM4MWViMThiZmRhMjc4NDUzNjIwYmYwOGUwYWVjNjQ4YmQ=
12
+ data.tar.gz: !binary |-
13
+ YTVlMjQxYWY0MTJmNzgzZDQxMzkyMTk0ZjE2ZjE4OTY2YTkzYzY2YWIxYzM4
14
+ YmJhNDY2MjBjNjY3ZmUyMTg0NGVkMWYxNDc1MzIxMDM5NGIzZmI5NDMzYWUz
15
+ ZmRhZGMwOTQ0NzcxNWUzNzkzZjA3NmYxNzE3NmE2OTEyY2EyM2M=
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+ .idea/
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Caleb Clark
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,177 @@
1
+ # fanforce-factory
2
+ The simplest way to create and manage a folder of Fanforce addons (plugins, widgets, and apps). This gem provides
3
+ bulk commands that you can run against your entire list of addons.
4
+
5
+ ## Installation
6
+
7
+ Run this command from your command line:
8
+
9
+ gem install fanforce-factory
10
+
11
+ ## Usage
12
+
13
+ Run this command to view usage instructions
14
+
15
+ $ factory
16
+
17
+
18
+ ## Filtering Commands by Addon Type
19
+ Filtering is supported in version 0.1.0 and higher for all bulk commands. To filter a command, just prefix it with a
20
+ colon (:) and the filter you want to apply (:[FILTER]). Filter can be any addon_type (plugin, widget, or app), singular
21
+ or plural. For example, the following command would only run bundle update on apps:
22
+
23
+ ```
24
+ factory :apps bundle update
25
+ ```
26
+
27
+ If you are using the addon type of "plugins", you can further filter by any of the plugin types (data_connector,
28
+ data_processor, broadcaster, identifier, behavior):
29
+
30
+ ```
31
+ factory :plugins:behaviors bundle update
32
+ ```
33
+
34
+ If you want to get really specific, you can filter on a specific directory:
35
+
36
+ ```
37
+ factory :plugin-watch-youtube bundle update
38
+ ```
39
+
40
+ ## Customizing Your Factory Setup with .factoryconfig
41
+ You can add a .factoryconfig to any factory folder you've setup for your addons. It accepts any valid YAML markup.
42
+ Below is a example file that sets a custom local path for plugin, app, and widget gems, plus a version for app gems.
43
+
44
+ ```yaml
45
+ factory_gems:
46
+
47
+ plugin:
48
+ path: '../../fanforce-plugin-factory'
49
+
50
+ app:
51
+ path: '../../fanforce-app-factory'
52
+ version: '~> 0.1.3'
53
+
54
+ widget:
55
+ path: '../../fanforce-widget-factory'
56
+
57
+ ```
58
+ For more options see BitBucket and Heroku below.
59
+
60
+
61
+ ## Run Factory Inside an Addon
62
+
63
+ Sometimes you'll want to use factory commands inside a specific addon. You can as long as the folder contains a config.ru
64
+ file, and the addon is a subdirectory of a factory folder containing a .factoryconfig file.
65
+
66
+
67
+ ## Supercharge Your Factory
68
+
69
+ Fanforce-factory comes with a special application when you're running commands on a long list of addons and
70
+ each command is taking a long time to run. It forks each command and runs them in parallel. Only a subset of the full
71
+ factory commands are available with factory-supercharge. Run the following from your terminal to see your available
72
+ options:
73
+
74
+ ```
75
+ factory-supercharge
76
+ ```
77
+
78
+ ## BitBucket: Creating Repositories and Pushing
79
+ A BitBucket repository will be automatically setup for each new addon if a bitbucket object is defined in your .factoryconfig file:
80
+
81
+ ```yaml
82
+ bitbucket:
83
+ user: fanforce-factory
84
+ password: zE27fbitbucket
85
+ ```
86
+
87
+ ## Heroku Apps: Creating and Updating
88
+ A heroku app will be automatically setup for each new addon if a heroku object is defined in your .factoryconfig file. You'll need
89
+ one for each environment (qa|production) you want apps setup for.
90
+
91
+ ```yaml
92
+ heroku:
93
+ production:
94
+ user: technical@fanforce.com
95
+ git_ssh_domain: heroku_fanforce_factory
96
+ password: zE27fheroku
97
+ app_domain: ffapp.io
98
+ plugin_domain: ffplugin.io
99
+ widget_domain: ffwidget.io
100
+ smarturl_domain: fanforce.io
101
+ ```
102
+ The git_ssh_domain variable listed above is needed if you're pushing to multiple Heroku accounts as you'll need to specify
103
+ the SSH key that must be used (each Heroku account requires a unique ssh key). For example, the above git_ssh_domain variable
104
+ references the following lines in ~/.ssh/config:
105
+
106
+ ```
107
+ Host heroku_fanforce_factory
108
+ HostName heroku.com
109
+ User technical@fanforce.com
110
+ IdentityFile ~/.ssh/heroku_fanforce_factory
111
+ ```
112
+
113
+ See http://www.springloops.com/blog/git-config-for-mutiply-ssh-keys/ for more info.
114
+
115
+ ## Environment Variables
116
+ Fanforce-factory allows you to setup a list of env variables that can be loaded by your app it's running in your local
117
+ development environment, pushed to Heroku, or tasks setup on IronWorker.
118
+
119
+ First, you need to setup a folder called ".env" in your factory folder with a series of YAML files inside. The only required
120
+ file is _bind.yaml:
121
+
122
+ ```yaml
123
+ constantcontact:
124
+ - app-constantcontact
125
+ - plugin-constantcontact-subscribers
126
+
127
+ facebook:
128
+ - app-facebook
129
+ - plugin-facebook-friends
130
+ - plugin-facebook-posts
131
+
132
+ iron: ALL
133
+ ```
134
+
135
+ Each key is the filename (minus .yaml) of the YAML file holding a list of ENV variables. The array underneath is the folder names of addons
136
+ that these ENV variables will be loaded into. Alternatively, you can specify the keyword ALL if those ENV variables should
137
+ be loaded into all addons.
138
+
139
+ Second, you'll need to setup the filenames references in _bind.yaml. Each file contains a hash of ENV key/values organized
140
+ by RACK_ENV. Here's is an example of facebook.yaml (of course, you'll need to provide legit facebook api_keys and api_secrets):
141
+
142
+ ```yaml
143
+ development:
144
+ api_key: 175620799120142
145
+ api_secret: b4e86cbf9db2ch5777609396b1d0a52f
146
+
147
+ qa:
148
+ api_key: 362017347478160
149
+ api_secret: cf3605c0cnda02c95ddd1bb983a59e7a
150
+ ```
151
+
152
+ ## Using IronWorker
153
+
154
+ Fanforce-factory comes with built in support for using IronWorker in your addons:
155
+
156
+ ```
157
+ factory iron_workers upload:development
158
+ ```
159
+
160
+ There are a couple things you'll want to setup first:
161
+
162
+ #### 1. Env Variables
163
+
164
+ - IRON_TOKEN
165
+ - IRON_PROJECT_ID
166
+
167
+ #### 2.
168
+
169
+
170
+
171
+ ## Contributing
172
+
173
+ 1. Fork it
174
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
175
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
176
+ 4. Push to the branch (`git push origin my-new-feature`)
177
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/factory ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fanforce/factory'
3
+
4
+ factory = Fanforce::Factory.new('factory')
5
+
6
+ if File.exists?("#{$HomeDir}/config.ru") and File.exists?("#{$HomeDir}/../.factoryconfig")
7
+ ARGV.unshift(":#{File.basename($HomeDir)}")
8
+ $HomeDir = File.expand_path('..', $HomeDir)
9
+ factory.start(
10
+ :runtype => :single,
11
+ :allowed => [:update, :restart, :push, :count, :bundle, :git, :iron, :version, :config],
12
+ )
13
+ else
14
+ factory.start(
15
+ :runtype => :realtime,
16
+ :allowed => [:create, :delete, :update, :restart, :push, :count, :bundle, :git, :iron, :version, :config],
17
+ )
18
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fanforce/factory'
3
+
4
+ Fanforce::Factory.new('factory-supercharge').start(
5
+ :runtype => :forked,
6
+ :allowed => [:update, :restart, :push, :bundle, :git, :iron, :version, :config],
7
+ )
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fanforce/factory/version'
5
+ require 'rubygems'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = 'fanforce-factory'
9
+ gem.version = Fanforce::Factory::VERSION
10
+ gem.authors = ['Caleb Clark']
11
+ gem.email = ['cclark@mobilizationlabs.com']
12
+ gem.description = %q{CLI for managing a folder of Fanforce addons}
13
+ gem.summary = %q{Manage a folder of Fanforce addons}
14
+ gem.homepage = ''
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = ['factory','factory-supercharge']
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ['lib']
20
+
21
+ gem.add_dependency 'rest-client'
22
+ gem.add_dependency 'iron_worker_ng'
23
+ gem.add_dependency 'activesupport'
24
+ gem.add_dependency 'heroku-api', '0.3.8'
25
+ gem.add_dependency 'multi_json'
26
+ gem.add_dependency 'bitbucket_rest_api'
27
+
28
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_support/all'
2
+ require 'optparse'
3
+ require 'shell'
4
+ require 'fanforce/factory/initializers/ff_globals'
5
+ require 'rest-client'
6
+ require 'multi_json'
7
+ require 'bitbucket_rest_api'
8
+ require 'heroku-api'
9
+ require 'yaml'
10
+
11
+ require_relative 'factory/version'
12
+ require_relative 'factory/_base'
@@ -0,0 +1,290 @@
1
+ require 'fanforce/factory'
2
+
3
+ class Fanforce::Factory
4
+ require 'fanforce/factory/utils'
5
+ require 'fanforce/factory/files'
6
+ require 'fanforce/factory/addons'
7
+ require 'fanforce/factory/run'
8
+ require 'fanforce/factory/developer_config'
9
+ require 'fanforce/factory/env'
10
+ require 'fanforce/factory/help'
11
+ require 'fanforce/factory/commands'
12
+ include Fanforce::Factory::Utils
13
+
14
+ def initialize(executable)
15
+ @executable = executable
16
+ $HomeDir = Shell.new.pwd
17
+ end
18
+
19
+ def start(options)
20
+ @allowed_commands = options[:allowed]
21
+ @runtype = options[:runtype]
22
+ setup_config
23
+ init_counter if @runtype == :forked
24
+ parse_addons_filter
25
+ parse_command
26
+ destroy_counter if @runtype == :forked
27
+ end
28
+
29
+ def setup_config
30
+ puts 'ERROR: Fanforce Factory could not find the required config file.'.format(:red) if !File.exists?("#{$HomeDir}/.factoryconfig")
31
+ $Config = format_config(YAML.load_file("#{$HomeDir}/.factoryconfig"))
32
+ end
33
+
34
+ def parse_addons_filter
35
+ if ARGV[0] =~ /^:(apps?|widgets?|plugins?)(:(data_connectors?|data_processors?|broadcasters?|identifiers?|behaviors?))?$/
36
+ $Filter = {type: $1.singularize.to_sym}
37
+ $Filter[:plugin_type] = $3.singularize.to_sym if $3.present? and $Filter[:type] == :plugin
38
+ ARGV.shift
39
+ elsif ARGV[0] =~ /^:((app|widget|plugin)-(.+))$/
40
+ $Filter = {dir_name: $1}
41
+ ARGV.shift
42
+ end
43
+ end
44
+
45
+ #################################################################################
46
+
47
+ def parse_command
48
+ if ARGV.length == 0 or !@allowed_commands.include?(ARGV[0].to_sym)
49
+ puts Fanforce::Factory::Help.intro(@executable, @runtype)
50
+ puts Fanforce::Factory::Help.commands(@allowed_commands)
51
+
52
+ elsif ARGV[0] == 'create'
53
+ ARGV[1] =~ /^(plugin|widget|app)-(.+)$/ || error('You supplied an invalid create command.', :create)
54
+
55
+ addon_type = $1
56
+ addon_id = $2
57
+
58
+ if addon_type =~ /app|widget/
59
+ create_addon(addon_type, addon_id)
60
+ else
61
+ if ARGV[2].nil?
62
+ error('You failed to supply a valid plugin type for create.', :create)
63
+ elsif ![:data_connector, :data_processor, :broadcaster, :identifier, :behavior].include?(ARGV[2].to_sym)
64
+ error('You supplied an invalid plugin type.', :create)
65
+ else
66
+ create_addon(addon_type, addon_id, ARGV[2])
67
+ end
68
+ end
69
+
70
+ #################################################################
71
+
72
+ elsif ARGV[0] == 'delete'
73
+ ARGV[1] =~ /^(plugin|widget|app)-(.+)$/ || error('You supplied an invalid delete command.', :delete)
74
+
75
+ confirm("Are you sure you want to delete all files, repositories, and other items for #{ARGV[1]}?")
76
+ delete_addon($1, $2)
77
+
78
+ #################################################################
79
+
80
+ elsif ARGV[0] == 'setup'
81
+ ARGV[1] =~ /^(all|files?|envs?(:all|:development|:qa|:production)?|pows?|bitbuckets?|herokus?(:all|:qa|:production)?)$/ || error('You supplied an invalid setup command.', :setup)
82
+
83
+ tmp_str = $1
84
+ if tmp_str =~ /^envs?|herokus?/
85
+ command, environment = tmp_str.split(':').map {|d| d.singularize.to_sym }
86
+ environment = :all if !environment
87
+ confirm('Are you wanting to setup all environments?') if environment == :all
88
+ else
89
+ command = tmp_str.singularize.to_sym
90
+ environment = command == :all ? :all : nil
91
+ end
92
+ confirm('Are you wanting to setup everything in all environments?') if command == :all
93
+ run(:setup, command, environment)
94
+
95
+ #################################################################
96
+
97
+ elsif ARGV[0] == 'update'
98
+ ARGV[1] =~ /^(all|files?|envs?(:all|:development|:qa|:production)?|pows?|bitbuckets?|herokus?(:all|:qa|:production)?)$/ || error('You supplied an invalid update command.', :update)
99
+
100
+ tmp_str = $1
101
+ if tmp_str =~ /^envs?|herokus?/
102
+ command, environment = tmp_str.split(':').map {|d| d.singularize.to_sym }
103
+ environment = :all if !environment
104
+ confirm('Are you sure you want to setup all environments?') if environment == :all
105
+ else
106
+ command = tmp_str.singularize.to_sym
107
+ environment = command == :all ? :all : nil
108
+ end
109
+
110
+ confirm('Are you sure you want to setup everything in all environments?') if command == :all
111
+ run(:update, command, environment)
112
+
113
+ #################################################################
114
+
115
+ elsif ARGV[0] == 'restart'
116
+ if ARGV[1].present?
117
+ ARGV[1] =~ /^(development|qa|production|all)?$/ || error('You supplied an invalid restart command.', :restart)
118
+ environment = $1.to_sym
119
+ else
120
+ environment = :development
121
+ end
122
+
123
+ confirm('Are you sure you want to restart all environments?') if environment == :all
124
+ run(:restart, environment)
125
+
126
+ #################################################################
127
+
128
+ elsif ARGV[0] == 'push'
129
+ ARGV[1] =~ /^(development|qa|production)(:all|:heroku|:irons?(:upload|:nuclear)?|bitbuckets?)?$/ || error('You supplied an invalid push command.', :push)
130
+
131
+ environment = $1.to_sym
132
+ tmp_str = $2 || 'all'
133
+
134
+ if tmp_str =~ /^irons?/
135
+ command, subcommand = tmp_str.split(':').map {|d| d.singularize.to_sym }
136
+ subcommand = :upload if !subcommand
137
+ else
138
+ command = tmp_str.gsub(':','').singularize.to_sym
139
+ subcommand = nil
140
+ end
141
+
142
+ confirm("Are you sure you want to push to all services on #{environment}") if command == :all
143
+ run(:push, environment, command, subcommand)
144
+
145
+ #################################################################
146
+
147
+ elsif ARGV[0] == 'count'
148
+ count
149
+
150
+ #################################################################
151
+
152
+ elsif ARGV[0] == 'bundle'
153
+ ARGV[1] =~ /^(install|update)$/ || error('You supplied an invalid bundle command.', :bundle)
154
+
155
+ run(:bundle, $1.to_sym, ARGV[3..-1] || [])
156
+
157
+ #################################################################
158
+
159
+ elsif ARGV[0] == 'git' and ARGV[1] == 'status:overview'
160
+ run(:git_overview)
161
+
162
+ elsif ARGV[0] == 'git'
163
+ run(:git, ARGV[1..-1] || [])
164
+
165
+ #################################################################
166
+
167
+ elsif ARGV[0] == 'iron'
168
+ ARGV[1] =~ /^(upload|reset|delete)(:all|:development|:qa|:production)?$/ || error('You supplied an invalid iron command.', :iron)
169
+
170
+ command = $1.to_sym
171
+ environment = ($2.present?) ? $2.gsub(':','').to_sym : :all
172
+
173
+ confirm("Are you sure you want to #{command} workers in all environments?") if environment == :all
174
+ if command == :delete
175
+ delete_all_iron_workers(environment)
176
+ else
177
+ run(:iron, command, environment)
178
+ end
179
+
180
+ #################################################################
181
+
182
+ elsif ARGV[0] == 'version'
183
+ puts '---------------------------------------------------------------------------------------------------------------'
184
+ puts "You are using version #{Fanforce::Factory::VERSION} of Fanforce Factory "
185
+ puts '---------------------------------------------------------------------------------------------------------------'
186
+
187
+ elsif ARGV[0] == 'config'
188
+ puts '---------------------------------------------------------------------------------------------------------------'
189
+ if !File.exists?("#{$HomeDir}/.factoryconfig")
190
+ puts 'Oops'.format(:red,:bold) + '... no ".factoryconfig" file was found in this directory.'.format(:red)
191
+ else
192
+ puts $Config
193
+ end
194
+ puts '---------------------------------------------------------------------------------------------------------------'
195
+
196
+ #elsif ARGV[0] == 'upgrade'
197
+ # factory.upgrade
198
+
199
+ end
200
+
201
+ end
202
+
203
+ #################################################################################
204
+
205
+ def run(method, *args)
206
+ (@runtype == :forked) ? run_forked(method, *args) : run_realtime(method, *args)
207
+ end
208
+
209
+ def run_realtime(method, *args)
210
+ if (dirs = Addons.dirs).size == 0
211
+ puts "\n---------------------------------------------------------------------------------------------------------------"
212
+ puts "#{'Oops'.format(:bold)}... no factory addons #{$Filter.length > 0 ? 'matching your filter was' : 'were'} found in this directory."
213
+ puts "---------------------------------------------------------------------------------------------------------------\n"
214
+ return
215
+ end
216
+
217
+ if self.respond_to?(:"preprocess_#{method}")
218
+ args << self.method(:"preprocess_#{method}").call
219
+ end
220
+ Addons.each do |addon, processed_count, total_count|
221
+ self.method(:"run_#{method}").call(addon.dir, processed_count, total_count, *args)
222
+ end
223
+ if self.respond_to?(:"postprocess_#{method}")
224
+ self.method(:"postprocess_#{method}").call
225
+ else
226
+ puts "\n---------------------------------------------------------------------------------------------------------------"
227
+ puts 'DONE!'
228
+ puts '---------------------------------------------------------------------------------------------------------------'
229
+ end
230
+ end
231
+
232
+ def run_forked(method, *args)
233
+ processes = []
234
+ dirs = Addons.dirs
235
+ puts "\n---------------------------------------------------------------------------------------------------------------"
236
+ dirs.each_with_index do |addon_dir, i|
237
+ puts "#{'Forking'.format(:white,:bold)} #{addon_dir}"
238
+ end
239
+ dirs.each_with_index do |addon_dir, i|
240
+ processes << fork do
241
+ response = capture_stdout do
242
+ self.method(:"run_#{method}").call(addon_dir, 'PROCESSED_ADDONS_COUNT', dirs.size, *args)
243
+ end
244
+ puts response.gsub('PROCESSED_ADDONS_COUNT', incr_counter.to_s)
245
+ end
246
+ sleep(0.25)
247
+ end
248
+
249
+ processes.each { |pid| Process.waitpid(pid) }
250
+ puts "\n---------------------------------------------------------------------------------------------------------------"
251
+ puts 'DONE!'
252
+ puts '---------------------------------------------------------------------------------------------------------------'
253
+ end
254
+
255
+ require 'stringio'
256
+ def capture_stdout
257
+ previous_stdout, previous_stderr = $stdout, $stderr
258
+ io = StringIO.new
259
+ $stdout = io
260
+ $stderr = io
261
+ yield
262
+ io.string
263
+ ensure
264
+ $stdout = previous_stdout
265
+ $stderr = previous_stderr
266
+ end
267
+
268
+ def init_counter
269
+ File.open("#{$HomeDir}/.forked-counter", 'w') {|f| f.write('0') }
270
+ end
271
+
272
+ def incr_counter
273
+ new_count = nil
274
+ File.open("#{$HomeDir}/.forked-counter", File::RDWR|File::CREAT, 0644) do |f|
275
+ f.flock(File::LOCK_EX)
276
+ new_count = f.read.to_i + 1
277
+ f.rewind
278
+ f.write("#{new_count}\n")
279
+ f.flush
280
+ f.truncate(f.pos)
281
+ end
282
+ new_count
283
+ end
284
+
285
+ def destroy_counter
286
+ File.delete("#{$HomeDir}/.forked-counter")
287
+ end
288
+
289
+ end
290
+