olag 0.1.10
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +24 -0
- data/LICENSE +19 -0
- data/README.rdoc +17 -0
- data/Rakefile +24 -0
- data/codnar.html +4379 -0
- data/doc/root.html +26 -0
- data/doc/system.markdown +283 -0
- data/lib/olag/application.rb +167 -0
- data/lib/olag/change_log.rb +59 -0
- data/lib/olag/data_files.rb +20 -0
- data/lib/olag/errors.rb +44 -0
- data/lib/olag/gem_specification.rb +77 -0
- data/lib/olag/globals.rb +43 -0
- data/lib/olag/hash_struct.rb +12 -0
- data/lib/olag/rake.rb +263 -0
- data/lib/olag/string_unindent.rb +19 -0
- data/lib/olag/test.rb +8 -0
- data/lib/olag/test/with_errors.rb +26 -0
- data/lib/olag/test/with_fakefs.rb +36 -0
- data/lib/olag/test/with_rake.rb +34 -0
- data/lib/olag/test/with_tempfile.rb +49 -0
- data/lib/olag/update_version.rb +53 -0
- data/lib/olag/version.rb +8 -0
- data/test/access_data_files.rb +15 -0
- data/test/collect_errors.rb +33 -0
- data/test/missing_keys.rb +17 -0
- data/test/run_application.rb +44 -0
- data/test/unindent_text.rb +26 -0
- metadata +243 -0
data/doc/root.html
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
|
5
|
+
<title>Olag - Oren's Library/Application Gem framework.</title>
|
6
|
+
<style type="text/css">
|
7
|
+
<embed src="codnar/data/yui/reset.css" type="x-codnar/file"/>
|
8
|
+
<embed src="codnar/data/yui/base.css" type="x-codnar/file"/>
|
9
|
+
<embed src="codnar/data/style.css" type="x-codnar/file"/>
|
10
|
+
<embed src="codnar/data/sunlight/default.css" type="x-codnar/file"/>
|
11
|
+
</style>
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
<div id="contents"></div>
|
15
|
+
<embed src="README.rdoc" type="x-codnar/include"/>
|
16
|
+
<embed src="doc/system.markdown" type="x-codnar/include"/>
|
17
|
+
<script type="text/javascript">
|
18
|
+
<embed src="codnar/data/contents.js" type="x-codnar/file"/>
|
19
|
+
<embed src="codnar/data/control_chunks.js" type="x-codnar/file"/>
|
20
|
+
<embed src="codnar/data/sunlight/min.js" type="x-codnar/file"/>
|
21
|
+
<embed src="codnar/data/sunlight/ruby-min.js" type="x-codnar/file"/>
|
22
|
+
Sunlight.globalOptions.lineNumbers = false;
|
23
|
+
Sunlight.highlightAll();
|
24
|
+
</script>
|
25
|
+
</body>
|
26
|
+
</html>
|
data/doc/system.markdown
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
## Rakefile ##
|
2
|
+
|
3
|
+
Olag's Rakefile is a good example of how to use Olag's classes to create a
|
4
|
+
full-featured gem Rakefile:
|
5
|
+
|
6
|
+
[[Rakefile|named_chunk_with_containers]]
|
7
|
+
|
8
|
+
The overall Rakefile structure is as follows:
|
9
|
+
|
10
|
+
* A first line sets up the Ruby module load path to begin with
|
11
|
+
the current gem's `lib` directory. This standard idiom ensures we have access
|
12
|
+
to the current gem.
|
13
|
+
|
14
|
+
* The next line imports Olag's `rake` support module.
|
15
|
+
|
16
|
+
* This is followed by setting up the gem specification, which is enhanced by
|
17
|
+
Olag using monkey-patching.
|
18
|
+
|
19
|
+
* Finally, Olag::Rake sets up the following tasks (as reported by `rake -T`):
|
20
|
+
rake all # Version, verify, document, package
|
21
|
+
rake analyze # Analyze source code
|
22
|
+
rake changelog # Update ChangeLog from Git
|
23
|
+
rake clean # Remove any temporary products.
|
24
|
+
rake clean_codnar # Clean all split chunks
|
25
|
+
rake clobber # Remove any generated file.
|
26
|
+
rake clobber_codnar # Remove woven HTML documentation
|
27
|
+
rake clobber_coverage # Remove rcov products for coverage
|
28
|
+
rake clobber_package # Remove package products
|
29
|
+
rake clobber_rdoc # Remove rdoc products
|
30
|
+
rake codnar # Build the code narrative HTML
|
31
|
+
rake codnar_split # Split all files into chunks
|
32
|
+
rake codnar_weave # Weave chunks into HTML
|
33
|
+
rake commit # Git commit process
|
34
|
+
rake coverage # Test code covarage with RCov
|
35
|
+
rake doc # Generate all documentation
|
36
|
+
rake first_commit # Perform the 1st (main) Git commit
|
37
|
+
rake flay # Check for duplicated code with Flay
|
38
|
+
rake gem # Build the gem file olag-<version>.gem
|
39
|
+
rake package # Build all the packages
|
40
|
+
rake rdoc # Build the rdoc HTML Files
|
41
|
+
rake reek # Check for smelly code with Reek
|
42
|
+
rake repackage # Force a rebuild of the package files
|
43
|
+
rake rerdoc # Force a rebuild of the RDOC files
|
44
|
+
rake roodi # Check for smelly code with Roodi
|
45
|
+
rake saikuro # Check for complex code with Saikuro
|
46
|
+
rake second_commit # Perform the 2nd (amend) Git commit
|
47
|
+
rake test # Run tests for test
|
48
|
+
rake verify # Test, coverage, analyze code
|
49
|
+
rake version # Update version file from Git
|
50
|
+
|
51
|
+
### Gem Specification ###
|
52
|
+
|
53
|
+
The gem specification is provided as usual:
|
54
|
+
|
55
|
+
[[Gem specification|named_chunk_with_containers]]
|
56
|
+
|
57
|
+
However, the Gem::Specification class is monkey-patched to automatically
|
58
|
+
several of the specification fields, and adding some new ones:
|
59
|
+
|
60
|
+
[[lib/olag/gem_specification.rb|named_chunk_with_containers]]
|
61
|
+
|
62
|
+
### Rake tasks ###
|
63
|
+
|
64
|
+
The Olag::Rake class sets up the tasks listed above as follows:
|
65
|
+
|
66
|
+
[[lib/olag/rake.rb|named_chunk_with_containers]]
|
67
|
+
|
68
|
+
#### Task utilities ####
|
69
|
+
|
70
|
+
The following utilities are used to create the different tasks. It would have
|
71
|
+
be nicer if Rake had treated the task description as just another task
|
72
|
+
property.
|
73
|
+
|
74
|
+
[[Task utilities|named_chunk_with_containers]]
|
75
|
+
|
76
|
+
#### Verify the gem ####
|
77
|
+
|
78
|
+
The following tasks verify that the gem is correct. Testing for 100% code
|
79
|
+
coverage seems excessive but in reality it isn't that hard to do, and is really
|
80
|
+
only a modest form of test coverage verification.
|
81
|
+
|
82
|
+
[[Verify gem functionality|named_chunk_with_containers]]
|
83
|
+
|
84
|
+
The following tasks verify that the code is squeacky-clean. While passing the
|
85
|
+
code through all these verifiers seems excessive, it isn't that hard to achieve
|
86
|
+
in practice. There were several times I did refactorings "just to satisfy
|
87
|
+
`reek` (or `flay`)" and ended up with an unexpected code improvement. Anyway,
|
88
|
+
if you aren't a youch OCD about this sort of thing, Olag is probably not for
|
89
|
+
you :-)
|
90
|
+
|
91
|
+
[[Analyze the source code|named_chunk_with_containers]]
|
92
|
+
|
93
|
+
#### Generate Documentation ####
|
94
|
+
|
95
|
+
The following tasks generate the usual RDoc documentation, required to make the
|
96
|
+
gem behave well in the Ruby tool ecosystem:
|
97
|
+
|
98
|
+
[[Generate RDoc documentation|named_chunk_with_containers]]
|
99
|
+
|
100
|
+
The following tasks generate the Codnar documentation (e.g., the document you
|
101
|
+
are reading now), which goes beyond RDoc to provide an end-to-end linear
|
102
|
+
narrative describing the gem:
|
103
|
+
|
104
|
+
[[Generate Codnar documentation|named_chunk_with_containers]]
|
105
|
+
|
106
|
+
Codnar is very configurable, and the above provides a reasonable default
|
107
|
+
configuration for pure Ruby gems. You can modify the CODNAR_CONFIGURATIONS
|
108
|
+
array before creating the Rake object, by unshifting additional/overriding
|
109
|
+
patterns into it. For example, you may choose to use GVim for syntax
|
110
|
+
highlighting. This will cause splitting to become much slower, but the
|
111
|
+
generated HTML will already include the highlighting markup so it will display
|
112
|
+
instantly. Or, you may have additional source file types (Javascript, CSS,
|
113
|
+
HTML, C, etc.) to be highlighted.
|
114
|
+
|
115
|
+
#### Automate Git commit process ####
|
116
|
+
|
117
|
+
In an ideal world, committing to Git would be a simple matter of typing `git
|
118
|
+
commit -m "..."`. In our case, things get a bit complicated.
|
119
|
+
|
120
|
+
There is some information that we need to extract out of Git and inject into
|
121
|
+
our files (to be committed). Since Git pre-commit hooks do not allow us to
|
122
|
+
modify any source files, this turns commit into a two-phase process: we do an
|
123
|
+
initial commit, update some files, then `git commit --amend` to merge them with
|
124
|
+
the first commit.
|
125
|
+
|
126
|
+
[[Automate Git commit process|named_chunk_with_containers]]
|
127
|
+
|
128
|
+
The first piece of information we need to extract from Git is the current build
|
129
|
+
number, which needs to be injected into the gem's version number:
|
130
|
+
|
131
|
+
[[lib/olag/version.rb|named_chunk_with_containers]]
|
132
|
+
|
133
|
+
Documentation generation will depend on the content (and therefore modification
|
134
|
+
time) of this file. Luckily, we can update this number before the first commit,
|
135
|
+
and we can ensure it only touches the file if there is a real change, to avoid
|
136
|
+
unnecessary documentation regeneration:
|
137
|
+
|
138
|
+
[[lib/olag/update_version.rb|named_chunk_with_containers]]
|
139
|
+
|
140
|
+
The second information we extract from Git is the ChangeLog file. Here,
|
141
|
+
obviously, the ChangeLog needs to include the first commit's message, so we are
|
142
|
+
forced to regenerate the file and amend Git's history with a second commit:
|
143
|
+
|
144
|
+
[[lib/olag/change_log.rb|named_chunk_with_containers]]
|
145
|
+
|
146
|
+
## Utility classes ##
|
147
|
+
|
148
|
+
Olag provides a set of utility classes that are useful in implementing
|
149
|
+
well-behaved gems.
|
150
|
+
|
151
|
+
### Unindeting text ###
|
152
|
+
|
153
|
+
When using "here documents" (`<<EOF` data), it is nice to be able to indent the
|
154
|
+
data to match the surrounding code. There are other cases where it is useful to
|
155
|
+
"unindent" multi-line text. The following tests demonstrates using the
|
156
|
+
`unindent` function:
|
157
|
+
|
158
|
+
[[test/unindent_text.rb|named_chunk_with_containers]]
|
159
|
+
|
160
|
+
And here is the implementation extending the built-in String class:
|
161
|
+
|
162
|
+
[[lib/olag/string_unindent.rb|named_chunk_with_containers]]
|
163
|
+
|
164
|
+
### Accessing gem data files ###
|
165
|
+
|
166
|
+
Sometimes it is useful to package some files inside a gem, to be read by user
|
167
|
+
code. This is of course trivial for Ruby code files (just use `require`) but
|
168
|
+
not trivial if you want, say, to include some CSS files in your gem for
|
169
|
+
everyone to use. Olag provides a way to resolve the path of any file in any gem
|
170
|
+
(basically replicating what `require` does). Here is a simple test of using
|
171
|
+
this functionality:
|
172
|
+
|
173
|
+
[[test/access_data_files.rb|named_chunk_with_containers]]
|
174
|
+
|
175
|
+
And here is the implementation:
|
176
|
+
|
177
|
+
[[lib/olag/data_files.rb|named_chunk_with_containers]]
|
178
|
+
|
179
|
+
### Simulating objects with Hash tables ###
|
180
|
+
|
181
|
+
Javascript has an interesting convention where `hash["key"]` and `hash.key`
|
182
|
+
mean the same thing. This is very useful in cutting down boilerplate code, and
|
183
|
+
it also makes your data serialize to very clean YAML. Unlike OpenStruct, you do
|
184
|
+
not need to define all the members in advance, and you can alternate between
|
185
|
+
the `.key` and `["key"]` forms as convenient for any particular piece of code.
|
186
|
+
The down side is that you lose any semblance of type checking - misspelled
|
187
|
+
member names and other errors are silently ignored. Well, that's what we have
|
188
|
+
unit tests for, right? :-)
|
189
|
+
|
190
|
+
Olag provides an extension to the Hash class that provides the above, for these
|
191
|
+
who have chosen to follow the dark side of the force. Here is a simple test
|
192
|
+
demonstrating using this ability:
|
193
|
+
|
194
|
+
[[test/missing_keys.rb|named_chunk_with_containers]]
|
195
|
+
|
196
|
+
And here is the implementation:
|
197
|
+
|
198
|
+
[[lib/olag/hash_struct.rb|named_chunk_with_containers]]
|
199
|
+
|
200
|
+
### Collecting errors ###
|
201
|
+
|
202
|
+
In library code, it is bad practice to terminate the program on an error.
|
203
|
+
Raising an exception is preferrable, but that forces you to abort the
|
204
|
+
processing. In some cases, it is preferrable to collect the error, skip a bit
|
205
|
+
of processing, and continue (if only for detecting additional errors). For
|
206
|
+
example, one would expect a compiler to emit more than just the first syntax
|
207
|
+
error message.
|
208
|
+
|
209
|
+
Olag provides an error collection class that also automatically formats the
|
210
|
+
error to indicate its location. Here is a simple test that demonstrates
|
211
|
+
collecting errors:
|
212
|
+
|
213
|
+
[[test/collect_errors.rb|named_chunk_with_containers]]
|
214
|
+
|
215
|
+
Which uses a mix-in that helps writing tests that use errors:
|
216
|
+
|
217
|
+
[[lib/olag/test/with_errors.rb|named_chunk_with_containers]]
|
218
|
+
|
219
|
+
Here is the actual implementation:
|
220
|
+
|
221
|
+
[[lib/olag/errors.rb|named_chunk_with_containers]]
|
222
|
+
|
223
|
+
### Testing with a fake file system ###
|
224
|
+
|
225
|
+
Sometimes tests need to muck around with disk files. One way to go about it is
|
226
|
+
to create a temporary disk directory, work in there, and clean it up when done.
|
227
|
+
Another, simpler way is to use the FakeFS file system, which captures all(most)
|
228
|
+
of Ruby's file operations and redirect them to an in-memory fake file system.
|
229
|
+
Here is a mix-in that helps writing tests using FakeFS (we will use it below
|
230
|
+
when running applications inside unit tests):
|
231
|
+
|
232
|
+
[[lib/olag/test/with_fakefs.rb|named_chunk_with_containers]]
|
233
|
+
|
234
|
+
### Testing with a temporary file ###
|
235
|
+
|
236
|
+
When running external programs, actually generating a temporary disk file is
|
237
|
+
sometimes inevitable. Of course, such files need to be cleaned up when the test
|
238
|
+
is done. If we need more than just one such file, it is easier to create a
|
239
|
+
whole temporary directory which is easily cleaned up in one operation.
|
240
|
+
|
241
|
+
Here is a mix-in that helps writing tests using temporary files and folders:
|
242
|
+
|
243
|
+
[[lib/olag/test/with_tempfile.rb|named_chunk_with_containers]]
|
244
|
+
|
245
|
+
### Testing Rake tasks ###
|
246
|
+
|
247
|
+
Testing Rake tasks is tricky because tests may be run in the context of Rake.
|
248
|
+
Therefore, the best practice is to create a new Rake application and restore
|
249
|
+
the original when the test is done:
|
250
|
+
|
251
|
+
[[lib/olag/test/with_rake.rb|named_chunk_with_containers]]
|
252
|
+
|
253
|
+
### Testing in general ###
|
254
|
+
|
255
|
+
Rather than requiring each of the above test mix-in modules on its own, it is
|
256
|
+
convenient to just `require "olag/test"` and be done:
|
257
|
+
|
258
|
+
[[lib/olag/test.rb|named_chunk_with_containers]]
|
259
|
+
|
260
|
+
## Applications ##
|
261
|
+
|
262
|
+
Writing an application requires a lot of boilerplate. Olag provides an
|
263
|
+
Application base class that handles standard command line flags, execution from
|
264
|
+
within tests, and errors collection.
|
265
|
+
|
266
|
+
Here is a simple test for running such an application from unit tests:
|
267
|
+
|
268
|
+
[[test/run_application.rb|named_chunk_with_containers]]
|
269
|
+
|
270
|
+
And here is the implementation:
|
271
|
+
|
272
|
+
[[lib/olag/application.rb|named_chunk_with_containers]]
|
273
|
+
|
274
|
+
It makes use of the following utility class, for saving and restoring the
|
275
|
+
global state when running an application in a test:
|
276
|
+
|
277
|
+
[[lib/olag/globals.rb|named_chunk_with_containers]]
|
278
|
+
|
279
|
+
## License ##
|
280
|
+
|
281
|
+
Olag is published under the MIT license:
|
282
|
+
|
283
|
+
[[LICENSE|named_chunk_with_containers]]
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "olag/errors"
|
3
|
+
require "olag/globals"
|
4
|
+
require "olag/string_unindent.rb"
|
5
|
+
require "olag/version"
|
6
|
+
require "optparse"
|
7
|
+
|
8
|
+
module Olag
|
9
|
+
|
10
|
+
# Base class for Olag applications.
|
11
|
+
class Application
|
12
|
+
|
13
|
+
# Create a Olag application.
|
14
|
+
def initialize(is_test = nil)
|
15
|
+
@errors = Errors.new
|
16
|
+
@is_test = !!is_test
|
17
|
+
end
|
18
|
+
|
19
|
+
# Run the Olag application, returning its status.
|
20
|
+
def run(*arguments, &block)
|
21
|
+
parse_options
|
22
|
+
yield(*arguments) if block_given?
|
23
|
+
return print_errors
|
24
|
+
rescue ExitException => exception
|
25
|
+
return exception.status
|
26
|
+
end
|
27
|
+
|
28
|
+
# Execute a block with an overriden ARGV, typically for running an
|
29
|
+
# application.
|
30
|
+
def self.with_argv(argv)
|
31
|
+
return Globals.without_changes do
|
32
|
+
ARGV.replace(argv)
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# Parse the command line options of the program.
|
40
|
+
def parse_options
|
41
|
+
parser = OptionParser.new do |options|
|
42
|
+
(@options = options).banner = banner + "\n\nOPTIONS:\n\n"
|
43
|
+
define_flags
|
44
|
+
end
|
45
|
+
parser.parse!
|
46
|
+
parse_arguments
|
47
|
+
end
|
48
|
+
|
49
|
+
# Parse remaining command-line file arguments. This is expected to be
|
50
|
+
# overriden by the concrete application sub-class. By default assumes there
|
51
|
+
# are no such arguments.
|
52
|
+
def parse_arguments
|
53
|
+
return if ARGV.size == 0
|
54
|
+
$stderr.puts("#{$0}: Expects no command line file arguments.")
|
55
|
+
exit(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Define application flags. This is expected to be overriden by the
|
59
|
+
# concrete application sub-class.
|
60
|
+
def define_flags
|
61
|
+
define_help_flag
|
62
|
+
define_version_flag
|
63
|
+
define_redirect_flag("$stdout", "output", "w")
|
64
|
+
define_redirect_flag("$stderr", "error", "w")
|
65
|
+
#! Most scripts do not use this, but they can add it.
|
66
|
+
#! define_redirect_flag("$stdin", "input", "r")
|
67
|
+
end
|
68
|
+
|
69
|
+
# Define the standard help flag.
|
70
|
+
def define_help_flag
|
71
|
+
@options.on("-h", "--help", "Print this help message and exit.") do
|
72
|
+
puts(@options)
|
73
|
+
print_additional_help
|
74
|
+
exit(0)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Print additional help message. This includes both the command line file
|
79
|
+
# arguments, if any, and a short description of the program.
|
80
|
+
def print_additional_help
|
81
|
+
arguments_name, arguments_description = arguments
|
82
|
+
puts(format(" %-33s%s", arguments_name, arguments_description)) if arguments_name
|
83
|
+
print("\nDESCRIPTION:\n\n")
|
84
|
+
print(description)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return the banner line of the help message. This is expected to be
|
88
|
+
# overriden by the concrete application sub-class. By default returns the
|
89
|
+
# path name of thje executed program.
|
90
|
+
def banner
|
91
|
+
return $0
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return the name and description of any final command-line file arguments,
|
95
|
+
# if any. This is expected to be overriden by the concrete application
|
96
|
+
# sub-class. By default, assume there are no final command-line file
|
97
|
+
# arguments (however, `parse_options` does not enforce this by default).
|
98
|
+
def arguments
|
99
|
+
return nil, nil
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return a short description of the program. This is expected to be
|
103
|
+
# overriden by the concrete application sub-class. By default, provide
|
104
|
+
def description
|
105
|
+
return "Sample description\n"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Define the standard version flag.
|
109
|
+
def define_version_flag
|
110
|
+
version_number = version
|
111
|
+
@options.on("-v", "--version", "Print the version number (#{version_number}) and exit.") do
|
112
|
+
puts("#{$0}: Version: #{version_number}")
|
113
|
+
exit(0)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Define a flag redirecting one of the standard IO files.
|
118
|
+
def define_redirect_flag(variable, name, mode)
|
119
|
+
@options.on("-#{name[0,1]}", "--#{name} FILE", String, "Redirect standard #{name} to a file.") do |file|
|
120
|
+
eval("#{variable} = Application::redirect_file(#{variable}, file, mode)")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Redirect a standard file.
|
125
|
+
def self.redirect_file(default, file, mode)
|
126
|
+
return default if file.nil? || file == "-"
|
127
|
+
FileUtils.mkdir_p(File.dirname(File.expand_path(file))) if mode == "w"
|
128
|
+
return File.open(file, mode)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return the application's version. This is expected to be overriden by the
|
132
|
+
# concrete application sub-class. In the base class, we just return Olag's
|
133
|
+
# version which only useful for Olag's tests.
|
134
|
+
def version
|
135
|
+
return Olag::VERSION
|
136
|
+
end
|
137
|
+
|
138
|
+
# Print all the collected errors.
|
139
|
+
def print_errors
|
140
|
+
@errors.each do |error|
|
141
|
+
$stderr.puts(error)
|
142
|
+
end
|
143
|
+
return @errors.size
|
144
|
+
end
|
145
|
+
|
146
|
+
# Exit the application, unless we are running inside a test.
|
147
|
+
def exit(status)
|
148
|
+
Kernel.exit(status) unless @is_test
|
149
|
+
raise ExitException.new(status)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
# Exception used to exit when running inside tests.
|
155
|
+
class ExitException < Exception
|
156
|
+
|
157
|
+
# The exit status.
|
158
|
+
attr_reader :status
|
159
|
+
|
160
|
+
# Create a new exception to indicate exiting the program with some status.
|
161
|
+
def initialize(status)
|
162
|
+
@status = status
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|