monolens 0.5.3 → 0.6.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 (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