cuttlebone 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -0
- data/README.md +56 -0
- data/Rakefile +55 -0
- data/cuttlebone.gemspec +27 -0
- data/examples/todo.rb +57 -0
- data/features/example.feature +53 -0
- data/features/step_definitions/steps.rb +76 -0
- data/features/support/env.rb +2 -0
- data/features/support/test_driver.rb +24 -0
- data/features/switching_contexts.feature +78 -0
- data/lib/cuttlebone.rb +19 -0
- data/lib/cuttlebone/controller.rb +69 -0
- data/lib/cuttlebone/definition.rb +58 -0
- data/lib/cuttlebone/exceptions.rb +24 -0
- data/lib/cuttlebone/session.rb +4 -0
- data/lib/cuttlebone/session/base.rb +61 -0
- data/lib/cuttlebone/session/rack.rb +4 -0
- data/lib/cuttlebone/session/shell.rb +17 -0
- data/lib/cuttlebone/version.rb +3 -0
- data/spec/controller_spec.rb +79 -0
- data/spec/cuttlebone_spec.rb +19 -0
- data/spec/definition_spec.rb +100 -0
- data/spec/rcov.opts +2 -0
- data/spec/session_base_spec.rb +82 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +8 -0
- data/vendor/active_support.rb +56 -0
- metadata +142 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
## 0.1.2 / 2011-03-18
|
2
|
+
|
3
|
+
* Changed directory structure to look like a real gem. :)
|
4
|
+
* Finalized initial test cases (spec/cucumber).
|
5
|
+
* github.com release!
|
6
|
+
* Added my original todo example using the new syntax.
|
7
|
+
|
8
|
+
## 0.1.1 / 2011-03-15
|
9
|
+
|
10
|
+
* Added new test cases and new "polished" features.
|
11
|
+
|
12
|
+
## 0.1.0 / 2010-07-06
|
13
|
+
|
14
|
+
* After a long (inactive) year, some projects generated a need to push
|
15
|
+
cuttlebone a little further. There are no new features but tests and
|
16
|
+
documentation.
|
17
|
+
|
18
|
+
## 0.0.1 / 2009-05-13
|
19
|
+
|
20
|
+
* Birthday! Presentation on the topic (and my proof of concept demo):
|
21
|
+
http://www.viddler.com/explore/budapestrb/videos/4/
|
22
|
+
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# cuttlebone
|
2
|
+
|
3
|
+
Cuttlebone helps you creating shell-alike applications. Easily.
|
4
|
+
|
5
|
+
## INSTALL AND USAGE
|
6
|
+
|
7
|
+
Install Ruby 1.9.2, clone, setup, try. :) Check `examples` and `features`
|
8
|
+
directories for examples.
|
9
|
+
|
10
|
+
## TODO
|
11
|
+
|
12
|
+
* Documentation-documentation-documentation...
|
13
|
+
|
14
|
+
* Tests-tests-tests...
|
15
|
+
|
16
|
+
* Methods defined in context definitions should be called from command/prompt
|
17
|
+
blocks.
|
18
|
+
|
19
|
+
* For Symbol contexts special attribute and method definitions should work
|
20
|
+
intuitively.
|
21
|
+
|
22
|
+
* Terminal handling, colors, sizes (eg.
|
23
|
+
http://codeidol.com/other/rubyckbk/User-Interface/Determining-Terminal-Size/,
|
24
|
+
ncurses(?)).
|
25
|
+
|
26
|
+
* Autocomplete (eg. cldwalker/bond or with a new approach based on 1.9.2's
|
27
|
+
readline enhancement).
|
28
|
+
|
29
|
+
* Asynchronous (server-initiated) output.
|
30
|
+
|
31
|
+
* Web driver.
|
32
|
+
|
33
|
+
## LICENSE
|
34
|
+
|
35
|
+
(The MIT License)
|
36
|
+
|
37
|
+
Copyright (c) 2009-2011 Golda Bence <bence@golda.me>
|
38
|
+
|
39
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
40
|
+
a copy of this software and associated documentation files (the
|
41
|
+
'Software'), to deal in the Software without restriction, including
|
42
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
43
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
44
|
+
permit persons to whom the Software is furnished to do so, subject to
|
45
|
+
the following conditions:
|
46
|
+
|
47
|
+
The above copyright notice and this permission notice shall be
|
48
|
+
included in all copies or substantial portions of the Software.
|
49
|
+
|
50
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
51
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
52
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
53
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
54
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
55
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
56
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
require 'cucumber/rake/task'
|
8
|
+
|
9
|
+
require 'rake/packagetask'
|
10
|
+
require 'rake/gempackagetask'
|
11
|
+
|
12
|
+
CUTTLEBONE_GEMSPEC = eval(File.read(File.expand_path('../cuttlebone.gemspec', __FILE__)))
|
13
|
+
|
14
|
+
desc 'Default: run specs'
|
15
|
+
task :default => 'spec'
|
16
|
+
|
17
|
+
namespace :spec do
|
18
|
+
desc 'Run all specs in spec directory (format=progress)'
|
19
|
+
RSpec::Core::RakeTask.new(:progress) do |t|
|
20
|
+
t.pattern = './spec/**/*_spec.rb'
|
21
|
+
t.rspec_opts = ['--color', '--format=progress']
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Run all specs in spec directory (format=documentation)'
|
25
|
+
RSpec::Core::RakeTask.new(:documentation) do |t|
|
26
|
+
t.pattern = './spec/**/*_spec.rb'
|
27
|
+
t.rspec_opts = ['--color', '--format=documentation']
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Run specs with rcov"
|
31
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
32
|
+
t.pattern = './spec/**/*_spec.rb'
|
33
|
+
t.rcov = true
|
34
|
+
t.rcov_opts = ['--exclude', 'gems/,spec/,features/']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
task :spec => 'spec:progress'
|
39
|
+
|
40
|
+
desc 'Run all cucumber tests.'
|
41
|
+
Cucumber::Rake::Task.new do |t|
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Generate documentation for the a4-core plugin.'
|
45
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = 'A4::Core'
|
48
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
|
49
|
+
rdoc.rdoc_files.include('README')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
52
|
+
|
53
|
+
Rake::GemPackageTask.new(CUTTLEBONE_GEMSPEC) do |p|
|
54
|
+
p.gem_spec = CUTTLEBONE_GEMSPEC
|
55
|
+
end
|
data/cuttlebone.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path("../lib/cuttlebone/version", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "cuttlebone"
|
5
|
+
s.version = Cuttlebone::VERSION
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ["Bence Golda"]
|
8
|
+
s.email = ["bence@golda.me"]
|
9
|
+
s.homepage = "http://github.com/gbence/cuttlebone"
|
10
|
+
s.summary = "cuttlebone-#{Cuttlebone::VERSION}"
|
11
|
+
s.description = "Cuttlebone helps you creating shell-alike applications."
|
12
|
+
|
13
|
+
s.rubyforge_project = "cuttlebone"
|
14
|
+
s.required_rubygems_version = ">= 1.3.7"
|
15
|
+
|
16
|
+
s.add_development_dependency "bundler", "~> 1.0.0"
|
17
|
+
s.add_development_dependency "rspec", "~> 2.5.0"
|
18
|
+
s.add_development_dependency "i18n"
|
19
|
+
s.add_development_dependency "cucumber", "~> 0.10.0"
|
20
|
+
s.add_development_dependency "rcov", "~> 0.9.0"
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
|
24
|
+
s.extra_rdoc_files = [ "README.md" ]
|
25
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
26
|
+
s.require_path = 'lib'
|
27
|
+
end
|
data/examples/todo.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require File.expand_path('../../lib/cuttlebone', __FILE__)
|
5
|
+
|
6
|
+
##
|
7
|
+
# MODEL
|
8
|
+
|
9
|
+
class Task
|
10
|
+
@@id = 0
|
11
|
+
attr_accessor :id, :title
|
12
|
+
def initialize options={}
|
13
|
+
@id = (@@id+=1)
|
14
|
+
@title = options.delete(:title)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# CUTTLEBONE DESCRIPTIONS
|
20
|
+
|
21
|
+
context Array do
|
22
|
+
#def find_task_by_id(id_s)
|
23
|
+
# id = id_s.to_i
|
24
|
+
# detect { |t| t.id == id }
|
25
|
+
#end
|
26
|
+
|
27
|
+
prompt() { "tasks(#{size})" }
|
28
|
+
|
29
|
+
command(?l) { each { |t| output('(%03d) %50s' % [t.id, t.title]) } }
|
30
|
+
command(?n) { send(:<<, t = Task.new); add t }
|
31
|
+
command /^([0-9]+)$/ do |id_s|
|
32
|
+
#t = find_task_by_id(id_s)
|
33
|
+
id = id_s.to_i
|
34
|
+
t = detect { |t| t.id == id }
|
35
|
+
add(t) if t
|
36
|
+
end
|
37
|
+
command /^d ([0-9]+)$/ do |id_s|
|
38
|
+
#t = find_task_by_id(id_s)
|
39
|
+
id = id_s.to_i
|
40
|
+
t = detect { |t| t.id == id }
|
41
|
+
delete(t)
|
42
|
+
end
|
43
|
+
command(?q) { drop }
|
44
|
+
end
|
45
|
+
|
46
|
+
context Task do
|
47
|
+
prompt() { ('%03d' % [id]) + (' ' + (title.size<=20 ? title : "#{title[0..18]}…") rescue '') }
|
48
|
+
|
49
|
+
command(?q) { drop }
|
50
|
+
command /^(.+)$/ do |text|
|
51
|
+
send(:title=, text)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
at_exit do
|
56
|
+
Cuttlebone.run([Task.new(:title => 'x'), Task.new(:title => 'y')])
|
57
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
@console @example
|
2
|
+
Feature: a simple example
|
3
|
+
In order to be able to manage a simple todo list
|
4
|
+
As a programmer
|
5
|
+
I want to create cuttlebone program
|
6
|
+
|
7
|
+
Scenario: starting with no defined contexts
|
8
|
+
Given no cuttlebone code
|
9
|
+
When I start an "x" session
|
10
|
+
Then I should get an error
|
11
|
+
|
12
|
+
Scenario: starting with an existing context
|
13
|
+
Given the following cuttlebone code:
|
14
|
+
"""
|
15
|
+
context "x" do
|
16
|
+
end
|
17
|
+
"""
|
18
|
+
When I start an "x" session
|
19
|
+
Then I should see an empty prompt
|
20
|
+
And I should be in context "x"
|
21
|
+
|
22
|
+
Scenario: starting with a missing context
|
23
|
+
Given the following cuttlebone code:
|
24
|
+
"""
|
25
|
+
context "x" do
|
26
|
+
end
|
27
|
+
"""
|
28
|
+
When I start a "y" session
|
29
|
+
Then I should get an error
|
30
|
+
|
31
|
+
Scenario: starting a context with prompt defined
|
32
|
+
Given the following cuttlebone code:
|
33
|
+
"""
|
34
|
+
context "x" do
|
35
|
+
prompt { "prompt" }
|
36
|
+
end
|
37
|
+
"""
|
38
|
+
When I start an "x" session
|
39
|
+
Then I should see "prompt" as prompt
|
40
|
+
|
41
|
+
Scenario: invoking a simple command
|
42
|
+
Given the following cuttlebone code:
|
43
|
+
"""
|
44
|
+
context "x" do
|
45
|
+
command /^y$/ do
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
"""
|
50
|
+
When I start an "x" session
|
51
|
+
And I call command "y"
|
52
|
+
Then I should see an empty prompt
|
53
|
+
And I should be in context "x"
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'pp'
|
2
|
+
# schema / definitions
|
3
|
+
|
4
|
+
Given /^no cuttlebone code$/ do
|
5
|
+
Cuttlebone.definitions.clear
|
6
|
+
end
|
7
|
+
|
8
|
+
Given /^the following cuttlebone code:$/ do |string|
|
9
|
+
Given %{no cuttlebone code}
|
10
|
+
Cuttlebone.instance_eval(string)
|
11
|
+
end
|
12
|
+
|
13
|
+
# initialization
|
14
|
+
|
15
|
+
Given /^a started "([^"]*)" session$/ do |objects|
|
16
|
+
When %{I start a #{objects.inspect} session}
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^I start (?:an? )?"([^"]*)" session$/ do |objects|
|
20
|
+
@d = Cuttlebone::Session::Test.new(*(objects.scan(/([^,]{1,})(?:,\s*)?/).flatten))
|
21
|
+
end
|
22
|
+
|
23
|
+
# invocation
|
24
|
+
|
25
|
+
When /^I call command "([^"]*)"$/ do |command|
|
26
|
+
@d.call(command)
|
27
|
+
end
|
28
|
+
|
29
|
+
# context related steps
|
30
|
+
|
31
|
+
Then /^I should be in context "([^"]*)"$/ do |name|
|
32
|
+
@d.active_context.context.to_s.should == name
|
33
|
+
end
|
34
|
+
|
35
|
+
Then /^I should see a terminated session$/ do
|
36
|
+
@d.should be_terminated
|
37
|
+
end
|
38
|
+
|
39
|
+
Then /^I should be in the same context$/ do
|
40
|
+
@d.previous_active_context.should == @d.active_context
|
41
|
+
end
|
42
|
+
|
43
|
+
Then /^I should not be in the same context$/ do
|
44
|
+
@d.previous_active_context.should_not == @d.active_context
|
45
|
+
end
|
46
|
+
|
47
|
+
# output related steps
|
48
|
+
|
49
|
+
Then /^I should see "([^"]*)"$/ do |text|
|
50
|
+
@d.output.should include(text)
|
51
|
+
end
|
52
|
+
|
53
|
+
# prompt related steps
|
54
|
+
|
55
|
+
Then /^I should see "([^"]*)" as prompt$/ do |text|
|
56
|
+
@d.prompt.should include(text)
|
57
|
+
end
|
58
|
+
|
59
|
+
Then /^I should see \/([^\/]*)\/ as prompt$/ do |regexp|
|
60
|
+
@d.prompt.should match(regexp)
|
61
|
+
end
|
62
|
+
|
63
|
+
Then /^I should see an empty prompt$/ do
|
64
|
+
@d.prompt.should be_empty
|
65
|
+
end
|
66
|
+
|
67
|
+
# error related steps
|
68
|
+
|
69
|
+
Then /^I should see an error$/ do
|
70
|
+
@d.error.should_not be_blank
|
71
|
+
end
|
72
|
+
|
73
|
+
Then /^I should get an error$/ do
|
74
|
+
@d.internal_error.should_not be_blank
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Cuttlebone::Session::Test < Cuttlebone::Session::Base
|
2
|
+
def initialize *args
|
3
|
+
super
|
4
|
+
stack = []
|
5
|
+
@stack.each { |c| stack << c }
|
6
|
+
@stack_history = [ stack ]
|
7
|
+
end
|
8
|
+
|
9
|
+
def process command
|
10
|
+
stack = []
|
11
|
+
@stack.each { |c| stack << c }
|
12
|
+
@stack_history << stack
|
13
|
+
a, n, o, e = super(command)
|
14
|
+
@output = o
|
15
|
+
@error = e
|
16
|
+
[ a, n, o, e ]
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :output, :error
|
20
|
+
|
21
|
+
def previous_active_context
|
22
|
+
@stack_history[-2].last
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
Feature: switching between contexts
|
2
|
+
In order to be able to manage my todo list
|
3
|
+
As a programmer
|
4
|
+
I want to switch between cuttlebone contexts
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given the following cuttlebone code:
|
8
|
+
"""
|
9
|
+
context 'x' do
|
10
|
+
command 'q' do
|
11
|
+
drop
|
12
|
+
end
|
13
|
+
command 'y' do
|
14
|
+
replace 'y'
|
15
|
+
end
|
16
|
+
command 'yy' do
|
17
|
+
add 'y'
|
18
|
+
end
|
19
|
+
command 'x' do
|
20
|
+
self
|
21
|
+
end
|
22
|
+
command 'xx' do
|
23
|
+
add 'x'
|
24
|
+
end
|
25
|
+
command 'r' do
|
26
|
+
replace 'x'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'y' do
|
31
|
+
command 'q' do
|
32
|
+
drop
|
33
|
+
end
|
34
|
+
command 'y' do
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
"""
|
39
|
+
|
40
|
+
Scenario: quitting
|
41
|
+
Given a started "x" session
|
42
|
+
When I call command "q"
|
43
|
+
Then I should see a terminated session
|
44
|
+
|
45
|
+
Scenario: replacing current context
|
46
|
+
Given a started "x" session
|
47
|
+
When I call command "y"
|
48
|
+
Then I should be in context "y"
|
49
|
+
|
50
|
+
Scenario: returning the same context
|
51
|
+
Given a started "x" session
|
52
|
+
When I call command "x"
|
53
|
+
Then I should be in the same context
|
54
|
+
|
55
|
+
Scenario: replacing with a similar context
|
56
|
+
Given a started "x" session
|
57
|
+
When I call command "r"
|
58
|
+
Then I should not be in the same context
|
59
|
+
|
60
|
+
Scenario: entering into a new context
|
61
|
+
Given a started "x" session
|
62
|
+
When I call command "yy"
|
63
|
+
Then I should be in context "y"
|
64
|
+
|
65
|
+
Scenario: dropping previously built context
|
66
|
+
Given a started "x" session
|
67
|
+
When I call command "yy"
|
68
|
+
And I call command "q"
|
69
|
+
Then I should be in context "x"
|
70
|
+
|
71
|
+
Scenario: consume multiple contexts and quit
|
72
|
+
Given a started "x,y,y,x" session
|
73
|
+
When I call command "q"
|
74
|
+
And I call command "q"
|
75
|
+
And I call command "q"
|
76
|
+
Then I should be in context "x"
|
77
|
+
When I call command "q"
|
78
|
+
Then I should see a terminated session
|
data/lib/cuttlebone.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$: << File.expand_path('../', __FILE__)
|
2
|
+
|
3
|
+
require File.expand_path('../../vendor/active_support.rb', __FILE__)
|
4
|
+
|
5
|
+
module Cuttlebone
|
6
|
+
require 'cuttlebone/exceptions'
|
7
|
+
autoload :Controller, 'cuttlebone/controller'
|
8
|
+
autoload :Definition, 'cuttlebone/definition'
|
9
|
+
autoload :Session, 'cuttlebone/session'
|
10
|
+
|
11
|
+
@@definitions = []
|
12
|
+
def self.definitions; @@definitions; end
|
13
|
+
|
14
|
+
def self.run starting_objects, default_driver=Session::Shell
|
15
|
+
default_driver.new(starting_objects).run
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
include Cuttlebone::Definition::DSL
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#
|
2
|
+
# NOTE: we don't really want to pollute this proxy with a lot of private
|
3
|
+
# (internal) methods to leave space for <<context>>'s real methods.
|
4
|
+
class Cuttlebone::Controller
|
5
|
+
|
6
|
+
attr_reader :session, :context, :definition
|
7
|
+
|
8
|
+
def initialize session, context
|
9
|
+
@session = session
|
10
|
+
@context = context
|
11
|
+
@definition = Cuttlebone.definitions.find { |d| d.match(context) }
|
12
|
+
raise Cuttlebone::InvalidContextError, context, "No definiton was found for #{context.inspect}!" unless @definition
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Processes a command on its context's domain.
|
17
|
+
#
|
18
|
+
def process command
|
19
|
+
@next_action = nil
|
20
|
+
@output = []
|
21
|
+
|
22
|
+
return([:self, self, [], nil]) if command.empty?
|
23
|
+
block, arguments = definition.proc_for(command)
|
24
|
+
|
25
|
+
instance_exec(*arguments, &block)
|
26
|
+
action, context = @next_action || [ :self, self ]
|
27
|
+
|
28
|
+
return([ action, context, @output, nil ])
|
29
|
+
rescue Cuttlebone::DoubleActionError
|
30
|
+
raise
|
31
|
+
rescue => e
|
32
|
+
return([ :self, self, [], %{Cuttlebone::Controller: #{e.message} (#{e.class})} ])
|
33
|
+
end
|
34
|
+
|
35
|
+
def prompt
|
36
|
+
return(instance_exec(&@definition.prompt) || '')
|
37
|
+
rescue => e
|
38
|
+
%{error: #{e.message} (#{e.class.name})}
|
39
|
+
end
|
40
|
+
|
41
|
+
def drop
|
42
|
+
__save_next_action! :drop
|
43
|
+
end
|
44
|
+
|
45
|
+
def add context
|
46
|
+
__save_next_action! :add, context
|
47
|
+
end
|
48
|
+
|
49
|
+
def replace context
|
50
|
+
__save_next_action! :replace, context
|
51
|
+
end
|
52
|
+
|
53
|
+
def output text
|
54
|
+
@output << text
|
55
|
+
end
|
56
|
+
|
57
|
+
def method_missing method_name, *args, &block
|
58
|
+
return(@context.send(method_name, *args, &block)) if @context.respond_to?(method_name)
|
59
|
+
return(super)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def __save_next_action! action, context=nil
|
65
|
+
raise Cuttlebone::DoubleActionError if @next_action
|
66
|
+
@next_action = [ action, context ]
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Cuttlebone
|
2
|
+
class Definition
|
3
|
+
|
4
|
+
module DSL
|
5
|
+
def context object, options={}, &definition
|
6
|
+
Cuttlebone.definitions << Definition.new(object, options, &definition)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Parser
|
11
|
+
def initialize definition
|
12
|
+
@definition = definition
|
13
|
+
end
|
14
|
+
|
15
|
+
def command string_or_regexp, &block
|
16
|
+
@definition.commands << [ string_or_regexp, block, @last_description ]
|
17
|
+
@last_description = nil
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def description text
|
22
|
+
@last_description = text
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def prompt text='', &block
|
27
|
+
@definition.prompt = block_given? ? block : proc { text }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :prompt, :commands
|
32
|
+
|
33
|
+
def initialize object_or_class, options={}, &definition
|
34
|
+
raise ArgumentError, 'missing block' unless block_given?
|
35
|
+
@object_or_class = object_or_class
|
36
|
+
@options = options
|
37
|
+
@commands = [] #Array.new(proc { |c| [:self, c, '', nil] })
|
38
|
+
@prompt = proc { |c| '' }
|
39
|
+
|
40
|
+
@parser = Parser.new(self)
|
41
|
+
@parser.instance_eval(&definition)
|
42
|
+
end
|
43
|
+
|
44
|
+
delegate :command, :to => '@parser'
|
45
|
+
|
46
|
+
def match object
|
47
|
+
@object_or_class === object
|
48
|
+
# TODO :if :unless options
|
49
|
+
end
|
50
|
+
|
51
|
+
def proc_for command
|
52
|
+
string_or_regexp, block, description = commands.find { |(sr,b,d)| sr === command } || raise(UnknownCommandError, "Unknown command: #{command.inspect}!")
|
53
|
+
return([ block, $~.captures ]) if $~
|
54
|
+
return([ block, [] ])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cuttlebone
|
2
|
+
|
3
|
+
##
|
4
|
+
# Raised when Controller is invoked with an invalid (not matching) context.
|
5
|
+
#
|
6
|
+
class InvalidContextError < StandardError
|
7
|
+
attr_reader :context
|
8
|
+
def initialize context, *args, &block
|
9
|
+
@context = context
|
10
|
+
super *args, &block
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Raised on unknown command calls.
|
16
|
+
#
|
17
|
+
class UnknownCommandError < StandardError; end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Raised when multiple actions are invoked simultanously.
|
21
|
+
#
|
22
|
+
class DoubleActionError < StandardError; end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Cuttlebone::Session::Base
|
2
|
+
|
3
|
+
attr_reader :stack, :internal_error
|
4
|
+
|
5
|
+
def initialize *stack_objects
|
6
|
+
setup_contexts 'cuttlebone.stack_objects' => stack_objects
|
7
|
+
end
|
8
|
+
|
9
|
+
def active_context
|
10
|
+
stack.last
|
11
|
+
end
|
12
|
+
|
13
|
+
delegate :name, :prompt, :to => :active_context
|
14
|
+
|
15
|
+
def setup_contexts env={}
|
16
|
+
@stack ||= (env['cuttlebone.stack_objects'] || []).map{ |o| Cuttlebone::Controller.new(self, o) }
|
17
|
+
rescue Cuttlebone::InvalidContextError => e
|
18
|
+
@internal_error = %{Context initialization failed for #{e.context.inspect}!}
|
19
|
+
@stack = []
|
20
|
+
rescue => e
|
21
|
+
@internal_error = %{Internal error occured: #{e.message} (#{e.class})}
|
22
|
+
@stack = []
|
23
|
+
end
|
24
|
+
private :setup_contexts
|
25
|
+
|
26
|
+
def process command
|
27
|
+
active_context = @stack.pop
|
28
|
+
|
29
|
+
action, next_context, output, error = begin
|
30
|
+
a, n, o, e = active_context.process(command)
|
31
|
+
raise ArgumentError, "Unknown action: #{a}" unless [ :self, :replace, :add, :drop ].include?(a.to_s.to_sym)
|
32
|
+
raise TypeError, "Output must be an instance of String or nil!" unless o.is_a?(Array) or o.nil?
|
33
|
+
raise TypeError, "Error must be an instance of String or nil!" unless e.is_a?(String) or e.nil?
|
34
|
+
[ a.to_s.to_sym, n, o, e ]
|
35
|
+
rescue => e
|
36
|
+
[ :self, active_context, nil, %{Cuttlebone::Session: #{e.message} (#{e.class})} ]
|
37
|
+
end
|
38
|
+
case action
|
39
|
+
when :self
|
40
|
+
@stack << active_context
|
41
|
+
when :replace
|
42
|
+
@stack << Cuttlebone::Controller.new(self, next_context)
|
43
|
+
when :add
|
44
|
+
@stack << active_context << Cuttlebone::Controller.new(self, next_context)
|
45
|
+
when :drop
|
46
|
+
# noop
|
47
|
+
end
|
48
|
+
[ action, next_context, output, error ]
|
49
|
+
end
|
50
|
+
private :process
|
51
|
+
|
52
|
+
def call command, env={}
|
53
|
+
setup_contexts env
|
54
|
+
process command
|
55
|
+
end
|
56
|
+
|
57
|
+
def terminated?
|
58
|
+
stack.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# TODO FIXME load readline iff it was invoked through command line interface
|
2
|
+
require 'readline'
|
3
|
+
|
4
|
+
class Cuttlebone::Session::Shell < Cuttlebone::Session::Base
|
5
|
+
def run
|
6
|
+
loop do
|
7
|
+
break if terminated?
|
8
|
+
command = Readline::readline("#{prompt} > ")
|
9
|
+
break unless command
|
10
|
+
Readline::HISTORY.push(command)
|
11
|
+
_, _, output, error = call(command)
|
12
|
+
print (output<<'').join("\n")
|
13
|
+
print "\033[01;31m#{error}\033[00m\n" if error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cuttlebone::Controller do
|
4
|
+
let(:valid_session) { mock("Cuttlebone::Session::Base") }
|
5
|
+
let(:valid_context) { 'x' }
|
6
|
+
|
7
|
+
let(:drop_proc) { proc { |*args| output 'dropped'; drop } }
|
8
|
+
let(:self_proc) { proc { |*args| output 'noop'; self } }
|
9
|
+
let(:add_proc) { proc { |*args| output 'added'; add 'x' } } # TODO FIXME change 'x' => valid_context somehow 'cause they are the same instance
|
10
|
+
let(:replace_proc) { proc { |*args| output 'replaced'; replace 'x' } }
|
11
|
+
let(:double_action_error_proc) { proc { |*args| output 'double action error'; drop; add 'x' } }
|
12
|
+
let(:prompt_proc) { proc { |*args| 'prompt' } }
|
13
|
+
|
14
|
+
let(:valid_context_definition) do
|
15
|
+
x = mock('Cuttlebone::Definition "x"')
|
16
|
+
x.stub!(:match).and_return() { |*args| args == [valid_context] }
|
17
|
+
x.stub!(:proc_for).with('drop').and_return(drop_proc)
|
18
|
+
x.stub!(:proc_for).with('self').and_return(self_proc)
|
19
|
+
x.stub!(:proc_for).with('add').and_return(add_proc)
|
20
|
+
x.stub!(:proc_for).with('replace').and_return(replace_proc)
|
21
|
+
x.stub!(:proc_for).with('double').and_return(double_action_error_proc)
|
22
|
+
x.stub!(:prompt).and_return(prompt_proc)
|
23
|
+
x
|
24
|
+
end
|
25
|
+
|
26
|
+
before :each do
|
27
|
+
Cuttlebone.stub!(:definitions).and_return([ valid_context_definition ])
|
28
|
+
end
|
29
|
+
|
30
|
+
context "given a valid context" do
|
31
|
+
subject { Cuttlebone::Controller.new(valid_session, valid_context) }
|
32
|
+
|
33
|
+
it "should return context object" do
|
34
|
+
subject.context.should == valid_context
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return [:self, self, nil, nil] to empty commands" do
|
38
|
+
subject.process('').should == [ :self, subject, [], nil ]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return [:drop, nil, 'dropped', nil] to 'drop' commands" do
|
42
|
+
subject.process('drop').should == [:drop, nil, ['dropped'], nil]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should return [:self, self, 'noop', nil] to 'drop' commands" do
|
46
|
+
subject.process('self').should == [:self, subject, ['noop'], nil]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return [:add, valid_context, 'added', nil] to 'drop' commands" do
|
50
|
+
subject.process('add').should == [:add, valid_context, ['added'], nil]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return [:replace, valid_context, 'replaced', nil] to 'drop' commands" do
|
54
|
+
subject.process('replace').should == [:replace, valid_context, ['replaced'], nil]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should raise error on double action commands" do
|
58
|
+
expect{ subject.process('double') }.should raise_error(Cuttlebone::DoubleActionError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should execute command in <<context>>'s context" do
|
62
|
+
valid_context_definition.stub!(:proc_for).with('x').and_return(proc{ x })
|
63
|
+
valid_context.should_receive(:x)
|
64
|
+
subject.process('x')
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should execute prompt in <<context>>'s context" do
|
68
|
+
valid_context_definition.stub!(:prompt).and_return(proc{ x })
|
69
|
+
valid_context.should_receive(:x)
|
70
|
+
subject.prompt()
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "given an invalid context" do
|
75
|
+
it "should raise an exception" do
|
76
|
+
expect{ Cuttlebone::Controller.new(valid_session, 'invalid_context') }.should raise_error(Cuttlebone::InvalidContextError)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cuttlebone do
|
4
|
+
before :each do
|
5
|
+
Cuttlebone.definitions.clear
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should start with no contexts defined" do
|
9
|
+
Cuttlebone.definitions.should be_empty
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to evaluate new context definitions" do
|
13
|
+
Cuttlebone.should respond_to(:context)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should start a new session" do
|
17
|
+
Cuttlebone.should respond_to(:run)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '/spec_helper'))
|
2
|
+
|
3
|
+
describe Cuttlebone::Definition do
|
4
|
+
let(:valid_context_identifier) { :c }
|
5
|
+
let(:valid_options) { {} }
|
6
|
+
let(:valid_block) { proc { command('x') {} } }
|
7
|
+
|
8
|
+
context "given valid but meaningless parameters" do
|
9
|
+
subject { Cuttlebone::Definition.new(valid_context_identifier, valid_options, &valid_block) }
|
10
|
+
|
11
|
+
it "should match to the context id given" do
|
12
|
+
subject.should match(valid_context_identifier)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return prompt" do
|
16
|
+
subject.should respond_to(:prompt)
|
17
|
+
subject.prompt.should be_a(Proc)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "given class for context matcher" do
|
22
|
+
let(:klass) { Class.new(Object) }
|
23
|
+
let(:instance1) { klass.new }
|
24
|
+
let(:instance2) { klass.new }
|
25
|
+
let(:other_instance) { Object.new }
|
26
|
+
let(:subclass) { Class.new(klass) }
|
27
|
+
let(:instance3) { subclass.new }
|
28
|
+
subject { Cuttlebone::Definition.new(klass, valid_options, &valid_block) }
|
29
|
+
|
30
|
+
it "should match instances" do
|
31
|
+
subject.should match(instance1)
|
32
|
+
subject.should match(instance2)
|
33
|
+
subject.should_not match(other_instance)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should match subclass instances" do
|
37
|
+
subject.should match(instance3)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "given several commands" do
|
42
|
+
let(:drop_block) { proc { drop } }
|
43
|
+
let(:add_block) { proc { |arg| add arg.to_s.to_sym } }
|
44
|
+
let(:definition_with_several_commands) do
|
45
|
+
proc do
|
46
|
+
command 'nil_will_be_self' do
|
47
|
+
end
|
48
|
+
command 'drop' do
|
49
|
+
drop
|
50
|
+
end
|
51
|
+
command 'add x' do
|
52
|
+
add 0
|
53
|
+
end
|
54
|
+
command 'replace x' do
|
55
|
+
replace :x
|
56
|
+
end
|
57
|
+
command 'self' do
|
58
|
+
self
|
59
|
+
end
|
60
|
+
command 'double_action_error' do
|
61
|
+
add :x
|
62
|
+
drop
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
subject { Cuttlebone::Definition.new(valid_context_identifier, valid_options, &definition_with_several_commands) }
|
68
|
+
|
69
|
+
it "should have several commands" do
|
70
|
+
subject.should have(6).commands
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should parse new commands" do
|
74
|
+
subject.command(/new command/) { self }
|
75
|
+
subject.should have(7).commands
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should return the specific block for a command" do
|
79
|
+
subject.command /^add (.)$/, &add_block
|
80
|
+
|
81
|
+
subject.proc_for('add x').should_not == [add_block, []]
|
82
|
+
subject.proc_for('add y').should == [add_block, ['y']]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should parse arguments properly" do
|
86
|
+
subject.command /^add (.)(.)?$/, &(proc{|a,b|})
|
87
|
+
|
88
|
+
subject.proc_for('add y').last.should == ['y', nil]
|
89
|
+
subject.proc_for('add yz').last.should == ['y', 'z']
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return an error for unmatched commands" do
|
93
|
+
expect{ subject.proc_for('unmatched') }.should raise_error(Cuttlebone::UnknownCommandError)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should return no error on semantically wrong commands" do
|
97
|
+
expect{ subject.proc_for('double_action_error') }.should_not raise_error
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/spec/rcov.opts
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cuttlebone::Session::Base do
|
4
|
+
let(:valid_context) { 'x' }
|
5
|
+
let(:valid_command) { 'y' }
|
6
|
+
|
7
|
+
let(:valid_context_definition) do
|
8
|
+
x = mock('Definition:x')
|
9
|
+
x.stub!(:match).and_return() { |*args| args == [valid_context] }
|
10
|
+
x.stub!(:proc_for).with(any_args()).and_return([proc{}, []])
|
11
|
+
x
|
12
|
+
end
|
13
|
+
|
14
|
+
before :each do
|
15
|
+
Cuttlebone.stub!(:definitions).and_return([ valid_context_definition ])
|
16
|
+
end
|
17
|
+
|
18
|
+
context "having an active 'x' context" do
|
19
|
+
subject { Cuttlebone::Session::Base.new(valid_context) }
|
20
|
+
|
21
|
+
it { should_not be_terminated }
|
22
|
+
|
23
|
+
it "should have no internal error" do
|
24
|
+
subject.internal_error.should be_blank
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should evaluate a string command" do
|
28
|
+
valid_context_definition.should_receive(:match).with(valid_context).and_return(true)
|
29
|
+
subject.call(valid_command)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return active context" do
|
33
|
+
subject.should respond_to(:active_context)
|
34
|
+
subject.active_context.should be_a(Cuttlebone::Controller)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return with a [action, context, output, error] quadruple" do
|
38
|
+
a, c, o, e = subject.call(valid_context)
|
39
|
+
[ :drop, :replace, :self, :add ].should include(a)
|
40
|
+
o and o.should be_a(Array)
|
41
|
+
e and e.should be_a(String)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "having no active contexts" do
|
46
|
+
subject { Cuttlebone::Session::Base.new() }
|
47
|
+
|
48
|
+
it "should have no internal error" do
|
49
|
+
subject.internal_error.should be_blank
|
50
|
+
end
|
51
|
+
|
52
|
+
it { should be_terminated }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "having 1 context" do
|
56
|
+
subject { Cuttlebone::Session::Base.new(valid_context) }
|
57
|
+
|
58
|
+
it "should have no internal error" do
|
59
|
+
subject.internal_error.should be_blank
|
60
|
+
end
|
61
|
+
|
62
|
+
it { should_not be_terminated }
|
63
|
+
|
64
|
+
it "should be terminated after a 'drop'" do
|
65
|
+
valid_context_definition.should_receive(:proc_for).with('drop').and_return([proc{drop}, []])
|
66
|
+
subject.call('drop')
|
67
|
+
subject.should be_terminated
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "given a wrong context" do
|
72
|
+
subject { Cuttlebone::Session::Base.new('invalid_context') }
|
73
|
+
|
74
|
+
it "should indicate internal error" do
|
75
|
+
subject.internal_error.should_not be_blank
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be terminated" do
|
79
|
+
subject.should be_terminated
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'cuttlebone'))
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
config.alias_it_should_behave_like_to(:it_should_behave_like, '')
|
6
|
+
config.filter_run :focused => true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# (active_support)/lib/active_support/core_ext/module/remove_method.rb
|
2
|
+
class Module
|
3
|
+
def remove_possible_method(method)
|
4
|
+
remove_method(method)
|
5
|
+
rescue NameError
|
6
|
+
end
|
7
|
+
|
8
|
+
def redefine_method(method, &block)
|
9
|
+
remove_possible_method(method)
|
10
|
+
define_method(method, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# (active_support)/lib/active_support/core_ext/module/delegation.rb (stripped)
|
15
|
+
class Module
|
16
|
+
def delegate(*methods)
|
17
|
+
options = methods.pop
|
18
|
+
unless options.is_a?(Hash) && to = options[:to]
|
19
|
+
raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
|
20
|
+
end
|
21
|
+
|
22
|
+
if options[:prefix] == true && options[:to].to_s =~ /^[^a-z_]/
|
23
|
+
raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
|
24
|
+
end
|
25
|
+
|
26
|
+
prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || ''
|
27
|
+
|
28
|
+
file, line = caller.first.split(':', 2)
|
29
|
+
line = line.to_i
|
30
|
+
|
31
|
+
methods.each do |method|
|
32
|
+
on_nil =
|
33
|
+
if options[:allow_nil]
|
34
|
+
'return'
|
35
|
+
else
|
36
|
+
%(raise "#{self}##{prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
37
|
+
end
|
38
|
+
|
39
|
+
module_eval(<<-EOS, file, line - 5)
|
40
|
+
if instance_methods(false).map(&:to_s).include?("#{prefix}#{method}")
|
41
|
+
remove_possible_method("#{prefix}#{method}")
|
42
|
+
end
|
43
|
+
|
44
|
+
def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block)
|
45
|
+
#{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block)
|
46
|
+
rescue NoMethodError # rescue NoMethodError
|
47
|
+
if #{to}.nil? # if client.nil?
|
48
|
+
#{on_nil} # return # depends on :allow_nil
|
49
|
+
else # else
|
50
|
+
raise # raise
|
51
|
+
end # end
|
52
|
+
end # end
|
53
|
+
EOS
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cuttlebone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.3
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bence Golda
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-03-18 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.0
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.5.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: i18n
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: cucumber
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.10.0
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: rcov
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.9.0
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
description: Cuttlebone helps you creating shell-alike applications.
|
72
|
+
email:
|
73
|
+
- bence@golda.me
|
74
|
+
executables: []
|
75
|
+
|
76
|
+
extensions: []
|
77
|
+
|
78
|
+
extra_rdoc_files:
|
79
|
+
- README.md
|
80
|
+
files:
|
81
|
+
- .gitignore
|
82
|
+
- CHANGELOG.md
|
83
|
+
- Gemfile
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- cuttlebone.gemspec
|
87
|
+
- examples/todo.rb
|
88
|
+
- features/example.feature
|
89
|
+
- features/step_definitions/steps.rb
|
90
|
+
- features/support/env.rb
|
91
|
+
- features/support/test_driver.rb
|
92
|
+
- features/switching_contexts.feature
|
93
|
+
- lib/cuttlebone.rb
|
94
|
+
- lib/cuttlebone/controller.rb
|
95
|
+
- lib/cuttlebone/definition.rb
|
96
|
+
- lib/cuttlebone/exceptions.rb
|
97
|
+
- lib/cuttlebone/session.rb
|
98
|
+
- lib/cuttlebone/session/base.rb
|
99
|
+
- lib/cuttlebone/session/rack.rb
|
100
|
+
- lib/cuttlebone/session/shell.rb
|
101
|
+
- lib/cuttlebone/version.rb
|
102
|
+
- spec/controller_spec.rb
|
103
|
+
- spec/cuttlebone_spec.rb
|
104
|
+
- spec/definition_spec.rb
|
105
|
+
- spec/rcov.opts
|
106
|
+
- spec/session_base_spec.rb
|
107
|
+
- spec/spec.opts
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
- vendor/active_support.rb
|
110
|
+
has_rdoc: true
|
111
|
+
homepage: http://github.com/gbence/cuttlebone
|
112
|
+
licenses: []
|
113
|
+
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options:
|
116
|
+
- --charset=UTF-8
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
hash: 2700429317005123117
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
version: "0"
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 1.3.7
|
134
|
+
requirements: []
|
135
|
+
|
136
|
+
rubyforge_project: cuttlebone
|
137
|
+
rubygems_version: 1.5.3
|
138
|
+
signing_key:
|
139
|
+
specification_version: 3
|
140
|
+
summary: cuttlebone-0.1.3
|
141
|
+
test_files: []
|
142
|
+
|