diff --git a/lib/mix/tasks/ecsx.gen.component.ex b/lib/mix/tasks/ecsx.gen.component.ex index fdb6753..b58837b 100644 --- a/lib/mix/tasks/ecsx.gen.component.ex +++ b/lib/mix/tasks/ecsx.gen.component.ex @@ -44,8 +44,13 @@ defmodule Mix.Tasks.Ecsx.Gen.Component do def run([component_type_name, value_type | opts]) do value_type = validate(value_type) - {opts, _, _} = OptionParser.parse(opts, strict: [index: :boolean]) - Helpers.inject_component_module_into_manager(component_type_name) + {opts, _, _} = OptionParser.parse(opts, strict: [index: :boolean, namespace: :string]) + + case namespace = Keyword.get(opts, :namespace, nil) do + nil -> Helpers.inject_component_module_into_manager(component_type_name) + _ -> Helpers.inject_component_module_into_manager({component_type_name, namespace}) + end + create_component_file(component_type_name, value_type, opts) end @@ -68,17 +73,28 @@ defmodule Mix.Tasks.Ecsx.Gen.Component do do: Mix.raise("Invalid value type. Possible types are: #{inspect(@valid_value_types)}") defp create_component_file(component_type_name, value_type, opts) do + {namespace, namespace_file_name} = format_namespace(Keyword.get(opts, :namespace, nil)) filename = Macro.underscore(component_type_name) - target = "lib/#{Helpers.otp_app()}/components/#{filename}.ex" + + target = + case namespace do + nil -> "lib/#{Helpers.otp_app()}/components/#{filename}.ex" + _ -> "lib/#{Helpers.otp_app()}/components/#{namespace_file_name}/#{filename}.ex" + end + source = Application.app_dir(:ecsx, "/priv/templates/component.ex") binding = [ app_name: Helpers.root_module(), index: Keyword.get(opts, :index, false), + namespace: namespace, component_type: component_type_name, value: value_type ] Mix.Generator.create_file(target, EEx.eval_file(source, binding)) end + + defp format_namespace(nil), do: {nil, nil} + defp format_namespace(namespace), do: {Macro.camelize(namespace), Macro.underscore(namespace)} end diff --git a/lib/mix/tasks/ecsx.gen.system.ex b/lib/mix/tasks/ecsx.gen.system.ex index da06dd7..7f1d30e 100644 --- a/lib/mix/tasks/ecsx.gen.system.ex +++ b/lib/mix/tasks/ecsx.gen.system.ex @@ -27,20 +27,58 @@ defmodule Mix.Tasks.Ecsx.Gen.System do """) end - def run([system_name | _] = _args) do - inject_system_module_into_manager(system_name) - create_system_file(system_name) + def run([system_name | opts] = _args) do + {opts, _, _} = OptionParser.parse(opts, strict: [namespace: :string]) + + case namespace = Keyword.get(opts, :namespace, nil) do + nil -> + inject_system_module_into_manager(system_name) + create_system_file(system_name) + + _ -> + inject_system_module_into_manager({system_name, namespace}) + create_system_file(system_name, namespace) + end end defp create_system_file(system_name) do filename = Macro.underscore(system_name) target = "lib/#{Helpers.otp_app()}/systems/#{filename}.ex" source = Application.app_dir(:ecsx, "/priv/templates/system.ex") - binding = [app_name: Helpers.root_module(), system_name: system_name] + binding = [app_name: Helpers.root_module(), system_name: system_name, namespace: nil] Mix.Generator.create_file(target, EEx.eval_file(source, binding)) end + defp create_system_file(system_name, namespace) do + {namespace, namespace_file_name} = format_namespace(namespace) + + filename = Macro.underscore(system_name) + target = "lib/#{Helpers.otp_app()}/systems/#{namespace_file_name}/#{filename}.ex" + source = Application.app_dir(:ecsx, "/priv/templates/system.ex") + binding = [app_name: Helpers.root_module(), namespace: namespace, system_name: system_name] + + Mix.Generator.create_file(target, EEx.eval_file(source, binding)) + end + + defp inject_system_module_into_manager({system_name, namespace}) do + manager_path = ECSx.manager_path() + {before_systems, after_systems, list} = parse_manager(manager_path) + + new_list = + {system_name, namespace} + |> add_system_to_list(list) + |> ensure_list_format() + + new_contents = + [before_systems, "def systems do\n ", new_list, "\n end\n", after_systems] + |> IO.iodata_to_binary() + |> Code.format_string!() + + Mix.shell().info([:green, "* injecting ", :reset, manager_path]) + File.write!(manager_path, [new_contents, "\n"]) + end + defp inject_system_module_into_manager(system_name) do manager_path = ECSx.manager_path() {before_systems, after_systems, list} = parse_manager(manager_path) @@ -76,6 +114,10 @@ defmodule Mix.Tasks.Ecsx.Gen.System do |> inspect() end + defp full_system_module({system_name, namespace}) do + Module.concat([Helpers.root_module(), "Systems", namespace, system_name]) + end + defp full_system_module(system_name) do Module.concat([Helpers.root_module(), "Systems", system_name]) end @@ -86,4 +128,7 @@ defmodule Mix.Tasks.Ecsx.Gen.System do ["[\n" | rest] end + + defp format_namespace(nil), do: {nil, nil} + defp format_namespace(namespace), do: {Macro.camelize(namespace), Macro.underscore(namespace)} end diff --git a/lib/mix/tasks/ecsx/helpers.ex b/lib/mix/tasks/ecsx/helpers.ex index 3820bb3..6c2f344 100644 --- a/lib/mix/tasks/ecsx/helpers.ex +++ b/lib/mix/tasks/ecsx/helpers.ex @@ -26,6 +26,24 @@ defmodule Mix.Tasks.ECSx.Helpers do def write_file(contents, path), do: File.write!(path, contents) + def inject_component_module_into_manager({component_type, namespace}) do + manager_path = ECSx.manager_path() + {before_components, after_components, list} = parse_manager_components(manager_path) + + new_list = + {component_type, namespace} + |> add_component_to_list(list) + |> ensure_list_format() + + new_contents = + [before_components, "def components do\n ", new_list, "\n end\n", after_components] + |> IO.iodata_to_binary() + |> Code.format_string!() + + Mix.shell().info([:green, "* injecting ", :reset, manager_path]) + File.write!(manager_path, [new_contents, "\n"]) + end + def inject_component_module_into_manager(component_type) do manager_path = ECSx.manager_path() {before_components, after_components, list} = parse_manager_components(manager_path) @@ -61,6 +79,10 @@ defmodule Mix.Tasks.ECSx.Helpers do |> inspect() end + defp full_component_module({component_type, namespace}) do + Module.concat([root_module(), "Components", namespace, component_type]) + end + defp full_component_module(component_type) do Module.concat([root_module(), "Components", component_type]) end diff --git a/mix.lock b/mix.lock index cf7c6cb..f6426c0 100644 --- a/mix.lock +++ b/mix.lock @@ -19,7 +19,7 @@ "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, diff --git a/priv/templates/component.ex b/priv/templates/component.ex index 9e182c4..f0a1cb1 100644 --- a/priv/templates/component.ex +++ b/priv/templates/component.ex @@ -1,4 +1,4 @@ -defmodule <%= app_name %>.Components.<%= component_type %> do +defmodule <%= app_name %>.Components.<%= if namespace do %><%= namespace %>.<%= component_type %><% else %><%= component_type %><% end %> do @moduledoc """ Documentation for <%= component_type %> components. """ diff --git a/priv/templates/system.ex b/priv/templates/system.ex index 57aff5f..a2afd16 100644 --- a/priv/templates/system.ex +++ b/priv/templates/system.ex @@ -1,4 +1,4 @@ -defmodule <%= app_name %>.Systems.<%= system_name %> do +defmodule <%= app_name %>.Systems.<%= if namespace do %><%= namespace %>.<%= system_name %><% else %><%= system_name %><% end %> do @moduledoc """ Documentation for <%= system_name %> system. """ diff --git a/test/mix/tasks/ecsx.gen.component_test.exs b/test/mix/tasks/ecsx.gen.component_test.exs index 54a756d..9ce710f 100644 --- a/test/mix/tasks/ecsx.gen.component_test.exs +++ b/test/mix/tasks/ecsx.gen.component_test.exs @@ -79,6 +79,14 @@ defmodule Mix.Tasks.Ecsx.Gen.ComponentTest do Mix.Tasks.Ecsx.Gen.Component.run(["FooComponent", "binary"]) Mix.Tasks.Ecsx.Gen.Component.run(["BarComponent", "integer"]) Mix.Tasks.Ecsx.Gen.Component.run(["BazComponent", "float"]) + Mix.Tasks.Ecsx.Gen.Component.run(["NamespacedComponent1", "float", "--namespace", "Sample"]) + + Mix.Tasks.Ecsx.Gen.Component.run([ + "NamespacedComponent2", + "integer", + "--namespace", + "SampleNamespace" + ]) manager_file = File.read!("lib/my_app/manager.ex") @@ -105,6 +113,8 @@ defmodule Mix.Tasks.Ecsx.Gen.ComponentTest do # Declare all valid Component types def components do [ + MyApp.Components.SampleNamespace.NamespacedComponent2, + MyApp.Components.Sample.NamespacedComponent1, MyApp.Components.BazComponent, MyApp.Components.BarComponent, MyApp.Components.FooComponent @@ -206,4 +216,28 @@ defmodule Mix.Tasks.Ecsx.Gen.ComponentTest do """ end) end + + test "generates a component with a namespace flag" do + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.Component.run([ + "FooComponent", + "binary", + "--namespace", + "EntityNamespace" + ]) + + component_file = File.read!("lib/my_app/components/entity_namespace/foo_component.ex") + + assert component_file == + """ + defmodule MyApp.Components.EntityNamespace.FooComponent do + @moduledoc \"\"\" + Documentation for FooComponent components. + \"\"\" + use ECSx.Component, + value: :binary + end + """ + end) + end end diff --git a/test/mix/tasks/ecsx.gen.system_test.exs b/test/mix/tasks/ecsx.gen.system_test.exs index d426fce..a795e63 100644 --- a/test/mix/tasks/ecsx.gen.system_test.exs +++ b/test/mix/tasks/ecsx.gen.system_test.exs @@ -83,6 +83,7 @@ defmodule Mix.Tasks.Ecsx.Gen.SystemTest do Mix.Project.in_project(:my_app, ".", fn _module -> Mix.Tasks.Ecsx.Gen.System.run(["FooSystem"]) Mix.Tasks.Ecsx.Gen.System.run(["BarSystem"]) + Mix.Tasks.Ecsx.Gen.System.run(["BazSystem", "--namespace", "NewNamespace"]) manager_file = File.read!("lib/my_app/manager.ex") @@ -116,6 +117,7 @@ defmodule Mix.Tasks.Ecsx.Gen.SystemTest do # Declare all Systems to run def systems do [ + MyApp.Systems.NewNamespace.BazSystem, MyApp.Systems.BarSystem, MyApp.Systems.FooSystem ] @@ -178,4 +180,28 @@ defmodule Mix.Tasks.Ecsx.Gen.SystemTest do """ end) end + + test "Generates a name spaced system" do + Mix.Project.in_project(:my_app, ".", fn _module -> + Mix.Tasks.Ecsx.Gen.System.run(["FooSystem", "--namespace", "EntityNamespace"]) + + system_file = File.read!("lib/my_app/systems/entity_namespace/foo_system.ex") + + assert system_file == + """ + defmodule MyApp.Systems.EntityNamespace.FooSystem do + @moduledoc \"\"\" + Documentation for FooSystem system. + \"\"\" + @behaviour ECSx.System + + @impl ECSx.System + def run do + # System logic + :ok + end + end + """ + end) + end end