query_string_parser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +88 -0
- data/ext/extconf.rb +9 -0
- data/ext/pr_query_parser.c +129 -0
- data/lib/query_string_parser.rb +13 -0
- data/spec/query_string_parser_spec.rb +166 -0
- metadata +67 -0
data/Rakefile
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
require 'fileutils'
|
8
|
+
include FileUtils
|
9
|
+
|
10
|
+
# Default Rake task is compile
|
11
|
+
task :default => :compile
|
12
|
+
|
13
|
+
def make(makedir)
|
14
|
+
Dir.chdir(makedir) { sh 'make' }
|
15
|
+
end
|
16
|
+
|
17
|
+
def extconf(dir)
|
18
|
+
Dir.chdir(dir) { ruby "extconf.rb" }
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup_extension(dir, extension)
|
22
|
+
ext = "ext/#{dir}"
|
23
|
+
ext_so = "#{ext}/#{extension}.#{Config::CONFIG['DLEXT']}"
|
24
|
+
ext_files = FileList[
|
25
|
+
"#{ext}/*.c",
|
26
|
+
"#{ext}/*.h",
|
27
|
+
"#{ext}/extconf.rb",
|
28
|
+
"#{ext}/Makefile",
|
29
|
+
"lib"
|
30
|
+
]
|
31
|
+
|
32
|
+
task "lib" do
|
33
|
+
directory "lib"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Builds just the #{extension} extension"
|
37
|
+
task extension.to_sym => ["#{ext}/Makefile", ext_so ]
|
38
|
+
|
39
|
+
file "#{ext}/Makefile" => ["#{ext}/extconf.rb"] do
|
40
|
+
extconf "#{ext}"
|
41
|
+
end
|
42
|
+
|
43
|
+
file ext_so => ext_files do
|
44
|
+
make "#{ext}"
|
45
|
+
cp ext_so, "lib"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
setup_extension("", "pr_query_parser")
|
50
|
+
|
51
|
+
desc "Compile the extension"
|
52
|
+
task :compile => [:pr_query_parser]
|
53
|
+
|
54
|
+
Spec::Rake::SpecTask.new('do_spec') do |t|
|
55
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
56
|
+
end
|
57
|
+
desc "run spec tests"
|
58
|
+
task :spec => [:clean, :compile, :do_spec]
|
59
|
+
|
60
|
+
CLEAN.include ['build/*', '**/*.o', '**/*.so', '**/*.bundle', '**/*.a', '**/*.log', 'pkg']
|
61
|
+
CLEAN.include ['ext/Makefile']
|
62
|
+
|
63
|
+
begin
|
64
|
+
require 'jeweler'
|
65
|
+
Jeweler::Tasks.new do |s|
|
66
|
+
s.name = "query_string_parser"
|
67
|
+
s.summary = "Simple query string parser"
|
68
|
+
s.email = "dj2@everburning.com"
|
69
|
+
s.homepage = "http://github.com/dj2/query_string_parser"
|
70
|
+
s.description = s.summary
|
71
|
+
s.authors = ["dan sinclair"]
|
72
|
+
s.files = [
|
73
|
+
"Rakefile",
|
74
|
+
"ext/extconf.rb",
|
75
|
+
"ext/pr_query_parser.c",
|
76
|
+
"lib/query_string_parser.rb",
|
77
|
+
"spec/query_string_parser_spec.rb"
|
78
|
+
]
|
79
|
+
s.extensions << 'ext/extconf.rb'
|
80
|
+
s.require_paths = ['lib']
|
81
|
+
s.test_files = ['spec/query_string_parser_spec.rb']
|
82
|
+
end
|
83
|
+
|
84
|
+
Jeweler::GemcutterTasks.new
|
85
|
+
rescue LoadError
|
86
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
87
|
+
end
|
88
|
+
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <ctype.h>
|
5
|
+
#include <curl/curl.h>
|
6
|
+
|
7
|
+
static void
|
8
|
+
pr_set_param(VALUE params, char *key, char *value)
|
9
|
+
{
|
10
|
+
VALUE rkey, rvalue, cur;
|
11
|
+
char *v;
|
12
|
+
|
13
|
+
/* no key do nothing */
|
14
|
+
if (key == NULL || *key == 0) return;
|
15
|
+
|
16
|
+
/* strip [] from the key name */
|
17
|
+
v = curl_easy_unescape(NULL, key, 0, NULL);
|
18
|
+
if (strlen(v) > 2)
|
19
|
+
{
|
20
|
+
char *t = v;
|
21
|
+
char *open_bracket = NULL;
|
22
|
+
|
23
|
+
while (*(t + 1) != 0)
|
24
|
+
{
|
25
|
+
if (*t == '[') open_bracket = t;
|
26
|
+
t++;
|
27
|
+
}
|
28
|
+
|
29
|
+
if ((*t == ']') && (open_bracket != NULL))
|
30
|
+
{
|
31
|
+
char *pos = (open_bracket + 1);
|
32
|
+
int is_digit = 1;
|
33
|
+
|
34
|
+
while (pos != t)
|
35
|
+
{
|
36
|
+
if (!isdigit(*pos))
|
37
|
+
{
|
38
|
+
is_digit = 0;
|
39
|
+
break;
|
40
|
+
}
|
41
|
+
pos ++;
|
42
|
+
}
|
43
|
+
|
44
|
+
if ((*t == ']') && (*(t - 1) == '['))
|
45
|
+
{
|
46
|
+
*(t - 1) = 0;
|
47
|
+
}
|
48
|
+
else if ((*t == ']') && (*open_bracket == '[') && is_digit)
|
49
|
+
{
|
50
|
+
*open_bracket = 0;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
/* key was only [], do nothing */
|
56
|
+
if (v == NULL || *v == 0) return;
|
57
|
+
|
58
|
+
rkey = rb_str_new2(v);
|
59
|
+
free(v);
|
60
|
+
|
61
|
+
v = curl_easy_unescape(NULL, value, 0, NULL);
|
62
|
+
rvalue = rb_str_new2(v);
|
63
|
+
free(v);
|
64
|
+
|
65
|
+
cur = rb_hash_aref(params, rkey);
|
66
|
+
if (NIL_P(cur))
|
67
|
+
{
|
68
|
+
rb_hash_aset(params, rkey, rvalue);
|
69
|
+
}
|
70
|
+
else
|
71
|
+
{
|
72
|
+
VALUE arry;
|
73
|
+
if (rb_obj_is_kind_of(cur, rb_cArray) == Qtrue)
|
74
|
+
{
|
75
|
+
arry = cur;
|
76
|
+
}
|
77
|
+
else
|
78
|
+
{
|
79
|
+
arry = rb_ary_new();
|
80
|
+
rb_ary_push(arry, cur);
|
81
|
+
rb_hash_aset(params, rkey, arry);
|
82
|
+
}
|
83
|
+
rb_ary_push(arry, rvalue);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
VALUE
|
88
|
+
pr_query_parser(VALUE self, VALUE query_string, VALUE delim)
|
89
|
+
{
|
90
|
+
VALUE params = Qnil;
|
91
|
+
char *qs, *p, *s, *key = NULL;
|
92
|
+
char delimiter = RSTRING_PTR(delim)[0];
|
93
|
+
|
94
|
+
params = rb_hash_new();
|
95
|
+
qs = strdup(RSTRING_PTR(query_string));
|
96
|
+
for (s = qs, p = qs, key = qs; *p != 0; p++)
|
97
|
+
{
|
98
|
+
if (*p == delimiter)
|
99
|
+
{
|
100
|
+
if (key != NULL)
|
101
|
+
{
|
102
|
+
*p = 0;
|
103
|
+
pr_set_param(params, key, s);
|
104
|
+
}
|
105
|
+
s = (p + 1);
|
106
|
+
key = s;
|
107
|
+
}
|
108
|
+
else if (*p == '=')
|
109
|
+
{
|
110
|
+
*p = 0;
|
111
|
+
key = s;
|
112
|
+
s = (p + 1);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
if (key != NULL) pr_set_param(params, key, s);
|
117
|
+
free(qs);
|
118
|
+
|
119
|
+
return params;
|
120
|
+
self = self;
|
121
|
+
}
|
122
|
+
|
123
|
+
void
|
124
|
+
Init_pr_query_parser(void)
|
125
|
+
{
|
126
|
+
VALUE QueryStringParser = rb_define_module("QueryStringParser");
|
127
|
+
|
128
|
+
rb_define_module_function(QueryStringParser, "pr_query_parser", pr_query_parser, 2);
|
129
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'iconv'
|
2
|
+
require 'pr_query_parser'
|
3
|
+
|
4
|
+
module QueryStringParser
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def qs_parse(query_string, delim = '&')
|
8
|
+
return {} if query_string.nil? || query_string == ""
|
9
|
+
|
10
|
+
ret = Iconv.iconv("UTF-8//IGNORE", "ISO-8859-1", (query_string + "\x20")).first[0..-2]
|
11
|
+
pr_query_parser(ret, delim)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
|
3
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
|
5
|
+
require 'query_string_parser'
|
6
|
+
|
7
|
+
describe 'QueryStringParser' do
|
8
|
+
include QueryStringParser
|
9
|
+
|
10
|
+
it 'should parse a well formed query string' do
|
11
|
+
h = qs_parse('a=b&c=d&e=f')
|
12
|
+
h['a'].should == 'b'
|
13
|
+
h['c'].should == 'd'
|
14
|
+
h['e'].should == 'f'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should parse a nil query string' do
|
18
|
+
qs_parse(nil).should == {}
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should allow different delimiters' do
|
22
|
+
h = qs_parse('a=b|c=d|e=f', '|')
|
23
|
+
h['a'].should == 'b'
|
24
|
+
h['c'].should == 'd'
|
25
|
+
h['e'].should == 'f'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should parse a blank query string' do
|
29
|
+
qs_parse("").should == {}
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should parse a query string with multiple delimiters together' do
|
33
|
+
h = qs_parse("appkey=ff-postrank&&id=http%3A%2F%2Fmail.google.com%2Fmail%2Ffeed%2Fatom")
|
34
|
+
h.keys.length.should == 2
|
35
|
+
h['appkey'].should == 'ff-postrank'
|
36
|
+
h['id'].should == 'http://mail.google.com/mail/feed/atom'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should parse a key with no value at the end of the string' do
|
40
|
+
h = qs_parse("appkey=ff-postrank&id=http%3A%2F%2Fmail.google.com%2Fmail%2Ffeed%2Fatom&noindex")
|
41
|
+
h.keys.length.should == 3
|
42
|
+
h['appkey'].should == 'ff-postrank'
|
43
|
+
h['id'].should == 'http://mail.google.com/mail/feed/atom'
|
44
|
+
h['noindex'].should == 'noindex'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should parse a key with no value at the beginning of the string' do
|
48
|
+
h = qs_parse("noindex&appkey=ff-postrank&id=http%3A%2F%2Fmail.google.com%2Fmail%2Ffeed%2Fatom")
|
49
|
+
h.keys.length.should == 3
|
50
|
+
h['appkey'].should == 'ff-postrank'
|
51
|
+
h['id'].should == 'http://mail.google.com/mail/feed/atom'
|
52
|
+
h['noindex'].should == 'noindex'
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should parse a key with no value in the middle of the string' do
|
56
|
+
h = qs_parse("appkey=ff-postrank&noindex&id=http%3A%2F%2Fmail.google.com%2Fmail%2Ffeed%2Fatom")
|
57
|
+
h.keys.length.should == 3
|
58
|
+
h['appkey'].should == 'ff-postrank'
|
59
|
+
h['id'].should == 'http://mail.google.com/mail/feed/atom'
|
60
|
+
h['noindex'].should == 'noindex'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should handle receiving only a key without a value' do
|
64
|
+
h = qs_parse('noindex')
|
65
|
+
h.keys.length.should == 1
|
66
|
+
h['noindex'].should == 'noindex'
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should parse a string with only =' do
|
70
|
+
qs_parse("=").should == {}
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should parse a string with only &' do
|
74
|
+
qs_parse('&').should == {}
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should parse a key with a blank value' do
|
78
|
+
h = qs_parse("mykey=&otherkey=zsf")
|
79
|
+
h.keys.length.should == 2
|
80
|
+
h['mykey'].should == ''
|
81
|
+
h['otherkey'].should == 'zsf'
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should parse a value with no key' do
|
85
|
+
h = qs_parse("mykey=asdf&=myvalue")
|
86
|
+
h.keys.length.should == 1
|
87
|
+
h['mykey'].should == 'asdf'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should handle arrays of values' do
|
91
|
+
h = qs_parse("url[]=http%3A%2F%2Fscience.slashdot.org%2Farticle.pl%3Fsid%3D08%2F09%2F23%2F1210218%26from%3Drss&feed_id[]=132&url[]=http%3A%2F%2Fhardware.slashdot.org%2Farticle.pl%3Fsid%3D08%2F09%2F23%2F0313242%26from%3Drss&feed_id[]=132")
|
92
|
+
h['url'].class.should == Array
|
93
|
+
h['url'].length.should == 2
|
94
|
+
h['url'][0].should == 'http://science.slashdot.org/article.pl?sid=08/09/23/1210218&from=rss'
|
95
|
+
h['url'][1].should == 'http://hardware.slashdot.org/article.pl?sid=08/09/23/0313242&from=rss'
|
96
|
+
|
97
|
+
h['feed_id'].class.should == Array
|
98
|
+
h['feed_id'].length.should == 2
|
99
|
+
h['feed_id'][0].should == '132'
|
100
|
+
h['feed_id'][1].should == '132'
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should handle arrays of values with indexes' do
|
104
|
+
h = qs_parse("url[0]=http%3A%2F%2Fscience.slashdot.org%2Farticle.pl%3Fsid%3D08%2F09%2F23%2F1210218%26from%3Drss&feed_id[0]=132&url[1]=http%3A%2F%2Fhardware.slashdot.org%2Farticle.pl%3Fsid%3D08%2F09%2F23%2F0313242%26from%3Drss&feed_id[1]=132")
|
105
|
+
h['url'].class.should == Array
|
106
|
+
h['url'].length.should == 2
|
107
|
+
h['url'][0].should == 'http://science.slashdot.org/article.pl?sid=08/09/23/1210218&from=rss'
|
108
|
+
h['url'][1].should == 'http://hardware.slashdot.org/article.pl?sid=08/09/23/0313242&from=rss'
|
109
|
+
|
110
|
+
h['feed_id'].class.should == Array
|
111
|
+
h['feed_id'].length.should == 2
|
112
|
+
h['feed_id'][0].should == '132'
|
113
|
+
h['feed_id'][1].should == '132'
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should handle escaped []'s on array keys" do
|
117
|
+
h = qs_parse("url%5B%5D=http%3A%2F%2Fscience.slashdot.org%2Farticle.pl%3Fsid%3D08%2F09%2F23%2F1210218%26from%3Drss&feed_id%5B%5D=132&url%5B%5D=http%3A%2F%2Fhardware.slashdot.org%2Farticle.pl%3Fsid%3D08%2F09%2F23%2F0313242%26from%3Drss&feed_id%5B%5D=132")
|
118
|
+
h['url'].class.should == Array
|
119
|
+
h['url'].length.should == 2
|
120
|
+
h['url'][0].should == 'http://science.slashdot.org/article.pl?sid=08/09/23/1210218&from=rss'
|
121
|
+
h['url'][1].should == 'http://hardware.slashdot.org/article.pl?sid=08/09/23/0313242&from=rss'
|
122
|
+
|
123
|
+
h['feed_id'].class.should == Array
|
124
|
+
h['feed_id'].length.should == 2
|
125
|
+
h['feed_id'][0].should == '132'
|
126
|
+
h['feed_id'][1].should == '132'
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should handle arrays without [] provided' do
|
130
|
+
h = qs_parse('key=1&key[]=2&key%5B%5D=3')
|
131
|
+
h['key'].class.should == Array
|
132
|
+
h['key'].length.should == 3
|
133
|
+
h['key'][0].should == '1'
|
134
|
+
h['key'][1].should == '2'
|
135
|
+
h['key'][2].should == '3'
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'should handle blank items at the beginning and end of the strings' do
|
139
|
+
h = qs_parse("&baz=1&bar=2&splat=23&splat[]=42&splat=543&splat%5b%5d=99&splay=http:%2f%2f%3casdf&")
|
140
|
+
h.keys.length.should == 4
|
141
|
+
|
142
|
+
h['baz'].should == "1"
|
143
|
+
h['bar'].should == "2"
|
144
|
+
h['splat'].class.should == Array
|
145
|
+
h['splat'].length.should == 4
|
146
|
+
h['splat'][0].should == "23"
|
147
|
+
h['splat'][1].should == "42"
|
148
|
+
h['splat'][2].should == "543"
|
149
|
+
h['splat'][3].should == "99"
|
150
|
+
h['splay'].should == 'http://<asdf'
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should handle UTF-8 values' do
|
154
|
+
h = qs_parse("url%5B%5D=http%3A%2F%2Fwattf.com%2Fwp%2F2008%2F12%2F03%2Fmichael-nielsen-lectures-on-the-google-technology-stack%2F&feed_id%5B%5D=2e2b55b3058696ccbe633e7241d53b16&url%5B%5D=http%3A%2F%2Fjamesgolick.com%2F2008%2F10%2F27%2Foff-topic-caf%C3%A9-myriade&feed_id%5B%5D=0381dd0545852d28d82ad7d0befbfd92")
|
155
|
+
h.keys.length.should == 2
|
156
|
+
h['url'].class.should == Array
|
157
|
+
h['url'].length.should == 2
|
158
|
+
h['url'][0].should == 'http://wattf.com/wp/2008/12/03/michael-nielsen-lectures-on-the-google-technology-stack/'
|
159
|
+
h['url'][1].should == 'http://jamesgolick.com/2008/10/27/off-topic-café-myriade'
|
160
|
+
|
161
|
+
h['feed_id'].class.should == Array
|
162
|
+
h['feed_id'].length.should == 2
|
163
|
+
h['feed_id'][0].should == '2e2b55b3058696ccbe633e7241d53b16'
|
164
|
+
h['feed_id'][1].should == '0381dd0545852d28d82ad7d0befbfd92'
|
165
|
+
end
|
166
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: query_string_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- dan sinclair
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-06-15 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Simple query string parser
|
22
|
+
email: dj2@everburning.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions:
|
26
|
+
- ext/extconf.rb
|
27
|
+
- ext/extconf.rb
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- Rakefile
|
32
|
+
- ext/extconf.rb
|
33
|
+
- ext/pr_query_parser.c
|
34
|
+
- lib/query_string_parser.rb
|
35
|
+
- spec/query_string_parser_spec.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/dj2/query_string_parser
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
segments:
|
50
|
+
- 0
|
51
|
+
version: "0"
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.6
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Simple query string parser
|
66
|
+
test_files:
|
67
|
+
- spec/query_string_parser_spec.rb
|