methadone 1.0.0.rc5 → 1.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/lib/methadone/cli.rb +2 -0
  2. data/lib/methadone/cli_logging.rb +3 -0
  3. data/lib/methadone/cucumber.rb +1 -1
  4. data/lib/methadone/error.rb +8 -1
  5. data/lib/methadone/execution_strategy/jvm.rb +2 -0
  6. data/lib/methadone/execution_strategy/mri.rb +2 -0
  7. data/lib/methadone/execution_strategy/open_3.rb +2 -0
  8. data/lib/methadone/execution_strategy/open_4.rb +2 -0
  9. data/lib/methadone/execution_strategy/rbx_open_4.rb +2 -0
  10. data/lib/methadone/exit_now.rb +18 -3
  11. data/lib/methadone/main.rb +34 -6
  12. data/lib/methadone/process_status.rb +45 -0
  13. data/lib/methadone/sh.rb +52 -29
  14. data/lib/methadone/version.rb +1 -1
  15. data/methadone.gemspec +10 -0
  16. data/test/test_main.rb +20 -0
  17. data/test/test_sh.rb +104 -3
  18. metadata +23 -47
  19. data/tutorial/.vimrc +0 -6
  20. data/tutorial/1_intro.md +0 -52
  21. data/tutorial/2_bootstrap.md +0 -174
  22. data/tutorial/3_ui.md +0 -336
  23. data/tutorial/4_happy_path.md +0 -405
  24. data/tutorial/5_more_features.md +0 -693
  25. data/tutorial/6_refactor.md +0 -220
  26. data/tutorial/7_logging_debugging.md +0 -274
  27. data/tutorial/8_conclusion.md +0 -11
  28. data/tutorial/code/.rvmrc +0 -1
  29. data/tutorial/code/fullstop/.gitignore +0 -5
  30. data/tutorial/code/fullstop/Gemfile +0 -4
  31. data/tutorial/code/fullstop/LICENSE.txt +0 -202
  32. data/tutorial/code/fullstop/README.rdoc +0 -23
  33. data/tutorial/code/fullstop/Rakefile +0 -31
  34. data/tutorial/code/fullstop/bin/fullstop +0 -43
  35. data/tutorial/code/fullstop/features/fullstop.feature +0 -40
  36. data/tutorial/code/fullstop/features/step_definitions/fullstop_steps.rb +0 -64
  37. data/tutorial/code/fullstop/features/support/env.rb +0 -22
  38. data/tutorial/code/fullstop/fullstop.gemspec +0 -28
  39. data/tutorial/code/fullstop/lib/fullstop.rb +0 -2
  40. data/tutorial/code/fullstop/lib/fullstop/repo.rb +0 -38
  41. data/tutorial/code/fullstop/lib/fullstop/version.rb +0 -3
  42. data/tutorial/code/fullstop/test/tc_something.rb +0 -7
  43. data/tutorial/en.utf-8.add +0 -18
  44. data/tutorial/toc.md +0 -27
@@ -1,220 +0,0 @@
1
- # Refactoring
2
-
3
- Refactoring is an important step in TDD, and a Methadone-powered app works just as well with the code all jumbled inside our
4
- executable as it would with things nicely organized in classes. Since we'll distribute our app with RubyGems, it will all work
5
- out at runtime. This means that there's no additional complexity to organizing our code into classes that live inside the `lib`
6
- directory.
7
-
8
- Currently, our `main` block looks like this:
9
-
10
- ```ruby
11
- main do |repo_url|
12
-
13
- Dir.chdir options['checkout-dir'] do
14
- basedir = repo_url.split(/\//)[-1].gsub(/\.git$/,'')
15
- if options[:force] && Dir.exists?(basedir)
16
- warn "deleting #{basedir} before cloning"
17
- FileUtils.rm_rf basedir
18
- end
19
- if sh("git clone #{repo_url}") == 0
20
- Dir.entries(basedir).each do |file|
21
- next if file == '.' || file == '..' || file == '.git'
22
- source_file = File.join(basedir,file)
23
- FileUtils.rm(file) if File.exists?(file) && options[:force]
24
- FileUtils.ln_s source_file,'.'
25
- end
26
- else
27
- exit_now!("checkout dir already exists, use --force to overwrite")
28
- end
29
- end
30
- end
31
- ```
32
-
33
- Let's use method extraction to clean this up before we worry about classes. This exercise will help us identify classes we can
34
- create later.
35
-
36
- ```ruby
37
- main do |repo_url|
38
- Dir.chdir options['checkout-dir'] do
39
- repo_dir = clone_repo(repo_url,options[:force])
40
- files_in(repo_dir) do |file|
41
- link_file(repo_dir,file,options[:force])
42
- end
43
- end
44
- end
45
-
46
- def self.link_file(repo_dir,file,overwrite)
47
- source_file = File.join(repo_dir,file)
48
- FileUtils.rm(file) if File.exists?(file) && overwrite
49
- FileUtils.ln_s source_file,'.'
50
- end
51
-
52
- def self.files_in(repo_dir)
53
- Dir.entries(repo_dir).each do |file|
54
- next if file == '.' || file == '..' || file == '.git'
55
- yield file
56
- end
57
- end
58
-
59
- def self.clone_repo(repo_url,force)
60
- repo_dir = repo_url.split(/\//)[-1].gsub(/\.git$/,'')
61
- if force && Dir.exists?(repo_dir)
62
- warn "deleting #{repo_dir} before cloning"
63
- FileUtils.rm_rf repo_dir
64
- end
65
- unless sh("git clone #{repo_url}") == 0
66
- exit_now!("checkout dir already exists, use --force to overwrite")
67
- end
68
- repo_dir
69
- end
70
- ```
71
-
72
- Our `main` block is now a lot clearer, and, although we have more code, each routine is much more concise and cohesive. Let's
73
- run our features to make sure nothing's broken.
74
-
75
- ```sh
76
- $ rake features
77
- Feature: Checkout dotfiles
78
- In order to get my dotfiles onto a new computer
79
- I want a one-command way to keep them up to date
80
- So I don't have to do it myself
81
-
82
- Scenario: Basic UI
83
- When I get help for "fullstop"
84
- Then the exit status should be 0
85
- And the banner should be present
86
- And there should be a one line summary of what the app does
87
- And the banner should include the version
88
- And the banner should document that this app takes options
89
- And the banner should document that this app's arguments are:
90
- | repo_url | which is required |
91
- And the following options should be documented:
92
- | --force |
93
- | --checkout-dir |
94
- | -d |
95
-
96
- Scenario: Happy Path
97
- Given a git repo with some dotfiles at "/tmp/dotfiles.git"
98
- When I successfully run `fullstop file:///tmp/dotfiles.git`
99
- Then the dotfiles should be checked out in the directory "~/dotfiles"
100
- And the files in "~/dotfiles" should be symlinked in my home directory
101
-
102
- Scenario: Fail if directory is cloned
103
- Given a git repo with some dotfiles at "/tmp/dotfiles.git"
104
- And I have my dotfiles cloned and symlinked to "~/dotfiles"
105
- And there's a new file in the git repo
106
- When I run `fullstop file:///tmp/dotfiles.git`
107
- Then the exit status should not be 0
108
- And the stderr should contain "checkout dir already exists, use --force to overwrite"
109
-
110
- Scenario: Force overwrite
111
- Given a git repo with some dotfiles at "/tmp/dotfiles.git"
112
- And I have my dotfiles cloned and symlinked to "~/dotfiles"
113
- And there's a new file in the git repo
114
- When I successfully run `fullstop --force file:///tmp/dotfiles.git`
115
- Then the dotfiles in "~/dotfiles" should be re-cloned
116
- And the files in "~/dotfiles" should be symlinked in my home directory
117
-
118
- 4 scenarios (4 passed)
119
- 24 steps (24 passed)
120
- 0m1.277s
121
- ```
122
-
123
- Everything's still working, so our refactor was good. We'd like to move a lot of the code out of our executable. This will let
124
- us unit test it better, and generally make things a bit easier to organize and understand (as always, [my book][clibook] contains more in-depth discussion of why this is and how to do it). The objects of our app are "Repositories" and "Files". Ruby already has a `File` class, so let's start with "Repository". We'll make one in `lib` that can be cloned and whose files can be listed.
125
-
126
- We'll create a class named `Repo` in `lib/fullstop/repo.rb` that has a factory method, `clone_from`, that will clone and create a
127
- `Repo` instance that has a method `repo_dir` exposing the dir where the repo was cloned, and `files` which iterates over each
128
- file in the repo, skipping '.' and '..' as before:
129
-
130
- ```ruby
131
- module Fullstop
132
- class Repo
133
-
134
- include Methadone::CLILogging
135
- include Methadone::SH
136
- include Methadone::Main
137
-
138
- def self.clone_from(repo_url,force=false)
139
- repo_dir = repo_url.split(/\//)[-1].gsub(/\.git$/,'')
140
- if force && Dir.exists?(repo_dir)
141
- warn "deleting #{repo_dir} before cloning"
142
- FileUtils.rm_rf repo_dir
143
- end
144
- unless sh("git clone #{repo_url}") == 0
145
- exit_now!("checkout dir already exists, use --force to overwrite")
146
- end
147
- Repo.new(repo_dir)
148
- end
149
-
150
- attr_reader :repo_dir
151
- def initialize(repo_dir)
152
- @repo_dir = repo_dir
153
- end
154
-
155
- def files
156
- Dir.entries(@repo_dir).each do |file|
157
- next if file == '.' || file == '..' || file == '.git'
158
- yield file
159
- end
160
- end
161
- end
162
- end
163
- ```
164
-
165
- We'll explain why we included the Methadone modules a bit later. Now, our `bin/fullstop` executable now looks like so:
166
-
167
- ```ruby
168
- #!/usr/bin/env ruby
169
-
170
- require 'optparse'
171
- require 'methadone'
172
- require 'fullstop'
173
- require 'fileutils'
174
-
175
- class App
176
- include Methadone::ExitNow
177
- include Methadone::CLILogging
178
- include Methadone::SH
179
- include Fullstop
180
-
181
- main do |repo_url|
182
- Dir.chdir options['checkout-dir'] do
183
- repo = Repo.clone_from(repo_url,options[:force])
184
- repo.files do |file|
185
- link_file(repo,file,options[:force])
186
- end
187
- end
188
- end
189
-
190
- def self.link_file(repo,file,overwrite)
191
- source_file = File.join(repo.repo_dir,file)
192
- FileUtils.rm(file) if File.exists?(file) && overwrite
193
- FileUtils.ln_s source_file,'.'
194
- end
195
-
196
- version Fullstop::VERSION
197
-
198
- description 'Manages dotfiles from a git repo'
199
-
200
- options['checkout-dir'] = ENV['HOME']
201
- on("--force","Overwrite files if they exist")
202
- on("-d DIR","--checkout-dir","Where to clone the repo")
203
-
204
- arg :repo_url, "URL to the git repository containing your dotfiles"
205
-
206
- use_log_level_option
207
-
208
- go!
209
- end
210
- ```
211
-
212
- It's now a lot shorter, easier to understand and we have our code in classes, where they can be tested in a fast-running unit
213
- test (we'll leave those tests as an exercise to the reader).
214
-
215
- The point of all this is that *none of this matters to Methadone*. When you distribute your app, the code will be available, and
216
- thus you can organize it however you'd like.
217
-
218
- You've noticed that we've been punting on a few things that we've seen, most recently, the module `Methadone::CLILogging`. At
219
- this point, you know enough to effectively use Methadone to make awesome command-line apps. In the next section, we'll take a
220
- closer look at how logging and debugging work with a Methadone app, which will clear up a few details that we've glossed over.
@@ -1,274 +0,0 @@
1
- # Logging & Debugging
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, 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.
4
-
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`
6
- statements, like so:
7
-
8
- ```ruby
9
- if sh "rsync /apps/my_app/ backup-srv:/apps/my_app" == 0
10
- puts "rsync successful"
11
- # do other things
12
- else
13
- puts "Something, went wrong: #{$!}"
14
- end
15
- ```
16
-
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.
18
-
19
- Instead, you should use logging, and Methadone bakes logging right in.
20
-
21
- ## Logging
22
-
23
- We've seen the module `Methadone::CLILogging` before. This module can be mixed into any class and does two things:
24
-
25
- * Provides a shared instance of a logger, available via the method `logger`
26
- * Provides the convienience methods `debug`, `info`, `warn`, `error`, and `fatal`, which proxy to the underlying logger.
27
-
28
- In a Methadone app, most output should be done using the logger. The above code would look like so:
29
-
30
- ```ruby
31
- if sh "rsync /apps/my_app/ backup-srv:/apps/my_app" == 0
32
- debug "rsync successful"
33
- # do other things
34
- else
35
- warn "Something, went wrong: #{$!}"
36
- end
37
- ```
38
-
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.
40
-
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.
42
-
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.
44
-
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.
46
-
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.
48
-
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.
50
-
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:
52
-
53
- ```ruby
54
- main do |repo_url|
55
- Dir.chdir options['checkout-dir'] do
56
- repo = Repo.clone_from(repo_url,options[:force])
57
- repo.files do |file|
58
- link_file(repo,file,options[:force])
59
- end
60
- end
61
- # vvv
62
- info "Dotfiles symlinked"
63
- # ^^^
64
- end
65
- ```
66
-
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:
68
-
69
- ```ruby
70
- module Fullstop
71
- class Repo
72
-
73
- include Methadone::CLILogging
74
- include Methadone::SH
75
- include Methadone::ExitNow
76
-
77
- def self.clone_from(repo_url,force=false)
78
- repo_dir = repo_url.split(/\//)[-1].gsub(/\.git$/,'')
79
- # vvv
80
- debug "Cloning #{repo_url} into #{repo_dir}"
81
- # ^^^
82
- if force && Dir.exists?(repo_dir)
83
- warn "deleting #{repo_dir} before cloning"
84
- FileUtils.rm_rf repo_dir
85
- end
86
- if sh("git clone #{repo_url}") == 0
87
- exit_now!(1,"checkout dir already exists, use --force to overwrite")
88
- end
89
- Repo.new(repo_dir)
90
- end
91
-
92
- attr_reader :repo_dir
93
- def initialize(repo_dir)
94
- @repo_dir = repo_dir
95
- end
96
-
97
- def files
98
- Dir.entries(@repo_dir).each do |file|
99
- next if file == '.' || file == '..' || file == '.git'
100
- # vvv
101
- debug "Yielding #{file}"
102
- # ^^^
103
- yield file
104
- end
105
- end
106
- end
107
- end
108
- ```
109
-
110
- Let's run our app on the command-line:
111
-
112
- ```sh
113
- $ HOME=/tmp/fake-home bundle exec bin/fullstop file:///tmp/dotfiles.git
114
- Dotfiles symlinked
115
- ```
116
-
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:
118
-
119
- ```sh
120
- $ rm -rf /tmp/fake-home ; mkdir /tmp/fake-home/
121
- $ HOME=/tmp/fake-home bundle exec bin/fullstop --log-level=debug file:///tmp/dotfiles.git
122
- Cloning file:///tmp/dotfiles.git into dotfiles
123
- Executing 'git clone file:///tmp/dotfiles.git'
124
- Output of 'git clone file:///tmp/dotfiles.git': Cloning into dotfiles...
125
- Yielding .bashrc
126
- Yielding .exrc
127
- Yielding .inputrc
128
- Yielding .vimrc
129
- Dotfiles symlinked
130
- ```
131
-
132
- As you can see, we see all the debug messages. Now, let's redirect that to a log file and see what it looks like.
133
-
134
- ```sh
135
- $ rm -rf /tmp/fake-home ; mkdir /tmp/fake-home/
136
- $ HOME=/tmp/fake-home bundle exec bin/fullstop --log-level=debug file:///tmp/dotfiles.git > fullstop.log
137
- $ cat fullstop.log
138
- D, [2012-02-13T21:11:05.924220 #49986] DEBUG -- : Cloning file:///tmp/dotfiles.git into dotfiles
139
- D, [2012-02-13T21:11:05.928311 #49986] DEBUG -- : Executing 'git clone file:///tmp/dotfiles.git'
140
- D, [2012-02-13T21:11:05.950333 #49986] DEBUG -- : Output of 'git clone file:///tmp/dotfiles.git': Cloning into dotfiles...
141
- D, [2012-02-13T21:11:05.950566 #49986] DEBUG -- : Yielding .bashrc
142
- D, [2012-02-13T21:11:05.950752 #49986] DEBUG -- : Yielding .exrc
143
- D, [2012-02-13T21:11:05.950866 #49986] DEBUG -- : Yielding .inputrc
144
- D, [2012-02-13T21:11:05.950968 #49986] DEBUG -- : Yielding .vimrc
145
- I, [2012-02-13T21:11:05.951086 #49986] INFO -- : Dotfiles symlinked
146
- ```
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.
148
-
149
- Now, let's run the app again, but without "resetting" our fake home directory in `/tmp/fake-home`.
150
-
151
- ```sh
152
- $ HOME=/tmp/fake-home bundle exec bin/fullstop file:///tmp/dotfiles.git
153
- Error output of 'git clone file:///tmp/dotfiles.git': fatal: destination path 'dotfiles' already exists and is not an empty directory.
154
- Output of 'git clone file:///tmp/dotfiles.git':
155
- Error running 'git clone file:///tmp/dotfiles.git'
156
- checkout dir already exists, use --force to overwrite
157
- ```
158
-
159
- We get several error messages. Let's redirect the standard output to a file and try again.
160
-
161
- ```ruby
162
- $ HOME=/tmp/fake-home bundle exec bin/fullstop file:///tmp/dotfiles.git > fullstop.log
163
- Error output of 'git clone file:///tmp/dotfiles.git': fatal: destination path 'dotfiles' already exists and is not an empty directory.
164
- Error running 'git clone file:///tmp/dotfiles.git'
165
- checkout dir already exists, use --force to overwrite
166
- $ cat fullstop.log
167
- W, [2012-02-13T21:13:29.819867 #50061] WARN -- : Error output of 'git clone file:///tmp/dotfiles.git': fatal: destination path 'dotfiles' already exists and is not an empty directory.
168
- I, [2012-02-13T21:13:29.820076 #50061] INFO -- : Output of 'git clone file:///tmp/dotfiles.git':
169
- W, [2012-02-13T21:13:29.820120 #50061] WARN -- : Error running 'git clone file:///tmp/dotfiles.git'
170
- E, [2012-02-13T21:13:29.820251 #50061] ERROR -- : checkout dir already exists, use --force to overwrite
171
- ```
172
-
173
- As you can see, our terminal (which is only showing the standard error output) shows us only the warning and log messages, and
174
- ourlog file has *all* the messages. This gives you a *lot* of flexibility.
175
-
176
- The normal Ruby `Logger` doesn't have quite these smarts; it produces messages onto one `IO` device. Methadone is sending
177
- messages to potentially many places. How does this work?
178
-
179
- ## Methadone's Special Logger
180
-
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.
182
-
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.
184
-
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.
188
-
189
- We just need to change one line in `bin/fullstop`, to call `change_logger` inside our `main` block:
190
-
191
- ```ruby
192
- main do |repo_url|
193
- # vvv
194
- change_logger(Logger.new(STDERR))
195
- # ^^^
196
- Dir.chdir options['checkout-dir'] do
197
- repo = Repo.clone_from(repo_url,options[:force])
198
- repo.files do |file|
199
- link_file(repo,file,options[:force])
200
- end
201
- end
202
- info "Dotfiles symlinked"
203
- end
204
- ```
205
-
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.
207
-
208
- ```sh
209
- $ rm -rf /tmp/fake-home ; mkdir /tmp/fake-home/
210
- $ HOME=/tmp/fake-home bundle exec bin/fullstop --log-level=debug file:///tmp/dotfiles.git > fullstop.log
211
- D, [2012-02-13T21:18:11.492004 #50317] DEBUG -- : Cloning file:///tmp/dotfiles.git into dotfiles
212
- D, [2012-02-13T21:18:11.492125 #50317] DEBUG -- : Executing 'git clone file:///tmp/dotfiles.git'
213
- D, [2012-02-13T21:18:11.513846 #50317] DEBUG -- : Output of 'git clone file:///tmp/dotfiles.git': Cloning into dotfiles...
214
- D, [2012-02-13T21:18:11.514113 #50317] DEBUG -- : Yielding .bashrc
215
- D, [2012-02-13T21:18:11.514339 #50317] DEBUG -- : Yielding .exrc
216
- D, [2012-02-13T21:18:11.514516 #50317] DEBUG -- : Yielding .inputrc
217
- D, [2012-02-13T21:18:11.514719 #50317] DEBUG -- : Yielding .vimrc
218
- I, [2012-02-13T21:18:11.514899 #50317] INFO -- : Dotfiles symlinked
219
- HOME=/tmp/fake-home bundle exec bin/fullstop --log-level=debug file:///tmp/dotfiles.git > fullstop.log
220
- D, [2012-02-13T21:18:17.181995 #50348] DEBUG -- : Cloning file:///tmp/dotfiles.git into dotfiles
221
- D, [2012-02-13T21:18:17.182112 #50348] DEBUG -- : Executing 'git clone file:///tmp/dotfiles.git'
222
- W, [2012-02-13T21:18:17.186447 #50348] WARN -- : Error output of 'git clone file:///tmp/dotfiles.git': fatal: destination path 'dotfiles' already exists and is not an empty directory.
223
- I, [2012-02-13T21:18:17.186579 #50348] INFO -- : Output of 'git clone file:///tmp/dotfiles.git':
224
- W, [2012-02-13T21:18:17.186621 #50348] WARN -- : Error running 'git clone file:///tmp/dotfiles.git'
225
- E, [2012-02-13T21:18:17.186798 #50348] ERROR -- : checkout dir already exists, use --force to overwrite
226
- ```
227
-
228
- As you can see, our output is the default for a Ruby `Logger`, and there's no special formatting. You'll also notice that, even
229
- though we redirected standard out to a log, we still saw all the messages. Since our `Logger` was configured to use the standard
230
- error stream, our terminal gets all the messages.
231
-
232
- Note that all of our code, include code in `lib/fullstop/repo.rb` uses this logger via the convienience methods provided by
233
- `Methadone::CLILogging`. This is a great way to avoid global variables, and can provide central control over your logging and
234
- output.
235
-
236
- ## Exceptions
237
-
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.
239
-
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.
241
-
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.
243
-
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:
245
-
246
- ```sh
247
- $ HOME=/tmp/fake-home bundle exec bin/fullstop --log-level=debug file:///tmp/dotfiles.git
248
- Cloning file:///tmp/dotfiles.git into dotfiles
249
- Executing 'git clone file:///tmp/dotfiles.git'
250
- Error output of 'git clone file:///tmp/dotfiles.git': fatal: destination path 'dotfiles' already exists and is not an empty directory.
251
- Output of 'git clone file:///tmp/dotfiles.git':
252
- Error running 'git clone file:///tmp/dotfiles.git'
253
- checkout dir already exists, use --force to overwrite
254
- $ DEBUG=true HOME=/tmp/fake-home bundle exec bin/fullstop --log-level=debug file:///tmp/dotfiles.git
255
- Cloning file:///tmp/dotfiles.git into dotfiles
256
- Executing 'git clone file:///tmp/dotfiles.git'
257
- Error output of 'git clone file:///tmp/dotfiles.git': fatal: destination path 'dotfiles' already exists and is not an empty directory.
258
- Output of 'git clone file:///tmp/dotfiles.git':
259
- Error running 'git clone file:///tmp/dotfiles.git'
260
- /Users/davec/.rvm/gems/ruby-1.9.3-p0@methadone-tutorial/gems/methadone-1.0.0.rc2/lib/methadone/exit_now.rb:21:in `exit_now!': checkout dir already exists, use --force to overwrite (Methadone::Error)
261
- from /Users/davec/Projects/methadone/tutorial/code/fullstop/lib/fullstop/repo.rb:18:in `clone_from'
262
- from bin/fullstop:16:in `block (2 levels) in <class:App>'
263
- from bin/fullstop:15:in `chdir'
264
- from bin/fullstop:15:in `block in <class:App>'
265
- from /Users/davec/.rvm/gems/ruby-1.9.3-p0@methadone-tutorial/gems/methadone-1.0.0.rc2/lib/methadone/main.rb:273:in `call'
266
- from /Users/davec/.rvm/gems/ruby-1.9.3-p0@methadone-tutorial/gems/methadone-1.0.0.rc2/lib/methadone/main.rb:273:in `call_main'
267
- from /Users/davec/.rvm/gems/ruby-1.9.3-p0@methadone-tutorial/gems/methadone-1.0.0.rc2/lib/methadone/main.rb:147:in `go!'
268
- from bin/fullstop:42:in `<class:App>'
269
- from bin/fullstop:8:in `<main>'
270
- ```
271
-
272
- Occasionally, you might *always* want the exceptions to leak through. For example, if your app is being run as part of some
273
- other system that you don't have precise control over, such as [monit][monit], the backtrace will tell you what went wrong if the
274
- system can't properly start your app. In this case, use the method `leak_exceptions` to permanently show the backtrace. Note that this method will only leak exceptions that *aren't* of type `Methadone::Error`.