stomp_parser 1.0.0-universal-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +33 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Brewfile +2 -0
- data/Gemfile +11 -0
- data/MIT-LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +134 -0
- data/ext/java/stomp_parser/JavaParser.java.rl +179 -0
- data/ext/java/stomp_parser/JavaParserService.java +23 -0
- data/ext/stomp_parser/c_parser.c.rl +225 -0
- data/ext/stomp_parser/extconf.rb +15 -0
- data/lib/stomp_parser.rb +46 -0
- data/lib/stomp_parser/error.rb +18 -0
- data/lib/stomp_parser/frame.rb +133 -0
- data/lib/stomp_parser/ruby_parser.rb.rl +155 -0
- data/lib/stomp_parser/version.rb +3 -0
- data/parser_common.rl +25 -0
- data/spec/bench_helper.rb +67 -0
- data/spec/benchmarks/message_bench.rb +50 -0
- data/spec/benchmarks/parser_bench.rb +43 -0
- data/spec/profile.rb +27 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/stomp_parser/c_parser_spec.rb +5 -0
- data/spec/stomp_parser/java_parser_spec.rb +5 -0
- data/spec/stomp_parser/message_spec.rb +50 -0
- data/spec/stomp_parser/ruby_parser_spec.rb +3 -0
- data/spec/stomp_parser_spec.rb +9 -0
- data/spec/support/shared_parser_examples.rb +268 -0
- data/stomp_parser.gemspec +28 -0
- metadata +162 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc2d9bd4c0e92423027543f02348d68441534536
|
4
|
+
data.tar.gz: a16e1abf7f2eda5f48932fe148e72e7e14d3f88e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 67fb331703eb2b68e2aa70fc38f50883a09f0016c20210ff8aa5f7a9242a76162493f5535d371eb192563693215387ca3590ff7ebd6baa324e05242e6cbf223a
|
7
|
+
data.tar.gz: 59585100cab11f493bd3063905c543928e8d2abe4eb80217bd133771cfc257b0ff5c151769d7ef0718e4c135bab993e1cb02a14741d7ee166785b425f225cede
|
data/.gitignore
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
spec/profile
|
19
|
+
|
20
|
+
# C extension
|
21
|
+
*.o
|
22
|
+
*.bundle
|
23
|
+
mkmf.log
|
24
|
+
Makefile
|
25
|
+
tmp/
|
26
|
+
spec/spec.log
|
27
|
+
|
28
|
+
# Ragel
|
29
|
+
lib/stomp_parser/ruby_parser.rb
|
30
|
+
lib/stomp_parser/c_parser.rb
|
31
|
+
ext/stomp_parser/c_parser.c
|
32
|
+
ext/java/stomp_parser/JavaParser.java
|
33
|
+
lib/stomp_parser/java_parser.jar
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-r spec_helper
|
data/.travis.yml
ADDED
data/Brewfile
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kim Burgestrand, Jonas Nicklas
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# StompParser
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/stompede/stomp_parser.png?branch=master)](https://travis-ci.org/stompede/stomp_parser)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/stompede/stomp_parser.png)](https://codeclimate.com/github/stompede/stomp_parser)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/stomp_parser.png)](http://badge.fury.io/rb/stomp_parser)
|
6
|
+
|
7
|
+
Fast STOMP parser and serializer for Ruby with native extensions in C for MRI
|
8
|
+
and Rubinius and in Java for JRuby, as well as a pure Ruby parser.
|
9
|
+
|
10
|
+
## Parsing
|
11
|
+
|
12
|
+
``` ruby
|
13
|
+
parser = StompParser::Parser.new
|
14
|
+
parser.parse(chunk) do |frame|
|
15
|
+
puts "We received #{frame.command} frame with #{frame.body} and headers #{frame.headers}!"
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
The chunks do not have to be complete STOMP frames. The callback will be called
|
20
|
+
whenever a complete frame has been parsed. Additionally, StompParser handles
|
21
|
+
escape sequences in headers and body encoding for you.
|
22
|
+
|
23
|
+
## Serializing
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
StompParser::Frame.new("SEND", { some: "header" }, "Hello").to_str
|
27
|
+
```
|
28
|
+
|
29
|
+
## Development
|
30
|
+
|
31
|
+
Development should be ez.
|
32
|
+
|
33
|
+
``` bash
|
34
|
+
git clone git@github.com:stompede/stomp_parser.git # git, http://git-scm.com/
|
35
|
+
cd stomp_parser
|
36
|
+
brew bundle # Homebrew, http://brew.sh/
|
37
|
+
bundle install # Bundler, http://bundler.io/
|
38
|
+
rake # compile state machine, run test suite
|
39
|
+
```
|
40
|
+
|
41
|
+
A few notes:
|
42
|
+
|
43
|
+
- Native dependencies are listed in the Brewfile.
|
44
|
+
- The stomp message parser is written in [Ragel](http://www.complang.org/ragel/), see [parser_common.rl](parser_common.rl).
|
45
|
+
- Graphviz is used to visualize the Ragel state machine.
|
46
|
+
- Most rake tasks will compile the state machine anew if needed.
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
1. Fork it on GitHub (<http://github.com/stompede/stomp_parser/fork>).
|
51
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
52
|
+
3. Follow the [Development](#development) instructions in this README.
|
53
|
+
4. Create your changes, please add tests.
|
54
|
+
5. Commit your changes (`git commit -am 'Add some feature'`).
|
55
|
+
6. Push to the branch (`git push origin my-new-feature`).
|
56
|
+
7. Create new pull request on GitHub.
|
57
|
+
|
58
|
+
## License
|
59
|
+
|
60
|
+
[MIT](MIT-LICENSE.txt)
|
data/Rakefile
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
def ragel(*args)
|
2
|
+
sh "ragel", "-I.", *args
|
3
|
+
end
|
4
|
+
|
5
|
+
# Build state machine before building gem.
|
6
|
+
task :build => :compile
|
7
|
+
|
8
|
+
file "parser_common.rl"
|
9
|
+
|
10
|
+
rule ".rb" => %w[.rb.rl parser_common.rl] do |t|
|
11
|
+
ragel "-F1", "-R", t.source, "-o", t.name
|
12
|
+
end
|
13
|
+
|
14
|
+
rule ".c" => %w[.c.rl parser_common.rl] do |t|
|
15
|
+
ragel "-G2", "-C", t.source, "-o", t.name
|
16
|
+
end
|
17
|
+
|
18
|
+
rule ".java" => %w[.java.rl parser_common.rl] do |t|
|
19
|
+
ragel "-T0", "-J", t.source, "-o", t.name
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "ragel machines"
|
23
|
+
task :compile => %w[lib/stomp_parser/ruby_parser.rb]
|
24
|
+
|
25
|
+
# JRuby do not like C extensions to even exist.
|
26
|
+
if %w[rbx ruby].include?(RUBY_ENGINE)
|
27
|
+
require "rake/extensiontask"
|
28
|
+
task :compile => %w[ext/stomp_parser/c_parser.c]
|
29
|
+
|
30
|
+
Rake::ExtensionTask.new do |ext|
|
31
|
+
ext.name = "c_parser"
|
32
|
+
ext.ext_dir = "ext/stomp_parser"
|
33
|
+
ext.lib_dir = "lib/stomp_parser"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
require "rake/javaextensiontask"
|
38
|
+
task :compile => %w[ext/java/stomp_parser/JavaParser.java]
|
39
|
+
|
40
|
+
Rake::JavaExtensionTask.new do |ext|
|
41
|
+
ext.name = "java_parser"
|
42
|
+
ext.lib_dir = "lib/stomp_parser"
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "ragel machines"
|
46
|
+
task :clean do |t|
|
47
|
+
source_tasks = Rake::Task[:compile].prerequisite_tasks.grep(Rake::FileTask)
|
48
|
+
rm_f source_tasks.map(&:name)
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :ragel do
|
52
|
+
desc "Show stomp parser state machine as an image"
|
53
|
+
task :show => "lib/stomp_parser/ruby_parser.rb" do |t|
|
54
|
+
mkdir_p "tmp"
|
55
|
+
ragel "-V", "-p", t.prerequisite_tasks[0].source, "-o", "tmp/parser.dot"
|
56
|
+
sh "dot -Tpng -O tmp/parser.dot"
|
57
|
+
rm "tmp/parser.dot"
|
58
|
+
sh "open tmp/parser.dot.png"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "Start a pry session with the gem loaded."
|
63
|
+
task :console => :compile do
|
64
|
+
exec "pry", "-rbundler/setup", "-rstomp_parser"
|
65
|
+
end
|
66
|
+
|
67
|
+
require "rspec/core/rake_task"
|
68
|
+
RSpec::Core::RakeTask.new(:spec)
|
69
|
+
task :spec => :compile
|
70
|
+
|
71
|
+
desc "Run all benchmarks."
|
72
|
+
task :bench => :compile do
|
73
|
+
sh "ruby", "-I.", *FileList["spec/benchmarks/**/*.rb"].flat_map { |x| ["-r", x] }, "-e", "''"
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "Run the profiler and show a gif, requires perftools.rb"
|
77
|
+
task :profile => :compile do
|
78
|
+
# CPUPROFILE_METHODS=0 CPUPROFILE_OBJECTS=0 CPUPROFILE_REALTIME=1
|
79
|
+
sh "CPUPROFILE_REALTIME=1 ruby spec/profile.rb"
|
80
|
+
sh "pprof.rb --text spec/profile/parser.profile"
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Build gem in preparation for release"
|
84
|
+
task :build, [:everything_compiled?] => :compile do |task, args|
|
85
|
+
unless args[:everything_compiled?] == "all_compiled"
|
86
|
+
puts "Hi there. This is a safeguard to stop you from doing mistakes."
|
87
|
+
puts
|
88
|
+
puts "Make sure that the lib/stomp_parser/java_parser.jar is up to"
|
89
|
+
puts "date by switching to JRuby and running `rake compile`. This is"
|
90
|
+
puts "because JRuby cannot build native extensions on gem installation,"
|
91
|
+
puts "so we must bundle the .jar with the gem."
|
92
|
+
puts
|
93
|
+
puts "To do this, switch to JRuby and issue `rake compile`."
|
94
|
+
puts
|
95
|
+
puts "Once you've compiled the parser, run `rake build[all_compiled]`"
|
96
|
+
puts "to build gems for all platforms!"
|
97
|
+
abort
|
98
|
+
end
|
99
|
+
|
100
|
+
require "rubygems/package"
|
101
|
+
|
102
|
+
root = File.dirname(__FILE__)
|
103
|
+
pkgs = File.join(root, "pkg/")
|
104
|
+
mkdir_p(pkgs, verbose: true)
|
105
|
+
|
106
|
+
gemspec = Gem::Specification.load("stomp_parser.gemspec")
|
107
|
+
|
108
|
+
# For all the rubies. Except JRuby.
|
109
|
+
ruby_gemspec = gemspec.dup
|
110
|
+
ruby_gemspec.platform = Gem::Platform::RUBY
|
111
|
+
ruby_gemname = Gem::Package.build(ruby_gemspec)
|
112
|
+
ruby_gempath = File.join(pkgs, File.basename(ruby_gemname))
|
113
|
+
mv(ruby_gemname, ruby_gempath, verbose: true)
|
114
|
+
|
115
|
+
# JRuby don't like C extensions.
|
116
|
+
java_gemspec = gemspec.dup
|
117
|
+
java_gemspec.files += ["lib/stomp_parser/java_parser.jar"]
|
118
|
+
java_gemspec.extensions = []
|
119
|
+
java_gemspec.platform = "universal-java"
|
120
|
+
java_gemname = Gem::Package.build(java_gemspec)
|
121
|
+
java_gempath = File.join(pkgs, File.basename(java_gemname))
|
122
|
+
mv(java_gemname, java_gempath, verbose: true)
|
123
|
+
|
124
|
+
puts
|
125
|
+
puts "Now you may push the gems:"
|
126
|
+
puts
|
127
|
+
puts " gem push #{ruby_gempath}"
|
128
|
+
puts " gem push #{java_gempath}"
|
129
|
+
puts
|
130
|
+
puts "Do not forget to tag and push the versions to GitHub!"
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
task :default => :spec
|
@@ -0,0 +1,179 @@
|
|
1
|
+
package stomp_parser;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyModule;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyObject;
|
7
|
+
import org.jruby.RubyFixnum;
|
8
|
+
import org.jruby.RubyString;
|
9
|
+
import org.jruby.RubyNumeric;
|
10
|
+
import org.jruby.RubyException;
|
11
|
+
import org.jruby.exceptions.RaiseException;
|
12
|
+
|
13
|
+
import org.jruby.runtime.ThreadContext;
|
14
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
15
|
+
import org.jruby.runtime.Block;
|
16
|
+
|
17
|
+
import org.jruby.anno.JRubyClass;
|
18
|
+
import org.jruby.anno.JRubyMethod;
|
19
|
+
|
20
|
+
%%{
|
21
|
+
machine frame;
|
22
|
+
alphtype byte;
|
23
|
+
|
24
|
+
action mark {
|
25
|
+
mark = p;
|
26
|
+
}
|
27
|
+
|
28
|
+
action mark_frame {
|
29
|
+
mark_frame = context.runtime.getClassFromPath("StompParser::Frame").callMethod("new", context.nil, context.nil);
|
30
|
+
mark_frame_size = 0;
|
31
|
+
}
|
32
|
+
|
33
|
+
action write_command {
|
34
|
+
mark_frame.callMethod(context, "write_command", RubyString.newString(context.runtime, data, mark, p - mark));
|
35
|
+
mark = -1;
|
36
|
+
}
|
37
|
+
|
38
|
+
action mark_key {
|
39
|
+
mark_key = RubyString.newString(context.runtime, data, mark, p - mark);
|
40
|
+
mark = -1;
|
41
|
+
}
|
42
|
+
|
43
|
+
action write_header {
|
44
|
+
IRubyObject args[] = { mark_key, RubyString.newString(context.runtime, data, mark, p - mark) };
|
45
|
+
mark_frame.callMethod(context, "write_header", args);
|
46
|
+
mark_key = null;
|
47
|
+
mark = -1;
|
48
|
+
}
|
49
|
+
|
50
|
+
action finish_headers {
|
51
|
+
IRubyObject content_length = mark_frame.callMethod(context, "content_length");
|
52
|
+
|
53
|
+
if ( ! content_length.isNil()) {
|
54
|
+
mark_content_length = RubyNumeric.num2int(content_length);
|
55
|
+
} else {
|
56
|
+
mark_content_length = -1;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
action write_body {
|
61
|
+
mark_frame.callMethod(context, "write_body", RubyString.newString(context.runtime, data, mark, p - mark));
|
62
|
+
mark = -1;
|
63
|
+
}
|
64
|
+
|
65
|
+
action consume_null {
|
66
|
+
((mark_content_length != -1) && ((p - mark) < mark_content_length))
|
67
|
+
}
|
68
|
+
|
69
|
+
action consume_octet {
|
70
|
+
((mark_content_length == -1) || ((p - mark) < mark_content_length))
|
71
|
+
}
|
72
|
+
|
73
|
+
action check_frame_size {
|
74
|
+
mark_frame_size += 1;
|
75
|
+
if (mark_frame_size > maxFrameSize) {
|
76
|
+
RubyModule frameSizeExceeded = context.runtime.getClassFromPath("StompParser::FrameSizeExceeded");
|
77
|
+
RubyException error = (RubyException) frameSizeExceeded.callMethod("new");
|
78
|
+
throw new RaiseException(error);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
action finish_frame {
|
83
|
+
block.yield(context, mark_frame);
|
84
|
+
mark_frame = null;
|
85
|
+
}
|
86
|
+
|
87
|
+
include frame_common "parser_common.rl";
|
88
|
+
}%%
|
89
|
+
|
90
|
+
@JRubyClass(name="JavaParser", parent="Object")
|
91
|
+
public class JavaParser extends RubyObject {
|
92
|
+
%% write data noprefix;
|
93
|
+
|
94
|
+
private class State {
|
95
|
+
public int cs = JavaParser.start;
|
96
|
+
public byte[] chunk;
|
97
|
+
public int mark = -1;
|
98
|
+
public RubyString mark_key;
|
99
|
+
public IRubyObject mark_frame;
|
100
|
+
public int mark_frame_size = -1;
|
101
|
+
public int mark_content_length = -1;
|
102
|
+
}
|
103
|
+
|
104
|
+
private RubyException parseError;
|
105
|
+
private long maxFrameSize;
|
106
|
+
private State state;
|
107
|
+
|
108
|
+
public JavaParser(Ruby runtime, RubyClass klass) {
|
109
|
+
super(runtime, klass);
|
110
|
+
state = new State();
|
111
|
+
parseError = null;
|
112
|
+
}
|
113
|
+
|
114
|
+
@JRubyMethod
|
115
|
+
public IRubyObject initialize(ThreadContext context) {
|
116
|
+
RubyModule mStompParser = context.runtime.getClassFromPath("StompParser");
|
117
|
+
return initialize(context, mStompParser.callMethod("max_frame_size"));
|
118
|
+
}
|
119
|
+
|
120
|
+
@JRubyMethod(argTypes = {RubyFixnum.class})
|
121
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject maxFrameSize) {
|
122
|
+
this.maxFrameSize = ((RubyFixnum) maxFrameSize).getLongValue();
|
123
|
+
return context.nil;
|
124
|
+
}
|
125
|
+
|
126
|
+
@JRubyMethod(argTypes = {RubyString.class})
|
127
|
+
public IRubyObject parse(ThreadContext context, IRubyObject chunk, Block block) {
|
128
|
+
if (parseError == null) {
|
129
|
+
int p;
|
130
|
+
byte data[] = null;
|
131
|
+
byte bytes[] = ((RubyString) chunk).getBytes();
|
132
|
+
|
133
|
+
if (state.chunk != null) {
|
134
|
+
p = state.chunk.length;
|
135
|
+
data = new byte[state.chunk.length + bytes.length];
|
136
|
+
System.arraycopy(state.chunk, 0, data, 0, state.chunk.length);
|
137
|
+
System.arraycopy(bytes, 0, data, state.chunk.length, bytes.length);
|
138
|
+
} else {
|
139
|
+
p = 0;
|
140
|
+
data = bytes;
|
141
|
+
}
|
142
|
+
|
143
|
+
int pe = data.length;
|
144
|
+
|
145
|
+
int cs = state.cs;
|
146
|
+
int mark = state.mark;
|
147
|
+
RubyString mark_key = state.mark_key;
|
148
|
+
IRubyObject mark_frame = state.mark_frame;
|
149
|
+
int mark_frame_size = state.mark_frame_size;
|
150
|
+
int mark_content_length = state.mark_content_length;
|
151
|
+
|
152
|
+
%% write exec;
|
153
|
+
|
154
|
+
if (mark != -1) {
|
155
|
+
state.chunk = data;
|
156
|
+
} else {
|
157
|
+
state.chunk = null;
|
158
|
+
}
|
159
|
+
|
160
|
+
state.cs = cs;
|
161
|
+
state.mark = mark;
|
162
|
+
state.mark_key = mark_key;
|
163
|
+
state.mark_frame = mark_frame;
|
164
|
+
state.mark_frame_size = mark_frame_size;
|
165
|
+
state.mark_content_length = mark_content_length;
|
166
|
+
|
167
|
+
if (cs == error) {
|
168
|
+
IRubyObject args[] = { RubyString.newString(context.runtime, data), RubyFixnum.newFixnum(context.runtime, (long) p) };
|
169
|
+
parseError = (RubyException) context.runtime.getClassFromPath("StompParser").callMethod(context, "build_parse_error", args);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
if (parseError != null) {
|
174
|
+
throw new RaiseException(parseError);
|
175
|
+
}
|
176
|
+
|
177
|
+
return context.nil;
|
178
|
+
}
|
179
|
+
}
|