monolens 0.2.0 → 0.5.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 +4 -4
- data/README.md +6 -0
- data/bin/monolens +11 -0
- data/lib/monolens/array/compact.rb +2 -2
- data/lib/monolens/array/join.rb +2 -2
- data/lib/monolens/array/map.rb +45 -6
- data/lib/monolens/array.rb +2 -2
- data/lib/monolens/coerce/date.rb +22 -6
- data/lib/monolens/coerce/date_time.rb +30 -6
- data/lib/monolens/coerce/integer.rb +15 -0
- data/lib/monolens/coerce/string.rb +13 -0
- data/lib/monolens/coerce.rb +12 -3
- data/lib/monolens/command.rb +96 -0
- data/lib/monolens/core/chain.rb +2 -2
- data/lib/monolens/core/dig.rb +52 -0
- data/lib/monolens/core/mapping.rb +23 -3
- data/lib/monolens/core.rb +6 -0
- data/lib/monolens/error.rb +9 -2
- data/lib/monolens/error_handler.rb +21 -0
- data/lib/monolens/file.rb +2 -7
- data/lib/monolens/lens/fetch_support.rb +19 -0
- data/lib/monolens/lens/location.rb +17 -0
- data/lib/monolens/lens/options.rb +41 -0
- data/lib/monolens/lens.rb +39 -23
- data/lib/monolens/object/extend.rb +53 -0
- data/lib/monolens/object/keys.rb +8 -10
- data/lib/monolens/object/rename.rb +3 -3
- data/lib/monolens/object/select.rb +71 -15
- data/lib/monolens/object/transform.rb +34 -12
- data/lib/monolens/object/values.rb +34 -10
- data/lib/monolens/object.rb +6 -0
- data/lib/monolens/skip/null.rb +1 -1
- data/lib/monolens/str/downcase.rb +2 -2
- data/lib/monolens/str/split.rb +2 -2
- data/lib/monolens/str/strip.rb +3 -1
- data/lib/monolens/str/upcase.rb +2 -2
- data/lib/monolens/version.rb +1 -1
- data/lib/monolens.rb +6 -0
- data/spec/fixtures/coerce.yml +3 -2
- data/spec/fixtures/transform.yml +5 -4
- data/spec/monolens/array/test_map.rb +89 -6
- data/spec/monolens/coerce/test_date.rb +34 -4
- data/spec/monolens/coerce/test_datetime.rb +70 -7
- data/spec/monolens/coerce/test_integer.rb +46 -0
- data/spec/monolens/coerce/test_string.rb +15 -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 +53 -11
- data/spec/monolens/lens/test_options.rb +73 -0
- data/spec/monolens/object/test_extend.rb +94 -0
- data/spec/monolens/object/test_keys.rb +54 -22
- data/spec/monolens/object/test_rename.rb +1 -1
- data/spec/monolens/object/test_select.rb +217 -4
- data/spec/monolens/object/test_transform.rb +93 -6
- data/spec/monolens/object/test_values.rb +110 -12
- data/spec/monolens/test_command.rb +128 -0
- data/spec/monolens/test_error_traceability.rb +60 -0
- data/spec/monolens/test_lens.rb +1 -1
- data/spec/test_readme.rb +7 -5
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2a9267753f8725ed91fc71b3c9b995a11d3b0c1
|
4
|
+
data.tar.gz: 4aaaf0247bcb4f2195052a416ebe7742bfd2b72a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99df61206eb26141c13a7a364fc9f5af97ade23d9a50e92e82cd8b2980cb88790e36402a852116d800a2caebe71af7b1bb90e4ad3dc8fdd450360d1ce63fe5e6
|
7
|
+
data.tar.gz: a93491cec013f9993299ee227c2055c0c93a9f224c5da6a2526afbb95fe2516bf1b29250670c769667cd93bb018e613e3ae21b98d545a62109602e2fa2c1f843
|
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/array/join.rb
CHANGED
data/lib/monolens/array/map.rb
CHANGED
@@ -3,16 +3,55 @@ module Monolens
|
|
3
3
|
class Map
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(arg)
|
7
|
+
options, lenses = case arg
|
8
|
+
when ::Hash
|
9
|
+
opts = arg.dup; opts.delete(:lenses)
|
10
|
+
_, ls = fetch_on(:lenses, arg)
|
11
|
+
raise ArgumentError, 'Lenses are required' if ls.nil?
|
12
|
+
[ opts, ls ]
|
13
|
+
else
|
14
|
+
[{}, arg]
|
15
|
+
end
|
16
|
+
super(options)
|
17
|
+
@lenses = Monolens.lens(lenses)
|
9
18
|
end
|
10
19
|
|
11
|
-
def call(arg,
|
12
|
-
is_enumerable!(arg)
|
20
|
+
def call(arg, world = {})
|
21
|
+
is_enumerable!(arg, world)
|
13
22
|
|
14
|
-
|
23
|
+
result = []
|
24
|
+
arg.each_with_index do |a, i|
|
25
|
+
deeper(world, i) do |w|
|
26
|
+
begin
|
27
|
+
result << @lenses.call(a, w)
|
28
|
+
rescue Monolens::LensError => ex
|
29
|
+
strategy = option(:on_error, :fail)
|
30
|
+
handle_error(strategy, ex, result, world)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
result
|
15
35
|
end
|
36
|
+
|
37
|
+
def handle_error(strategy, ex, result, world)
|
38
|
+
strategy = strategy.to_sym unless strategy.is_a?(::Array)
|
39
|
+
case strategy
|
40
|
+
when ::Array
|
41
|
+
strategy.each{|s| handle_error(s, ex, result, world) }
|
42
|
+
when :handler
|
43
|
+
error_handler!(world).call(ex)
|
44
|
+
when :fail
|
45
|
+
raise
|
46
|
+
when :null
|
47
|
+
result << nil
|
48
|
+
when :skip
|
49
|
+
nil
|
50
|
+
else
|
51
|
+
raise Monolens::Error, "Unexpected error strategy `#{strategy}`"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
private :handle_error
|
16
55
|
end
|
17
56
|
end
|
18
57
|
end
|
data/lib/monolens/array.rb
CHANGED
data/lib/monolens/coerce/date.rb
CHANGED
@@ -5,13 +5,21 @@ module Monolens
|
|
5
5
|
class Date
|
6
6
|
include Lens
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
DEFAULT_FORMATS = [
|
9
|
+
nil
|
10
|
+
]
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
def call(arg, world = {})
|
13
|
+
return arg if arg.is_a?(::Date)
|
14
|
+
|
15
|
+
is_string!(arg, world)
|
16
|
+
|
17
|
+
date = nil
|
18
|
+
first_error = nil
|
19
|
+
formats = @options.fetch(:formats, DEFAULT_FORMATS)
|
20
|
+
formats.each do |format|
|
13
21
|
begin
|
14
|
-
return date =
|
22
|
+
return date = strptime(arg, format)
|
15
23
|
rescue ArgumentError => ex
|
16
24
|
first_error ||= ex
|
17
25
|
rescue ::Date::Error => ex
|
@@ -19,7 +27,15 @@ module Monolens
|
|
19
27
|
end
|
20
28
|
end
|
21
29
|
|
22
|
-
|
30
|
+
fail!(first_error.message, world)
|
31
|
+
end
|
32
|
+
|
33
|
+
def strptime(arg, format = nil)
|
34
|
+
if format.nil?
|
35
|
+
::Date.strptime(arg)
|
36
|
+
else
|
37
|
+
::Date.strptime(arg, format)
|
38
|
+
end
|
23
39
|
end
|
24
40
|
end
|
25
41
|
end
|
@@ -5,13 +5,21 @@ module Monolens
|
|
5
5
|
class DateTime
|
6
6
|
include Lens
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
DEFAULT_FORMATS = [
|
9
|
+
nil
|
10
|
+
]
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
def call(arg, world = {})
|
13
|
+
return arg if arg.is_a?(::DateTime)
|
14
|
+
|
15
|
+
is_string!(arg, world)
|
16
|
+
|
17
|
+
date = nil
|
18
|
+
first_error = nil
|
19
|
+
formats = @options.fetch(:formats, DEFAULT_FORMATS)
|
20
|
+
formats.each do |format|
|
13
21
|
begin
|
14
|
-
return date =
|
22
|
+
return date = strptime(arg, format)
|
15
23
|
rescue ArgumentError => ex
|
16
24
|
first_error ||= ex
|
17
25
|
rescue ::Date::Error => ex
|
@@ -19,7 +27,23 @@ module Monolens
|
|
19
27
|
end
|
20
28
|
end
|
21
29
|
|
22
|
-
|
30
|
+
fail!(first_error.message, world)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def strptime(arg, format = nil)
|
36
|
+
parsed = if format.nil?
|
37
|
+
parser.parse(arg)
|
38
|
+
else
|
39
|
+
parser.strptime(arg, format)
|
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)
|
23
47
|
end
|
24
48
|
end
|
25
49
|
end
|
data/lib/monolens/coerce.rb
CHANGED
@@ -5,16 +5,25 @@ 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
|
11
16
|
module_function :datetime
|
12
17
|
|
18
|
+
def string(options = {})
|
19
|
+
String.new(options)
|
20
|
+
end
|
21
|
+
module_function :string
|
22
|
+
|
13
23
|
Monolens.define_namespace 'coerce', self
|
14
24
|
end
|
15
25
|
end
|
16
|
-
require_relative 'str/strip'
|
17
|
-
require_relative 'str/upcase'
|
18
|
-
require_relative 'str/downcase'
|
19
26
|
require_relative 'coerce/date'
|
20
27
|
require_relative 'coerce/date_time'
|
28
|
+
require_relative 'coerce/integer'
|
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
|
data/lib/monolens/core/chain.rb
CHANGED
@@ -8,12 +8,12 @@ module Monolens
|
|
8
8
|
@lenses = lenses
|
9
9
|
end
|
10
10
|
|
11
|
-
def call(arg,
|
11
|
+
def call(arg, world = {})
|
12
12
|
result = arg
|
13
13
|
@lenses.each do |lens|
|
14
14
|
done = false
|
15
15
|
catch(:skip) do
|
16
|
-
result = lens.call(result,
|
16
|
+
result = lens.call(result, world)
|
17
17
|
done = true
|
18
18
|
end
|
19
19
|
break unless done
|
@@ -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
|
@@ -3,13 +3,33 @@ module Monolens
|
|
3
3
|
class Mapping
|
4
4
|
include Lens
|
5
5
|
|
6
|
-
def call(arg,
|
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'
|
data/lib/monolens/error.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
module Monolens
|
2
|
-
class Error < StandardError
|
3
|
-
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
class LensError < Error
|
5
|
+
def initialize(message, location = [])
|
6
|
+
super(message)
|
7
|
+
@location = location
|
8
|
+
end
|
9
|
+
attr_reader :location
|
10
|
+
end
|
4
11
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Monolens
|
2
|
+
class ErrorHandler
|
3
|
+
def initialize
|
4
|
+
@errors = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(error)
|
8
|
+
@errors << error
|
9
|
+
end
|
10
|
+
|
11
|
+
def empty?
|
12
|
+
@errors.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
def report
|
16
|
+
@errors
|
17
|
+
.map{|err| "[#{err.location.join('/')}] #{err.message}" }
|
18
|
+
.join("\n")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/monolens/file.rb
CHANGED
@@ -2,13 +2,8 @@ module Monolens
|
|
2
2
|
class File
|
3
3
|
include Lens
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
options[:lenses] = Monolens.lens(options[:lenses])
|
8
|
-
end
|
9
|
-
|
10
|
-
def call(*args, &bl)
|
11
|
-
options[:lenses].call(*args, &bl)
|
5
|
+
def call(arg, world = {})
|
6
|
+
option(:lenses).call(arg, world)
|
12
7
|
end
|
13
8
|
end
|
14
9
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Lens
|
3
|
+
module FetchSupport
|
4
|
+
def fetch_on(attr, arg, default = nil)
|
5
|
+
if arg.key?(attr)
|
6
|
+
[ attr, arg[attr] ]
|
7
|
+
elsif arg.key?(attr_s = attr.to_s)
|
8
|
+
[ attr_s, arg[attr_s] ]
|
9
|
+
elsif arg.key?(attr_sym = attr.to_sym)
|
10
|
+
[ attr_sym, arg[attr_sym] ]
|
11
|
+
elsif default
|
12
|
+
[ attr, default ]
|
13
|
+
else
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Monolens
|
2
|
+
module Lens
|
3
|
+
class Options
|
4
|
+
include FetchSupport
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@options = case options
|
8
|
+
when Hash
|
9
|
+
options.dup
|
10
|
+
else
|
11
|
+
{ lenses: options }
|
12
|
+
end
|
13
|
+
actual, lenses = fetch_on(:lenses, @options)
|
14
|
+
@options[actual] = Monolens.lens(lenses) if actual && lenses
|
15
|
+
@options.freeze
|
16
|
+
end
|
17
|
+
attr_reader :options
|
18
|
+
private :options
|
19
|
+
|
20
|
+
NO_DEFAULT = Object.new.freeze
|
21
|
+
|
22
|
+
def fetch(key, default = NO_DEFAULT, on = @options)
|
23
|
+
if on.key?(key)
|
24
|
+
on[key]
|
25
|
+
elsif on.key?(s = key.to_s)
|
26
|
+
on[s]
|
27
|
+
elsif on.key?(sym = key.to_sym)
|
28
|
+
on[sym]
|
29
|
+
elsif default != NO_DEFAULT
|
30
|
+
default
|
31
|
+
else
|
32
|
+
raise Error, "Missing option #{key}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
@options.dup
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|