module JSI
class SchemaModule < Module
def initialize(schema, &block)
super(&block)
@jsi_node = schema
schema.jsi_schemas.each do |schema_schema|
extend SchemaClasses.schema_property_reader_module(schema_schema, conflicting_modules: Set[SchemaModule])
end
end
def schema
@jsi_node
end
def schema_uri
schema.schema_uri
end
def inspect
if name_from_ancestor
if schema.schema_absolute_uri
-"#{name_from_ancestor} <#{schema.schema_absolute_uri}> (JSI Schema Module)"
else
-"#{name_from_ancestor} (JSI Schema Module)"
end
else
-"(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri})"
end
end
alias_method :to_s, :inspect
def new_jsi(instance, **kw)
schema.new_jsi(instance, **kw)
end
def schema_content
schema.jsi_node_content
end
def instance_validate(instance)
schema.instance_validate(instance)
end
def instance_valid?(instance)
schema.instance_valid?(instance)
end
end
module SchemaModule::DescribesSchemaModule
def new_schema(schema_content, **kw, &block)
schema.new_schema(schema_content, **kw, &block)
end
def new_schema_module(schema_content, **kw, &block)
schema.new_schema(schema_content, **kw, &block).jsi_schema_module
end
attr_reader :schema_implementation_modules
end
module SchemaClasses
class << self
def includes_for(instance)
includes = Set[]
includes << Base::ArrayNode if instance.respond_to?(:to_ary)
includes << Base::HashNode if instance.respond_to?(:to_hash)
includes << Base::StringNode if instance.respond_to?(:to_str)
includes.freeze
end
def class_for_schemas(schemas, includes: )
@class_for_schemas_map[
schemas: SchemaSet.ensure_schema_set(schemas),
includes: Util.ensure_module_set(includes),
]
end
private def class_for_schemas_compute(schemas: , includes: )
Class.new(Base) do
define_singleton_method(:jsi_class_schemas) { schemas }
define_method(:jsi_schemas) { schemas }
define_singleton_method(:jsi_class_includes) { includes }
conflicting_modules = Set[JSI::Base] + includes + schemas.map(&:jsi_schema_module)
reader_modules = schemas.map do |schema|
JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: conflicting_modules)
end
reader_modules.each { |m| include m }
readers = reader_modules.map(&:jsi_property_readers).inject(Set[], &:merge).freeze
define_method(:jsi_property_readers) { readers }
define_singleton_method(:jsi_property_readers) { readers }
writer_modules = schemas.map do |schema|
JSI::SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules)
end
writer_modules.each { |m| include m }
includes.each { |m| include(m) }
schemas.each { |schema| include(schema.jsi_schema_module) }
jsi_class = self
define_method(:jsi_class) { jsi_class }
self
end
end
def bootstrap_schema_class(modules)
@bootstrap_schema_class_map[
modules: Util.ensure_module_set(modules),
]
end
private def bootstrap_schema_class_compute(modules: )
Class.new(MetaschemaNode::BootstrapSchema) do
define_singleton_method(:schema_implementation_modules) { modules }
define_method(:schema_implementation_modules) { modules }
modules.each { |mod| include(mod) }
self
end
end
def module_for_schema(schema)
Schema.ensure_schema(schema)
raise(Bug, "non-Base schema cannot have schema module: #{schema}") unless schema.is_a?(Base)
@schema_module_map[schema: schema]
end
private def schema_module_compute(schema: )
SchemaModule.new(schema)
end
def accessor_module_for_schema(schema, conflicting_modules: , setters: true)
Module.new do
include SchemaClasses.schema_property_reader_module(schema, conflicting_modules: conflicting_modules)
include SchemaClasses.schema_property_writer_module(schema, conflicting_modules: conflicting_modules) if setters
end
end
def schema_property_reader_module(schema, conflicting_modules: )
Schema.ensure_schema(schema)
@schema_property_reader_module_map[schema: schema, conflicting_modules: conflicting_modules]
end
private def schema_property_reader_module_compute(schema: , conflicting_modules: )
Module.new do
define_singleton_method(:inspect) { '(JSI Schema Property Reader Module)' }
readers = schema.described_object_property_names.select do |name|
Util.ok_ruby_method_name?(name) &&
!conflicting_modules.any? { |m| m.method_defined?(name) || m.private_method_defined?(name) }
end.to_set.freeze
define_singleton_method(:jsi_property_readers) { readers }
readers.each do |property_name|
define_method(property_name) do |**kw, &block|
self[property_name, **kw, &block]
end
end
end
end
def schema_property_writer_module(schema, conflicting_modules: )
Schema.ensure_schema(schema)
@schema_property_writer_module_map[schema: schema, conflicting_modules: conflicting_modules]
end
private def schema_property_writer_module_compute(schema: , conflicting_modules: )
Module.new do
define_singleton_method(:inspect) { '(JSI Schema Property Writer Module)' }
writers = schema.described_object_property_names.select do |name|
writer = "#{name}="
Util.ok_ruby_method_name?(name) &&
!conflicting_modules.any? { |m| m.method_defined?(writer) || m.private_method_defined?(writer) }
end.to_set.freeze
define_singleton_method(:jsi_property_writers) { writers }
writers.each do |property_name|
define_method("#{property_name}=") do |value|
self[property_name] = value
end
end
end
end
end
@class_for_schemas_map = Hash.new { |h, k| h[k] = class_for_schemas_compute(**k) }
@bootstrap_schema_class_map = Hash.new { |h, k| h[k] = bootstrap_schema_class_compute(**k) }
@schema_module_map = Hash.new { |h, k| h[k] = schema_module_compute(**k) }
@schema_property_reader_module_map = Hash.new { |h, k| h[k] = schema_property_reader_module_compute(**k) }
@schema_property_writer_module_map = Hash.new { |h, k| h[k] = schema_property_writer_module_compute(**k) }
end
module SchemaModule::Connects
attr_reader :jsi_node
def name_from_ancestor
named_ancestor_schema, tokens = named_ancestor_schema_tokens
return nil unless named_ancestor_schema
name = named_ancestor_schema.jsi_schema_module.name
ancestor = named_ancestor_schema
tokens.each do |token|
if ancestor.jsi_property_readers.include?(token)
name += ".#{token}"
elsif [String, Numeric, TrueClass, FalseClass, NilClass].any? { |m| token.is_a?(m) }
name += "[#{token.inspect}]"
else
return nil
end
ancestor = ancestor[token]
end
name
end
def [](token, **kw, &block)
raise(ArgumentError) unless kw.empty? sub = @jsi_node[token]
if sub.is_a?(JSI::Schema)
sub.jsi_schema_module_exec(&block) if block
sub.jsi_schema_module
elsif block
raise(ArgumentError, "block given but token #{token.inspect} does not identify a schema")
elsif sub.is_a?(JSI::Base)
SchemaModule::Connection.new(sub)
else
sub
end
end
private
def named_ancestor_schema_tokens
schema_ancestors = @jsi_node.jsi_ancestor_nodes
named_ancestor_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
return nil unless named_ancestor_schema
tokens = @jsi_node.jsi_ptr.relative_to(named_ancestor_schema.jsi_ptr).tokens
[named_ancestor_schema, tokens]
end
end
class SchemaModule
include Connects
end
class SchemaModule::Connection
include SchemaModule::Connects
def initialize(node)
raise(Bug, "node must be JSI::Base: #{node.pretty_inspect.chomp}") unless node.is_a?(JSI::Base)
raise(Bug, "node must not be JSI::Schema: #{node.pretty_inspect.chomp}") if node.is_a?(JSI::Schema)
@jsi_node = node
node.jsi_schemas.each do |schema|
extend(JSI::SchemaClasses.schema_property_reader_module(schema, conflicting_modules: [SchemaModule::Connection]))
end
end
def inspect
if name_from_ancestor
-"#{name_from_ancestor} (#{self.class})"
else
-"(#{self.class}: #{@jsi_node.jsi_ptr.uri})"
end
end
alias_method :to_s, :inspect
end
end