diff --git a/lib/rule_systems/characters.ex b/lib/characters.ex similarity index 72% rename from lib/rule_systems/characters.ex rename to lib/characters.ex index 33f7e71..f2d496a 100644 --- a/lib/rule_systems/characters.ex +++ b/lib/characters.ex @@ -1,44 +1,22 @@ -defmodule ExTTRPGDev.RuleSystems.Characters do +defmodule ExTTRPGDev.Characters do @moduledoc """ - This module handles the definition of rule system characters, and what they do + This module handles handles character operations """ + alias ExTTRPGDev.Characters.Character + alias ExTTRPGDev.Characters.Metadata alias ExTTRPGDev.Globals - defmodule CharacterMetadata do - @moduledoc """ - Metadata for an individual charater - """ - defstruct [:slug, :rule_system] - end - - defmodule Character do - @moduledoc """ - Definition of an individual character - """ - defstruct [:name, :ability_scores, :metadata] - end - - def from_json!(character_json) when is_bitstring(character_json) do - character_json - |> Poison.decode!( - as: %Character{ - metadata: %CharacterMetadata{ - rule_system: %ExTTRPGDev.RuleSystems.Metadata{} - } - } - ) - end - @doc """ Get the file path for a character ## Examples - iex> Characters.character_file_path!(%Character{metadata: %CharacterMetadata{slug: "mr_whiskers"}}) + iex> Characters.character_file_path!(%Character{metadata: %Characters.Metadata{slug: "mr_whiskers"}}) "mr_whiskers.json" """ - def character_file_path!(%Character{metadata: %CharacterMetadata{slug: slug}}), - do: character_file_path!(slug) + def character_file_path!(%Character{metadata: %Metadata{slug: slug}}) do + character_file_path!(slug) + end def character_file_path!(character_slug) when is_bitstring(character_slug) do Path.join(Globals.characters_path(), "#{character_slug}.json") @@ -112,6 +90,6 @@ defmodule ExTTRPGDev.RuleSystems.Characters do def load_character!(character_slug) do character_file_path!(character_slug) |> File.read!() - |> from_json!() + |> Character.from_json!() end end diff --git a/lib/characters/character.ex b/lib/characters/character.ex new file mode 100644 index 0000000..6c3e779 --- /dev/null +++ b/lib/characters/character.ex @@ -0,0 +1,51 @@ +defmodule ExTTRPGDev.Characters.Character do + alias __MODULE__ + alias ExTTRPGDev.Characters.Metadata + alias ExTTRPGDev.RuleSystems + + @moduledoc """ + Definition of an individual character + """ + defstruct [:name, :ability_scores, :metadata] + + @doc """ + Load a character from json representation + """ + def from_json!(character_json) when is_bitstring(character_json) do + character_json + |> Poison.decode!( + as: %Character{ + metadata: %Metadata{ + rule_system: %RuleSystems.Metadata{} + } + } + ) + end + + @doc """ + Returns an auto generated character for the system + + ## Examples + iex> Character.gen_character(rule_system) + %Character{} + """ + def gen_character!(%RuleSystems.RuleSystem{ + abilities: %RuleSystems.Abilities{} = abilities, + metadata: %RuleSystems.Metadata{} = rule_system_metadata + }) do + character_name = Faker.Person.name() + + %Character{ + name: character_name, + ability_scores: RuleSystems.Abilities.gen_scores(abilities), + metadata: %ExTTRPGDev.Characters.Metadata{ + slug: + character_name + |> String.downcase() + |> String.replace(~r/[!#$%&()*+,.:;<=>?@\^_`'{|}~-]/, "") + |> String.replace(" ", "_"), + rule_system: rule_system_metadata + } + } + end +end diff --git a/lib/characters/metadata.ex b/lib/characters/metadata.ex new file mode 100644 index 0000000..69958f0 --- /dev/null +++ b/lib/characters/metadata.ex @@ -0,0 +1,6 @@ +defmodule ExTTRPGDev.Characters.Metadata do + @moduledoc """ + Metadata for an individual charater + """ + defstruct [:slug, :rule_system] +end diff --git a/lib/cli.ex b/lib/cli.ex index d17123b..b718173 100644 --- a/lib/cli.ex +++ b/lib/cli.ex @@ -1,11 +1,8 @@ # credo:disable-for-this-file Credo.Check.Warning.IoInspect defmodule ExTTRPGDev.CLI do - alias ExTTRPGDev.Dice - alias ExTTRPGDev.RuleSystems - alias ExTTRPGDev.RuleSystems.Abilities - alias ExTTRPGDev.RuleSystems.Languages - alias ExTTRPGDev.RuleSystems.Skills - alias ExTTRPGDev.CustomParsers + alias ExTTRPGDev.CLI.Generate + alias ExTTRPGDev.CLI.Roll + alias ExTTRPGDev.CLI.RuleSystems @moduledoc """ The CLI for the project @@ -21,125 +18,10 @@ defmodule ExTTRPGDev.CLI do about: "Utility for playing tabletop role-playing games.", allow_unknown_args: false, parse_double_dash: true, - subcommands: [ - roll: [ - name: "roll", - about: "Roll some dice", - args: [ - dice: [ - value_name: "DICE", - help: - "Dice in the format of xdy wherein x is the number of dice, y is the number of sides the dice should have", - required: true, - parser: &CustomParsers.dice_parser(&1) - ] - ] - ], - list_systems: [ - name: "list-systems", - about: "List systems that are setup to be used with ExTTRPGDev" - ], - system: [ - name: "system", - about: "Top level command fo systems", - subcommands: [ - gen: [ - name: "gen", - about: "Used for generating things for the system", - subcommands: [ - stat_block: [ - name: "stat-block", - about: "Generate stat blocks for characters of the system", - args: [ - system: [ - value_name: "SYSTEM", - help: "A supported system, e.g. dnd5e", - required: true, - parser: :string - ] - ] - ], - character: [ - name: "character", - about: "Generate characters for system", - args: [ - system: [ - value_name: "SYSTEM", - help: "A supported system, e.g. dnd5e", - required: true, - parser: :string - ] - ] - ] - ] - ], - show: [ - name: "show", - about: "Used for showing information about the rule system", - subcommands: [ - abilities: [ - name: "abilities", - about: "Show the rule systems character abilities", - args: [ - system: [ - value_name: "SYSTEM", - help: "A supported system, e.g. dnd5e", - required: true, - parser: :string - ] - ] - ], - languages: [ - name: "languages", - about: "Show the rule systems languages", - args: [ - system: [ - value_name: "SYSTEM", - help: "A supported system, e.g. dnd5e", - required: true, - parser: :string - ] - ] - ], - metadata: [ - name: "metadata", - about: "Show system metadata", - args: [ - system: [ - value_name: "SYSTEM", - help: "A supported system, e.g. dnd5e", - required: true, - parser: :string - ] - ] - ], - skills: [ - name: "skills", - about: "Show rule system skills", - args: [ - system: [ - value_name: "SYSTEM", - help: "A supported system, e.g. dnd5e", - required: true, - parser: :string - ] - ] - ] - ] - ] - ] - ], - gen: [ - name: "gen", - about: "system agnostic generation helpers", - subcommands: [ - name: [ - name: "name", - about: "Generate a random name" - ] - ] - ] - ] + subcommands: + Roll.commands() ++ + RuleSystems.commands() ++ + Generate.commands() ) case Optimus.parse!(optimus, argv) do @@ -147,17 +29,16 @@ defmodule ExTTRPGDev.CLI do Optimus.parse!(optimus, ["--help"]) {[:roll], parse_result} -> - handle_roll(parse_result) + Roll.handle(parse_result) {[:list_systems], _} -> - RuleSystems.list_systems() - |> IO.inspect(label: "Configured Systems") + RuleSystems.handle_list_systems() {[:system | sub_commands], parse_result} -> - handle_system_subcommands(sub_commands, parse_result) + RuleSystems.handle_system_subcommands(sub_commands, parse_result) {[:gen | sub_commands], _} -> - handle_generate_subcommands(sub_commands) + Generate.handle_generate_subcommands(sub_commands) {unhandled, _parse_result} -> str_command = @@ -169,96 +50,4 @@ defmodule ExTTRPGDev.CLI do raise "Unhandled CLI command `#{str_command}`, if you are seeing this error please report the issue" end end - - def handle_roll(%Optimus.ParseResult{args: %{dice: dice}}) do - dice - |> Dice.multi_roll!() - |> Enum.each(fn {dice_spec, results} -> - IO.inspect(results, label: dice_spec, charlists: :as_lists) - end) - end - - def handle_system_subcommands([command | subcommands], %Optimus.ParseResult{ - args: %{system: system} - }) do - loaded_system = - system - |> RuleSystems.assert_configured!() - |> RuleSystems.load_system!() - - case command do - :gen -> - handle_system_generation_subcommands(subcommands, loaded_system) - - :show -> - handle_system_show_subcommands(subcommands, loaded_system) - end - end - - def handle_system_generation_subcommands( - [command | _subcommands], - %RuleSystems.RuleSystem{} = system - ) do - case command do - :stat_block -> - RuleSystems.RuleSystem.gen_ability_scores_assigned(system) - |> IO.inspect() - - :character -> - character = RuleSystems.RuleSystem.gen_character!(system) - IO.puts("-- Name: #{character.name}") - - Enum.each(character.ability_scores, fn {ability, scores} -> - IO.puts("#{ability}: #{Enum.sum(scores)}") - end) - end - end - - def handle_system_show_subcommands( - [command | _subcommands], - %RuleSystems.RuleSystem{} = system - ) do - case command do - :abilities -> - show_abilities(system) - - :languages -> - show_languages(system) - - :metadata -> - Map.get(system, :metadata) - |> IO.inspect() - - :skills -> - show_skills(system) - end - end - - def handle_generate_subcommands([command | _subcommands]) do - case command do - :name -> - IO.inspect(Faker.Person.name()) - end - end - - def show_abilities(%RuleSystems.RuleSystem{abilities: %Abilities{specs: specs}}) do - Enum.each(specs, fn %Abilities.Spec{name: name, abbreviation: abbr} -> - IO.puts("(#{abbr}) #{name}") - end) - end - - def show_languages(%RuleSystems.RuleSystem{languages: languages}) do - Enum.each(languages, fn %Languages.Language{name: name, script: script} -> - IO.puts("Name: #{name}, Script: #{script}") - end) - end - - def show_skills(%RuleSystems.RuleSystem{skills: skills} = system) do - Enum.each(skills, fn %Skills.Skill{name: name, modifying_stat: mod_stat} -> - %Abilities.Spec{abbreviation: abbr} = - RuleSystems.RuleSystem.get_spec_by_name(system, mod_stat) - - IO.puts("(#{abbr}) #{name}") - end) - end end diff --git a/lib/custom_parsers.ex b/lib/cli/custom_parsers.ex similarity index 77% rename from lib/custom_parsers.ex rename to lib/cli/custom_parsers.ex index 08b5e0b..7e2bb23 100644 --- a/lib/custom_parsers.ex +++ b/lib/cli/custom_parsers.ex @@ -1,4 +1,4 @@ -defmodule ExTTRPGDev.CustomParsers do +defmodule ExTTRPGDev.CLI.CustomParsers do @moduledoc """ Custom parsers to be used with Optimus args :parse """ @@ -8,7 +8,7 @@ defmodule ExTTRPGDev.CustomParsers do ## Examples - iex> ExTTRPGDev.CustomParsers.dice_parser("3d4, 1d10,2d20") + iex> ExTTRPGDev.CLI.CustomParsers.dice_parser("3d4, 1d10,2d20") {:ok, ["3d4", "1d10", "2d20"]} """ def dice_parser(arg) when is_bitstring(arg) do diff --git a/lib/cli/generate.ex b/lib/cli/generate.ex new file mode 100644 index 0000000..14eecb3 --- /dev/null +++ b/lib/cli/generate.ex @@ -0,0 +1,34 @@ +# credo:disable-for-this-file Credo.Check.Warning.IoInspect +defmodule ExTTRPGDev.CLI.Generate do + @moduledoc """ + Definitions for dealing with genenerate CLI commands + """ + + @doc """ + Command specifications for generate commands + """ + def commands do + [ + gen: [ + name: "gen", + about: "system agnostic generation helpers", + subcommands: [ + name: [ + name: "name", + about: "Generate a random name" + ] + ] + ] + ] + end + + @doc """ + Handles generate sub commands + """ + def handle_generate_subcommands([command | _subcommands]) do + case command do + :name -> + IO.inspect(Faker.Person.name()) + end + end +end diff --git a/lib/cli/roll.ex b/lib/cli/roll.ex new file mode 100644 index 0000000..5ee6e60 --- /dev/null +++ b/lib/cli/roll.ex @@ -0,0 +1,40 @@ +# credo:disable-for-this-file Credo.Check.Warning.IoInspect +defmodule ExTTRPGDev.CLI.Roll do + @moduledoc """ + Defintions for dealing with the CLI `roll` commond + """ + alias ExTTRPGDev.Dice + alias ExTTRPGDev.CLI.CustomParsers + + @doc """ + Command specifications for CLI `roll` command + """ + def commands do + [ + roll: [ + name: "roll", + about: "Roll some dice", + args: [ + dice: [ + value_name: "DICE", + help: + "Dice in the format of xdy wherein x is the number of dice, y is the number of sides the dice should have", + required: true, + parser: &CustomParsers.dice_parser(&1) + ] + ] + ] + ] + end + + @doc """ + Handles CLI `roll` command + """ + def handle(%Optimus.ParseResult{args: %{dice: dice}}) do + dice + |> Dice.multi_roll!() + |> Enum.each(fn {dice_spec, results} -> + IO.inspect(results, label: dice_spec, charlists: :as_lists) + end) + end +end diff --git a/lib/cli/system.ex b/lib/cli/system.ex new file mode 100644 index 0000000..adeabd5 --- /dev/null +++ b/lib/cli/system.ex @@ -0,0 +1,216 @@ +# credo:disable-for-this-file Credo.Check.Warning.IoInspect +defmodule ExTTRPGDev.CLI.RuleSystems do + @moduledoc """ + Defintions for dealing with rule system CLI commands + """ + alias ExTTRPGDev.Characters.Character + alias ExTTRPGDev.RuleSystems.Abilities + alias ExTTRPGDev.RuleSystems.Languages + alias ExTTRPGDev.RuleSystems.RuleSystem + alias ExTTRPGDev.RuleSystems.Skills + + @doc """ + Command specifications for rule system CLI commands + """ + def commands do + [ + list_systems: [ + name: "list-systems", + about: "List systems that are setup to be used with ExTTRPGDev" + ], + system: [ + name: "system", + about: "Top level command fo systems", + subcommands: [ + gen: [ + name: "gen", + about: "Used for generating things for the system", + subcommands: [ + stat_block: [ + name: "stat-block", + about: "Generate stat blocks for characters of the system", + args: [ + system: [ + value_name: "SYSTEM", + help: "A supported system, e.g. dnd5e", + required: true, + parser: :string + ] + ] + ], + character: [ + name: "character", + about: "Generate characters for system", + args: [ + system: [ + value_name: "SYSTEM", + help: "A supported system, e.g. dnd5e", + required: true, + parser: :string + ] + ] + ] + ] + ], + show: [ + name: "show", + about: "Used for showing information about the rule system", + subcommands: [ + abilities: [ + name: "abilities", + about: "Show the rule systems character abilities", + args: [ + system: [ + value_name: "SYSTEM", + help: "A supported system, e.g. dnd5e", + required: true, + parser: :string + ] + ] + ], + languages: [ + name: "languages", + about: "Show the rule systems languages", + args: [ + system: [ + value_name: "SYSTEM", + help: "A supported system, e.g. dnd5e", + required: true, + parser: :string + ] + ] + ], + metadata: [ + name: "metadata", + about: "Show system metadata", + args: [ + system: [ + value_name: "SYSTEM", + help: "A supported system, e.g. dnd5e", + required: true, + parser: :string + ] + ] + ], + skills: [ + name: "skills", + about: "Show rule system skills", + args: [ + system: [ + value_name: "SYSTEM", + help: "A supported system, e.g. dnd5e", + required: true, + parser: :string + ] + ] + ] + ] + ] + ] + ] + ] + end + + @doc """ + Handle list-systems CLI command + """ + def handle_list_systems() do + ExTTRPGDev.RuleSystems.list_systems() + |> IO.inspect(label: "Configured Systems") + end + + @doc """ + Handle `system` CLI command and sub commands + """ + def handle_system_subcommands([command | subcommands], %Optimus.ParseResult{ + args: %{system: system} + }) do + loaded_system = + system + |> ExTTRPGDev.RuleSystems.assert_configured!() + |> ExTTRPGDev.RuleSystems.load_system!() + + case command do + :gen -> + handle_system_generation_subcommands(subcommands, loaded_system) + + :show -> + handle_system_show_subcommands(subcommands, loaded_system) + end + end + + @doc """ + Handle generation commands for a rule system + """ + def handle_system_generation_subcommands( + [command | _subcommands], + %RuleSystem{} = system + ) do + case command do + :stat_block -> + RuleSystem.gen_ability_scores_assigned(system) + |> IO.inspect() + + :character -> + character = Character.gen_character!(system) + IO.puts("-- Name: #{character.name}") + + Enum.each(character.ability_scores, fn {ability, scores} -> + IO.puts("#{ability}: #{Enum.sum(scores)}") + end) + end + end + + @doc """ + Hand showing a rule system's components + """ + def handle_system_show_subcommands( + [command | _subcommands], + %RuleSystem{} = system + ) do + case command do + :abilities -> + show_abilities(system) + + :languages -> + show_languages(system) + + :metadata -> + Map.get(system, :metadata) + |> IO.inspect() + + :skills -> + show_skills(system) + end + end + + @doc """ + Show a rule system's abilities + """ + def show_abilities(%RuleSystem{abilities: %Abilities{specs: specs}}) do + Enum.each(specs, fn %Abilities.Spec{name: name, abbreviation: abbr} -> + IO.puts("(#{abbr}) #{name}") + end) + end + + @doc """ + Show a rule system's languages + """ + def show_languages(%RuleSystem{languages: languages}) do + Enum.each(languages, fn %Languages.Language{name: name, script: script} -> + IO.puts("Name: #{name}, Script: #{script}") + end) + end + + @doc """ + Show a rule system's skills + """ + def show_skills(%RuleSystem{skills: skills} = system) do + Enum.each(skills, fn %Skills.Skill{name: name, modifying_stat: mod_stat} -> + %Abilities.Spec{abbreviation: abbr} = + RuleSystem.get_spec_by_name(system, mod_stat) + + IO.puts("(#{abbr}) #{name}") + end) + end +end diff --git a/lib/rule_systems/rule_system.ex b/lib/rule_systems/rule_system.ex index fb4ddc1..c358001 100644 --- a/lib/rule_systems/rule_system.ex +++ b/lib/rule_systems/rule_system.ex @@ -4,7 +4,6 @@ defmodule ExTTRPGDev.RuleSystems.RuleSystem do alias ExTTRPGDev.RuleSystems.Abilities alias ExTTRPGDev.RuleSystems.Skills alias ExTTRPGDev.RuleSystems.Languages - alias ExTTRPGDev.RuleSystems.Characters @moduledoc """ Module for handling a specific Rule System @@ -89,31 +88,4 @@ defmodule ExTTRPGDev.RuleSystems.RuleSystem do when is_bitstring(spec_name) do Abilities.get_spec_by_name(abilities, spec_name) end - - @doc """ - Returns an auto generated character for the system - - ## Examples - iex> ExTTRPGDev.RuleSystems.gen_character(rule_system) - %Characters.Character{} - """ - def gen_character!(%RuleSystem{ - abilities: %Abilities{} = abilities, - metadata: %Metadata{} = metadata - }) do - name = Faker.Person.name() - - %Characters.Character{ - name: name, - ability_scores: Abilities.gen_scores(abilities), - metadata: %Characters.CharacterMetadata{ - slug: - name - |> String.downcase() - |> String.replace(~r/[!#$%&()*+,.:;<=>?@\^_`'{|}~-]/, "") - |> String.replace(" ", "_"), - rule_system: metadata - } - } - end end diff --git a/test/characters/character_test.exs b/test/characters/character_test.exs new file mode 100644 index 0000000..fc89fde --- /dev/null +++ b/test/characters/character_test.exs @@ -0,0 +1,28 @@ +defmodule ExTTRPGDevTest.Characters.Character do + use ExUnit.Case + alias ExTTRPGDev.Characters.Character + alias ExTTRPGDev.RuleSystems + + doctest ExTTRPGDev.Characters.Character, + except: [ + to_json!: 1, + gen_character!: 1 + ] + + test "gen_character!/1" do + dnd_5e_srd = RuleSystems.load_system!("dnd_5e_srd") + generated_character = Character.gen_character!(dnd_5e_srd) + + assert generated_character.name != nil + assert generated_character.metadata.slug != nil + assert not String.contains?(generated_character.metadata.slug, " ") + assert generated_character.metadata.rule_system == dnd_5e_srd.metadata + + # Assert that each ability spec is found within the generated character's ability_scores + dnd_5e_srd.abilities.specs + |> Enum.each(fn spec -> + score = Map.get(generated_character.ability_scores, spec.name) + assert score != nil, "Could not find ability #{spec.name} on generated character" + end) + end +end diff --git a/test/characters_test.exs b/test/characters_test.exs index 3d4b02b..fec850d 100644 --- a/test/characters_test.exs +++ b/test/characters_test.exs @@ -1,10 +1,10 @@ defmodule ExTTRPGDevTest.Characters do use ExUnit.Case - alias ExTTRPGDev.RuleSystems.Characters - alias ExTTRPGDev.RuleSystems.RuleSystem + alias ExTTRPGDev.Characters + alias ExTTRPGDev.Characters.Character alias ExTTRPGDev.RuleSystems - doctest ExTTRPGDev.RuleSystems.Characters, + doctest ExTTRPGDev.Characters, except: [ character_file_path!: 1, character_exists?: 1, @@ -18,7 +18,7 @@ defmodule ExTTRPGDevTest.Characters do RuleSystems.list_systems() |> List.first() |> RuleSystems.load_system!() - |> RuleSystem.gen_character!() + |> Character.gen_character!() end def save_test_character do @@ -27,7 +27,7 @@ defmodule ExTTRPGDevTest.Characters do character end - def delete_test_character(%Characters.Character{} = character) do + def delete_test_character(%Character{} = character) do File.rm(Characters.character_file_path!(character)) end diff --git a/test/custom_parsers_test.exs b/test/custom_parsers_test.exs index d16849f..4312f13 100644 --- a/test/custom_parsers_test.exs +++ b/test/custom_parsers_test.exs @@ -1,5 +1,5 @@ -defmodule ExTTRPGDevTest.CustomParsers do +defmodule ExTTRPGDevTest.CLI.CustomParsers do use ExUnit.Case - doctest ExTTRPGDev.CustomParsers + doctest ExTTRPGDev.CLI.CustomParsers end diff --git a/test/rule_systems_test.exs b/test/rule_systems_test.exs index 138fc60..ba532be 100644 --- a/test/rule_systems_test.exs +++ b/test/rule_systems_test.exs @@ -9,8 +9,7 @@ defmodule ExTTRPGDevTest.RuleSystems do system_path!: 1, load_system!: 1, save_system!: 1, - save_system!: 2, - gen_character!: 1 + save_system!: 2 ] def build_test_system do @@ -135,21 +134,4 @@ defmodule ExTTRPGDevTest.RuleSystems do bundled_system = RuleSystems.load_system!(bundled_system_slug) assert_raise RuntimeError, fn -> RuleSystems.save_system!(bundled_system) end end - - test "gen_character!/1" do - dnd_5e_srd = RuleSystems.load_system!("dnd_5e_srd") - generated_character = RuleSystems.RuleSystem.gen_character!(dnd_5e_srd) - - assert generated_character.name != nil - assert generated_character.metadata.slug != nil - assert not String.contains?(generated_character.metadata.slug, " ") - assert generated_character.metadata.rule_system == dnd_5e_srd.metadata - - # Assert that each ability spec is found within the generated character's ability_scores - dnd_5e_srd.abilities.specs - |> Enum.each(fn spec -> - score = Map.get(generated_character.ability_scores, spec.name) - assert score != nil, "Could not find ability #{spec.name} on generated character" - end) - end end