nyara 0.0.1.pre

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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/example/design.rb +62 -0
  3. data/example/fib.rb +15 -0
  4. data/example/hello.rb +5 -0
  5. data/example/stream.rb +10 -0
  6. data/ext/accept.c +133 -0
  7. data/ext/event.c +89 -0
  8. data/ext/extconf.rb +34 -0
  9. data/ext/hashes.c +130 -0
  10. data/ext/http-parser/AUTHORS +41 -0
  11. data/ext/http-parser/CONTRIBUTIONS +4 -0
  12. data/ext/http-parser/LICENSE-MIT +23 -0
  13. data/ext/http-parser/contrib/parsertrace.c +156 -0
  14. data/ext/http-parser/contrib/url_parser.c +44 -0
  15. data/ext/http-parser/http_parser.c +2175 -0
  16. data/ext/http-parser/http_parser.h +304 -0
  17. data/ext/http-parser/test.c +3425 -0
  18. data/ext/http_parser.c +1 -0
  19. data/ext/inc/epoll.h +60 -0
  20. data/ext/inc/kqueue.h +77 -0
  21. data/ext/inc/status_codes.inc +64 -0
  22. data/ext/inc/str_intern.h +66 -0
  23. data/ext/inc/version.inc +1 -0
  24. data/ext/mime.c +107 -0
  25. data/ext/multipart-parser-c/README.md +18 -0
  26. data/ext/multipart-parser-c/multipart_parser.c +309 -0
  27. data/ext/multipart-parser-c/multipart_parser.h +48 -0
  28. data/ext/multipart_parser.c +1 -0
  29. data/ext/nyara.c +56 -0
  30. data/ext/nyara.h +59 -0
  31. data/ext/request.c +474 -0
  32. data/ext/route.cc +325 -0
  33. data/ext/url_encoded.c +304 -0
  34. data/hello.rb +5 -0
  35. data/lib/nyara/config.rb +64 -0
  36. data/lib/nyara/config_hash.rb +51 -0
  37. data/lib/nyara/controller.rb +336 -0
  38. data/lib/nyara/cookie.rb +31 -0
  39. data/lib/nyara/cpu_counter.rb +65 -0
  40. data/lib/nyara/header_hash.rb +18 -0
  41. data/lib/nyara/mime_types.rb +612 -0
  42. data/lib/nyara/nyara.rb +82 -0
  43. data/lib/nyara/param_hash.rb +5 -0
  44. data/lib/nyara/request.rb +144 -0
  45. data/lib/nyara/route.rb +138 -0
  46. data/lib/nyara/route_entry.rb +43 -0
  47. data/lib/nyara/session.rb +104 -0
  48. data/lib/nyara/view.rb +317 -0
  49. data/lib/nyara.rb +25 -0
  50. data/nyara.gemspec +20 -0
  51. data/rakefile +91 -0
  52. data/readme.md +35 -0
  53. data/spec/ext_mime_match_spec.rb +27 -0
  54. data/spec/ext_parse_accept_value_spec.rb +29 -0
  55. data/spec/ext_parse_spec.rb +138 -0
  56. data/spec/ext_route_spec.rb +70 -0
  57. data/spec/hashes_spec.rb +71 -0
  58. data/spec/path_helper_spec.rb +77 -0
  59. data/spec/request_delegate_spec.rb +67 -0
  60. data/spec/request_spec.rb +56 -0
  61. data/spec/route_entry_spec.rb +12 -0
  62. data/spec/route_spec.rb +84 -0
  63. data/spec/session_spec.rb +66 -0
  64. data/spec/spec_helper.rb +52 -0
  65. data/spec/view_spec.rb +87 -0
  66. data/tools/bench-cookie.rb +22 -0
  67. metadata +111 -0
data/lib/nyara/view.rb ADDED
@@ -0,0 +1,317 @@
1
+ module Nyara
2
+ module Renderable
3
+ end
4
+
5
+ # A support class which provides:
6
+ # - layout / locals for rendering
7
+ # - template search
8
+ # - template default content-type mapping
9
+ # - template precompile
10
+ # - streaming
11
+ #
12
+ # Streaming is implemented in this way: when +Fiber.yield+ is called, we flush +View#out+ and send the data.
13
+ # This adds a bit limitations to the layouts.
14
+ # Consider this case (+friend+ fills into View#out, while +enemy+ doesn't):
15
+ #
16
+ # friend layout { enemy layout { friend page } }
17
+ #
18
+ # Friend layout and friend page shares one buffer, but enemy layout just concats +buffer.join+ before we flush friend layout.
19
+ # So the simple solution is: templates other than stream-friendly ones are not allowed to be a layout.
20
+ #
21
+ # Note on Erubis: to support streaming, Erubis is disabled even loaded.
22
+ class View
23
+ # ext (without dot) => most preferrable content type (e.g. "text/html")
24
+ ENGINE_DEFAULT_CONTENT_TYPES = ParamHash.new
25
+
26
+ # ext (without dot) => stream friendly
27
+ ENGINE_STREAM_FRIENDLY = ParamHash.new
28
+
29
+ class Buffer < Array
30
+ def join
31
+ r = super
32
+ clear
33
+ r
34
+ end
35
+ end
36
+
37
+ class << self
38
+ def init
39
+ @root = Config['views']
40
+ @meth2ext = {} # meth => ext (without dot)
41
+ @meth2sig = {}
42
+ end
43
+ attr_reader :root
44
+
45
+ # +path+ needs extension
46
+ def on_delete path
47
+ meth = path2meth path
48
+ Renderable.class_eval do
49
+ undef meth
50
+ end
51
+
52
+ @meth2ext.delete meth
53
+ @meth2sig.delete meth
54
+ end
55
+
56
+ def on_delete_all
57
+ meths = @meth2sig
58
+ Renderable.class_eval do
59
+ meths.each do |meth, _|
60
+ undef meth
61
+ end
62
+ end
63
+ @meth2sig.clear
64
+ @meth2ext.clear
65
+ end
66
+
67
+ # +path+ needs extension
68
+ # returns dot_ext for further use
69
+ def on_update path
70
+ meth = path2meth path
71
+ return unless @meth2sig[meth] # has not been searched before, see also View.template
72
+
73
+ ext = File.extname(path)[1..-1]
74
+ return unless ext
75
+ src = precompile ext do
76
+ Dir.chdir(@root){ File.read path, encoding: 'utf-8' }
77
+ end
78
+
79
+ if src
80
+ sig = @meth2sig[meth].map{|k| "#{k}: nil" }.join ','
81
+ sig = '_={}' if sig.empty?
82
+ Renderable.class_eval <<-RUBY, path, 1
83
+ def render #{sig}
84
+ #{src}
85
+ end
86
+ alias :#{meth.inspect} render
87
+ RUBY
88
+ else
89
+ t = Dir.chdir @root do
90
+ # todo display template error
91
+ Tilt.new path rescue return
92
+ end
93
+ # partly precompiled
94
+ Renderable.send :define_method, meth do |locals=nil, &p|
95
+ t.render self, locals, &p
96
+ end
97
+ end
98
+
99
+ @meth2ext[meth] = ext
100
+ end
101
+
102
+ # define inline render method and add Content-Type mapping
103
+ def register_engine ext, default_content_type, stream_friendly=false
104
+ # todo figure out fname and line
105
+ meth = engine2meth ext
106
+ file = "file".inspect
107
+ line = 1
108
+
109
+ if stream_friendly
110
+ Renderable.class_eval <<-RUBY, __FILE__, __LINE__ + 1
111
+ def render locals={}
112
+ @_nyara_locals = locals
113
+ src = locals.map{|k, _| "\#{k} = @_nyara_locals[:\#{k}];" }.join
114
+ src << View.precompile(#{ext.inspect}){ @_nyara_view.in }
115
+ instance_eval src, #{file}, #{line}
116
+ end
117
+ alias :#{meth.inspect} render
118
+ RUBY
119
+ ENGINE_STREAM_FRIENDLY[ext] = true
120
+ else
121
+ Renderable.class_eval <<-RUBY, __FILE__, __LINE__ + 1
122
+ def render locals=nil
123
+ Tilt[#{ext.inspect}].new(#{file}, #{line}){ @_nyara_view.in }.render self, locals
124
+ end
125
+ alias :#{meth.inspect} render
126
+ RUBY
127
+ end
128
+ ENGINE_DEFAULT_CONTENT_TYPES[ext] = default_content_type
129
+ end
130
+
131
+ # returns +[meth, ext_without_dot]+
132
+ def template path, locals={}
133
+ if File.extname(path).empty?
134
+ @ext_list ||= Tilt.mappings.keys.delete_if(&:empty?).join ','
135
+ Dir.chdir @root do
136
+ paths = Dir.glob("#{path}.{#@ext_list}")
137
+ if paths.size > 1
138
+ raise ArgumentError, "more than 1 matching views: #{paths.inspect}, add file extension to distinguish them"
139
+ end
140
+ path = paths.first
141
+ end
142
+ end
143
+
144
+ meth = path2meth path
145
+ ext = @meth2ext[meth]
146
+ return [meth, ext] if ext
147
+
148
+ @meth2sig[meth] = locals.keys
149
+ ext = on_update path
150
+ raise "template not found or not valid in Tilt: #{path}" unless ext
151
+ [meth, ext]
152
+ end
153
+
154
+ # private
155
+
156
+ # Block is lazy invoked when it's ok to read the template source.
157
+ def precompile ext
158
+ src_method =\
159
+ case ext
160
+ when 'slim'
161
+ :slim_src
162
+ when 'erb', 'rhtml'
163
+ :erb_src
164
+ when 'haml'
165
+ :haml_src
166
+ end
167
+ return unless src_method
168
+
169
+ send src_method, yield
170
+ end
171
+
172
+ def erb_src template
173
+ @erb_compiler ||= begin
174
+ c = ERB::Compiler.new '<>' # trim mode
175
+ c.pre_cmd = ["_erbout = @_nyara_view.out"]
176
+ c.put_cmd = "_erbout.push" # after newline
177
+ c.insert_cmd = "_erbout.push" # before newline
178
+ c.post_cmd = ["_erbout.join"]
179
+ c
180
+ end
181
+ src, enc = @erb_compiler.compile template
182
+ # todo do sth with enc?
183
+ src
184
+ end
185
+
186
+ def slim_src template
187
+ # todo pretty by env
188
+ t = Slim::Template.new(nil, nil, pretty: false){ template }
189
+ src = t.instance_variable_get :@src
190
+ if src.start_with?('_buf = []')
191
+ src.sub! '_buf = []', '_buf = @_nyara_view.out'
192
+ end
193
+ src
194
+ end
195
+
196
+ def haml_src template
197
+ e = Haml::Engine.new template
198
+ # todo trim mode
199
+ <<-RUBY
200
+ _hamlout = Haml::Buffer.new(nil, encoding: 'utf-8')
201
+ _hamlout.buffer = @_nyara_view.out
202
+ #{e.precompiled}
203
+ _hamlout.buffer.join
204
+ RUBY
205
+ end
206
+
207
+ def path2meth path
208
+ "!!#{path}"
209
+ end
210
+
211
+ def engine2meth engine
212
+ "!:#{engine}"
213
+ end
214
+ end
215
+
216
+ # NOTE this is the list used in View.precompile
217
+ %w[slim erb rhtml haml].each {|e| register_engine e, 'text/html', true }
218
+
219
+ %w[ad adoc asciidoc erubis builder liquid mab markdown mkd md
220
+ textile rdoc radius nokogiri wiki creole mediawiki mw
221
+ ].each {|e| register_engine e, 'text/html' }
222
+
223
+ register_engine 'str', 'text/plain'
224
+ register_engine 'coffee', 'application/javascript'
225
+ register_engine 'yajl', 'application/javascript'
226
+ register_engine 'rcsv', 'application/csv'
227
+ register_engine 'sass', 'text/stylesheet'
228
+ register_engine 'scss', 'text/stylesheet'
229
+ register_engine 'less', 'text/stylesheet'
230
+
231
+ # If view_path not given, find template source in opts
232
+ def initialize instance, view_path, layout, locals, opts
233
+ locals ||= {}
234
+ if view_path
235
+ raise ArgumentError, "unkown options: #{opts.inspect}" unless opts.empty?
236
+ meth, ext = View.template(view_path, locals)
237
+
238
+ unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
239
+ raise ArgumentError, "unkown template engine: #{ext.inspect}"
240
+ end
241
+
242
+ @layouts = [[meth, ext]]
243
+ else
244
+ raise ArgumentError, "too many options, expected only 1: #{opts.inspect}" if opts.size > 1
245
+ ext, template = opts.first
246
+ meth = View.engine2meth ext
247
+
248
+ unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
249
+ raise ArgumentError, "unkown template engine: #{ext.inspect}"
250
+ end
251
+
252
+ @layouts = [meth]
253
+ @in = template
254
+ end
255
+
256
+ unless layout.is_a?(Array)
257
+ layout = layout ? [layout] : []
258
+ end
259
+ layout.each do |l|
260
+ pair = View.template(l)
261
+ # see notes on View
262
+ raise "can not use #{meth} as layout" unless ENGINE_STREAM_FRIENDLY[pair[1]]
263
+ @layouts << pair
264
+ end
265
+
266
+ @locals = locals
267
+ @instance = instance
268
+ @instance.instance_variable_set :@_nyara_view, self
269
+ @out = Buffer.new
270
+ end
271
+ attr_reader :deduced_content_type, :in, :out
272
+
273
+ def render
274
+ @rest_layouts = @layouts.dup
275
+ @instance.send_chunk _render
276
+ Fiber.yield :term_close
277
+ end
278
+
279
+ # :nodoc:
280
+ def _render
281
+ t, _ = @rest_layouts.pop
282
+ if @rest_layouts.empty?
283
+ @instance.send t, @locals
284
+ else
285
+ @instance.send t do
286
+ _render
287
+ end
288
+ end
289
+ end
290
+
291
+ def stream
292
+ @rest_layouts = @layouts.dup
293
+ @fiber = Fiber.new do
294
+ @rest_result = _render
295
+ nil
296
+ end
297
+ self
298
+ end
299
+
300
+ def resume
301
+ r = @fiber.resume
302
+ Fiber.yield r if r
303
+ unless @out.empty?
304
+ @instance.send_chunk @out.join
305
+ @out.clear
306
+ end
307
+ end
308
+
309
+ def end
310
+ while @fiber.alive?
311
+ resume
312
+ end
313
+ @instance.send_chunk @rest_result
314
+ Fiber.yield :term_close
315
+ end
316
+ end
317
+ end
data/lib/nyara.rb ADDED
@@ -0,0 +1,25 @@
1
+ # auto run
2
+ require_relative "nyara/nyara"
3
+
4
+ at_exit do
5
+ Nyara::Route.compile
6
+ Nyara::View.init
7
+ Nyara.start_server
8
+ end
9
+
10
+ module Nyara
11
+ class SimpleController < Controller
12
+ end
13
+ end
14
+
15
+ %w[on tag get post put delete patch options].each do |m|
16
+ eval <<-RUBY
17
+ def #{m} *xs, &blk
18
+ Nyara::SimpleController.#{m} *xs, &blk
19
+ end
20
+ RUBY
21
+ end
22
+
23
+ configure do
24
+ map '/', 'nyara::simple'
25
+ end
data/nyara.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "nyara"
3
+ s.version = "0.0.1.pre"
4
+ s.author = "Zete Lui"
5
+ s.email = "nobody@example.com"
6
+ s.homepage = "https://github.com/luikore/nyara"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "Fast, slim and fuzzy ruby web framework + server"
9
+ s.description = "Fast, slim and fuzzy ruby web framework + server, based on preforked event queue and Fiber. NO rack NOR eventmachine are used."
10
+ s.required_ruby_version = ">=2.0.0"
11
+ s.licenses = ['BSD']
12
+
13
+ s.files = Dir.glob('{rakefile,nyara.gemspec,readme.md,**/*.{rb,h,c,cc,inc}}')
14
+ s.files += Dir.glob('ext/http-parser/{AUTHORS,CONTRIBUTIONS,LICENSE-MIT}')
15
+ s.files += Dir.glob('ext/multipart-parser-c/README.md')
16
+ s.require_paths = ["lib"]
17
+ s.extensions = ["ext/extconf.rb"]
18
+ s.rubygems_version = '2.0.3'
19
+ s.has_rdoc = false
20
+ end
data/rakefile ADDED
@@ -0,0 +1,91 @@
1
+ require "rake"
2
+
3
+ Dir.chdir __dir__
4
+
5
+ status_file = "ext/inc/status_codes.inc"
6
+ version_file = "ext/inc/version.inc"
7
+
8
+ desc "code generate"
9
+ task :gen => [status_file, version_file]
10
+
11
+ desc "generate #{status_file}"
12
+ file status_file do
13
+ puts "generating: #{status_file}"
14
+ require "nokogiri"
15
+ require "open-uri"
16
+ f = File.open status_file, 'w'
17
+ f.puts "#define HTTP_STATUS_CODES(XX)\\"
18
+ Nokogiri::XML(open("http://www.iana.org/assignments/http-status-codes/http-status-codes.xml")).css("record").each do |r|
19
+ value = r.css('value').text
20
+ next if value.index '-'
21
+ description = r.css('description').text
22
+ f.puts %Q| XX(#{value}, "#{description}");\\|
23
+ end
24
+ f.puts "// end define"
25
+ f.close
26
+ end
27
+
28
+ desc "generate #{version_file}"
29
+ file version_file => 'nyara.gemspec' do
30
+ puts "generating: #{version_file}"
31
+ lines = File.readlines('nyara.gemspec')
32
+ version = nil
33
+ lines.each do |line|
34
+ if line =~ /s\.version =/
35
+ version = line[/\d+(\.\d+)*(\.pre)?/]
36
+ break
37
+ end
38
+ end
39
+ abort 'version not found' unless version
40
+ File.open version_file, 'w' do |f|
41
+ f.puts %Q{#define NYARA_VERSION "#{version}"}
42
+ end
43
+ end
44
+
45
+ desc "build ext"
46
+ task :build do
47
+ Dir.chdir 'ext' do
48
+ sh 'make'
49
+ end
50
+ end
51
+
52
+ desc "test"
53
+ task :test => :build do
54
+ sh 'rspec', '-c'
55
+ end
56
+
57
+ desc "build and test"
58
+ task :default => :test
59
+
60
+ desc "build and install gem"
61
+ task :gem do
62
+ sh 'rm', '-f', '*.gem'
63
+ sh 'gem', 'build', 'nyara.gemspec'
64
+ gem_package = Dir.glob('*.gem').first
65
+ sh 'gem', 'install', '--no-rdoc', '--no-ri', gem_package
66
+ end
67
+
68
+ desc "clean"
69
+ task :clean do
70
+ sh 'rm', '-f', '*.gem'
71
+ Dir.chdir 'ext' do
72
+ sh 'make', 'clean'
73
+ end
74
+ end
75
+
76
+ # -- utils --
77
+
78
+ desc "collect line stat"
79
+ task :lines do
80
+ c = 0
81
+ Dir.glob('{**/*.rb,ext/*.{c,cc,h}}') do |f|
82
+ c += (File.read(f).count "\n")
83
+ end
84
+ puts "#{c} lines"
85
+ end
86
+
87
+ desc "list Nyara::Ext methods"
88
+ task :list_ext do
89
+ require_relative "lib/nyara/nyara"
90
+ puts Nyara::Ext.methods - Module.methods
91
+ end
data/readme.md ADDED
@@ -0,0 +1,35 @@
1
+ git submodule update --recursive
2
+ rake gen
3
+ rake gem
4
+
5
+ # Why fast
6
+
7
+ ## Solid http parsers written in C
8
+
9
+ These evented parsers are used:
10
+
11
+ - http_parser with chunked encoding support
12
+ - multipart request parser
13
+
14
+ And these are implemented in addition:
15
+
16
+ - `Accept-*` parser
17
+ - mime type parser and content type matcher
18
+ - url-encoded parser for parsing path / query / request body
19
+
20
+ ## Fast first-line routing and request class
21
+
22
+ ## Thin evented IO layer built for *nix and Fiber
23
+
24
+ # How fast
25
+
26
+ Speed is feature, there are specs on:
27
+
28
+ - Accept-* parse vs rack
29
+ - MIME parse vs rack
30
+ - query parse vs rack
31
+ - query parse vs houdini
32
+ - layout engine vs tilt
33
+ - evented IO vs eventmachine
34
+ - helloworld vs sinatra
35
+ - pseudo real app speed vs jruby sinatra threaded mode
@@ -0,0 +1,27 @@
1
+ require_relative "spec_helper"
2
+
3
+ module Nyara
4
+ describe Ext, '.mime_match' do
5
+ it "#mime_match_seg" do
6
+ a = Ext.mime_match_seg 'text', 'text', 'html'
7
+ assert_equal true, a
8
+
9
+ a = Ext.mime_match_seg 'text/html', 'text', 'stylesheet'
10
+ assert_equal false, a
11
+
12
+ a = Ext.mime_match_seg '*', 'text', 'html'
13
+ assert_equal true, a
14
+ end
15
+
16
+ it "#mime_match works with wildcards" do
17
+ a = Ext.mime_match %w'*', [%w'text html html']
18
+ assert_equal 'html', a
19
+
20
+ a = Ext.mime_match %w'application/javascript text/*', [%w'some text txt', %w'text html html']
21
+ assert_equal 'html', a
22
+
23
+ a = Ext.mime_match %w'text/*', [%w'some text txt']
24
+ assert_nil a
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "spec_helper"
2
+
3
+ module Nyara
4
+ describe Ext, ".parse_accept_value" do
5
+ it 'works' do
6
+ a = Ext.parse_accept_value ''
7
+ assert_equal [], a
8
+
9
+ a = Ext.parse_accept_value "text/plain; q=0.5, text/html,text/x-dvi; q=3.8, text/x-c"
10
+ assert_equal %w[text/html text/x-dvi text/x-c text/plain], a
11
+ end
12
+
13
+ it "ignores q <= 0" do
14
+ a = Ext.parse_accept_value "text/plain; q=0.0, text/html"
15
+ assert_equal(%w'text/html', a)
16
+
17
+ a = Ext.parse_accept_value "*, text/plain; q=-3"
18
+ assert_equal(%w'*', a)
19
+
20
+ a = Ext.parse_accept_value "text/plain; q=0, text/*"
21
+ assert_equal(%w'text/*', a)
22
+ end
23
+
24
+ it ".parse_accept_value should be robust" do
25
+ a = Ext.parse_accept_value 'q=0.1, text/html'
26
+ assert_equal 'text/html', a[1]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,138 @@
1
+ require_relative "spec_helper"
2
+
3
+ module Nyara
4
+ describe Ext, "parse" do
5
+ # note: this method is only used in C code
6
+ context "#parse_url_encoded_seg" do
7
+ [false, true].each do |nested|
8
+ context (nested ? 'nested mode' : 'flat mode') do
9
+ it "normal parse" do
10
+ assert_equal({'a' => 'b'}, parse('a=b', nested))
11
+ end
12
+
13
+ it "param seg end with '='" do
14
+ assert_equal({'a' => ''}, parse('a=', nested))
15
+ end
16
+
17
+ it "param seg begin with '='" do
18
+ assert_equal({'' => 'b'}, parse('=b', nested))
19
+ end
20
+
21
+ it "param seg without value" do
22
+ assert_equal({'a' => ''}, parse('a', nested))
23
+ end
24
+
25
+ it "raises error" do
26
+ assert_raise ArgumentError do
27
+ parse 'a=&b'
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ context "nested key" do
34
+ it "parses nested key" do
35
+ res = {"a"=>{"b"=>[[{"c"=>"1"}]]}}
36
+ assert_equal res, Ext.parse_url_encoded_seg({}, "a[b][][][c]=1", true)
37
+ end
38
+
39
+ it 'allows "[]" as input' do
40
+ res = {""=>[""]}
41
+ assert_equal res, Ext.parse_url_encoded_seg({}, "[]", true)
42
+ end
43
+
44
+ it 'ignores empty input' do
45
+ res = {}
46
+ assert_equal res, Ext.parse_url_encoded_seg({}, "", true)
47
+ end
48
+
49
+ it "content hash is ParamHash" do
50
+ h = ParamHash.new
51
+ assert_equal ParamHash, Ext.parse_url_encoded_seg(h, "a[b]=c", true)[:a].class
52
+ end
53
+ end
54
+
55
+ def parse str, nested
56
+ h = {}
57
+ Ext.parse_url_encoded_seg h, str, nested
58
+ end
59
+ end
60
+
61
+ context "#parse_path" do
62
+ before :each do
63
+ @output = ''
64
+ end
65
+
66
+ it "parses" do
67
+ i = '/%23+%24'
68
+ assert_equal i.bytesize, parse(i)
69
+ assert_equal "/\x23 \x24", @output
70
+ end
71
+
72
+ it "truncates ? after %" do
73
+ i = '/hello%f3%?world'
74
+ len = parse i
75
+ assert_equal '/hello%f3%?'.bytesize, len
76
+ assert_equal "/hello\xf3%", @output
77
+ end
78
+
79
+ it "truncates ? after begin" do
80
+ i = '?a'
81
+ len = parse i
82
+ assert_equal 1, len
83
+ assert_equal '', @output
84
+ end
85
+
86
+ it "truncates ? before end" do
87
+ i = 'a?'
88
+ len = parse i
89
+ assert_equal 2, len
90
+ assert_equal 'a', @output
91
+ end
92
+
93
+ it "truncates ? after unescaped char" do
94
+ i = 'a?a'
95
+ len = parse i
96
+ assert_equal 2, len
97
+ assert_equal 'a', @output
98
+ end
99
+
100
+ it "truncates ? after escaped char" do
101
+ i = '%40?'
102
+ len = parse i
103
+ assert_equal 4, len
104
+ assert_equal "\x40", @output
105
+ end
106
+
107
+ def parse input
108
+ Ext.parse_path @output, input
109
+ end
110
+ end
111
+
112
+ context ".parse_cookie" do
113
+ it "parses complex cookie" do
114
+ history = CGI.escape '历史'
115
+ cookie = "pgv_pvi; pgv_si= ; pgv_pvi=som; sid=1d6c75f0 ; PLHistory=<#{history}>;"
116
+ h = Ext.parse_cookie ParamHash.new, cookie
117
+ assert_equal '1d6c75f0', h['sid']
118
+ assert_equal '', h['pgv_si']
119
+ assert_equal '', h['pgv_pvi'] # left orverrides right
120
+ assert_equal '<历史>', h['PLHistory']
121
+ end
122
+
123
+ it "parses empty cookie" do
124
+ cookie = ''
125
+ h = Ext.parse_cookie ParamHash.new, cookie
126
+ assert_empty h
127
+ end
128
+ end
129
+
130
+ context ".parse_param" do
131
+ it "parses param with non-utf-8 chars" do
132
+ bad_s = CGI.escape "\xE2"
133
+ h = Ext.parse_param ParamHash.new, bad_s
134
+ assert_equal "", h["\xE2"]
135
+ end
136
+ end
137
+ end
138
+ end