methadone 1.0.0.rc4 → 1.0.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -70,7 +70,7 @@ Basically, this sets you up with all the boilerplate that you *should* be using
70
70
 
71
71
  == DSL for your <tt>bin</tt> file
72
72
 
73
- A canonical <tt>OptionParser</tt> driven app has a few problems with it structurally that methadone can solve
73
+ A canonical <tt>OptionParser</tt> driven app has a few problems with it structurally that Methadone can solve:
74
74
 
75
75
  * Backwards organization - main logic is at the bottom of the file, not the top
76
76
  * Verbose to use +opts.on+ just to set a value in a +Hash+
data/bin/methadone CHANGED
@@ -60,7 +60,7 @@ on("--force","Overwrite files if they exist")
60
60
  on("--[no-]readme","[Do not ]produce a README file")
61
61
 
62
62
  licenses = %w(mit apache custom NONE)
63
- on("-l LICENSE","--license",licenses,"Specify the license for your project (#{licenses.join('|')})")
63
+ on("-l LICENSE","--license",licenses,"Specify the license for your project",'(' + licenses.join('|') + ')')
64
64
 
65
65
  use_log_level_option
66
66
 
@@ -68,8 +68,5 @@ arg :app_name, :required, "Name of your app, which is used for the gem name and
68
68
 
69
69
  version Methadone::VERSION
70
70
 
71
- defaults_from_env_var 'METHODONE_OPTS'
72
- defaults_from_config_file '.methadone.rc'
73
-
74
71
  go!
75
72
 
@@ -84,7 +84,9 @@ module Methadone
84
84
  # go!
85
85
  #
86
86
  def use_log_level_option
87
- on("--log-level LEVEL",LOG_LEVELS,"Set the logging level (#{LOG_LEVELS.keys.join('|')})","(Default: info)") do |level|
87
+ on("--log-level LEVEL",LOG_LEVELS,'Set the logging level',
88
+ '(' + LOG_LEVELS.keys.join('|') + ')',
89
+ '(Default: info)') do |level|
88
90
  @log_level = level
89
91
  logger.level = level
90
92
  end
@@ -43,7 +43,10 @@ module Methadone
43
43
  #
44
44
  # arg :needed
45
45
  # arg :maybe, :optional
46
- #
46
+ #
47
+ # defaults_from_env_var SOME_VAR
48
+ # defaults_from_config_file '.my_app.rc'
49
+ #
47
50
  # go!
48
51
  # end
49
52
  #
@@ -53,6 +56,12 @@ module Methadone
53
56
  # # => parse error: 'needed' is required
54
57
  # $ our_app foo
55
58
  # # => succeeds; "maybe" in main is nil
59
+ # $ our_app --flag foo
60
+ # # => options[:flag] has the value "foo"
61
+ # $ SOME_VAR='--flag foo' our_app
62
+ # # => options[:flag] has the value "foo"
63
+ # $ SOME_VAR='--flag foo' our_app --flag bar
64
+ # # => options[:flag] has the value "bar"
56
65
  #
57
66
  # Note that we've done all of this inside a class that we called +App+. This isn't strictly
58
67
  # necessary, and you can just +include+ Methadone::Main and Methadone::CLILogging at the root
@@ -130,22 +139,6 @@ module Methadone
130
139
  @rc_file = File.join(ENV['HOME'],filename)
131
140
  end
132
141
 
133
- def add_defaults_to_docs
134
- if @env_var && @rc_file
135
- opts.separator ''
136
- opts.separator 'Default values can be placed in:'
137
- opts.separator ''
138
- opts.separator " #{@env_var} environment variable, as a String of options"
139
- opts.separator " #{@rc_file} with contents either a String of options or a YAML-encoded Hash"
140
- elsif @env_var
141
- opts.separator ''
142
- opts.separator "Default values can be placed in the #{@env_var} environment variable"
143
- elsif @rc_file
144
- opts.separator ''
145
- opts.separator "Default values can be placed in #{@rc_file}"
146
- end
147
- end
148
-
149
142
  # Start your command-line app, exiting appropriately when
150
143
  # complete.
151
144
  #
@@ -158,17 +151,9 @@ module Methadone
158
151
  #
159
152
  # If a required argument (see #arg) is not found, this exits with
160
153
  # 64 and a message about that missing argument.
161
- #
162
154
  def go!
163
- add_defaults_to_docs
164
- set_defaults_from_rc_file
165
- normalize_defaults
155
+ setup_defaults
166
156
  opts.post_setup
167
- if @env_var
168
- String(ENV[@env_var]).split(/\s+/).each do |arg|
169
- ::ARGV.unshift(arg)
170
- end
171
- end
172
157
  opts.parse!
173
158
  opts.check_args!
174
159
  result = call_main
@@ -282,6 +267,39 @@ module Methadone
282
267
 
283
268
  private
284
269
 
270
+ def setup_defaults
271
+ add_defaults_to_docs
272
+ set_defaults_from_rc_file
273
+ normalize_defaults
274
+ set_defaults_from_env_var
275
+ end
276
+
277
+ def add_defaults_to_docs
278
+ if @env_var && @rc_file
279
+ opts.separator ''
280
+ opts.separator 'Default values can be placed in:'
281
+ opts.separator ''
282
+ opts.separator " #{@env_var} environment variable, as a String of options"
283
+ opts.separator " #{@rc_file} with contents either a String of options "
284
+ spaces = (0..@rc_file.length).reduce('') { |a,_| a << ' ' }
285
+ opts.separator " #{spaces}or a YAML-encoded Hash"
286
+ elsif @env_var
287
+ opts.separator ''
288
+ opts.separator "Default values can be placed in the #{@env_var} environment variable"
289
+ elsif @rc_file
290
+ opts.separator ''
291
+ opts.separator "Default values can be placed in #{@rc_file}"
292
+ end
293
+ end
294
+
295
+ def set_defaults_from_env_var
296
+ if @env_var
297
+ String(ENV[@env_var]).split(/\s+/).each do |arg|
298
+ ::ARGV.unshift(arg)
299
+ end
300
+ end
301
+ end
302
+
285
303
  def set_defaults_from_rc_file
286
304
  if @rc_file && File.exists?(@rc_file)
287
305
  File.open(@rc_file) do |file|
@@ -1,3 +1,3 @@
1
1
  module Methadone
2
- VERSION = "1.0.0.rc4"
2
+ VERSION = "1.0.0.rc5"
3
3
  end
@@ -17,7 +17,7 @@ Installing ri documentation for methadone-1.0.0...
17
17
  Installing RDoc documentation for methadone-1.0.0...
18
18
  ```
19
19
 
20
- Methadone comes bundled with a command-linen app that will do the bootstrapping:
20
+ Methadone comes bundled with a command-line app that will do the bootstrapping:
21
21
 
22
22
  ```sh
23
23
  $ methadone --help
@@ -120,9 +120,7 @@ from bin/fullstop:5:in `<main>'
120
120
 
121
121
  Oops! What happened?
122
122
 
123
- Methadone is encouraging you to develop your app with best practices, and one such practice is to not have
124
- your executables mess with the load path. In many Ruby command-line applications, you'll see code like this at the top of the
125
- file:
123
+ Methadone is encouraging you to develop your app with best practices, and one such practice is to not have your executables mess with the load path. In many Ruby command-line applications, you'll see code like this at the top of the file:
126
124
 
127
125
  ```ruby
128
126
  $: << File.join(File.dirname(__FILE__),'..','lib')
data/tutorial/3_ui.md CHANGED
@@ -1,4 +1,4 @@
1
- # UI
1
+ # Tutorial: UI
2
2
 
3
3
  We're taking an [outside-in][outsidein] approach to our app. This means that we start with the user interface, and work our way
4
4
  down to make that interface a reality. I highly recommend this approach, since it forces you to focus on the user of your app
@@ -87,18 +87,11 @@ Tasks: TOP => features
87
87
  (See full trace by running task with --trace)
88
88
  ```
89
89
 
90
- We have a failing test! Note that cucumber knows about all of these steps; between Aruba and Metahdone, they are all already
91
- defined. We'll see some custom steps later, that are specific to our app, but for now, we haven't had to write any testing code,
92
- which is great!
90
+ We have a failing test! Note that cucumber knows about all of these steps; between Aruba and Metahdone, they are all already defined. We'll see some custom steps later, that are specific to our app, but for now, we haven't had to write any testing code, which is great!
93
91
 
94
- You'll also notice that some steps are already passing, despite the fact that we've done no coding. Also notice that
95
- cucumber didn't complain about unknown steps. Methadone provides almost all of these cucumber steps for us. The rest are
96
- provided by Aruba. Since Methadone generated an executable for us when we ran the `methadone` command, it already provides the
97
- ability to get help, and exits with the correct exit status.
92
+ You'll also notice that some steps are already passing, despite the fact that we've done no coding. Also notice that cucumber didn't complain about unknown steps. Methadone provides almost all of these cucumber steps for us. The rest are provided by Aruba. Since Methadone generated an executable for us when we ran the `methadone` command, it already provides the ability to get help, and exits with the correct exit status.
98
93
 
99
- Let's fix things one step at a time, so we can see exactly what we need to do. The current scenario is failing because our app
100
- doesn't have a one line summary. This summary is important so that we can remember what the app does later on (despite how
101
- clever our name is, it's likely we'll forget a few months from now and the description will jog our memory).
94
+ Let's fix things one step at a time, so we can see exactly what we need to do. The current scenario is failing because our app doesn't have a one line summary. This summary is important so that we can remember what the app does later on (despite how clever our name is, it's likely we'll forget a few months from now and the description will jog our memory).
102
95
 
103
96
  Let's have a look at our executable. A Methadone app is made up of four parts: the setup where we require necessary libraries, a "main" block containing the primary logic of our code, a block of code that declares the app's UI, and a call to `go!`, which runs our app.
104
97
 
@@ -230,9 +223,7 @@ We got farther this time. Our step for checking that we have a one-line summary
230
223
 
231
224
  The call to Methadone's `version` method ensures that the version of our app appears in the online help. The other step, "And the banner should document that this app takes options" passes because we are allowing Methadone to manage the banner. Methadone knows that our app takes options (namely `--version`), and inserts the string `"[options]"` into the usage statement.
232
225
 
233
- The last step in our scenario is still failing, so let's fix that to finish up our user interface.
234
- What Methadone is looking for is for the string `repo_url` (the name of our only,
235
- required, argument) to be in the usage string, in other words, Methadone is expecting to see this:
226
+ The last step in our scenario is still failing, so let's fix that to finish up our user interface. What Methadone is looking for is for the string `repo_url` (the name of our only, required, argument) to be in the usage string, in other words, Methadone is expecting to see this:
236
227
 
237
228
  ```
238
229
  Usage: fullstop [option] repo_url
@@ -244,9 +235,7 @@ Right now, our app's usage string looks like this:
244
235
  Usage: fullstop [option]
245
236
  ```
246
237
 
247
- Again, if we were using `OptionParser`, we would need to modify the argument given to `banner` to include this string. Methadone
248
- provides a method, `arg` that will do this automatically for us. We'll add it right after the call to `description` in the
249
- "declare UI" section of our app:
238
+ Again, if we were using `OptionParser`, we would need to modify the argument given to `banner` to include this string. Methadone provides a method, `arg` that will do this automatically for us. We'll add it right after the call to `description` in the "declare UI" section of our app:
250
239
 
251
240
  ```ruby
252
241
  #!/usr/bin/env ruby
@@ -340,9 +329,7 @@ $ echo $?
340
329
 
341
330
  We see an error message, and exited nonzero (64 is a somewhat standard exit code for errors in command-line invocation).
342
331
 
343
- It's also worth pointing out that Methadone is taking a very light touch. We could completely re-implement `bin/fullstop` using
344
- `OptionParser` and still have our scenario pass. As we'll see, few of Methadone's parts really rely on each other, and many can
345
- be used piecemeal, if that's what you want.
332
+ It's also worth pointing out that Methadone is taking a very light touch. We could completely re-implement `bin/fullstop` using `OptionParser` and still have our scenario pass. As we'll see, few of Methadone's parts really rely on each other, and many can be used piecemeal, if that's what you want.
346
333
 
347
334
  Now that we have our UI, the next order of business is to actually implement something.
348
335
 
@@ -14,19 +14,13 @@ We'll append this to `features/fullstop.feature`:
14
14
  And the files in "~/dotfiles" should be symlinked in my home directory
15
15
  ```
16
16
 
17
- Basically, what we're doing is assuming a git repository in `/tmp/dotfiles.git`, which we then expect `fullstop` to clone,
18
- followed by symlinking the contents to our home directory. There is, however, a slight problem.
17
+ Basically, what we're doing is assuming a git repository in `/tmp/dotfiles.git`, which we then expect `fullstop` to clone, followed by symlinking the contents to our home directory. There is, however, a slight problem.
19
18
 
20
- Suppose we make this scenario pass. This means that *every* time we run this scenario, our dotfiles in our *actual* home
21
- directory will be blown away. Yikes! We don't want that; we want our test as isolated as it can be. What we'd like is to work
22
- in a home directory that, from the perspective of our cucumber tests, is not our home directory and completely under the conrol
23
- of the tests but, from the perspective of the `fullstop` app, is the user's bona-fide home directory.
19
+ Suppose we make this scenario pass. This means that *every* time we run this scenario, our dotfiles in our *actual* home directory will be blown away. Yikes! We don't want that; we want our test as isolated as it can be. What we'd like is to work in a home directory that, from the perspective of our cucumber tests, is not our home directory and completely under the conrol of the tests but, from the perspective of the `fullstop` app, is the user's bona-fide home directory.
24
20
 
25
- We can easily fake this by changing the environment variable `$HOME` just for the tests. As long as `bin/fullstop` uses this
26
- environment variable to access the user's home directory (which is perfectly valid), everything will be OK.
21
+ We can easily fake this by changing the environment variable `$HOME` just for the tests. As long as `bin/fullstop` uses this environment variable to access the user's home directory (which is perfectly valid), everything will be OK.
27
22
 
28
- To do that, we need to modify some of cucumber's plumbing. Methadone won't do this for you, since it's not applicable to every
29
- situation or app. Open up `features/support/env.rb`. It should look like this:
23
+ To do that, we need to modify some of cucumber's plumbing. Methadone won't do this for you, since it's not applicable to every situation or app. Open up `features/support/env.rb`. It should look like this:
30
24
 
31
25
  ```ruby
32
26
  require 'aruba/cucumber'
@@ -47,10 +41,7 @@ After do
47
41
  end
48
42
  ```
49
43
 
50
- There's a lot in there already to make our tests work and, fortunately, it makes our job of faking the home directory a bit
51
- easier. We need to save the original location in `Before`, and then change it there, setting it back to normal in `After`, just
52
- as we have done with the `$RUBYLIB` environment variable (incidentally, this is how Aruba can run our app without using `bundle
53
- exec`).
44
+ There's a lot in there already to make our tests work and, fortunately, it makes our job of faking the home directory a bit easier. We need to save the original location in `Before`, and then change it there, setting it back to normal in `After`, just as we have done with the `$RUBYLIB` environment variable (incidentally, this is how Aruba can run our app without using `bundle exec`).
54
45
 
55
46
  ```ruby
56
47
  require 'aruba/cucumber'
@@ -128,10 +119,7 @@ Then /^the files in "([^"]*)" should be symlinked in my home directory$/ do |arg
128
119
  end
129
120
  ```
130
121
 
131
- As you can see there are three steps that cucumber doesn't know how to execute. It provides boilerplate for doing so, so let's
132
- do that next. We're going to move a bit faster here, since the specifics of implementing cucumber steps is orthogonal to
133
- Methadone, and we don't want to stray too far from our goal of learning Methadone. If you'd like
134
- to explore this in more detail, check out the testing chapter of [my book][clibook].
122
+ As you can see there are three steps that cucumber doesn't know how to execute. It provides boilerplate for doing so, so let's do that next. We're going to move a bit faster here, since the specifics of implementing cucumber steps is orthogonal to Methadone, and we don't want to stray too far from our goal of learning Methadone. If you'd like to explore this in more detail, check out the testing chapter of [my book][clibook].
135
123
 
136
124
  [clibook]: http://www.awesomecommandlineapps.com
137
125
 
@@ -239,31 +227,16 @@ It's a bit hard to understand *why* it's failing, but the error message and line
239
227
  File.exist?(dotfiles_dir).should == true
240
228
  ```
241
229
 
242
- Since `dotfiles_dir` is `~/dotfiles` (or, more specifically, `File.join(ENV['HOME'],'dotfiles')`), and it doesn't exist, since we
243
- haven't written any code that might cause it to exist, the test fails. Although it's outside the scope of this tutorial, you
244
- should consider writing some custom RSpec matchers for your assertions, since they can allow you to produce better failure
245
- messages.
246
-
247
- Now that we have a failing test, we can start writing some code. This is the first bit of actual logic we'll write, and we need
248
- to revisit the canonical structure of a Methadone app to know where to put it.
249
-
230
+ Since `dotfiles_dir` is `~/dotfiles` (or, more specifically, `File.join(ENV['HOME'],'dotfiles')`), and it doesn't exist, since we haven't written any code that might cause it to exist, the test fails. Although it's outside the scope of this tutorial, you should consider writing some custom RSpec matchers for your assertions, since they can allow you to produce better failure messages.
231
+ Now that we have a failing test, we can start writing some code. This is the first bit of actual logic we'll write, and we need to revisit the canonical structure of a Methadone app to know where to put it.
250
232
  Recall that the second part of our app is the "main" block, and it's intended to hold the primary logic of your application. Methadone provides the method `main`, which lives in `Methadone::Main`, and takes a block. This block is where you put your logic. Think of it like the `main` method of a C program.
251
-
252
- Now that we know where to put our code, we need to know *what* code we need to add. To make this step pass, we need to clone the
253
- repo given to us on the command-line. To do that we need:
254
-
233
+ Now that we know where to put our code, we need to know *what* code we need to add. To make this step pass, we need to clone the repo given to us on the command-line. To do that we need:
255
234
  * The ability to execute `git`
256
235
  * The ability to change to the user's home directory
257
236
  * Access to the repo's URL from the command line
237
+ Although we can use `system` or the backtick operator to call `git`, we're going to use `sh`, which is available by mixing in `Methadone::SH`. We'll go into the advantages of why we might want to do that later in the tutorial, but for now, think of it as saving us a few characters over `system`.
258
238
 
259
- Although we can use `system` or the backtick operator to call `git`, we're going to use `sh`, which is available by mixing in
260
- `Methadone::SH`. We'll go into the advantages of why we might want to do that later in the tutorial, but for now, think of it as
261
- saving us a few characters over `system`.
262
-
263
- We can change to the user's home directory using the `chdir` method of `Dir`, which is built-in to Ruby. To get the value of the
264
- URL the user provided on the command-line, we could certainly take it from `ARGV`, but Methadone allows you `main` block to take
265
- arguments, which it will populate with the contents of `ARGV`. All we need to do is change our `main` block to accept `repo_url`
266
- as an argument.
239
+ We can change to the user's home directory using the `chdir` method of `Dir`, which is built-in to Ruby. To get the value of the URL the user provided on the command-line, we could certainly take it from `ARGV`, but Methadone allows you `main` block to take arguments, which it will populate with the contents of `ARGV`. All we need to do is change our `main` block to accept `repo_url` as an argument.
267
240
 
268
241
  Here's the code:
269
242
 
@@ -304,8 +277,7 @@ class App
304
277
  end
305
278
  ```
306
279
 
307
- Note that all we're doing here is getting the currently-failing step to pass. We *aren't* implementing the entire app. We want
308
- to write only the code we need to, and we go one step at a time. Let's re-run our scenario and see if we get farther:
280
+ Note that all we're doing here is getting the currently-failing step to pass. We *aren't* implementing the entire app. We want to write only the code we need to, and we go one step at a time. Let's re-run our scenario and see if we get farther:
309
281
 
310
282
  ```sh
311
283
  rake features
@@ -357,10 +329,7 @@ We're now failing at the next step:
357
329
  And the files in "~/dotfiles" should be symlinked in my home directory
358
330
  ```
359
331
 
360
- The error, "No such file or directory - .vimrc", is being raised from `File.lstat` (as opposed to an explicit test failure).
361
- This is enough to allow us to write some more code. What we need to do know is iterate over the files in the cloned repo and
362
- symlink them to the user's home directory. The tools to do this are already available to use via the built-in Ruby library
363
- `FileUtils`. We'll require it and implement the symlinking logic:
332
+ The error, "No such file or directory - .vimrc", is being raised from `File.lstat` (as opposed to an explicit test failure). This is enough to allow us to write some more code. What we need to do know is iterate over the files in the cloned repo and symlink them to the user's home directory. The tools to do this are already available to use via the built-in Ruby library `FileUtils`. We'll require it and implement the symlinking logic:
364
333
 
365
334
  ```ruby
366
335
  #!/usr/bin/env ruby
@@ -433,5 +402,4 @@ Feature: Checkout dotfiles
433
402
  0m0.396s
434
403
  ```
435
404
 
436
- Everything passed! Our app now works for the "happy path". As long as the user starts from a clean home directory, `fullstop`
437
- will clone their dotfiles, and setup symlinks to them in their home directory. Now that we have the basics of our app running, we'll see how Methadone makes it easy to add new features.
405
+ Everything passed! Our app now works for the "happy path". As long as the user starts from a clean home directory, `fullstop` will clone their dotfiles, and setup symlinks to them in their home directory. Now that we have the basics of our app running, we'll see how Methadone makes it easy to add new features.
@@ -1,8 +1,9 @@
1
1
  # Adding Features
2
2
 
3
3
  Our command-line app isn't very interesting at this point; it's more of a glorified shell script. Where Ruby and Methadone
4
- really shine is when things start getting complex. There's a lot of features we can add and error cases we can handle, for
5
- example:
4
+ really shine is when things start getting complex.
5
+
6
+ There's a lot of features we can add and error cases we can handle, for example:
6
7
 
7
8
  * The app will blow up if the git repo is already cloned
8
9
  * The app might blow up if the files are already symlinked
@@ -180,7 +181,8 @@ on("--force","Force overwriting of existing files")
180
181
  on("-d DIR","--checkout-dir","Set the location of the checkout dir")
181
182
  ```
182
183
 
183
- That's it! 14 lines become 3. When `main` executes, the following keys in `options` will be available:
184
+ That's it! 14 lines become 3.
185
+ When `main` executes, the following keys in `options` will be available:
184
186
 
185
187
  * `"force"` - true if the user specified `--force`
186
188
  * `:force` - the same
@@ -375,9 +377,7 @@ Tasks: TOP => features
375
377
  (See full trace by running task with --trace)
376
378
  ```
377
379
 
378
- We're failing because the new file we added to our repo after the initial clone can't be found. It's likely that our second
379
- clone failed, but we didn't notice, because we aren't checking. If we run our app manually, we can see that errors are flying,
380
- but we're ignoring them:
380
+ We're failing because the new file we added to our repo after the initial clone can't be found. It's likely that our second clone failed, but we didn't notice, because we aren't checking. If we run our app manually, we can see that errors are flying, but we're ignoring them:
381
381
 
382
382
  ```sh
383
383
  $ HOME=/tmp/fake-home bundle exec bin/fullstop file:///tmp/dotfiles.git
@@ -390,10 +390,7 @@ $ echo $?
390
390
  70
391
391
  ```
392
392
 
393
- We can see that error output is being produced from `git`, but we're ignoring it. `fullstop` fails later in the process when we
394
- ask it to symlink files that already exist. This is actually a bug, so let's take a short detour and fix
395
- this problem. When doing TDD, it's important to know how your app is failing, so you can be confident that the code you are
396
- about to write fixes the correct failing in the existing app.
393
+ We can see that error output is being produced from `git`, but we're ignoring it. `fullstop` fails later in the process when we ask it to symlink files that already exist. This is actually a bug, so let's take a short detour and fix this problem. When doing TDD, it's important to know how your app is failing, so you can be confident that the code you are about to write fixes the correct failing in the existing app.
397
394
 
398
395
  We'll write a scenario to reveal the bug:
399
396
 
@@ -465,9 +462,7 @@ Tasks: TOP => features
465
462
  (See full trace by running task with --trace)
466
463
  ```
467
464
 
468
- It looks like `fullstop` is writing log messages. It is, and we'll talk about that more later, but right now, we need to focus
469
- on the fact that we aren't producing the error message we expect. Let's modify `bin/fullstop` to check that the call to `git`
470
- succeeded. `sh` returns the exit status of the command it calls, so we can use that to fix things.
465
+ It looks like `fullstop` is writing log messages. It is, and we'll talk about that more later, but right now, we need to focus on the fact that we aren't producing the error message we expect. Let's modify `bin/fullstop` to check that the call to `git` succeeded. `sh` returns the exit status of the command it calls, so we can use that to fix things.
471
466
 
472
467
  Here's the changes we'll make to `bin/fullstop` to check for this:
473
468
 
@@ -566,10 +561,7 @@ Feature: Checkout dotfiles
566
561
  0m0.789s
567
562
  ```
568
563
 
569
- Note that there is a companion method to `sh`, called `sh!` that will throw an exception if the underlying command it calls
570
- fails. In a Methadone app, any unhandled exception will trigger a nonzero exit from the app, and show the user the message of
571
- the exception that caused the exit. We can customize the message of the exception thrown from `sh!`, and thus our change
572
- to our app could also be implemented like so:
564
+ Note that there is a companion method to `sh`, called `sh!` that will throw an exception if the underlying command it calls fails. In a Methadone app, any unhandled exception will trigger a nonzero exit from the app, and show the user the message of the exception that caused the exit. We can customize the message of the exception thrown from `sh!`, and thus our change to our app could also be implemented like so:
573
565
 
574
566
  ```ruby
575
567
  main do |repo_url|
@@ -591,8 +583,7 @@ Which method to use is purely stylistic and up to you.
591
583
 
592
584
  NOW, we can get back to the `--force` flag. We're going to change our scenario a bit, as well. Instead of using "When I run `fullstop --force file:///tmp/dotfiles.git`" we'll use "When I successfully run `fullstop --force file:///tmp/dotfiles.git`", which will fail if the app exits nonzero. This will cause our scenario to fail earlier.
593
585
 
594
- To fix this, we'll change the code in `bin/fullstop` so that if the user specified `--force`, we'll delete the directory before we
595
- clone. We'll also need to delete the files that were symlinked in the home directory as well.
586
+ To fix this, we'll change the code in `bin/fullstop` so that if the user specified `--force`, we'll delete the directory before we clone. We'll also need to delete the files that were symlinked in the home directory as well.
596
587
 
597
588
  ```ruby
598
589
  #!/usr/bin/env ruby
@@ -1,8 +1,6 @@
1
1
  # Logging & Debugging
2
2
 
3
- By now, you've got the basics of using Methadone, but there's a few things happening under the covers that you should know about,
4
- and a few things built-in to the methods and modules we've been using that will be helpful both in developing your app and in
5
- examining its behavior in production.
3
+ By now, you've got the basics of using Methadone, but there's a few things happening under the covers that you should know about, and a few things built-in to the methods and modules we've been using that will be helpful both in developing your app and in examining its behavior in production.
6
4
 
7
5
  When trying to figure out what's going on with our apps, be it in development or producton, we often first turn to `puts`
8
6
  statements, like so:
@@ -16,9 +14,7 @@ else
16
14
  end
17
15
  ```
18
16
 
19
- Because of the way `system` or the backtick operator work, this sort of debugging isn't terribly helpful. It's also hard to turn
20
- off: you either delete the lines (possibly adding them back later when things go wrong again), or comment them out, which
21
- leads to hard-to-follow code and potentially misleading messages.
17
+ Because of the way `system` or the backtick operator work, this sort of debugging isn't terribly helpful. It's also hard to turn off: you either delete the lines (possibly adding them back later when things go wrong again), or comment them out, which leads to hard-to-follow code and potentially misleading messages.
22
18
 
23
19
  Instead, you should use logging, and Methadone bakes logging right in.
24
20
 
@@ -40,29 +36,19 @@ else
40
36
  end
41
37
  ```
42
38
 
43
- At runtime, you can change the log level, meaning you can hide the `debug` statement without changing your code. You may have
44
- noticed in our tutorial app, `fullstop`, that the flag `--log-level` was shown as an option. The method `use_log_level_option`
45
- enables this flag. This means that you don't have to do *anything additional* to get full control over your logging.
39
+ At runtime, you can change the log level, meaning you can hide the `debug` statement without changing your code. You may have noticed in our tutorial app, `fullstop`, that the flag `--log-level` was shown as an option. The method `use_log_level_option` enables this flag. This means that you don't have to do *anything additional* to get full control over your logging.
46
40
 
47
- Methadone goes beyond this, however, and makes heavy use of the logger in the `Methadone::SH` module. This module assumes that
48
- `Methadone::CLILogging` is mixed in (or, more specifically, assumes a method `logger` which returns a `Logger`), and all
49
- interaction with external commands via `sh` is logged in a useful and appropriate manner.
41
+ Methadone goes beyond this, however, and makes heavy use of the logger in the `Methadone::SH` module. This module assumes that `Methadone::CLILogging` is mixed in (or, more specifically, assumes a method `logger` which returns a `Logger`), and all interaction with external commands via `sh` is logged in a useful and appropriate manner.
50
42
 
51
43
  By default, `sh` will log the full command it executes at debug level. It will also capture the standard output and standard error of the commands you run and examine the exit code.
52
44
 
53
- Any output to the standard error device is logged as a warning; error output from commands you call is important and should be
54
- examined.
45
+ Any output to the standard error device is logged as a warning; error output from commands you call is important and should be examined.
55
46
 
56
47
  If the exit code of the command is zero, the standard output is logged at debug level, otherwise it will be logged at info level.
57
48
 
58
- What this means is that you can dial up logging to debug level in production to see everything your app is doing, but can
59
- generally keep the log level higher, to reduce log noise. This is a powerful tool for debugging your apps, and it doesn't
60
- require any code changes.
49
+ What this means is that you can dial up logging to debug level in production to see everything your app is doing, but can generally keep the log level higher, to reduce log noise. This is a powerful tool for debugging your apps, and it doesn't require any code changes.
61
50
 
62
- Let's enhance `bin/fullstop` to log more things, and examine what's going on. First, we'll add an info message to our executable
63
- that indicates that everything worked. Generally, you don't want to add noisy messages like this (see [my book][clibook] for a
64
- deeper discussion as to why), however for demonstration purposes, it should be OK. Here's just the `main` block with our
65
- additional logging:
51
+ Let's enhance `bin/fullstop` to log more things, and examine what's going on. First, we'll add an info message to our executable that indicates that everything worked. Generally, you don't want to add noisy messages like this (see [my book][clibook] for a deeper discussion as to why), however for demonstration purposes, it should be OK. Here's just the `main` block with our additional logging:
66
52
 
67
53
  ```ruby
68
54
  main do |repo_url|
@@ -78,8 +64,7 @@ main do |repo_url|
78
64
  end
79
65
  ```
80
66
 
81
- We'll also add some debug logging to `Repo`. This can be useful since we're doing some filename manipulation with regular
82
- expressions and it might help to see what's going on if we encouter an odd bug:
67
+ We'll also add some debug logging to `Repo`. This can be useful since we're doing some filename manipulation with regular expressions and it might help to see what's going on if we encouter an odd bug:
83
68
 
84
69
  ```ruby
85
70
  module Fullstop
@@ -129,8 +114,7 @@ $ HOME=/tmp/fake-home bundle exec bin/fullstop file:///tmp/dotfiles.git
129
114
  Dotfiles symlinked
130
115
  ```
131
116
 
132
- As we can see, things went normally and we saw just our info message. By default, the Methadone logger is set at info level.
133
- Let's try it again at debug level:
117
+ As we can see, things went normally and we saw just our info message. By default, the Methadone logger is set at info level. Let's try it again at debug level:
134
118
 
135
119
  ```sh
136
120
  $ rm -rf /tmp/fake-home ; mkdir /tmp/fake-home/
@@ -160,9 +144,7 @@ D, [2012-02-13T21:11:05.950866 #49986] DEBUG -- : Yielding .inputrc
160
144
  D, [2012-02-13T21:11:05.950968 #49986] DEBUG -- : Yielding .vimrc
161
145
  I, [2012-02-13T21:11:05.951086 #49986] INFO -- : Dotfiles symlinked
162
146
  ```
163
- The format has changed. Methadone reasons that if you are showing output to a terminal TTY, the user will not need or want to
164
- see the logging level of each message nor the timestamp. However, if the user has redirected the output to a file, this
165
- information becomes much more useful.
147
+ The format has changed. Methadone reasons that if you are showing output to a terminal TTY, the user will not need or want to see the logging level of each message nor the timestamp. However, if the user has redirected the output to a file, this information becomes much more useful.
166
148
 
167
149
  Now, let's run the app again, but without "resetting" our fake home directory in `/tmp/fake-home`.
168
150
 
@@ -196,18 +178,13 @@ messages to potentially many places. How does this work?
196
178
 
197
179
  ## Methadone's Special Logger
198
180
 
199
- The logger used by default in `Methadone::CLILogging` is a `Methadone::CLILogger`. This
200
- is a special logger designed for command-line apps. By default, any message logged at warn or higher will go to the standard
201
- error stream. Messages logged at info and debug will go to the standard output stream. This allows you to fluently communicate
202
- things to the user and have them go to the appropriate place.
181
+ The logger used by default in `Methadone::CLILogging` is a `Methadone::CLILogger`. This is a special logger designed for command-line apps. By default, any message logged at warn or higher will go to the standard error stream. Messages logged at info and debug will go to the standard output stream. This allows you to fluently communicate things to the user and have them go to the appropriate place.
203
182
 
204
- Further, when your app is run at a terminal, these messages are unformatted. When your apps output is redirected somewhere, the
205
- messages are formatted with date and time stamps, as you'd expect in a log.
183
+ Further, when your app is run at a terminal, these messages are unformatted. When your apps output is redirected somewhere, the messages are formatted with date and time stamps, as you'd expect in a log.
206
184
 
207
- Note that if you want a normal Ruby logger (or want to use the Rails logger in a Rails environment), you can still get the
208
- benefits of `Methadone::CLILogging` without being required to use the `Methadone::CLILogger`. I've used this to great affect to
209
- use the thread-safe [Log4r][log4r] logger in a JRuby app. Let's change `bin/fullstop` to use a plain Ruby Logger instead of
210
- Methadone's fancy logger.
185
+ Note that if you want a normal Ruby logger (or want to use the Rails logger in a Rails environment), you can still get the benefits of `Methadone::CLILogging` without being required to use the `Methadone::CLILogger`. I've used this to great affect to use the thread-safe [Log4r][log4r] logger in a JRuby app.
186
+
187
+ Let's change `bin/fullstop` to use a plain Ruby Logger instead of Methadone's fancy logger.
211
188
 
212
189
  We just need to change one line in `bin/fullstop`, to call `change_logger` inside our `main` block:
213
190
 
@@ -226,8 +203,7 @@ main do |repo_url|
226
203
  end
227
204
  ```
228
205
 
229
- All other files stay as they are. Now, let's re-run our app, first cleaning up the fake home directory, and then immediately
230
- running the app again to see errors.
206
+ All other files stay as they are. Now, let's re-run our app, first cleaning up the fake home directory, and then immediately running the app again to see errors.
231
207
 
232
208
  ```sh
233
209
  $ rm -rf /tmp/fake-home ; mkdir /tmp/fake-home/
@@ -259,19 +235,13 @@ output.
259
235
 
260
236
  ## Exceptions
261
237
 
262
- We've already seen the use of `exit_now!` to abort our app and show the user an error message. `exit_now!` is implemented to
263
- raise a `Methadone::Error`, but we could've just as easily raised a `StandardError` or `RuntimeError` ourselves. The result
264
- would be the same: Methadone would show the user just the error message and exit nonzero.
238
+ We've already seen the use of `exit_now!` to abort our app and show the user an error message. `exit_now!` is implemented to raise a `Methadone::Error`, but we could've just as easily raised a `StandardError` or `RuntimeError` ourselves. The result would be the same: Methadone would show the user just the error message and exit nonzero.
265
239
 
266
- Methadone traps all exceptions, so that users never see a backtrace. Generally, this is what you want, because it allows you to
267
- write your code without complex exit logic and you don't need to worry about a bad user experience by letting stack traces leak
268
- through to the output. In fact, the method `go!` that we've seen at the bottom of our executables handles this.
240
+ Methadone traps all exceptions, so that users never see a backtrace. Generally, this is what you want, because it allows you to write your code without complex exit logic and you don't need to worry about a bad user experience by letting stack traces leak through to the output. In fact, the method `go!` that we've seen at the bottom of our executables handles this.
269
241
 
270
- There are times, however, when you want to see these traces. When writing and debugging your app, the exception backtraces are
271
- crucial for identifying where things went wrong.
242
+ There are times, however, when you want to see these traces. When writing and debugging your app, the exception backtraces are crucial for identifying where things went wrong.
272
243
 
273
- All Methadone apps look for the environment variable `DEBUG` and, if it's set to "true", will show the stack trace on errors
274
- instead of hiding it. Let's see it work with `bin/fullstop`. We've restored it back to use a `Methadone::CLILogger`, and we can now see how `DEBUG` affects the output:
244
+ All Methadone apps look for the environment variable `DEBUG` and, if it's set to "true", will show the stack trace on errors instead of hiding it. Let's see it work with `bin/fullstop`. We've restored it back to use a `Methadone::CLILogger`, and we can now see how `DEBUG` affects the output:
275
245
 
276
246
  ```sh
277
247
  $ HOME=/tmp/fake-home bundle exec bin/fullstop --log-level=debug file:///tmp/dotfiles.git
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: methadone
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc4
4
+ version: 1.0.0.rc5
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-21 00:00:00.000000000Z
12
+ date: 2012-02-28 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
- requirement: &70117187014260 !ruby/object:Gem::Requirement
16
+ requirement: &70123273030540 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70117187014260
24
+ version_requirements: *70123273030540
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec-expectations
27
- requirement: &70117187013540 !ruby/object:Gem::Requirement
27
+ requirement: &70123273030040 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '2.6'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70117187013540
35
+ version_requirements: *70123273030040
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &70117187012900 !ruby/object:Gem::Requirement
38
+ requirement: &70123273029560 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70117187012900
46
+ version_requirements: *70123273029560
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rdoc
49
- requirement: &70117187012080 !ruby/object:Gem::Requirement
49
+ requirement: &70123273028900 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '3.9'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70117187012080
57
+ version_requirements: *70123273028900
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: cucumber
60
- requirement: &70117187011340 !ruby/object:Gem::Requirement
60
+ requirement: &70123273028320 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.1.1
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70117187011340
68
+ version_requirements: *70123273028320
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: aruba
71
- requirement: &70117187010760 !ruby/object:Gem::Requirement
71
+ requirement: &70123273027800 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70117187010760
79
+ version_requirements: *70123273027800
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: simplecov
82
- requirement: &70117187010060 !ruby/object:Gem::Requirement
82
+ requirement: &70123273027220 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0.5'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70117187010060
90
+ version_requirements: *70123273027220
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: clean_test
93
- requirement: &70117187009160 !ruby/object:Gem::Requirement
93
+ requirement: &70123273026680 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ~>
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0.10'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *70117187009160
101
+ version_requirements: *70123273026680
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: mocha
104
- requirement: &70117187008340 !ruby/object:Gem::Requirement
104
+ requirement: &70123273026220 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,7 +109,7 @@ dependencies:
109
109
  version: '0'
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *70117187008340
112
+ version_requirements: *70123273026220
113
113
  description: Methadone provides a lot of small but useful features for developing
114
114
  a command-line app, including an opinionated bootstrapping process, some helpful
115
115
  cucumber steps, and some classes to bridge logging and output into a simple, unified,