valet 0.0.7 → 0.0.8
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/.travis.yml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +2 -2
- data/bin/valet +14 -0
- data/features/help.feature +2 -2
- data/features/version.feature +5 -103
- data/lib/valet.rb +5 -4
- data/lib/valet/application.rb +9 -17
- data/lib/valet/command.rb +60 -0
- data/lib/valet/commands.rb +7 -0
- data/lib/valet/helpers/type_cast.rb +46 -0
- data/lib/valet/option/common.rb +58 -0
- data/lib/valet/option/flag.rb +44 -0
- data/lib/valet/option/switch.rb +32 -0
- data/lib/valet/options.rb +31 -0
- data/lib/valet/version.rb +1 -1
- data/spec/spec_helper.rb +5 -9
- data/spec/support/shared_examples.rb +54 -0
- data/spec/support/terminal_helper.rb +20 -0
- data/spec/valet/application_spec.rb +57 -27
- data/spec/valet/command_spec.rb +116 -0
- data/spec/valet/commands_spec.rb +5 -0
- data/spec/valet/helpers/type_cast_spec.rb +33 -0
- data/spec/valet/option/flag_spec.rb +73 -0
- data/spec/valet/option/switch_spec.rb +59 -0
- data/spec/valet/options_spec.rb +56 -0
- data/spec/valet/version_spec.rb +4 -4
- data/valet.gemspec +9 -11
- metadata +30 -36
- data/features/step_definitions/command_line_steps.rb +0 -3
- data/features/step_definitions/file_steps.rb +0 -3
- data/features/support/helpers/cli_helper.rb +0 -23
- data/features/support/world_extensions.rb +0 -1
- data/lib/valet/templates/version.mustache +0 -5
- data/lib/valet/view.rb +0 -29
- data/lib/valet/views/version.rb +0 -31
- data/spec/valet/view_spec.rb +0 -20
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'valet/option/common'
|
2
|
+
|
3
|
+
module Valet
|
4
|
+
module Option
|
5
|
+
class Switch < Common
|
6
|
+
attr_reader :default, :value
|
7
|
+
|
8
|
+
def initialize(long_name)
|
9
|
+
super
|
10
|
+
@default = false
|
11
|
+
@value = @default
|
12
|
+
end
|
13
|
+
|
14
|
+
def default=(new_default)
|
15
|
+
@default = new_default ? true : false
|
16
|
+
@value = @default
|
17
|
+
end
|
18
|
+
|
19
|
+
def switch
|
20
|
+
@value = ! @value
|
21
|
+
end
|
22
|
+
|
23
|
+
def long_name_to_s
|
24
|
+
if default
|
25
|
+
"--no-#{long_name}".gsub(/_/, '-')
|
26
|
+
else
|
27
|
+
"--#{long_name}".gsub(/_/, '-')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Valet
|
4
|
+
class Options < Set
|
5
|
+
def [](name)
|
6
|
+
find_option(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(method_name, *args, &block)
|
10
|
+
if (option = find_switch(method_name)) || (option = find_flag(method_name))
|
11
|
+
option.value
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def find_option(name)
|
20
|
+
self.find { |option| name == option.name }
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_switch(name)
|
24
|
+
self.find { |option| name =~ /#{option.name}\?/ if option.switch? }
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_flag(name)
|
28
|
+
self.find { |option| name == option.name if option.flag? }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/valet/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -15,19 +15,15 @@ Dir[File.expand_path('spec/support/**/*.rb')].each { |file| require file }
|
|
15
15
|
require 'valet'
|
16
16
|
include Valet
|
17
17
|
|
18
|
-
# Capture STDOUT and STDIN in new IO streams
|
19
|
-
require 'stringio'
|
20
|
-
$stdout = StringIO.new
|
21
|
-
$stderr = StringIO.new
|
22
|
-
|
23
|
-
# Reset ARGV to contain no arguments
|
24
|
-
ARGV.clear
|
25
|
-
|
26
18
|
# Configure RSpec
|
27
19
|
RSpec.configure do |config|
|
20
|
+
# Ensure usage of RSpec's new expectation syntax
|
21
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
22
|
+
|
23
|
+
# Metadata keys can be written as ":symbol" instead of "symbol: true"
|
28
24
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
29
25
|
|
30
|
-
# Use a :focus tag for running only specific example(s)
|
26
|
+
# Use a ":focus" tag for running only specific example(s)
|
31
27
|
config.filter_run :focus
|
32
28
|
config.run_all_when_everything_filtered = true
|
33
29
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
shared_examples_for "a common option" do
|
2
|
+
subject(:opt) { described_class.new(:format) }
|
3
|
+
|
4
|
+
its(:long_name) { should be_a(Symbol) }
|
5
|
+
its(:short_name) { should be_nil }
|
6
|
+
its(:summary) { should be_nil }
|
7
|
+
its(:description) { should be_nil }
|
8
|
+
|
9
|
+
describe "#initialize" do
|
10
|
+
context "raises an OptionError" do
|
11
|
+
it "if the long name is not a symbol" do
|
12
|
+
expect { described_class.new('format') }.to raise_error(OptionError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "if the long name is only one character long" do
|
16
|
+
expect { described_class.new(:f) }.to raise_error(OptionError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "attributes" do
|
22
|
+
it "can be given a short name" do
|
23
|
+
opt.short_name = :f
|
24
|
+
expect(opt.short_name).to be(:f)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can be given a summary" do
|
28
|
+
opt.summary = 'Specify a format (Markdown or Textile)'
|
29
|
+
expect(opt.summary).to eq('Specify a format (Markdown or Textile)')
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can be given a description" do
|
33
|
+
opt.description = 'A longer option description...'
|
34
|
+
expect(opt.description).to eq('A longer option description...')
|
35
|
+
end
|
36
|
+
|
37
|
+
context "raises an OptionError" do
|
38
|
+
it "if the short name is not a symbol" do
|
39
|
+
expect { opt.short_name = 'f' }.to raise_error(OptionError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "if the short name is longer than one character" do
|
43
|
+
expect { opt.short_name = 'format' }.to raise_error(OptionError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#short_name_to_s" do
|
49
|
+
it "returns the short name's string representation" do
|
50
|
+
opt.short_name = :f
|
51
|
+
expect(opt.short_name_to_s).to eq('-f')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
# Clear all remaining arguments in ARGV to make it testable
|
5
|
+
config.before(:suite) { ARGV.clear }
|
6
|
+
|
7
|
+
# Reroute STDOUT, STDERR and STDIN to testable streams
|
8
|
+
config.before(:each) do
|
9
|
+
$stdout = StringIO.new
|
10
|
+
$stderr = StringIO.new
|
11
|
+
$stdin = StringIO.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set STDOUT, STDERR and STDIN back to default
|
15
|
+
config.after(:each) do
|
16
|
+
$stdout = STDOUT
|
17
|
+
$stderr = STDERR
|
18
|
+
$stdin = STDIN
|
19
|
+
end
|
20
|
+
end
|
@@ -1,52 +1,82 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Valet::Application do
|
4
|
-
|
4
|
+
subject(:app) { Application.new }
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
its(:arguments) { should be(ARGV) }
|
7
|
+
its(:options) { should be_an_instance_of(Options) }
|
8
|
+
its(:commands) { should be_an_instance_of(Commands) }
|
9
|
+
|
10
|
+
its(:name) { should be_nil }
|
11
|
+
its(:version) { should be_nil }
|
12
|
+
its(:authors) { should be_nil }
|
13
|
+
its(:email) { should be_nil }
|
14
|
+
its(:homepage) { should be_nil }
|
15
|
+
its(:copyright) { should be_nil }
|
16
|
+
its(:license) { should be_nil }
|
17
|
+
its(:summary) { should be_nil }
|
18
|
+
its(:description) { should be_nil }
|
19
|
+
its(:examples) { should eq([]) }
|
20
|
+
|
21
|
+
describe "attributes" do
|
22
|
+
it "can be given a name" do
|
23
|
+
app.name = 'Backup'
|
24
|
+
expect(app.name).to eq('Backup')
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can be given a version" do
|
28
|
+
app.version = '1.2.3'
|
29
|
+
expect(app.version).to eq('1.2.3')
|
10
30
|
end
|
11
31
|
|
12
|
-
it "can
|
13
|
-
|
14
|
-
|
32
|
+
it "can be given authors" do
|
33
|
+
app.authors = ['Alexander Baumann', 'Bob the Builder']
|
34
|
+
expect(app).to have(2).authors
|
15
35
|
end
|
16
36
|
|
17
|
-
it "can
|
18
|
-
|
19
|
-
|
37
|
+
it "can be given email addresses" do
|
38
|
+
app.email = ['alexander.baumann@arclight.ch', 'bob@builder.com']
|
39
|
+
expect(app.email).to have(2).emails
|
20
40
|
end
|
21
41
|
|
22
|
-
it "can
|
23
|
-
|
24
|
-
|
42
|
+
it "can be given a homepage" do
|
43
|
+
app.homepage = 'http://gitkeeper.github.com/valet'
|
44
|
+
expect(app.homepage).to eq('http://gitkeeper.github.com/valet')
|
25
45
|
end
|
26
46
|
|
27
|
-
it "can
|
28
|
-
|
47
|
+
it "can be given a copyright notice" do
|
48
|
+
app.copyright = 'Copyright (c) 2012 Free Software Foundation, Inc.'
|
49
|
+
expect(app.copyright).to eq('Copyright (c) 2012 Free Software Foundation, Inc.')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "can be given a license" do
|
53
|
+
app.license = <<-LICENSE.gsub(/^ {8}/, '').strip
|
29
54
|
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
30
55
|
This is free software: You are free to change and redistribute it.
|
31
56
|
There is NO WARRANTY, to the extent permitted by law.
|
32
57
|
LICENSE
|
33
|
-
|
58
|
+
expect(app.license).to include('GNU GPL version 3 or later')
|
34
59
|
end
|
35
|
-
end
|
36
60
|
|
37
|
-
|
38
|
-
|
39
|
-
app
|
40
|
-
app.args.should == ARGV
|
61
|
+
it "can be given a summary" do
|
62
|
+
app.summary = 'A framework for creating GNU compliant CLIs.'
|
63
|
+
expect(app.summary).to eq('A framework for creating GNU compliant CLIs.')
|
41
64
|
end
|
42
65
|
|
43
|
-
it "can
|
44
|
-
app =
|
45
|
-
|
66
|
+
it "can be given a description" do
|
67
|
+
app.description = <<-DESC.gsub(/^ {8}/, '').strip
|
68
|
+
Valet helps you write the sophisticated command-line interfaces you're
|
69
|
+
so used to from GNU/Linux. It provides a beautiful API, rich template
|
70
|
+
support, smart configuration, man page generator, and many other useful
|
71
|
+
features. No matter how large or complex your application is, Valet tops
|
72
|
+
it off with the command-line interface it deserves.
|
73
|
+
DESC
|
74
|
+
expect(app.description).to include('provides a beautiful API')
|
46
75
|
end
|
47
76
|
|
48
|
-
it "
|
49
|
-
|
77
|
+
it "can be given examples" do
|
78
|
+
app.examples << ['Generate command files' 'valet generate command backup']
|
79
|
+
expect(app.examples).to have(1).example
|
50
80
|
end
|
51
81
|
end
|
52
82
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Valet::Command do
|
4
|
+
subject(:cmd) { Command.new(:backup) }
|
5
|
+
|
6
|
+
its(:name) { should be_a(Symbol) }
|
7
|
+
its(:aliases) { should be_nil }
|
8
|
+
its(:action) { should be_nil }
|
9
|
+
its(:options) { should be_an_instance_of(Options) }
|
10
|
+
its(:commands) { should be_an_instance_of(Commands) }
|
11
|
+
its(:syntax) { should be_nil }
|
12
|
+
its(:summary) { should be_nil }
|
13
|
+
its(:description) { should be_nil }
|
14
|
+
its(:examples) { should eq([]) }
|
15
|
+
|
16
|
+
describe "#initialize" do
|
17
|
+
context "raises a CommandError" do
|
18
|
+
it "if the name is not a symbol" do
|
19
|
+
expect { Command.new('backup') }.to raise_error(CommandError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "attributes" do
|
25
|
+
it "can be given aliases" do
|
26
|
+
cmd.aliases = [:bak, :b]
|
27
|
+
expect(cmd.aliases).to eq([:bak, :b])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can be given options" do
|
31
|
+
cmd.options << double("Option")
|
32
|
+
expect(cmd.options).to have(1).option
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can be given (sub-)commands" do
|
36
|
+
cmd.commands << double("Command")
|
37
|
+
expect(cmd.commands).to have(1).command
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can be given a syntax" do
|
41
|
+
cmd.syntax = 'backup [OPTION]... SOURCE... DIRECTORY'
|
42
|
+
expect(cmd.syntax).to eq('backup [OPTION]... SOURCE... DIRECTORY')
|
43
|
+
end
|
44
|
+
|
45
|
+
it "can be given a summary" do
|
46
|
+
cmd.summary = 'Backup compresses and archives your precious data.'
|
47
|
+
expect(cmd.summary).to eq('Backup compresses and archives your precious data.')
|
48
|
+
end
|
49
|
+
|
50
|
+
it "can be given a description" do
|
51
|
+
description = <<-DESC.gsub(/ {8}/, '')
|
52
|
+
Backup tends to your precious data by scheduling cron jobs that
|
53
|
+
compress and archive your files efficiently. You can specify one or
|
54
|
+
multiple locations to copy your archives to.
|
55
|
+
DESC
|
56
|
+
cmd.description = description
|
57
|
+
expect(cmd.description).to be(description)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "can be given examples" do
|
61
|
+
cmd.examples << ['A zipped backup', 'backup --gzip ~/projects ~/backups ']
|
62
|
+
expect(cmd.examples).to have(1).example
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#action" do
|
67
|
+
before { cmd.action { 'Backing up...' } }
|
68
|
+
|
69
|
+
it "attaches a new action if a block is supplied" do
|
70
|
+
cmd.action { 'Deleting all backups...' }
|
71
|
+
expect(cmd.action.call).to eq('Deleting all backups...')
|
72
|
+
end
|
73
|
+
|
74
|
+
it "attaches actions with one block parameter (options)" do
|
75
|
+
expect { cmd.action { |opts| "#{opts}" } }.not_to raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
it "attaches actions with two block parameters (operands, options)" do
|
79
|
+
expect { cmd.action { |args, opts| "#{args} #{opts}" } }.not_to raise_error
|
80
|
+
end
|
81
|
+
|
82
|
+
it "raises a CommandError if more than two block parameters are given" do
|
83
|
+
expect { cmd.action { |a, b, c| a + b + c } }.to raise_error(CommandError)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "raises a CommandError if arbitrary number of parameters are given" do
|
87
|
+
expect { cmd.action { |*args, opts| "#{args} #{opts}" } }.to raise_error(CommandError)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns the action if no block is supplied" do
|
91
|
+
expect(cmd.action.call).to eq('Backing up...')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#execute" do
|
96
|
+
it "can call an action without block parameters" do
|
97
|
+
cmd.stub(action: -> { 'Backing up...' })
|
98
|
+
expect(cmd.execute).to eq('Backing up...')
|
99
|
+
end
|
100
|
+
|
101
|
+
it "can call an action with one block parameter (options)" do
|
102
|
+
cmd.stub(options: double("Options", verbose?: true))
|
103
|
+
cmd.stub(action: ->(opts) { 'Backing up...' if opts.verbose? })
|
104
|
+
|
105
|
+
cmd.options.should_receive(:verbose?)
|
106
|
+
cmd.execute
|
107
|
+
end
|
108
|
+
|
109
|
+
it "can call an action with two block parameters (operands, options)" do
|
110
|
+
cmd.stub(options: double("Options", verbose?: true))
|
111
|
+
cmd.stub(action: ->(args, opts) { args.shift if opts.verbose? })
|
112
|
+
|
113
|
+
expect(cmd.execute('Backing up...')).to eq('Backing up...')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Valet::Helpers::TypeCast do
|
4
|
+
subject(:obj) { Class.new.extend(Helpers::TypeCast) }
|
5
|
+
|
6
|
+
let(:array) { %w( Bob Alisha Fred ) }
|
7
|
+
let(:array_comma) { 'Bob,Alisha,Fred' }
|
8
|
+
let(:array_space) { 'Bob Alisha Fred' }
|
9
|
+
|
10
|
+
let(:hash) { { name: 'Bob', gender: 'male', age: '42' } }
|
11
|
+
let(:hash_comma) { 'name:Bob,gender:male,age:42' }
|
12
|
+
let(:hash_space) { 'name:Bob gender:male age:42' }
|
13
|
+
|
14
|
+
let(:time) { Time.parse('11.07.2012 08:00') }
|
15
|
+
let(:time_string) { '11.07.2012 08:00'}
|
16
|
+
|
17
|
+
describe "#string_to" do
|
18
|
+
specify { expect(obj.string_to(String, 'Bob')).to eq('Bob') }
|
19
|
+
specify { expect(obj.string_to(Integer, '13')).to eq(13) }
|
20
|
+
specify { expect(obj.string_to(Float, '13.37')).to eq(13.37) }
|
21
|
+
specify { expect(obj.string_to(Array, array_comma)).to eq(array) }
|
22
|
+
specify { expect(obj.string_to(Array, array_space)).to eq(array) }
|
23
|
+
specify { expect(obj.string_to(Hash, hash_comma)).to eq(hash) }
|
24
|
+
specify { expect(obj.string_to(Hash, hash_space)).to eq(hash) }
|
25
|
+
specify { expect(obj.string_to(Time, time_string)).to eq(time) }
|
26
|
+
|
27
|
+
context "it raises a TypeError" do
|
28
|
+
it "if the given type is not allowed" do
|
29
|
+
expect { obj.string_to(Mutex, 'Bob') }.to raise_error(TypeError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|