query_string_parser 0.1.0
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.
- 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
|