crazy_ivan 1.2.3 → 1.2.4

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