olag 0.1.10

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/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