Class: Metaschema::RubySourceEmitter

Inherits:
Object
  • Object
show all
Defined in:
lib/metaschema/ruby_source_emitter.rb

Overview

Emits Ruby source code from generated metaschema classes.

After ModelGenerator#generate creates in-memory classes, this class introspects them and emits equivalent Ruby source code that can be saved to .rb files and loaded with ‘require`.

Handles three kinds of type references:

  1. Builtin types (:string, :integer, etc.) — emitted as symbol literals

  2. Generated types (in @classes) — emitted as fully-qualified string refs

  3. Framework types (named, from other gems) — emitted as bare class refs

  4. Anonymous inline types — collected and emitted as separate named classes

Usage:

files = Metaschema::ModelGenerator.to_ruby_source(
  "oscal_complete_metaschema.xml",
  module_name: "Oscal::V1_2_1"
)
files.each { |name, source| File.write(name, source) }

Constant Summary collapse

BUILTIN_TYPES =
%i[string integer boolean float date time datetime
symbol].freeze
RESERVED_CLASS_NAMES =
%w[Base Hash Method Object Class Module].freeze

Instance Method Summary collapse

Constructor Details

#initialize(classes, module_name, generator) ⇒ RubySourceEmitter

Returns a new instance of RubySourceEmitter.



28
29
30
31
32
33
34
# File 'lib/metaschema/ruby_source_emitter.rb', line 28

def initialize(classes, module_name, generator)
  @classes = classes
  @module_name = module_name
  @generator = generator
  @class_name_cache = {}
  @anon_name_map = {} # anonymous class → assigned name
end

Instance Method Details

#emitObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/metaschema/ruby_source_emitter.rb', line 36

def emit
  sorted = sort_classes
  collect_anonymous_types(sorted)
  files = {}

  source = emit_module_header

  # Emit anonymous types first (they're dependencies of named classes)
  @anon_name_map.each_value do |anon_name|
    anon_class = @anon_name_map.key(anon_name)
    source += "\n#{emit_anonymous_class(anon_name, anon_class)}"
  end

  sorted.each do |key, klass|
    next unless klass.is_a?(Class) && klass < Lutaml::Model::Serializable

    source += "\n#{emit_class(key, klass)}"
  end
  source += emit_module_footer
  files["all_models.rb"] = source

  files
end

#emit_splitObject

Emit as separate files per root model type.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/metaschema/ruby_source_emitter.rb', line 61

def emit_split
  sorted = sort_classes
  collect_anonymous_types(sorted)
  root_classes = find_root_classes
  emitted = Set.new
  files = {}

  root_classes.each do |root_key, root_klass|
    deps = find_dependencies(root_key, root_klass)
    all_keys = ([root_key] + deps).uniq

    source = emit_module_header

    # Emit anonymous types needed by this root's dependency tree
    emit_anon_deps_for(all_keys, source)

    all_keys.each do |key|
      klass = @classes[key]
      next unless klass.is_a?(Class) && klass < Lutaml::Model::Serializable
      next if emitted.include?(key)

      source += "\n#{emit_class(key, klass)}"
      emitted.add(key)
    end
    source += emit_module_footer

    filename = clean_class_name(root_key).gsub(/([a-z])([A-Z])/,
                                               '\1_\2').downcase + ".rb"
    files[filename] = source
  end

  # Emit any remaining classes not covered by roots
  remaining = sorted.except(*emitted)
  unless remaining.empty?
    source = emit_module_header
    remaining.each do |key, klass|
      next unless klass.is_a?(Class) && klass < Lutaml::Model::Serializable

      source += "\n#{emit_class(key, klass)}"
    end
    source += emit_module_footer
    files["common.rb"] = source
  end

  files
end