amp-front 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +24 -0
- data/Ampfile +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +36 -0
- data/LICENSE +20 -0
- data/README.md +50 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/design_docs/commands.md +91 -0
- data/design_docs/dependencies.md +35 -0
- data/design_docs/plugins.md +47 -0
- data/features/amp.feature +8 -0
- data/features/amp_help.feature +36 -0
- data/features/amp_plugin_list.feature +10 -0
- data/features/step_definitions/amp-front_steps.rb +23 -0
- data/features/support/env.rb +4 -0
- data/lib/amp-front.rb +30 -0
- data/lib/amp-front/dispatch/commands/base.rb +158 -0
- data/lib/amp-front/dispatch/commands/builtin/help.rb +23 -0
- data/lib/amp-front/dispatch/commands/builtin/plugin.rb +24 -0
- data/lib/amp-front/dispatch/commands/validations.rb +171 -0
- data/lib/amp-front/dispatch/runner.rb +86 -0
- data/lib/amp-front/help/entries/__default__.erb +31 -0
- data/lib/amp-front/help/entries/ampfiles.md +42 -0
- data/lib/amp-front/help/entries/commands.erb +6 -0
- data/lib/amp-front/help/entries/new-commands.md +81 -0
- data/lib/amp-front/help/help.rb +312 -0
- data/lib/amp-front/plugins/base.rb +87 -0
- data/lib/amp-front/support/module_extensions.rb +92 -0
- data/lib/amp-front/third_party/maruku.rb +136 -0
- data/lib/amp-front/third_party/maruku/attributes.rb +227 -0
- data/lib/amp-front/third_party/maruku/defaults.rb +71 -0
- data/lib/amp-front/third_party/maruku/errors_management.rb +92 -0
- data/lib/amp-front/third_party/maruku/helpers.rb +260 -0
- data/lib/amp-front/third_party/maruku/input/charsource.rb +326 -0
- data/lib/amp-front/third_party/maruku/input/extensions.rb +69 -0
- data/lib/amp-front/third_party/maruku/input/html_helper.rb +189 -0
- data/lib/amp-front/third_party/maruku/input/linesource.rb +111 -0
- data/lib/amp-front/third_party/maruku/input/parse_block.rb +615 -0
- data/lib/amp-front/third_party/maruku/input/parse_doc.rb +234 -0
- data/lib/amp-front/third_party/maruku/input/parse_span_better.rb +746 -0
- data/lib/amp-front/third_party/maruku/input/rubypants.rb +225 -0
- data/lib/amp-front/third_party/maruku/input/type_detection.rb +147 -0
- data/lib/amp-front/third_party/maruku/input_textile2/t2_parser.rb +163 -0
- data/lib/amp-front/third_party/maruku/maruku.rb +33 -0
- data/lib/amp-front/third_party/maruku/output/to_ansi.rb +223 -0
- data/lib/amp-front/third_party/maruku/output/to_html.rb +991 -0
- data/lib/amp-front/third_party/maruku/output/to_markdown.rb +164 -0
- data/lib/amp-front/third_party/maruku/output/to_s.rb +56 -0
- data/lib/amp-front/third_party/maruku/string_utils.rb +191 -0
- data/lib/amp-front/third_party/maruku/structures.rb +167 -0
- data/lib/amp-front/third_party/maruku/structures_inspect.rb +87 -0
- data/lib/amp-front/third_party/maruku/structures_iterators.rb +61 -0
- data/lib/amp-front/third_party/maruku/textile2.rb +1 -0
- data/lib/amp-front/third_party/maruku/toc.rb +199 -0
- data/lib/amp-front/third_party/maruku/usage/example1.rb +33 -0
- data/lib/amp-front/third_party/maruku/version.rb +40 -0
- data/lib/amp-front/third_party/trollop.rb +766 -0
- data/spec/amp-front_spec.rb +25 -0
- data/spec/command_specs/base_spec.rb +123 -0
- data/spec/command_specs/command_spec.rb +97 -0
- data/spec/command_specs/help_spec.rb +33 -0
- data/spec/command_specs/spec_helper.rb +37 -0
- data/spec/command_specs/validations_spec.rb +267 -0
- data/spec/dispatch_specs/runner_spec.rb +116 -0
- data/spec/dispatch_specs/spec_helper.rb +15 -0
- data/spec/help_specs/help_entry_spec.rb +78 -0
- data/spec/help_specs/help_registry_spec.rb +77 -0
- data/spec/help_specs/spec_helper.rb +15 -0
- data/spec/plugin_specs/base_spec.rb +36 -0
- data/spec/plugin_specs/spec_helper.rb +15 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support_specs/module_extensions_spec.rb +104 -0
- data/spec/support_specs/spec_helper.rb +15 -0
- data/test/third_party_tests/test_trollop.rb +1181 -0
- metadata +192 -0
data/.document
ADDED
data/.gitignore
ADDED
data/Ampfile
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
builder (2.1.2)
|
5
|
+
cucumber (0.9.2)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
diff-lcs (~> 1.1.2)
|
8
|
+
gherkin (~> 2.2.5)
|
9
|
+
json (~> 1.4.6)
|
10
|
+
term-ansicolor (~> 1.0.5)
|
11
|
+
diff-lcs (1.1.2)
|
12
|
+
gemcutter (0.6.1)
|
13
|
+
gherkin (2.2.9)
|
14
|
+
json (~> 1.4.6)
|
15
|
+
term-ansicolor (~> 1.0.5)
|
16
|
+
git (1.2.5)
|
17
|
+
jeweler (1.4.0)
|
18
|
+
gemcutter (>= 0.1.0)
|
19
|
+
git (>= 1.2.5)
|
20
|
+
rubyforge (>= 2.0.0)
|
21
|
+
json (1.4.6)
|
22
|
+
json_pure (1.4.6)
|
23
|
+
rspec (1.3.1)
|
24
|
+
rubyforge (2.0.4)
|
25
|
+
json_pure (>= 1.1.7)
|
26
|
+
term-ansicolor (1.0.5)
|
27
|
+
yard (0.6.1)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
cucumber
|
34
|
+
jeweler
|
35
|
+
rspec (< 2.0.0)
|
36
|
+
yard
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Michael Edgar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Rules
|
2
|
+
|
3
|
+
We've learned a lot about software development. Time to get serious.
|
4
|
+
|
5
|
+
## Commits
|
6
|
+
|
7
|
+
* In the past our commits weren't really well-factored, or really had structure. This makes reading our history hard.
|
8
|
+
* Use hg collapse to collapse several small commits into one logical commit.
|
9
|
+
* Branch locally to work on separate features.
|
10
|
+
|
11
|
+
To do that, clone a local repo to another local repo. Change the branch on that other local repo. Make your commits. Then, in that clone, collapse your commits into something logical. Then, merge your changes into the `default` branch. This will show up as a fast-forward commit. Finally, push those to your main checkout.
|
12
|
+
|
13
|
+
* One commit, one feature. If your feature doesn't fit in one nicely-sized commit, then prefix your commit message with "PARTIAL:".
|
14
|
+
* Until we have our own server, we can't really have code reviews. Until then: every commit that goes through, respond to it with *any* questions you have about the code. If you see nothing wrong, respond "LGTM". That stands for "Looks good to me." Continue this conversation until the other person says "LGTM". Otherwise, consider that commit incomplete.
|
15
|
+
|
16
|
+
## Tests
|
17
|
+
|
18
|
+
* Nothing goes in without a test of some kind. If you need to submit code that is untested, include a justification in your commit.
|
19
|
+
* Tests are either small, medium, or large.
|
20
|
+
* * Small tests are unit tests. They may need mocks to avoid interaction with other modules.
|
21
|
+
* * Medium tests are integration tests. This is testing modules' interactions with each other.
|
22
|
+
* * Large tests are functional tests. They test the whole system at once.
|
23
|
+
|
24
|
+
## Design
|
25
|
+
|
26
|
+
* Don't use anonymous arrays/hashes. Use objects. We're in rubyland.
|
27
|
+
* Defining classes is cheap. In Java, it'll take you thirty lines of code to make a small struct-like object. In Ruby, it takes us 3 or 4. Go for it! It's better to model the data you're moving around using classes than stuffing it into arrays and whatnot.
|
28
|
+
* * Exception: Returning two values can be okay. Justify it and make it crystal clear in the docs.
|
29
|
+
|
30
|
+
## Documentation
|
31
|
+
|
32
|
+
* Use YARD.
|
33
|
+
* Don't worry too hard about 1-liners.
|
34
|
+
* Document why, not what. If
|
35
|
+
|
36
|
+
## Code Style
|
37
|
+
|
38
|
+
* Strive for 80 columns.
|
39
|
+
* Never more than 100 columns.
|
40
|
+
* Don't make between 80-100 a habit.
|
41
|
+
* Descriptive method names.
|
42
|
+
* Parentheses in method declarations unless there are no arguments.
|
43
|
+
* Spaces around operators.
|
44
|
+
* No mathematic operator overloading. `[]`, `[]=` are fair game though.
|
45
|
+
|
46
|
+
## License
|
47
|
+
|
48
|
+
MIT. See LICENSE. No GPL code is allowed in this codebase.
|
49
|
+
|
50
|
+
By contributing code to the Amp Project, you agree to license your work under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "amp-front"
|
8
|
+
gem.summary = %Q{Generic front-end for Amp.}
|
9
|
+
gem.description = %Q{The generic front-end used by Amp. May be completely published as a generic library in the future, at which point a name change will be necessary.}
|
10
|
+
gem.email = "michael.j.edgar@dartmouth.edu"
|
11
|
+
gem.homepage = "http://github.com/michaeledgar/amp-front"
|
12
|
+
gem.authors = ["Michael Edgar"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "yard", ">= 0"
|
15
|
+
gem.add_development_dependency "cucumber", ">= 0"
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rake/testtask'
|
36
|
+
Rake::TestTask.new do |t|
|
37
|
+
t.libs << "test"
|
38
|
+
t.test_files = FileList['test/**/test*.rb']
|
39
|
+
t.verbose = true
|
40
|
+
end
|
41
|
+
|
42
|
+
task :spec => :check_dependencies
|
43
|
+
|
44
|
+
begin
|
45
|
+
require 'cucumber/rake/task'
|
46
|
+
Cucumber::Rake::Task.new(:features)
|
47
|
+
|
48
|
+
task :features => :check_dependencies
|
49
|
+
rescue LoadError
|
50
|
+
task :features do
|
51
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
task :default => [:spec, :test, :features]
|
56
|
+
|
57
|
+
begin
|
58
|
+
require 'yard'
|
59
|
+
YARD::Rake::YardocTask.new
|
60
|
+
rescue LoadError
|
61
|
+
task :yardoc do
|
62
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
63
|
+
end
|
64
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Command Dispatch Design Doc
|
2
|
+
|
3
|
+
This design document intends to describe how commands will be dispatched
|
4
|
+
by Amp.
|
5
|
+
|
6
|
+
The user's invocations are in the form:
|
7
|
+
|
8
|
+
amp --logging=WARN add --force some_file
|
9
|
+
amp config set key value --fiddler="fiddly"
|
10
|
+
amp git twiddle --dee="x" --dum="y"
|
11
|
+
|
12
|
+
## Command classes
|
13
|
+
|
14
|
+
Each command is its own class. It must inherit (either directly or indirectly)
|
15
|
+
from Amp::Command::Base.
|
16
|
+
|
17
|
+
Commands are discovered by looking up the class with the name of the requested
|
18
|
+
command. Thus, the three amp invocations above would run the following (hypothetical)
|
19
|
+
code to create the command object:
|
20
|
+
|
21
|
+
cmd = Amp::Command::Add.new
|
22
|
+
cmd = Amp::Command::Config::Set.new
|
23
|
+
cmd = Amp::Command::Git::Twiddle.new
|
24
|
+
|
25
|
+
## Command Groups
|
26
|
+
|
27
|
+
There are a number of commands that will be built into Amp Core: help, add, commit, push,
|
28
|
+
and so on. These will always be searched first.
|
29
|
+
|
30
|
+
Then, we go to command groups. Each plugin comprises a command group, and the Amp::Plugin
|
31
|
+
subclass that defines a plugin declares a way to access its commands as a group.
|
32
|
+
|
33
|
+
Each plugin's command groups are searched using the names provided on the command line, such
|
34
|
+
as "config set key value --fiddler='fiddly'". Every plugin (hg, git, server, etc.) will be
|
35
|
+
searched for a "config" command, then a "config set" command, then a "commit set key" command,
|
36
|
+
and a "config set key value" command. The results are collected, and then only the most
|
37
|
+
specific matches are kept. If the user has defined a "default command group", and one of the
|
38
|
+
resulting matches is in that default command group, it is moved to the top of the choice list.
|
39
|
+
The rest are sorted by how often they have been invoked by the user. This list is presented
|
40
|
+
to the user, and the user picks which command they intended.
|
41
|
+
|
42
|
+
Partial matches *do not match*. Thus, if the user entered:
|
43
|
+
|
44
|
+
amp config --fiddler='fiddly'
|
45
|
+
|
46
|
+
Then the following commands would match:
|
47
|
+
|
48
|
+
Amp::Command::Config
|
49
|
+
Amp::Command::GitPlugin::Config
|
50
|
+
Amp::Command::HgPlugin::Config
|
51
|
+
|
52
|
+
But the following commands would not:
|
53
|
+
|
54
|
+
Amp::Command::Config::Set
|
55
|
+
Amp::Command::Config::Get
|
56
|
+
Amp::Command::GitPlugin::Config::Set
|
57
|
+
Amp::Command::GitPlugin::Config::Get::ByKey
|
58
|
+
|
59
|
+
## Command definition
|
60
|
+
|
61
|
+
A command definition is roughly as follows:
|
62
|
+
|
63
|
+
command "search" do
|
64
|
+
# This command has a repository. Pull in helpers for this common
|
65
|
+
# case, and add the --repository (-R) command-line option.
|
66
|
+
has_repo
|
67
|
+
|
68
|
+
# Define options using Trollop syntax
|
69
|
+
opt :verbose, "Verbose output", :type => :boolean
|
70
|
+
opt :query, "The query to search for", :type => :string
|
71
|
+
|
72
|
+
# Use macros to quickly add features
|
73
|
+
has_paging_options
|
74
|
+
|
75
|
+
# Add validations
|
76
|
+
validates_length_of :query, :in => 4..32
|
77
|
+
validates_inclusion_of :query
|
78
|
+
validates_has_repository # validates :repository option
|
79
|
+
|
80
|
+
# specify on_call
|
81
|
+
on_call do
|
82
|
+
repo = self.repository
|
83
|
+
p repo.search(options[:query])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
When the command runs, its on\_call block is instance\_eval'd by the command
|
88
|
+
object. That way, any helper methods defined by the command class can be used
|
89
|
+
in the on\_call block. One particularly useful helper for on\_call is "repository"
|
90
|
+
which will get the current repository, regardless of type. This is provided by
|
91
|
+
amp-core.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Amp Dependancies Design Doc
|
2
|
+
|
3
|
+
Amp has so far avoided Rubygems dependancies, due to performance concerns.
|
4
|
+
Required libraries are included wholesale; so far all such libraires have
|
5
|
+
been modified in some way.
|
6
|
+
|
7
|
+
## Dependancies
|
8
|
+
|
9
|
+
### Maruku
|
10
|
+
|
11
|
+
Maruku is a pure ruby markdown interpreter.
|
12
|
+
Amp uses a highly hacked version of Maruku
|
13
|
+
(it adds an output form with ANSI color codes)
|
14
|
+
that was trimmed down to try to make it lightweight.
|
15
|
+
|
16
|
+
### Trollop
|
17
|
+
|
18
|
+
Trollop is a command-line parsing library.
|
19
|
+
It didn't originally expose its --help behavior,
|
20
|
+
which is really nice because it lists options automatically.
|
21
|
+
The Amp version exposes the parser object so we can run parser.educate!.
|
22
|
+
|
23
|
+
## The Case Against Rubygems
|
24
|
+
|
25
|
+
Loading Rubygems on each invocation of amp adds at least 150-500ms+ to each invocation.
|
26
|
+
With the current plugin architecture, we require users to either use rubygems,
|
27
|
+
or individually download each repo and load plugins manually using their Ampfile.
|
28
|
+
An installer script down the line could automate the latter option.
|
29
|
+
|
30
|
+
### Bundler
|
31
|
+
|
32
|
+
Bundler (require 'bundler/setup') adds another 400ms+ to startup time.
|
33
|
+
A Gemfile is used to streamline gem installation, but it is not used by
|
34
|
+
Amp itself.
|
35
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Amp Frontend Plugin Design Doc
|
2
|
+
|
3
|
+
This document intends to specify how plugins will be managed by the generic
|
4
|
+
Amp front-end.
|
5
|
+
|
6
|
+
## Amp::Plugin Subclasses
|
7
|
+
|
8
|
+
By tracking the subclasses (and subclasses of subclasses) of Amp::Plugin, we
|
9
|
+
can look up all plugins that are loaded. Thus, to register a plugin, one simply
|
10
|
+
needs to load a Ruby file that contains the definition of an Amp::Plugin subclass.
|
11
|
+
|
12
|
+
The list of all plugins can be retrieved with the following Ruby code:
|
13
|
+
|
14
|
+
Amp::Plugin.all_plugins
|
15
|
+
|
16
|
+
Which is simply an accessor to an ivar on the Amp::Plugin metaclass. Following
|
17
|
+
the Ruby convention of preferring convention over configuration, the default
|
18
|
+
Ampfile contains the following, single statement:
|
19
|
+
|
20
|
+
Amp::Plugin.load_rubygems_plugins
|
21
|
+
|
22
|
+
which, when run, all gems are searched for the file 'amp/plugin.rb'. All such files
|
23
|
+
are loaded in an arbitrary order.
|
24
|
+
|
25
|
+
If a plugin is automatically loaded that you *do not* wish to be loaded, you
|
26
|
+
will have to require each one individually. You do not need to manually load
|
27
|
+
amp-core. amp-core will be loaded first.
|
28
|
+
|
29
|
+
## Plugin Callbacks
|
30
|
+
|
31
|
+
Each plugin class in `Amp::Plugin.all_plugins` is instantiated with the current
|
32
|
+
global configuration as the sole parameter:
|
33
|
+
|
34
|
+
class Amp::Plugin::Git < Amp::Plugin::Base
|
35
|
+
def initialize(settings)
|
36
|
+
puts "oh noez" if settings[:failboatz]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Or the simpler:
|
41
|
+
|
42
|
+
amp_plugin 'git' do
|
43
|
+
init do |settings|
|
44
|
+
puts "oh noez" if settings[:failboatz]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
Feature: Help Command
|
2
|
+
In order to learn to use Amp
|
3
|
+
A user can use the help command
|
4
|
+
to be delivered useful information
|
5
|
+
|
6
|
+
Scenario: Running help with no parameters
|
7
|
+
Given the argument help
|
8
|
+
When I run dispatch
|
9
|
+
Then I should see "Thanks for using Amp!"
|
10
|
+
|
11
|
+
Scenario: Running help commands
|
12
|
+
Given the argument help
|
13
|
+
And the argument commands
|
14
|
+
When I run dispatch
|
15
|
+
Then I should see "Prints the help for the program."
|
16
|
+
And I should see "^help"
|
17
|
+
|
18
|
+
Scenario: Running help ampfiles
|
19
|
+
Given the argument help
|
20
|
+
And the argument ampfiles
|
21
|
+
When I run dispatch
|
22
|
+
Then I should see "What'?s an Ampfile?"
|
23
|
+
|
24
|
+
Scenario: Running help new-commands
|
25
|
+
Given the argument help
|
26
|
+
And the argument new-commands
|
27
|
+
When I run dispatch
|
28
|
+
Then I should see "commands are the driving force behind"
|
29
|
+
|
30
|
+
Scenario: Running help help
|
31
|
+
Given the argument help
|
32
|
+
And the argument help
|
33
|
+
When I run dispatch
|
34
|
+
Then I should see " --help, -h"
|
35
|
+
And I should see "Show this message"
|
36
|
+
And I should see "Prints the help for the program."
|