monolens 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: fded1da33ff2b2e68f5c0ec9177d77fed38d12576a7a505e7e50d25dd8c356c8
4
- data.tar.gz: ebcc624d96c03a82b1939a9cd9d76c0902fd170510ca93a670b5fbf02e477f08
2
+ SHA1:
3
+ metadata.gz: 704980b0a35ef47c4ea022a0b2bf39922966a11d
4
+ data.tar.gz: 0a032437e56dbd4d2214ba7b435120ceceb62709
5
5
  SHA512:
6
- metadata.gz: 508ede1d192eb3cc5d5f9a359cb18f627e44b5cdbd4d9c89bc89c90b52787b96061efcd95a01a4855f7477213e33aa56a7178c9afe4ccf16124af9155d54af1d
7
- data.tar.gz: 82138ad714e74d73c880eff43423d01546cfdf59569913d7d20734460dfc6ad08bab6ac7e4e8ae760a3a9de7884716297bd2433bd2a7c88f109d7fbc400b0fbd
6
+ metadata.gz: 2a580e3564f5bd208273e16c0c1da4c261917d371e0e6236bab873920b6a2372b7f36e5a085654f8f2ff79490f762cfeb68a8d38a24265d8cd4da5e983389f6e
7
+ data.tar.gz: '0229785824434dd14c87581f0060a784b623e3154b514bea8548d7cce94bb35563ceb6fdbc45146a883c4ca476c1c329b9adbd6a329ab1382f197377709170a6'
data/README.md CHANGED
@@ -37,13 +37,13 @@ The following monolens file, say `lens.yml`
37
37
  ---
38
38
  version: 1.0
39
39
  lenses:
40
- - map:
41
- - object.transform
40
+ - array.map:
41
+ - object.transform:
42
42
  status:
43
43
  - str.upcase
44
44
  body:
45
45
  - str.strip
46
- - object.rename
46
+ - object.rename:
47
47
  body: description
48
48
  ```
49
49
 
@@ -76,10 +76,11 @@ result = lens.call(input)
76
76
  ## Available lenses
77
77
 
78
78
  ```
79
- core.map - Apply a lens to each member of an Array
80
79
  core.chain - Applies a chain of lenses to an input value
80
+ core.mapping - Converts the input value via a key:value mapping
81
81
 
82
82
  str.strip - Remove leading and trailing spaces of an input string
83
+ str.split - Splits the input string as an array
83
84
  str.downcase - Converts the input string to lowercase
84
85
  str.upcase - Converts the input string to uppercase
85
86
 
@@ -89,11 +90,14 @@ object.rename - Rename some keys of the input object
89
90
  object.transform - Applies specific lenses to specific values of the input object
90
91
  object.keys - Applies a lens to all keys of the input object
91
92
  object.values - Applies a lens to all values of the input object
93
+ object.select - Builds an object by selecting key/values from the input object
92
94
 
93
95
  coerce.date - Coerces the input value to a date
94
96
  coerce.datetime - Coerces the input value to a datetime
95
97
 
96
98
  array.compact - Removes null from the input array
99
+ array.join - Joins values of the input array as a string
100
+ array.map - Apply a lens to each member of an Array
97
101
  ```
98
102
 
99
103
  ## Public API
@@ -0,0 +1,13 @@
1
+ module Monolens
2
+ module Array
3
+ class Join
4
+ include Lens
5
+
6
+ def call(arg, *rest)
7
+ is_array!(arg)
8
+
9
+ arg.join(option(:separator, ' '))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  module Monolens
2
- module Core
2
+ module Array
3
3
  class Map
4
4
  include Lens
5
5
 
@@ -5,7 +5,19 @@ module Monolens
5
5
  end
6
6
  module_function :compact
7
7
 
8
+ def join(options = {})
9
+ Join.new(options)
10
+ end
11
+ module_function :join
12
+
13
+ def map(lens)
14
+ Map.new(lens)
15
+ end
16
+ module_function :map
17
+
8
18
  Monolens.define_namespace 'array', self
9
19
  end
10
20
  end
11
21
  require_relative 'array/compact'
22
+ require_relative 'array/join'
23
+ require_relative 'array/map'
@@ -0,0 +1,15 @@
1
+ module Monolens
2
+ module Core
3
+ class Mapping
4
+ include Lens
5
+
6
+ def call(arg, *rest)
7
+ option(:values, {}).fetch(arg) do
8
+ raise LensError if option(:fail_if_missing)
9
+
10
+ option(:default)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/monolens/core.rb CHANGED
@@ -5,13 +5,13 @@ module Monolens
5
5
  end
6
6
  module_function :chain
7
7
 
8
- def map(lens)
9
- Map.new(lens)
8
+ def mapping(options)
9
+ Mapping.new(options)
10
10
  end
11
- module_function :map
11
+ module_function :mapping
12
12
 
13
13
  Monolens.define_namespace 'core', self
14
14
  end
15
15
  end
16
16
  require_relative 'core/chain'
17
- require_relative 'core/map'
17
+ require_relative 'core/mapping'
data/lib/monolens/lens.rb CHANGED
@@ -12,11 +12,18 @@ module Monolens
12
12
  [ attr, arg[attr] ]
13
13
  elsif arg.key?(attr_s = attr.to_s)
14
14
  [ attr_s, arg[attr_s] ]
15
+ elsif arg.key?(attr_sym = attr.to_sym)
16
+ [ attr_sym, arg[attr_sym] ]
15
17
  else
16
18
  [ attr, nil ]
17
19
  end
18
20
  end
19
21
 
22
+ def option(name, default = nil)
23
+ _, option = fetch_on(name, options)
24
+ option.nil? ? default : option
25
+ end
26
+
20
27
  def is_string!(arg)
21
28
  return if arg.is_a?(::String)
22
29
 
@@ -0,0 +1,30 @@
1
+ module Monolens
2
+ module Object
3
+ class Select
4
+ include Lens
5
+
6
+ def initialize(selection)
7
+ super({})
8
+ @selection = selection
9
+ end
10
+
11
+ def call(arg, *rest)
12
+ is_hash!(arg)
13
+
14
+ result = {}
15
+ @selection.each_pair do |new_attr, selector|
16
+ is_array = selector.is_a?(::Array)
17
+ is_symbol = false
18
+ values = Array(selector).map do |old_attr|
19
+ actual, fetched = fetch_on(old_attr, arg)
20
+ is_symbol ||= actual.is_a?(Symbol)
21
+ fetched
22
+ end
23
+ new_attr = is_symbol ? new_attr.to_sym : new_attr.to_s
24
+ result[new_attr] = is_array ? values : values.first
25
+ end
26
+ result
27
+ end
28
+ end
29
+ end
30
+ end
@@ -20,6 +20,11 @@ module Monolens
20
20
  end
21
21
  module_function :values
22
22
 
23
+ def select(lens)
24
+ Select.new(lens)
25
+ end
26
+ module_function :select
27
+
23
28
  Monolens.define_namespace 'object', self
24
29
  end
25
30
  end
@@ -27,3 +32,4 @@ require_relative 'object/rename'
27
32
  require_relative 'object/transform'
28
33
  require_relative 'object/keys'
29
34
  require_relative 'object/values'
35
+ require_relative 'object/select'
@@ -0,0 +1,14 @@
1
+ module Monolens
2
+ module Str
3
+ class Split
4
+ include Lens
5
+
6
+ def call(arg, *rest)
7
+ is_string!(arg)
8
+
9
+ sep = option(:separator)
10
+ sep ? arg.split(sep) : arg.split
11
+ end
12
+ end
13
+ end
14
+ end
data/lib/monolens/str.rb CHANGED
@@ -1,23 +1,29 @@
1
1
  module Monolens
2
2
  module Str
3
+ def downcase(options = {})
4
+ Downcase.new(options)
5
+ end
6
+ module_function :downcase
7
+
3
8
  def strip(options = {})
4
9
  Strip.new(options)
5
10
  end
6
11
  module_function :strip
7
12
 
13
+ def split(options = {})
14
+ Split.new(options)
15
+ end
16
+ module_function :split
17
+
8
18
  def upcase(options = {})
9
19
  Upcase.new(options)
10
20
  end
11
21
  module_function :upcase
12
22
 
13
- def downcase(options = {})
14
- Downcase.new(options)
15
- end
16
- module_function :downcase
17
-
18
23
  Monolens.define_namespace 'str', self
19
24
  end
20
25
  end
26
+ require_relative 'str/downcase'
21
27
  require_relative 'str/strip'
28
+ require_relative 'str/split'
22
29
  require_relative 'str/upcase'
23
- require_relative 'str/downcase'
@@ -1,7 +1,7 @@
1
1
  module Monolens
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 1
4
+ MINOR = 2
5
5
  TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
data/lib/monolens.rb CHANGED
@@ -24,7 +24,7 @@ module Monolens
24
24
  end
25
25
 
26
26
  def load_yaml(yaml)
27
- Monolens::File.new(YAML.load(yaml))
27
+ Monolens::File.new(YAML.safe_load(yaml))
28
28
  end
29
29
 
30
30
  def lens(arg)
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'array.compact' do
4
+ subject do
5
+ Monolens.lens('array.compact')
6
+ end
7
+
8
+ it 'removes nils' do
9
+ expect(subject.call([nil, 'notnil'])).to eql(['notnil'])
10
+ end
11
+
12
+ it 'supports empty arrays' do
13
+ expect(subject.call([])).to eql([])
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'array.join' do
4
+ context 'when used without options' do
5
+ subject do
6
+ Monolens.lens('array.join')
7
+ end
8
+
9
+ it 'joins values with spaces' do
10
+ expect(subject.call(['hello', 'world'])).to eql('hello world')
11
+ end
12
+
13
+ it 'supports empty arrays' do
14
+ expect(subject.call([])).to eql('')
15
+ end
16
+ end
17
+
18
+ context 'when specifying the separator' do
19
+ subject do
20
+ Monolens.lens('array.join' => { separator: ', ' })
21
+ end
22
+
23
+ it 'joins values with it' do
24
+ expect(subject.call(['hello', 'world'])).to eql('hello, world')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'array.map' do
4
+ subject do
5
+ Monolens.lens('array.map' => 'str.upcase')
6
+ end
7
+
8
+ it 'joins values with spaces' do
9
+ input = ['hello', 'world']
10
+ expected = ['HELLO', 'WORLD']
11
+ expect(subject.call(input)).to eql(expected)
12
+ end
13
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'core.mapping' do
4
+ let(:mapping) do
5
+ { 'values' => { 'todo' => 'open' }}
6
+ end
7
+
8
+ context 'with default options' do
9
+ subject do
10
+ Monolens.lens('core.mapping' => mapping)
11
+ end
12
+
13
+ it 'replaces the value by its mapped' do
14
+ expect(subject.call('todo')).to eql('open')
15
+ end
16
+
17
+ it 'returns nil if not found' do
18
+ expect(subject.call('nosuchone')).to eql(nil)
19
+ end
20
+ end
21
+
22
+ context 'specifying a default value' do
23
+ subject do
24
+ Monolens.lens('core.mapping' => mapping.merge('default' => 'foo'))
25
+ end
26
+
27
+ it 'replaces the value by its mapped' do
28
+ expect(subject.call('todo')).to eql('open')
29
+ end
30
+
31
+ it 'returns the default if not found' do
32
+ expect(subject.call('nosuchone')).to eql('foo')
33
+ end
34
+ end
35
+
36
+ context 'lets raise if not found' do
37
+ subject do
38
+ Monolens.lens('core.mapping' => mapping.merge('fail_if_missing' => true))
39
+ end
40
+
41
+ it 'replaces the value by its mapped' do
42
+ expect(subject.call('todo')).to eql('open')
43
+ end
44
+
45
+ it 'raises if not found' do
46
+ expect {
47
+ subject.call('nosuchone')
48
+ }.to raise_error(Monolens::LensError)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'object.select' do
4
+ context 'when using symbols in the definition' do
5
+ subject do
6
+ Monolens.lens('object.select' => {
7
+ name: [:firstname, :lastname],
8
+ status: :priority
9
+ })
10
+ end
11
+
12
+ it 'works as expected' do
13
+ input = {
14
+ firstname: 'Bernard',
15
+ lastname: 'Lambeau',
16
+ priority: 12
17
+ }
18
+ expected = {
19
+ name: ['Bernard', 'Lambeau'],
20
+ status: 12
21
+ }
22
+ expect(subject.call(input)).to eql(expected)
23
+ end
24
+
25
+ it 'works as with Strings' do
26
+ input = {
27
+ 'firstname' => 'Bernard',
28
+ 'lastname' => 'Lambeau',
29
+ 'priority' => 12
30
+ }
31
+ expected = {
32
+ 'name' => ['Bernard', 'Lambeau'],
33
+ 'status' => 12
34
+ }
35
+ expect(subject.call(input)).to eql(expected)
36
+ end
37
+ end
38
+
39
+ context 'when using strings in the definition' do
40
+ subject do
41
+ Monolens.lens('object.select' => {
42
+ 'name' => ['firstname', 'lastname'],
43
+ 'status' => 'priority'
44
+ })
45
+ end
46
+
47
+ it 'works as expected with Symbols' do
48
+ input = {
49
+ firstname: 'Bernard',
50
+ lastname: 'Lambeau',
51
+ priority: 12
52
+ }
53
+ expected = {
54
+ name: ['Bernard', 'Lambeau'],
55
+ status: 12
56
+ }
57
+ expect(subject.call(input)).to eql(expected)
58
+ end
59
+
60
+ it 'works with Strings' do
61
+ input = {
62
+ 'firstname' => 'Bernard',
63
+ 'lastname' => 'Lambeau',
64
+ 'priority' => 12
65
+ }
66
+ expected = {
67
+ 'name' => ['Bernard', 'Lambeau'],
68
+ 'status' => 12
69
+ }
70
+ expect(subject.call(input)).to eql(expected)
71
+ end
72
+ end
73
+ end
@@ -17,7 +17,7 @@ describe Monolens, 'skip.null' do
17
17
 
18
18
  context 'when used in a Map' do
19
19
  subject do
20
- Monolens.lens('map' => ['skip.null', 'str.upcase'])
20
+ Monolens.lens('array.map' => ['skip.null', 'str.upcase'])
21
21
  end
22
22
 
23
23
  it 'maps nils' do
@@ -27,7 +27,7 @@ describe Monolens, 'skip.null' do
27
27
 
28
28
  context 'when used in a Map, but we want no nils' do
29
29
  subject do
30
- Monolens.lens(['array.compact', { 'map' => ['skip.null', 'str.upcase'] }])
30
+ Monolens.lens(['array.compact', { 'array.map' => ['skip.null', 'str.upcase'] }])
31
31
  end
32
32
 
33
33
  it 'works' do
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.downcase' do
4
+ subject do
5
+ Monolens.lens('str.downcase')
6
+ end
7
+
8
+ it 'converts to lowercase' do
9
+ input = 'FOO'
10
+ expected = 'foo'
11
+ expect(subject.call(input)).to eql(expected)
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.split' do
4
+ context 'without options' do
5
+ subject do
6
+ Monolens.lens('str.split')
7
+ end
8
+
9
+ it 'splits a string as an array using space separator' do
10
+ input = 'foo bar'
11
+ expected = ['foo', 'bar']
12
+ expect(subject.call(input)).to eql(expected)
13
+ end
14
+
15
+ it 'is greedy on spaces and splits on carriage returns and tabs' do
16
+ input = "foo bar\nbaz\tboz"
17
+ expected = ['foo', 'bar', 'baz', 'boz']
18
+ expect(subject.call(input)).to eql(expected)
19
+ end
20
+ end
21
+
22
+ context 'when specifying the separator' do
23
+ subject do
24
+ Monolens.lens('str.split' => { separator: ',' })
25
+ end
26
+
27
+ it 'uses it' do
28
+ input = 'foo,bar'
29
+ expected = ['foo', 'bar']
30
+ expect(subject.call(input)).to eql(expected)
31
+ end
32
+
33
+ it 'does not make whitespace magic' do
34
+ input = "foo, bar"
35
+ expected = ['foo', ' bar']
36
+ expect(subject.call(input)).to eql(expected)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.strip' do
4
+ subject do
5
+ Monolens.lens('str.strip')
6
+ end
7
+
8
+ it 'strips leading and trailing spaces' do
9
+ input = ' foo '
10
+ expected = 'foo'
11
+ expect(subject.call(input)).to eql(expected)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.upcase' do
4
+ subject do
5
+ Monolens.lens('str.upcase')
6
+ end
7
+
8
+ it 'converts to uppercase' do
9
+ input = 'foo'
10
+ expected = 'FOO'
11
+ expect(subject.call(input)).to eql(expected)
12
+ end
13
+ end
data/spec/test_readme.rb CHANGED
@@ -21,7 +21,7 @@ describe "What's said in README" do
21
21
  ---
22
22
  version: 1.0
23
23
  lenses:
24
- - map:
24
+ - array.map:
25
25
  - object.transform:
26
26
  status:
27
27
  - str.upcase
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monolens
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-04 00:00:00.000000000 Z
11
+ date: 2022-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -65,38 +65,50 @@ files:
65
65
  - lib/monolens.rb
66
66
  - lib/monolens/array.rb
67
67
  - lib/monolens/array/compact.rb
68
+ - lib/monolens/array/join.rb
69
+ - lib/monolens/array/map.rb
68
70
  - lib/monolens/coerce.rb
69
71
  - lib/monolens/coerce/date.rb
70
72
  - lib/monolens/coerce/date_time.rb
71
73
  - lib/monolens/core.rb
72
74
  - lib/monolens/core/chain.rb
73
- - lib/monolens/core/map.rb
75
+ - lib/monolens/core/mapping.rb
74
76
  - lib/monolens/error.rb
75
77
  - lib/monolens/file.rb
76
78
  - lib/monolens/lens.rb
77
79
  - lib/monolens/object.rb
78
80
  - lib/monolens/object/keys.rb
79
81
  - lib/monolens/object/rename.rb
82
+ - lib/monolens/object/select.rb
80
83
  - lib/monolens/object/transform.rb
81
84
  - lib/monolens/object/values.rb
82
85
  - lib/monolens/skip.rb
83
86
  - lib/monolens/skip/null.rb
84
87
  - lib/monolens/str.rb
85
88
  - lib/monolens/str/downcase.rb
89
+ - lib/monolens/str/split.rb
86
90
  - lib/monolens/str/strip.rb
87
91
  - lib/monolens/str/upcase.rb
88
92
  - lib/monolens/version.rb
89
93
  - spec/fixtures/coerce.yml
90
94
  - spec/fixtures/simple.yml
91
95
  - spec/fixtures/transform.yml
96
+ - spec/monolens/array/test_compact.rb
97
+ - spec/monolens/array/test_join.rb
98
+ - spec/monolens/array/test_map.rb
92
99
  - spec/monolens/coerce/test_date.rb
93
100
  - spec/monolens/coerce/test_datetime.rb
94
- - spec/monolens/core/test_map.rb
101
+ - spec/monolens/core/test_mapping.rb
95
102
  - spec/monolens/object/test_keys.rb
96
103
  - spec/monolens/object/test_rename.rb
104
+ - spec/monolens/object/test_select.rb
97
105
  - spec/monolens/object/test_transform.rb
98
106
  - spec/monolens/object/test_values.rb
99
107
  - spec/monolens/skip/test_null.rb
108
+ - spec/monolens/str/test_downcase.rb
109
+ - spec/monolens/str/test_split.rb
110
+ - spec/monolens/str/test_strip.rb
111
+ - spec/monolens/str/test_upcase.rb
100
112
  - spec/monolens/test_lens.rb
101
113
  - spec/spec_helper.rb
102
114
  - spec/test_monolens.rb
@@ -122,7 +134,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
134
  - !ruby/object:Gem::Version
123
135
  version: '0'
124
136
  requirements: []
125
- rubygems_version: 3.1.4
137
+ rubyforge_project:
138
+ rubygems_version: 2.6.14.4
126
139
  signing_key:
127
140
  specification_version: 4
128
141
  summary: Data transformations inspired by Cambria lenses
@@ -1,11 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Monolens, 'map' do
4
- subject do
5
- Monolens.lens('map' => ['str.upcase'])
6
- end
7
-
8
- it 'maps as expected' do
9
- expect(subject.call(['foo','bar'])).to eql(['FOO','BAR'])
10
- end
11
- end