climax 0.0.1 → 0.0.2
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.
- 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: []
|