MishkaChelekom.Generators.Core (Mishka Chelekom v0.0.9)

Copy Markdown View Source

Shared generator plumbing used by the styled (mix mishka.ui.gen.component(s)) and headless (mix mishka.ui.gen.headless(.components)) tasks.

This is the reusable foundation the mix tasks build on: library priv/ resolution, catalog discovery/validation, component naming, the EEx assign skeleton, generated-file tracking and prefix persistence. Each mix task stays a thin front-end over these helpers.

Larger, more specialized concerns live in sibling modules of the same namespace: MishkaChelekom.Generators.Preflight, .Assets, .Multi and .ImportMacro.

Summary

Functions

Every available component name for a layer (sorted, unique basenames of the .eex files).

Appends [flag, value] to args when value is present (not nil/empty).

Prints the Mishka owl banner in color, optionally with a subtitle line beneath it.

Verifies Phoenix >= 1.8.0 and Tailwind >= 4.0.0, adding an issue per problem found. Returns the (possibly annotated) igniter.

Turns a component name string into an atom (used to index a catalog .exs).

Converts a CSV string or list of values into a trimmed list of strings.

Builds the EEx assigns shared by both layers: nil-fills every declared catalog arg, then merges :module, :web_module, :component_prefix and :module_prefix_camel.

Creates the sample priv/mishka_chelekom/config.exs if it does not exist yet (idempotent).

Composes task once per component in list, prepending each name to extra_args.

Resolves a component's .eex template and sibling .exs catalog, reads and validates it.

Joins a sub-path onto the library priv/ dir.

Returns the absolute path to the library's priv/ directory.

Persists --component-prefix/--module-prefix to the user config unless this is a sub generation (--sub) or saving is disabled (--no-save).

Turns a dotted, lower-case string into a module atom ("a_b.c" -> :"AB.C").

Like module_atom/1 but keeps only the last segment ("a.b.c" -> :C).

Resolves the component list to generate for a plural task.

The catalog template directory for a layer.

Records a generated file in igniter_exs[:dont_move_files] so Igniter does not relocate it.

Validates that a loaded catalog config has the minimal expected shape, returning {:ok, config} or {:error, reason}.

Functions

all_component_names(igniter, atom)

@spec all_component_names(Igniter.t(), :styled | :headless) :: [String.t()]

Every available component name for a layer (sorted, unique basenames of the .eex files).

:styled also discovers host-app custom templates under priv/mishka_chelekom/{components,templates,presets}.

append_arg(args, flag, value)

@spec append_arg([String.t()], String.t(), String.t() | nil) :: [String.t()]

Appends [flag, value] to args when value is present (not nil/empty).

banner(color, subtitle \\ nil)

@spec banner(String.t(), String.t() | nil) :: :ok

Prints the Mishka owl banner in color, optionally with a subtitle line beneath it.

check_dependencies(igniter)

@spec check_dependencies(Igniter.t()) :: Igniter.t()

Verifies Phoenix >= 1.8.0 and Tailwind >= 4.0.0, adding an issue per problem found. Returns the (possibly annotated) igniter.

component_atom(component_str)

@spec component_atom(String.t()) :: atom()

Turns a component name string into an atom (used to index a catalog .exs).

convert_options(value)

@spec convert_options(nil | String.t() | [String.t()]) :: nil | [String.t()]

Converts a CSV string or list of values into a trimmed list of strings.

eex_assigns(igniter, component_module, config, opts \\ [])

@spec eex_assigns(Igniter.t(), module(), keyword(), keyword()) :: keyword()

Builds the EEx assigns shared by both layers: nil-fills every declared catalog arg, then merges :module, :web_module, :component_prefix and :module_prefix_camel.

opts:

  • :values - already-validated arg values to seed (styled); defaults to [] (headless)
  • :component_prefix - resolved public-function prefix (or nil)
  • :module_prefix - resolved module-name prefix used to derive module_prefix_camel

ensure_user_config(igniter)

@spec ensure_user_config(Igniter.t()) :: Igniter.t()

Creates the sample priv/mishka_chelekom/config.exs if it does not exist yet (idempotent).

Shared by the styled and headless setup so both layers handle the config file the same way.

fan_out(igniter, task, list, extra_args)

@spec fan_out(Igniter.t(), String.t(), [String.t()], [String.t()]) :: Igniter.t()

Composes task once per component in list, prepending each name to extra_args.

fetch_catalog(igniter, component, layer)

@spec fetch_catalog(Igniter.t(), String.t(), :styled | :headless) ::
  {:ok, %{component: String.t(), path: String.t(), config: keyword()}}
  | {:error, {:not_found, String.t()} | {:bad_catalog, String.t(), String.t()}}

Resolves a component's .eex template and sibling .exs catalog, reads and validates it.

Returns {:ok, %{component, path, config}} (the underscored name, the template path and the validated catalog) or a structured error the caller turns into a user-facing message:

  • {:error, {:not_found, path}}
  • {:error, {:bad_catalog, reason, config_path}}

For :styled, names prefixed component_/preset_/template_ resolve against the host app's priv/mishka_chelekom/{components,presets,templates}; everything else (and all :headless names) resolves against the library priv/.

lib_priv(sub)

@spec lib_priv(String.t()) :: String.t()

Joins a sub-path onto the library priv/ dir.

iex> MishkaChelekom.Generators.Core.lib_priv("components/button.eex")

lib_priv_dir()

@spec lib_priv_dir() :: String.t()

Returns the absolute path to the library's priv/ directory.

Prefers :code.priv_dir(:mishka_chelekom) (works for hex deps, path deps, and umbrellas), falling back to the legacy "deps/mishka_chelekom/priv" string only if the app priv dir is unavailable.

maybe_save_prefixes(igniter, options)

@spec maybe_save_prefixes(
  Igniter.t(),
  keyword()
) :: Igniter.t()

Persists --component-prefix/--module-prefix to the user config unless this is a sub generation (--sub) or saving is disabled (--no-save).

module_atom(field)

@spec module_atom(String.t()) :: atom()

Turns a dotted, lower-case string into a module atom ("a_b.c" -> :"AB.C").

module_atom(field, atom)

@spec module_atom(String.t(), :last) :: atom()

Like module_atom/1 but keeps only the last segment ("a.b.c" -> :C).

resolve_components(igniter, requested_csv, layer, user_config, cli_exclude)

@spec resolve_components(
  Igniter.t(),
  String.t() | nil,
  :styled | :headless,
  map(),
  [String.t()] | nil
) :: [String.t()]

Resolves the component list to generate for a plural task.

An empty request (or one containing "all") expands to every component of layer; otherwise the requested names are used. Names in the user config :exclude_components or cli_exclude are removed.

template_dir(atom)

@spec template_dir(:styled | :headless) :: String.t()

The catalog template directory for a layer.

  • :styledpriv/components
  • :headlesspriv/headless

track_generated_file(igniter, location)

@spec track_generated_file(Igniter.t(), String.t()) :: Igniter.t()

Records a generated file in igniter_exs[:dont_move_files] so Igniter does not relocate it.

validate_catalog(config)

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

Validates that a loaded catalog config has the minimal expected shape, returning {:ok, config} or {:error, reason}.