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.
@@ -1,4 +1,13 @@
1
- ## 0.1.2 / 2011-03-18
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
- ).render()
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 the a4-core plugin.'
75
+ desc 'Generate documentation for cuttlebone.'
76
76
  Rake::RDocTask.new(:rdoc) do |rdoc|
77
77
  rdoc.rdoc_dir = 'rdoc'
78
- rdoc.title = 'A4::Core'
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')
@@ -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"
@@ -12,6 +12,7 @@ module Cuttlebone
12
12
  autoload :Base, 'cuttlebone/drivers/base'
13
13
  autoload :Shell, 'cuttlebone/drivers/shell'
14
14
  autoload :Rack, 'cuttlebone/drivers/rack'
15
+ autoload :XMPP, 'cuttlebone/drivers/xmpp'
15
16
  end
16
17
 
17
18
  @@definitions = []
@@ -50,8 +50,8 @@ class Cuttlebone::Controller
50
50
  __save_next_action! :replace, context
51
51
  end
52
52
 
53
- def output text
54
- @output << text
53
+ def output *texts
54
+ @output += texts.map(&:to_s)
55
55
  end
56
56
 
57
57
  def method_missing method_name, *args, &block
@@ -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
- @request = Rack::Request.new(env)
21
+ request = Rack::Request.new(env)
24
22
 
25
- return(@app.call(env)) unless @request.post?
26
- return(@app.call(env)) unless @request.path =~ %r{^/(?:(?:prompt|call)/([0-9a-f]{40}))|init$}
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
- @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!'
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
- return(@response.finish)
53
- rescue
54
- json 'id' => (session.id rescue nil), 'prompt' => (session.prompt rescue nil), 'error' => $!.message
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 json data
61
- @response.write(data.to_json)
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
- @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
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.', 404
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
- @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)
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
@@ -1,3 +1,3 @@
1
1
  module Cuttlebone
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  end
@@ -2,14 +2,10 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Cuttlebone - Error</title>
5
- <link href='/stylesheets/cuttlebone.css?1301248065' rel='stylesheet' type='text/css'>
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>
@@ -2,19 +2,16 @@
2
2
  <html>
3
3
  <head>
4
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>
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>&gt;</span>
17
- <input id='input' name='command' type='text'>
14
+ <div class='input'><span id='prompt'></span><span>&gt;</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 cuttlebone_session_id;
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
- // 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']);};}});
33
+ cuttlebone.init();
34
+ jQuery('#input').focus();
14
35
  });
15
36
 
16
37
  // evaluates input
17
- jQuery("#input").live("keypress", function(e) {
38
+ jQuery('#input').live('keypress', function(e) {
18
39
  if (e.keyCode == 13) {
19
40
  jQuery.ajax({
20
- type: "POST",
21
- url: "/call/"+cuttlebone_session_id,
41
+ type: 'POST',
42
+ url: '/call/'+cuttlebone.sessionId,
22
43
  data: {command:jQuery('#input').val()},
23
- dataType: "json",
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
- if (d['prompt']) {
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
- font-family: "sans serif"
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
- - timestamp ||= Time.now.strftime('%s')
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 => 'text/css', :href => "/stylesheets/cuttlebone.css?#{timestamp}", :rel => 'stylesheet' }
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 &gt;
14
+ %span#prompt>
15
+ %span> &gt;
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
- font-family: "sans serif"; }
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.4
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-26 23:00:00 +01:00
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: bundler
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: *id001
59
+ version_requirements: *id004
27
60
  - !ruby/object:Gem::Dependency
28
61
  name: rspec
29
- requirement: &id002 !ruby/object:Gem::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: *id002
70
+ version_requirements: *id005
38
71
  - !ruby/object:Gem::Dependency
39
72
  name: i18n
40
- requirement: &id003 !ruby/object:Gem::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: *id003
81
+ version_requirements: *id006
49
82
  - !ruby/object:Gem::Dependency
50
83
  name: cucumber
51
- requirement: &id004 !ruby/object:Gem::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: *id004
92
+ version_requirements: *id007
60
93
  - !ruby/object:Gem::Dependency
61
94
  name: rcov
62
- requirement: &id005 !ruby/object:Gem::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: *id005
103
+ version_requirements: *id008
71
104
  - !ruby/object:Gem::Dependency
72
105
  name: capybara
73
- requirement: &id006 !ruby/object:Gem::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: *id006
114
+ version_requirements: *id009
82
115
  - !ruby/object:Gem::Dependency
83
116
  name: haml
84
- requirement: &id007 !ruby/object:Gem::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: *id007
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: 836932418591510968
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.5.3
210
+ rubygems_version: 1.6.2
176
211
  signing_key:
177
212
  specification_version: 3
178
- summary: cuttlebone-0.1.4
213
+ summary: cuttlebone-0.1.5
179
214
  test_files: []
180
215