oj 2.0.0 → 3.0.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.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +17 -23
  3. data/README.md +74 -425
  4. data/ext/oj/buf.h +103 -0
  5. data/ext/oj/cache8.c +4 -0
  6. data/ext/oj/circarray.c +68 -0
  7. data/ext/oj/circarray.h +23 -0
  8. data/ext/oj/code.c +227 -0
  9. data/ext/oj/code.h +40 -0
  10. data/ext/oj/compat.c +243 -0
  11. data/ext/oj/custom.c +1097 -0
  12. data/ext/oj/dump.c +766 -1534
  13. data/ext/oj/dump.h +92 -0
  14. data/ext/oj/dump_compat.c +937 -0
  15. data/ext/oj/dump_leaf.c +254 -0
  16. data/ext/oj/dump_object.c +810 -0
  17. data/ext/oj/dump_rails.c +329 -0
  18. data/ext/oj/dump_strict.c +416 -0
  19. data/ext/oj/encode.h +51 -0
  20. data/ext/oj/err.c +57 -0
  21. data/ext/oj/err.h +70 -0
  22. data/ext/oj/extconf.rb +17 -7
  23. data/ext/oj/fast.c +213 -180
  24. data/ext/oj/hash.c +163 -0
  25. data/ext/oj/hash.h +46 -0
  26. data/ext/oj/hash_test.c +512 -0
  27. data/ext/oj/mimic_json.c +817 -0
  28. data/ext/oj/mimic_rails.c +806 -0
  29. data/ext/oj/mimic_rails.h +17 -0
  30. data/ext/oj/object.c +752 -0
  31. data/ext/oj/odd.c +230 -0
  32. data/ext/oj/odd.h +44 -0
  33. data/ext/oj/oj.c +1288 -929
  34. data/ext/oj/oj.h +240 -69
  35. data/ext/oj/parse.c +1014 -0
  36. data/ext/oj/parse.h +92 -0
  37. data/ext/oj/reader.c +223 -0
  38. data/ext/oj/reader.h +151 -0
  39. data/ext/oj/resolve.c +127 -0
  40. data/ext/oj/{cache.h → resolve.h} +6 -13
  41. data/ext/oj/rxclass.c +133 -0
  42. data/ext/oj/rxclass.h +27 -0
  43. data/ext/oj/saj.c +77 -175
  44. data/ext/oj/scp.c +224 -0
  45. data/ext/oj/sparse.c +911 -0
  46. data/ext/oj/stream_writer.c +301 -0
  47. data/ext/oj/strict.c +162 -0
  48. data/ext/oj/string_writer.c +480 -0
  49. data/ext/oj/val_stack.c +98 -0
  50. data/ext/oj/val_stack.h +188 -0
  51. data/lib/oj/active_support_helper.rb +41 -0
  52. data/lib/oj/bag.rb +6 -10
  53. data/lib/oj/easy_hash.rb +52 -0
  54. data/lib/oj/json.rb +172 -0
  55. data/lib/oj/mimic.rb +260 -5
  56. data/lib/oj/saj.rb +13 -10
  57. data/lib/oj/schandler.rb +142 -0
  58. data/lib/oj/state.rb +131 -0
  59. data/lib/oj/version.rb +1 -1
  60. data/lib/oj.rb +11 -23
  61. data/pages/Advanced.md +22 -0
  62. data/pages/Compatibility.md +25 -0
  63. data/pages/Custom.md +23 -0
  64. data/pages/Encoding.md +65 -0
  65. data/pages/JsonGem.md +79 -0
  66. data/pages/Modes.md +140 -0
  67. data/pages/Options.md +250 -0
  68. data/pages/Rails.md +60 -0
  69. data/pages/Security.md +20 -0
  70. data/test/_test_active.rb +76 -0
  71. data/test/_test_active_mimic.rb +96 -0
  72. data/test/_test_mimic_rails.rb +126 -0
  73. data/test/activesupport4/decoding_test.rb +105 -0
  74. data/test/activesupport4/encoding_test.rb +531 -0
  75. data/test/activesupport4/test_helper.rb +41 -0
  76. data/test/activesupport5/decoding_test.rb +125 -0
  77. data/test/activesupport5/encoding_test.rb +483 -0
  78. data/test/activesupport5/encoding_test_cases.rb +90 -0
  79. data/test/activesupport5/test_helper.rb +50 -0
  80. data/test/activesupport5/time_zone_test_helpers.rb +24 -0
  81. data/test/helper.rb +27 -0
  82. data/test/isolated/shared.rb +310 -0
  83. data/test/isolated/test_mimic_after.rb +13 -0
  84. data/test/isolated/test_mimic_alone.rb +12 -0
  85. data/test/isolated/test_mimic_as_json.rb +45 -0
  86. data/test/isolated/test_mimic_before.rb +13 -0
  87. data/test/isolated/test_mimic_define.rb +28 -0
  88. data/test/isolated/test_mimic_rails_after.rb +22 -0
  89. data/test/isolated/test_mimic_rails_before.rb +21 -0
  90. data/test/isolated/test_mimic_redefine.rb +15 -0
  91. data/test/json_gem/json_addition_test.rb +216 -0
  92. data/test/json_gem/json_common_interface_test.rb +143 -0
  93. data/test/json_gem/json_encoding_test.rb +109 -0
  94. data/test/json_gem/json_ext_parser_test.rb +20 -0
  95. data/test/json_gem/json_fixtures_test.rb +35 -0
  96. data/test/json_gem/json_generator_test.rb +383 -0
  97. data/test/json_gem/json_generic_object_test.rb +90 -0
  98. data/test/json_gem/json_parser_test.rb +470 -0
  99. data/test/json_gem/json_string_matching_test.rb +42 -0
  100. data/test/json_gem/test_helper.rb +18 -0
  101. data/test/perf_compat.rb +130 -0
  102. data/test/perf_fast.rb +9 -9
  103. data/test/perf_file.rb +64 -0
  104. data/test/{perf_obj.rb → perf_object.rb} +24 -10
  105. data/test/perf_scp.rb +151 -0
  106. data/test/perf_strict.rb +32 -113
  107. data/test/sample.rb +2 -3
  108. data/test/test_compat.rb +474 -0
  109. data/test/test_custom.rb +355 -0
  110. data/test/test_debian.rb +53 -0
  111. data/test/test_fast.rb +66 -16
  112. data/test/test_file.rb +237 -0
  113. data/test/test_gc.rb +49 -0
  114. data/test/test_hash.rb +29 -0
  115. data/test/test_null.rb +376 -0
  116. data/test/test_object.rb +1010 -0
  117. data/test/test_saj.rb +16 -16
  118. data/test/test_scp.rb +417 -0
  119. data/test/test_strict.rb +410 -0
  120. data/test/test_various.rb +815 -0
  121. data/test/test_writer.rb +308 -0
  122. data/test/tests.rb +9 -902
  123. data/test/tests_mimic.rb +14 -0
  124. data/test/tests_mimic_addition.rb +7 -0
  125. metadata +253 -38
  126. data/ext/oj/cache.c +0 -148
  127. data/ext/oj/foo.rb +0 -6
  128. data/ext/oj/load.c +0 -1049
  129. data/test/a.rb +0 -38
  130. data/test/perf1.rb +0 -64
  131. data/test/perf2.rb +0 -76
  132. data/test/perf_obj_old.rb +0 -213
  133. data/test/test_mimic.rb +0 -208
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(__FILE__)
5
+ %w(lib ext test).each do |dir|
6
+ $LOAD_PATH.unshift File.expand_path("../../#{dir}", __FILE__)
7
+ end
8
+
9
+ require 'minitest'
10
+ require 'minitest/autorun'
11
+
12
+ require 'sqlite3'
13
+ require 'active_record'
14
+ require 'oj'
15
+
16
+ #Oj.mimic_JSON()
17
+ Oj.default_options = {mode: :compat, indent: 2}
18
+
19
+ #ActiveRecord::Base.logger = Logger.new(STDERR)
20
+
21
+ ActiveRecord::Base.establish_connection(
22
+ :adapter => "sqlite3",
23
+ :database => ":memory:"
24
+ )
25
+
26
+ ActiveRecord::Schema.define do
27
+ create_table :users do |table|
28
+ table.column :first_name, :string
29
+ table.column :last_name, :string
30
+ table.column :email, :string
31
+ end
32
+ end
33
+
34
+ class User < ActiveRecord::Base
35
+ end
36
+
37
+ class ActiveTest < Minitest::Test
38
+
39
+ def test_active
40
+ User.find_or_create_by(first_name: "John", last_name: "Smith", email: "john@example.com")
41
+ User.find_or_create_by(first_name: "Joan", last_name: "Smith", email: "joan@example.com")
42
+
43
+ # Single instance.
44
+ assert_equal(%|{
45
+ "id":1,
46
+ "first_name":"John",
47
+ "last_name":"Smith",
48
+ "email":"john@example.com"
49
+ }
50
+ |, Oj.dump(User.first))
51
+
52
+ # Array of instances.
53
+ assert_equal(%|[
54
+ {
55
+ "id":1,
56
+ "first_name":"John",
57
+ "last_name":"Smith",
58
+ "email":"john@example.com"
59
+ },
60
+ {
61
+ "id":2,
62
+ "first_name":"Joan",
63
+ "last_name":"Smith",
64
+ "email":"joan@example.com"
65
+ }
66
+ ]
67
+ |, Oj.dump(User.all))
68
+
69
+ # Single instance as json. (not Oj)
70
+ assert_equal(%|{"id":1,"first_name":"John","last_name":"Smith","email":"john@example.com"}|, User.first.to_json)
71
+
72
+ # Array of instances as json. (not Oj)
73
+ assert_equal(%|[{"id":1,"first_name":"John","last_name":"Smith","email":"john@example.com"},{"id":2,"first_name":"Joan","last_name":"Smith","email":"joan@example.com"}]|, User.all.to_json)
74
+
75
+ end
76
+ end
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(__FILE__)
5
+ %w(lib ext test).each do |dir|
6
+ $LOAD_PATH.unshift File.expand_path("../../#{dir}", __FILE__)
7
+ end
8
+
9
+ require 'minitest'
10
+ require 'minitest/autorun'
11
+
12
+ require 'sqlite3'
13
+ require 'active_record'
14
+ require 'oj'
15
+
16
+ Oj.mimic_JSON()
17
+ Oj.default_options = {mode: :compat, indent: 2}
18
+
19
+ #ActiveRecord::Base.logger = Logger.new(STDERR)
20
+
21
+ ActiveRecord::Base.establish_connection(
22
+ :adapter => "sqlite3",
23
+ :database => ":memory:"
24
+ )
25
+
26
+ ActiveRecord::Schema.define do
27
+ create_table :users do |table|
28
+ table.column :first_name, :string
29
+ table.column :last_name, :string
30
+ table.column :email, :string
31
+ end
32
+ end
33
+
34
+ class User < ActiveRecord::Base
35
+ end
36
+
37
+ class ActiveTest < Minitest::Test
38
+
39
+ def test_active
40
+ User.find_or_create_by(first_name: "John", last_name: "Smith", email: "john@example.com")
41
+ User.find_or_create_by(first_name: "Joan", last_name: "Smith", email: "joan@example.com")
42
+
43
+ # Single instance.
44
+ assert_equal(%|{
45
+ "id":1,
46
+ "first_name":"John",
47
+ "last_name":"Smith",
48
+ "email":"john@example.com"
49
+ }
50
+ |, Oj.dump(User.first))
51
+
52
+ # Array of instances.
53
+ assert_equal(%|[
54
+ {
55
+ "id":1,
56
+ "first_name":"John",
57
+ "last_name":"Smith",
58
+ "email":"john@example.com"
59
+ },
60
+ {
61
+ "id":2,
62
+ "first_name":"Joan",
63
+ "last_name":"Smith",
64
+ "email":"joan@example.com"
65
+ }
66
+ ]
67
+ |, Oj.dump(User.all))
68
+
69
+ # Single instance as json. (not Oj)
70
+ assert_equal(%|{
71
+ "id":1,
72
+ "first_name":"John",
73
+ "last_name":"Smith",
74
+ "email":"john@example.com"
75
+ }
76
+ |, User.first.to_json)
77
+
78
+ # Array of instances as json. (not Oj)
79
+ assert_equal(%|[
80
+ {
81
+ "id":1,
82
+ "first_name":"John",
83
+ "last_name":"Smith",
84
+ "email":"john@example.com"
85
+ },
86
+ {
87
+ "id":2,
88
+ "first_name":"Joan",
89
+ "last_name":"Smith",
90
+ "email":"joan@example.com"
91
+ }
92
+ ]
93
+ |, User.all.to_json)
94
+
95
+ end
96
+ end
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ $: << File.dirname(__FILE__)
5
+
6
+ require 'helper'
7
+ #Oj.mimic_JSON
8
+ require 'rails/all'
9
+
10
+ require 'active_model'
11
+ require 'active_model_serializers'
12
+ require 'active_support/json'
13
+ require 'active_support/time'
14
+ require 'active_support/all'
15
+
16
+ require 'oj/active_support_helper'
17
+
18
+ Oj.mimic_JSON
19
+
20
+ class Category
21
+ include ActiveModel::Model
22
+ include ActiveModel::SerializerSupport
23
+
24
+ attr_accessor :id, :name
25
+
26
+ def initialize(id, name)
27
+ @id = id
28
+ @name = name
29
+ end
30
+ end
31
+
32
+ class CategorySerializer < ActiveModel::Serializer
33
+ attributes :id, :name
34
+ end
35
+
36
+ class MimicRails < Minitest::Test
37
+
38
+ def test_mimic_exception
39
+ begin
40
+ ActiveSupport::JSON.decode("{")
41
+ puts "Failed"
42
+ rescue ActiveSupport::JSON.parse_error
43
+ assert(true)
44
+ rescue Exception
45
+ assert(false, 'Expected a JSON::ParserError')
46
+ end
47
+ end
48
+
49
+ def test_dump_string
50
+ Oj.default_options= {:indent => 2}
51
+ json = ActiveSupport::JSON.encode([1, true, nil])
52
+ assert_equal(%{[
53
+ 1,
54
+ true,
55
+ null
56
+ ]
57
+ }, json)
58
+ end
59
+
60
+ def test_dump_rational
61
+ Oj.default_options= {:indent => 2}
62
+ json = ActiveSupport::JSON.encode([1, true, Rational(1)])
63
+ assert_equal(%{[
64
+ 1,
65
+ true,
66
+ "1/1"
67
+ ]
68
+ }, json)
69
+ end
70
+
71
+ def test_dump_range
72
+ Oj.default_options= {:indent => 2}
73
+ json = ActiveSupport::JSON.encode([1, true, '01'..'12'])
74
+ assert_equal(%{[
75
+ 1,
76
+ true,
77
+ "01..12"
78
+ ]
79
+ }, json)
80
+ end
81
+
82
+ def test_dump_object
83
+ Oj.default_options= {:indent => 2}
84
+ category = Category.new(1, 'test')
85
+ serializer = CategorySerializer.new(category)
86
+
87
+ json = serializer.to_json()
88
+ puts "*** serializer.to_json() #{serializer.to_json()}"
89
+ json = serializer.as_json()
90
+ puts "*** serializer.as_json() #{serializer.as_json()}"
91
+ json = JSON.dump(serializer)
92
+ puts "*** JSON.dump(serializer) #{JSON.dump(serializer)}"
93
+
94
+ puts "*** category.to_json() #{category.to_json()}"
95
+ puts "*** category.as_json() #{category.as_json()}"
96
+ puts "*** JSON.dump(serializer) #{JSON.dump(category)}"
97
+ puts "*** Oj.dump(serializer) #{Oj.dump(category)}"
98
+
99
+ end
100
+
101
+ def test_dump_object_array
102
+ Oj.default_options= {:indent => 2}
103
+ cat1 = Category.new(1, 'test')
104
+ cat2 = Category.new(2, 'test')
105
+ a = Array.wrap([cat1, cat2])
106
+
107
+ #serializer = CategorySerializer.new(a)
108
+
109
+ puts "*** a.to_json() #{a.to_json()}"
110
+ puts "*** a.as_json() #{a.as_json()}"
111
+ puts "*** JSON.dump(a) #{JSON.dump(a)}"
112
+ puts "*** Oj.dump(a) #{Oj.dump(a)}"
113
+ end
114
+
115
+ def test_dump_time
116
+ Oj.default_options= {:indent => 2}
117
+ now = ActiveSupport::TimeZone['America/Chicago'].parse("2014-11-01 13:20:47")
118
+ json = Oj.dump(now, mode: :object, time_format: :xmlschema)
119
+ #puts "*** json: #{json}"
120
+
121
+ oj_dump = Oj.load(json, mode: :object, time_format: :xmlschema)
122
+ #puts "Now: #{now}\n Oj: #{oj_dump}"
123
+ assert_equal("2014-11-01T13:20:47-05:00", oj_dump.xmlschema)
124
+ end
125
+
126
+ end # MimicRails
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+ require 'activesupport4/test_helper'
3
+ require 'active_support/json'
4
+ require 'active_support/time'
5
+
6
+ class TestJSONDecoding < ActiveSupport::TestCase
7
+ class Foo
8
+ def self.json_create(object)
9
+ "Foo"
10
+ end
11
+ end
12
+
13
+ TESTS = {
14
+ %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
15
+ %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}},
16
+ %q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}},
17
+ %({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]},
18
+ %({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]},
19
+ %({"a": "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"},
20
+ %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"},
21
+ # multibyte
22
+ %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"},
23
+ %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)},
24
+ %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)},
25
+ %(["2007-01-01 01:12:34 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34)],
26
+ %(["2007-01-01 01:12:34 Z", "2007-01-01 01:12:35 Z"]) => [Time.utc(2007, 1, 1, 1, 12, 34), Time.utc(2007, 1, 1, 1, 12, 35)],
27
+ # no time zone
28
+ %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
29
+ # invalid date
30
+ %({"a": "1089-10-40"}) => {'a' => "1089-10-40"},
31
+ # xmlschema date notation
32
+ %({"a": "2009-08-10T19:01:02Z"}) => {'a' => Time.utc(2009, 8, 10, 19, 1, 2)},
33
+ %({"a": "2009-08-10T19:01:02+02:00"}) => {'a' => Time.utc(2009, 8, 10, 17, 1, 2)},
34
+ %({"a": "2009-08-10T19:01:02-05:00"}) => {'a' => Time.utc(2009, 8, 11, 00, 1, 2)},
35
+ # needs to be *exact*
36
+ %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "},
37
+ %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"},
38
+ %([]) => [],
39
+ %({}) => {},
40
+ %({"a":1}) => {"a" => 1},
41
+ %({"a": ""}) => {"a" => ""},
42
+ %({"a":"\\""}) => {"a" => "\""},
43
+ %({"a": null}) => {"a" => nil},
44
+ %({"a": true}) => {"a" => true},
45
+ %({"a": false}) => {"a" => false},
46
+ %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""},
47
+ %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"},
48
+ %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"},
49
+ %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"},
50
+ %q({"a": "\u003cbr /\u003e"}) => {'a' => "<br />"},
51
+ %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]},
52
+ # test combination of dates and escaped or unicode encoded data in arrays
53
+ %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) =>
54
+ [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}],
55
+ %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) =>
56
+ [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'},
57
+ {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}],
58
+ # tests escaping of "\n" char with Yaml backend
59
+ %q({"a":"\n"}) => {"a"=>"\n"},
60
+ %q({"a":"\u000a"}) => {"a"=>"\n"},
61
+ %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"},
62
+ # prevent json unmarshalling
63
+ %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"},
64
+ # json "fragments" - these are invalid JSON, but ActionPack relies on this
65
+ %q("a string") => "a string",
66
+ %q(1.1) => 1.1,
67
+ %q(1) => 1,
68
+ %q(-1) => -1,
69
+ %q(true) => true,
70
+ %q(false) => false,
71
+ %q(null) => nil
72
+ }
73
+
74
+ TESTS.each_with_index do |(json, expected), index|
75
+ test "json decodes #{index}" do
76
+ prev = ActiveSupport.parse_json_times
77
+ ActiveSupport.parse_json_times = true
78
+ silence_warnings do
79
+ assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
80
+ failed for #{json}"
81
+ end
82
+ ActiveSupport.parse_json_times = prev
83
+ end
84
+ end
85
+
86
+ test "json decodes time json with time parsing disabled" do
87
+ prev = ActiveSupport.parse_json_times
88
+ ActiveSupport.parse_json_times = false
89
+ expected = {"a" => "2007-01-01 01:12:34 Z"}
90
+ assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"}))
91
+ ActiveSupport.parse_json_times = prev
92
+ end
93
+
94
+ def test_failed_json_decoding
95
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) }
96
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) }
97
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
98
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) }
99
+ end
100
+
101
+ def test_cannot_pass_unsupported_options
102
+ assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
103
+ end
104
+ end
105
+