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 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/afazio/climax"
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
 
@@ -1,3 +1,3 @@
1
1
  module Climax
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
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
- post_initialize
20
- slop.parse
21
- @opts = slop
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
- run
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
- unless event.nil?
144
- case event.type
145
- when :set_log_level then log_level = event.payload
146
- when :start_remote_debugger then binding.remote_pry
147
- when :quit then exit
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
@@ -14,6 +14,7 @@ module HelloWorld
14
14
  def main
15
15
  puts "Hello World!"
16
16
  sleep 3
17
+ return nil
17
18
  end
18
19
  end
19
20
  end
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.1
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-19 00:00:00.000000000 Z
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/afazio/climax
113
+ homepage: https://github.com/appriss/climax
113
114
  licenses: []
114
115
  post_install_message:
115
116
  rdoc_options: []