methadone 1.0.0.rc2 → 1.0.0.rc3

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.
Files changed (40) hide show
  1. data/bin/methadone +2 -2
  2. data/lib/methadone/cucumber.rb +6 -3
  3. data/lib/methadone/error.rb +3 -2
  4. data/lib/methadone/exit_now.rb +25 -0
  5. data/lib/methadone/main.rb +21 -10
  6. data/lib/methadone/sh.rb +19 -4
  7. data/lib/methadone/version.rb +1 -1
  8. data/lib/methadone.rb +1 -0
  9. data/templates/full/Rakefile.erb +1 -1
  10. data/templates/full/features/step_definitions/executable_steps.rb.erb +1 -1
  11. data/test/test_exit_now.rb +37 -0
  12. data/test/test_main.rb +11 -5
  13. data/test/test_sh.rb +17 -0
  14. data/tutorial/.vimrc +6 -0
  15. data/tutorial/1_intro.md +52 -0
  16. data/tutorial/2_bootstrap.md +176 -0
  17. data/tutorial/3_ui.md +349 -0
  18. data/tutorial/4_happy_path.md +437 -0
  19. data/tutorial/5_more_features.md +702 -0
  20. data/tutorial/6_refactor.md +220 -0
  21. data/tutorial/7_logging_debugging.md +304 -0
  22. data/tutorial/8_conclusion.md +11 -0
  23. data/tutorial/code/.rvmrc +1 -0
  24. data/tutorial/code/fullstop/.gitignore +5 -0
  25. data/tutorial/code/fullstop/Gemfile +4 -0
  26. data/tutorial/code/fullstop/LICENSE.txt +202 -0
  27. data/tutorial/code/fullstop/README.rdoc +23 -0
  28. data/tutorial/code/fullstop/Rakefile +31 -0
  29. data/tutorial/code/fullstop/bin/fullstop +43 -0
  30. data/tutorial/code/fullstop/features/fullstop.feature +40 -0
  31. data/tutorial/code/fullstop/features/step_definitions/fullstop_steps.rb +64 -0
  32. data/tutorial/code/fullstop/features/support/env.rb +22 -0
  33. data/tutorial/code/fullstop/fullstop.gemspec +28 -0
  34. data/tutorial/code/fullstop/lib/fullstop/repo.rb +38 -0
  35. data/tutorial/code/fullstop/lib/fullstop/version.rb +3 -0
  36. data/tutorial/code/fullstop/lib/fullstop.rb +2 -0
  37. data/tutorial/code/fullstop/test/tc_something.rb +7 -0
  38. data/tutorial/en.utf-8.add +18 -0
  39. data/tutorial/toc.md +27 -0
  40. metadata +49 -20
data/bin/methadone CHANGED
@@ -48,7 +48,7 @@ main do |app_name|
48
48
  " #{gem_variable}.add_development_dependency('rdoc')",
49
49
  " #{gem_variable}.add_development_dependency('aruba')",
50
50
  " #{gem_variable}.add_development_dependency('rake','~> 0.9.2')",
51
- " #{gem_variable}.add_dependency('methadone')",
51
+ " #{gem_variable}.add_dependency('methadone', '~>1.0.0.rc3')",
52
52
  ], :before => /^end\s*$/
53
53
  end
54
54
 
@@ -64,7 +64,7 @@ on("-l LICENSE","--license",licenses,"Specify the license for your project (#{li
64
64
 
65
65
  use_log_level_option
66
66
 
67
- arg :app_name, :required
67
+ arg :app_name, :required, "Name of your app, which is used for the gem name and executable name"
68
68
 
69
69
  version Methadone::VERSION
70
70
 
@@ -34,7 +34,9 @@ module Methadone
34
34
  #
35
35
  # * Checks that the app's usage banner documents that its arguments are <tt>args</tt>
36
36
  #
37
- # Then the banner should document that this app's arguments are "args"
37
+ # Then the banner should document that this app's arguments are
38
+ # |foo|which is optional|
39
+ # |bar|which is required|
38
40
  #
39
41
  # * Do the opposite; check that your app doesn't take any arguments
40
42
  #
@@ -42,7 +44,7 @@ module Methadone
42
44
  #
43
45
  # * Check for a usage description which occurs after the banner and a blank line
44
46
  #
45
- # Then there should be a one-line summary of what the app does
47
+ # Then there should be a one line summary of what the app does
46
48
  #
47
49
  module Cucumber
48
50
  end
@@ -59,7 +61,7 @@ Then /^the following options should be documented:$/ do |options|
59
61
  end
60
62
 
61
63
  Then /^the option "([^"]*)" should be documented$/ do |option|
62
- step %(the output should match /^\\s*#{option}\\s+\\w\\w\\w+/)
64
+ step %(the output should match /\\s*#{option}[\\s\\W]+\\w\\w\\w+/)
63
65
  end
64
66
 
65
67
  Then /^the banner should be present$/ do
@@ -75,6 +77,7 @@ Then /^the banner should document that this app's arguments are:$/ do |table|
75
77
  expected_arguments = table.raw.map { |row|
76
78
  option = row[0]
77
79
  option = "[#{option}]" if row[1] == 'optional' || row[1] == 'which is optional'
80
+ option
78
81
  }.join(' ')
79
82
  step %(the output should contain "#{expected_arguments}")
80
83
  end
@@ -16,8 +16,9 @@ module Methadone
16
16
  # The command that caused the failure
17
17
  attr_reader :command
18
18
 
19
- def initialize(exit_code,command)
20
- super(exit_code,"Command '#{command}' exited #{exit_code}")
19
+ def initialize(exit_code,command,custom_error_message = nil)
20
+ error_message = String(custom_error_message).empty? ? "Command '#{command}' exited #{exit_code}" : custom_error_message
21
+ super(exit_code,error_message)
21
22
  @command = command
22
23
  end
23
24
  end
@@ -0,0 +1,25 @@
1
+ module Methadone
2
+ module ExitNow
3
+ def self.included(k)
4
+ k.extend(self)
5
+ end
6
+ # Call this to exit the program immediately
7
+ # with the given error code and message.
8
+ #
9
+ # +exit_code+:: exit status you'd like to exit with
10
+ # +message+:: message to display to the user explaining the problem
11
+ #
12
+ # Also can be used without an exit code like so:
13
+ #
14
+ # exit_now!("Oh noes!")
15
+ #
16
+ # In this case, it's equivalent to <code>exit_now!(1,"Oh noes!")</code>.
17
+ def exit_now!(exit_code,message=nil)
18
+ if exit_code.kind_of?(String) && message.nil?
19
+ raise Methadone::Error.new(1,exit_code)
20
+ else
21
+ raise Methadone::Error.new(exit_code,message)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -61,6 +61,7 @@ module Methadone
61
61
  # *not* do this.
62
62
  #
63
63
  module Main
64
+ include Methadone::ExitNow
64
65
  def self.included(k)
65
66
  k.extend(self)
66
67
  end
@@ -118,7 +119,6 @@ module Methadone
118
119
  @env_var = env_var
119
120
  opts.separator ''
120
121
  opts.separator "Default values can be placed in the #{env_var} environment variable"
121
- opts.separator ''
122
122
  end
123
123
 
124
124
  # Start your command-line app, exiting appropriately when
@@ -136,6 +136,7 @@ module Methadone
136
136
  #
137
137
  def go!
138
138
  normalize_defaults
139
+ opts.post_setup
139
140
  if @env_var
140
141
  String(ENV[@env_var]).split(/\s+/).each do |arg|
141
142
  ::ARGV.unshift(arg)
@@ -156,15 +157,6 @@ module Methadone
156
157
  exit 64 # Linux standard for bad command line
157
158
  end
158
159
 
159
- # Call this to exit the program immediately
160
- # with the given error code and message.
161
- #
162
- # +exit_code+:: exit status you'd like to exit with
163
- # +message+:: message to display to the user explaining the problem
164
- def exit_now!(exit_code,message=nil)
165
- raise Methadone::Error.new(exit_code,message)
166
- end
167
-
168
160
  # Returns an OptionParser that you can use
169
161
  # to declare your command-line interface. Generally, you
170
162
  # won't use this and will use #on directly, but this allows
@@ -212,6 +204,7 @@ module Methadone
212
204
  # <tt>:one</tt>:: only one of this arg should be supplied (default)
213
205
  # <tt>:many</tt>:: many of this arg may be supplied, but at least one is required
214
206
  # <tt>:any</tt>:: any number, include zero, may be supplied
207
+ # A string:: if present, this will be documentation for the argument and appear in the help
215
208
  def arg(arg_name,*options)
216
209
  opts.arg(arg_name,*options)
217
210
  end
@@ -311,6 +304,7 @@ module Methadone
311
304
  @accept_options = false
312
305
  @args = []
313
306
  @arg_options = {}
307
+ @arg_documentation = {}
314
308
  @description = nil
315
309
  @version = nil
316
310
  set_banner
@@ -363,6 +357,9 @@ module Methadone
363
357
  options << :one unless options.include?(:any) || options.include?(:many)
364
358
  @args << arg_name
365
359
  @arg_options[arg_name] = options
360
+ options.select { |_| _.kind_of? ::String }.each do |doc|
361
+ @arg_documentation[arg_name] = doc + (options.include?(:optional) ? " (optional)" : "")
362
+ end
366
363
  set_banner
367
364
  end
368
365
 
@@ -388,6 +385,20 @@ module Methadone
388
385
  set_banner
389
386
  end
390
387
 
388
+ # We need some documentation to appear at the end, after all OptionParser setup
389
+ # has occured, but before we actually start. This method serves that purpose
390
+ def post_setup
391
+ unless @arg_documentation.empty?
392
+ @option_parser.separator ''
393
+ @option_parser.separator "Arguments:"
394
+ @option_parser.separator ''
395
+ @args.each do |arg|
396
+ @option_parser.separator " #{arg}"
397
+ @option_parser.separator " #{@arg_documentation[arg]}"
398
+ end
399
+ end
400
+ end
401
+
391
402
  private
392
403
 
393
404
  def add_default_value_to_docstring(*args)
data/lib/methadone/sh.rb CHANGED
@@ -57,6 +57,9 @@ module Methadone
57
57
  # This is to make it easy for you to shell-out to external commands and have your app be robust and
58
58
  # easy to maintain.
59
59
  module SH
60
+ def self.included(k)
61
+ k.extend(self)
62
+ end
60
63
  # Run a shell command, capturing and logging its output.
61
64
  # If the command completed successfully, it's output is logged at DEBUG.
62
65
  # If not, its output as logged at INFO. In either case, its
@@ -87,10 +90,10 @@ module Methadone
87
90
  sh_logger.warn("Error output of '#{command}': #{stderr}") unless stderr.strip.length == 0
88
91
 
89
92
  if status.exitstatus != 0
90
- sh_logger.info("Output of '#{command}': #{stdout}")
93
+ sh_logger.info("Output of '#{command}': #{stdout}") unless stdout.strip.length == 0
91
94
  sh_logger.warn("Error running '#{command}'")
92
95
  else
93
- sh_logger.debug("Output of '#{command}': #{stdout}")
96
+ sh_logger.debug("Output of '#{command}': #{stdout}") unless stdout.strip.length == 0
94
97
  call_block(block,stdout,stderr) unless block.nil?
95
98
  end
96
99
 
@@ -103,10 +106,22 @@ module Methadone
103
106
  # Run a command, throwing an exception if the command exited nonzero.
104
107
  # Otherwise, behaves exactly like #sh.
105
108
  #
109
+ # options - options hash, responding to:
110
+ # <tt>:on_fail</tt>:: a custom error message. This allows you to have your
111
+ # app exit on shell command failures, but customize the error
112
+ # message that they see.
113
+ #
106
114
  # Raises Methadone::FailedCommandError if the command exited nonzero.
107
- def sh!(command,&block)
115
+ #
116
+ # Examples:
117
+ #
118
+ # sh!("rsync foo bar")
119
+ # # => if command fails, app exits and user sees: "error: Command 'rsync foo bar' exited 12"
120
+ # sh!("rsync foo bar", :on_fail => "Couldn't rsync, check log for details")
121
+ # # => if command fails, app exits and user sees: "error: Couldn't rsync, check log for details
122
+ def sh!(command,options={},&block)
108
123
  sh(command,&block).tap do |exitstatus|
109
- raise Methadone::FailedCommandError.new(exitstatus,command) if exitstatus != 0
124
+ raise Methadone::FailedCommandError.new(exitstatus,command,options[:on_fail]) if exitstatus != 0
110
125
  end
111
126
  end
112
127
 
@@ -1,3 +1,3 @@
1
1
  module Methadone
2
- VERSION = "1.0.0.rc2"
2
+ VERSION = "1.0.0.rc3"
3
3
  end
data/lib/methadone.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'methadone/version'
2
2
  require 'methadone/cli_logger'
3
3
  require 'methadone/cli_logging'
4
+ require 'methadone/exit_now'
4
5
  require 'methadone/main'
5
6
  require 'methadone/error'
6
7
  require 'methadone/execution_strategy/base'
@@ -17,7 +17,7 @@ end
17
17
  CUKE_RESULTS = 'results.html'
18
18
  CLEAN << CUKE_RESULTS
19
19
  Cucumber::Rake::Task.new(:features) do |t|
20
- t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format progress -x"
20
+ t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty --no-source -x"
21
21
  t.fork = false
22
22
  end
23
23
 
@@ -1 +1 @@
1
- # Put your step defintions here
1
+ # Put your step definitions here
@@ -0,0 +1,37 @@
1
+ require 'base_test'
2
+ require 'methadone'
3
+ require 'stringio'
4
+
5
+ class TestExitNow < BaseTest
6
+ include Methadone
7
+ include Methadone::ExitNow
8
+
9
+ test_that "exit_now raises the proper error" do
10
+ Given {
11
+ @exit_code = any_int :min => 1
12
+ @message = any_string
13
+ }
14
+ When {
15
+ @code = lambda { exit_now!(@exit_code,@message) }
16
+ }
17
+ Then {
18
+ exception = assert_raises(Methadone::Error,&@code)
19
+ exception.exit_code.should == @exit_code
20
+ exception.message.should == @message
21
+ }
22
+ end
23
+
24
+ test_that "exit_now without an exit code uses 1 as the exti code" do
25
+ Given {
26
+ @message = any_string
27
+ }
28
+ When {
29
+ @code = lambda { exit_now!(@message) }
30
+ }
31
+ Then {
32
+ exception = assert_raises(Methadone::Error,&@code)
33
+ exception.exit_code.should == 1
34
+ exception.message.should == @message
35
+ }
36
+ end
37
+ end
data/test/test_main.rb CHANGED
@@ -334,17 +334,23 @@ class TestMain < BaseTest
334
334
 
335
335
  end
336
336
 
337
- test_that "I can specify which arguments my app takes and if they are required" do
337
+ test_that "I can specify which arguments my app takes and if they are required as well as document them" do
338
338
  Given {
339
339
  main {}
340
+ @db_name_desc = any_string
341
+ @user_desc = any_string
342
+ @password_desc = any_string
340
343
 
341
- arg :db_name
342
- arg :user, :required
343
- arg :password, :optional
344
+ arg :db_name, @db_name_desc
345
+ arg :user, :required, @user_desc
346
+ arg :password, :optional, @password_desc
344
347
  }
345
-
348
+ When run_go_safely
346
349
  Then {
347
350
  opts.banner.should match /db_name user \[password\]$/
351
+ opts.to_s.should match /#{@db_name_desc}/
352
+ opts.to_s.should match /#{@user_desc}/
353
+ opts.to_s.should match /#{@password_desc}/
348
354
  }
349
355
  end
350
356
 
data/test/test_sh.rb CHANGED
@@ -170,6 +170,23 @@ class TestSH < Clean::Test::TestCase
170
170
  }
171
171
  end
172
172
 
173
+ test_that "sh! runs a command that will fail and includes an error message that appears in the exception" do
174
+ Given {
175
+ use_capturing_logger
176
+ @command = test_command("foo")
177
+ @custom_error_message = any_sentence
178
+ }
179
+ When {
180
+ @code = lambda { sh! @command, :on_fail => @custom_error_message }
181
+ }
182
+ Then {
183
+ exception = assert_raises(Methadone::FailedCommandError,&@code)
184
+ exception.command.should == @command
185
+ exception.message.should == @custom_error_message
186
+ assert_logger_output_for_failure(@logger,@command,test_command_stdout,test_command_stderr)
187
+ }
188
+ end
189
+
173
190
  class MyTestApp
174
191
  include Methadone::SH
175
192
  def initialize(logger)
data/tutorial/.vimrc ADDED
@@ -0,0 +1,6 @@
1
+ set spell spelllang=en_us
2
+ syn spell toplevel
3
+ set spf=~/Projects/methadone/tutorial/en.utf-8.add
4
+ ab taht that
5
+ ab builting built-in
6
+ ab builtin built-in
@@ -0,0 +1,52 @@
1
+ # Awesome Ruby Command Line Apps with Methadone
2
+
3
+ Kick the bash habit, and make all your command-line apps with Ruby.
4
+
5
+ In [Build Awesome Command-Line Applications in Ruby][clibook], I lay out how to make an awesome command-line application using
6
+ Ruby. The book focuses on tools like `OptionParser` to create the app. As I wrote and researched, it became clear that there
7
+ was a gap between `OptionParser`, which is very powerful, yet verbose, and other command line tools like [trollop][trollop],
8
+ [main][main], and [thor][thor], which have simple APIs, but aren't very powerful.
9
+
10
+ [clibook]: http://www.awesomecommandlineapps.com
11
+ [main]: http://github.com/ahoward/main
12
+ [trollop]: http://trollop.rubyforge.org
13
+ [thor]: http://www.github.com/wycats/thor
14
+
15
+ I created Methadone to bridge that gap. Methadone provides all the power of `OptionParser`, but has a simple, clean API.
16
+ Methadone also includes additional tools and classes to make your command-line apps even better.
17
+
18
+ This tutorial will show you how to make a simple command-line app using Methadone that will be easy-to-use, easy-to-maintain, and
19
+ fully tested.
20
+
21
+ ## What you'll need
22
+
23
+ You'll need an installation of Ruby and the ability to install Ruby gems. I would recommend that you use rvm and a gemset to
24
+ work through these, but they aren't required. There's a decent [walkthrough][setup] on my book's website of setting this up the
25
+ way I work. Although Methadone works on most versions of Ruby, I would recommend you use Ruby
26
+ 1.9.3, if you can. If not, try to use an MRI Ruby as those versions (1.8.7, REE, 1.9.2, or 1.9.3) have the highest compatibility
27
+ with other gems.
28
+
29
+ [setup]: http://www.awesomecommandlineapps.com/setup.html
30
+
31
+ ## How this is organized
32
+
33
+ This is a tutorial for making a simple command-line app. Unlike some tutorials and books, we will be working through this using
34
+ a "test-first" approach. One thing that Methadone tries to enable is using [TDD][tdd] for creating and writing your command-line
35
+ app. As such, we'll write tests as much as possible to drive our work.
36
+
37
+ [tdd]: http://en.wikipedia.org/wiki/Test-driven_development
38
+
39
+ ## The tutorial app
40
+
41
+ The app we'll build is going to manage "dot files". These are the files that live in your home directory and configure your
42
+ shell, editor, and various other programs. For example, `~/.bashrc` is the file to configure `bash`. Many developers keep these
43
+ files on [Github][github] so that they can maintain the same files across multiple computers.
44
+
45
+ [github]: http://www.github.com
46
+
47
+ To set this up on a new computer, you have to checkout the repo, and symlink all the files to your home directory. To update the
48
+ files you have to update the repo and then check if any new files were added. This is the sort of tedious manual process that is
49
+ ripe for automation via a command-line app.
50
+
51
+ We'll develop a simplified version to demonstrate how to use Methadone.
52
+
@@ -0,0 +1,176 @@
1
+ # Bootstrapping our app
2
+
3
+ One thing that's great about writing a webapp with Ruby on Rails is that, with one command, you have a skeleton app, including
4
+ a fully functional test framework set up. You can start writing tests immediately. There's no common equivalent for a
5
+ command-line app, which is what Methadone aims to provide.
6
+
7
+ Methadone will also bootstrap other aspects of your app, such a `Rakefile`, a gemspec, a shell of an executable, a license, and a
8
+ README. First, let's install Methadone via RubyGems (note that if your aren't using rvm, you may need to use `sudo` to install
9
+ gems):
10
+
11
+ ```sh
12
+ $ gem install methadone
13
+ Fetching: methadone-1.0.0.gem (100%)
14
+ Successfully installed methadone-1.0.0
15
+ 1 gem installed
16
+ Installing ri documentation for methadone-1.0.0...
17
+ Installing RDoc documentation for methadone-1.0.0...
18
+ ```
19
+
20
+ Methadone comes bundled with a command-linen app that will do the bootstrapping:
21
+
22
+ ```sh
23
+ $ methadone --help
24
+ Usage: methadone [options] app_name
25
+
26
+ Kick the bash habit by bootstrapping your Ruby command-line apps
27
+
28
+ v1.0.0
29
+
30
+ Options:
31
+ --force Overwrite files if they exist
32
+ --[no-]readme [Do not ]produce a README file
33
+ -l, --license LICENSE Specify the license for your project (mit|apache|custom|NONE)
34
+ --log-level LEVEL Set the logging level (debug|info|warn|error|fatal)
35
+ (Default: info)
36
+ --version Show help/version info
37
+
38
+ Default values can be placed in the METHODONE_OPTS environment variable
39
+ ```
40
+
41
+ The app that we'll be building in this tutorial is be called `fullstop`, which is derived from the [British name][fullstop] for a period, which is the character used as a prefix to our dotfiles, and, is the reason they are called "dot" files in the first place. Based on the command-line syntax for `methadone`, we can create our app right now with one simple command. We'll use the Apache license as well as a README.
42
+
43
+ [fullstop]: http://en.wikipedia.org/wiki/Full_stop
44
+
45
+ ```sh
46
+ $ methadone --readme --license apache fullstop
47
+ $ ls fullstop
48
+ Gemfile README.rdoc bin/
49
+ fullstop.gemspec test/ LICENSE.txt
50
+ Rakefile features/ lib/
51
+ ```
52
+
53
+ As you can see, we've got a generic gemified project. Before we can start developing, we'll need to install a few gems using Bundler first:
54
+
55
+ ```sh
56
+ $ cd fullstop
57
+ $ bundle install
58
+ Fetching source index for http://rubygems.org/
59
+ Installing rake (0.9.2.2)
60
+ Installing ffi (1.0.11) with native extensions
61
+ Installing childprocess (0.3.1)
62
+ Installing builder (3.0.0)
63
+ Installing diff-lcs (1.1.3)
64
+ Installing json (1.6.5) with native extensions
65
+ Installing gherkin (2.7.6) with native extensions
66
+ Installing term-ansicolor (1.0.7)
67
+ Installing cucumber (1.1.4)
68
+ Installing rspec-core (2.8.0)
69
+ Installing rspec-expectations (2.8.0)
70
+ Installing rspec-mocks (2.8.0)
71
+ Installing rspec (2.8.0)
72
+ Installing aruba (0.4.11)
73
+ Using bundler (1.0.21)
74
+ Installing methadone (0.5.1)
75
+ Using fullstop (0.0.1) from source at /Users/davec/Projects/methadone/tutorial/code/fullstop
76
+ Installing rdoc (3.12)
77
+ Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
78
+ ```
79
+
80
+ Your versions might not match up, but this should be more or less what you see. The first thing you'll notice is that this seems
81
+ like a *lot* of gems! Most of them are brought in by our acceptance testing framework, [aruba][aruba], which is a library on top
82
+ of [cucumber][cucumber] tailor-made for testing command-line apps. Methadone also assumes you'll be unit
83
+ testing with `Test::Unit`, which is a fine default. In fact, both unit and acceptance tests are set up for you and available
84
+ via `rake` tasks. Let's see them in action.
85
+
86
+ [aruba]: http://www.github.com/cucumber/aruba
87
+ [cucumber]: http://cukes.info
88
+
89
+ ```sh
90
+ $ rake
91
+ Run options:
92
+
93
+ # Running tests:
94
+
95
+ .
96
+
97
+ Finished tests in 0.000623s, 1605.1364 tests/s, 1605.1364 assertions/s.
98
+
99
+ 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
100
+ ......
101
+
102
+ 1 scenario (1 passed)
103
+ 6 steps (6 passed)
104
+ 0m0.136s
105
+ ```
106
+
107
+ As you can see, we ran one unit test and one cucumber scenario. These were provided by Methadone as placeholders for your tests.
108
+ Just like what Ruby on Rails does when you create an app, Methadone has reduced the friction between your need to write software
109
+ and your ability to do so.
110
+
111
+ Methadone also generated a very basic scaffold of the command-line app itself. It's in `bin` and is called `fullstop` (the
112
+ argument we gave to `methadone`). Let's run it now:
113
+
114
+ ```sh
115
+ $ bin/fullstop
116
+ /Users/davec/.rvm/rubies/ruby-1.9.3-p0/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require': cannot load such file -- fullstop (LoadError)
117
+ from /Users/davec/.rvm/rubies/ruby-1.9.3-p0/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
118
+ from bin/fullstop:5:in `<main>'
119
+ ```
120
+
121
+ Oops! What happened?
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:
126
+
127
+ ```ruby
128
+ $: << File.join(File.dirname(__FILE__),'..','lib')
129
+ ```
130
+
131
+ This puts the `lib` directory, that is relative to the `bin` directory (where our executable lives), into Ruby's load path. This will allow *us* to run the app easily, but for your users, it's not necessary if you distribute your app with RubyGems (which you should do) and it's generally not a good idea to modify the load path. In order to run the app directly during development, we'll need to use `bundle exec`, like so (note that we won't be running our app a lot in development, but scripting it using Aruba so we can test its behavior in an automated way):
132
+
133
+ ```sh
134
+ $ bundle exec bin/fullstop --help
135
+ Usage: fullstop [options]
136
+
137
+ v0.0.1
138
+
139
+ Options:
140
+ --version Show help/version info
141
+ --log-level LEVEL Set the logging level (debug|info|warn|error|fatal)
142
+ (Default: info)
143
+ ```
144
+
145
+
146
+ Not too bad! We've got the makings of a reasonable help system, versioning support, a usage statement and a working executable.
147
+ Just remember to run the app with `bundle exec` while you're developing. Remember, your users won't have to worry about as long
148
+ as they installed it with RubyGems.
149
+
150
+ Before we move on, let's look at the cucumber scenario that Methadone generated for us. We're going to work "outside in" on our
151
+ app, so this will be a sneak peek at what we'll be doing next.
152
+
153
+ ```sh
154
+ $ cat features/fullstop.feature
155
+ ```
156
+ ```cucumber
157
+ Feature: My bootstrapped app kinda works
158
+ In order to get going on coding my awesome app
159
+ I want to have aruba and cucumber setup
160
+ So I don't have to do it myself
161
+
162
+ Scenario: App just runs
163
+ When I get help for "fullstop"
164
+ Then the exit status should be 0
165
+ And the banner should be present
166
+ And the banner should document that this app takes options
167
+ And the following options should be documented:
168
+ |--version|
169
+ And the banner should document that this app takes no arguments
170
+ ```
171
+
172
+ We probably won't keep this exactly scenario around, but it's a good demonstration of Aruba and Cucumber, and will help to get us
173
+ going. Since this scenario passes, that means that we already have the cucumber steps defined somewhere. As we'll see, the
174
+ combination of Aruba and Methadone results in a lot of pre-defined steps that make acceptance testing a snap.
175
+
176
+ In the next section, we'll expand this scenario to create the user interface we'll need to get our app going.