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

TTRPG-Dev / ex_ttrpg_dev / d524a0e49003859b6341b86011cf5d0572df4cdc-PR-107

29 Mar 2026 12:17AM UTC coverage: 87.9% (-0.02%) from 87.916%
d524a0e49003859b6341b86011cf5d0572df4cdc-PR-107

Pull #107

github

QMalcolm
feat(cli): add --random-resolve flag to characters resolve_choice

Adds characters resolve_choice <slug> --random-resolve which calls the
new characters.random_resolve engine command. Displays the updated
character sheet followed by a bullet-point summary of every resolved
choice — showing the selected concept name (for selection types) or the
rolled/average value and method (for value types) alongside the level
at which the choice was earned.
Pull Request #107: feat: add characters resolve_choice --random-resolve flag

56 of 73 new or added lines in 2 files covered. (76.71%)

1 existing line in 1 file now uncovered.

988 of 1124 relevant lines covered (87.9%)

13131.14 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"] || ""
17✔
37
  def render(nil, fields, :default), do: fields["name"] || ""
110✔
38
  def render(template, fields, :default), do: apply_template(template, fields)
218✔
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)
218✔
47
  end
48

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

55
  defp replace_substitutions(template, fields) do
56
    Regex.replace(~r/\{\{(\w+)\}\}/, template, fn _, field ->
218✔
57
      to_string(fields[field] || "")
534✔
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

UNCOV
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
69✔
78
  defp truthy?(0), do: false
×
79
  defp truthy?(""), do: false
×
80
  defp truthy?(_), do: true
224✔
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