crazy_ivan 1.2.3 → 1.2.4

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.
@@ -3,11 +3,8 @@
3
3
  begin
4
4
  require 'crazy_ivan'
5
5
  rescue LoadError
6
- # If people are not using gems, the load path must still
7
- # be correct.
8
- # TODO: Remove the begin / rescue block somehow
9
6
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
10
- retry
7
+ require 'crazy_ivan'
11
8
  end
12
9
 
13
10
  require "optparse"
@@ -20,7 +17,7 @@ Signal.trap("INT") do
20
17
  CrazyIvan.interrupt_test
21
18
  ProcessManager.unlock
22
19
  puts
23
- exit
20
+ exit(1)
24
21
  end
25
22
 
26
23
  def show_howto
@@ -68,19 +65,19 @@ ARGV.options do |opts|
68
65
 
69
66
  begin
70
67
  opts.parse!
71
-
68
+
72
69
  case ARGV[0]
73
70
  when /setup/
74
71
  CrazyIvan::setup
75
72
  when /\w+/ # a directory for test results
76
- ProcessManager.acquire_lock do
77
- Syslog.debug "Generating reports in #{ARGV[0]}"
73
+ begin
78
74
  CrazyIvan::generate_test_reports_in(ARGV[0])
75
+ rescue AlreadyRunningError
76
+ Process.kill('INT', 0)
79
77
  end
80
78
  else
81
79
  show_howto
82
80
  exit
83
81
  end
84
82
  end
85
- end
86
-
83
+ end
@@ -1,13 +1,14 @@
1
1
  require 'syslog'
2
2
  require 'fileutils'
3
3
  require 'yaml'
4
+ require 'crazy_ivan/vendor/json'
5
+ require 'crazy_ivan/vendor/open4'
6
+ require 'crazy_ivan/vendor/tmpdir'
7
+ require 'crazy_ivan/vendor/lockfile'
4
8
  require 'crazy_ivan/process_manager'
5
9
  require 'crazy_ivan/report_assembler'
6
10
  require 'crazy_ivan/test_runner'
7
11
  require 'crazy_ivan/version'
8
- require 'crazy_ivan/vendor/json'
9
- require 'crazy_ivan/vendor/open4'
10
- require 'crazy_ivan/vendor/tmpdir'
11
12
 
12
13
  module CrazyIvan
13
14
  def self.setup
@@ -114,20 +115,28 @@ module CrazyIvan
114
115
  puts
115
116
  end
116
117
 
117
- def self.generate_test_reports_in(output_directory)
118
+ def self.generate_test_reports_in(path)
118
119
  Syslog.open('crazy_ivan', Syslog::LOG_PID | Syslog::LOG_CONS) unless Syslog.opened?
119
- FileUtils.mkdir_p(output_directory)
120
120
 
121
- report = ReportAssembler.new(Dir.pwd, output_directory)
122
- report.generate
121
+ output_path = File.expand_path(path)
122
+
123
+ ProcessManager.acquire_lock! do
124
+ Syslog.debug "Generating reports in #{output_path}"
125
+
126
+ FileUtils.mkdir_p(output_path)
127
+
128
+ report = ReportAssembler.new(Dir.pwd, output_path)
129
+ report.generate
130
+
131
+ # TODO indicate how many projects were tested
132
+ msg = "Ran CI on #{report.runners.size} projects"
133
+
134
+ # REFACTOR to use a logger that spits out to both STDOUT and Syslog
135
+ Syslog.info(msg)
136
+ puts msg
137
+ end
123
138
 
124
- # TODO this should really indicate how many projects were tested
125
- msg = "Generated test reports for #{report.runners.size} projects"
126
- Syslog.info(msg)
127
- puts msg
128
- # REFACTOR to use a logger that spits out to both STDOUT and Syslog
129
- ensure
130
- Syslog.close
139
+ Syslog.close if Syslog.opened?
131
140
  end
132
141
 
133
142
  def self.interrupt_test
@@ -1,48 +1,34 @@
1
+ require 'lockfile'
2
+
3
+ class AlreadyRunningError < StandardError; end
4
+
1
5
  class ProcessManager
2
- @@pidfile = '/tmp/crazy_ivan.pid'
6
+ @@lockfile = Lockfile.new('/tmp/crazy_ivan.lock', :retries => 1)
3
7
 
4
- def self.pidfile=(file)
5
- @@pidfile = file
8
+ def self.lockfilepath=(filepath)
9
+ @@lockfile = Lockfile.new(filepath, :retries => 1)
6
10
  end
7
11
 
8
- def self.acquire_lock
9
- lock_exclusively!
12
+ def self.acquire_lock!
13
+ lock
10
14
  yield
15
+ ensure
11
16
  unlock
12
17
  end
13
18
 
14
19
  def self.unlock
15
- File.new(@@pidfile).flock(File::LOCK_UN)
16
- end
17
-
18
- def self.ci_already_running?
19
- File.exists?(@@pidfile) && !File.new(@@pidfile).flock(File::LOCK_EX | File::LOCK_NB)
20
+ @@lockfile.unlock
21
+ rescue Lockfile::UnLockError
20
22
  end
21
23
 
22
- def self.lock_exclusively!(options = {})
23
- pid = Integer(File.read(@@pidfile)) if File.exists?(@@pidfile)
24
-
24
+ def self.lock
25
25
  Syslog.debug "Acquiring lock"
26
-
27
- if options[:interrupt_existing_process]
28
- File.open(@@pidfile, "w+") { |fp| fp << Process.pid }
29
-
30
- if ci_already_running?
31
- Process.kill("INT", pid)
32
- Syslog.debug("Detected another running CI process #{pid}; interrupting it and starting myself")
33
- File.new(@@pidfile).flock(File::LOCK_EX)
34
- end
35
- else
36
- if ci_already_running?
37
- msg = "Detected another running CI process #{pid} - interrupting and terminating myself"
38
- Syslog.warning msg
39
- puts msg
40
- Process.kill("INT", 0)
41
- else
42
- Syslog.debug("Locked CI process pid file")
43
- Syslog.debug("Writing to pid file with #{Process.pid}")
44
- File.open(@@pidfile, "w+") { |fp| fp << Process.pid }
45
- end
46
- end
26
+ @@lockfile.lock
27
+ Syslog.debug("Locked CI process")
28
+ rescue Lockfile::LockError
29
+ msg = "Detected another running CI process - cannot start"
30
+ Syslog.warning msg
31
+ puts msg
32
+ raise AlreadyRunningError, msg
47
33
  end
48
34
  end
@@ -99,7 +99,6 @@ class ReportAssembler
99
99
 
100
100
  def update_currently_building(runner)
101
101
  project_path = File.expand_path(runner.project_name, @output_directory)
102
-
103
102
  Dir.chdir(project_path) do
104
103
  File.open('currently_building.json', 'w+') do |f|
105
104
  f.puts runner.results.to_json
@@ -223,6 +223,15 @@
223
223
  }
224
224
  });
225
225
  });
226
+
227
+ // Working here:
228
+
229
+ // location.href will look like this
230
+ // http://ci.shopify.com/#shopify-ree-187-802d405d56410b3159dac66644846468d2d83ef8
231
+
232
+ // Check it for which domId to make active and show, but only do this on the first full render
233
+
234
+ // Also: add a loading screen
226
235
  }
227
236
 
228
237
  // listen to clicking of test result links
@@ -0,0 +1,227 @@
1
+ URLS
2
+
3
+ http://rubyforge.org/projects/codeforpeople/
4
+ http://codeforpeople.com/lib/ruby/
5
+
6
+ SYNOPSIS
7
+
8
+ lib/lockfile.rb : a ruby library for creating NFS safe lockfiles
9
+
10
+ bin/rlock : ruby command line tool which uses this library to create lockfiles
11
+ and to run arbitrary commands while holding them.
12
+
13
+ for example
14
+
15
+ rlock lockfile -- cp -r huge/ huge.bak/
16
+
17
+ run 'rlock -h' for more info
18
+
19
+ INSTALL
20
+
21
+ sudo ruby install.rb
22
+
23
+
24
+ BASIC ALGORITHIM
25
+
26
+ * create a globally uniq filename in the same filesystem as the desired
27
+ lockfile - this can be nfs mounted
28
+
29
+ * link(2) this file to the desired lockfile, ignore all errors
30
+
31
+ * stat the uniq filename and desired lockfile to determine is they are the
32
+ same, use only stat.rdev and stat.ino - ignore stat.nlink as NFS can cause
33
+ this report incorrect values
34
+
35
+ * iff same, you have lock. either return or run optional code block with
36
+ optional refresher thread keeping lockfile fresh during execution of code
37
+ block, ensuring that the lockfile is removed..
38
+
39
+ * iff not same try again a few times in rapid succession (poll), then, iff
40
+ this fails, sleep using incremental backoff time. optionally remove
41
+ lockfile if it is older than a certain time, timeout if more than a certain
42
+ amount of time has passed attempting to lock file.
43
+
44
+
45
+ BASIC USAGE
46
+
47
+ 1)
48
+ lockfile = Lockfile.new 'file.lock'
49
+ begin
50
+ lockfile.lock
51
+ p 42
52
+ ensure
53
+ lockfile.unlock
54
+ end
55
+
56
+
57
+ 2)
58
+ require 'pstore' # which is NOT nfs safe on it's own
59
+
60
+ opts = { # the keys can be symbols or strings
61
+
62
+ :retries => nil, # we will try forever to aquire the lock
63
+
64
+ :sleep_inc => 2, # we will sleep 2 seconds longer than the
65
+ # previous sleep after each retry, cycling from
66
+ # min_sleep upto max_sleep downto min_sleep upto
67
+ # max_sleep, etc., etc.
68
+
69
+ :min_sleep => 2, # we will never sleep less than 2 seconds
70
+
71
+ :max_sleep => 32, # we will never sleep longer than 32 seconds
72
+
73
+ :max_age => 3600, # we will blow away any files found to be older
74
+ # than this (lockfile.thief? #=> true)
75
+
76
+ :suspend => 1800, # iff we steal the lock from someone else - wait
77
+ # this long to give them a chance to realize it
78
+
79
+ :refresh => 8, # we will spawn a bg thread that touches file
80
+ # every 8 sec. this thread also causes a
81
+ # StolenLockError to be thrown if the lock
82
+ # disappears from under us - note that the
83
+ # 'detection' rate is limited to the refresh
84
+ # interval - this is a race condition
85
+
86
+ :timeout => nil, # we will wait forever
87
+
88
+ :poll_retries => 16, # the initial attempt to grab a lock is done in a
89
+ # polling fashion, this number controls how many
90
+ # times this is done - the total polling attempts
91
+ # are considered ONE actual attempt (see retries
92
+ # above)
93
+
94
+ :poll_max_sleep => 0.08, # when polling a very brief sleep is issued
95
+ # between attempts, this is the upper limit of
96
+ # that sleep timeout
97
+
98
+ :dont_clean => false, # normally a finalizer is defined to clean up
99
+ # after lockfiles, settin this to true prevents this
100
+
101
+ :dont_sweep => false, # normally locking causes a sweep to be made. a
102
+ # sweep removes any old tmp files created by
103
+ # processes of this host only which are no
104
+ # longer alive
105
+
106
+ :debug => true, # trace execution step on stdout
107
+ }
108
+
109
+ pstore = PStore.new 'file.db'
110
+ lockfile = Lockfile.new 'file.db.lock', opts
111
+ lockfile.lock do
112
+ pstore.transaction do
113
+ pstore[:last_update_time] = Time.now
114
+ end
115
+ end
116
+
117
+ 3) same as 1 above - Lockfile.new takes a block and ensures unlock is called
118
+
119
+ Lockfile.new('file.lock') do
120
+ p 42
121
+ end
122
+
123
+ 4) watch locking algorithim in action (debugging only)
124
+
125
+ Lockfile.debug = true
126
+ Lockfile.new('file.lock') do
127
+ p 42
128
+ end
129
+
130
+ you can also set debugging via the ENV var LOCKFILE_DEBUG, eg.
131
+
132
+ ~ > LOCKFILE_DEBUG=true rlock lockfile
133
+
134
+ 5) simplified interface : no lockfile object required
135
+
136
+ Lockfile('lock', :retries => 0) do
137
+ puts 'only one instance running!'
138
+ end
139
+
140
+
141
+ SAMPLES
142
+
143
+ * see samples/a.rb
144
+ * see samples/nfsstore.rb
145
+ * see samples/lock.sh
146
+ * see bin/rlock
147
+
148
+ AUTHOR
149
+
150
+ Ara T. Howard
151
+
152
+ EMAIL
153
+
154
+ Ara.T.Howard@noaa.gov
155
+
156
+ BUGS
157
+
158
+ bugno > 1 && bugno < 42
159
+
160
+ HISTORY
161
+
162
+ 1.4.3:
163
+ - fixed a small non-critical bug in the require gaurd
164
+
165
+ 1.4.2:
166
+ - upped defaults for max_age to 3600 and suspend to 1800.
167
+ - tweaked a few things to play nice with rubygems
168
+
169
+ 1.4.1:
170
+ - Mike Kasick <mkasick@club.cc.cmu.edu> reported a bug whereby false/nil
171
+ values for options were ignored. patched to address this bug
172
+ - added Lockfile method for high level interface sans lockfile object
173
+ - updated rlock program to allow nil/true/false values passed on command
174
+ line. eg
175
+
176
+ rlock --max_age=nil lockfile -- date --iso-8601=seconds
177
+
178
+ 1.4.0:
179
+ - gem'd up
180
+ - added Lockfile::create method which atomically creates a file and opens
181
+ it:
182
+
183
+ Lockfile::create("atomic_even_on_nfs", "r+") do |f|
184
+ f.puts 42
185
+ end
186
+
187
+ arguments are the same as those for File::open. note that there is no way
188
+ to accomplish this otherwise since File::O_EXCL fails silently on nfs,
189
+ flock does not work on nfs, and posixlock (see raa) can only lock a file
190
+ after it is open so the open itself is still a race.
191
+
192
+ 1.3.0:
193
+ - added sweep functionality. hosts can clean up any tmp files left around
194
+ by other processes that may have been killed using -9 to prevent proper
195
+ clean up. it is only possible to clean up after processes on the same
196
+ host as there is no other way to determine if the process that created the
197
+ file is alive. in summary - hosts clean up after other processes on that
198
+ same host if needed.
199
+ - added attempt/try_again methods
200
+ 1.2.0:
201
+ - fixed bug where stale locks not removed when retries == 0
202
+ 1.1.0
203
+ - unfortunately i've forgotten
204
+ 1.0.1:
205
+ - fixed bugette in sleep cycle where repeated locks with same lockfile would
206
+ not reset the cycle at the start of each lock
207
+ 1.0.0:
208
+ - allow rertries to be nil, meaning to try forever
209
+ - default timeout is now nil - never timeout
210
+ - default refresh is now 8
211
+ - fixed bug where refresher thread was not actually touching lockfile! (ouch)
212
+ - added cycle method to timeouts
213
+ 1-2-3-2-1-2-3-1...
214
+ pattern is constructed using min_sleep, sleep_inc, max_sleep
215
+ 0.3.0:
216
+ - removed use of yaml in favour of hand parsing the lockfile contents, the
217
+ file as so small it just wasn't worth and i have had one core dump when yaml
218
+ failed to parse a (corrupt) file
219
+ 0.2.0:
220
+ - added an initial polling style attempt to grab lock before entering normal
221
+ sleep/retry loop. this has really helped performance when lock is under
222
+ heavy contention: i see almost no sleeping done by any of in the interested
223
+ processes
224
+ 0.1.0:
225
+ - added ability of Lockfile.new to accept a block
226
+ 0.0.0:
227
+ - initial version
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # built-in
4
+ #
5
+ require 'optparse'
6
+ require 'logger'
7
+ #
8
+ # http://raa.ruby-lang.org/project/lockfile/
9
+ #
10
+ require 'lockfile-1.4.3'
11
+
12
+ class Main
13
+ #--{{{
14
+ VERSION = Lockfile::VERSION
15
+
16
+ USAGE =
17
+ #--{{{
18
+ <<-usage
19
+ NAME
20
+ rlock v#{ VERSION }
21
+
22
+ SYNOPSIS
23
+ rlock [options]+ lockfile [program [args]+ | -- program options+ [args]+]
24
+
25
+ DESCRIPTTION
26
+ rlock creates NFS safe lockfiles. it can optionally run a program while
27
+ holding the lock, ensuring lockfile removal on program exit. if a program
28
+ is specified to be run rlock will spawn a background thread to kept the
29
+ lockfile 'fresh' by touching it at a regular interval. in this way a lease
30
+ is maintained on the lockfile and other processes attempting to obtain the
31
+ lock can determine that it is in use. see the '--refresh' option for how to
32
+ control the touch interval. any other process trying to obtain a lock will
33
+ automatically remove a stale lockfile; a stale lockfile is one that is older
34
+ than a certain age. this age be controled via the '--max_age' option.
35
+
36
+ ENVIRONMENT
37
+ LOCKFILE_DEBUG=1
38
+ causes internal actions of the library to be shown on STDERR
39
+
40
+ DIAGNOSTICS
41
+ rlock attempts to exit with the status of 'program' except where it
42
+ cannot due to exceptional conditions. in addition the message
43
+
44
+ 'RLOCK SUBCOMMAND FAILURE'
45
+
46
+ will be printed on STDERR if 'program' exits with non-zero status.
47
+
48
+ success => $? == 0
49
+ failure => $? != 0
50
+
51
+ AUTHOR
52
+ ara.t.howard@noaa.gov
53
+
54
+ BUGS
55
+ 1 < bugno && bugno < 42
56
+
57
+ OPTIONS
58
+ usage
59
+ #--}}}
60
+
61
+ EXAMPLES =
62
+ #--{{{
63
+ <<-examples
64
+ EXAMPLES
65
+
66
+ 0) simple usage - create lockfile in an atomic fashion (obtain a lock)
67
+
68
+ ~ > rlock lockfile
69
+
70
+ 1) safe usage - create a lockfile, execute a command, and remove lockfile
71
+
72
+ ~ > rlock lockfile ls lockfile
73
+
74
+ 2) same as above, but logging verbose messages
75
+
76
+ ~ > rlock -v4 lockfile ls lockfile
77
+
78
+ 3) same as above, but logging verbose messages and showing actions internal
79
+ to lockfile library
80
+
81
+ ~ > rlock -v4 -d lockfile ls lockfile
82
+
83
+ 4) same as above
84
+
85
+ ~ > LOCKFILE_DEBUG=1 rlock -v4 lockfile ls lockfile
86
+
87
+ 5) same as above
88
+
89
+ ~ > export LOCKFILE_DEBUG=1
90
+ ~ > rlock -v4 lockfile ls lockfile
91
+
92
+ 6) you need to tell the option parser to stop parsing rlock options if you
93
+ intend to pass options to 'program'
94
+
95
+ ~ > rlock -v4 -d lockfile -- ls -ltar lockfile
96
+
97
+ without the '--' rlock would consume the '-ltar' option as one of
98
+ it's own.
99
+
100
+ 7) lock lockfile and exec 'program' - remove the lockfile if it is older
101
+ than 4242 seconds
102
+
103
+ ~ > rlock --max_age=4242 lockfile program
104
+
105
+ 8) lock lockfile and exec 'program' - remove the lockfile if it is older
106
+ than 4242 seconds, set the refresh rate to be 8 seconds.
107
+
108
+ ~ > rlock --max_age=4242 --refresh=8 lockfile program
109
+
110
+ 9) same as above, but fail if lockfile cannot be obtained within 1 minute
111
+
112
+ ~ > rlock --max_age=4242 --refresh=8 --timeout=60 lockfile program
113
+
114
+ 10) lockfile creation involves making some temporary files. normally these
115
+ are cleaned up unless rlock is killed with 'kill -9'. these temp files are
116
+ normally 'sweeped' - searched for and removed - unless the '--dont_sweep'
117
+ option is given. note that sweeping can remove ONLY old temp files created
118
+ by the same host since there is otherwise no way to tell if the offending
119
+ process is still running.
120
+
121
+ lock lockfile and run program - do not do any sweeping
122
+
123
+ ~ > rlock --dont_sweep lockfile program
124
+
125
+ examples
126
+ #--}}}
127
+
128
+ EXIT_SUCCESS = 0
129
+ EXIT_FAILURE = 1
130
+
131
+ attr :argv
132
+ attr :op
133
+ attr :logger
134
+ attr :config
135
+
136
+ def initialize argv = ARGV
137
+ #--{{{
138
+ @argv = mcp argv
139
+ parse_opts
140
+ if @opt_version
141
+ puts Main::VERSION
142
+ exit EXIT_SUCCESS
143
+ end
144
+ if @opt_help
145
+ usage
146
+ exit EXIT_SUCCESS
147
+ end
148
+ parse_argv
149
+ run
150
+ #--}}}
151
+ end
152
+ def run
153
+ #--{{{
154
+ init_logging
155
+
156
+ debug{ "lockpath <#{ @lockpath }>" }
157
+
158
+ opts = {}
159
+ options =
160
+ %w(retries max_age sleep_inc min_sleep max_sleep suspend timeout refresh poll_retries poll_max_sleep)
161
+ options.each do |opt|
162
+ #if((val = eval("opt_#{ opt }")))
163
+ if(send("opt_#{ opt }?"))
164
+ val = send "opt_#{ opt }"
165
+ begin
166
+ val = (opts[opt] = String === val ? Integer(val) : val)
167
+ logger.debug{ "<#{ opt }> <#{ val.inspect }>" }
168
+ rescue
169
+ logger.fatal{ "illegal value <#{ val.inspect }> for opt <#{ opt }>" }
170
+ exit EXIT_FAILURE
171
+ end
172
+ end
173
+ end
174
+
175
+ opts['debug'] = true if opt_debug
176
+
177
+ begin
178
+ case @argv.size
179
+ when 0
180
+ opts['dont_clean'] = true
181
+ logger.debug{ "opts <#{ opts.inspect }>" }
182
+ logger.debug{ "aquiring lock <#{ @lockpath }>..." }
183
+ #
184
+ # simple usage - just create the lockfile with opts
185
+ #
186
+ lockfile = ::Lockfile.new @lockpath, opts
187
+ lockfile.lock
188
+
189
+ logger.debug{ "aquired lock <#{ @lockpath }>" }
190
+ else
191
+ logger.debug{ "opts <#{ opts.inspect }>" }
192
+ logger.debug{ "aquiring lock <#{ @lockpath }>..." }
193
+ #
194
+ # block usage - create the lockfile with opts, run block, rm lockfile
195
+ #
196
+ status = 1
197
+
198
+ lockfile = ::Lockfile.new @lockpath, opts
199
+
200
+ lockfile.lock do
201
+ logger.debug{ "aquired lock <#{ @lockpath }>" }
202
+ logger.debug{ "cmd <#{ @argv.join ' ' }>" }
203
+ v = nil
204
+ begin
205
+ v = $VERBOSE
206
+ $VERBOSE = nil
207
+ STDOUT.flush
208
+ STDERR.flush
209
+ fork{ exec(*@argv) }
210
+ pid, status = Process::wait2
211
+ ensure
212
+ $VERBOSE = v
213
+ end
214
+ logger.debug{ "status <#{ $? }>" }
215
+ end
216
+
217
+ status = status.exitstatus
218
+ STDERR.puts "RLOCK SUBCOMMAND FAILURE" unless status == 0
219
+ exit status
220
+ end
221
+ rescue => e
222
+ logger.fatal{ e }
223
+ exit EXIT_FAILURE
224
+ end
225
+
226
+ exit EXIT_SUCCESS
227
+ #--}}}
228
+ end
229
+ def parse_opts
230
+ #--{{{
231
+ @op = OptionParser::new
232
+ @op.banner = ''
233
+ define_options
234
+ @op.parse! argv
235
+
236
+ #--}}}
237
+ end
238
+ def parse_argv
239
+ #--{{{
240
+ usage and exit EXIT_FAILURE if @argv.empty?
241
+ @lockpath = @argv.shift
242
+ #--}}}
243
+ end
244
+ def define_options
245
+ #--{{{
246
+ options = [
247
+ ['--retries=n','-r', "default(#{ Lockfile.retries.inspect }) - (nil => forever)"],
248
+ ['--max_age=n','-a', "default(#{ Lockfile.max_age.inspect })"],
249
+ ['--sleep_inc=n','-s', "default(#{ Lockfile.sleep_inc.inspect })"],
250
+ ['--max_sleep=n','-p', "default(#{ Lockfile.max_sleep.inspect })"],
251
+ ['--min_sleep=n','-P', "default(#{ Lockfile.min_sleep.inspect })"],
252
+ ['--suspend=n','-u', "default(#{ Lockfile.suspend.inspect })"],
253
+ ['--timeout=n','-t', "default(#{ Lockfile.timeout.inspect }) - (nil => never)"],
254
+ ['--refresh=n','-f', "default(#{ Lockfile.refresh.inspect })"],
255
+ ['--debug','-d', "default(#{ Lockfile.debug.inspect })"],
256
+ ['--poll_retries=n','-R', "default(#{ Lockfile.poll_retries.inspect })"],
257
+ ['--poll_max_sleep=n','-S', "default(#{ Lockfile.poll_max_sleep.inspect })"],
258
+ ['--dont_sweep','-w', "default(#{ Lockfile.dont_sweep.inspect })"],
259
+
260
+ ['--version'],
261
+ ['--verbosity=0-4|debug|info|warn|error|fatal','-v'],
262
+ ['--log=path','-l'],
263
+ ['--log_age=log_age'],
264
+ ['--log_size=log_size'],
265
+ ['--help','-h'],
266
+ ]
267
+ options.each do |option|
268
+ opt = option.first.gsub(%r/(?:--)|(?:=.*$)/o,'').strip
269
+ get, set = opt_attr opt
270
+ value4 = lambda do |v|
271
+ case v
272
+ when NilClass, %r/^t|true$/i
273
+ true
274
+ when %r/^f|false$/i
275
+ false
276
+ when %r/^nil|nul|null$/i
277
+ nil
278
+ else
279
+ v
280
+ end
281
+ end
282
+ @op.def_option(*option) do |v|
283
+ send set, value4[v]
284
+ end
285
+ end
286
+ #--}}}
287
+ end
288
+ %w(debug info warn error fatal).each do |m|
289
+ eval "def #{ m }(*args,&block);@logger.#{ m }(*args,&block);end"
290
+ end
291
+ def init_logging
292
+ #--{{{
293
+ if @opt_log_age
294
+ @opt_log_age = @opt_log_age.to_i if @opt_log_age =~ /\d/
295
+ end
296
+ if @opt_log_size
297
+ @opt_log_size = @opt_log_size.to_i if @opt_log_size =~ /\d/
298
+ end
299
+ $logger = @logger = Logger::new(@opt_log || STDERR, @opt_log_age, @opt_log_size)
300
+
301
+ level = nil
302
+ @opt_verbosity ||= 'info'
303
+ @opt_verbosity =
304
+ case @opt_verbosity
305
+ when /^\s*(?:4|d|debug)\s*$/io
306
+ level = 'Logging::DEBUG'
307
+ 4
308
+ when /^\s*(?:3|i|info)\s*$/io
309
+ level = 'Logging::INFO'
310
+ 3
311
+ when /^\s*(?:2|w|warn)\s*$/io
312
+ level = 'Logging::WARN'
313
+ 2
314
+ when /^\s*(?:1|e|error)\s*$/io
315
+ level = 'Logging::ERROR'
316
+ 1
317
+ when /^\s*(?:0|f|fatal)\s*$/io
318
+ level = 'Logging::FATAL'
319
+ 0
320
+ else
321
+ abort "illegal verbosity setting <#{ @opt_verbosity }>"
322
+ end
323
+ @logger.level = 2 - ((@opt_verbosity % 5) - 2)
324
+ #--}}}
325
+ end
326
+ def usage io = STDOUT
327
+ #--{{{
328
+ io << USAGE
329
+ io << "\n"
330
+ io << @op
331
+ io << "\n"
332
+ io << EXAMPLES if defined? EXAMPLES
333
+ self
334
+ #--}}}
335
+ end
336
+ def opt_attr opt
337
+ #--{{{
338
+ query = "opt_#{ opt }?"
339
+ get = "opt_#{ opt }"
340
+ set = "#{ get }="
341
+ code = <<-code
342
+ class << self
343
+ def #{ query }; defined? @#{ get }; end
344
+ def #{ get }; defined?(@#{ get }) ? @#{ get } : nil; end
345
+ def #{ set } value; @#{ get } = value; end
346
+ end
347
+ code
348
+ instance_eval code
349
+ [get, set]
350
+ #--}}}
351
+ end
352
+ def mcp obj
353
+ #--{{{
354
+ Marshal::load(Marshal::dump(obj))
355
+ #--}}}
356
+ end
357
+ #--}}}
358
+ end
359
+
360
+ Main::new