olag 0.1.10

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