l43_open_object 0.2.9 → 0.4.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
2
  SHA256:
3
- metadata.gz: 7008cb448ef1b5ee3d8d07a1412f1edca5b90d9d3b1f6134208ff9534b985053
4
- data.tar.gz: cd3867b8d7006c3b1cd76d8f76d48283625080ff13ca816e679037f95410b801
3
+ metadata.gz: 4de3a4790f943a126eec581676c8b3dfcfb19ee6a300baba9bc5d93a7e43d084
4
+ data.tar.gz: 7da96a9783f76cf20571dec4808dcf33e3690e399d20e320cc51e0445bee3860
5
5
  SHA512:
6
- metadata.gz: 499291c4eddc3efc8467a57c4772b8fe3ebaa15dca640fd9ef429c0878ef71e3fbc53f24c7ca63c4f57e4d4840863d43a215998a960923c7621e2ca65fe7446c
7
- data.tar.gz: ee82dcfc0e21aaf3b1f3d01fe1adec6f329295092f608846d6632673553a9b9abc7c0fb30b68d7f74803d5a5c11f47dae0f213d6a31c43b08c65b265694dc59e
6
+ metadata.gz: 25f70fda2074d8008c758d0614c4448e921212591ec049148b09ad517ca0d1eedf72209e6bc05acdf4f3e81ac99c69d04597bdf07362de314663c0a7c2bad1cf
7
+ data.tar.gz: b8004ec9f68be0978e5b3d036a441c2a845d3550d5cfa0b3bc26467dd8132f5b1940fef68616ecec5b1ab6725e4740fc251cadf7b7a44a7b83ac4bab161f0e22
data/lib/l43/kernel.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kernel
4
+ def import(*path)
5
+ require(File.join(["l43", *path]))
6
+
7
+ segments = path.map do |seg|
8
+ seg.split("_").map(&:capitalize).join
9
+ end.join("::")
10
+ Kernel.const_get("L43::#{segments}")
11
+ end
12
+
13
+ end
14
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,25 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ostruct'
3
4
  module L43
4
5
  module OpenObject
5
6
  module Initializer extend self
6
7
 
7
8
  def define_initialize(klass, atts, defaults, blk)
8
- klass.define_method :initialize do |**kwds|
9
- missing = atts - kwds.keys
10
- raise ArgumentError, "missing required keyword parameters: #{missing.inspect}" unless missing.empty?
9
+ klass.define_method :initialize do |**kwds, &b|
11
10
  spurious = kwds.keys - atts - defaults.keys
12
11
  raise ArgumentError, "spurious keyword parameters: #{spurious.inspect}" unless spurious.empty?
13
12
 
14
13
  values = defaults.merge(kwds)
15
14
  values.each do |key, value|
16
- instance_variable_set("@#{key}", value)
15
+ if Proc == value
16
+ instance_variable_set("@#{key}", b)
17
+ else
18
+ instance_variable_set("@#{key}", value)
19
+ end
20
+ end
21
+
22
+ if blk
23
+ more_values = instance_exec(OpenStruct.new(values), &blk)
24
+ if Hash === more_values
25
+ more_values.each do |key, value|
26
+ instance_variable_set("@#{key}", value)
27
+ end
28
+ values = values.merge(more_values)
29
+ end
17
30
  end
31
+
32
+ missing = atts - values.keys
33
+ raise ArgumentError, "missing required keyword parameters: #{missing.inspect}" unless missing.empty?
34
+
18
35
  if respond_to?(:_init)
19
36
  _init
20
37
  end
21
- do_we_freeze = instance_exec(&blk) if blk
22
- if do_we_freeze == :stay_warm
38
+
39
+ if klass.mutable?
23
40
  Initializer.define_dynamic_to_h(klass, values.keys)
24
41
  else
25
42
  Initializer.define_to_h(klass, values.keys)
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module OpenObject
5
+ VERSION = "0.4.0"
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'open_object/initializer'
4
- require_relative 'open_object/behavior'
3
+ require 'l43/open_object/initializer'
4
+ require 'l43/open_object/behavior'
5
5
  module L43
6
6
  module OpenObject
7
- VERSION = "0.2.9"
8
7
 
9
8
  def attributes(*atts, **defaults, &blk)
10
9
  attr_reader(*atts)
@@ -31,6 +30,14 @@ module L43
31
30
  # end
32
31
  end
33
32
 
33
+ def mutable_object!
34
+ instance_variable_set("@__mutable__", true)
35
+ end
36
+
37
+ def mutable?
38
+ instance_variable_get("@__mutable__") || false
39
+ end
40
+
34
41
  def self.def_class(*a, **k, &b)
35
42
  oo = self
36
43
  Class.new do
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+ module L43
5
+ module TypedObject
6
+ module Behavior
7
+ def ==(other)
8
+ return false unless self.class === other
9
+ other.to_h == to_h
10
+ end
11
+
12
+ def deconstruct_keys(*) = to_h
13
+
14
+ def merge(**args)
15
+ self.class.new(**to_h.merge(**args))
16
+ end
17
+
18
+
19
+ def to_ostruct
20
+ OpenStruct.new(to_h)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module TypedObject
5
+ None = BasicObject.new
6
+
7
+ MissingAttribute = Class.new(RuntimeError)
8
+ SpuriousAttribute = Class.new(RuntimeError)
9
+ ValueError = Class.new(RuntimeError)
10
+ end
11
+ end
12
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+ require_relative 'implementation'
5
+ module L43
6
+ module TypedObject
7
+ class Description
8
+
9
+ def check_types!
10
+ ImplHelper.check_value_errors!(args: defaults, attributes:)
11
+ end
12
+
13
+ def implement =
14
+ Implementation.new(klass:, attributes: attributes.freeze, defaults:)
15
+
16
+ private
17
+ attr_reader :allowed, :klass, :required
18
+ def initialize(klass)
19
+ @klass = klass
20
+ end
21
+
22
+
23
+ def attr(name, default: None, as: None, &blk)
24
+ attributes.update(name => OpenStruct.new(default:, type: as, constraint: blk))
25
+ end
26
+
27
+ def attributes = @__attributes__ ||= {}
28
+
29
+ def defaults
30
+ @__defaults__ ||= attributes.inject({}) do |defaults, (name, desc)|
31
+ if desc.default == None
32
+ defaults
33
+ else
34
+ defaults.merge(name => desc.default)
35
+ end
36
+ end.freeze
37
+ end
38
+ end
39
+ end
40
+ end
41
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module TypedObject
5
+ module ImplHelper extend self
6
+ def check_for_missing!(actual:, required:)
7
+ missing = required - actual
8
+ return if missing.empty?
9
+
10
+ raise MissingAttribute, "the following attributes do not have a default, but are not provided: #{missing.inspect}"
11
+ end
12
+
13
+ def check_for_spurious!(actual:, allowed:)
14
+ spurious = actual - allowed
15
+ return if spurious.empty?
16
+
17
+ raise SpuriousAttribute, "the following attributes have not been defined on this class: #{spurious.inspect}"
18
+ end
19
+
20
+ def check_type(type, value)
21
+ return if None == type
22
+ case type
23
+ when Array
24
+ return true unless type.member?(value)
25
+ else
26
+ return true unless type === value
27
+ end
28
+ end
29
+
30
+ def check_value(desc, value)
31
+ return :type if check_type(desc.type, value)
32
+ case desc.constraint
33
+ when nil
34
+ nil
35
+ else
36
+ return :constraint unless desc.constraint.(value)
37
+ end
38
+ end
39
+
40
+ def check_value_errors!(args:, attributes:)
41
+ errors = _get_value_errors(args:, attributes:)
42
+ return if errors.empty?
43
+
44
+ raise ValueError, "bad values:\n#{errors.join("\n")}"
45
+ end
46
+
47
+ private
48
+ def _get_value_errors(args:, attributes:)
49
+ args.map do |name, value|
50
+ desc = attributes[name]
51
+ case check_value(desc, value)
52
+ when :type
53
+ " bad value: #{value.inspect} for attribute #{name}, expected type was: #{desc.type.inspect}"
54
+ when :constraint
55
+ " value: #{value.inspect} violates constraint for attribute #{name}"
56
+ else
57
+ nil
58
+ end
59
+ end.compact
60
+ end
61
+ end
62
+ end
63
+ end
64
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'behavior'
4
+ require_relative 'impl_helper'
5
+
6
+ module L43
7
+ module TypedObject
8
+ class Implementation
9
+
10
+ private
11
+ attr_reader :attributes, :defaults, :klass
12
+
13
+ def initialize(klass:, attributes:, defaults:)
14
+ @attributes = attributes
15
+ @defaults = defaults
16
+ @klass = klass
17
+ implement
18
+ end
19
+
20
+ def allowed = @__allowed__ ||= attributes.map(&:first)
21
+
22
+ def define_behavior
23
+ klass.send :include, Behavior
24
+ end
25
+
26
+ def define_initialize
27
+ allowed = self.allowed
28
+ attributes = self.attributes
29
+ defaults = self.defaults
30
+ required = self.required.keys
31
+
32
+ klass.define_method(:initialize) do |**args|
33
+ args = defaults.merge(args)
34
+ actual = args.keys
35
+ ImplHelper.check_for_missing!(required:, actual:)
36
+ ImplHelper.check_for_spurious!(allowed:, actual:)
37
+ ImplHelper.check_value_errors!(args:, attributes:)
38
+ args.each do |name, value|
39
+ instance_variable_set("@#{name}", value)
40
+ end
41
+ end
42
+ end
43
+
44
+ def define_read_access
45
+ allowed = self.allowed
46
+ klass.module_eval do
47
+ attr_reader(*allowed)
48
+ end
49
+ end
50
+
51
+ def define_to_h
52
+ attributes = self.attributes.keys
53
+ klass.define_method(:to_h) do
54
+ attributes.inject({}) do |result, att_name|
55
+ result.merge(att_name => instance_variable_get("@#{att_name}"))
56
+ end
57
+ end
58
+ end
59
+
60
+ def define_update
61
+ allowed = self.allowed
62
+ attributes = self.attributes
63
+ klass.define_method(:update) do |**args|
64
+ ImplHelper.check_value_errors!(args:, attributes:)
65
+ ImplHelper.check_for_spurious!(allowed:, actual: args.keys)
66
+ args.each do |name, value|
67
+ instance_variable_set("@#{name}", value)
68
+ end
69
+ self
70
+ end
71
+ end
72
+
73
+ def implement
74
+ separate_attributes
75
+ define_read_access
76
+ define_to_h
77
+ define_update
78
+ define_behavior
79
+ define_initialize
80
+ end
81
+
82
+ def required = @__required__ ||= {}
83
+
84
+ def separate_attributes
85
+ attributes.each { |name, desc| required.update(name => desc) if desc.default == None }
86
+ end
87
+
88
+ end
89
+ end
90
+ end
91
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'typed_object/constants'
4
+ require_relative 'typed_object/description'
5
+ module L43
6
+ module TypedObject
7
+ def def_atts(&blk)
8
+ desc = Description.new(self)
9
+ desc.instance_eval(&blk)
10
+ desc.check_types!
11
+ desc.implement
12
+ end
13
+ end
14
+ end
15
+ # SPDX-License-Identifier: AGPL-3.0-or-later
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: l43_open_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-04-13 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 2026-11-01 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ostruct
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.6.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.6.3
12
26
  description: |-
13
27
  When using the class method `attributes` in a class you get:
14
28
  - an initialize method with attributes as kwd params and the respective defaults
@@ -23,9 +37,17 @@ extra_rdoc_files: []
23
37
  files:
24
38
  - LICENSE
25
39
  - lib/l43.rb
40
+ - lib/l43/kernel.rb
26
41
  - lib/l43/open_object.rb
27
42
  - lib/l43/open_object/behavior.rb
28
43
  - lib/l43/open_object/initializer.rb
44
+ - lib/l43/open_object/version.rb
45
+ - lib/l43/typed_object.rb
46
+ - lib/l43/typed_object/behavior.rb
47
+ - lib/l43/typed_object/constants.rb
48
+ - lib/l43/typed_object/description.rb
49
+ - lib/l43/typed_object/impl_helper.rb
50
+ - lib/l43/typed_object/implementation.rb
29
51
  homepage: https://codeberg.org/lab419/l43_open_object
30
52
  licenses:
31
53
  - AGPL-3.0-or-later
@@ -44,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
66
  - !ruby/object:Gem::Version
45
67
  version: '0'
46
68
  requirements: []
47
- rubygems_version: 4.0.9
69
+ rubygems_version: 4.0.20
48
70
  specification_version: 4
49
71
  summary: Define attributes with potential defaults, get all the goodies...
50
72
  test_files: []