ruby-optics 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c5a90f6ad934879e9b5522055637df2dc1386b6931c8c69ab7474f5dd1ba5591
4
+ data.tar.gz: f165845f3ac79940f54071fa66dcbf673157af38dd69ae0094b89acb1c89326c
5
+ SHA512:
6
+ metadata.gz: 70ee1a8f3f1be4e7f4401c937dea535e19e85f7d39d6e54418fb48c6df93c4d81ac1e5bb1748df0ab6c1f033b6550d96b5a9073256643cd30c45d089b825c99b
7
+ data.tar.gz: 0f9b3bd70954701427f663839c56a1ae00f35f1592efbf07a87bb956c41ae8938fab9f9df9a484531fe8e6b3884d526ddca379151a2d7cd70f41585063fae2b1
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Daniil Bober
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,15 @@
1
+ # ruby-optics
2
+
3
+ Simple library with common functional optics for Ruby.
4
+
5
+ ## Links
6
+
7
+ ## Supported Ruby versions
8
+
9
+ This library officially supports the following Ruby versions:
10
+
11
+ * MRI >= `2.5.1`
12
+
13
+ ## License
14
+
15
+ See `LICENSE` file.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-optics/optics'
4
+ require 'ruby-optics/record/record'
5
+
6
+ module Optics
7
+ VERSION = "0.0.1"
8
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Each
4
+ attr_reader :inner_focus
5
+ attr_reader :outer_focus
6
+ attr_reader :filter
7
+
8
+ Filter = Struct.new(:focus, :blk) do
9
+ def call(object)
10
+ blk.(focus.get(object))
11
+ end
12
+ end
13
+
14
+ def initialize(outer_focus = nil, inner_focus = nil, filter = nil)
15
+ @outer_focus = outer_focus || Lens.identity
16
+ @inner_focus = inner_focus || Lens.identity
17
+ @filter = filter
18
+ end
19
+
20
+ def set(new_value, object)
21
+ modify_all(object) { |_| new_value }
22
+ end
23
+
24
+ def modify_all(object, &blk)
25
+ outer_focus.modify(object) { |enumerable|
26
+ enumerable.map { |a|
27
+ if !filter.nil?
28
+ filter.call(a) ? inner_focus.modify(a, &blk) : a
29
+ else
30
+ inner_focus.modify(a, &blk)
31
+ end
32
+ }
33
+ }
34
+ end
35
+
36
+ def get_all(object)
37
+ filtered(outer_focus.get(object)).map { |a| inner_focus.get(a) }
38
+ end
39
+
40
+ def with_filter(focusing_lens, &blk)
41
+ Each.new(
42
+ outer_focus = self.outer_focus,
43
+ inner_focus = self.inner_focus,
44
+ filter = Filter.new(focusing_lens, blk)
45
+ )
46
+ end
47
+
48
+ def compose_lens(lens)
49
+ Each.new(
50
+ outer_focus = self.outer_focus,
51
+ inner_focus = self.inner_focus.compose_lens(lens),
52
+ filter = self.filter
53
+ )
54
+ end
55
+
56
+ def self.with_filter(focusing_lens, &blk)
57
+ Each.new.with_filter(focusing_lens, &blk)
58
+ end
59
+
60
+ private
61
+
62
+ def filtered(enumerable)
63
+ return enumerable if filter.nil?
64
+
65
+ enumerable.select { |object| filter.call(object) }
66
+ end
67
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'nullable'
4
+
5
+ class Lens
6
+ attr_reader :getter
7
+ attr_reader :setter
8
+
9
+ include NullableOptic
10
+
11
+ def initialize(getter, setter)
12
+ @getter = getter
13
+ @setter = setter
14
+ end
15
+
16
+ def get(obj)
17
+ getter.(obj)
18
+ end
19
+
20
+ def set(new_val, obj)
21
+ setter.(new_val, obj)
22
+ end
23
+
24
+ def modify(obj, &blk)
25
+ setter.(
26
+ blk.(getter.(obj)),
27
+ obj
28
+ )
29
+ end
30
+
31
+ def compose_lens(other_lense)
32
+ Lens.new(
33
+ -> (obj) { other_lense.getter.(getter.(obj)) },
34
+ -> (new_val, obj) {
35
+ setter.(
36
+ other_lense.setter.(
37
+ new_val,
38
+ getter.(obj)
39
+ ),
40
+ obj
41
+ )
42
+ }
43
+ )
44
+ end
45
+
46
+ def each_lens
47
+ Each.new(outer_focus = self)
48
+ end
49
+
50
+ def self.identity
51
+ @_identity ||= Lens.new(
52
+ -> (obj) { obj },
53
+ -> (new_val, _obj) { new_val }
54
+ )
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NullableOptic
4
+ def nullable
5
+ self.class.new(
6
+ -> (obj) {
7
+ if obj.nil?
8
+ nil
9
+ else
10
+ getter.(obj)
11
+ end
12
+ },
13
+
14
+ -> (new_val, obj) {
15
+ if obj.nil?
16
+ nil
17
+ else
18
+ setter.(new_val, obj)
19
+ end
20
+ }
21
+ )
22
+ end
23
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lens'
4
+ require_relative 'nullable'
5
+ require_relative 'each'
6
+
7
+ module Optics
8
+ def self.hash_lens(*keys)
9
+ head, *tail = keys
10
+
11
+ [build_hash_lens(head), *tail].reduce { |result_lens, key|
12
+ result_lens.compose_lens(build_hash_lens(key))
13
+ }
14
+ end
15
+
16
+ def self.indiffirent_access_hash_lens(*keys)
17
+ head, *tail = keys
18
+ opts = { indiffirent_access: true }
19
+
20
+ [build_hash_lens(head, opts), *tail].reduce { |result_lens, key|
21
+ result_lens.compose_lens(build_hash_lens(key, opts))
22
+ }
23
+ end
24
+
25
+ def self.struct_lens(*method_names)
26
+ head, *tail = method_names
27
+
28
+ [build_struct_lens(head), *tail].reduce { |result_lens, key|
29
+ result_lens.compose_lens(build_struct_lens(key))
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def self.build_hash_lens(key, indiffirent_access: false)
36
+ ::Lens.new(
37
+ -> (hash) {
38
+ if indiffirent_access
39
+ case key
40
+ when String
41
+ val = hash[key]
42
+ val.nil? ? hash[key.to_sym] : val
43
+ when Symbol
44
+ val = hash[key]
45
+ val.nil? ? hash[key.to_s] : val
46
+ else
47
+ hash[key]
48
+ end
49
+ else
50
+ hash[key]
51
+ end
52
+ },
53
+ -> (new_value, hash) { hash.merge(key => new_value) }
54
+ ).nullable
55
+ end
56
+
57
+ def self.build_struct_lens(method_name)
58
+ ::Lens.new(
59
+ -> (struct) { struct[method_name] },
60
+ -> (new_value, struct) {
61
+ struct.class.new(
62
+ *struct.members.map { |member|
63
+ (member == method_name) ? new_value : struct[member]
64
+ }
65
+ )
66
+ }
67
+ )
68
+ end
69
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'record_lens'
4
+
5
+ module Record
6
+ def initialize(args_hash)
7
+ defined_attributes = self.class.instance_variable_get(
8
+ :"@_record_attributes_defs"
9
+ ) || []
10
+
11
+ defined_attributes.each do |defined_attribute_params|
12
+ attribute_name = defined_attribute_params[:attribute_name]
13
+ attribute_argument = args_hash[attribute_name]
14
+ if attribute_argument.nil?
15
+ if defined_attribute_params[:nullable]
16
+ instance_variable_set(
17
+ :"@#{attribute_name}",
18
+ defined_attribute_params[:default]
19
+ )
20
+ else
21
+ raise ArgumentError.new(
22
+ "Attribute with name #{attribute_name} is not provided"
23
+ )
24
+ end
25
+ else
26
+ instance_variable_set(
27
+ :"@#{attribute_name}",
28
+ attribute_argument
29
+ )
30
+ end
31
+ end
32
+ end
33
+
34
+ def copy_with(args_hash)
35
+ self.class.new(
36
+ _current_attributes.merge(
37
+ args_hash.reject { |k, v| !_current_attributes.keys.include?(k) }
38
+ )
39
+ )
40
+ end
41
+
42
+ def ==(another_record)
43
+ return false unless another_record.is_a?(self.class)
44
+
45
+ another_record_attributes = another_record.send(:_current_attributes)
46
+
47
+ _current_attributes == another_record_attributes
48
+ end
49
+
50
+ alias_method :eql?, :==
51
+
52
+ def hash
53
+ [self.class, _current_attributes].hash
54
+ end
55
+
56
+ def self.included(base)
57
+ base.define_singleton_method(:attribute) do |attribute_name, nullable: false, default: nil|
58
+ @_record_attributes_defs ||= []
59
+ @_record_attributes_defs << {
60
+ attribute_name: attribute_name,
61
+ nullable: nullable,
62
+ default: default
63
+ }
64
+
65
+ base.attr_reader(attribute_name)
66
+ end
67
+
68
+ base.instance_variable_set(:"@_lenses", {})
69
+
70
+ base.define_singleton_method(:lens) do |*attribute_names|
71
+ head, *tail = attribute_names
72
+ fst_lens = (@_lenses[head] ||= RecordLens.build(head))
73
+
74
+ return fst_lens if tail.empty?
75
+
76
+ [fst_lens, *tail].reduce { |result_lens, attribute_name|
77
+ result_lens.compose_lens(RecordLens.build(attribute_name))
78
+ }
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def _current_attributes
85
+ defined_attributes = self.class.instance_variable_get(
86
+ :"@_record_attributes_defs"
87
+ ) || []
88
+
89
+ defined_attributes
90
+ .map { |attribute_params| attribute_params[:attribute_name] }
91
+ .map { |attr_name| [attr_name, instance_variable_get(:"@#{attr_name}")] }
92
+ .to_h
93
+ end
94
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RecordLens
4
+ def self.build(attribute_name)
5
+ Lens.new(
6
+ -> (record) { record.send(attribute_name) },
7
+ -> (new_attr_value, record) {
8
+ record.copy_with(attribute_name => new_attr_value)
9
+ }
10
+ )
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'ruby-optics'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'ruby-optics'
10
+ spec.authors = ["Daniil Bober"]
11
+ spec.email = ["cardinal.ximinez.again@gmail.com"]
12
+ spec.license = 'MIT'
13
+ spec.version = Optics::VERSION.dup
14
+ spec.homepage = "https://github.com/boberdaniil/ruby-optics"
15
+ spec.summary = "Common optics for Ruby"
16
+ spec.description = spec.summary
17
+ spec.files = Dir["LICENSE", "README.md", "ruby-optics.gemspec", "lib/**/*"]
18
+ spec.bindir = 'bin'
19
+ spec.executables = []
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.required_ruby_version = ">= 2.4.0"
23
+
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-optics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniil Bober
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Common optics for Ruby
56
+ email:
57
+ - cardinal.ximinez.again@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE
63
+ - README.md
64
+ - lib/ruby-optics.rb
65
+ - lib/ruby-optics/each.rb
66
+ - lib/ruby-optics/lens.rb
67
+ - lib/ruby-optics/nullable.rb
68
+ - lib/ruby-optics/optics.rb
69
+ - lib/ruby-optics/record/record.rb
70
+ - lib/ruby-optics/record/record_lens.rb
71
+ - ruby-optics.gemspec
72
+ homepage: https://github.com/boberdaniil/ruby-optics
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 2.4.0
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.1.2
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Common optics for Ruby
95
+ test_files: []