cli-forge 0.0.0 → 0.1.0
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.
- data/.gitignore +1 -0
- data/.travis.yml +5 -1
- data/Gemfile +27 -8
- data/Guardfile +3 -5
- data/README.md +1 -1
- data/Rakefile +3 -3
- data/cli-forge.gemspec +1 -1
- data/lib/cli_forge.rb +27 -0
- data/lib/cli_forge/autoload_convention.rb +4 -1
- data/lib/cli_forge/bin_command.rb +17 -0
- data/lib/cli_forge/command_set.rb +38 -0
- data/lib/cli_forge/configuration.rb +66 -0
- data/lib/cli_forge/default_configuration.rb +27 -0
- data/lib/cli_forge/embedded_commands.rb +3 -0
- data/lib/cli_forge/embedded_commands/help.rb +11 -0
- data/lib/cli_forge/runner.rb +42 -0
- data/lib/cli_forge/version.rb +1 -1
- data/spec/common/fixture_config.rb +13 -0
- data/spec/fixtures/autoload_convention/abc_one_two_three.rb +5 -0
- data/spec/fixtures/bins/foo-bar +0 -0
- data/spec/fixtures/bins2/foo-bar +0 -0
- data/spec/fixtures/bins2/foo-too +0 -0
- data/spec/fixtures/bins3/foo-bar +0 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/unit/cli_forge/autoload_convention/const_missing_spec.rb +6 -0
- data/spec/unit/cli_forge/class_methods/caller_path_spec.rb +38 -0
- data/spec/unit/cli_forge/class_methods/guess_bin_name_spec.rb +17 -0
- data/spec/unit/cli_forge/class_methods/start_spec.rb +93 -0
- data/spec/unit/cli_forge/command_set/element_reader_spec.rb +55 -0
- data/spec/unit/cli_forge/configuration/register_argument_filter_spec.rb +26 -0
- data/spec/unit/cli_forge/configuration/register_command_spec.rb +26 -0
- data/spec/unit/cli_forge/configuration/remove_argument_filter_spec.rb +29 -0
- data/spec/unit/cli_forge/configuration/remove_command_spec.rb +30 -0
- data/spec/unit/cli_forge/runner/start_spec.rb +28 -0
- data/tasks/spec/coverage.rake +1 -1
- data/tasks/spec/mutate.rake +122 -15
- metadata +31 -3
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
language: ruby
|
2
|
-
bundler_args: --without debugging
|
2
|
+
bundler_args: --without debugging guard
|
3
3
|
script: bundle exec rake spec:ci
|
4
4
|
rvm:
|
5
5
|
- 1.8.7
|
@@ -11,3 +11,7 @@ rvm:
|
|
11
11
|
- jruby-18mode
|
12
12
|
- jruby-19mode
|
13
13
|
- ree
|
14
|
+
- topaz
|
15
|
+
matrix:
|
16
|
+
allow_failures:
|
17
|
+
- rvm: topaz
|
data/Gemfile
CHANGED
@@ -13,7 +13,10 @@ group :test do
|
|
13
13
|
|
14
14
|
# The preferred code mutation library.
|
15
15
|
# MIT License - https://github.com/mbj/mutant/blob/master/LICENSE
|
16
|
-
gem "mutant", "
|
16
|
+
gem "mutant", "~> 0.2", ">= 0.2.20", :platforms => :ruby_19
|
17
|
+
|
18
|
+
# Dependency of mutant; this version fixes some breaking source gen bugs.
|
19
|
+
gem "to_source", ">= 0.2.20"
|
17
20
|
|
18
21
|
# Cover all the things - https://github.com/colszowka/simplecov
|
19
22
|
# MIT License - https://github.com/colszowka/simplecov/blob/master/LICENSE
|
@@ -34,7 +37,7 @@ group :debugging do
|
|
34
37
|
gem "debugger", "~> 1.3", :platforms => :mri
|
35
38
|
end
|
36
39
|
|
37
|
-
group :
|
40
|
+
group :guard do
|
38
41
|
# A generic file system event handler; spin it up and see the tests fly
|
39
42
|
# MIT License - https://github.com/guard/guard/blob/master/LICENSE
|
40
43
|
gem "guard", "~> 1.6"
|
@@ -51,11 +54,27 @@ group :development do
|
|
51
54
|
# MIT License - https://github.com/guard/guard-rspec/blob/master/LICENSE
|
52
55
|
gem "guard-rspec", "~> 2.4"
|
53
56
|
|
54
|
-
#
|
55
|
-
# MIT License - https://github.com/snaka/ruby_gntp/blob/master/lib/ruby_gntp.rb
|
56
|
-
gem "ruby_gntp", "~> 0.3"
|
57
|
-
|
58
|
-
# FS event hooks for OS X
|
57
|
+
# File system event hooks for OS X
|
59
58
|
# MIT License - https://github.com/thibaudgg/rb-fsevent/blob/master/LICENSE
|
60
|
-
gem "rb-fsevent", "~> 0.9"
|
59
|
+
gem "rb-fsevent", "~> 0.9"
|
60
|
+
|
61
|
+
# File system event hooks for Linux
|
62
|
+
# MIT License - https://github.com/nex3/rb-inotify/blob/master/MIT-LICENSE
|
63
|
+
gem "rb-inotify", "~> 0.9"
|
64
|
+
|
65
|
+
# File system event hooks for Windows
|
66
|
+
# MIT License - https://github.com/stereobooster/rb-fchange/blob/master/LICENSE.md
|
67
|
+
gem "rb-fchange", "~> 0.0"
|
68
|
+
|
69
|
+
# OS X 10.8+ notification center support
|
70
|
+
# MIT License - https://github.com/Springest/terminal-notifier-guard#license
|
71
|
+
gem "terminal-notifier-guard", "~> 1.5"
|
72
|
+
|
73
|
+
# libnotify bindings (Linux)
|
74
|
+
# MIT License - https://github.com/splattael/libnotify/blob/master/LICENSE
|
75
|
+
gem "libnotify", "~> 0.8"
|
76
|
+
|
77
|
+
# notifu adapter (Windows)
|
78
|
+
# Unknown License
|
79
|
+
gem "rb-notifu", "~> 0.0"
|
61
80
|
end
|
data/Guardfile
CHANGED
@@ -3,8 +3,6 @@ GLOBAL_SPEC_FILES = [
|
|
3
3
|
".rspec",
|
4
4
|
%r{^spec/.*_helper\.rb$},
|
5
5
|
%r{^spec/common/.*\.rb$},
|
6
|
-
"lib/cli_forge.rb",
|
7
|
-
"lib/cli_forge/autoload_convention.rb",
|
8
6
|
]
|
9
7
|
|
10
8
|
def specs_for_path(path)
|
@@ -26,9 +24,9 @@ guard "spork", rspec_port: 2733 do
|
|
26
24
|
end
|
27
25
|
|
28
26
|
guard "rspec", cli: "--drb --drb-port 2733" do
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
watch("lib/cli_forge.rb") { "spec" }
|
28
|
+
watch("lib/cli_forge/autoload_convention.rb") { "spec" }
|
29
|
+
watch(%r{^spec/fixtures/.*\.rb$}) { "spec" }
|
32
30
|
|
33
31
|
watch(%r{^spec/.+_spec\.rb$})
|
34
32
|
watch(%r{^lib/(.+)\.rb$}) { |m| specs_for_path(m[1]) }
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
CLI Forge [](https://rubygems.org/gems/cli-forge) [](http://travis-ci.org/nevir/cli-forge) [](https://rubygems.org/gems/cli-forge) [](http://travis-ci.org/nevir/cli-forge) [](https://gemnasium.com/nevir/cli-forge) [](https://coveralls.io/r/nevir/cli-forge) [](https://codeclimate.com/github/nevir/cli-forge)
|
2
2
|
=========
|
3
3
|
|
4
4
|
Beat your CLI tool suites into submission!
|
data/Rakefile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
|
4
|
-
|
5
|
-
Dir["#{
|
4
|
+
PROJECT_ROOT = File.expand_path("..", __FILE__)
|
5
|
+
Dir["#{PROJECT_ROOT}/tasks/**/*.rake"].each do |path|
|
6
6
|
load path
|
7
7
|
end
|
8
8
|
|
9
|
-
$LOAD_PATH.unshift File.join(
|
9
|
+
$LOAD_PATH.unshift File.join(PROJECT_ROOT, "lib")
|
10
10
|
|
11
11
|
|
12
12
|
desc "Run the full test suite"
|
data/cli-forge.gemspec
CHANGED
@@ -5,7 +5,7 @@ require File.expand_path("../lib/cli_forge/version", __FILE__)
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "cli-forge"
|
7
7
|
gem.version = CLIForge::VERSION
|
8
|
-
gem.homepage = "
|
8
|
+
gem.homepage = "https://github.com/nevir/cli-forge"
|
9
9
|
gem.summary = "Beat your CLI tool suites into submission!"
|
10
10
|
gem.description = "A library for building CLI tools that are intended to be easily extensible (git style)"
|
11
11
|
gem.author = "Ian MacLeod"
|
data/lib/cli_forge.rb
CHANGED
@@ -2,4 +2,31 @@ require "cli_forge/autoload_convention"
|
|
2
2
|
|
3
3
|
module CLIForge
|
4
4
|
extend CLIForge::AutoloadConvention
|
5
|
+
|
6
|
+
def self.start(bin_name=nil, &block)
|
7
|
+
config = CLIForge::DefaultConfiguration.new
|
8
|
+
|
9
|
+
config.search_paths = Array(caller_path(caller.first))
|
10
|
+
config.search_paths += ENV["PATH"].split(":")
|
11
|
+
config.search_paths.uniq!
|
12
|
+
|
13
|
+
block.call(config) if block
|
14
|
+
|
15
|
+
config.bin_name ||= bin_name || guess_bin_name
|
16
|
+
|
17
|
+
CLIForge::Runner.new(config).start(ARGV)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.guess_bin_name
|
21
|
+
File.basename($0)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.caller_path(stack_line)
|
25
|
+
return unless stack_line
|
26
|
+
stack_path = stack_line.split(":").first
|
27
|
+
return if stack_path =~ /^\(.*\)$/
|
28
|
+
|
29
|
+
File.expand_path(File.dirname(stack_path))
|
30
|
+
end
|
31
|
+
|
5
32
|
end
|
@@ -15,7 +15,10 @@ module CLIForge
|
|
15
15
|
def const_missing(sym)
|
16
16
|
full_sym = "#{self.name}::#{sym}"
|
17
17
|
path_parts = full_sym.split("::").map { |part|
|
18
|
-
part.gsub
|
18
|
+
part.gsub! /([^A-Z])([A-Z]+)/, "\\1_\\2" # OneTwo -> One_Two
|
19
|
+
part.gsub! /([A-Z]+)([A-Z][^A-Z]+)/, "\\1_\\2" # ABCOne -> ABC_One
|
20
|
+
|
21
|
+
part.downcase
|
19
22
|
}
|
20
23
|
|
21
24
|
require File.join(*path_parts)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class CLIForge::CommandSet
|
2
|
+
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def [](command_name)
|
8
|
+
if embedded_command = @config.embedded_commands[command_name.to_sym]
|
9
|
+
return embedded_command
|
10
|
+
end
|
11
|
+
|
12
|
+
target_bin = @config.bin_name + @config.bin_separator + command_name.to_s
|
13
|
+
commands = find_bin_commands(target_bin)
|
14
|
+
|
15
|
+
# if commands.size > 1
|
16
|
+
# TODO: Warn
|
17
|
+
# end
|
18
|
+
|
19
|
+
commands.first
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def clean_search_paths
|
25
|
+
@config.search_paths.map { |path| File.expand_path(path) }.uniq
|
26
|
+
end
|
27
|
+
|
28
|
+
# Given a glob expression for the bin name, find any matching commands on the
|
29
|
+
# search paths.
|
30
|
+
def find_bin_commands(glob_expression)
|
31
|
+
clean_search_paths.reduce([]) { |paths, search_path|
|
32
|
+
paths + Dir["#{search_path}/#{glob_expression}"].map { |found_bin|
|
33
|
+
CLIForge::BinCommand.new(found_bin)
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class CLIForge::Configuration
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@default_command = "help"
|
5
|
+
@bin_separator = "-"
|
6
|
+
|
7
|
+
@search_paths = []
|
8
|
+
@argument_filters = {}
|
9
|
+
@embedded_commands = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# The name of the binary for this program; used to determine where to find
|
13
|
+
# sub-commands, help, etc.
|
14
|
+
attr_accessor :bin_name
|
15
|
+
|
16
|
+
# If no command is given, fall back to this.
|
17
|
+
attr_accessor :default_command
|
18
|
+
|
19
|
+
# Paths to search for sub command binaries.
|
20
|
+
attr_accessor :search_paths
|
21
|
+
|
22
|
+
# Separator to use when searching for subcommands.
|
23
|
+
attr_accessor :bin_separator
|
24
|
+
|
25
|
+
|
26
|
+
# Argument Filters
|
27
|
+
# ----------------
|
28
|
+
# Argument filters are named procs that are given the current command's
|
29
|
+
# arguments and have the opportunity to modify them.
|
30
|
+
#
|
31
|
+
# The return value of the proc is used as the new argument set.
|
32
|
+
def register_argument_filter(name, &block)
|
33
|
+
@argument_filters[name.to_sym] = block
|
34
|
+
end
|
35
|
+
|
36
|
+
# Remove a previously registered argument filter.
|
37
|
+
def remove_argument_filter(name)
|
38
|
+
@argument_filters.delete(name.to_sym)
|
39
|
+
end
|
40
|
+
|
41
|
+
# The active argument filters.
|
42
|
+
def argument_filters
|
43
|
+
@argument_filters.values
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Embedded Sub Commands
|
48
|
+
# ---------------------
|
49
|
+
# In addition to exposing sub commands via external bins, you can register
|
50
|
+
# specific commands directly:
|
51
|
+
def register_command(name, command_object)
|
52
|
+
@embedded_commands[name.to_sym] = command_object
|
53
|
+
end
|
54
|
+
|
55
|
+
# Remove a previosuly registered command.
|
56
|
+
#
|
57
|
+
# This _does not_ block an external bin by the same name from being run!
|
58
|
+
def remove_command(name)
|
59
|
+
@embedded_commands.delete(name.to_sym)
|
60
|
+
end
|
61
|
+
|
62
|
+
# The set of embedded commands. Prefer using `register_command` and
|
63
|
+
# `remove_command`.
|
64
|
+
attr_reader :embedded_commands
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class CLIForge::DefaultConfiguration < CLIForge::Configuration
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
|
6
|
+
with_help_command
|
7
|
+
with_implicit_help_flags
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Help is one of those global commands that pretty much everyone wants
|
13
|
+
def with_help_command
|
14
|
+
register_command :help, CLIForge::EmbeddedCommands::Help.new(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Passing a help flag to any command results in us calling the help command.
|
18
|
+
def with_implicit_help_flags
|
19
|
+
register_argument_filter(:help_flags) { |arguments|
|
20
|
+
new_arguments = arguments - ["--help", "-h", "/?"]
|
21
|
+
new_arguments.unshift("help") if new_arguments.length != arguments.length
|
22
|
+
|
23
|
+
new_arguments
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class CLIForge::Runner
|
2
|
+
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def start(arguments)
|
8
|
+
arguments = filter_arguments(arguments.dup)
|
9
|
+
command_name = pop_command(arguments) || @config.default_command
|
10
|
+
command_set = CLIForge::CommandSet.new(@config)
|
11
|
+
|
12
|
+
unless command = command_set[command_name]
|
13
|
+
# TODO: Output!
|
14
|
+
command = command_set[@config.default_command]
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: Output!
|
18
|
+
return 1 unless command
|
19
|
+
|
20
|
+
command.call(arguments)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def filter_arguments(arguments)
|
26
|
+
@config.argument_filters.each do |argument_filter|
|
27
|
+
arguments = argument_filter.call(arguments)
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: Validate args
|
31
|
+
|
32
|
+
arguments
|
33
|
+
end
|
34
|
+
|
35
|
+
def pop_command(arguments)
|
36
|
+
index = arguments.find_index { |a| !a.start_with? "-" }
|
37
|
+
return unless index
|
38
|
+
|
39
|
+
arguments.delete_at(index)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/cli_forge/version.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
shared_context "fixture config" do
|
2
|
+
|
3
|
+
let(:config) {
|
4
|
+
CLIForge::Configuration.new.tap do |config|
|
5
|
+
config.bin_name = "foo"
|
6
|
+
|
7
|
+
config.search_paths << File.join(FIXTURE_ROOT, "bins")
|
8
|
+
config.search_paths << File.join(FIXTURE_ROOT, "bins2")
|
9
|
+
config.search_paths << File.join(FIXTURE_ROOT, "bins3")
|
10
|
+
end
|
11
|
+
}
|
12
|
+
|
13
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/spec/spec_helper.rb
CHANGED
@@ -16,7 +16,8 @@ end
|
|
16
16
|
|
17
17
|
Spork.prefork do
|
18
18
|
# Allow requires relative to the spec dir
|
19
|
-
SPEC_ROOT
|
19
|
+
SPEC_ROOT = File.expand_path("..", __FILE__)
|
20
|
+
FIXTURE_ROOT = File.join(SPEC_ROOT, "fixtures")
|
20
21
|
$LOAD_PATH << SPEC_ROOT
|
21
22
|
|
22
23
|
require "rspec"
|
@@ -40,6 +40,12 @@ describe CLIForge::AutoloadConvention, "#const_missing" do
|
|
40
40
|
expect(namespace::ALLCAPS).to eq(:yelling)
|
41
41
|
end
|
42
42
|
|
43
|
+
it "should split ACRONYMSEndingWithRegularNames" do
|
44
|
+
namespace.should_receive(:require).with("fixtures/autoload_convention/abc_one_two_three").and_call_original
|
45
|
+
|
46
|
+
expect(namespace::ABCOneTwoThree).to eq(:you_can_count!)
|
47
|
+
end
|
48
|
+
|
43
49
|
it "shouldn't pick up constants in parent namespaces" do
|
44
50
|
namespace.should_receive(:require).with("fixtures/autoload_convention/string").and_call_original
|
45
51
|
|
@@ -0,0 +1,38 @@
|
|
1
|
+
describe CLIForge, ".caller_path" do
|
2
|
+
|
3
|
+
def spec_caller_line
|
4
|
+
caller.first
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should extract the current platform's stack format" do
|
8
|
+
caller_line = spec_caller_line
|
9
|
+
path = described_class.caller_path(caller_line)
|
10
|
+
|
11
|
+
expect(path).to eq(File.dirname(__FILE__)), "Extracted #{path.inspect} from #{caller_line.inspect}"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should extract paths from well formed MRI stack lines" do
|
15
|
+
path = described_class.caller_path("/usr/bin/foo:6:in `<main>'")
|
16
|
+
|
17
|
+
expect(path).to eq("/usr/bin")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should expand relative paths" do
|
21
|
+
path = described_class.caller_path("bin/bar:6:in `<main>'")
|
22
|
+
|
23
|
+
expect(path).to eq(File.expand_path("bin"))
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not freak out if given nil" do
|
27
|
+
path = described_class.caller_path(nil)
|
28
|
+
|
29
|
+
expect(path).to eq(nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not expose fake paths as the current path" do
|
33
|
+
path = described_class.caller_path("(irb):3:in `irb_binding'")
|
34
|
+
|
35
|
+
expect(path).to eq(nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
describe CLIForge, ".guess_bin_name" do
|
2
|
+
|
3
|
+
it "should return the basename of $0" do
|
4
|
+
old_name = $0
|
5
|
+
|
6
|
+
begin
|
7
|
+
$0 = "/foo/bar/baz-biff"
|
8
|
+
expect(described_class.send(:guess_bin_name)).to eq("baz-biff")
|
9
|
+
|
10
|
+
ensure
|
11
|
+
$0 = old_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should freak out if we don't have a reasonable $0"
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
describe CLIForge, ".start" do
|
2
|
+
|
3
|
+
let(:mock_runner) {
|
4
|
+
double("CLIForge::Runner").tap do |runner|
|
5
|
+
runner.stub(:start)
|
6
|
+
end
|
7
|
+
}
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
CLIForge::Runner.stub(:new) { |config|
|
11
|
+
@config = config
|
12
|
+
mock_runner
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Stub out our environment
|
17
|
+
around(:each) do |spec|
|
18
|
+
old_path, old_argv = ENV["PATH"], ARGV
|
19
|
+
|
20
|
+
begin
|
21
|
+
spec.run
|
22
|
+
ensure
|
23
|
+
ENV["PATH"] = old_path
|
24
|
+
Object.send(:remove_const, :ARGV)
|
25
|
+
::ARGV = old_argv
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
it "should not require any arguments" do
|
31
|
+
mock_runner.should_receive(:start)
|
32
|
+
described_class.should_receive(:guess_bin_name)
|
33
|
+
|
34
|
+
described_class.start
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should pass the configuration to the given block" do
|
38
|
+
mock_runner.should_receive(:start)
|
39
|
+
described_class.should_not_receive(:guess_bin_name)
|
40
|
+
|
41
|
+
described_class.start do |config|
|
42
|
+
config.bin_name = "foo-bar"
|
43
|
+
end
|
44
|
+
|
45
|
+
expect(@config.bin_name).to eq("foo-bar")
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "special-cased configuration properties" do
|
49
|
+
|
50
|
+
it "should consume the passed name if given" do
|
51
|
+
mock_runner.should_receive(:start)
|
52
|
+
described_class.should_not_receive(:guess_bin_name)
|
53
|
+
|
54
|
+
described_class.start("awesomesauce")
|
55
|
+
|
56
|
+
expect(@config.bin_name).to eq("awesomesauce")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should extract the caller path" do
|
60
|
+
ENV["PATH"] = ""
|
61
|
+
|
62
|
+
described_class.start
|
63
|
+
|
64
|
+
expect(@config.search_paths).to eq([
|
65
|
+
File.dirname(__FILE__)
|
66
|
+
])
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should pull search paths in order from PATH" do
|
70
|
+
ENV["PATH"] = "/usr/bin:/usr/local/bin:/foo/bar:/usr/bin"
|
71
|
+
described_class.stub(:caller_path) { nil }
|
72
|
+
|
73
|
+
described_class.start
|
74
|
+
|
75
|
+
expect(@config.search_paths).to eq([
|
76
|
+
"/usr/bin",
|
77
|
+
"/usr/local/bin",
|
78
|
+
"/foo/bar"
|
79
|
+
])
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should extract ARGV" do
|
83
|
+
Object.send(:remove_const, :ARGV)
|
84
|
+
::ARGV = ["my-bin", "sub-command", "--stuff"]
|
85
|
+
|
86
|
+
mock_runner.should_receive(:start).with(["my-bin", "sub-command", "--stuff"])
|
87
|
+
|
88
|
+
described_class.start
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
describe CLIForge::CommandSet, "#[]" do
|
2
|
+
include_context "fixture config"
|
3
|
+
|
4
|
+
subject {
|
5
|
+
described_class.new(config)
|
6
|
+
}
|
7
|
+
|
8
|
+
it "should return nil if no command was found" do
|
9
|
+
expect(subject[:missing_command]).to eq(nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "bin commands" do
|
13
|
+
|
14
|
+
it "should return the first matching command on the search paths" do
|
15
|
+
command = subject[:bar]
|
16
|
+
|
17
|
+
expect(command).to be_a(CLIForge::BinCommand)
|
18
|
+
expect(command.path).to eq(File.join(FIXTURE_ROOT, "bins", "foo-bar"))
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should search subsequent search paths" do
|
22
|
+
command = subject[:too]
|
23
|
+
|
24
|
+
expect(command).to be_a(CLIForge::BinCommand)
|
25
|
+
expect(command.path).to eq(File.join(FIXTURE_ROOT, "bins2", "foo-too"))
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should accept strings" do
|
29
|
+
command = subject["bar"]
|
30
|
+
|
31
|
+
expect(command).to be_a(CLIForge::BinCommand)
|
32
|
+
expect(command.path).to eq(File.join(FIXTURE_ROOT, "bins", "foo-bar"))
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "embedded commands" do
|
38
|
+
|
39
|
+
it "should return embedded commands" do
|
40
|
+
command = double("TestCommand")
|
41
|
+
config.register_command("my-command", command)
|
42
|
+
|
43
|
+
expect(subject["my-command"]).to be(command)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should prioritize embedded commands" do
|
47
|
+
command = double("ABetterBar")
|
48
|
+
config.register_command(:bar, command)
|
49
|
+
|
50
|
+
expect(subject[:bar]).to be(command)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
describe CLIForge::Configuration, "#register_argument_filter" do
|
2
|
+
|
3
|
+
it "should register the filter" do
|
4
|
+
subject.register_argument_filter(:hi) { "hi" }
|
5
|
+
|
6
|
+
expect(subject.argument_filters.size).to eq(1)
|
7
|
+
expect(subject.argument_filters[0].call).to eq("hi")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should allow you to register over existing commands" do
|
11
|
+
subject.register_argument_filter(:hi) { "hi" }
|
12
|
+
subject.register_argument_filter(:hi) { "bye" }
|
13
|
+
|
14
|
+
expect(subject.argument_filters.size).to eq(1)
|
15
|
+
expect(subject.argument_filters[0].call).to eq("bye")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should coerce string names to symbols" do
|
19
|
+
subject.register_argument_filter("hi") { "hi" }
|
20
|
+
subject.register_argument_filter(:hi) { "bye" }
|
21
|
+
|
22
|
+
expect(subject.argument_filters.size).to eq(1)
|
23
|
+
expect(subject.argument_filters[0].call).to eq("bye")
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
describe CLIForge::Configuration, "#register_command" do
|
2
|
+
|
3
|
+
it "should register the command" do
|
4
|
+
command = double("TestCommand")
|
5
|
+
subject.register_command(:foo, command)
|
6
|
+
|
7
|
+
expect(subject.embedded_commands[:foo]).to be(command)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should coerce string names to symbols" do
|
11
|
+
command = double("TestCommand")
|
12
|
+
subject.register_command("foo", command)
|
13
|
+
|
14
|
+
expect(subject.embedded_commands[:foo]).to be(command)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should allow you to register over existing commands" do
|
18
|
+
command1 = double("TestCommand1")
|
19
|
+
command2 = double("TestCommand2")
|
20
|
+
subject.register_command(:foo, command1)
|
21
|
+
subject.register_command(:foo, command2)
|
22
|
+
|
23
|
+
expect(subject.embedded_commands[:foo]).to be(command2)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
describe CLIForge::Configuration, "#remove_argument_filter" do
|
2
|
+
|
3
|
+
it "should remove the argument filter" do
|
4
|
+
expect(subject.argument_filters).to eq([])
|
5
|
+
|
6
|
+
subject.register_argument_filter(:hi) { "hi" }
|
7
|
+
subject.remove_argument_filter(:hi)
|
8
|
+
|
9
|
+
expect(subject.argument_filters).to eq([])
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not freak out if the argument filter doesn't exist" do
|
13
|
+
expect(subject.argument_filters).to eq([])
|
14
|
+
|
15
|
+
subject.remove_argument_filter(:bar)
|
16
|
+
|
17
|
+
expect(subject.argument_filters).to eq([])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should coerce string names to symbols" do
|
21
|
+
expect(subject.argument_filters).to eq([])
|
22
|
+
|
23
|
+
subject.register_argument_filter(:hi) { "hi" }
|
24
|
+
subject.remove_argument_filter("hi")
|
25
|
+
|
26
|
+
expect(subject.argument_filters).to eq([])
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
describe CLIForge::Configuration, "#remove_command" do
|
2
|
+
|
3
|
+
it "should remove the command" do
|
4
|
+
command = double("TestCommand")
|
5
|
+
subject.register_command(:foo, command)
|
6
|
+
subject.remove_command(:foo)
|
7
|
+
|
8
|
+
expect(subject.embedded_commands[:foo]).to eq(nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not freak out if the command doesn't exist" do
|
12
|
+
expect(subject.embedded_commands[:bar]).to eq(nil)
|
13
|
+
|
14
|
+
subject.remove_command(:bar)
|
15
|
+
|
16
|
+
expect(subject.embedded_commands[:bar]).to eq(nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should coerce string names to symbols" do
|
20
|
+
expect(subject.embedded_commands[:bar]).to eq(nil)
|
21
|
+
|
22
|
+
command = double("TestCommand")
|
23
|
+
subject.register_command(:bar, command)
|
24
|
+
subject.remove_command("bar")
|
25
|
+
|
26
|
+
expect(subject.embedded_commands[:bar]).to eq(nil)
|
27
|
+
expect(subject.embedded_commands["bar"]).to eq(nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
describe CLIForge::Runner, "#start" do
|
2
|
+
include_context "fixture config"
|
3
|
+
|
4
|
+
subject {
|
5
|
+
described_class.new(config)
|
6
|
+
}
|
7
|
+
|
8
|
+
let(:default_command) {
|
9
|
+
double("DefaultCommand").tap do |command|
|
10
|
+
command.stub(:call) { "Called and stuff" }
|
11
|
+
end
|
12
|
+
}
|
13
|
+
|
14
|
+
describe "edge cases" do
|
15
|
+
|
16
|
+
it "should not modify the input arguments" do
|
17
|
+
config.register_command(:default, default_command)
|
18
|
+
|
19
|
+
arguments = ["default", "and", "stuff"]
|
20
|
+
|
21
|
+
subject.start(arguments)
|
22
|
+
|
23
|
+
expect(arguments).to eq(["default", "and", "stuff"])
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/tasks/spec/coverage.rake
CHANGED
data/tasks/spec/mutate.rake
CHANGED
@@ -1,30 +1,21 @@
|
|
1
1
|
namespace :spec do
|
2
2
|
|
3
|
-
def mutant_supported?
|
4
|
-
return false unless RUBY_VERSION.start_with?("1.9")
|
5
|
-
|
6
|
-
begin
|
7
|
-
return false unless RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx"
|
8
|
-
rescue NameError
|
9
|
-
return false
|
10
|
-
end
|
11
|
-
|
12
|
-
true
|
13
|
-
end
|
14
|
-
|
15
3
|
desc "Runs tests with code mutation"
|
16
4
|
task :mutate, [:focus_on] do |t, args|
|
17
5
|
next unless mutant_supported?
|
18
6
|
|
19
7
|
require "cli_forge"
|
20
8
|
require "mutant"
|
9
|
+
require "mutant/constants"
|
10
|
+
require "mutant/mutation/filter/regexp"
|
21
11
|
|
22
12
|
# You can focus on a particular symbol/method by passing it to the task:
|
23
13
|
# rake spec:mutate[AutoloadConvention#const_missing], for example.
|
24
14
|
if args.focus_on
|
25
|
-
matcher =
|
15
|
+
matcher = matcher_for_filter(args.focus_on)
|
16
|
+
# Otherwise we're doing a full mutation suite
|
26
17
|
else
|
27
|
-
matcher =
|
18
|
+
matcher = all_matcher
|
28
19
|
end
|
29
20
|
|
30
21
|
# Mutant doesn't have a public scripting API yet; so we're cheating.
|
@@ -37,7 +28,7 @@ namespace :spec do
|
|
37
28
|
:strategy => Mutant::Strategy::Rspec::DM2.new(config),
|
38
29
|
:killer => Mutant::Killer::Rspec,
|
39
30
|
:matcher => matcher,
|
40
|
-
:filter =>
|
31
|
+
:filter => filter_for_specs,
|
41
32
|
:reporter => Mutant::Reporter::CLI.new(config)
|
42
33
|
)
|
43
34
|
|
@@ -47,4 +38,120 @@ namespace :spec do
|
|
47
38
|
end
|
48
39
|
end
|
49
40
|
|
41
|
+
def mutant_supported?
|
42
|
+
return false unless RUBY_VERSION.start_with?("1.9")
|
43
|
+
# TODO: Rubinius crashes under mutant:
|
44
|
+
# https://github.com/rubinius/rubinius/issues/2186
|
45
|
+
return false if RUBY_ENGINE == "rbx"
|
46
|
+
|
47
|
+
begin
|
48
|
+
return false unless RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx"
|
49
|
+
rescue NameError
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Also preloads the related constants
|
57
|
+
def matcher_for_filter(filter)
|
58
|
+
# Method on CLIForge?
|
59
|
+
if filter.start_with?(".") || filter.start_with?("#")
|
60
|
+
matcher = Mutant::Matcher.from_string("::CLIForge#{filter}")
|
61
|
+
|
62
|
+
# Or regular constant?
|
63
|
+
else
|
64
|
+
matcher = Mutant::Matcher.from_string("::CLIForge::#{filter}")
|
65
|
+
# Force that symbol to load
|
66
|
+
const = CLIForge
|
67
|
+
filter[/^[^#\.]+/].split("::").each do |const_name|
|
68
|
+
const = const.const_get(const_name.to_sym)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
matcher
|
73
|
+
end
|
74
|
+
|
75
|
+
def all_matcher
|
76
|
+
# Force everything to load
|
77
|
+
Dir["#{PROJECT_ROOT}/lib/cli_forge/**/*.rb"].each do |path|
|
78
|
+
require path[/lib.(cli_forge..+)\.rb$/, 1]
|
79
|
+
end
|
80
|
+
|
81
|
+
unit_folders = Dir["spec/unit/**/*/"].map { |f| f[/^spec.unit.(.+)./, 1] }
|
82
|
+
unit_constants = unit_folders.map do |folder|
|
83
|
+
folder.gsub(File::SEPARATOR, "::").gsub("_", "").downcase
|
84
|
+
end
|
85
|
+
|
86
|
+
Mutant::Matcher::ObjectSpace.new(/^CLIForge/)
|
87
|
+
end
|
88
|
+
|
89
|
+
def spec_filter_klass
|
90
|
+
# Avoid loading w/ rake
|
91
|
+
filter_klass = Class.new(Mutant::Mutation::Filter)
|
92
|
+
filter_klass.class_eval do
|
93
|
+
def initialize(regexp)
|
94
|
+
@regexp = regexp
|
95
|
+
end
|
96
|
+
|
97
|
+
def match?(mutation)
|
98
|
+
@regexp =~ mutation.subject.matcher.identification
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
filter_klass
|
103
|
+
end
|
104
|
+
|
105
|
+
def filter_for_specs
|
106
|
+
# Only run mutations for methods that we have specs for. Basic code
|
107
|
+
# coverage can cover simple cases.
|
108
|
+
spec_symbols = Dir["#{PROJECT_ROOT}/spec/unit/**/*_spec.rb"].map { |path|
|
109
|
+
path_to_symbol(path[/spec.unit.(.+)_spec\.rb$/, 1])
|
110
|
+
}
|
111
|
+
safe_symbols = spec_symbols.map { |s| Regexp.escape(s) }
|
112
|
+
|
113
|
+
spec_filter_klass.new(/^(#{safe_symbols.join("|")})$/i)
|
114
|
+
end
|
115
|
+
|
116
|
+
# convert a spec path back to a (case insensitive) symbol
|
117
|
+
def path_to_symbol(spec_path)
|
118
|
+
parts = spec_path.split(/[\/\\]/)
|
119
|
+
method = symbolicate_method_name(parts.pop)
|
120
|
+
if parts.last == "class_methods"
|
121
|
+
method = ".#{method}"
|
122
|
+
parts.pop
|
123
|
+
else
|
124
|
+
method = "##{method}"
|
125
|
+
end
|
126
|
+
|
127
|
+
parts.join("::").gsub("_", "") + method
|
128
|
+
end
|
129
|
+
|
130
|
+
# https://github.com/mbj/mutant/blob/master/lib/mutant/constants.rb
|
131
|
+
def postfix_expansions
|
132
|
+
# @postfix_expansions ||= Mutant::METHOD_POSTFIX_EXPANSIONS.invert
|
133
|
+
@postfix_expansions ||= {
|
134
|
+
'_predicate' => '?',
|
135
|
+
'_writer' => '=',
|
136
|
+
'_bang' => '!'
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
def operator_expansions
|
141
|
+
@operator_expansions ||= Mutant::OPERATOR_EXPANSIONS.invert
|
142
|
+
end
|
143
|
+
|
144
|
+
def symbolicate_method_name(method_name)
|
145
|
+
operator = operator_expansions[method_name.to_sym]
|
146
|
+
return operator.to_s if operator
|
147
|
+
|
148
|
+
postfix_expansions.each do |postfix, symbol|
|
149
|
+
if method_name.end_with? postfix
|
150
|
+
method_name = method_name[0...-postfix.size] + symbol.to_s
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
method_name
|
155
|
+
end
|
156
|
+
|
50
157
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli-forge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-07 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A library for building CLI tools that are intended to be easily extensible
|
15
15
|
(git style)
|
@@ -31,20 +31,42 @@ files:
|
|
31
31
|
- lib/cli-forge.rb
|
32
32
|
- lib/cli_forge.rb
|
33
33
|
- lib/cli_forge/autoload_convention.rb
|
34
|
+
- lib/cli_forge/bin_command.rb
|
35
|
+
- lib/cli_forge/command_set.rb
|
36
|
+
- lib/cli_forge/configuration.rb
|
37
|
+
- lib/cli_forge/default_configuration.rb
|
38
|
+
- lib/cli_forge/embedded_commands.rb
|
39
|
+
- lib/cli_forge/embedded_commands/help.rb
|
40
|
+
- lib/cli_forge/runner.rb
|
34
41
|
- lib/cli_forge/version.rb
|
42
|
+
- spec/common/fixture_config.rb
|
43
|
+
- spec/fixtures/autoload_convention/abc_one_two_three.rb
|
35
44
|
- spec/fixtures/autoload_convention/allcaps.rb
|
36
45
|
- spec/fixtures/autoload_convention/mismatched.rb
|
37
46
|
- spec/fixtures/autoload_convention/multi_token.rb
|
38
47
|
- spec/fixtures/autoload_convention/single.rb
|
39
48
|
- spec/fixtures/autoload_convention/string.rb
|
49
|
+
- spec/fixtures/bins/foo-bar
|
50
|
+
- spec/fixtures/bins2/foo-bar
|
51
|
+
- spec/fixtures/bins2/foo-too
|
52
|
+
- spec/fixtures/bins3/foo-bar
|
40
53
|
- spec/spec_helper.rb
|
41
54
|
- spec/unit/cli_forge/autoload_convention/const_missing_spec.rb
|
55
|
+
- spec/unit/cli_forge/class_methods/caller_path_spec.rb
|
56
|
+
- spec/unit/cli_forge/class_methods/guess_bin_name_spec.rb
|
57
|
+
- spec/unit/cli_forge/class_methods/start_spec.rb
|
58
|
+
- spec/unit/cli_forge/command_set/element_reader_spec.rb
|
59
|
+
- spec/unit/cli_forge/configuration/register_argument_filter_spec.rb
|
60
|
+
- spec/unit/cli_forge/configuration/register_command_spec.rb
|
61
|
+
- spec/unit/cli_forge/configuration/remove_argument_filter_spec.rb
|
62
|
+
- spec/unit/cli_forge/configuration/remove_command_spec.rb
|
63
|
+
- spec/unit/cli_forge/runner/start_spec.rb
|
42
64
|
- tasks/console.rake
|
43
65
|
- tasks/spec.rake
|
44
66
|
- tasks/spec/ci.rake
|
45
67
|
- tasks/spec/coverage.rake
|
46
68
|
- tasks/spec/mutate.rake
|
47
|
-
homepage:
|
69
|
+
homepage: https://github.com/nevir/cli-forge
|
48
70
|
licenses: []
|
49
71
|
post_install_message:
|
50
72
|
rdoc_options: []
|
@@ -56,12 +78,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
78
|
- - ! '>='
|
57
79
|
- !ruby/object:Gem::Version
|
58
80
|
version: '0'
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
hash: 850686175948221601
|
59
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
85
|
none: false
|
61
86
|
requirements:
|
62
87
|
- - ! '>='
|
63
88
|
- !ruby/object:Gem::Version
|
64
89
|
version: '0'
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
hash: 850686175948221601
|
65
93
|
requirements: []
|
66
94
|
rubyforge_project:
|
67
95
|
rubygems_version: 1.8.23
|