cuttlebone 0.1.3 → 0.1.4
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/.gitignore +1 -0
- data/Rakefile +31 -0
- data/cuttlebone.gemspec +2 -0
- data/examples/todo.rb +1 -1
- data/features/rack.feature +19 -0
- data/features/step_definitions/rack_steps.rb +13 -0
- data/features/step_definitions/steps.rb +3 -2
- data/features/support/env.rb +8 -1
- data/features/support/test_driver.rb +24 -11
- data/lib/cuttlebone/drivers/base.rb +13 -0
- data/lib/cuttlebone/drivers/rack.rb +154 -0
- data/lib/cuttlebone/{session → drivers}/shell.rb +5 -4
- data/lib/cuttlebone/session.rb +90 -3
- data/lib/cuttlebone/version.rb +1 -1
- data/lib/cuttlebone.rb +8 -2
- data/public/error.html +21 -0
- data/public/favicon.png +0 -0
- data/public/index.html +20 -0
- data/public/javascripts/cuttlebone.js +41 -0
- data/public/javascripts/jquery-1.5.1.min.js +16 -0
- data/public/javascripts/jquery.min.js +16 -0
- data/public/sources/cuttlebone.sass +2 -0
- data/public/sources/error.html.haml +14 -0
- data/public/sources/index.html.haml +16 -0
- data/public/stylesheets/cuttlebone.css +2 -0
- data/spec/drivers_base_spec.rb +7 -0
- data/spec/rack_application_spec.rb +42 -0
- data/spec/rack_driver_spec.rb +19 -0
- data/spec/rack_middleware_spec.rb +68 -0
- data/spec/{session_base_spec.rb → session_spec.rb} +12 -9
- data/vendor/active_support.rb +18 -0
- metadata +46 -8
- data/lib/cuttlebone/session/base.rb +0 -61
- data/lib/cuttlebone/session/rack.rb +0 -4
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -9,11 +9,42 @@ require 'cucumber/rake/task'
|
|
9
9
|
require 'rake/packagetask'
|
10
10
|
require 'rake/gempackagetask'
|
11
11
|
|
12
|
+
begin
|
13
|
+
require 'haml'
|
14
|
+
require 'sass'
|
15
|
+
rescue LoadError
|
16
|
+
end
|
17
|
+
|
12
18
|
CUTTLEBONE_GEMSPEC = eval(File.read(File.expand_path('../cuttlebone.gemspec', __FILE__)))
|
13
19
|
|
14
20
|
desc 'Default: run specs'
|
15
21
|
task :default => 'spec'
|
16
22
|
|
23
|
+
if defined?(Haml) and defined?(Sass)
|
24
|
+
task :gem => 'rack:compile'
|
25
|
+
|
26
|
+
namespace :rack do
|
27
|
+
desc 'Compiles HTML/CSS files.'
|
28
|
+
task :compile do
|
29
|
+
{
|
30
|
+
'index.html.haml' => 'index.html',
|
31
|
+
'error.html.haml' => 'error.html',
|
32
|
+
'cuttlebone.sass' => 'stylesheets/cuttlebone.css'
|
33
|
+
}.each_pair do |f,t|
|
34
|
+
File.open(File.expand_path("../public/#{t}", __FILE__), 'w') do |tt|
|
35
|
+
tt.write(
|
36
|
+
(
|
37
|
+
f =~ /\.haml$/ ?
|
38
|
+
Haml::Engine.new(File.read(File.expand_path("../public/sources/#{f}",__FILE__)), :format => :html5, :ugly => true) :
|
39
|
+
Sass::Engine.new(File.read(File.expand_path("../public/sources/#{f}",__FILE__)))
|
40
|
+
).render()
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
17
48
|
namespace :spec do
|
18
49
|
desc 'Run all specs in spec directory (format=progress)'
|
19
50
|
RSpec::Core::RakeTask.new(:progress) do |t|
|
data/cuttlebone.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_development_dependency "i18n"
|
19
19
|
s.add_development_dependency "cucumber", "~> 0.10.0"
|
20
20
|
s.add_development_dependency "rcov", "~> 0.9.0"
|
21
|
+
s.add_development_dependency "capybara", "~> 0.4.0"
|
22
|
+
s.add_development_dependency "haml", "~> 3.0.0"
|
21
23
|
|
22
24
|
s.files = `git ls-files`.split("\n")
|
23
25
|
s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
|
data/examples/todo.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
@browser @wip
|
2
|
+
Feature: rack compatibility
|
3
|
+
In order to manage my todo list through the web
|
4
|
+
As a programmer
|
5
|
+
I want to connect to a cuttlebone web-server
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the following cuttlebone code:
|
9
|
+
"""
|
10
|
+
context "x" do
|
11
|
+
prompt { 'prompt' }
|
12
|
+
command(?y) { output 'ok' }
|
13
|
+
end
|
14
|
+
"""
|
15
|
+
|
16
|
+
Scenario: start a rack on top of cuttlebone
|
17
|
+
When I start an "x" session on rack
|
18
|
+
And I go to "/"
|
19
|
+
Then I should see "prompt" in the prompt
|
@@ -0,0 +1,13 @@
|
|
1
|
+
When /^I start (?:an? )?"([^"]*)" session on rack$/ do |objects|
|
2
|
+
@d = Cuttlebone::Drivers::Rack.new(*(objects.scan(/([^,]{1,})(?:,\s*)?/).flatten))
|
3
|
+
Capybara.app = @d.send(:app)
|
4
|
+
end
|
5
|
+
|
6
|
+
When /^I go to "([^"]*)"$/ do |path|
|
7
|
+
visit path
|
8
|
+
end
|
9
|
+
|
10
|
+
Then /^I should see "([^"]*)" in the prompt$/ do |text|
|
11
|
+
page.should have_xpath('//span[@id="prompt"]', :text => text)
|
12
|
+
end
|
13
|
+
|
@@ -17,7 +17,7 @@ Given /^a started "([^"]*)" session$/ do |objects|
|
|
17
17
|
end
|
18
18
|
|
19
19
|
When /^I start (?:an? )?"([^"]*)" session$/ do |objects|
|
20
|
-
@d = Cuttlebone::
|
20
|
+
@d = Cuttlebone::Drivers::Test.new(*(objects.scan(/([^,]{1,})(?:,\s*)?/).flatten))
|
21
21
|
end
|
22
22
|
|
23
23
|
# invocation
|
@@ -71,6 +71,7 @@ Then /^I should see an error$/ do
|
|
71
71
|
end
|
72
72
|
|
73
73
|
Then /^I should get an error$/ do
|
74
|
-
@d.internal_error.should_not
|
74
|
+
@d.internal_error.should_not be_nil
|
75
|
+
@d.internal_error.should_not be_empty
|
75
76
|
end
|
76
77
|
|
data/features/support/env.rb
CHANGED
@@ -1,2 +1,9 @@
|
|
1
|
-
require File.expand_path(
|
1
|
+
require File.expand_path('../../../lib/cuttlebone.rb', __FILE__)
|
2
2
|
require 'cucumber/formatter/unicode'
|
3
|
+
|
4
|
+
require 'capybara/cucumber'
|
5
|
+
require 'capybara/session'
|
6
|
+
Capybara.default_selector = :css
|
7
|
+
Capybara.default_driver = :selenium
|
8
|
+
#Capybara.app = 0 #
|
9
|
+
|
@@ -1,16 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# TODO FIXME
|
2
|
+
class Cuttlebone::Drivers::Test < Cuttlebone::Drivers::Base
|
3
|
+
def initialize *stack_objects
|
3
4
|
super
|
4
|
-
|
5
|
-
|
6
|
-
@stack_history = [ stack ]
|
5
|
+
@session = Cuttlebone::Session.sessions.create(*stack_objects)
|
6
|
+
save_stack_to_history
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
@stack_history << stack
|
13
|
-
a, n, o, e = super(command)
|
9
|
+
def call command
|
10
|
+
a, n, o, e = @session.call(command)
|
11
|
+
save_stack_to_history
|
14
12
|
@output = o
|
15
13
|
@error = e
|
16
14
|
[ a, n, o, e ]
|
@@ -18,7 +16,22 @@ class Cuttlebone::Session::Test < Cuttlebone::Session::Base
|
|
18
16
|
|
19
17
|
attr_reader :output, :error
|
20
18
|
|
19
|
+
def active_context
|
20
|
+
@history[-1].last
|
21
|
+
end
|
22
|
+
|
21
23
|
def previous_active_context
|
22
|
-
@
|
24
|
+
@history[-2].last
|
25
|
+
end
|
26
|
+
|
27
|
+
delegate :terminated?, :prompt, :internal_error, :to => '@session'
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def save_stack_to_history
|
32
|
+
stack = []
|
33
|
+
@session.stack.each { |c| stack << c }
|
34
|
+
(@history ||= []) << stack
|
23
35
|
end
|
36
|
+
|
24
37
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Cuttlebone::Drivers::Base
|
2
|
+
def initialize *stack_objects
|
3
|
+
Cuttlebone::Session.set_default *stack_objects
|
4
|
+
end
|
5
|
+
|
6
|
+
def sessions
|
7
|
+
Cuttlebone::Session.sessions
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
raise NotImplementedError, "You must implement #run in your #{send(:class).name}!"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'haml'
|
3
|
+
require 'sass/plugin/rack'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class Cuttlebone::Drivers::Rack < Cuttlebone::Drivers::Base
|
7
|
+
|
8
|
+
##
|
9
|
+
#
|
10
|
+
# Cuttlebone::Drivers::Rack::Middleware
|
11
|
+
#
|
12
|
+
# It responds only to predefined HTTP POST calls and deals them according to
|
13
|
+
# Cuttlebone definitions and the associated driver's internal state.
|
14
|
+
#
|
15
|
+
class Middleware
|
16
|
+
|
17
|
+
def initialize app, driver=nil
|
18
|
+
@app = app
|
19
|
+
@driver = driver || Cuttlebone::Drivers::Rack.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def call env
|
23
|
+
@request = Rack::Request.new(env)
|
24
|
+
|
25
|
+
return(@app.call(env)) unless @request.post?
|
26
|
+
return(@app.call(env)) unless @request.path =~ %r{^/(?:(?:prompt|call)/([0-9a-f]{40}))|init$}
|
27
|
+
# return(@app.call(env)) unless @request.is_json_request?
|
28
|
+
|
29
|
+
@response = Rack::Response.new
|
30
|
+
@response['Content-Type'] = 'application/json'
|
31
|
+
|
32
|
+
if @request.path == '/init'
|
33
|
+
session = @driver.sessions.create
|
34
|
+
json 'id' => session.id, 'prompt' => session.prompt
|
35
|
+
elsif @request.path =~ %r{/prompt/([0-9a-f]{40})}
|
36
|
+
id = $1
|
37
|
+
session = @driver.sessions[id]
|
38
|
+
json 'id' => session.id, 'prompt' => session.prompt
|
39
|
+
elsif @request.path =~ %r{/call/([0-9a-f]{40})}
|
40
|
+
id = $1
|
41
|
+
session = @driver.sessions[id]
|
42
|
+
|
43
|
+
if command=@request.params['command']
|
44
|
+
_, _, output, error = session.call(command.force_encoding("UTF-8"))
|
45
|
+
json 'id' => session.id, 'prompt' => session.prompt, 'output' => output, 'error' => error
|
46
|
+
else
|
47
|
+
@response.status = 409
|
48
|
+
json 'id' => session.id, 'prompt' => session.prompt, 'error' => 'No command was given!'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return(@response.finish)
|
53
|
+
rescue
|
54
|
+
json 'id' => (session.id rescue nil), 'prompt' => (session.prompt rescue nil), 'error' => $!.message
|
55
|
+
return(@response.finish)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def json data
|
61
|
+
@response.write(data.to_json)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
#
|
67
|
+
# Cuttlebone::Driver::Rack::Application
|
68
|
+
#
|
69
|
+
# It's a fully functional rack-enabled application that serves all other
|
70
|
+
# materials for a fully functional Cuttlebone web server.
|
71
|
+
#
|
72
|
+
class Application
|
73
|
+
|
74
|
+
STATIC_FILES = %w{ /index.html /favicon.png /stylesheets/cuttlebone.css /javascripts/cuttlebone.js /javascripts/jquery.min.js }
|
75
|
+
|
76
|
+
def self.public_path path=''
|
77
|
+
File.expand_path("../../../../public/#{path}", __FILE__)
|
78
|
+
end
|
79
|
+
|
80
|
+
def call(env)
|
81
|
+
@request = Rack::Request.new(env)
|
82
|
+
@response = Rack::Response.new
|
83
|
+
|
84
|
+
if @request.get? and @request.path == '/'
|
85
|
+
redirect_to '/index.html'
|
86
|
+
elsif @request.get? and STATIC_FILES.include?(@request.path)
|
87
|
+
static @request.path
|
88
|
+
else
|
89
|
+
error 'Not found.', 404
|
90
|
+
end
|
91
|
+
|
92
|
+
@response.finish
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def static path
|
98
|
+
@response.write(File.read(File.expand_path("../../../../public#{path}", __FILE__)))
|
99
|
+
end
|
100
|
+
|
101
|
+
def error message, status=500
|
102
|
+
@response.write(File.read(File.expand_path("../../../../public/error.html", __FILE__)).gsub(/Error happens. It always does./, message))
|
103
|
+
@response.status = status
|
104
|
+
end
|
105
|
+
|
106
|
+
def redirect_to path
|
107
|
+
@response.redirect(path)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
#
|
113
|
+
# Starts Cuttlebone Rack application.
|
114
|
+
#
|
115
|
+
def run
|
116
|
+
trap(:INT) do
|
117
|
+
if server.respond_to?(:shutdown)
|
118
|
+
server.shutdown
|
119
|
+
else
|
120
|
+
exit
|
121
|
+
end
|
122
|
+
end
|
123
|
+
server.run app, :Host=>'0.0.0.0', :Port=>9292
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
##
|
129
|
+
#
|
130
|
+
# Builds Rack application stack.
|
131
|
+
#
|
132
|
+
# @return Rack::Application
|
133
|
+
#
|
134
|
+
def app
|
135
|
+
driver = self
|
136
|
+
Rack::Builder.app do
|
137
|
+
use Rack::ShowExceptions
|
138
|
+
use Rack::Lint
|
139
|
+
use Rack::ContentType
|
140
|
+
use Rack::ContentLength
|
141
|
+
use Rack::Session::Pool, :expire_after => 2592000
|
142
|
+
use Middleware, driver
|
143
|
+
run Application.new
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
#
|
149
|
+
# Returns a Rack server.
|
150
|
+
#
|
151
|
+
def server
|
152
|
+
@server ||= ::Rack::Handler.default()
|
153
|
+
end
|
154
|
+
end
|
@@ -1,14 +1,15 @@
|
|
1
1
|
# TODO FIXME load readline iff it was invoked through command line interface
|
2
2
|
require 'readline'
|
3
3
|
|
4
|
-
class Cuttlebone::
|
4
|
+
class Cuttlebone::Drivers::Shell < Cuttlebone::Drivers::Base
|
5
5
|
def run
|
6
|
+
session = Cuttlebone::Session.sessions.create
|
6
7
|
loop do
|
7
|
-
break if terminated?
|
8
|
-
command = Readline::readline("#{prompt} > ")
|
8
|
+
break if session.terminated?
|
9
|
+
command = Readline::readline("#{session.prompt} > ")
|
9
10
|
break unless command
|
10
11
|
Readline::HISTORY.push(command)
|
11
|
-
_, _, output, error = call(command)
|
12
|
+
_, _, output, error = session.call(command)
|
12
13
|
print (output<<'').join("\n")
|
13
14
|
print "\033[01;31m#{error}\033[00m\n" if error
|
14
15
|
end
|
data/lib/cuttlebone/session.rb
CHANGED
@@ -1,4 +1,91 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
class Cuttlebone::Session
|
4
|
+
|
5
|
+
# TODO FIXME locking / mutexes / serialization / deserialization
|
6
|
+
class NotFound < StandardError; end
|
7
|
+
|
8
|
+
@@sessions = {}
|
9
|
+
|
10
|
+
@@default_stack_objects = []
|
11
|
+
|
12
|
+
module SessionCollectionExtensions
|
13
|
+
def [] key
|
14
|
+
raise NotFound unless has_key?(key)
|
15
|
+
super(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create *stack_objects
|
19
|
+
s = Cuttlebone::Session.new(*stack_objects)
|
20
|
+
send(:[]=, s.id, s)
|
21
|
+
s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
@@sessions.extend SessionCollectionExtensions
|
26
|
+
|
27
|
+
def self.set_default *stack_objects
|
28
|
+
@@default_stack_objects = stack_objects
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.sessions
|
32
|
+
@@sessions
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize *stack_objects
|
36
|
+
options = stack_objects.extract_options!
|
37
|
+
@id = options[:id] || Digest::SHA1.hexdigest(Time.now.to_s + Time.now.usec.to_s + rand(1000).to_s) # TODO FIXME
|
38
|
+
@@default_stack_objects.each { |so| stack_objects << (so.dup rescue so) } if stack_objects.empty?
|
39
|
+
@stack ||= stack_objects.map{ |o| Cuttlebone::Controller.new(self, o) }
|
40
|
+
rescue Cuttlebone::InvalidContextError => e
|
41
|
+
@internal_error = %{Context initialization failed for #{e.context.inspect}!}
|
42
|
+
rescue => e
|
43
|
+
@internal_error = %{Internal error occured: #{e.message} (#{e.class})}
|
44
|
+
ensure
|
45
|
+
@stack ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :id, :stack, :internal_error
|
49
|
+
|
50
|
+
def active_context
|
51
|
+
stack.last
|
52
|
+
end
|
53
|
+
|
54
|
+
delegate :name, :prompt, :to => :active_context
|
55
|
+
|
56
|
+
def call command
|
57
|
+
process command
|
58
|
+
end
|
59
|
+
|
60
|
+
def terminated?
|
61
|
+
stack.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def process command
|
67
|
+
active_context = stack.pop
|
68
|
+
|
69
|
+
action, next_context, output, error = begin
|
70
|
+
a, n, o, e = active_context.process(command)
|
71
|
+
raise ArgumentError, "Unknown action: #{a}" unless [ :self, :replace, :add, :drop ].include?(a.to_s.to_sym)
|
72
|
+
raise TypeError, "Output must be an instance of String or nil!" unless o.is_a?(Array) or o.nil?
|
73
|
+
raise TypeError, "Error must be an instance of String or nil!" unless e.is_a?(String) or e.nil?
|
74
|
+
[ a.to_s.to_sym, n, o, e ]
|
75
|
+
rescue => e
|
76
|
+
[ :self, active_context, nil, %{Cuttlebone::Session: #{e.message} (#{e.class})} ]
|
77
|
+
end
|
78
|
+
case action
|
79
|
+
when :self
|
80
|
+
stack << active_context
|
81
|
+
when :replace
|
82
|
+
stack << Cuttlebone::Controller.new(self, next_context)
|
83
|
+
when :add
|
84
|
+
stack << active_context << Cuttlebone::Controller.new(self, next_context)
|
85
|
+
when :drop
|
86
|
+
# noop
|
87
|
+
end
|
88
|
+
[ action, next_context, output, error ]
|
89
|
+
end
|
90
|
+
|
4
91
|
end
|
data/lib/cuttlebone/version.rb
CHANGED
data/lib/cuttlebone.rb
CHANGED
@@ -8,11 +8,17 @@ module Cuttlebone
|
|
8
8
|
autoload :Definition, 'cuttlebone/definition'
|
9
9
|
autoload :Session, 'cuttlebone/session'
|
10
10
|
|
11
|
+
module Drivers
|
12
|
+
autoload :Base, 'cuttlebone/drivers/base'
|
13
|
+
autoload :Shell, 'cuttlebone/drivers/shell'
|
14
|
+
autoload :Rack, 'cuttlebone/drivers/rack'
|
15
|
+
end
|
16
|
+
|
11
17
|
@@definitions = []
|
12
18
|
def self.definitions; @@definitions; end
|
13
19
|
|
14
|
-
def self.run
|
15
|
-
default_driver.new(
|
20
|
+
def self.run stack_objects, default_driver=Drivers::Shell
|
21
|
+
default_driver.new(stack_objects).run
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
data/public/error.html
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Cuttlebone - Error</title>
|
5
|
+
<link href='/stylesheets/cuttlebone.css?1301248065' rel='stylesheet' type='text/css'>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<div class='output'>
|
9
|
+
<ul>
|
10
|
+
<li class='error'>Error happens. It always does.</li>
|
11
|
+
</ul>
|
12
|
+
</div>
|
13
|
+
<div class='actions'>
|
14
|
+
<ul>
|
15
|
+
<li>
|
16
|
+
<a href='/'>back to console</a>
|
17
|
+
</li>
|
18
|
+
</ul>
|
19
|
+
</div>
|
20
|
+
</body>
|
21
|
+
</html>
|
data/public/favicon.png
ADDED
File without changes
|
data/public/index.html
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Cuttlebone</title>
|
5
|
+
<link href='/favicon.png?1301248065' rel='icon' type='image/png'>
|
6
|
+
<link href='/stylesheets/cuttlebone.css?1301248065' rel='stylesheet' type='text/css'>
|
7
|
+
<script src='/javascripts/jquery.min.js?1301248065' type='text/javascript'></script>
|
8
|
+
<script src='/javascripts/cuttlebone.js?1301248065' type='text/javascript'></script>
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
<div class='output'>
|
12
|
+
<ul></ul>
|
13
|
+
</div>
|
14
|
+
<div class='input'>
|
15
|
+
<span id='prompt'></span>
|
16
|
+
<span>></span>
|
17
|
+
<input id='input' name='command' type='text'>
|
18
|
+
</div>
|
19
|
+
</body>
|
20
|
+
</html>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
// I know that it's ugly, but this is a POC/WIP project.
|
2
|
+
// TODO:
|
3
|
+
// * get rid of that global variable
|
4
|
+
// * #input should determine (with a special html5 attribute) which session
|
5
|
+
// will it talk to
|
6
|
+
// * #input should know about the updated output too
|
7
|
+
// * create a convenience function that sets up and handles all the magic
|
8
|
+
|
9
|
+
var cuttlebone_session_id;
|
10
|
+
|
11
|
+
jQuery(document).ready(function(){
|
12
|
+
// updates prompt
|
13
|
+
jQuery.ajax({type:'POST',url:"/init",dataType:"json",success:function(d){if(d['id']){cuttlebone_session_id=d['id'];};if(d['prompt']){jQuery('#prompt').html(d['prompt']);};}});
|
14
|
+
});
|
15
|
+
|
16
|
+
// evaluates input
|
17
|
+
jQuery("#input").live("keypress", function(e) {
|
18
|
+
if (e.keyCode == 13) {
|
19
|
+
jQuery.ajax({
|
20
|
+
type: "POST",
|
21
|
+
url: "/call/"+cuttlebone_session_id,
|
22
|
+
data: {command:jQuery('#input').val()},
|
23
|
+
dataType: "json",
|
24
|
+
success: function(d){
|
25
|
+
if (d['output']) {
|
26
|
+
jQuery.each(d['output'], function(i,v) {
|
27
|
+
jQuery('.output ul').append('<li><pre>'+v+'</pre></li>');
|
28
|
+
});
|
29
|
+
};
|
30
|
+
if (d['error']) {
|
31
|
+
jQuery('.output ul').append('<li class="error">'+d['error']+'</li>');
|
32
|
+
};
|
33
|
+
if (d['prompt']) {
|
34
|
+
jQuery('#prompt').html(d['prompt']);
|
35
|
+
}
|
36
|
+
}
|
37
|
+
});
|
38
|
+
$(this).val('');
|
39
|
+
};
|
40
|
+
});
|
41
|
+
|