monolens 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -85
  3. data/lib/monolens/command.rb +111 -14
  4. data/lib/monolens/error.rb +2 -0
  5. data/lib/monolens/file.rb +6 -0
  6. data/lib/monolens/jsonpath.rb +76 -0
  7. data/lib/monolens/lens/options.rb +26 -12
  8. data/lib/monolens/lens/signature/missing.rb +11 -0
  9. data/lib/monolens/lens/signature.rb +60 -0
  10. data/lib/monolens/lens.rb +25 -4
  11. data/lib/monolens/macros.rb +28 -0
  12. data/lib/monolens/namespace.rb +11 -0
  13. data/lib/monolens/registry.rb +77 -0
  14. data/lib/monolens/{array → stdlib/array}/compact.rb +2 -0
  15. data/lib/monolens/{array → stdlib/array}/join.rb +4 -0
  16. data/lib/monolens/{array → stdlib/array}/map.rb +13 -19
  17. data/lib/monolens/{array.rb → stdlib/array.rb} +8 -6
  18. data/lib/monolens/{check → stdlib/check}/not_empty.rb +4 -0
  19. data/lib/monolens/{check.rb → stdlib/check.rb} +4 -2
  20. data/lib/monolens/{coerce → stdlib/coerce}/date.rb +5 -0
  21. data/lib/monolens/{coerce → stdlib/coerce}/date_time.rb +6 -1
  22. data/lib/monolens/{coerce → stdlib/coerce}/integer.rb +4 -0
  23. data/lib/monolens/{coerce → stdlib/coerce}/string.rb +2 -0
  24. data/lib/monolens/{coerce.rb → stdlib/coerce.rb} +10 -8
  25. data/lib/monolens/{core → stdlib/core}/chain.rb +5 -3
  26. data/lib/monolens/{core → stdlib/core}/dig.rb +5 -0
  27. data/lib/monolens/stdlib/core/literal.rb +68 -0
  28. data/lib/monolens/{core → stdlib/core}/mapping.rb +15 -5
  29. data/lib/monolens/{core.rb → stdlib/core.rb} +10 -8
  30. data/lib/monolens/stdlib/object/allbut.rb +22 -0
  31. data/lib/monolens/{object → stdlib/object}/extend.rb +10 -5
  32. data/lib/monolens/{object → stdlib/object}/keys.rb +4 -0
  33. data/lib/monolens/stdlib/object/merge.rb +56 -0
  34. data/lib/monolens/{object → stdlib/object}/rename.rb +5 -1
  35. data/lib/monolens/{object → stdlib/object}/select.rb +9 -0
  36. data/lib/monolens/{object → stdlib/object}/transform.rb +8 -3
  37. data/lib/monolens/{object → stdlib/object}/values.rb +9 -4
  38. data/lib/monolens/stdlib/object.rb +55 -0
  39. data/lib/monolens/{skip → stdlib/skip}/null.rb +2 -0
  40. data/lib/monolens/{skip.rb → stdlib/skip.rb} +4 -2
  41. data/lib/monolens/{str → stdlib/str}/downcase.rb +2 -0
  42. data/lib/monolens/{str → stdlib/str}/split.rb +5 -1
  43. data/lib/monolens/{str → stdlib/str}/strip.rb +2 -0
  44. data/lib/monolens/{str → stdlib/str}/upcase.rb +2 -0
  45. data/lib/monolens/{str.rb → stdlib/str.rb} +10 -8
  46. data/lib/monolens/stdlib.rb +7 -0
  47. data/lib/monolens/type/any.rb +39 -0
  48. data/lib/monolens/type/array.rb +27 -0
  49. data/lib/monolens/type/boolean.rb +17 -0
  50. data/lib/monolens/type/callback.rb +17 -0
  51. data/lib/monolens/type/coercible.rb +10 -0
  52. data/lib/monolens/type/diggable.rb +9 -0
  53. data/lib/monolens/type/emptyable.rb +9 -0
  54. data/lib/monolens/type/integer.rb +18 -0
  55. data/lib/monolens/type/lenses.rb +17 -0
  56. data/lib/monolens/type/map.rb +30 -0
  57. data/lib/monolens/type/object.rb +17 -0
  58. data/lib/monolens/type/responding.rb +25 -0
  59. data/lib/monolens/type/strategy.rb +56 -0
  60. data/lib/monolens/type/string.rb +18 -0
  61. data/lib/monolens/type/symbol.rb +20 -0
  62. data/lib/monolens/type.rb +33 -0
  63. data/lib/monolens/version.rb +2 -2
  64. data/lib/monolens.rb +22 -66
  65. data/spec/fixtures/macro.yml +13 -0
  66. data/spec/fixtures/recursive.yml +15 -0
  67. data/spec/monolens/command/literal.yml +2 -0
  68. data/spec/monolens/command/literal2.yml +2 -0
  69. data/spec/monolens/command/upcase.lens.yml +4 -0
  70. data/spec/monolens/lens/test_options.rb +2 -14
  71. data/spec/monolens/lens/test_signature.rb +38 -0
  72. data/spec/monolens/{array → stdlib/array}/test_compact.rb +8 -0
  73. data/spec/monolens/{array → stdlib/array}/test_join.rb +0 -0
  74. data/spec/monolens/{array → stdlib/array}/test_map.rb +15 -0
  75. data/spec/monolens/{check → stdlib/check}/test_not_empty.rb +0 -0
  76. data/spec/monolens/{coerce → stdlib/coerce}/test_date.rb +0 -0
  77. data/spec/monolens/{coerce → stdlib/coerce}/test_datetime.rb +1 -1
  78. data/spec/monolens/{coerce → stdlib/coerce}/test_integer.rb +0 -0
  79. data/spec/monolens/{coerce → stdlib/coerce}/test_string.rb +0 -0
  80. data/spec/monolens/{core → stdlib/core}/test_dig.rb +0 -0
  81. data/spec/monolens/stdlib/core/test_literal.rb +73 -0
  82. data/spec/monolens/{core → stdlib/core}/test_mapping.rb +37 -1
  83. data/spec/monolens/stdlib/object/test_allbut.rb +31 -0
  84. data/spec/monolens/{object → stdlib/object}/test_extend.rb +0 -0
  85. data/spec/monolens/{object → stdlib/object}/test_keys.rb +0 -0
  86. data/spec/monolens/stdlib/object/test_merge.rb +133 -0
  87. data/spec/monolens/{object → stdlib/object}/test_rename.rb +0 -0
  88. data/spec/monolens/{object → stdlib/object}/test_select.rb +0 -0
  89. data/spec/monolens/{object → stdlib/object}/test_transform.rb +0 -0
  90. data/spec/monolens/{object → stdlib/object}/test_values.rb +0 -0
  91. data/spec/monolens/{skip → stdlib/skip}/test_null.rb +0 -0
  92. data/spec/monolens/{str → stdlib/str}/test_downcase.rb +0 -0
  93. data/spec/monolens/{str → stdlib/str}/test_split.rb +0 -0
  94. data/spec/monolens/{str → stdlib/str}/test_strip.rb +0 -0
  95. data/spec/monolens/{str → stdlib/str}/test_upcase.rb +0 -0
  96. data/spec/monolens/test_command.rb +145 -0
  97. data/spec/monolens/test_error_traceability.rb +1 -1
  98. data/spec/monolens/test_jsonpath.rb +88 -0
  99. data/spec/monolens/test_lens.rb +1 -1
  100. data/spec/test_documentation.rb +52 -0
  101. data/spec/test_monolens.rb +20 -0
  102. data/tasks/test.rake +1 -1
  103. metadata +91 -55
  104. data/lib/monolens/core/literal.rb +0 -11
  105. data/lib/monolens/object.rb +0 -41
  106. data/spec/monolens/core/test_literal.rb +0 -13
data/lib/monolens.rb CHANGED
@@ -1,82 +1,38 @@
1
1
  require 'yaml'
2
+ require 'date'
3
+ require 'ostruct'
4
+
2
5
  module Monolens
3
- NAMESPACES = { }
6
+ require_relative 'monolens/version'
7
+ require_relative 'monolens/error'
8
+ require_relative 'monolens/error_handler'
9
+ require_relative 'monolens/type'
10
+ require_relative 'monolens/jsonpath'
11
+ require_relative 'monolens/lens'
12
+ require_relative 'monolens/namespace'
13
+ require_relative 'monolens/registry'
14
+ require_relative 'monolens/macros'
15
+
16
+ STDLIB = Registry.new
4
17
 
5
18
  class << self
6
- require_relative 'monolens/version'
7
- require_relative 'monolens/error'
8
- require_relative 'monolens/error_handler'
9
- require_relative 'monolens/lens'
10
-
11
19
  def define_namespace(name, impl_module)
12
- NAMESPACES[name] = impl_module
20
+ STDLIB.define_namespace(name, impl_module)
13
21
  end
14
22
 
15
- require_relative 'monolens/file'
16
- require_relative 'monolens/core'
17
- require_relative 'monolens/skip'
18
- require_relative 'monolens/str'
19
- require_relative 'monolens/array'
20
- require_relative 'monolens/object'
21
- require_relative 'monolens/coerce'
22
- require_relative 'monolens/check'
23
-
24
23
  def load_file(file)
25
- Monolens::File.new(YAML.safe_load(::File.read(file)))
24
+ STDLIB.load_file(file)
26
25
  end
27
26
 
28
- def load_yaml(yaml)
29
- Monolens::File.new(YAML.safe_load(yaml))
27
+ def load_yaml(yaml_str)
28
+ STDLIB.load_yaml(yaml_str)
30
29
  end
31
30
 
32
31
  def lens(arg)
33
- case arg
34
- when Lens then arg
35
- when ::Array then chain(arg)
36
- when ::String, ::Symbol then leaf_lens(arg)
37
- when ::Hash then hash_lens(arg)
38
- else
39
- raise Error, "No such lens #{arg} (#{arg.class})"
40
- end
41
- end
42
-
43
- def chain(lenses)
44
- Core::Chain.new(lenses.map{|l| lens(l) })
45
- end
46
- private :chain
47
-
48
- def file_lens(arg)
49
- File.new(arg)
32
+ STDLIB.lens(arg)
50
33
  end
51
-
52
- def leaf_lens(arg)
53
- namespace_name, lens_name = arg.to_s.split('.')
54
- factor_lens(namespace_name, lens_name, {})
55
- end
56
- private :leaf_lens
57
-
58
- def hash_lens(arg)
59
- return file_lens(arg) if arg['version'] || arg[:version]
60
- raise "Invalid lens #{arg}" unless arg.size == 1
61
-
62
- name, options = arg.to_a.first
63
- namespace_name, lens_name = if name =~ /^[a-z]+\.[a-z][a-zA-Z]+$/
64
- name.to_s.split('.')
65
- else
66
- ['core', name]
67
- end
68
- factor_lens(namespace_name, lens_name, options)
69
- end
70
- private :hash_lens
71
-
72
- def factor_lens(namespace_name, lens_name, options)
73
- namespace = NAMESPACES[namespace_name]
74
- if namespace&.private_method_defined?(lens_name, false)
75
- namespace.send(lens_name, options)
76
- else
77
- raise Error, "No such lens #{[namespace_name, lens_name].join('.')}"
78
- end
79
- end
80
- private :factor_lens
81
34
  end
35
+
36
+ require_relative 'monolens/file'
37
+ require_relative 'monolens/stdlib'
82
38
  end
@@ -0,0 +1,13 @@
1
+ ---
2
+ version: "1.0"
3
+ #
4
+ macros:
5
+ join_them:
6
+ - array.map:
7
+ - str.strip
8
+ - str.upcase
9
+ - array.join: { separator: <.separator }
10
+ #
11
+ lenses:
12
+ - join_them:
13
+ separator: ', '
@@ -0,0 +1,15 @@
1
+ ---
2
+ version: "1.0"
3
+ #
4
+ macros:
5
+ join_them:
6
+ - array.map:
7
+ - str.strip
8
+ - str.upcase
9
+ - array.join: { separator: <.separator }
10
+ ## This is strictly forbidden
11
+ - join_them: { separator: ',' }
12
+ #
13
+ lenses:
14
+ - join_them:
15
+ separator: ', '
@@ -0,0 +1,2 @@
1
+ ---
2
+ $([0]) $([1])
@@ -0,0 +1,2 @@
1
+ ---
2
+ $
@@ -0,0 +1,4 @@
1
+ ---
2
+ version: '1.0'
3
+ lenses:
4
+ - str.upcase
@@ -4,7 +4,7 @@ module Monolens
4
4
  module Lens
5
5
  describe Options do
6
6
  subject do
7
- Options.new(input)
7
+ Options.new(input, STDLIB, Signature::MISSING)
8
8
  end
9
9
 
10
10
  describe 'initialize' do
@@ -28,19 +28,7 @@ module Monolens
28
28
  it 'converts it to lenses' do
29
29
  expect(subject.to_h.keys).to eql([:lenses])
30
30
  lenses = subject.to_h[:lenses]
31
- expect(lenses).to be_a(Core::Chain)
32
- end
33
- end
34
-
35
- context('when used with a String') do
36
- let(:input) do
37
- 'str.strip'
38
- end
39
-
40
- it 'converts it to lenses' do
41
- expect(subject.to_h.keys).to eql([:lenses])
42
- lenses = subject.to_h[:lenses]
43
- expect(lenses).to be_a(Str::Strip)
31
+ expect(lenses).to eql(input)
44
32
  end
45
33
  end
46
34
  end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ module Monolens
4
+ module Lens
5
+ describe Signature do
6
+ let(:signature) do
7
+ Signature.new(Type::String, Type::String, {
8
+ separator: [Type::String, true],
9
+ help: [Type::String, false]
10
+ })
11
+ end
12
+
13
+ it 'is ok with valid options' do
14
+ expect {
15
+ signature.dress_options({ separator: ',' }, nil)
16
+ }.not_to raise_error
17
+ end
18
+
19
+ it 'detects a missing option' do
20
+ expect {
21
+ signature.dress_options({}, nil)
22
+ }.to raise_error(Error, /Missing option `separator`/)
23
+ end
24
+
25
+ it 'detects an extra option' do
26
+ expect {
27
+ signature.dress_options({ foo: 'bar' }, nil)
28
+ }.to raise_error(Error, /Invalid option `foo`/)
29
+ end
30
+
31
+ it 'detects an option of the wrong type' do
32
+ expect {
33
+ signature.dress_options({ separator: 12 }, nil)
34
+ }.to raise_error(Error, /Invalid option `separator`: Invalid string 12/)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -12,4 +12,12 @@ describe Monolens, 'array.compact' do
12
12
  it 'supports empty arrays' do
13
13
  expect(subject.call([])).to eql([])
14
14
  end
15
+
16
+ describe 'signature checking' do
17
+ it 'detects unexisting options' do
18
+ expect {
19
+ Monolens.lens('array.compact' => { foo: 'bar' })
20
+ }.to raise_error(Monolens::Error, /Invalid option `foo`/)
21
+ end
22
+ end
15
23
  end
File without changes
@@ -58,6 +58,21 @@ describe Monolens, 'array.map' do
58
58
  end
59
59
  end
60
60
 
61
+ context 'keeping on error' do
62
+ subject do
63
+ Monolens.lens('array.map' => {
64
+ on_error: 'keep',
65
+ lenses: [ 'str.upcase' ]
66
+ })
67
+ end
68
+
69
+ it 'skips errors' do
70
+ input = [12, 'world']
71
+ expected = [12, 'WORLD']
72
+ expect(subject.call(input)).to eql(expected)
73
+ end
74
+ end
75
+
61
76
  context 'on error with :handler' do
62
77
  subject do
63
78
  Monolens.lens('array.map' => {
@@ -35,7 +35,7 @@ describe Monolens, 'coerce.datetime' do
35
35
  end
36
36
 
37
37
  let(:timezone) do
38
- Object.new
38
+ Struct.new(:parse, :strptime).new(1, 2)
39
39
  end
40
40
 
41
41
  before do
File without changes
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, "core.literal" do
4
+ let(:lens) do
5
+ Monolens.lens('core.literal' => { defn: 'hello' })
6
+ end
7
+
8
+ it 'works' do
9
+ input = {}
10
+ expected = 'hello'
11
+ expect(lens.call(input)).to eql(expected)
12
+ end
13
+
14
+ context 'with a simple json path expressions' do
15
+ let(:lens) do
16
+ Monolens.lens('core.literal' => { defn: '$.foo' })
17
+ end
18
+
19
+ it 'works' do
20
+ input = { 'foo' => 'bar' }
21
+ expected = 'bar'
22
+ expect(lens.call(input)).to eql(expected)
23
+ end
24
+
25
+ it 'works with symbols too' do
26
+ input = { foo: 'bar' }
27
+ expected = 'bar'
28
+ expect(lens.call(input)).to eql(expected)
29
+ end
30
+ end
31
+
32
+ context 'with an object literal with json path expressions' do
33
+ let(:lens) do
34
+ Monolens.lens('core.literal' => {
35
+ defn: {
36
+ hobbies: [{
37
+ name: '$.foo'
38
+ }]
39
+ }
40
+ })
41
+ end
42
+
43
+ it 'works' do
44
+ input = { 'foo' => 'bar' }
45
+ expected = { hobbies: [{ name: 'bar' }] }
46
+ expect(lens.call(input)).to eql(expected)
47
+ end
48
+
49
+ it 'works with symbols too' do
50
+ input = { foo: 'bar' }
51
+ expected = { hobbies: [{ name: 'bar' }] }
52
+ expect(lens.call(input)).to eql(expected)
53
+ end
54
+ end
55
+
56
+ context 'changing the root symbol' do
57
+ let(:lens) do
58
+ Monolens.lens('core.literal' => {
59
+ defn: {
60
+ one: '<.foo',
61
+ interpolate: 'Hello <(.foo)'
62
+ },
63
+ jsonpath: { root_symbol: '<' }
64
+ })
65
+ end
66
+
67
+ it 'keeps working' do
68
+ input = { foo: 'bar' }
69
+ expected = { one: 'bar', interpolate: 'Hello bar' }
70
+ expect(lens.call(input)).to eql(expected)
71
+ end
72
+ end
73
+ end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Monolens, 'core.mapping' do
4
4
  let(:mapping) do
5
- { 'values' => { 'todo' => 'open' } }
5
+ { 'defn' => { 'todo' => 'open' } }
6
6
  end
7
7
 
8
8
  context 'with default options' do
@@ -49,6 +49,20 @@ describe Monolens, 'core.mapping' do
49
49
  end
50
50
  end
51
51
 
52
+ context 'on_missing: keep' do
53
+ subject do
54
+ Monolens.lens('core.mapping' => mapping.merge('on_missing' => 'keep'))
55
+ end
56
+
57
+ it 'replaces the value by its mapped' do
58
+ expect(subject.call('todo')).to eql('open')
59
+ end
60
+
61
+ it 'returns nil if missing' do
62
+ expect(subject.call('nosuchone')).to eql('nosuchone')
63
+ end
64
+ end
65
+
52
66
  context 'on_missing: fallback' do
53
67
  subject do
54
68
  Monolens.lens('core.mapping' => mapping.merge(
@@ -90,4 +104,26 @@ describe Monolens, 'core.mapping' do
90
104
  expect(subject.location).to eql([1])
91
105
  end
92
106
  end
107
+
108
+ context 'backward compatibility' do
109
+ let(:mapping) do
110
+ { 'values' => { 'todo' => 'open' } }
111
+ end
112
+
113
+ context 'with using values instead of defn' do
114
+ subject do
115
+ Monolens.lens('core.mapping' => mapping)
116
+ end
117
+
118
+ it 'replaces the value by its mapped' do
119
+ expect(subject.call('todo')).to eql('open')
120
+ end
121
+
122
+ it 'raises if not found' do
123
+ expect {
124
+ subject.call('nosuchone')
125
+ }.to raise_error(Monolens::LensError)
126
+ end
127
+ end
128
+ end
93
129
  end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'object.allbut' do
4
+ subject do
5
+ Monolens.lens('object.allbut' => { defn: [ :lastname, :city ] })
6
+ end
7
+
8
+ it 'works as expected' do
9
+ input = {
10
+ 'firstname' => 'Bernard',
11
+ 'lastname' => 'Lambeau',
12
+ 'city' => 'Brussels'
13
+ }
14
+ expected = {
15
+ 'firstname' => 'Bernard',
16
+ }
17
+ expect(subject.call(input)).to eql(expected)
18
+ end
19
+
20
+ it 'works as expected with Symbol keys' do
21
+ input = {
22
+ firstname: 'Bernard',
23
+ lastname: 'Lambeau',
24
+ city: 'Brussels'
25
+ }
26
+ expected = {
27
+ firstname: 'Bernard',
28
+ }
29
+ expect(subject.call(input)).to eql(expected)
30
+ end
31
+ end
@@ -0,0 +1,133 @@
1
+ describe Monolens, 'object.merge' do
2
+ subject do
3
+ Monolens.lens('object.merge' => {
4
+ priority: priority,
5
+ deep: deep,
6
+ defn: {
7
+ name: 'Monolens',
8
+ version: "1.0",
9
+ links: {
10
+ github: "https://github.com/enspirit/monolens"
11
+ },
12
+ }
13
+ }.compact)
14
+ end
15
+
16
+ let(:priority) do
17
+ nil
18
+ end
19
+
20
+ let(:deep) do
21
+ nil
22
+ end
23
+
24
+ describe 'with default options' do
25
+ it 'works as expected on a flat structure' do
26
+ input = {
27
+ version: "1.2",
28
+ extra: "foo"
29
+ }
30
+ expected = {
31
+ name: 'Monolens',
32
+ version: "1.0",
33
+ links: {
34
+ github: "https://github.com/enspirit/monolens"
35
+ },
36
+ extra: "foo"
37
+ }
38
+ expect(subject.call(input)).to eql(expected)
39
+ end
40
+
41
+ it 'does not apply a deep merge' do
42
+ input = {
43
+ links: {
44
+ owner: "https://enspirit.be/"
45
+ },
46
+ }
47
+ expected = {
48
+ name: 'Monolens',
49
+ version: "1.0",
50
+ links: {
51
+ github: "https://github.com/enspirit/monolens"
52
+ }
53
+ }
54
+ expect(subject.call(input)).to eql(expected)
55
+ end
56
+ end
57
+
58
+ describe 'priority: input' do
59
+ let(:priority) do
60
+ 'input'
61
+ end
62
+
63
+ it 'reverse the logic on shared keys' do
64
+ input = {
65
+ version: "1.2",
66
+ extra: "foo",
67
+ }
68
+ expected = {
69
+ name: 'Monolens',
70
+ version: "1.2",
71
+ links: {
72
+ github: "https://github.com/enspirit/monolens"
73
+ },
74
+ extra: "foo"
75
+ }
76
+ expect(subject.call(input)).to eql(expected)
77
+ end
78
+ end
79
+
80
+ describe 'deep: true' do
81
+ let(:deep) do
82
+ true
83
+ end
84
+
85
+ it 'applies a deep merge' do
86
+ input = {
87
+ version: "1.2",
88
+ links: {
89
+ owner: "https://enspirit.be/",
90
+ github: "https://github.com/enspirit/monolens/"
91
+ },
92
+ }
93
+ expected = {
94
+ name: 'Monolens',
95
+ version: "1.0",
96
+ links: {
97
+ owner: "https://enspirit.be/",
98
+ github: "https://github.com/enspirit/monolens"
99
+ }
100
+ }
101
+ expect(subject.call(input)).to eql(expected)
102
+ end
103
+ end
104
+
105
+ describe 'deep: true, priority: input' do
106
+ let(:deep) do
107
+ true
108
+ end
109
+
110
+ let(:priority) do
111
+ 'input'
112
+ end
113
+
114
+ it 'applies a deep merge' do
115
+ input = {
116
+ version: "1.2",
117
+ links: {
118
+ owner: "https://enspirit.be/",
119
+ github: "https://github.com/enspirit/monolens/"
120
+ },
121
+ }
122
+ expected = {
123
+ name: 'Monolens',
124
+ version: "1.2",
125
+ links: {
126
+ owner: "https://enspirit.be/",
127
+ github: "https://github.com/enspirit/monolens/"
128
+ }
129
+ }
130
+ expect(subject.call(input)).to eql(expected)
131
+ end
132
+ end
133
+ end
File without changes
File without changes
File without changes
File without changes
File without changes