cuttlebone 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
|