MobDev.Plugin.Assets (mob_dev v0.6.16)

Copy Markdown View Source

Pure planners for the tier-3 build-time file merges — migrations, fonts, and images — that native_build copies into the host app at build.

Unlike the runtime manifest (behavioral data read on device), these are physical files: a plugin's migration .exs, font, and image files are meaningless as build-machine paths on device, so they're copied into the host bundle at build time. This module computes what gets copied where (pure + unit-tested); native_build does the I/O (listing dirs, copying, patching Info.plist).

Summary

Functions

The Android res/font/ resource name for a font file: lowercase, the extension dropped, and any character outside [a-z0-9_] replaced with _ (Android resource-name rules). "Georgia.ttf""georgia", "Inter-Regular.ttf""inter_regular". The renderer normalises the font: prop the same way to find the resource. A leading non-letter is prefixed with f_ so the name is a valid resource identifier.

The on-device bundle path a plugin://<plugin>/<file> reference resolves to. Plugin images are copied here at build time; the core plugin:// resolver maps to the same convention. Returns a path relative to the app bundle root.

Merges font basenames into an iOS Info.plist XML string under UIAppFonts, creating the array if absent and de-duplicating existing entries. Pure string transform over the plist's XML (the same approach as the plist-keys merge).

Plans the migration copies: maps each plugin migration source file to a destination under the host's migrations_dir, prefixed with the plugin's repo_namespace so files from different vendors don't collide.

Namespaces a migration filename with the plugin's repo_namespace, inserting it into the name part after the <version>_ prefix so Ecto can still parse the leading-integer version (20260101000000_create.exs20260101000000_kv_create.exs). Files without a numeric version prefix fall back to a plain prefix.

Extracts the current UIAppFonts entries from an Info.plist (or []).

Plans the Android res/font/ copies: each distinct source maps to <android_font_resource_name>.<ext>. Two distinct sources normalising to the same resource name (e.g. Inter-Regular.ttf and Inter_Regular.ttf both → inter_regular.ttf) would silently overwrite each other, so that is surfaced as an error.

Plans the iOS font-bundle copies: each distinct source font copies to the .app root under its basename (and is listed in UIAppFonts by basename). Two distinct sources sharing a basename (e.g. two plugins both shipping Icons.ttf) would silently overwrite each other in the bundle and collapse to a single UIAppFonts entry, so that is surfaced as an error instead of a silent loss.

Functions

android_font_resource_name(filename)

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

The Android res/font/ resource name for a font file: lowercase, the extension dropped, and any character outside [a-z0-9_] replaced with _ (Android resource-name rules). "Georgia.ttf""georgia", "Inter-Regular.ttf""inter_regular". The renderer normalises the font: prop the same way to find the resource. A leading non-letter is prefixed with f_ so the name is a valid resource identifier.

image_bundle_path(plugin, basename)

@spec image_bundle_path(atom() | String.t(), String.t()) :: String.t()

The on-device bundle path a plugin://<plugin>/<file> reference resolves to. Plugin images are copied here at build time; the core plugin:// resolver maps to the same convention. Returns a path relative to the app bundle root.

merge_ui_app_fonts(plist, font_basenames)

@spec merge_ui_app_fonts(String.t(), [String.t()]) :: String.t()

Merges font basenames into an iOS Info.plist XML string under UIAppFonts, creating the array if absent and de-duplicating existing entries. Pure string transform over the plist's XML (the same approach as the plist-keys merge).

migration_copies(plugin_migrations, dest_dir)

@spec migration_copies([%{repo_namespace: String.t(), files: [Path.t()]}], Path.t()) ::
  [
    {Path.t(), Path.t()}
  ]

Plans the migration copies: maps each plugin migration source file to a destination under the host's migrations_dir, prefixed with the plugin's repo_namespace so files from different vendors don't collide.

Takes [%{repo_namespace, files: [src_path]}] (the caller lists each plugin's migration dir) and the host migrations dir; returns [{src, dest}].

namespaced_filename(ns, filename)

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

Namespaces a migration filename with the plugin's repo_namespace, inserting it into the name part after the <version>_ prefix so Ecto can still parse the leading-integer version (20260101000000_create.exs20260101000000_kv_create.exs). Files without a numeric version prefix fall back to a plain prefix.

The namespace is always inserted — there is no "already namespaced?" guard. Migration sources are always the plugin author's raw files (never our output), so re-run idempotency comes from the deterministic source→dest mapping, not from inspecting the name. A guard that skipped namespacing when the description happened to begin with the namespace text would silently drop the namespace and cause cross-vendor collisions, so we don't do it.

parse_ui_app_fonts(plist)

@spec parse_ui_app_fonts(String.t()) :: [String.t()]

Extracts the current UIAppFonts entries from an Info.plist (or []).

plan_android_font_copies(fonts)

@spec plan_android_font_copies([Path.t()]) ::
  {:ok, [{Path.t(), String.t()}]}
  | {:error, {:font_resource_collision, String.t(), [Path.t()]}}

Plans the Android res/font/ copies: each distinct source maps to <android_font_resource_name>.<ext>. Two distinct sources normalising to the same resource name (e.g. Inter-Regular.ttf and Inter_Regular.ttf both → inter_regular.ttf) would silently overwrite each other, so that is surfaced as an error.

Returns {:ok, [{src, res_filename}]} or {:error, {:font_resource_collision, res_filename, [src, ...]}}.

plan_ios_font_bundle(fonts)

@spec plan_ios_font_bundle([Path.t()]) ::
  {:ok, [{Path.t(), String.t()}]}
  | {:error, {:font_basename_collision, String.t(), [Path.t()]}}

Plans the iOS font-bundle copies: each distinct source font copies to the .app root under its basename (and is listed in UIAppFonts by basename). Two distinct sources sharing a basename (e.g. two plugins both shipping Icons.ttf) would silently overwrite each other in the bundle and collapse to a single UIAppFonts entry, so that is surfaced as an error instead of a silent loss.

Returns {:ok, [{src, dest_basename}]} or {:error, {:font_basename_collision, basename, [src, ...]}}.