cli-forge 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Gem Version](https://badge.fury.io/rb/cli-forge.png)](https://rubygems.org/gems/cli-forge) [![Build Status](https://secure.travis-ci.org/nevir/cli-forge.png?branch=master)](http://travis-ci.org/nevir/cli-forge) [![
|
1
|
+
CLI Forge [![Gem Version](https://badge.fury.io/rb/cli-forge.png)](https://rubygems.org/gems/cli-forge) [![Build Status](https://secure.travis-ci.org/nevir/cli-forge.png?branch=master)](http://travis-ci.org/nevir/cli-forge) [![Dependency Status](https://gemnasium.com/nevir/cli-forge.png)](https://gemnasium.com/nevir/cli-forge) [![Coverage Status](https://coveralls.io/repos/nevir/cli-forge/badge.png?branch=master)](https://coveralls.io/r/nevir/cli-forge) [![Code Climate](https://codeclimate.com/github/nevir/cli-forge.png)](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
|