methadone 1.0.0.rc2 → 1.0.0.rc3

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