# `Monty.ModuleMap`
[🔗](https://github.com/d-led/ex.tbp.monty/blob/v0.1.0/packages/ex_tbp_monty_core/lib/monty/module_map.ex#L1)

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.

# `entry`

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

# `class_module`

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

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

# `implemented?`

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

Whether a target resolves to a module that exists here.

# `load`

```elixir
@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`

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

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

# `moved`

```elixir
@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`

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

One package by name.

# `packages`

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

The Python packages this application mirrors.

# `python_files`

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

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

# `resolve`

```elixir
@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`

```elixir
@spec sources() :: %{optional(String.t()) =&gt; 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`

```elixir
@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`

```elixir
@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?`

```elixir
@spec written?(atom() | %{optional(atom()) =&gt; 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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
