cuttlebone 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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