• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TTRPG-Dev / ex_ttrpg_dev / 7e6d277ce8bfab10ae50bf89a12be8918834f826

28 Mar 2026 06:17AM UTC coverage: 84.64% (-9.9%) from 94.539%
7e6d277ce8bfab10ae50bf89a12be8918834f826

push

github

web-flow
Merge pull request #91 from TTRPG-Dev/qmalcolm--concept-display-templates

feat: concept display templates and display mode

57 of 67 new or added lines in 3 files covered. (85.07%)

788 of 931 relevant lines covered (84.64%)

9620.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/apps/ttrpg_dev_cli/lib/cli/concept_display.ex
1
defmodule ExTTRPGDev.CLI.ConceptDisplay do
2
  @moduledoc """
3
  Renders concept metadata into a display string for the CLI.
4

5
  Three modes are supported:
6

7
  - `:succinct` — name only (the `"name"` field in the metadata map).
8
  - `:default` — uses the system-defined display template when one is provided;
9
    falls back to `:succinct` when no template is available.
10
  - `:verbose` — all metadata fields formatted as `key: value` pairs on one line,
11
    with the name first.
12

13
  Templates are plain strings with two substitution constructs:
14

15
  - `{{field}}` — replaced with the string value of `fields["field"]`, or `""` when absent.
16
  - `{{?field:text}}` — replaced with `text` when `fields["field"]` is truthy
17
    (non-nil, non-false, non-zero, non-empty-string); replaced with `""` otherwise.
18

19
  Example template:
20

21
      "{{name}}: Level {{level}}, {{school}} ({{?verbal:V}}{{?somatic:S}}{{?material:M}})"
22

23
  With `%{"name" => "Fire Bolt", "level" => 1, "school" => "evocation",
24
  "verbal" => true, "somatic" => true, "material" => false}` in `:default` mode
25
  this renders as `"Fire Bolt: Level 1, evocation (VS)"`.
26
  """
27

28
  @doc """
29
  Renders `fields` into a display string according to `mode`.
30

31
  `template` is the system-defined template string (may be `nil`).
32
  `fields` is the concept's raw metadata map (string keys).
33
  `mode` is `:succinct`, `:default`, or `:verbose`.
34
  """
35
  @spec render(String.t() | nil, map(), :succinct | :default | :verbose) :: String.t()
36
  def render(_template, fields, :succinct), do: fields["name"] || ""
16✔
37
  def render(nil, fields, :default), do: fields["name"] || ""
50✔
38
  def render(template, fields, :default), do: apply_template(template, fields)
106✔
39
  def render(_template, fields, :verbose), do: format_verbose(fields)
10✔
40

41
  # --- Helpers ---
42

43
  defp apply_template(template, fields) do
44
    template
45
    |> replace_conditionals(fields)
46
    |> replace_substitutions(fields)
106✔
47
  end
48

49
  defp replace_conditionals(template, fields) do
50
    Regex.replace(~r/\{\{\?(\w+):([^}]*)\}\}/, template, fn _, field, text ->
106✔
51
      if truthy?(fields[field]), do: text, else: ""
111✔
52
    end)
53
  end
54

55
  defp replace_substitutions(template, fields) do
56
    Regex.replace(~r/\{\{(\w+)\}\}/, template, fn _, field ->
106✔
57
      to_string(fields[field] || "")
249✔
58
    end)
59
  end
60

61
  defp format_verbose(fields) do
62
    name = fields["name"] || ""
10✔
63

64
    details =
10✔
65
      fields
66
      |> Map.drop(["name", "hidden"])
67
      |> Enum.sort_by(fn {k, _} -> k end)
30✔
68
      |> Enum.map_join("  ", fn {k, v} -> "#{k}: #{value_to_string(v)}" end)
30✔
69

70
    if details == "", do: name, else: "#{name}  #{details}"
10✔
71
  end
72

NEW
73
  defp value_to_string(v) when is_map(v) or is_list(v), do: inspect(v)
×
74
  defp value_to_string(v), do: to_string(v)
30✔
75

76
  defp truthy?(nil), do: false
1✔
77
  defp truthy?(false), do: false
35✔
NEW
78
  defp truthy?(0), do: false
×
NEW
79
  defp truthy?(""), do: false
×
80
  defp truthy?(_), do: true
75✔
81
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc