# `MobNew.ProjectGenerator`
[🔗](https://github.com/genericjam/mob_new/blob/master/lib/mob_new/project_generator.ex#L1)

Generates a new Mob project from EEx templates in `priv/templates/mob.new/`.

## Naming conventions

Given `app_name = "my_cool_app"` and the default bundle prefix:
  - `module_name`  → `"MyCoolApp"`
  - `display_name` → `"MyCoolApp"`
  - `bundle_id`    → `"com.example.my_cool_app"`
  - `java_package` → `"com.example.my_cool_app"`
  - `lib_name`     → `"mycoolapp"` (no underscores, for `System.loadLibrary`)
  - `java_path`    → `"com/example/my_cool_app"` (for directory structure)

## Bundle prefix

The reverse-DNS prefix for the bundle ID defaults to `com.example`, the
universal "must change before shipping" placeholder. Override at generation
time with the `MOB_BUNDLE_PREFIX` env var:

    MOB_BUNDLE_PREFIX=net.acme mix mob.new my_cool_app
    # → bundle_id = "net.acme.my_cool_app"

We deliberately do **not** use `com.mob` — that's our reverse-DNS namespace,
and Apple/Google enforce ownership at submission time, so a project that
ships with `com.mob.*` would have to be renamed before reaching either store.

# `apply_python_patches`

```elixir
@spec apply_python_patches(String.t(), String.t()) :: :ok
```

Patches a generated project to enable Pythonx (embedded CPython,
iOS + Android).

Two patches:
  * `mix.exs` — adds `{:pythonx, "~> 0.4"}` to deps.
  * `lib/<app>/python_paths.ex` — pure detection module that reads
    `:code.root_dir/0` for iOS and `MOB_PYTHON_HOME` / `MOB_PYTHON_DL`
    env vars (set by Android's `MainActivity`) for Android. Returns
    `:desktop` / `{:ios, paths}` / `{:android, paths}` /
    `{:partial, missing}`.

Note: deliberately does NOT patch `config/config.exs` — `:pythonx,
:uv_init` in compile-time config makes `Pythonx.Application.start/2`
auto-run uv at boot, which fails on device. The generated `app.ex`
(when --python is set) inlines the pyproject_toml and calls
`Pythonx.Uv.fetch + init` only on the `:desktop` branch.

Mirrors `mix mob.enable pythonx` (in `mob_dev`). Idempotent — safe to
run twice. Public for testing.

# `assigns`

```elixir
@spec assigns(
  String.t(),
  keyword()
) :: map()
```

Returns the EEx template assigns map for `app_name`.

Options:
- `:local` — when `true`, generates `path:` deps pointing to local mob/mob_dev
  repos instead of hex version constraints. Paths are resolved from the
  `MOB_DIR` and `MOB_DEV_DIR` environment variables, falling back to
  `../mob` and `../mob_dev` relative to the generated project location.

# `bundle_prefix`

```elixir
@spec bundle_prefix() :: String.t()
```

# `generate`

```elixir
@spec generate(String.t(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}
```

Generates a new project at `dest_dir/<app_name>` from the bundled templates.

Returns `{:ok, project_dir}` or `{:error, reason}`.

# `liveview_generate`

```elixir
@spec liveview_generate(String.t(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}
```

Generates a LiveView-wrapped Mob project at `dest_dir/<app_name>`.

This calls `mix phx.new` as a subprocess to create the Phoenix project, then:
- Patches `mix.exs` to add the mob / mob_dev dependencies
- Copies the standard Android/iOS native boilerplate
- Applies the `mix mob.enable liveview` patches:
  - Injects `MobHook` into `assets/js/app.js`
  - Injects the bridge `<div>` into `root.html.heex`
  - Generates `lib/<app>/mob_screen.ex`
  - Writes `mob.exs` with `liveview_port: 4000`
- Patches `lib/<app>/application.ex` to start `Mob.App` alongside Phoenix

Returns `{:ok, project_dir}` or `{:error, reason}`.

# `liveview_phoenix_owned?`

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

When generating a LiveView project, `mix phx.new` already produced its own
mix.exs, config/, lib/<app>/, lib/<app>_web/, .gitignore, and assets/ — and
those are the *correct* versions for a Phoenix app (with gettext,
telemetry_metrics, etc.). The native template's same-named files are
written for the bare-Mob path and would clobber Phoenix's, leaving the
project unable to compile.

When `:liveview` is true in opts, this predicate returns true for any
template path that Phoenix already owns, so the copy step skips it. Files
that are unique to Mob (mob.exs, src/<app>.erl, android/, ios/) still get
emitted normally.

Public for testing — guards against the regression where a new template
path lands in the native tree without being added to the LiveView
blocklist.

---

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