stomp_parser 1.0.0-universal-java
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.
- 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
|
+
[](https://travis-ci.org/stompede/stomp_parser)
|
4
|
+
[](https://codeclimate.com/github/stompede/stomp_parser)
|
5
|
+
[](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
|
+
}
|