shellout 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby-version +1 -0
- data/.travis.yml +2 -0
- data/CHANGES +5 -0
- data/Gemfile +2 -3
- data/README.md +56 -53
- data/Rakefile +8 -1
- data/example/bin/hunger.rb +67 -0
- data/example/features/order_a_pizza.feature +12 -0
- data/example/features/start_the_app.feature +11 -0
- data/example/features/step_definitions/hunger_steps.rb +71 -0
- data/example/features/support/env.rb +1 -0
- data/example/features/view_order.feature +14 -0
- data/lib/shellout/calendar.rb +6 -2
- data/lib/shellout/command_loop.rb +21 -0
- data/lib/shellout/date.rb +55 -0
- data/lib/shellout/date_query.rb +50 -0
- data/lib/shellout/menu.rb +4 -2
- data/lib/shellout/menu_query.rb +51 -0
- data/lib/shellout/paragraph.rb +31 -0
- data/lib/shellout/query.rb +31 -0
- data/lib/shellout/task.rb +41 -0
- data/lib/shellout/version.rb +1 -1
- data/shellout.gemspec +6 -3
- data/spec/shellout/calendar_spec.rb +30 -1
- data/spec/shellout/command_loop_spec.rb +9 -0
- data/spec/shellout/date_query_spec.rb +9 -0
- data/spec/shellout/date_spec.rb +87 -0
- data/spec/shellout/menu_query_spec.rb +39 -0
- data/spec/shellout/menu_spec.rb +29 -18
- data/spec/shellout/paragraph_spec.rb +39 -0
- data/spec/shellout/query_spec.rb +39 -0
- data/spec/shellout/task_spec.rb +43 -0
- metadata +121 -19
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'shellout/query'
|
3
|
+
require 'shellout/date'
|
4
|
+
|
5
|
+
module Shellout
|
6
|
+
class DateQuery < Query
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super("Date (?)", "today")
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
loop do
|
14
|
+
date = super
|
15
|
+
return date if date
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def ask
|
22
|
+
answer = super
|
23
|
+
if answer == '?'
|
24
|
+
print_help
|
25
|
+
return
|
26
|
+
end
|
27
|
+
parse(answer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse(answer)
|
31
|
+
Shellout::Date.from_str(answer)
|
32
|
+
rescue => e
|
33
|
+
puts e.message
|
34
|
+
end
|
35
|
+
|
36
|
+
def print_help
|
37
|
+
puts <<'EOT'
|
38
|
+
Accepted input formats:
|
39
|
+
today - today
|
40
|
+
[+|-]N - ±N days from today
|
41
|
+
D?D - FIXME
|
42
|
+
M?M-D?D - FIXME
|
43
|
+
(YY)?YY-M?M-D?D - FIXME
|
44
|
+
|
45
|
+
EOT
|
46
|
+
Shellout::Calendar.new.print3
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/shellout/menu.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
module Shellout
|
2
2
|
class Menu
|
3
3
|
|
4
|
-
def initialize(items)
|
5
|
-
@items
|
4
|
+
def initialize(items, zero_item=nil)
|
5
|
+
@items = items
|
6
|
+
@zero_item = zero_item
|
6
7
|
end
|
7
8
|
|
8
9
|
def padding; 1; end
|
@@ -12,6 +13,7 @@ module Shellout
|
|
12
13
|
@items.each_with_index do |item, i|
|
13
14
|
out.printf format, i+1, item
|
14
15
|
end
|
16
|
+
out.printf format, 0, @zero_item unless @zero_item.nil?
|
15
17
|
end
|
16
18
|
|
17
19
|
private
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'shellout/query'
|
2
|
+
require 'shellout'
|
3
|
+
|
4
|
+
module Shellout
|
5
|
+
class MenuQuery
|
6
|
+
|
7
|
+
include Shellout
|
8
|
+
|
9
|
+
def initialize(items, last_is_the_zero_item=false)
|
10
|
+
@items = items.is_a?(Array) ? Hash[*(items.zip(items).flatten)] : items
|
11
|
+
@has_a_zero_item = last_is_the_zero_item
|
12
|
+
initialize_menu
|
13
|
+
@prompt_class = Query
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
loop do
|
18
|
+
@menu.print
|
19
|
+
answer = ask
|
20
|
+
return answer if answer
|
21
|
+
puts "Invalid user input!"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def prompt_class=(klass)
|
26
|
+
@prompt_class = klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def ask
|
30
|
+
return @items.values[0] if only_one_option?
|
31
|
+
answer = @prompt_class.new('', 1).call.to_i
|
32
|
+
return nil if answer < 0
|
33
|
+
return nil if answer == 0 and !@has_a_zero_item
|
34
|
+
index = (@has_a_zero_item and answer == "0") ? -1 : answer.to_i-1
|
35
|
+
@items.values[index]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def initialize_menu
|
41
|
+
menu_items = @items.keys
|
42
|
+
zero_item = @has_a_zero_item ? menu_items.pop : nil
|
43
|
+
@menu = Menu(menu_items, zero_item)
|
44
|
+
end
|
45
|
+
|
46
|
+
def only_one_option?
|
47
|
+
@items.length == 1 && !@has_a_zero_item
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Shellout
|
2
|
+
class Paragraph
|
3
|
+
|
4
|
+
def initialize(str)
|
5
|
+
@str = str
|
6
|
+
@padding = 0
|
7
|
+
@width = 80
|
8
|
+
end
|
9
|
+
|
10
|
+
def width(width)
|
11
|
+
@width = width
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def padding(width)
|
16
|
+
@padding = width
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def print(out=$stdout)
|
21
|
+
text_width = @width - @padding
|
22
|
+
text = ' '*@padding + @str
|
23
|
+
# Stolen from ActionView::Helpers::TextHelper#word_wrap
|
24
|
+
p = text.split("\n").collect do |line|
|
25
|
+
line.length > text_width ? line.gsub(/(.{1,#{text_width}})(\s+|$)/, "\\1\n" + ' '*@padding).rstrip : line
|
26
|
+
end * "\n"
|
27
|
+
out.puts p
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
module Shellout
|
4
|
+
class Query
|
5
|
+
|
6
|
+
def initialize(question='', default=nil)
|
7
|
+
@question = question + (default.nil? ? '' : " [#{default}]")
|
8
|
+
@default = default
|
9
|
+
@io = Readline
|
10
|
+
end
|
11
|
+
|
12
|
+
def io=(io)
|
13
|
+
@io = io
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
answer = ask
|
18
|
+
return answer == '' ? @default : answer
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def bold(text); "\e[1m#{text}\e[0m"; end
|
24
|
+
|
25
|
+
def ask
|
26
|
+
answer = @io.readline(bold("#{@question}> "), true)
|
27
|
+
answer.strip
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Shellout
|
2
|
+
class Task
|
3
|
+
|
4
|
+
# For executing sub tasks in correct order, we depend on the fact that
|
5
|
+
# hash iteration is in insert order
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@sub_tasks = {}
|
9
|
+
@on_call_done = ->{}
|
10
|
+
yield self if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(name, *args)
|
14
|
+
super unless name =~ /=$/
|
15
|
+
add_sub_task(name.to_s.chop, args.first)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_sub_task(name, sub)
|
19
|
+
@sub_tasks[name.to_sym] = sub
|
20
|
+
end
|
21
|
+
|
22
|
+
def printf(format)
|
23
|
+
@sub_tasks['printf'] = -> do
|
24
|
+
super(format, @results)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_call_done(&callback)
|
29
|
+
@on_call_done = callback
|
30
|
+
end
|
31
|
+
|
32
|
+
def call
|
33
|
+
@results = {}
|
34
|
+
@sub_tasks.each do |k, sub|
|
35
|
+
@results[k] = sub.call
|
36
|
+
end
|
37
|
+
@on_call_done.call
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/lib/shellout/version.rb
CHANGED
data/shellout.gemspec
CHANGED
@@ -16,9 +16,12 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
17
17
|
s.require_paths = ["lib"]
|
18
18
|
|
19
|
-
s.add_development_dependency "rspec"
|
20
|
-
s.add_development_dependency "guard"
|
21
|
-
s.add_development_dependency "guard-rspec"
|
19
|
+
s.add_development_dependency "rspec", '2.14.1'
|
20
|
+
s.add_development_dependency "guard", '1.8.2'
|
21
|
+
s.add_development_dependency "guard-rspec", '3.0.2'
|
22
|
+
s.add_development_dependency "rake", '10.1.0'
|
23
|
+
s.add_development_dependency "cucumber", '1.3.10'
|
24
|
+
s.add_development_dependency "aruba", '0.5.3'
|
22
25
|
|
23
26
|
if RUBY_PLATFORM.downcase.include?("darwin")
|
24
27
|
s.add_development_dependency 'rb-fsevent'
|
@@ -45,6 +45,35 @@ Mo Tu We Th Fr Sa Su
|
|
45
45
|
17 18 19 20 21 22 23
|
46
46
|
24 25 26 27 28 29 \e[7m30\e[0m
|
47
47
|
31
|
48
|
+
EOC
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should highlight todays date if calendar is todays month' do
|
52
|
+
Calendar().print(@out)
|
53
|
+
res = @out.string.gsub(/\s+\n/, "\n")
|
54
|
+
res.should == <<EOC
|
55
|
+
October 2011
|
56
|
+
Mo Tu We Th Fr Sa Su
|
57
|
+
1 2
|
58
|
+
3 4 5 6 7 8 9
|
59
|
+
10 11 12 13 14 15 16
|
60
|
+
17 18 19 20 21 22 23
|
61
|
+
24 25 26 27 28 29 \e[7m30\e[0m
|
62
|
+
31
|
63
|
+
EOC
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should not highlight todays date if calendar is todays month, but not the same year' do
|
67
|
+
Calendar(Date.new(2012, 10, 30)).print(@out)
|
68
|
+
res = @out.string.gsub(/\s+\n/, "\n")
|
69
|
+
res.should == <<EOC
|
70
|
+
October 2012
|
71
|
+
Mo Tu We Th Fr Sa Su
|
72
|
+
1 2 3 4 5 6 7
|
73
|
+
8 9 10 11 12 13 14
|
74
|
+
15 16 17 18 19 20 21
|
75
|
+
22 23 24 25 26 27 28
|
76
|
+
29 30 31
|
48
77
|
EOC
|
49
78
|
end
|
50
79
|
|
@@ -79,4 +108,4 @@ Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
|
|
79
108
|
EOC
|
80
109
|
end
|
81
110
|
end
|
82
|
-
end
|
111
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'shellout/date'
|
2
|
+
|
3
|
+
module Shellout
|
4
|
+
describe Date do
|
5
|
+
|
6
|
+
describe "#from_str" do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@today = Date.today
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
context "when English date string is given" do
|
14
|
+
it "should parse 'today'" do
|
15
|
+
Date.from_str("today").should == @today
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should parse '' as 'today'" do
|
19
|
+
Date.from_str("").should == @today
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
context "when an offset is given" do
|
25
|
+
it "should parse '+1' as tomorrow" do
|
26
|
+
Date.from_str("+1").should == @today+1
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should parse '-1' as yesterday" do
|
30
|
+
Date.from_str("-1").should == @today-1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
context "when only the day of month is given" do
|
36
|
+
it "should parse '1'" do
|
37
|
+
Date.from_str("1").should == Date.civil(@today.year, @today.month, 1)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should parse '20'" do
|
41
|
+
Date.from_str("20").should == Date.civil(@today.year, @today.month, 20)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
context "when the month and day of month are given" do
|
47
|
+
it "should parse '11-1'" do
|
48
|
+
Date.from_str("11-1").should == Date.civil(@today.year, 11, 1)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should parse '11-01'" do
|
52
|
+
Date.from_str("11-01").should == Date.civil(@today.year, 11, 1)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when year, month, and day of month are given" do
|
57
|
+
|
58
|
+
# Two digits in year
|
59
|
+
it "should parse '11-11-11' as 2011-11-11" do
|
60
|
+
Date.from_str("11-11-11").should == Date.civil(2011, 11, 11)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should parse '91-11-11' as 1991-11-11" do
|
64
|
+
Date.from_str("91-11-11").should == Date.civil(1991, 11, 11)
|
65
|
+
end
|
66
|
+
|
67
|
+
# > Two digits in year
|
68
|
+
it "should parse '111-11-1' as 111-11-1" do
|
69
|
+
Date.from_str("111-11-1").should == Date.civil(111, 11, 1)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should parse '2008-08-26' as 2008-08-26" do
|
73
|
+
Date.from_str("2008-08-26").should == Date.civil(2008, 8, 26)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should fail on nil" do
|
78
|
+
expect { Date.from_str(nil) }.to raise_error(ArgumentError)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should fail on strings containing junk" do
|
82
|
+
expect { Date.from_str("foobar") }.to raise_error(ArgumentError)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'shellout/menu_query'
|
2
|
+
|
3
|
+
module Shellout
|
4
|
+
describe MenuQuery do
|
5
|
+
|
6
|
+
describe "#ask" do
|
7
|
+
before :each do
|
8
|
+
@query = MenuQuery.new(%w(a b c))
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_prompt_return(value)
|
12
|
+
prompt = double('prompt')
|
13
|
+
prompt.stub_chain(:new, :call).and_return(value)
|
14
|
+
@query.prompt_class = prompt
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return first element when prompt query returns '1'" do
|
18
|
+
make_prompt_return('1')
|
19
|
+
@query.ask.should == 'a'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return second element when prompt query returns '2'" do
|
23
|
+
make_prompt_return('2')
|
24
|
+
@query.ask.should == 'b'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return nil if user answer is invalid (too large)" do
|
28
|
+
make_prompt_return('9')
|
29
|
+
@query.ask.should == nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return nil if user answer is invalid (0)" do
|
33
|
+
make_prompt_return('0')
|
34
|
+
@query.ask.should == nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/spec/shellout/menu_spec.rb
CHANGED
@@ -1,31 +1,32 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'shellout'
|
3
3
|
|
4
|
-
|
4
|
+
module Shellout
|
5
|
+
describe Menu do
|
5
6
|
|
6
|
-
|
7
|
+
include Shellout
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
before(:each) do
|
10
|
+
@out = StringIO.new
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
it "should work for a basic example" do
|
14
|
+
Menu(%w(a b c)).print(@out)
|
15
|
+
@out.string.should == <<EOT
|
15
16
|
1. a
|
16
17
|
2. b
|
17
18
|
3. c
|
18
19
|
EOT
|
19
|
-
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
it "should print nothing when no items are given" do
|
23
|
+
Menu([]).print(@out)
|
24
|
+
@out.string.should == ''
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
it "should right justify the menu item number" do
|
28
|
+
Menu(('a'..'j').to_a).print(@out)
|
29
|
+
@out.string.should == <<EOT
|
29
30
|
1. a
|
30
31
|
2. b
|
31
32
|
3. c
|
@@ -37,7 +38,17 @@ EOT
|
|
37
38
|
9. i
|
38
39
|
10. j
|
39
40
|
EOT
|
40
|
-
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
+
it "should put the zero item last, prefixed by a '0.'" do
|
44
|
+
Menu(%w(a b c), 'd').print(@out)
|
45
|
+
@out.string.should == <<EOT
|
46
|
+
1. a
|
47
|
+
2. b
|
48
|
+
3. c
|
49
|
+
0. d
|
50
|
+
EOT
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
43
54
|
end
|