monolens 0.3.0 → 0.5.1
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 +4 -4
- data/README.md +6 -0
- data/bin/monolens +11 -0
- data/lib/monolens/coerce/date.rb +2 -0
- data/lib/monolens/coerce/date_time.rb +13 -3
- data/lib/monolens/coerce/integer.rb +15 -0
- data/lib/monolens/coerce.rb +6 -0
- data/lib/monolens/command.rb +96 -0
- data/lib/monolens/core/dig.rb +52 -0
- data/lib/monolens/core/mapping.rb +22 -2
- data/lib/monolens/core.rb +6 -0
- data/lib/monolens/error_handler.rb +31 -0
- data/lib/monolens/lens/fetch_support.rb +0 -2
- data/lib/monolens/lens.rb +5 -5
- data/lib/monolens/object/extend.rb +53 -0
- data/lib/monolens/object/select.rb +54 -16
- data/lib/monolens/object/transform.rb +1 -1
- data/lib/monolens/object.rb +6 -0
- data/lib/monolens/version.rb +2 -2
- data/lib/monolens.rb +6 -0
- data/spec/monolens/coerce/test_date.rb +5 -0
- data/spec/monolens/coerce/test_datetime.rb +41 -3
- data/spec/monolens/coerce/test_integer.rb +46 -0
- data/spec/monolens/command/map-upcase.lens.yml +5 -0
- data/spec/monolens/command/names-with-null.json +5 -0
- data/spec/monolens/command/names.json +4 -0
- data/spec/monolens/command/robust-map-upcase.lens.yml +7 -0
- data/spec/monolens/core/test_dig.rb +78 -0
- data/spec/monolens/core/test_mapping.rb +29 -12
- data/spec/monolens/object/test_extend.rb +94 -0
- data/spec/monolens/object/test_select.rb +110 -2
- data/spec/monolens/test_command.rb +128 -0
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f60bac69139624ed4d81f0ae72e710b384ca42f
|
4
|
+
data.tar.gz: f69b94c2440bd260a61d7619f9883501fa5237d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e9cebb9122bac25f9787f8061242b69315bac0bfa5c27eb9cea4c22802060b8b3c7d28e82c536d25011b23756f14663f00030f75d1a8852fea688b5ac6a3a55
|
7
|
+
data.tar.gz: de6e9e3fedf61c3ef624d11a958ff2de6a5e3d8babd6b8d6f65175179e015e8eff77088bcf7c93e1d571aeeb97ea67b30b94a3b90ff65922c1acd2058b1bbe96
|
data/README.md
CHANGED
@@ -76,6 +76,7 @@ result = lens.call(input)
|
|
76
76
|
## Available lenses
|
77
77
|
|
78
78
|
```
|
79
|
+
core.dig - Extract from the input value (object or array) using a path.
|
79
80
|
core.chain - Applies a chain of lenses to an input value
|
80
81
|
core.mapping - Converts the input value via a key:value mapping
|
81
82
|
|
@@ -86,6 +87,7 @@ str.upcase - Converts the input string to uppercase
|
|
86
87
|
|
87
88
|
skip.null - Aborts the current lens transformation if nil
|
88
89
|
|
90
|
+
object.extend - Adds key/value(s) to the input object
|
89
91
|
object.rename - Rename some keys of the input object
|
90
92
|
object.transform - Applies specific lenses to specific values of the input object
|
91
93
|
object.keys - Applies a lens to all keys of the input object
|
@@ -94,6 +96,8 @@ object.select - Builds an object by selecting key/values from the input obje
|
|
94
96
|
|
95
97
|
coerce.date - Coerces the input value to a date
|
96
98
|
coerce.datetime - Coerces the input value to a datetime
|
99
|
+
coerce.string - Coerces the input value to a string (aka to_s)
|
100
|
+
coerce.integer - Coerces the input value to an integer
|
97
101
|
|
98
102
|
array.compact - Removes null from the input array
|
99
103
|
array.join - Joins values of the input array as a string
|
@@ -114,6 +118,8 @@ Anyway, the public interface will cover at least the following:
|
|
114
118
|
|
115
119
|
* Exception classes: `Monolens::Error`, `Monolens::LensError`
|
116
120
|
|
121
|
+
* bin/monolens, its args, options and general behavior
|
122
|
+
|
117
123
|
Everything else is condidered private and may change any time
|
118
124
|
(i.e. even on patch releases).
|
119
125
|
|
data/bin/monolens
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
retried = false
|
3
|
+
begin
|
4
|
+
require 'monolens'
|
5
|
+
require 'monolens/command'
|
6
|
+
rescue LoadError
|
7
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
8
|
+
to_retry, retried = !retried, true
|
9
|
+
to_retry ? retry : raise
|
10
|
+
end
|
11
|
+
Monolens::Command.call(ARGV)
|
data/lib/monolens/coerce/date.rb
CHANGED
@@ -10,6 +10,8 @@ module Monolens
|
|
10
10
|
]
|
11
11
|
|
12
12
|
def call(arg, world = {})
|
13
|
+
return arg if arg.is_a?(::DateTime)
|
14
|
+
|
13
15
|
is_string!(arg, world)
|
14
16
|
|
15
17
|
date = nil
|
@@ -28,12 +30,20 @@ module Monolens
|
|
28
30
|
fail!(first_error.message, world)
|
29
31
|
end
|
30
32
|
|
33
|
+
private
|
34
|
+
|
31
35
|
def strptime(arg, format = nil)
|
32
|
-
if format.nil?
|
33
|
-
|
36
|
+
parsed = if format.nil?
|
37
|
+
parser.parse(arg)
|
34
38
|
else
|
35
|
-
|
39
|
+
parser.strptime(arg, format)
|
36
40
|
end
|
41
|
+
parsed = parsed.to_datetime if parsed.respond_to?(:to_datetime)
|
42
|
+
parsed
|
43
|
+
end
|
44
|
+
|
45
|
+
def parser
|
46
|
+
option(:parser, ::DateTime)
|
37
47
|
end
|
38
48
|
end
|
39
49
|
end
|
data/lib/monolens/coerce.rb
CHANGED
@@ -5,6 +5,11 @@ module Monolens
|
|
5
5
|
end
|
6
6
|
module_function :date
|
7
7
|
|
8
|
+
def integer(options = {})
|
9
|
+
Integer.new(options)
|
10
|
+
end
|
11
|
+
module_function :integer
|
12
|
+
|
8
13
|
def datetime(options = {})
|
9
14
|
DateTime.new(options)
|
10
15
|
end
|
@@ -20,4 +25,5 @@ module Monolens
|
|
20
25
|
end
|
21
26
|
require_relative 'coerce/date'
|
22
27
|
require_relative 'coerce/date_time'
|
28
|
+
require_relative 'coerce/integer'
|
23
29
|
require_relative 'coerce/string'
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'date'
|
3
|
+
require 'json'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module Monolens
|
7
|
+
class Command
|
8
|
+
def initialize(argv, stdin, stdout, stderr)
|
9
|
+
@argv = argv
|
10
|
+
@stdin = stdin
|
11
|
+
@stdout = stdout
|
12
|
+
@stderr = stderr
|
13
|
+
@pretty = false
|
14
|
+
end
|
15
|
+
attr_reader :argv, :stdin, :stdout, :stderr
|
16
|
+
attr_reader :pretty
|
17
|
+
|
18
|
+
def self.call(argv, stdin = $stdin, stdout = $stdout, stderr = $stderr)
|
19
|
+
new(argv, stdin, stdout, stderr).call
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
lens, input = options.parse!(argv)
|
24
|
+
show_help_and_exit if lens.nil? || input.nil?
|
25
|
+
|
26
|
+
lens, input = read_file(lens), read_file(input)
|
27
|
+
error_handler = ErrorHandler.new
|
28
|
+
lens = Monolens.lens(lens)
|
29
|
+
result = lens.call(input, error_handler: error_handler)
|
30
|
+
|
31
|
+
unless error_handler.empty?
|
32
|
+
stderr.puts(error_handler.report)
|
33
|
+
end
|
34
|
+
|
35
|
+
if result
|
36
|
+
output = if pretty
|
37
|
+
JSON.pretty_generate(result)
|
38
|
+
else
|
39
|
+
result.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
stdout.puts output
|
43
|
+
end
|
44
|
+
rescue Monolens::LensError => ex
|
45
|
+
stderr.puts("[#{ex.location.join('/')}] #{ex.message}")
|
46
|
+
do_exit(-2)
|
47
|
+
end
|
48
|
+
|
49
|
+
def read_file(file)
|
50
|
+
case ::File.extname(file)
|
51
|
+
when /json$/
|
52
|
+
content = ::File.read(file)
|
53
|
+
JSON.parse(content)
|
54
|
+
when /ya?ml$/
|
55
|
+
content = ::File.read(file)
|
56
|
+
YAML.safe_load(content)
|
57
|
+
when /csv$/
|
58
|
+
require 'bmg'
|
59
|
+
Bmg.csv(file).to_a
|
60
|
+
when /xlsx?$/
|
61
|
+
require 'bmg'
|
62
|
+
Bmg.excel(file).to_a
|
63
|
+
else
|
64
|
+
fail!("Unable to use #{file}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def fail!(msg)
|
69
|
+
stderr.puts(msg)
|
70
|
+
do_exit(1)
|
71
|
+
end
|
72
|
+
|
73
|
+
def do_exit(status)
|
74
|
+
exit(status)
|
75
|
+
end
|
76
|
+
|
77
|
+
def show_help_and_exit
|
78
|
+
stdout.puts options
|
79
|
+
do_exit(0)
|
80
|
+
end
|
81
|
+
|
82
|
+
def options
|
83
|
+
@options ||= OptionParser.new do |opts|
|
84
|
+
opts.banner = 'Usage: monolens [options] LENS INPUT'
|
85
|
+
|
86
|
+
opts.on('--version', 'Show version and exit') do
|
87
|
+
stdout.puts "Monolens v#{VERSION} - (c) Enspirit #{Date.today.year}"
|
88
|
+
do_exit(0)
|
89
|
+
end
|
90
|
+
opts.on('-p', '--[no-]pretty', 'Show version and exit') do |pretty|
|
91
|
+
@pretty = pretty
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Core
|
3
|
+
class Dig
|
4
|
+
include Lens
|
5
|
+
|
6
|
+
def call(arg, world = {})
|
7
|
+
option(:defn, []).inject(arg) do |memo, part|
|
8
|
+
dig_on(part, memo, world)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def path
|
15
|
+
option(:defn, []).join('.')
|
16
|
+
end
|
17
|
+
|
18
|
+
def dig_on(attr, arg, world)
|
19
|
+
if arg.is_a?(::Array)
|
20
|
+
index = attr.to_i
|
21
|
+
on_missing(world) if index >= arg.size
|
22
|
+
arg[index]
|
23
|
+
elsif arg.is_a?(::Hash)
|
24
|
+
actual, value = fetch_on(attr, arg)
|
25
|
+
on_missing(world) unless actual
|
26
|
+
value
|
27
|
+
elsif arg
|
28
|
+
if attr.is_a?(::Integer)
|
29
|
+
is_array!(arg, world)
|
30
|
+
else
|
31
|
+
is_hash!(arg, world)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
on_missing(world)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_missing(world)
|
39
|
+
strategy = option(:on_missing, :fail)
|
40
|
+
case strategy.to_sym
|
41
|
+
when :fail
|
42
|
+
fail!("Unable to find #{path}", world)
|
43
|
+
when :null
|
44
|
+
nil
|
45
|
+
else
|
46
|
+
raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
private :on_missing
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -5,11 +5,31 @@ module Monolens
|
|
5
5
|
|
6
6
|
def call(arg, world = {})
|
7
7
|
option(:values, {}).fetch(arg) do
|
8
|
-
|
8
|
+
on_missing(arg, world)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
9
13
|
|
10
|
-
|
14
|
+
def on_missing(arg, world)
|
15
|
+
strategy = option(:on_missing, :fail)
|
16
|
+
case strategy.to_sym
|
17
|
+
when :fail
|
18
|
+
fail!("Unrecognized value `#{arg}`", world)
|
19
|
+
when :default
|
20
|
+
option(:default, nil)
|
21
|
+
when :null
|
22
|
+
nil
|
23
|
+
when :fallback
|
24
|
+
missing_fallback = ->(arg, world) do
|
25
|
+
raise Monolens::Error, "Unexpected missing fallback handler"
|
26
|
+
end
|
27
|
+
option(:fallback, missing_fallback).call(self, arg, world)
|
28
|
+
else
|
29
|
+
raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
|
11
30
|
end
|
12
31
|
end
|
32
|
+
private :on_missing
|
13
33
|
end
|
14
34
|
end
|
15
35
|
end
|
data/lib/monolens/core.rb
CHANGED
@@ -5,6 +5,11 @@ module Monolens
|
|
5
5
|
end
|
6
6
|
module_function :chain
|
7
7
|
|
8
|
+
def dig(options)
|
9
|
+
Dig.new(options)
|
10
|
+
end
|
11
|
+
module_function :dig
|
12
|
+
|
8
13
|
def mapping(options)
|
9
14
|
Mapping.new(options)
|
10
15
|
end
|
@@ -14,4 +19,5 @@ module Monolens
|
|
14
19
|
end
|
15
20
|
end
|
16
21
|
require_relative 'core/chain'
|
22
|
+
require_relative 'core/dig'
|
17
23
|
require_relative 'core/mapping'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Monolens
|
2
|
+
class ErrorHandler
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@errors = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(error)
|
10
|
+
@errors << error
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&bl)
|
14
|
+
@errors.each(&bl)
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
@errors.size
|
19
|
+
end
|
20
|
+
|
21
|
+
def empty?
|
22
|
+
@errors.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def report
|
26
|
+
@errors
|
27
|
+
.map{|err| "[#{err.location.join('/')}] #{err.message}" }
|
28
|
+
.join("\n")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/monolens/lens.rb
CHANGED
@@ -11,6 +11,11 @@ module Monolens
|
|
11
11
|
end
|
12
12
|
attr_reader :options
|
13
13
|
|
14
|
+
def fail!(msg, world)
|
15
|
+
location = world[:location]&.to_a || []
|
16
|
+
raise Monolens::LensError.new(msg, location)
|
17
|
+
end
|
18
|
+
|
14
19
|
protected
|
15
20
|
|
16
21
|
def option(name, default = nil)
|
@@ -58,10 +63,5 @@ module Monolens
|
|
58
63
|
|
59
64
|
fail!("Array expected, got #{arg.class}", world)
|
60
65
|
end
|
61
|
-
|
62
|
-
def fail!(msg, world)
|
63
|
-
location = world[:location]&.to_a || []
|
64
|
-
raise Monolens::LensError.new(msg, location)
|
65
|
-
end
|
66
66
|
end
|
67
67
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Object
|
3
|
+
class Extend
|
4
|
+
include Lens
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
super(options)
|
8
|
+
ts = option(:defn, {})
|
9
|
+
ts.each_pair do |k,v|
|
10
|
+
ts[k] = Monolens.lens(v)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(arg, world = {})
|
15
|
+
is_hash!(arg, world)
|
16
|
+
|
17
|
+
result = arg.dup
|
18
|
+
is_symbol = arg.keys.any?{|k| k.is_a?(Symbol) }
|
19
|
+
option(:defn, {}).each_pair do |attr, lens|
|
20
|
+
deeper(world, attr) do |w|
|
21
|
+
actual = is_symbol ? attr.to_sym : attr.to_s
|
22
|
+
begin
|
23
|
+
result[actual] = lens.call(arg, w)
|
24
|
+
rescue Monolens::LensError => ex
|
25
|
+
strategy = option(:on_error, :fail)
|
26
|
+
handle_error(strategy, ex, result, actual, world)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_error(strategy, ex, result, attr, world)
|
34
|
+
strategy = strategy.to_sym unless strategy.is_a?(::Array)
|
35
|
+
case strategy
|
36
|
+
when ::Array
|
37
|
+
strategy.each{|s| handle_error(s, ex, result, attr, world) }
|
38
|
+
when :handler
|
39
|
+
error_handler!(world).call(ex)
|
40
|
+
when :fail
|
41
|
+
raise
|
42
|
+
when :null
|
43
|
+
result[attr] = nil
|
44
|
+
when :skip
|
45
|
+
nil
|
46
|
+
else
|
47
|
+
raise Monolens::Error, "Unexpected error strategy `#{strategy}`"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
private :handle_error
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -8,27 +8,65 @@ module Monolens
|
|
8
8
|
|
9
9
|
result = {}
|
10
10
|
is_symbol = arg.keys.any?{|k| k.is_a?(Symbol) }
|
11
|
-
|
11
|
+
defn.each_pair do |new_attr, selector|
|
12
|
+
new_attr = is_symbol ? new_attr.to_sym : new_attr.to_s
|
13
|
+
|
12
14
|
deeper(world, new_attr) do |w|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
actual, fetched = fetch_on(old_attr, arg)
|
17
|
-
if actual.nil?
|
18
|
-
on_missing(old_attr, values, w)
|
19
|
-
else
|
20
|
-
values << fetched
|
21
|
-
end
|
22
|
-
end
|
23
|
-
new_attr = is_symbol ? new_attr.to_sym : new_attr.to_s
|
24
|
-
unless values.empty?
|
25
|
-
result[new_attr] = is_array ? values : values.first
|
15
|
+
catch (:skip) do
|
16
|
+
value = do_select(arg, selector, w)
|
17
|
+
result[new_attr] = value
|
26
18
|
end
|
27
19
|
end
|
28
20
|
end
|
29
21
|
result
|
30
22
|
end
|
31
23
|
|
24
|
+
private
|
25
|
+
|
26
|
+
def do_select(arg, selector, world)
|
27
|
+
if selector.is_a?(::Array)
|
28
|
+
do_array_select(arg, selector, world)
|
29
|
+
else
|
30
|
+
do_single_select(arg, selector, world)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def do_array_select(arg, selector, world)
|
35
|
+
case option(:strategy, :all).to_sym
|
36
|
+
when :all
|
37
|
+
selector.each_with_object([]) do |old_attr, values|
|
38
|
+
catch (:skip) do
|
39
|
+
values << do_single_select(arg, old_attr, world)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
when :first
|
43
|
+
selector.each do |old_attr|
|
44
|
+
actual, fetched = fetch_on(old_attr, arg)
|
45
|
+
return fetched if actual
|
46
|
+
end
|
47
|
+
on_missing(selector.first, [], world).first
|
48
|
+
else
|
49
|
+
raise Monolens::Error, "Unexpected strategy `#{strategy}`"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def do_single_select(arg, selector, world)
|
54
|
+
actual, fetched = fetch_on(selector, arg)
|
55
|
+
if actual.nil?
|
56
|
+
on_missing(selector, [], world).first
|
57
|
+
else
|
58
|
+
fetched
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def defn
|
63
|
+
defn = option(:defn, {})
|
64
|
+
defn = defn.each_with_object({}) do |attr, memo|
|
65
|
+
memo[attr] = attr
|
66
|
+
end if defn.is_a?(::Array)
|
67
|
+
defn
|
68
|
+
end
|
69
|
+
|
32
70
|
def on_missing(attr, values, world)
|
33
71
|
strategy = option(:on_missing, :fail)
|
34
72
|
case strategy.to_sym
|
@@ -37,9 +75,9 @@ module Monolens
|
|
37
75
|
when :null
|
38
76
|
values << nil
|
39
77
|
when :skip
|
40
|
-
|
78
|
+
throw :skip
|
41
79
|
else
|
42
|
-
raise Monolens::Error, "Unexpected
|
80
|
+
raise Monolens::Error, "Unexpected on_missing strategy `#{strategy}`"
|
43
81
|
end
|
44
82
|
end
|
45
83
|
private :on_missing
|
data/lib/monolens/object.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
module Monolens
|
2
2
|
module Object
|
3
|
+
def extend(options)
|
4
|
+
Extend.new(options)
|
5
|
+
end
|
6
|
+
module_function :extend
|
7
|
+
|
3
8
|
def rename(parts)
|
4
9
|
Rename.new(parts)
|
5
10
|
end
|
@@ -33,3 +38,4 @@ require_relative 'object/transform'
|
|
33
38
|
require_relative 'object/keys'
|
34
39
|
require_relative 'object/values'
|
35
40
|
require_relative 'object/select'
|
41
|
+
require_relative 'object/extend'
|
data/lib/monolens/version.rb
CHANGED
data/lib/monolens.rb
CHANGED
@@ -5,6 +5,7 @@ module Monolens
|
|
5
5
|
class << self
|
6
6
|
require_relative 'monolens/version'
|
7
7
|
require_relative 'monolens/error'
|
8
|
+
require_relative 'monolens/error_handler'
|
8
9
|
require_relative 'monolens/lens'
|
9
10
|
|
10
11
|
def define_namespace(name, impl_module)
|
@@ -43,6 +44,10 @@ module Monolens
|
|
43
44
|
end
|
44
45
|
private :chain
|
45
46
|
|
47
|
+
def file_lens(arg)
|
48
|
+
File.new(arg)
|
49
|
+
end
|
50
|
+
|
46
51
|
def leaf_lens(arg)
|
47
52
|
namespace_name, lens_name = arg.to_s.split('.')
|
48
53
|
factor_lens(namespace_name, lens_name, {})
|
@@ -50,6 +55,7 @@ module Monolens
|
|
50
55
|
private :leaf_lens
|
51
56
|
|
52
57
|
def hash_lens(arg)
|
58
|
+
return file_lens(arg) if arg['version'] || arg[:version]
|
53
59
|
raise "Invalid lens #{arg}" unless arg.size == 1
|
54
60
|
|
55
61
|
name, options = arg.to_a.first
|
@@ -5,6 +5,11 @@ describe Monolens, 'coerce.date' do
|
|
5
5
|
Monolens.lens('coerce.date' => { formats: ['%d/%m/%Y'] })
|
6
6
|
end
|
7
7
|
|
8
|
+
it 'returns Date objects unchanged (idempotency)' do
|
9
|
+
input = Date.today
|
10
|
+
expect(subject.call(input)).to be(input)
|
11
|
+
end
|
12
|
+
|
8
13
|
it 'coerces valid dates' do
|
9
14
|
expect(subject.call('11/12/2022')).to eql(Date.parse('2022-12-11'))
|
10
15
|
end
|
@@ -2,11 +2,49 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Monolens, 'coerce.datetime' do
|
4
4
|
subject do
|
5
|
-
Monolens.lens('coerce.datetime' => {
|
5
|
+
Monolens.lens('coerce.datetime' => {
|
6
|
+
}.merge(options))
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
-
|
9
|
+
let(:options) do
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns DateTime objects unchanged (idempotency)' do
|
14
|
+
input = DateTime.now
|
15
|
+
expect(subject.call(input)).to be(input)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'support for formats' do
|
19
|
+
let(:options) do
|
20
|
+
{ formats: ['%d/%m/%Y %H:%M'] }
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'coerces valid date times' do
|
24
|
+
expect(subject.call('11/12/2022 17:38')).to eql(DateTime.parse('2022-12-11 17:38'))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'support for a timezone' do
|
29
|
+
let(:options) do
|
30
|
+
{ parser: timezone }
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:now) do
|
34
|
+
::DateTime.now
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:timezone) do
|
38
|
+
Object.new
|
39
|
+
end
|
40
|
+
|
41
|
+
before do
|
42
|
+
expect(timezone).to receive(:parse).and_return(now)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'uses it to parse' do
|
46
|
+
expect(subject.call('2022-01-01')).to be(now)
|
47
|
+
end
|
10
48
|
end
|
11
49
|
|
12
50
|
describe 'error handling' do
|