cuttlebone 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -1
- data/Rakefile +3 -3
- data/cuttlebone.gemspec +5 -0
- data/lib/cuttlebone.rb +1 -0
- data/lib/cuttlebone/controller.rb +2 -2
- data/lib/cuttlebone/drivers/rack.rb +50 -55
- data/lib/cuttlebone/drivers/xmpp.rb +61 -0
- data/lib/cuttlebone/version.rb +1 -1
- data/public/error.html +7 -6
- data/public/index.html +5 -8
- data/public/javascripts/cuttlebone.js +30 -11
- data/public/sources/cuttlebone.sass +43 -1
- data/public/sources/error.html.haml +6 -5
- data/public/sources/index.html.haml +3 -3
- data/public/stylesheets/cuttlebone.css +39 -1
- data/vendor/rexml_utf8_fix.rb +304 -0
- metadata +54 -19
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
## 0.1.
|
1
|
+
## 0.1.5 / 2011-03-29
|
2
|
+
|
3
|
+
* Added some new CSS and JS to improve user experience.
|
4
|
+
* Added XMPP driver (without tests).
|
5
|
+
|
6
|
+
## 0.1.4 / 2011-03-27
|
7
|
+
|
8
|
+
* Added Rack driver and its supporting files.
|
9
|
+
|
10
|
+
## 0.1.2-0.1.3 / 2011-03-18
|
2
11
|
|
3
12
|
* Changed directory structure to look like a real gem. :)
|
4
13
|
* Finalized initial test cases (spec/cucumber).
|
data/Rakefile
CHANGED
@@ -37,7 +37,7 @@ if defined?(Haml) and defined?(Sass)
|
|
37
37
|
f =~ /\.haml$/ ?
|
38
38
|
Haml::Engine.new(File.read(File.expand_path("../public/sources/#{f}",__FILE__)), :format => :html5, :ugly => true) :
|
39
39
|
Sass::Engine.new(File.read(File.expand_path("../public/sources/#{f}",__FILE__)))
|
40
|
-
|
40
|
+
).render()
|
41
41
|
)
|
42
42
|
end
|
43
43
|
end
|
@@ -72,10 +72,10 @@ desc 'Run all cucumber tests.'
|
|
72
72
|
Cucumber::Rake::Task.new do |t|
|
73
73
|
end
|
74
74
|
|
75
|
-
desc 'Generate documentation for
|
75
|
+
desc 'Generate documentation for cuttlebone.'
|
76
76
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
77
77
|
rdoc.rdoc_dir = 'rdoc'
|
78
|
-
rdoc.title = '
|
78
|
+
rdoc.title = 'Cuttlebone'
|
79
79
|
rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
|
80
80
|
rdoc.rdoc_files.include('README')
|
81
81
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
data/cuttlebone.gemspec
CHANGED
@@ -11,8 +11,13 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.description = "Cuttlebone helps you creating shell-alike applications."
|
12
12
|
|
13
13
|
s.rubyforge_project = "cuttlebone"
|
14
|
+
#s.required_ruby_version = ">= 1.9.2"
|
14
15
|
s.required_rubygems_version = ">= 1.3.7"
|
15
16
|
|
17
|
+
s.add_runtime_dependency "rack", "~> 1.2.2"
|
18
|
+
s.add_runtime_dependency "json", "~> 1.5.0"
|
19
|
+
s.add_runtime_dependency "xmpp4r", "~> 0.5"
|
20
|
+
|
16
21
|
s.add_development_dependency "bundler", "~> 1.0.0"
|
17
22
|
s.add_development_dependency "rspec", "~> 2.5.0"
|
18
23
|
s.add_development_dependency "i18n"
|
data/lib/cuttlebone.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
require 'rack'
|
2
|
-
require 'haml'
|
3
|
-
require 'sass/plugin/rack'
|
4
2
|
require 'json'
|
5
3
|
|
6
4
|
class Cuttlebone::Drivers::Rack < Cuttlebone::Drivers::Base
|
@@ -20,45 +18,51 @@ class Cuttlebone::Drivers::Rack < Cuttlebone::Drivers::Base
|
|
20
18
|
end
|
21
19
|
|
22
20
|
def call env
|
23
|
-
|
21
|
+
request = Rack::Request.new(env)
|
24
22
|
|
25
|
-
return(@app.call(env)) unless
|
26
|
-
return(@app.call(env)) unless
|
23
|
+
return(@app.call(env)) unless request.post?
|
24
|
+
return(@app.call(env)) unless request.path =~ %r{^/(?:(?:prompt|call)/([0-9a-f]{40}))|init$}
|
27
25
|
# return(@app.call(env)) unless @request.is_json_request?
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
27
|
+
response = Rack::Response.new
|
28
|
+
response['Content-Type'] = 'application/json'
|
29
|
+
|
30
|
+
results = { 'id' => nil, 'prompt' => nil }
|
31
|
+
|
32
|
+
begin
|
33
|
+
session = get_session_for(request.path)
|
34
|
+
raise ArgumentError, "Cannot load cuttlebone-session!" unless session
|
35
|
+
|
36
|
+
case request.path
|
37
|
+
when '/init', %r{^/prompt/}
|
38
|
+
results.merge! 'id' => session.id, 'prompt' => session.prompt
|
39
|
+
when %r{^/call/}
|
40
|
+
if command=request.params['command']
|
41
|
+
_, _, output, error = session.call(command.force_encoding("UTF-8"))
|
42
|
+
results.merge! 'id' => session.id, 'prompt' => session.prompt, 'output' => output, 'error' => error
|
43
|
+
else
|
44
|
+
response.status = 409
|
45
|
+
results.merge! 'id' => session.id, 'prompt' => session.prompt, 'error' => 'No command was given!'
|
46
|
+
end
|
49
47
|
end
|
48
|
+
rescue
|
49
|
+
results.merge! 'id' => (session.id rescue nil), 'prompt' => (session.prompt rescue nil), 'error' => $!.message
|
50
50
|
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
return(@response.finish)
|
52
|
+
response.write(results.to_json)
|
53
|
+
|
54
|
+
return(response.finish)
|
56
55
|
end
|
57
56
|
|
58
57
|
private
|
59
58
|
|
60
|
-
def
|
61
|
-
|
59
|
+
def get_session_for path
|
60
|
+
case path
|
61
|
+
when '/init'
|
62
|
+
@driver.sessions.create
|
63
|
+
when %r{^/(?:prompt|call)/([0-9a-f]{40})$}
|
64
|
+
@driver.sessions[$1]
|
65
|
+
end
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
@@ -78,33 +82,24 @@ class Cuttlebone::Drivers::Rack < Cuttlebone::Drivers::Base
|
|
78
82
|
end
|
79
83
|
|
80
84
|
def call(env)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
if
|
85
|
-
|
86
|
-
elsif
|
87
|
-
|
85
|
+
request = Rack::Request.new(env)
|
86
|
+
response = Rack::Response.new
|
87
|
+
|
88
|
+
if request.get? and request.path == '/'
|
89
|
+
response.redirect('/index.html')
|
90
|
+
elsif request.get? and STATIC_FILES.include?(request.path)
|
91
|
+
response['Content-Type'] = 'application/javascript' if request.path =~ /\.js$/
|
92
|
+
response['Content-Type'] = 'text/css' if request.path =~ /\.css$/
|
93
|
+
response['Content-Type'] = 'image/png' if request.path =~ /\.png$/
|
94
|
+
|
95
|
+
response.write(File.read(File.expand_path("../../../../public#{request.path}", __FILE__)))
|
96
|
+
response.status = 200
|
88
97
|
else
|
89
|
-
error 'Not found.'
|
98
|
+
response.write(File.read(File.expand_path("../../../../public/error.html", __FILE__)).gsub(/Error happens. It always does./, 'Not found.'))
|
99
|
+
response.status = 404
|
90
100
|
end
|
91
101
|
|
92
|
-
|
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)
|
102
|
+
response.finish
|
108
103
|
end
|
109
104
|
end
|
110
105
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'xmpp4r/client'
|
2
|
+
require 'xmpp4r/roster'
|
3
|
+
require File.expand_path('../../../../vendor/rexml_utf8_fix', __FILE__)
|
4
|
+
|
5
|
+
class Cuttlebone::Drivers::XMPP < Cuttlebone::Drivers::Base
|
6
|
+
@@jid = 'cuttlebone@localhost'
|
7
|
+
@@password = 'cuttlebone'
|
8
|
+
|
9
|
+
def self.jid= jid
|
10
|
+
@@jid = jid
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.password= password
|
14
|
+
@@password = password
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
client = Jabber::Client.new(@@jid)
|
19
|
+
client.connect()
|
20
|
+
client.auth(@@password)
|
21
|
+
client.send(Jabber::Presence.new.set_type(:available))
|
22
|
+
|
23
|
+
roster = Jabber::Roster::Helper.new(client)
|
24
|
+
|
25
|
+
client.add_message_callback do |message|
|
26
|
+
unless message.composing? or message.body.nil?
|
27
|
+
begin
|
28
|
+
session = sessions[message.from]
|
29
|
+
rescue Cuttlebone::Session::NotFound
|
30
|
+
session = sessions.create(:id => message.from)
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
_, _, output, error = session.call(message.body.force_encoding("UTF-8"))
|
35
|
+
rescue
|
36
|
+
output, error = nil, $!.message
|
37
|
+
end
|
38
|
+
|
39
|
+
result = Jabber::Message.new
|
40
|
+
result.to = message.from
|
41
|
+
result.body = output.join("\n") if output
|
42
|
+
result.body += %{<<#{error}>>} if error
|
43
|
+
client.send(result)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
roster.add_subscription_request_callback do |item,presence|
|
48
|
+
roster.accept_subscription(presence.from)
|
49
|
+
client.send(Jabber::Presence.new.set_type(:subscribe).set_to(presence.from))
|
50
|
+
|
51
|
+
m = Jabber::Message.new
|
52
|
+
m.to = presence.from
|
53
|
+
m.body = "Registered!"
|
54
|
+
client.send(m)
|
55
|
+
end
|
56
|
+
|
57
|
+
Thread.stop
|
58
|
+
|
59
|
+
client.close
|
60
|
+
end
|
61
|
+
end
|
data/lib/cuttlebone/version.rb
CHANGED
data/public/error.html
CHANGED
@@ -2,14 +2,10 @@
|
|
2
2
|
<html>
|
3
3
|
<head>
|
4
4
|
<title>Cuttlebone - Error</title>
|
5
|
-
<link href='/
|
5
|
+
<link href='/favicon.png?1301363510' rel='icon' type='image/png'>
|
6
|
+
<link href='/stylesheets/cuttlebone.css?1301363510' media='screen' rel='stylesheet' type='text/css'>
|
6
7
|
</head>
|
7
8
|
<body>
|
8
|
-
<div class='output'>
|
9
|
-
<ul>
|
10
|
-
<li class='error'>Error happens. It always does.</li>
|
11
|
-
</ul>
|
12
|
-
</div>
|
13
9
|
<div class='actions'>
|
14
10
|
<ul>
|
15
11
|
<li>
|
@@ -17,5 +13,10 @@
|
|
17
13
|
</li>
|
18
14
|
</ul>
|
19
15
|
</div>
|
16
|
+
<div class='output'>
|
17
|
+
<ul>
|
18
|
+
<li class='error'>Error happens. It always does.</li>
|
19
|
+
</ul>
|
20
|
+
</div>
|
20
21
|
</body>
|
21
22
|
</html>
|
data/public/index.html
CHANGED
@@ -2,19 +2,16 @@
|
|
2
2
|
<html>
|
3
3
|
<head>
|
4
4
|
<title>Cuttlebone</title>
|
5
|
-
<link href='/favicon.png?
|
6
|
-
<link href='/stylesheets/cuttlebone.css?
|
7
|
-
<script src='/javascripts/jquery.min.js?
|
8
|
-
<script src='/javascripts/cuttlebone.js?
|
5
|
+
<link href='/favicon.png?1301363510' rel='icon' type='image/png'>
|
6
|
+
<link href='/stylesheets/cuttlebone.css?1301363510' media='screen' rel='stylesheet' type='text/css'>
|
7
|
+
<script src='/javascripts/jquery.min.js?1301363510' type='text/javascript'></script>
|
8
|
+
<script src='/javascripts/cuttlebone.js?1301363510' type='text/javascript'></script>
|
9
9
|
</head>
|
10
10
|
<body>
|
11
11
|
<div class='output'>
|
12
12
|
<ul></ul>
|
13
13
|
</div>
|
14
|
-
<div class='input'>
|
15
|
-
<span id='prompt'></span>
|
16
|
-
<span>></span>
|
17
|
-
<input id='input' name='command' type='text'>
|
14
|
+
<div class='input'><span id='prompt'></span><span>></span><input id='input' name='command' type='text'>
|
18
15
|
</div>
|
19
16
|
</body>
|
20
17
|
</html>
|
@@ -1,26 +1,47 @@
|
|
1
1
|
// I know that it's ugly, but this is a POC/WIP project.
|
2
2
|
// TODO:
|
3
|
-
// * get rid of that global variable
|
4
3
|
// * #input should determine (with a special html5 attribute) which session
|
5
4
|
// will it talk to
|
6
5
|
// * #input should know about the updated output too
|
7
6
|
// * create a convenience function that sets up and handles all the magic
|
8
7
|
|
9
|
-
var
|
8
|
+
var cuttlebone = {
|
9
|
+
|
10
|
+
sessionId: null,
|
11
|
+
|
12
|
+
init: function() {
|
13
|
+
jQuery.ajax({
|
14
|
+
type: 'POST',
|
15
|
+
url: '/init',
|
16
|
+
dataType: 'json',
|
17
|
+
success: function(d){cuttlebone.updateSessionId(d);cuttlebone.updatePrompt(d);}
|
18
|
+
});
|
19
|
+
},
|
20
|
+
|
21
|
+
updateSessionId: function(results) {
|
22
|
+
cuttlebone.sessionId = results['id'];
|
23
|
+
},
|
24
|
+
|
25
|
+
updatePrompt: function(results) {
|
26
|
+
if (results) {jQuery('#prompt').html(results['prompt']);w=jQuery('div.input').width();jQuery('div.input span').each(function(){w-=jQuery(this).outerWidth();});jQuery('input#input').width(w-6);}
|
27
|
+
else {jQuery.ajax({type:'POST',url:'/prompt/'+cuttlebone.sessionId,dataType:'json',success:function(d){cuttlebone.updatePrompt(d);}});}
|
28
|
+
}
|
29
|
+
|
30
|
+
};
|
10
31
|
|
11
32
|
jQuery(document).ready(function(){
|
12
|
-
|
13
|
-
jQuery
|
33
|
+
cuttlebone.init();
|
34
|
+
jQuery('#input').focus();
|
14
35
|
});
|
15
36
|
|
16
37
|
// evaluates input
|
17
|
-
jQuery(
|
38
|
+
jQuery('#input').live('keypress', function(e) {
|
18
39
|
if (e.keyCode == 13) {
|
19
40
|
jQuery.ajax({
|
20
|
-
type:
|
21
|
-
url:
|
41
|
+
type: 'POST',
|
42
|
+
url: '/call/'+cuttlebone.sessionId,
|
22
43
|
data: {command:jQuery('#input').val()},
|
23
|
-
dataType:
|
44
|
+
dataType: 'json',
|
24
45
|
success: function(d){
|
25
46
|
if (d['output']) {
|
26
47
|
jQuery.each(d['output'], function(i,v) {
|
@@ -30,9 +51,7 @@ jQuery("#input").live("keypress", function(e) {
|
|
30
51
|
if (d['error']) {
|
31
52
|
jQuery('.output ul').append('<li class="error">'+d['error']+'</li>');
|
32
53
|
};
|
33
|
-
|
34
|
-
jQuery('#prompt').html(d['prompt']);
|
35
|
-
}
|
54
|
+
cuttlebone.updatePrompt(d);
|
36
55
|
}
|
37
56
|
});
|
38
57
|
$(this).val('');
|
@@ -1,2 +1,44 @@
|
|
1
|
+
*
|
2
|
+
font-family: monospace
|
3
|
+
margin: 0
|
4
|
+
padding: 0
|
5
|
+
|
1
6
|
body
|
2
|
-
|
7
|
+
width: 900px
|
8
|
+
margin: 0 auto
|
9
|
+
padding: 5px 0 0 0
|
10
|
+
|
11
|
+
.actions
|
12
|
+
ul li
|
13
|
+
list-style-type: none
|
14
|
+
padding: 2px 3px
|
15
|
+
|
16
|
+
.input
|
17
|
+
background: #ddd
|
18
|
+
width: 100%
|
19
|
+
border-left: 1px solid #aaa
|
20
|
+
border-right: 1px solid #aaa
|
21
|
+
border-bottom: 1px solid #aaa
|
22
|
+
|
23
|
+
span
|
24
|
+
padding: 2px 3px
|
25
|
+
|
26
|
+
input
|
27
|
+
border: 0
|
28
|
+
padding: 2px 3px
|
29
|
+
background: #ddd
|
30
|
+
|
31
|
+
.output
|
32
|
+
background: #fff
|
33
|
+
width: 100%
|
34
|
+
border-top: 1px solid #aaa
|
35
|
+
border-left: 1px solid #aaa
|
36
|
+
border-right: 1px solid #aaa
|
37
|
+
|
38
|
+
ul li
|
39
|
+
list-style-type: none
|
40
|
+
padding: 2px 3px
|
41
|
+
border-bottom: 1px solid #aaa
|
42
|
+
|
43
|
+
.error
|
44
|
+
background: #f88
|
@@ -1,14 +1,15 @@
|
|
1
|
-
-
|
1
|
+
- def ts(path); @ts ||= Time.now.strftime('%s'); %{#{path}?#{@ts}}; end
|
2
2
|
!!!
|
3
3
|
%html
|
4
4
|
%head
|
5
5
|
%title Cuttlebone - Error
|
6
|
-
%link{ :type => '
|
6
|
+
%link{ :type => 'image/png', :href => ts('/favicon.png'), :rel => 'icon' }
|
7
|
+
%link{ :type => 'text/css', :href => ts('/stylesheets/cuttlebone.css'), :rel => 'stylesheet', :media => 'screen' }
|
7
8
|
%body
|
8
|
-
%div.output
|
9
|
-
%ul
|
10
|
-
%li.error Error happens. It always does.
|
11
9
|
%div.actions
|
12
10
|
%ul
|
13
11
|
%li
|
14
12
|
%a{ :href => '/' } back to console
|
13
|
+
%div.output
|
14
|
+
%ul
|
15
|
+
%li.error Error happens. It always does.
|
@@ -4,13 +4,13 @@
|
|
4
4
|
%head
|
5
5
|
%title Cuttlebone
|
6
6
|
%link{ :type => 'image/png', :href => ts('/favicon.png'), :rel => 'icon' }
|
7
|
-
%link{ :type => 'text/css', :href => ts('/stylesheets/cuttlebone.css'), :rel => 'stylesheet' }
|
7
|
+
%link{ :type => 'text/css', :href => ts('/stylesheets/cuttlebone.css'), :rel => 'stylesheet', :media => 'screen' }
|
8
8
|
%script{ :type => 'text/javascript', :src => ts('/javascripts/jquery.min.js') }
|
9
9
|
%script{ :type => 'text/javascript', :src => ts('/javascripts/cuttlebone.js') }
|
10
10
|
%body
|
11
11
|
%div.output
|
12
12
|
%ul
|
13
13
|
%div.input
|
14
|
-
%span#prompt
|
15
|
-
%span >
|
14
|
+
%span#prompt>
|
15
|
+
%span> >
|
16
16
|
%input#input{ :type => 'text', :name => 'command' }
|
@@ -1,2 +1,40 @@
|
|
1
|
+
* {
|
2
|
+
font-family: monospace;
|
3
|
+
margin: 0;
|
4
|
+
padding: 0; }
|
5
|
+
|
1
6
|
body {
|
2
|
-
|
7
|
+
width: 900px;
|
8
|
+
margin: 0 auto;
|
9
|
+
padding: 5px 0 0 0; }
|
10
|
+
|
11
|
+
.actions ul li {
|
12
|
+
list-style-type: none;
|
13
|
+
padding: 2px 3px; }
|
14
|
+
|
15
|
+
.input {
|
16
|
+
background: #dddddd;
|
17
|
+
width: 100%;
|
18
|
+
border-left: 1px solid #aaaaaa;
|
19
|
+
border-right: 1px solid #aaaaaa;
|
20
|
+
border-bottom: 1px solid #aaaaaa; }
|
21
|
+
.input span {
|
22
|
+
padding: 2px 3px; }
|
23
|
+
.input input {
|
24
|
+
border: 0;
|
25
|
+
padding: 2px 3px;
|
26
|
+
background: #dddddd; }
|
27
|
+
|
28
|
+
.output {
|
29
|
+
background: white;
|
30
|
+
width: 100%;
|
31
|
+
border-top: 1px solid #aaaaaa;
|
32
|
+
border-left: 1px solid #aaaaaa;
|
33
|
+
border-right: 1px solid #aaaaaa; }
|
34
|
+
.output ul li {
|
35
|
+
list-style-type: none;
|
36
|
+
padding: 2px 3px;
|
37
|
+
border-bottom: 1px solid #aaaaaa; }
|
38
|
+
|
39
|
+
.error {
|
40
|
+
background: #ff8888; }
|
@@ -0,0 +1,304 @@
|
|
1
|
+
# REXML cannot handle UTF-8 characters in XML documents without proper XML
|
2
|
+
# header.
|
3
|
+
#
|
4
|
+
# This might be a workaround:
|
5
|
+
#
|
6
|
+
# https://github.com/ln/xmpp4r/issues#issue/3 ==>
|
7
|
+
# http://pastie.org/1458174
|
8
|
+
# http://pastie.org/1454110
|
9
|
+
|
10
|
+
class REXML::IOSource < REXML::Source
|
11
|
+
# block_size has been deprecated
|
12
|
+
def initialize(arg, block_size=500, encoding=nil)
|
13
|
+
@er_source = @source = arg
|
14
|
+
@to_utf = false
|
15
|
+
|
16
|
+
# Determining the encoding is a deceptively difficult issue to resolve.
|
17
|
+
# First, we check the first two bytes for UTF-16. Then we
|
18
|
+
# assume that the encoding is at least ASCII enough for the '>', and
|
19
|
+
# we read until we get one of those. This gives us the XML declaration,
|
20
|
+
# if there is one. If there isn't one, the file MUST be UTF-8, as per
|
21
|
+
# the XML spec. If there is one, we can determine the encoding from
|
22
|
+
# it.
|
23
|
+
@buffer = ""
|
24
|
+
str = @source.read( 2 ) || ''
|
25
|
+
if encoding
|
26
|
+
self.encoding = encoding
|
27
|
+
elsif str[0,2] == "\xfe\xff"
|
28
|
+
@line_break = "\000>"
|
29
|
+
elsif str[0,2] == "\xff\xfe"
|
30
|
+
@line_break = ">\000"
|
31
|
+
elsif str[0,2] == "\xef\xbb"
|
32
|
+
str += @source.read(1)
|
33
|
+
str = '' if (str[2,1] == "\xBF")
|
34
|
+
@line_break = ">"
|
35
|
+
else
|
36
|
+
@line_break = ">"
|
37
|
+
end
|
38
|
+
super( @source.eof? ? str : str+@source.readline( @line_break ) )
|
39
|
+
|
40
|
+
if !@to_utf and
|
41
|
+
@buffer.respond_to?(:force_encoding) and
|
42
|
+
(!@source.respond_to?(:external_encoding) or
|
43
|
+
@source.external_encoding != ::Encoding::UTF_8)
|
44
|
+
@force_utf8 = true
|
45
|
+
else
|
46
|
+
@force_utf8 = false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class REXML::Parsers::BaseParser
|
52
|
+
# Returns the next event. This is a +PullEvent+ object.
|
53
|
+
def pull
|
54
|
+
if @closed
|
55
|
+
x, @closed = @closed, nil
|
56
|
+
return [ :end_element, x ]
|
57
|
+
end
|
58
|
+
return [ :end_document ] if empty?
|
59
|
+
return @stack.shift if @stack.size > 0
|
60
|
+
#STDERR.puts @source.encoding
|
61
|
+
@source.read if @source.buffer.size<2
|
62
|
+
#STDERR.puts "BUFFER = #{@source.buffer.inspect}"
|
63
|
+
if @document_status == nil
|
64
|
+
#@source.consume( /^\s*/um )
|
65
|
+
word = @source.match( /^((?:\s+)|(?:<[^>]*>))/um )
|
66
|
+
word = word[1] unless word.nil?
|
67
|
+
#STDERR.puts "WORD = #{word.inspect}"
|
68
|
+
case word
|
69
|
+
when COMMENT_START
|
70
|
+
return [ :comment, @source.match( COMMENT_PATTERN, true )[1] ]
|
71
|
+
when XMLDECL_START
|
72
|
+
#STDERR.puts "XMLDECL"
|
73
|
+
results = @source.match( XMLDECL_PATTERN, true )[1]
|
74
|
+
version = VERSION.match( results )
|
75
|
+
version = version[1] unless version.nil?
|
76
|
+
encoding = ENCODING.match(results)
|
77
|
+
encoding = encoding[1] unless encoding.nil?
|
78
|
+
@source.encoding = encoding unless encoding.nil?
|
79
|
+
standalone = STANDALONE.match(results)
|
80
|
+
standalone = standalone[1] unless standalone.nil?
|
81
|
+
return [ :xmldecl, version, encoding, standalone ]
|
82
|
+
when INSTRUCTION_START
|
83
|
+
return [ :processing_instruction, *@source.match(INSTRUCTION_PATTERN, true)[1,2] ]
|
84
|
+
when DOCTYPE_START
|
85
|
+
md = @source.match( DOCTYPE_PATTERN, true )
|
86
|
+
@nsstack.unshift(curr_ns=Set.new)
|
87
|
+
identity = md[1]
|
88
|
+
close = md[2]
|
89
|
+
identity =~ IDENTITY
|
90
|
+
name = $1
|
91
|
+
raise REXML::ParseException.new("DOCTYPE is missing a name") if name.nil?
|
92
|
+
pub_sys = $2.nil? ? nil : $2.strip
|
93
|
+
long_name = $4.nil? ? nil : $4.strip
|
94
|
+
uri = $6.nil? ? nil : $6.strip
|
95
|
+
args = [ :start_doctype, name, pub_sys, long_name, uri ]
|
96
|
+
if close == ">"
|
97
|
+
@document_status = :after_doctype
|
98
|
+
@source.read if @source.buffer.size<2
|
99
|
+
md = @source.match(/^\s*/um, true)
|
100
|
+
@stack << [ :end_doctype ]
|
101
|
+
else
|
102
|
+
@document_status = :in_doctype
|
103
|
+
end
|
104
|
+
return args
|
105
|
+
when /^\s+/
|
106
|
+
else
|
107
|
+
@document_status = :after_doctype
|
108
|
+
@source.read if @source.buffer.size<2
|
109
|
+
md = @source.match(/\s*/um, true)
|
110
|
+
if @source.encoding == "UTF-8"
|
111
|
+
if @source.buffer.respond_to? :force_encoding
|
112
|
+
@source.buffer.force_encoding(Encoding::UTF_8)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
if @document_status == :in_doctype
|
118
|
+
md = @source.match(/\s*(.*?>)/um)
|
119
|
+
case md[1]
|
120
|
+
when SYSTEMENTITY
|
121
|
+
match = @source.match( SYSTEMENTITY, true )[1]
|
122
|
+
return [ :externalentity, match ]
|
123
|
+
|
124
|
+
when ELEMENTDECL_START
|
125
|
+
return [ :elementdecl, @source.match( ELEMENTDECL_PATTERN, true )[1] ]
|
126
|
+
|
127
|
+
when ENTITY_START
|
128
|
+
match = @source.match( ENTITYDECL, true ).to_a.compact
|
129
|
+
match[0] = :entitydecl
|
130
|
+
ref = false
|
131
|
+
if match[1] == '%'
|
132
|
+
ref = true
|
133
|
+
match.delete_at 1
|
134
|
+
end
|
135
|
+
# Now we have to sort out what kind of entity reference this is
|
136
|
+
if match[2] == 'SYSTEM'
|
137
|
+
# External reference
|
138
|
+
match[3] = match[3][1..-2] # PUBID
|
139
|
+
match.delete_at(4) if match.size > 4 # Chop out NDATA decl
|
140
|
+
# match is [ :entity, name, SYSTEM, pubid(, ndata)? ]
|
141
|
+
elsif match[2] == 'PUBLIC'
|
142
|
+
# External reference
|
143
|
+
match[3] = match[3][1..-2] # PUBID
|
144
|
+
match[4] = match[4][1..-2] # HREF
|
145
|
+
# match is [ :entity, name, PUBLIC, pubid, href ]
|
146
|
+
else
|
147
|
+
match[2] = match[2][1..-2]
|
148
|
+
match.pop if match.size == 4
|
149
|
+
# match is [ :entity, name, value ]
|
150
|
+
end
|
151
|
+
match << '%' if ref
|
152
|
+
return match
|
153
|
+
when ATTLISTDECL_START
|
154
|
+
md = @source.match( ATTLISTDECL_PATTERN, true )
|
155
|
+
raise REXML::ParseException.new( "Bad ATTLIST declaration!", @source ) if md.nil?
|
156
|
+
element = md[1]
|
157
|
+
contents = md[0]
|
158
|
+
|
159
|
+
pairs = {}
|
160
|
+
values = md[0].scan( ATTDEF_RE )
|
161
|
+
values.each do |attdef|
|
162
|
+
unless attdef[3] == "#IMPLIED"
|
163
|
+
attdef.compact!
|
164
|
+
val = attdef[3]
|
165
|
+
val = attdef[4] if val == "#FIXED "
|
166
|
+
pairs[attdef[0]] = val
|
167
|
+
if attdef[0] =~ /^xmlns:(.*)/
|
168
|
+
@nsstack[0] << $1
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
return [ :attlistdecl, element, pairs, contents ]
|
173
|
+
when NOTATIONDECL_START
|
174
|
+
md = nil
|
175
|
+
if @source.match( PUBLIC )
|
176
|
+
md = @source.match( PUBLIC, true )
|
177
|
+
vals = [md[1],md[2],md[4],md[6]]
|
178
|
+
elsif @source.match( SYSTEM )
|
179
|
+
md = @source.match( SYSTEM, true )
|
180
|
+
vals = [md[1],md[2],nil,md[4]]
|
181
|
+
else
|
182
|
+
raise REXML::ParseException.new( "error parsing notation: no matching pattern", @source )
|
183
|
+
end
|
184
|
+
return [ :notationdecl, *vals ]
|
185
|
+
when CDATA_END
|
186
|
+
@document_status = :after_doctype
|
187
|
+
@source.match( CDATA_END, true )
|
188
|
+
return [ :end_doctype ]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
begin
|
192
|
+
if @source.buffer[0] == ?<
|
193
|
+
if @source.buffer[1] == ?/
|
194
|
+
@nsstack.shift
|
195
|
+
last_tag = @tags.pop
|
196
|
+
#md = @source.match_to_consume( '>', CLOSE_MATCH)
|
197
|
+
md = @source.match( CLOSE_MATCH, true )
|
198
|
+
raise REXML::ParseException.new( "Missing end tag for "+
|
199
|
+
"'#{last_tag}' (got \"#{md[1]}\")",
|
200
|
+
@source) unless last_tag == md[1]
|
201
|
+
return [ :end_element, last_tag ]
|
202
|
+
elsif @source.buffer[1] == ?!
|
203
|
+
md = @source.match(/\A(\s*[^>]*>)/um)
|
204
|
+
#STDERR.puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}"
|
205
|
+
raise REXML::ParseException.new("Malformed node", @source) unless md
|
206
|
+
if md[0][2] == ?-
|
207
|
+
md = @source.match( COMMENT_PATTERN, true )
|
208
|
+
|
209
|
+
case md[1]
|
210
|
+
when /--/, /-$/
|
211
|
+
raise REXML::ParseException.new("Malformed comment", @source)
|
212
|
+
end
|
213
|
+
|
214
|
+
return [ :comment, md[1] ] if md
|
215
|
+
else
|
216
|
+
md = @source.match( CDATA_PATTERN, true )
|
217
|
+
return [ :cdata, md[1] ] if md
|
218
|
+
end
|
219
|
+
raise REXML::ParseException.new( "Declarations can only occur "+
|
220
|
+
"in the doctype declaration.", @source)
|
221
|
+
elsif @source.buffer[1] == ??
|
222
|
+
md = @source.match( INSTRUCTION_PATTERN, true )
|
223
|
+
return [ :processing_instruction, md[1], md[2] ] if md
|
224
|
+
raise REXML::ParseException.new( "Bad instruction declaration",
|
225
|
+
@source)
|
226
|
+
else
|
227
|
+
# Get the next tag
|
228
|
+
md = @source.match(TAG_MATCH, true)
|
229
|
+
unless md
|
230
|
+
# Check for missing attribute quotes
|
231
|
+
raise REXML::ParseException.new("missing attribute quote", @source) if @source.match(MISSING_ATTRIBUTE_QUOTES )
|
232
|
+
raise REXML::ParseException.new("malformed XML: missing tag start", @source)
|
233
|
+
end
|
234
|
+
attributes = {}
|
235
|
+
prefixes = Set.new
|
236
|
+
prefixes << md[2] if md[2]
|
237
|
+
@nsstack.unshift(curr_ns=Set.new)
|
238
|
+
if md[4].size > 0
|
239
|
+
attrs = md[4].scan( ATTRIBUTE_PATTERN )
|
240
|
+
raise REXML::ParseException.new( "error parsing attributes: [#{attrs.join ', '}], excess = \"#$'\"", @source) if $' and $'.strip.size > 0
|
241
|
+
attrs.each { |a,b,c,d,e|
|
242
|
+
if b == "xmlns"
|
243
|
+
if c == "xml"
|
244
|
+
if d != "http://www.w3.org/XML/1998/namespace"
|
245
|
+
msg = "The 'xml' prefix must not be bound to any other namespace "+
|
246
|
+
"(http://www.w3.org/TR/REC-xml-names/#ns-decl)"
|
247
|
+
raise REXML::ParseException.new( msg, @source, self )
|
248
|
+
end
|
249
|
+
elsif c == "xmlns"
|
250
|
+
msg = "The 'xmlns' prefix must not be declared "+
|
251
|
+
"(http://www.w3.org/TR/REC-xml-names/#ns-decl)"
|
252
|
+
raise REXML::ParseException.new( msg, @source, self)
|
253
|
+
end
|
254
|
+
curr_ns << c
|
255
|
+
elsif b
|
256
|
+
prefixes << b unless b == "xml"
|
257
|
+
end
|
258
|
+
|
259
|
+
if attributes.has_key? a
|
260
|
+
msg = "Duplicate attribute #{a.inspect}"
|
261
|
+
raise REXML::ParseException.new( msg, @source, self)
|
262
|
+
end
|
263
|
+
|
264
|
+
attributes[a] = e
|
265
|
+
}
|
266
|
+
end
|
267
|
+
|
268
|
+
# Verify that all of the prefixes have been defined
|
269
|
+
for prefix in prefixes
|
270
|
+
unless @nsstack.find{|k| k.member?(prefix)}
|
271
|
+
raise UndefinedNamespaceException.new(prefix,@source,self)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
if md[6]
|
276
|
+
@closed = md[1]
|
277
|
+
@nsstack.shift
|
278
|
+
else
|
279
|
+
@tags.push( md[1] )
|
280
|
+
end
|
281
|
+
return [ :start_element, md[1], attributes ]
|
282
|
+
end
|
283
|
+
else
|
284
|
+
md = @source.match( TEXT_PATTERN, true )
|
285
|
+
if md[0].length == 0
|
286
|
+
@source.match( /(\s+)/, true )
|
287
|
+
end
|
288
|
+
#STDERR.puts "GOT #{md[1].inspect}" unless md[0].length == 0
|
289
|
+
#return [ :text, "" ] if md[0].length == 0
|
290
|
+
# unnormalized = Text::unnormalize( md[1], self )
|
291
|
+
# return PullEvent.new( :text, md[1], unnormalized )
|
292
|
+
return [ :text, md[1] ]
|
293
|
+
end
|
294
|
+
rescue REXML::UndefinedNamespaceException
|
295
|
+
raise
|
296
|
+
rescue REXML::ParseException
|
297
|
+
raise
|
298
|
+
rescue Exception, NameError => error
|
299
|
+
raise REXML::ParseException.new( "Exception parsing",
|
300
|
+
@source, self, (error ? error : $!) )
|
301
|
+
end
|
302
|
+
return [ :dummy ]
|
303
|
+
end
|
304
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: cuttlebone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.1.
|
5
|
+
version: 0.1.5
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Bence Golda
|
@@ -10,12 +10,45 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-03-
|
13
|
+
date: 2011-03-29 00:00:00 +02:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
17
|
+
name: rack
|
18
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.2
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.5.0
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: xmpp4r
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0.5"
|
46
|
+
type: :runtime
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: bundler
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
19
52
|
none: false
|
20
53
|
requirements:
|
21
54
|
- - ~>
|
@@ -23,10 +56,10 @@ dependencies:
|
|
23
56
|
version: 1.0.0
|
24
57
|
type: :development
|
25
58
|
prerelease: false
|
26
|
-
version_requirements: *
|
59
|
+
version_requirements: *id004
|
27
60
|
- !ruby/object:Gem::Dependency
|
28
61
|
name: rspec
|
29
|
-
requirement: &
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
30
63
|
none: false
|
31
64
|
requirements:
|
32
65
|
- - ~>
|
@@ -34,10 +67,10 @@ dependencies:
|
|
34
67
|
version: 2.5.0
|
35
68
|
type: :development
|
36
69
|
prerelease: false
|
37
|
-
version_requirements: *
|
70
|
+
version_requirements: *id005
|
38
71
|
- !ruby/object:Gem::Dependency
|
39
72
|
name: i18n
|
40
|
-
requirement: &
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
41
74
|
none: false
|
42
75
|
requirements:
|
43
76
|
- - ">="
|
@@ -45,10 +78,10 @@ dependencies:
|
|
45
78
|
version: "0"
|
46
79
|
type: :development
|
47
80
|
prerelease: false
|
48
|
-
version_requirements: *
|
81
|
+
version_requirements: *id006
|
49
82
|
- !ruby/object:Gem::Dependency
|
50
83
|
name: cucumber
|
51
|
-
requirement: &
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
52
85
|
none: false
|
53
86
|
requirements:
|
54
87
|
- - ~>
|
@@ -56,10 +89,10 @@ dependencies:
|
|
56
89
|
version: 0.10.0
|
57
90
|
type: :development
|
58
91
|
prerelease: false
|
59
|
-
version_requirements: *
|
92
|
+
version_requirements: *id007
|
60
93
|
- !ruby/object:Gem::Dependency
|
61
94
|
name: rcov
|
62
|
-
requirement: &
|
95
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
63
96
|
none: false
|
64
97
|
requirements:
|
65
98
|
- - ~>
|
@@ -67,10 +100,10 @@ dependencies:
|
|
67
100
|
version: 0.9.0
|
68
101
|
type: :development
|
69
102
|
prerelease: false
|
70
|
-
version_requirements: *
|
103
|
+
version_requirements: *id008
|
71
104
|
- !ruby/object:Gem::Dependency
|
72
105
|
name: capybara
|
73
|
-
requirement: &
|
106
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
74
107
|
none: false
|
75
108
|
requirements:
|
76
109
|
- - ~>
|
@@ -78,10 +111,10 @@ dependencies:
|
|
78
111
|
version: 0.4.0
|
79
112
|
type: :development
|
80
113
|
prerelease: false
|
81
|
-
version_requirements: *
|
114
|
+
version_requirements: *id009
|
82
115
|
- !ruby/object:Gem::Dependency
|
83
116
|
name: haml
|
84
|
-
requirement: &
|
117
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
85
118
|
none: false
|
86
119
|
requirements:
|
87
120
|
- - ~>
|
@@ -89,7 +122,7 @@ dependencies:
|
|
89
122
|
version: 3.0.0
|
90
123
|
type: :development
|
91
124
|
prerelease: false
|
92
|
-
version_requirements: *
|
125
|
+
version_requirements: *id010
|
93
126
|
description: Cuttlebone helps you creating shell-alike applications.
|
94
127
|
email:
|
95
128
|
- bence@golda.me
|
@@ -120,6 +153,7 @@ files:
|
|
120
153
|
- lib/cuttlebone/drivers/base.rb
|
121
154
|
- lib/cuttlebone/drivers/rack.rb
|
122
155
|
- lib/cuttlebone/drivers/shell.rb
|
156
|
+
- lib/cuttlebone/drivers/xmpp.rb
|
123
157
|
- lib/cuttlebone/exceptions.rb
|
124
158
|
- lib/cuttlebone/session.rb
|
125
159
|
- lib/cuttlebone/version.rb
|
@@ -145,6 +179,7 @@ files:
|
|
145
179
|
- spec/spec.opts
|
146
180
|
- spec/spec_helper.rb
|
147
181
|
- vendor/active_support.rb
|
182
|
+
- vendor/rexml_utf8_fix.rb
|
148
183
|
has_rdoc: true
|
149
184
|
homepage: http://github.com/gbence/cuttlebone
|
150
185
|
licenses: []
|
@@ -159,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
159
194
|
requirements:
|
160
195
|
- - ">="
|
161
196
|
- !ruby/object:Gem::Version
|
162
|
-
hash:
|
197
|
+
hash: 500076729674871657
|
163
198
|
segments:
|
164
199
|
- 0
|
165
200
|
version: "0"
|
@@ -172,9 +207,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
207
|
requirements: []
|
173
208
|
|
174
209
|
rubyforge_project: cuttlebone
|
175
|
-
rubygems_version: 1.
|
210
|
+
rubygems_version: 1.6.2
|
176
211
|
signing_key:
|
177
212
|
specification_version: 3
|
178
|
-
summary: cuttlebone-0.1.
|
213
|
+
summary: cuttlebone-0.1.5
|
179
214
|
test_files: []
|
180
215
|
|