ocl 0.1.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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ocl/core.rb +236 -0
  3. data/lib/ocl.rb +8 -0
  4. metadata +57 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4be0739c4acc7e4530cbc6ccd5b63e4050070b90de637017d29d598feac67f7f
4
+ data.tar.gz: de6f9bb78f5ef602cc66d65c4c05d06b9a25acb9a3fb049a3a38b0e7947a794f
5
+ SHA512:
6
+ metadata.gz: 5a8272e8983110c534ff752f03d7d5830afb8696b24db3af8d4d53c03109ed3becf0dc20ec5a3690dff65664fc0d080207bbebf0e5ec03cf4919943a2b407fa6
7
+ data.tar.gz: 5809cf6b6b646f05de920deb6efb7792fbda06889aff2dc1c1572a0bac030017e552ac6b017a095ce2007516d10c778853c7a988201267df3692030204f92ea4
data/lib/ocl/core.rb ADDED
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/ocl/core.rb
4
+
5
+ # Main OCL module providing invariant, precondition, postcondition, and derived attribute support.
6
+ module OCL
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ base.prepend InitializerHook
10
+ end
11
+
12
+ # Class-level methods to define OCL constraints.
13
+ module ClassMethods
14
+ def invariants
15
+ @invariants ||= []
16
+ end
17
+
18
+ def preconditions
19
+ @preconditions ||= Hash.new { |h, k| h[k] = [] }
20
+ end
21
+
22
+ def postconditions
23
+ @postconditions ||= Hash.new { |h, k| h[k] = [] }
24
+ end
25
+
26
+ def derived_properties
27
+ @derived_properties ||= {}
28
+ end
29
+
30
+ # Define an invariant constraint.
31
+ def inv(name, &block)
32
+ invariants << { name: name, block: block }
33
+ end
34
+
35
+ # Define a precondition constraint for a method.
36
+ def pre(method_name, name, &block)
37
+ wrap_method(method_name)
38
+ preconditions[method_name] << { name: name, block: block }
39
+ end
40
+
41
+ # Define a postcondition constraint for a method.
42
+ def post(method_name, name, &block)
43
+ wrap_method(method_name)
44
+ postconditions[method_name] << { name: name, block: block }
45
+ end
46
+
47
+ # Define a derived (calculated) attribute.
48
+ def derived(name, &block)
49
+ derived_properties[name.to_sym] = block
50
+
51
+ define_method(name) do
52
+ context = Context.new(self)
53
+ self.class.derived_properties[name.to_sym].call(context)
54
+ end
55
+ end
56
+
57
+ # Define an attribute with automatic invariant validation on assignment.
58
+ def attr_accessor_with_invariant(*names)
59
+ names.each do |name|
60
+ attr_reader name
61
+
62
+ define_method("#{name}=") do |value|
63
+ instance_variable_set("@#{name}", value)
64
+ validate_invariants!
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Wrap an instance method to validate preconditions and postconditions.
72
+ def wrap_method(method_name)
73
+ @wrapped_methods ||= Set.new
74
+ return if @wrapped_methods.include?(method_name)
75
+
76
+ original = instance_method(method_name)
77
+ @wrapped_methods.add(method_name)
78
+
79
+ define_method(method_name) do |*args, &block|
80
+ validate_preconditions!(method_name, *args)
81
+ result = original.bind(self).call(*args, &block)
82
+ validate_postconditions!(method_name, result, *args)
83
+ result
84
+ end
85
+ end
86
+ end
87
+
88
+ # Internal hook to ensure superclass initialize is called properly.
89
+ module InitializerHook
90
+ def initialize(*args, &block)
91
+ super(*args, &block)
92
+ end
93
+ end
94
+
95
+ # Raised when any constraint validation fails.
96
+ class ConstraintViolationError < StandardError
97
+ def initialize(errors)
98
+ super(errors.is_a?(Array) ? errors.join(', ') : errors)
99
+ end
100
+ end
101
+
102
+ # Context passed to validation blocks, providing access to the target object and helpers.
103
+ class Context
104
+ attr_reader :object
105
+
106
+ def initialize(object)
107
+ @object = object
108
+ @errors = []
109
+ end
110
+
111
+ def method_missing(name, *args, &block)
112
+ if args.empty?
113
+ object.public_send(name)
114
+ else
115
+ super
116
+ end
117
+ end
118
+
119
+ def respond_to_missing?(_name, _include_private = false)
120
+ true
121
+ end
122
+
123
+ # Create an expectation object for fluent assertions.
124
+ def expect(actual)
125
+ Expectation.new(actual, self)
126
+ end
127
+
128
+ # Record an error message in the context.
129
+ def add_error(detail)
130
+ @errors << detail
131
+ end
132
+
133
+ # Check if all assertions passed.
134
+ def valid?
135
+ @errors.empty?
136
+ end
137
+
138
+ # Return accumulated error messages.
139
+ def error_messages
140
+ @errors
141
+ end
142
+
143
+ # Fluent assertions used within validation blocks.
144
+ class Expectation
145
+ def initialize(actual, context)
146
+ @actual = actual
147
+ @context = context
148
+ end
149
+
150
+ def to_be(expected)
151
+ return if @actual == expected
152
+
153
+ @context.add_error("Expected #{@actual.inspect} to equal #{expected.inspect}")
154
+ end
155
+
156
+ def to_not_be(expected)
157
+ return unless @actual == expected
158
+
159
+ @context.add_error("Expected #{@actual.inspect} to not equal #{expected.inspect}")
160
+ end
161
+
162
+ def to_be_positive
163
+ return if @actual.positive?
164
+
165
+ @context.add_error("Expected #{@actual.inspect} to be positive")
166
+ end
167
+
168
+ def to_be_greater_than(value)
169
+ return if @actual > value
170
+
171
+ @context.add_error("Expected #{@actual.inspect} to be greater than #{value.inspect}")
172
+ end
173
+
174
+ def to_be_greater_than_or_equal_to(value)
175
+ return if @actual >= value
176
+
177
+ @context.add_error("Expected #{@actual.inspect} to be greater than or equal to #{value.inspect}")
178
+ end
179
+
180
+ def to_be_less_than(value)
181
+ return if @actual < value
182
+
183
+ @context.add_error("Expected #{@actual.inspect} to be less than #{value.inspect}")
184
+ end
185
+
186
+ def to_be_less_than_or_equal_to(value)
187
+ return if @actual <= value
188
+
189
+ @context.add_error("Expected #{@actual.inspect} to be less than or equal to #{value.inspect}")
190
+ end
191
+ end
192
+ end
193
+
194
+ # Validate all defined invariants.
195
+ def validate_invariants!
196
+ errors = []
197
+
198
+ self.class.invariants.each do |inv|
199
+ context = Context.new(self)
200
+ inv[:block].call(context)
201
+ errors << "Invariant '#{inv[:name]}' violated: " + context.error_messages.join(', ') unless context.valid?
202
+ end
203
+
204
+ raise ConstraintViolationError, errors unless errors.empty?
205
+ end
206
+
207
+ # Validate preconditions before method execution.
208
+ def validate_preconditions!(method_name, *args)
209
+ errors = []
210
+
211
+ self.class.preconditions[method_name].each do |pre|
212
+ context = Context.new(self)
213
+ pre[:block].call(context, *args)
214
+ unless context.valid?
215
+ errors << "Precondition '#{pre[:name]}' for #{method_name} violated: " + context.error_messages.join(', ')
216
+ end
217
+ end
218
+
219
+ raise ConstraintViolationError, errors unless errors.empty?
220
+ end
221
+
222
+ # Validate postconditions after method execution.
223
+ def validate_postconditions!(method_name, result, *args)
224
+ errors = []
225
+
226
+ self.class.postconditions[method_name].each do |post|
227
+ context = Context.new(self)
228
+ post[:block].call(context, result, *args)
229
+ unless context.valid?
230
+ errors << "Postcondition '#{post[:name]}' for #{method_name} violated: " + context.error_messages.join(', ')
231
+ end
232
+ end
233
+
234
+ raise ConstraintViolationError, errors unless errors.empty?
235
+ end
236
+ end
data/lib/ocl.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/ocl.rb
4
+ require_relative 'ocl/core'
5
+
6
+ # Main module for Object Constraint Language (OCL) support in Ruby.
7
+ module OCL
8
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ocl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Takeshi Kakeda
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.12'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.12'
26
+ description: Supports invariant, precondition, postcondition, and derived attributes
27
+ using a Ruby DSL.
28
+ email:
29
+ - takeshi@giantech.jp
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/ocl.rb
35
+ - lib/ocl/core.rb
36
+ homepage: https://github.com/yourname/ocl
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.6.8
55
+ specification_version: 4
56
+ summary: A minimal Object Constraint Language (OCL) engine for Ruby.
57
+ test_files: []