shellout 0.4 → 0.5
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/.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
|