Class: Torikago::PackageApiUpdater

Inherits:
Object
  • Object
show all
Defined in:
lib/torikago/package_api_updater.rb,
sig/torikago.rbs

Overview

Regenerates package_api.yml from files under a module's public API entrypoint while preserving exported methods and caller permissions already chosen by humans.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(configuration:) ⇒ PackageApiUpdater

Returns a new instance of PackageApiUpdater.

Parameters:



9
10
11
# File 'lib/torikago/package_api_updater.rb', line 9

def initialize(configuration:)
  @configuration = configuration
end

Instance Attribute Details

#configurationConfiguration (readonly)

Returns the value of attribute configuration.

Returns:



26
27
28
# File 'lib/torikago/package_api_updater.rb', line 26

def configuration
  @configuration
end

Instance Method Details

#allowed_callers(manifest_entry) ⇒ Array[untyped]

Parameters:

  • manifest_entry (Object)

Returns:

  • (Array[untyped])


61
62
63
64
65
66
67
68
# File 'lib/torikago/package_api_updater.rb', line 61

def allowed_callers(manifest_entry)
  return Array.new unless manifest_entry.is_a?(Hash)

  allowed = manifest_entry["allowed_callers"]
  return allowed if allowed.is_a?(Array)

  Array.new
end

#build_manifest(definition, existing_manifest) ⇒ Hash[String, untyped]

Parameters:

Returns:

  • (Hash[String, untyped])


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/torikago/package_api_updater.rb', line 40

def build_manifest(definition, existing_manifest)
  existing_public_api = exported_package_apis(existing_manifest)

  public_api_entries = discover_public_api_classes(definition).each_with_object(Hash.new) do |class_name, entries|
    existing_entry = existing_public_api.fetch(class_name, Hash.new)

    # update-package-api owns discovery, not policy. Keep methods and
    # allowed_callers so the command does not change the public surface.
    entries[class_name] = {
      "methods" => exported_methods(existing_entry),
      "allowed_callers" => allowed_callers(existing_entry).map { |caller| caller.to_s }
    }
  end

  { "exports" => public_api_entries }
end

#call(module_name = nil) ⇒ Hash[Symbol, Pathname]

Parameters:

  • module_name (Symbol, String, nil) (defaults to: nil)

Returns:

  • (Hash[Symbol, Pathname])


13
14
15
16
17
18
19
20
21
22
# File 'lib/torikago/package_api_updater.rb', line 13

def call(module_name = nil)
  definitions_for(module_name).each_with_object(Hash.new) do |definition, updates|
    manifest_path = definition.root.join("package_api.yml")
    existing_manifest = load_manifest(manifest_path)
    updated_manifest = build_manifest(definition, existing_manifest)

    manifest_path.write(render_manifest(updated_manifest))
    updates[definition.name] = manifest_path
  end
end

#camelize(segment) ⇒ String

Parameters:

  • segment (String)

Returns:

  • (String)


118
119
120
# File 'lib/torikago/package_api_updater.rb', line 118

def camelize(segment)
  segment.split("_").map(&:capitalize).join
end

#class_name_from(relative_path) ⇒ String

Parameters:

  • relative_path (String)

Returns:

  • (String)


114
115
116
# File 'lib/torikago/package_api_updater.rb', line 114

def class_name_from(relative_path)
  relative_path.delete_suffix(".rb").split("/").map { |segment| camelize(segment) }.join("::")
end

#definitions_for(module_name) ⇒ Array[Configuration::Definition]

Parameters:

  • module_name (Symbol, String, nil)

Returns:



28
29
30
31
32
# File 'lib/torikago/package_api_updater.rb', line 28

def definitions_for(module_name)
  return configuration.each_definition.to_a if module_name.nil?

  [configuration.fetch(module_name)]
end

#discover_public_api_classes(definition) ⇒ Array[String]

Parameters:

Returns:

  • (Array[String])


95
96
97
98
99
100
# File 'lib/torikago/package_api_updater.rb', line 95

def discover_public_api_classes(definition)
  Dir[public_api_root(definition).join("**/*.rb").to_s].sort.map do |path|
    relative_path = Pathname(path).relative_path_from(public_api_root(definition)).to_s
    class_name_from(relative_path)
  end
end

#exported_methods(manifest_entry) ⇒ Array[String]

Parameters:

  • manifest_entry (Object)

Returns:

  • (Array[String])


70
71
72
73
74
75
76
77
# File 'lib/torikago/package_api_updater.rb', line 70

def exported_methods(manifest_entry)
  return Array.new unless manifest_entry.is_a?(Hash)

  methods = manifest_entry["methods"]
  return methods.map(&:to_s) if methods.is_a?(Array)

  Array.new
end

#exported_package_apis(manifest) ⇒ Hash[String, untyped]

Parameters:

  • manifest (Hash[String, untyped])

Returns:

  • (Hash[String, untyped])


57
58
59
# File 'lib/torikago/package_api_updater.rb', line 57

def exported_package_apis(manifest)
  manifest.fetch("exports") { manifest.fetch("public_api", Hash.new) }
end

#load_manifest(path) ⇒ Hash[String, untyped]

Parameters:

  • path (Pathname)

Returns:

  • (Hash[String, untyped])


34
35
36
37
38
# File 'lib/torikago/package_api_updater.rb', line 34

def load_manifest(path)
  return Hash.new unless path.exist?

  YAML.safe_load(path.read, permitted_classes: Array.new, aliases: false) || Hash.new
end

#public_api_root(definition) ⇒ Pathname

Parameters:

Returns:

  • (Pathname)


102
103
104
105
106
107
108
109
110
111
112
# File 'lib/torikago/package_api_updater.rb', line 102

def public_api_root(definition)
  return definition.root.join("app/package_api") if definition.entrypoint.nil?

  # Match EngineContainer and Checker: directory entrypoints are roots,
  # while file entrypoints imply implementations live beside the file.
  candidate = definition.root.join(definition.entrypoint)
  return candidate if candidate.directory?
  return candidate unless candidate.extname == ".rb"

  candidate.dirname
end

#render_manifest(manifest) ⇒ String

Parameters:

  • manifest (Hash[String, untyped])

Returns:

  • (String)


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/torikago/package_api_updater.rb', line 79

def render_manifest(manifest)
  <<~YAML
    # This file declares the Package APIs exported by this module.
    #
    # Each key under exports is a class that may be called through:
    #
    #   Torikago::Gateway.invoke("ModuleName::SomeQuery", :call)
    #
    # methods lists the public instance methods exposed through Gateway.
    # allowed_callers lists other modules that may call that export. The
    # host app and the module itself are allowed implicitly.
    #
  YAML
    .then { |header| header + YAML.dump(manifest) }
end