Monty.ModuleMap (ExTbpMonty.Core v0.1.0)

Copy Markdown View Source

The contract that keeps this rewrite module for module.

One namespace per TBP package, and one rule inside each:

tbp/monty/<path>/<file>.py::Class  ->  Monty.<Path>.<File>.<Class>
tbp/teleop/<file>.py::Class        ->  Teleop.<File>.<Class>

with a repeated segment collapsed and the class dropped when it repeats the file name. A Monty developer should be able to take a Python file, apply the rule, and find the Elixir module that holds it — for the runtime and for the teleop tooling this application replaces with LiveView.

A module that exists here is implemented; one that does not is planned; one that will never exist (Hydra's own tooling) is excluded with a reason.

Summary

Functions

The Elixir module a class maps to, given its file's module.

Whether a target resolves to a module that exists here.

The generated map.

The Elixir module a Python file maps to, within a package namespace.

Packages whose checkout has moved away from the commit the map names.

One package by name.

The Python packages this application mirrors.

Python files of a package, or [] when the checkout is absent.

Resolves a Python dotted class path to its entry in the map.

The commit each package was at when the map was generated.

Counts by status, of the classes and of the files that hold them.

Checks the map against every checkout and against the compiled code.

Whether this status is a module this application has.

Types

entry()

@type entry() :: %{
  :package => String.t(),
  :python => String.t(),
  :elixir => String.t(),
  :status => :implemented | :planned | :excluded,
  optional(:reason) => String.t() | nil,
  classes: [
    %{
      :python => String.t(),
      :elixir => String.t(),
      :status => atom(),
      optional(:reason) => String.t() | nil
    }
  ]
}

Functions

class_module(file_module, class, relative_python_path)

@spec class_module(String.t(), String.t(), String.t()) :: String.t()

The Elixir module a class maps to, given its file's module.

implemented?(dotted, opts \\ [])

@spec implemented?(String.t(), keyword()) :: boolean()

Whether a target resolves to a module that exists here.

load()

@spec load() :: [entry()]

The generated map.

It is compiled in by mix monty.module_map.gen, not read from a file: the map is data, and a file read at run time is a path whose contents decide what this application does next.

module_for(namespace, relative_python_path)

@spec module_for(String.t(), String.t()) :: String.t()

The Elixir module a Python file maps to, within a package namespace.

moved()

@spec moved() :: [{String.t(), String.t() | nil, String.t()}]

Packages whose checkout has moved away from the commit the map names.

Answers {package, recorded_commit, current_commit}, empty when the map is current or when a checkout cannot be asked — a package unpacked from an archive has no commit, and that is not a drift.

package(name)

@spec package(String.t()) :: map() | nil

One package by name.

packages()

@spec packages() :: [map()]

The Python packages this application mirrors.

python_files(package)

@spec python_files(map()) :: [String.t()]

Python files of a package, or [] when the checkout is absent.

resolve(dotted, opts \\ [])

@spec resolve(String.t(), keyword()) :: {:ok, map()} | :error

Resolves a Python dotted class path to its entry in the map.

Monty's configs name classes the way Python imports them (tbp.monty.frameworks.models.sensor_modules.CameraSM), so this is how a composed config is read back as "which modules does this experiment use, and are they ported?".

A class the map does not list is reported as :unknown rather than guessed at: the map is generated from the Python source, so a missing class means the target names something the generator did not see.

sources()

@spec sources() :: %{optional(String.t()) => map()}

The commit each package was at when the map was generated.

The map describes a checkout at a moment, and checkouts move: the map carries that moment so a regenerated map shows it in the diff, and so a stale map can be recognised rather than trusted.

summary(entries)

@spec summary([entry()]) :: map()

Counts by status, of the classes and of the files that hold them.

The count that answers "how much of Monty is ported" is the classes': a file is ported when the classes a config can name in it are, and most of Monty's files hold several. files is there too, because a file is what a reader opens, and the two numbers are different questions rather than two spellings of one.

verify(opts \\ [])

@spec verify(keyword()) :: {:ok, map()} | {:error, [String.t()]}

Checks the map against every checkout and against the compiled code.

Returns a summary on success and the list of problems otherwise — the mix task prints them, the test asserts them empty.

written?(status)

@spec written?(atom() | %{optional(atom()) => term()}) :: boolean()

Whether this status is a module this application has.

Two statuses answer yes. :implemented is Monty's class ported whole; :partial is one ported as far as anything here uses it, with the shortfall written down beside it (docs/partial-classes.md). Both are modules that exist and a config can name, so every question of the form "can this run?" asks this and not the word implemented — a partial class that made its config unrunnable would be a port that refuses an experiment it can step.