climax 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +164 -0
- data/climax.gemspec +1 -1
- data/lib/climax/version.rb +1 -1
- data/lib/climax.rb +55 -18
- data/test/lib/hello_world.rb +1 -0
- metadata +4 -3
data/README.markdown
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
Introduction
|
2
|
+
============
|
3
|
+
|
4
|
+
Climax is a Ruby gem designed to speed-up the development of command line applications. It provides
|
5
|
+
a number of features that nearly all long-running cli application need such as logging, running as a
|
6
|
+
daemon, processing command line arguments, and something else unexpected. When your application
|
7
|
+
uses climax, a control DRb runs with your application, allowing you to manipulate your application
|
8
|
+
as it runs in the background. For instance, if your application is running, you can remotely debug
|
9
|
+
the application at any time. This is great for long-running processes in production. Something
|
10
|
+
strange going on that you'd like to investigate? Use the control drb to change the log level from
|
11
|
+
"info" to "debug" to get more info. Still not sure what's happening? Attach a debugger to the running process.
|
12
|
+
|
13
|
+
You get all of these features for free when your application uses climax.
|
14
|
+
|
15
|
+
Installation
|
16
|
+
============
|
17
|
+
|
18
|
+
To install climax simply install the climax gem: `gem install climax` or alternatively place `gem
|
19
|
+
"climax"` in your application's Gemfile and run bundler.
|
20
|
+
|
21
|
+
Getting Started
|
22
|
+
===============
|
23
|
+
|
24
|
+
It's easy to get started. Just include the `Climax::Application` module into your application
|
25
|
+
class:
|
26
|
+
|
27
|
+
require 'climax'
|
28
|
+
|
29
|
+
class MyApplication
|
30
|
+
include Climax::Application
|
31
|
+
|
32
|
+
def main
|
33
|
+
log.info "Hello World"
|
34
|
+
return 0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
MyApplication.new(ARGV).run()
|
39
|
+
|
40
|
+
The above example is about as simple as you can get. Here we define an application and give it a
|
41
|
+
`main` method. The `main` method will be called REPEATEDLY until it returns a value other than nil.
|
42
|
+
In the example above `main` will only be called once because it returns `0`.
|
43
|
+
|
44
|
+
If you save the above example in a file and run it with `--help` you will get a list of default
|
45
|
+
command line options provided by climax:
|
46
|
+
|
47
|
+
Usage: my_application [options]
|
48
|
+
-d, --daemon Fork application and run in background
|
49
|
+
--log_level Set to debug, info, warn, error, or fatal. Default: info.
|
50
|
+
--log_file File to log output to. By default logs to stdout.
|
51
|
+
--control_port Override the port for the control DRb to listen on. Default is 7249
|
52
|
+
-h, --help Display this help message.
|
53
|
+
|
54
|
+
As you can see climax by default provides some options free of charge. If you would like to modify
|
55
|
+
how climax behaves, such as by adding more command line options or maybe removing the option to fork
|
56
|
+
your application, you can provide these configurations by defining a method in your application
|
57
|
+
class named `configure`. The `configure` method is called by climax before it parses command line
|
58
|
+
options, before it sets up logging, basically before it does anything. This means that you cannot
|
59
|
+
use climax facilities such as logging in this method because climax has not yet bootstrapped.
|
60
|
+
|
61
|
+
After the `configure` method is run climax bootstraps the environment for your application. It
|
62
|
+
parses the arguments passed from the command line, it sets up logging, forks your application if
|
63
|
+
asked to, and starts the control drb unless your application requests otherwise.
|
64
|
+
|
65
|
+
The climax framework then runs your application as follows:
|
66
|
+
|
67
|
+
* Calls the `pre_main` method of your application class if it exists. This is an excellent place
|
68
|
+
to put code that should run once before your `main` method.
|
69
|
+
|
70
|
+
* Calls the `main` method of your application class. If this method does not exist climax will
|
71
|
+
raise an exception. The `main` method will be called **repeatedly** until it returns a value
|
72
|
+
other than nil. If the `main` method returns an integer then your application will exit with
|
73
|
+
that integer as the status code. If the `main` method returns a string then your application
|
74
|
+
will abort with that string as the message.
|
75
|
+
|
76
|
+
* Calls the `post_main` method of your application class if it exists. This is a good place to put
|
77
|
+
code that should run once when your application is exiting.
|
78
|
+
|
79
|
+
Here is a slightly larger example that shows more of climax's features:
|
80
|
+
|
81
|
+
require 'climax'
|
82
|
+
|
83
|
+
class MyApplication
|
84
|
+
include Climax::Application
|
85
|
+
|
86
|
+
def configure
|
87
|
+
options do
|
88
|
+
on 'v', 'verbose', 'More verbose'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def pre_main
|
93
|
+
log.debug "App has initialized and is about to run."
|
94
|
+
end
|
95
|
+
|
96
|
+
def main
|
97
|
+
puts "Hello World!"
|
98
|
+
sleep 3
|
99
|
+
return nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def post_main
|
103
|
+
log.debug "App has finished running and is about to exit."
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
The above example is a simple application that adds an extra command line option `--verbose` in the
|
108
|
+
`configure` method. The `pre_main` method is simple and just writes a debug log statement. As you
|
109
|
+
can see logging has been setup at this point. Then the `main` method is called. Because the `main`
|
110
|
+
method in this example always returns nil, this application will run forever until it is stopped
|
111
|
+
externally (`Ctrl-C`, `kill -9`, or using the Control DRb). Using `Ctrl-C` and `kill -9` will
|
112
|
+
immediately halt execution of the application and `post_main` will never be called. However if the
|
113
|
+
Control DRb is used then the application will exit in an orderly fashion and the `post_main` method
|
114
|
+
will be called. Notice that for free you can fork this application, change the log level with the
|
115
|
+
`--log-level` option, write the logs to a file using the `--log-file` option, and you can start a
|
116
|
+
debugger at any time while this application is running using the Control DRb. The Control DRb has a
|
117
|
+
lot of power. We'll talk about it more below.
|
118
|
+
|
119
|
+
Climax Event Queue
|
120
|
+
==================
|
121
|
+
|
122
|
+
An important concept to understand is that climax handles events that come in from the Control DRb.
|
123
|
+
DRb's by necessity run in a separate thread. However, climax makes absolutely no assumptions about
|
124
|
+
the thread-safeness of your application. **You do not need to write thread safe applications.**
|
125
|
+
Climax is written in such a way that it simply places events into a thread safe queue. Every time
|
126
|
+
your `main` method completes, any existing events in the queue are processed. Your `main` method is
|
127
|
+
then called again. This process goes on until the `main` method returns a value other than nil or
|
128
|
+
an `:exit` or `:quit` event is placed in the event queue.
|
129
|
+
|
130
|
+
Your application can send events to the climax event queue with the `send_event` method. You must
|
131
|
+
pass `send_event` the event type (e.g., `:exit` or `:start_remote_debugger`) and you may also
|
132
|
+
optionally pass a payload (i.e., extra data) as a second parameter.
|
133
|
+
|
134
|
+
For example, if you wish for your application to exit in an orderly fashion **after** the current
|
135
|
+
iteration of `main` has had a chance to finish, you can accomplish this by placing an `:exit` event
|
136
|
+
onto the event queue and letting your `main` method finish its work. When your `main` method is
|
137
|
+
finished the events on the queue will be processed by climax. When climax reads the `:exit` event
|
138
|
+
off of the queue it will call your `post_main` method and perform other cleanup tasks and then exit.
|
139
|
+
|
140
|
+
require 'climax'
|
141
|
+
|
142
|
+
class MyApplication
|
143
|
+
include Climax::Application
|
144
|
+
|
145
|
+
def main
|
146
|
+
send_event(:exit) if about_to_meet_work_quota?
|
147
|
+
work = get_some_work
|
148
|
+
do_work(work)
|
149
|
+
return nil
|
150
|
+
end
|
151
|
+
|
152
|
+
# ...
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
A consequence of this is that when you issue commands to the Control DRb, your command will not be
|
157
|
+
processed until the current iteration of `main` has had a chance to complete. Therefore it is a
|
158
|
+
good idea to keep each iteration of `main` as short as possible, although this is certainly not a
|
159
|
+
requirement. In other words, if you wish to enter the remote debugger for a running process, the
|
160
|
+
debugger will not begin until the current iteration of `main` has completed.
|
161
|
+
|
162
|
+
This is excellent for stopping a long running process without interrupting its work. By sending an
|
163
|
+
:exit or :quit event, whether through the Control DRb or from your application itself, your
|
164
|
+
application will be allowed to finish processing its current unit of work before exiting.
|
data/climax.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Climax::VERSION
|
8
8
|
s.authors = ["Alfred J. Fazio"]
|
9
9
|
s.email = ["alfred.fazio@gmail.com"]
|
10
|
-
s.homepage = "https://github.com/
|
10
|
+
s.homepage = "https://github.com/appriss/climax"
|
11
11
|
s.summary = %q{Ruby command line application framework}
|
12
12
|
s.description = %q{Opinionated framework for Ruby CLI applications that provides logging, cli argument parsing, daemonizing, configuration, testing, and even remote control of long running processes}
|
13
13
|
|
data/lib/climax/version.rb
CHANGED
data/lib/climax.rb
CHANGED
@@ -12,20 +12,35 @@ module Climax
|
|
12
12
|
@args = args.dup
|
13
13
|
options do
|
14
14
|
on :d, :daemon, "Fork application and run in background"
|
15
|
-
on :control_port=, "Override the port for the control DRb to listen on. Default is 7249", :as => :int, :default => 7249
|
16
15
|
on :log_level=, "Set to debug, info, warn, error, or fatal. Default: info.", :default => "info"
|
17
16
|
on :log_file=, "File to log output to. By default logs to stdout.", :default => nil
|
17
|
+
on :control_port=, "Override the port for the control DRb to listen on. Default is 7249", :as => :int, :default => 7249
|
18
18
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
configure
|
20
|
+
_parse_options
|
21
|
+
end
|
22
|
+
|
23
|
+
def _pre_main
|
22
24
|
if daemonize?
|
23
25
|
exit 0 if !Process.fork.nil?
|
24
26
|
log.debug "Running in background (#{$$})"
|
25
27
|
end
|
28
|
+
|
26
29
|
log.debug "Starting Control DRb on port #{control_port}"
|
27
30
|
@control_drb = Climax::ControlDRb.new(self, control_port)
|
28
|
-
|
31
|
+
end
|
32
|
+
|
33
|
+
def _post_main
|
34
|
+
log.debug "Stopping Control DRb"
|
35
|
+
@control_drb.stop_service
|
36
|
+
end
|
37
|
+
|
38
|
+
def _parse_options
|
39
|
+
slop.parse!
|
40
|
+
@opts = slop
|
41
|
+
exit 0 if opts.help?
|
42
|
+
rescue => e
|
43
|
+
abort("#{e.message}\n#{slop.to_s}")
|
29
44
|
end
|
30
45
|
|
31
46
|
def daemonize?
|
@@ -98,7 +113,7 @@ module Climax
|
|
98
113
|
|
99
114
|
# Return instance of Slop
|
100
115
|
def slop
|
101
|
-
@slop ||= Slop.new
|
116
|
+
@slop ||= Slop.new(:strict => true, :help => true)
|
102
117
|
end
|
103
118
|
|
104
119
|
# Method for wrapping calls to on() and banner(). Simply for readability.
|
@@ -112,9 +127,18 @@ module Climax
|
|
112
127
|
|
113
128
|
# Run the application
|
114
129
|
def run
|
130
|
+
_pre_main
|
115
131
|
pre_main
|
116
|
-
_event_loop
|
132
|
+
@exit_status = _event_loop
|
133
|
+
_post_main
|
117
134
|
post_main
|
135
|
+
|
136
|
+
exit exit_status if exit_status.is_a? Fixnum
|
137
|
+
abort(exit_status.to_s)
|
138
|
+
end
|
139
|
+
|
140
|
+
def exit_status
|
141
|
+
@exit_status
|
118
142
|
end
|
119
143
|
|
120
144
|
# Return current options. nil until ClimaxApplication::new has finished running
|
@@ -138,21 +162,22 @@ module Climax
|
|
138
162
|
|
139
163
|
def _event_loop
|
140
164
|
while true
|
141
|
-
event = _next_event
|
142
165
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
166
|
+
begin
|
167
|
+
event = _next_event
|
168
|
+
|
169
|
+
unless event.nil?
|
170
|
+
case event.type
|
171
|
+
when :set_log_level then log_level = event.payload
|
172
|
+
when :stop_control_drb then @control_drb && @control_drb.stop_service
|
173
|
+
when :start_remote_debugger then binding.remote_pry
|
174
|
+
when :quit, :exit then return 0
|
175
|
+
end
|
148
176
|
end
|
149
|
-
end
|
177
|
+
end while !event.nil?
|
150
178
|
|
151
179
|
result = main
|
152
|
-
|
153
|
-
exit result if result.is_a? Fixnum
|
154
|
-
raise result if result.is_a? String
|
155
|
-
raise "Unrecognized return value type: (#{result.class}: #{result}). Only recognize strings, ints, or nil" if !result.nil?
|
180
|
+
return result if !result.nil?
|
156
181
|
end
|
157
182
|
end
|
158
183
|
|
@@ -176,6 +201,18 @@ module Climax
|
|
176
201
|
@events_mutex ||= Mutex.new
|
177
202
|
end
|
178
203
|
|
204
|
+
def configure
|
205
|
+
end
|
206
|
+
|
207
|
+
def main
|
208
|
+
raise "Please implement a main() method for your application."
|
209
|
+
end
|
210
|
+
|
211
|
+
def pre_main
|
212
|
+
end
|
213
|
+
|
214
|
+
def post_main
|
215
|
+
end
|
179
216
|
|
180
217
|
end
|
181
218
|
end
|
data/test/lib/hello_world.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: climax
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -102,6 +102,7 @@ extra_rdoc_files: []
|
|
102
102
|
files:
|
103
103
|
- .gitignore
|
104
104
|
- Gemfile
|
105
|
+
- README.markdown
|
105
106
|
- Rakefile
|
106
107
|
- climax.gemspec
|
107
108
|
- lib/climax.rb
|
@@ -109,7 +110,7 @@ files:
|
|
109
110
|
- lib/climax/version.rb
|
110
111
|
- test/bin/hello_world
|
111
112
|
- test/lib/hello_world.rb
|
112
|
-
homepage: https://github.com/
|
113
|
+
homepage: https://github.com/appriss/climax
|
113
114
|
licenses: []
|
114
115
|
post_install_message:
|
115
116
|
rdoc_options: []
|