sinatra-json 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a5ae5ecf330e1e8b5143de378a1137138f77481e
4
+ data.tar.gz: a27e3be1ae4bcd992af0b5871380927bad7c301e
5
+ SHA512:
6
+ metadata.gz: aac7daf808c982419098a9ef5e26dfe8864c28647d517206ce42f11f6377097c5b5182d823353a552c77ba51c0008b5c5eeaebc80fdeafa3dffccb16e22b8a35
7
+ data.tar.gz: 6eff4bcda598997fc649e1b065b930e20a94698b180ea378eab72dca76d6db3d8be798ba076c88c2cfaa6b59b5c7897511a268e31572c36b0fbd9f6e75d65079
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ doc/
3
+ pkg/
4
+ vendor/cache/*.gem
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1 @@
1
+ --markup markdown --title "sinatra-json Documentation" --protected
@@ -0,0 +1,7 @@
1
+ ### 0.1.0 / 2015-02-28
2
+
3
+ * Initial release:
4
+ * Extracted `lib/sinatra/json.rb` from [sinatra-contrib] using:
5
+
6
+ git filter-branch --force --prune-empty --index-filter "$(cat git_file_filter.sh)" --tag-name-filter cat -- --all
7
+
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ platform :ruby_18, :jruby do
7
+ gem 'json'
8
+ end
9
+
10
+ platform :ruby do
11
+ gem 'yajl-ruby'
12
+ end
13
+
14
+ gem 'multi_json'
15
+ end
16
+
17
+ group :development do
18
+ gem 'kramdown'
19
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2015 Konstantin Haase, Hal Brodigan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,47 @@
1
+ # sinatra-json
2
+
3
+ * [Homepage](https://github.com/postmodern/sinatra-json#readme)
4
+ * [Issues](https://github.com/postmodern/sinatra-json/issues)
5
+ * [Documentation](http://rubydoc.info/gems/sinatra-json/frames)
6
+ * [Email](mailto:postmodern.mod3 at gmail.com)
7
+
8
+ ## Description
9
+
10
+ {Sinatra::JSON}, extracted from [sinatra-contrib].
11
+
12
+ ## Features
13
+
14
+ * Encodes JSON responses.
15
+ * Sets Content-Type to `application/json`.
16
+ * Supports multiple JSON backends via [multi_json].
17
+
18
+ ## Examples
19
+
20
+ require 'sinatra/json'
21
+
22
+ class App < Sinatra::Base
23
+
24
+ get '/' do
25
+ json(foo: 'bar')
26
+ end
27
+
28
+ end
29
+
30
+ ## Requirements
31
+
32
+ * [multi_json] ~> 1.0
33
+ * [sinatra] ~> 1.0
34
+
35
+ ## Install
36
+
37
+ $ gem install sinatra-json
38
+
39
+ ## Copyright
40
+
41
+ Copyright (c) 2009-2015 Konstantin Haase, Hal Brodigan
42
+
43
+ See {file:LICENSE.txt} for details.
44
+
45
+ [multi_json]: https://github.com/intridea/multi_json
46
+ [sinatra]: http://www.sinatrarb.com/
47
+ [sinatra-contrib]: https://github.com/sinatra/sinatra-contrib#readme
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler'
7
+ rescue LoadError => e
8
+ warn e.message
9
+ warn "Run `gem install bundler` to install Bundler."
10
+ exit -1
11
+ end
12
+
13
+ begin
14
+ Bundler.setup(:development)
15
+ rescue Bundler::BundlerError => e
16
+ warn e.message
17
+ warn "Run `bundle install` to install missing gems."
18
+ exit e.status_code
19
+ end
20
+
21
+ require 'rake'
22
+
23
+ require 'rubygems/tasks'
24
+ Gem::Tasks.new
25
+
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new
28
+
29
+ task :test => :spec
30
+ task :default => :spec
31
+
32
+ require 'yard'
33
+ YARD::Rake::YardocTask.new
34
+ task :doc => :yard
@@ -0,0 +1,20 @@
1
+ name: sinatra-json
2
+ summary: The json method for sinatra
3
+ description: sinatra/json extracted from sinatra-contrib.
4
+ license: MIT
5
+ authors:
6
+ - Konstantin Haase
7
+ - Postmodern
8
+ email: postmodern.mod3@gmail.com
9
+ homepage: https://github.com/postmodern/sinatra-json#readme
10
+
11
+ dependencies:
12
+ multi_json: ~> 1.0
13
+ sinatra: ~> 1.0
14
+
15
+ development_dependencies:
16
+ rake: ~> 10.0
17
+ rubygems-tasks: ~> 0.2
18
+ rspec: ~> 2.4
19
+ rack-test: ~> 0.6
20
+ yard: ~> 0.8
@@ -0,0 +1,130 @@
1
+ require 'sinatra/base'
2
+ require 'multi_json'
3
+
4
+ module Sinatra
5
+ #
6
+ # {Sinatra::JSON} adds a helper method, called {JSON#json json}, for
7
+ # (obviously) json generation.
8
+ #
9
+ # ## Usage
10
+ #
11
+ # ### Classic Application
12
+ #
13
+ # In a classic application simply require the helper, and start using it:
14
+ #
15
+ # require "sinatra"
16
+ # require "sinatra/json"
17
+ #
18
+ # # define a route that uses the helper
19
+ # get '/' do
20
+ # json :foo => 'bar'
21
+ # end
22
+ #
23
+ # # The rest of your classic application code goes here...
24
+ #
25
+ # ### Modular Application
26
+ #
27
+ # In a modular application you need to require the helper, and then tell the
28
+ # application you will use it:
29
+ #
30
+ # require "sinatra/base"
31
+ # require "sinatra/json"
32
+ #
33
+ # class MyApp < Sinatra::Base
34
+ #
35
+ # # define a route that uses the helper
36
+ # get '/' do
37
+ # json :foo => 'bar'
38
+ # end
39
+ #
40
+ # # The rest of your modular application code goes here...
41
+ # end
42
+ #
43
+ # ### Encoders
44
+ #
45
+ # By default it will try to call `to_json` on the object, but if it doesn't
46
+ # respond to that message, it will use its own rather simple encoder. You can
47
+ # easily change that anyways. To use `JSON`, simply require it:
48
+ #
49
+ # require 'json'
50
+ #
51
+ # The same goes for `Yajl::Encoder`:
52
+ #
53
+ # require 'yajl'
54
+ #
55
+ # For other encoders, besides requiring them, you need to define the
56
+ # `:json_encoder` setting. For instance, for the `Whatever` encoder:
57
+ #
58
+ # require 'whatever'
59
+ # set :json_encoder, Whatever
60
+ #
61
+ # To force `json` to simply call `to_json` on the object:
62
+ #
63
+ # set :json_encoder, :to_json
64
+ #
65
+ # Actually, it can call any method:
66
+ #
67
+ # set :json_encoder, :my_fancy_json_method
68
+ #
69
+ # ### Content-Type
70
+ #
71
+ # It will automatically set the content type to "application/json". As
72
+ # usual, you can easily change that, with the `:json_content_type`
73
+ # setting:
74
+ #
75
+ # set :json_content_type, :js
76
+ #
77
+ # ### Overriding the Encoder and the Content-Type
78
+ #
79
+ # The `json` helper will also take two options `:encoder` and
80
+ # `:content_type`. The values of this options are the same as the
81
+ # `:json_encoder` and `:json_content_type` settings,
82
+ # respectively. You can also pass those to the json method:
83
+ #
84
+ # get '/' do
85
+ # json({:foo => 'bar'}, :encoder => :to_json, :content_type => :js)
86
+ # end
87
+ #
88
+ module JSON
89
+ class << self
90
+ def encode(object)
91
+ ::MultiJson.dump(object)
92
+ end
93
+ end
94
+
95
+ def json(object, options = {})
96
+ content_type resolve_content_type(options)
97
+ resolve_encoder_action object, resolve_encoder(options)
98
+ end
99
+
100
+ private
101
+
102
+ def resolve_content_type(options = {})
103
+ options[:content_type] || settings.json_content_type
104
+ end
105
+
106
+ def resolve_encoder(options = {})
107
+ options[:json_encoder] || settings.json_encoder
108
+ end
109
+
110
+ def resolve_encoder_action(object, encoder)
111
+ [:encode, :generate].each do |method|
112
+ return encoder.send(method, object) if encoder.respond_to? method
113
+ end
114
+ if encoder.is_a? Symbol
115
+ object.__send__(encoder)
116
+ else
117
+ fail "#{encoder} does not respond to #generate nor #encode"
118
+ end #if
119
+ end #resolve_encoder_action
120
+ end #JSON
121
+
122
+ Base.set :json_encoder do
123
+ ::MultiJson
124
+ end
125
+
126
+ Base.set :json_content_type, :json
127
+
128
+ # Load the JSON helpers in modular style automatically
129
+ Base.helpers JSON
130
+ end
@@ -0,0 +1,6 @@
1
+ module Sinatra
2
+ module JSON
3
+ # sinatra-json version
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'sinatra/json/version'
14
+ Sinatra::JSON::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
36
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
37
+
38
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
39
+ %w[ext lib].select { |dir| File.directory?(dir) }
40
+ })
41
+
42
+ gem.requirements = gemspec['requirements']
43
+ gem.required_ruby_version = gemspec['required_ruby_version']
44
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
45
+ gem.post_install_message = gemspec['post_install_message']
46
+
47
+ split = lambda { |string| string.split(/,\s*/) }
48
+
49
+ if gemspec['dependencies']
50
+ gemspec['dependencies'].each do |name,versions|
51
+ gem.add_dependency(name,split[versions])
52
+ end
53
+ end
54
+
55
+ if gemspec['development_dependencies']
56
+ gemspec['development_dependencies'].each do |name,versions|
57
+ gem.add_development_dependency(name,split[versions])
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+ require 'multi_json'
3
+ require 'okjson'
4
+ require 'sinatra/json'
5
+
6
+ shared_examples_for "a json encoder" do |lib, const|
7
+ before do
8
+ begin
9
+ require lib if lib
10
+ @encoder = eval(const)
11
+ rescue LoadError
12
+ pending "unable to load #{lib}"
13
+ end
14
+ end
15
+
16
+ it "allows setting :encoder to #{const}" do
17
+ enc = @encoder
18
+ mock_app { get('/') { json({'foo' => 'bar'}, :encoder => enc) }}
19
+ results_in 'foo' => 'bar'
20
+ end
21
+
22
+ it "allows setting settings.json_encoder to #{const}" do
23
+ enc = @encoder
24
+ mock_app do
25
+ set :json_encoder, enc
26
+ get('/') { json 'foo' => 'bar' }
27
+ end
28
+ results_in 'foo' => 'bar'
29
+ end
30
+ end
31
+
32
+ describe Sinatra::JSON do
33
+ def mock_app(&block)
34
+ super do
35
+ class_eval(&block)
36
+ end
37
+ end
38
+
39
+ def results_in(obj)
40
+ expect(OkJson.decode(get('/').body)).to eq(obj)
41
+ end
42
+
43
+ it "encodes objects to json out of the box" do
44
+ mock_app { get('/') { json :foo => [1, 'bar', nil] } }
45
+ results_in 'foo' => [1, 'bar', nil]
46
+ end
47
+
48
+ it "sets the content type to 'application/json'" do
49
+ mock_app { get('/') { json({}) } }
50
+ expect(get('/')["Content-Type"]).to include("application/json")
51
+ end
52
+
53
+ it "allows overriding content type with :content_type" do
54
+ mock_app { get('/') { json({}, :content_type => "foo/bar") } }
55
+ expect(get('/')["Content-Type"]).to eq("foo/bar")
56
+ end
57
+
58
+ it "accepts shorthands for :content_type" do
59
+ mock_app { get('/') { json({}, :content_type => :js) } }
60
+ expect(get('/')["Content-Type"]).to eq("application/javascript;charset=utf-8")
61
+ end
62
+
63
+ it 'calls generate on :encoder if available' do
64
+ enc = Object.new
65
+ def enc.generate(obj) obj.inspect end
66
+ mock_app { get('/') { json(42, :encoder => enc) }}
67
+ expect(get('/').body).to eq('42')
68
+ end
69
+
70
+ it 'calls encode on :encoder if available' do
71
+ enc = Object.new
72
+ def enc.encode(obj) obj.inspect end
73
+ mock_app { get('/') { json(42, :encoder => enc) }}
74
+ expect(get('/').body).to eq('42')
75
+ end
76
+
77
+ it 'sends :encoder as method call if it is a Symbol' do
78
+ mock_app { get('/') { json(42, :encoder => :inspect) }}
79
+ expect(get('/').body).to eq('42')
80
+ end
81
+
82
+ it 'calls generate on settings.json_encoder if available' do
83
+ enc = Object.new
84
+ def enc.generate(obj) obj.inspect end
85
+ mock_app do
86
+ set :json_encoder, enc
87
+ get('/') { json 42 }
88
+ end
89
+ expect(get('/').body).to eq('42')
90
+ end
91
+
92
+ it 'calls encode on settings.json_encode if available' do
93
+ enc = Object.new
94
+ def enc.encode(obj) obj.inspect end
95
+ mock_app do
96
+ set :json_encoder, enc
97
+ get('/') { json 42 }
98
+ end
99
+ expect(get('/').body).to eq('42')
100
+ end
101
+
102
+ it 'sends settings.json_encode as method call if it is a Symbol' do
103
+ mock_app do
104
+ set :json_encoder, :inspect
105
+ get('/') { json 42 }
106
+ end
107
+ expect(get('/').body).to eq('42')
108
+ end
109
+
110
+ describe('Yajl') { it_should_behave_like "a json encoder", "yajl", "Yajl::Encoder" } unless defined? JRUBY_VERSION
111
+ describe('JSON') { it_should_behave_like "a json encoder", "json", "::JSON" }
112
+ describe('OkJson') { it_should_behave_like "a json encoder", nil, "OkJson" }
113
+ describe('to_json') { it_should_behave_like "a json encoder", "json", ":to_json" }
114
+ describe('without') { it_should_behave_like "a json encoder", nil, "Sinatra::JSON" }
115
+ end
@@ -0,0 +1,581 @@
1
+ # Copyright 2011 Keith Rarick
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ # See https://github.com/kr/okjson for updates.
22
+
23
+ require 'stringio'
24
+
25
+ # Some parts adapted from
26
+ # http://golang.org/src/pkg/json/decode.go and
27
+ # http://golang.org/src/pkg/utf8/utf8.go
28
+ module OkJson
29
+ extend self
30
+
31
+
32
+ # Decodes a json document in string s and
33
+ # returns the corresponding ruby value.
34
+ # String s must be valid UTF-8. If you have
35
+ # a string in some other encoding, convert
36
+ # it first.
37
+ #
38
+ # String values in the resulting structure
39
+ # will be UTF-8.
40
+ def decode(s)
41
+ ts = lex(s)
42
+ v, ts = textparse(ts)
43
+ if ts.length > 0
44
+ raise Error, 'trailing garbage'
45
+ end
46
+ v
47
+ end
48
+
49
+
50
+ # Parses a "json text" in the sense of RFC 4627.
51
+ # Returns the parsed value and any trailing tokens.
52
+ # Note: this is almost the same as valparse,
53
+ # except that it does not accept atomic values.
54
+ def textparse(ts)
55
+ if ts.length < 0
56
+ raise Error, 'empty'
57
+ end
58
+
59
+ typ, _, val = ts[0]
60
+ case typ
61
+ when '{' then objparse(ts)
62
+ when '[' then arrparse(ts)
63
+ else
64
+ raise Error, "unexpected #{val.inspect}"
65
+ end
66
+ end
67
+
68
+
69
+ # Parses a "value" in the sense of RFC 4627.
70
+ # Returns the parsed value and any trailing tokens.
71
+ def valparse(ts)
72
+ if ts.length < 0
73
+ raise Error, 'empty'
74
+ end
75
+
76
+ typ, _, val = ts[0]
77
+ case typ
78
+ when '{' then objparse(ts)
79
+ when '[' then arrparse(ts)
80
+ when :val,:str then [val, ts[1..-1]]
81
+ else
82
+ raise Error, "unexpected #{val.inspect}"
83
+ end
84
+ end
85
+
86
+
87
+ # Parses an "object" in the sense of RFC 4627.
88
+ # Returns the parsed value and any trailing tokens.
89
+ def objparse(ts)
90
+ ts = eat('{', ts)
91
+ obj = {}
92
+
93
+ if ts[0][0] == '}'
94
+ return obj, ts[1..-1]
95
+ end
96
+
97
+ k, v, ts = pairparse(ts)
98
+ obj[k] = v
99
+
100
+ if ts[0][0] == '}'
101
+ return obj, ts[1..-1]
102
+ end
103
+
104
+ loop do
105
+ ts = eat(',', ts)
106
+
107
+ k, v, ts = pairparse(ts)
108
+ obj[k] = v
109
+
110
+ if ts[0][0] == '}'
111
+ return obj, ts[1..-1]
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+ # Parses a "member" in the sense of RFC 4627.
118
+ # Returns the parsed values and any trailing tokens.
119
+ def pairparse(ts)
120
+ (typ, _, k), ts = ts[0], ts[1..-1]
121
+ if typ != :str
122
+ raise Error, "unexpected #{k.inspect}"
123
+ end
124
+ ts = eat(':', ts)
125
+ v, ts = valparse(ts)
126
+ [k, v, ts]
127
+ end
128
+
129
+
130
+ # Parses an "array" in the sense of RFC 4627.
131
+ # Returns the parsed value and any trailing tokens.
132
+ def arrparse(ts)
133
+ ts = eat('[', ts)
134
+ arr = []
135
+
136
+ if ts[0][0] == ']'
137
+ return arr, ts[1..-1]
138
+ end
139
+
140
+ v, ts = valparse(ts)
141
+ arr << v
142
+
143
+ if ts[0][0] == ']'
144
+ return arr, ts[1..-1]
145
+ end
146
+
147
+ loop do
148
+ ts = eat(',', ts)
149
+
150
+ v, ts = valparse(ts)
151
+ arr << v
152
+
153
+ if ts[0][0] == ']'
154
+ return arr, ts[1..-1]
155
+ end
156
+ end
157
+ end
158
+
159
+
160
+ def eat(typ, ts)
161
+ if ts[0][0] != typ
162
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
163
+ end
164
+ ts[1..-1]
165
+ end
166
+
167
+
168
+ # Sans s and returns a list of json tokens,
169
+ # excluding white space (as defined in RFC 4627).
170
+ def lex(s)
171
+ ts = []
172
+ while s.length > 0
173
+ typ, lexeme, val = tok(s)
174
+ if typ == nil
175
+ raise Error, "invalid character at #{s[0,10].inspect}"
176
+ end
177
+ if typ != :space
178
+ ts << [typ, lexeme, val]
179
+ end
180
+ s = s[lexeme.length..-1]
181
+ end
182
+ ts
183
+ end
184
+
185
+
186
+ # Scans the first token in s and
187
+ # returns a 3-element list, or nil
188
+ # if no such token exists.
189
+ #
190
+ # The first list element is one of
191
+ # '{', '}', ':', ',', '[', ']',
192
+ # :val, :str, and :space.
193
+ #
194
+ # The second element is the lexeme.
195
+ #
196
+ # The third element is the value of the
197
+ # token for :val and :str, otherwise
198
+ # it is the lexeme.
199
+ def tok(s)
200
+ case s[0]
201
+ when ?{ then ['{', s[0,1], s[0,1]]
202
+ when ?} then ['}', s[0,1], s[0,1]]
203
+ when ?: then [':', s[0,1], s[0,1]]
204
+ when ?, then [',', s[0,1], s[0,1]]
205
+ when ?[ then ['[', s[0,1], s[0,1]]
206
+ when ?] then [']', s[0,1], s[0,1]]
207
+ when ?n then nulltok(s)
208
+ when ?t then truetok(s)
209
+ when ?f then falsetok(s)
210
+ when ?" then strtok(s)
211
+ when Spc then [:space, s[0,1], s[0,1]]
212
+ when ?\t then [:space, s[0,1], s[0,1]]
213
+ when ?\n then [:space, s[0,1], s[0,1]]
214
+ when ?\r then [:space, s[0,1], s[0,1]]
215
+ else numtok(s)
216
+ end
217
+ end
218
+
219
+
220
+ def nulltok(s); s[0,4] == 'null' && [:val, 'null', nil] end
221
+ def truetok(s); s[0,4] == 'true' && [:val, 'true', true] end
222
+ def falsetok(s); s[0,5] == 'false' && [:val, 'false', false] end
223
+
224
+
225
+ def numtok(s)
226
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
227
+ if m && m.begin(0) == 0
228
+ if m[3] && !m[2]
229
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
230
+ elsif m[2]
231
+ [:val, m[0], Float(m[0])]
232
+ else
233
+ [:val, m[0], Integer(m[0])]
234
+ end
235
+ end
236
+ end
237
+
238
+
239
+ def strtok(s)
240
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
241
+ if ! m
242
+ raise Error, "invalid string literal at #{abbrev(s)}"
243
+ end
244
+ [:str, m[0], unquote(m[0])]
245
+ end
246
+
247
+
248
+ def abbrev(s)
249
+ t = s[0,10]
250
+ p = t['`']
251
+ t = t[0,p] if p
252
+ t = t + '...' if t.length < s.length
253
+ '`' + t + '`'
254
+ end
255
+
256
+
257
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
258
+ # The rules are different than for Ruby, so we cannot use eval.
259
+ # Unquote will raise an error if q contains control characters.
260
+ def unquote(q)
261
+ q = q[1...-1]
262
+ a = q.dup # allocate a big enough string
263
+ r, w = 0, 0
264
+ while r < q.length
265
+ c = q[r]
266
+ case true
267
+ when c == ?\\
268
+ r += 1
269
+ if r >= q.length
270
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
271
+ end
272
+
273
+ case q[r]
274
+ when ?",?\\,?/,?'
275
+ a[w] = q[r]
276
+ r += 1
277
+ w += 1
278
+ when ?b,?f,?n,?r,?t
279
+ a[w] = Unesc[q[r]]
280
+ r += 1
281
+ w += 1
282
+ when ?u
283
+ r += 1
284
+ uchar = begin
285
+ hexdec4(q[r,4])
286
+ rescue RuntimeError => e
287
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
288
+ end
289
+ r += 4
290
+ if surrogate? uchar
291
+ if q.length >= r+6
292
+ uchar1 = hexdec4(q[r+2,4])
293
+ uchar = subst(uchar, uchar1)
294
+ if uchar != Ucharerr
295
+ # A valid pair; consume.
296
+ r += 6
297
+ end
298
+ end
299
+ end
300
+ w += ucharenc(a, w, uchar)
301
+ else
302
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
303
+ end
304
+ when c == ?", c < Spc
305
+ raise Error, "invalid character in string literal \"#{q}\""
306
+ else
307
+ # Copy anything else byte-for-byte.
308
+ # Valid UTF-8 will remain valid UTF-8.
309
+ # Invalid UTF-8 will remain invalid UTF-8.
310
+ a[w] = c
311
+ r += 1
312
+ w += 1
313
+ end
314
+ end
315
+ a[0,w]
316
+ end
317
+
318
+
319
+ # Encodes unicode character u as UTF-8
320
+ # bytes in string a at position i.
321
+ # Returns the number of bytes written.
322
+ def ucharenc(a, i, u)
323
+ case true
324
+ when u <= Uchar1max
325
+ a[i] = (u & 0xff).chr
326
+ 1
327
+ when u <= Uchar2max
328
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
329
+ a[i+1] = (Utagx | (u&Umaskx)).chr
330
+ 2
331
+ when u <= Uchar3max
332
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
333
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
334
+ a[i+2] = (Utagx | (u&Umaskx)).chr
335
+ 3
336
+ else
337
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
338
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
339
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
340
+ a[i+3] = (Utagx | (u&Umaskx)).chr
341
+ 4
342
+ end
343
+ end
344
+
345
+
346
+ def hexdec4(s)
347
+ if s.length != 4
348
+ raise Error, 'short'
349
+ end
350
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
351
+ end
352
+
353
+
354
+ def subst(u1, u2)
355
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
356
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
357
+ end
358
+ return Ucharerr
359
+ end
360
+
361
+
362
+ def unsubst(u)
363
+ if u < Usurrself || u > Umax || surrogate?(u)
364
+ return Ucharerr, Ucharerr
365
+ end
366
+ u -= Usurrself
367
+ [Usurr1 + ((u>>10)&0x3ff), Usurr2 + (u&0x3ff)]
368
+ end
369
+
370
+
371
+ def surrogate?(u)
372
+ Usurr1 <= u && u < Usurr3
373
+ end
374
+
375
+
376
+ def nibble(c)
377
+ case true
378
+ when ?0 <= c && c <= ?9 then c.ord - ?0.ord
379
+ when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
380
+ when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
381
+ else
382
+ raise Error, "invalid hex code #{c}"
383
+ end
384
+ end
385
+
386
+
387
+ # Encodes x into a json text. It may contain only
388
+ # Array, Hash, String, Numeric, true, false, nil.
389
+ # (Note, this list excludes Symbol.)
390
+ # X itself must be an Array or a Hash.
391
+ # No other value can be encoded, and an error will
392
+ # be raised if x contains any other value, such as
393
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
394
+ # is not a String.
395
+ # Strings contained in x must be valid UTF-8.
396
+ def encode(x)
397
+ case x
398
+ when Hash then objenc(x)
399
+ when Array then arrenc(x)
400
+ else
401
+ raise Error, 'root value must be an Array or a Hash'
402
+ end
403
+ end
404
+
405
+
406
+ def valenc(x)
407
+ case x
408
+ when Hash then objenc(x)
409
+ when Array then arrenc(x)
410
+ when String then strenc(x)
411
+ when Numeric then numenc(x)
412
+ when true then "true"
413
+ when false then "false"
414
+ when nil then "null"
415
+ else
416
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
417
+ end
418
+ end
419
+
420
+
421
+ def objenc(x)
422
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
423
+ end
424
+
425
+
426
+ def arrenc(a)
427
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
428
+ end
429
+
430
+
431
+ def keyenc(k)
432
+ case k
433
+ when String then strenc(k)
434
+ else
435
+ raise Error, "Hash key is not a string: #{k.inspect}"
436
+ end
437
+ end
438
+
439
+
440
+ def strenc(s)
441
+ t = StringIO.new
442
+ t.putc(?")
443
+ r = 0
444
+ while r < s.length
445
+ case s[r]
446
+ when ?" then t.print('\\"')
447
+ when ?\\ then t.print('\\\\')
448
+ when ?\b then t.print('\\b')
449
+ when ?\f then t.print('\\f')
450
+ when ?\n then t.print('\\n')
451
+ when ?\r then t.print('\\r')
452
+ when ?\t then t.print('\\t')
453
+ else
454
+ c = s[r]
455
+ case true
456
+ when Spc <= c && c <= ?~
457
+ t.putc(c)
458
+ when true
459
+ u, size = uchardec(s, r)
460
+ r += size - 1 # we add one more at the bottom of the loop
461
+ if u < 0x10000
462
+ t.print('\\u')
463
+ hexenc4(t, u)
464
+ else
465
+ u1, u2 = unsubst(u)
466
+ t.print('\\u')
467
+ hexenc4(t, u1)
468
+ t.print('\\u')
469
+ hexenc4(t, u2)
470
+ end
471
+ else
472
+ # invalid byte; skip it
473
+ end
474
+ end
475
+ r += 1
476
+ end
477
+ t.putc(?")
478
+ t.string
479
+ end
480
+
481
+
482
+ def hexenc4(t, u)
483
+ t.putc(Hex[(u>>12)&0xf])
484
+ t.putc(Hex[(u>>8)&0xf])
485
+ t.putc(Hex[(u>>4)&0xf])
486
+ t.putc(Hex[u&0xf])
487
+ end
488
+
489
+
490
+ def numenc(x)
491
+ if x.nan? || x.infinite?
492
+ return 'null'
493
+ end rescue nil
494
+ "#{x}"
495
+ end
496
+
497
+
498
+ # Decodes unicode character u from UTF-8
499
+ # bytes in string s at position i.
500
+ # Returns u and the number of bytes read.
501
+ def uchardec(s, i)
502
+ n = s.length - i
503
+ return [Ucharerr, 1] if n < 1
504
+
505
+ c0 = s[i].ord
506
+
507
+ # 1-byte, 7-bit sequence?
508
+ if c0 < Utagx
509
+ return [c0, 1]
510
+ end
511
+
512
+ # unexpected continuation byte?
513
+ return [Ucharerr, 1] if c0 < Utag2
514
+
515
+ # need continuation byte
516
+ return [Ucharerr, 1] if n < 2
517
+ c1 = s[i+1].ord
518
+ return [Ucharerr, 1] if c1 < Utagx || Utag2 <= c1
519
+
520
+ # 2-byte, 11-bit sequence?
521
+ if c0 < Utag3
522
+ u = (c0&Umask2)<<6 | (c1&Umaskx)
523
+ return [Ucharerr, 1] if u <= Uchar1max
524
+ return [u, 2]
525
+ end
526
+
527
+ # need second continuation byte
528
+ return [Ucharerr, 1] if n < 3
529
+ c2 = s[i+2].ord
530
+ return [Ucharerr, 1] if c2 < Utagx || Utag2 <= c2
531
+
532
+ # 3-byte, 16-bit sequence?
533
+ if c0 < Utag4
534
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
535
+ return [Ucharerr, 1] if u <= Uchar2max
536
+ return [u, 3]
537
+ end
538
+
539
+ # need third continuation byte
540
+ return [Ucharerr, 1] if n < 4
541
+ c3 = s[i+3].ord
542
+ return [Ucharerr, 1] if c3 < Utagx || Utag2 <= c3
543
+
544
+ # 4-byte, 21-bit sequence?
545
+ if c0 < Utag5
546
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
547
+ return [Ucharerr, 1] if u <= Uchar3max
548
+ return [u, 4]
549
+ end
550
+
551
+ return [Ucharerr, 1]
552
+ end
553
+
554
+
555
+ class Error < ::StandardError
556
+ end
557
+
558
+
559
+ Utagx = 0x80 # 1000 0000
560
+ Utag2 = 0xc0 # 1100 0000
561
+ Utag3 = 0xe0 # 1110 0000
562
+ Utag4 = 0xf0 # 1111 0000
563
+ Utag5 = 0xF8 # 1111 1000
564
+ Umaskx = 0x3f # 0011 1111
565
+ Umask2 = 0x1f # 0001 1111
566
+ Umask3 = 0x0f # 0000 1111
567
+ Umask4 = 0x07 # 0000 0111
568
+ Uchar1max = (1<<7) - 1
569
+ Uchar2max = (1<<11) - 1
570
+ Uchar3max = (1<<16) - 1
571
+ Ucharerr = 0xFFFD # unicode "replacement char"
572
+ Usurrself = 0x10000
573
+ Usurr1 = 0xd800
574
+ Usurr2 = 0xdc00
575
+ Usurr3 = 0xe000
576
+ Umax = 0x10ffff
577
+
578
+ Spc = ' '[0]
579
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
580
+ Hex = '0123456789abcdef'
581
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helpers'
2
+
3
+ ENV['RACK_ENV'] = 'test'
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with :rspec#, :stdlib
7
+ config.include Sinatra::TestHelpers
8
+ end
@@ -0,0 +1,87 @@
1
+ require 'sinatra/base'
2
+ require 'rack/test'
3
+ require 'rack'
4
+ require 'forwardable'
5
+
6
+ module Sinatra
7
+ Base.set :environment, :test
8
+
9
+ module TestHelpers
10
+ class Session < Rack::Test::Session
11
+ def global_env
12
+ @global_env ||= {}
13
+ end
14
+
15
+ private
16
+
17
+ def default_env
18
+ super.merge global_env
19
+ end
20
+ end
21
+
22
+ include Rack::Test::Methods
23
+ extend Forwardable
24
+ attr_accessor :settings
25
+
26
+ def_delegators :last_response, :body, :headers, :status, :errors
27
+ def_delegators :app, :configure, :set, :enable, :disable, :use, :helpers, :register
28
+ def_delegators :current_session, :env_for
29
+ def_delegators :rack_mock_session, :cookie_jar
30
+
31
+ def mock_app(base = Sinatra::Base, &block)
32
+ inner = nil
33
+ @app = Sinatra.new(base) do
34
+ inner = self
35
+ class_eval(&block)
36
+ end
37
+ @settings = inner
38
+ app
39
+ end
40
+
41
+ def app=(base)
42
+ @app = base
43
+ end
44
+
45
+ alias set_app app=
46
+
47
+ def app
48
+ @app ||= Class.new Sinatra::Base
49
+ Rack::Lint.new @app
50
+ end
51
+
52
+ unless method_defined? :options
53
+ def options(uri, params = {}, env = {}, &block)
54
+ env = env_for(uri, env.merge(:method => "OPTIONS", :params => params))
55
+ current_session.send(:process_request, uri, env, &block)
56
+ end
57
+ end
58
+
59
+ unless method_defined? :patch
60
+ def patch(uri, params = {}, env = {}, &block)
61
+ env = env_for(uri, env.merge(:method => "PATCH", :params => params))
62
+ current_session.send(:process_request, uri, env, &block)
63
+ end
64
+ end
65
+
66
+ def last_request?
67
+ last_request
68
+ true
69
+ rescue Rack::Test::Error
70
+ false
71
+ end
72
+
73
+ def session
74
+ return {} unless last_request?
75
+ raise Rack::Test::Error, "session not enabled for app" unless last_env["rack.session"] or app.session?
76
+ last_request.session
77
+ end
78
+
79
+ def last_env
80
+ last_request.env
81
+ end
82
+
83
+ def build_rack_test_session(name) # :nodoc:
84
+ Session.new rack_mock_session(name)
85
+ end
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-json
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Haase
8
+ - Postmodern
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-03-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: multi_json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '1.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '1.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: sinatra
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '1.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '1.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '10.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '10.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubygems-tasks
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '0.2'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.2'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: '2.4'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: '2.4'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rack-test
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: '0.6'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ~>
96
+ - !ruby/object:Gem::Version
97
+ version: '0.6'
98
+ - !ruby/object:Gem::Dependency
99
+ name: yard
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ~>
103
+ - !ruby/object:Gem::Version
104
+ version: '0.8'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ~>
110
+ - !ruby/object:Gem::Version
111
+ version: '0.8'
112
+ description: sinatra/json extracted from sinatra-contrib.
113
+ email: postmodern.mod3@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files:
117
+ - ChangeLog.md
118
+ - LICENSE.txt
119
+ - README.md
120
+ files:
121
+ - .document
122
+ - .gitignore
123
+ - .rspec
124
+ - .yardopts
125
+ - ChangeLog.md
126
+ - Gemfile
127
+ - LICENSE.txt
128
+ - README.md
129
+ - Rakefile
130
+ - gemspec.yml
131
+ - lib/sinatra/json.rb
132
+ - lib/sinatra/json/version.rb
133
+ - sinatra-json.gemspec
134
+ - spec/json_spec.rb
135
+ - spec/okjson.rb
136
+ - spec/spec_helper.rb
137
+ - spec/test_helpers.rb
138
+ homepage: https://github.com/postmodern/sinatra-json#readme
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 2.0.14
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: The json method for sinatra
162
+ test_files: []