-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7150e02
Showing
19 changed files
with
669 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Used by "mix format" | ||
[ | ||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# The directory Mix will write compiled artifacts to. | ||
/_build/ | ||
|
||
# If you run "mix test --cover", coverage assets end up here. | ||
/cover/ | ||
|
||
# The directory Mix downloads your dependencies sources to. | ||
/deps/ | ||
|
||
# Where third-party dependencies like ExDoc output generated docs. | ||
/doc/ | ||
|
||
# Ignore .fetch files in case you like to edit your project deps locally. | ||
/.fetch | ||
|
||
# If the VM crashes, it generates a dump, let's ignore it too. | ||
erl_crash.dump | ||
|
||
# Also ignore archive artifacts (built via "mix archive.build"). | ||
*.ez | ||
|
||
# Ignore package tarball (built via "mix hex.build"). | ||
phx_tailwind_generators-*.tar | ||
|
||
|
||
# Temporary files for e.g. tests | ||
/tmp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# PhxTailwindGenerators | ||
|
||
**TODO: Add description** | ||
|
||
## Installation | ||
|
||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed | ||
by adding `phx_tailwind_generators` to your list of dependencies in `mix.exs`: | ||
|
||
```elixir | ||
def deps do | ||
[ | ||
{:phx_tailwind_generators, "~> 0.1.0"} | ||
] | ||
end | ||
``` | ||
|
||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) | ||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can | ||
be found at [https://hexdocs.pm/phx_tailwind_generators](https://hexdocs.pm/phx_tailwind_generators). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
defmodule Mix.Tasks.Phx.Gen.Tailwind do | ||
@shortdoc "Generates controller, views, and context for an HTML resource" | ||
|
||
@moduledoc """ | ||
Generates controller, views, and context for an HTML resource. | ||
mix tailwind.gen.html Accounts User users name:string age:integer | ||
The first argument is the context module followed by the schema module | ||
and its plural name (used as the schema table name). | ||
The context is an Elixir module that serves as an API boundary for | ||
the given resource. A context often holds many related resources. | ||
Therefore, if the context already exists, it will be augmented with | ||
functions for the given resource. | ||
> Note: A resource may also be split | ||
> over distinct contexts (such as `Accounts.User` and `Payments.User`). | ||
The schema is responsible for mapping the database fields into an | ||
Elixir struct. It is followed by an optional list of attributes, | ||
with their respective names and types. See `mix tailwind.gen.schema` | ||
for more information on attributes. | ||
Overall, this generator will add the following files to `lib/`: | ||
* a context module in `lib/app/accounts.ex` for the accounts API | ||
* a schema in `lib/app/accounts/user.ex`, with an `users` table | ||
* a view in `lib/app_web/views/user_view.ex` | ||
* a controller in `lib/app_web/controllers/user_controller.ex` | ||
* default CRUD templates in `lib/app_web/templates/user` | ||
## The context app | ||
A migration file for the repository and test files for the context and | ||
controller features will also be generated. | ||
The location of the web files (controllers, views, templates, etc) in an | ||
umbrella application will vary based on the `:context_app` config located | ||
in your applications `:generators` configuration. When set, the Phoenix | ||
generators will generate web files directly in your lib and test folders | ||
since the application is assumed to be isolated to web specific functionality. | ||
If `:context_app` is not set, the generators will place web related lib | ||
and test files in a `web/` directory since the application is assumed | ||
to be handling both web and domain specific functionality. | ||
Example configuration: | ||
config :my_app_web, :generators, context_app: :my_app | ||
Alternatively, the `--context-app` option may be supplied to the generator: | ||
mix tailwind.gen.html Sales User users --context-app warehouse | ||
## Web namespace | ||
By default, the controller and view will be namespaced by the schema name. | ||
You can customize the web module namespace by passing the `--web` flag with a | ||
module name, for example: | ||
mix tailwind.gen.html Sales User users --web Sales | ||
Which would generate a `lib/app_web/controllers/sales/user_controller.ex` and | ||
`lib/app_web/views/sales/user_view.ex`. | ||
## Customising the context, schema, tables and migrations | ||
In some cases, you may wish to bootstrap HTML templates, controllers, | ||
and controller tests, but leave internal implementation of the context | ||
or schema to yourself. You can use the `--no-context` and `--no-schema` | ||
flags for file generation control. | ||
You can also change the table name or configure the migrations to | ||
use binary ids for primary keys, see `mix tailwind.gen.schema` for more | ||
information. | ||
""" | ||
use Mix.Task | ||
|
||
alias Mix.Phoenix.{Context, Schema} | ||
alias Mix.Tasks.Phx.Gen | ||
|
||
@doc false | ||
def run(args) do | ||
if Mix.Project.umbrella?() do | ||
Mix.raise "mix tailwind.gen.html must be invoked from within your *_web application root directory" | ||
end | ||
|
||
{context, schema} = Gen.Context.build(args) | ||
Gen.Context.prompt_for_code_injection(context) | ||
|
||
binding = [context: context, schema: schema, inputs: inputs(schema)] | ||
paths = Mix.Phoenix.generator_paths() | ||
|
||
prompt_for_conflicts(context) | ||
|
||
context | ||
|> copy_new_files(paths, binding) | ||
|> print_shell_instructions() | ||
end | ||
|
||
defp prompt_for_conflicts(context) do | ||
context | ||
|> files_to_be_generated() | ||
|> Kernel.++(context_files(context)) | ||
|> Mix.Phoenix.prompt_for_conflicts() | ||
end | ||
defp context_files(%Context{generate?: true} = context) do | ||
Gen.Context.files_to_be_generated(context) | ||
end | ||
defp context_files(%Context{generate?: false}) do | ||
[] | ||
end | ||
|
||
@doc false | ||
def files_to_be_generated(%Context{schema: schema, context_app: context_app}) do | ||
web_prefix = Mix.Phoenix.web_path(context_app) | ||
test_prefix = Mix.Phoenix.web_test_path(context_app) | ||
web_path = to_string(schema.web_path) | ||
|
||
[ | ||
{:eex, "controller.ex", Path.join([web_prefix, "controllers", web_path, "#{schema.singular}_controller.ex"])}, | ||
{:eex, "edit.html.eex", Path.join([web_prefix, "templates", web_path, schema.singular, "edit.html.eex"])}, | ||
{:eex, "form.html.eex", Path.join([web_prefix, "templates", web_path, schema.singular, "form.html.eex"])}, | ||
{:eex, "index.html.eex", Path.join([web_prefix, "templates", web_path, schema.singular, "index.html.eex"])}, | ||
{:eex, "new.html.eex", Path.join([web_prefix, "templates", web_path, schema.singular, "new.html.eex"])}, | ||
{:eex, "show.html.eex", Path.join([web_prefix, "templates", web_path, schema.singular, "show.html.eex"])}, | ||
{:eex, "view.ex", Path.join([web_prefix, "views", web_path, "#{schema.singular}_view.ex"])}, | ||
{:eex, "controller_test.exs", Path.join([test_prefix, "controllers", web_path, "#{schema.singular}_controller_test.exs"])}, | ||
] | ||
end | ||
|
||
@doc false | ||
def copy_new_files(%Context{} = context, paths, binding) do | ||
files = files_to_be_generated(context) | ||
# Mix.Phoenix.copy_from(paths, "priv/templates/phx.gen.tailwind", binding, files) | ||
Mix.Phoenix.copy_from(paths, :code.priv_dir('templates/phx.gen.tailwind')|>to_string, binding, files) | ||
if context.generate?, do: Gen.Context.copy_new_files(context, paths, binding) | ||
context | ||
end | ||
|
||
@doc false | ||
def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do | ||
if schema.web_namespace do | ||
Mix.shell().info """ | ||
Add the resource to your #{schema.web_namespace} :browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex: | ||
scope "/#{schema.web_path}", #{inspect Module.concat(context.web_module, schema.web_namespace)}, as: :#{schema.web_path} do | ||
pipe_through :browser | ||
... | ||
resources "/#{schema.plural}", #{inspect schema.alias}Controller | ||
end | ||
""" | ||
else | ||
Mix.shell().info """ | ||
Add the resource to your browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex: | ||
resources "/#{schema.plural}", #{inspect schema.alias}Controller | ||
""" | ||
end | ||
if context.generate?, do: Gen.Context.print_shell_instructions(context) | ||
end | ||
|
||
@doc false | ||
def inputs(%Schema{} = schema) do | ||
Enum.map(schema.attrs, fn | ||
{_, {:references, _}} -> | ||
{nil, nil, nil} | ||
{key, :integer} -> | ||
{label(key), ~s(<%= number_input f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :float} -> | ||
{label(key), ~s(<%= number_input f, #{inspect(key)}, step: "any", class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :decimal} -> | ||
{label(key), ~s(<%= number_input f, #{inspect(key)}, step: "any", class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :boolean} -> | ||
{label(key), ~s(<%= checkbox f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :text} -> | ||
{label(key), ~s(<%= textarea f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :date} -> | ||
{label(key), ~s(<%= date_select f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :time} -> | ||
{label(key), ~s(<%= time_select f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :utc_datetime} -> | ||
{label(key), ~s(<%= datetime_select f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, :naive_datetime} -> | ||
{label(key), ~s(<%= datetime_select f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, {:array, :integer}} -> | ||
{label(key), ~s(<%= multiple_select f, #{inspect(key)}, ["1": 1, "2": 2], class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
{key, {:array, _}} -> | ||
{label(key), ~s(<%= multiple_select f, #{inspect(key)}, ["Option 1": "option1", "Option 2": "option2"] %>), error(key)} | ||
{key, _} -> | ||
{label(key), ~s(<%= text_input f, #{inspect(key)}, class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" %>), error(key)} | ||
end) | ||
end | ||
|
||
defp label(key) do | ||
~s(<%= label f, #{inspect(key)}, class: "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2" %>) | ||
end | ||
|
||
defp error(field) do | ||
~s(<%= tailwind_error_tag f, #{inspect(field)} %>) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
defmodule PhxTailwindGenerators do | ||
@moduledoc """ | ||
Documentation for `PhxTailwindGenerators`. | ||
""" | ||
|
||
@doc """ | ||
Hello world. | ||
## Examples | ||
iex> PhxTailwindGenerators.hello() | ||
:world | ||
""" | ||
def hello do | ||
:world | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
defmodule PhxTailwindGenerators.MixProject do | ||
use Mix.Project | ||
|
||
def project do | ||
[ | ||
app: :phx_tailwind_generators, | ||
version: "0.1.0", | ||
elixir: "~> 1.11", | ||
start_permanent: Mix.env() == :prod, | ||
deps: deps() | ||
] | ||
end | ||
|
||
# Run "mix help compile.app" to learn about applications. | ||
def application do | ||
[ | ||
extra_applications: [:logger] | ||
] | ||
end | ||
|
||
# Run "mix help deps" to learn about dependencies. | ||
defp deps do | ||
[ | ||
# {:dep_from_hexpm, "~> 0.3.0"}, | ||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} | ||
] | ||
end | ||
end |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Controller do | ||
use <%= inspect context.web_module %>, :controller | ||
|
||
alias <%= inspect context.module %> | ||
alias <%= inspect schema.module %> | ||
|
||
def index(conn, _params) do | ||
<%= schema.plural %> = <%= inspect context.alias %>.list_<%= schema.plural %>() | ||
render(conn, "index.html", <%= schema.plural %>: <%= schema.plural %>) | ||
end | ||
|
||
def new(conn, _params) do | ||
changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(%<%= inspect schema.alias %>{}) | ||
render(conn, "new.html", changeset: changeset) | ||
end | ||
|
||
def create(conn, %{<%= inspect schema.singular %> => <%= schema.singular %>_params}) do | ||
case <%= inspect context.alias %>.create_<%= schema.singular %>(<%= schema.singular %>_params) do | ||
{:ok, <%= schema.singular %>} -> | ||
conn | ||
|> put_flash(:info, "<%= schema.human_singular %> created successfully.") | ||
|> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>)) | ||
|
||
{:error, %Ecto.Changeset{} = changeset} -> | ||
render(conn, "new.html", changeset: changeset) | ||
end | ||
end | ||
|
||
def show(conn, %{"id" => id}) do | ||
<%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id) | ||
render(conn, "show.html", <%= schema.singular %>: <%= schema.singular %>) | ||
end | ||
|
||
def edit(conn, %{"id" => id}) do | ||
<%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id) | ||
changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(<%= schema.singular %>) | ||
render(conn, "edit.html", <%= schema.singular %>: <%= schema.singular %>, changeset: changeset) | ||
end | ||
|
||
def update(conn, %{"id" => id, <%= inspect schema.singular %> => <%= schema.singular %>_params}) do | ||
<%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id) | ||
|
||
case <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params) do | ||
{:ok, <%= schema.singular %>} -> | ||
conn | ||
|> put_flash(:info, "<%= schema.human_singular %> updated successfully.") | ||
|> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :show, <%= schema.singular %>)) | ||
|
||
{:error, %Ecto.Changeset{} = changeset} -> | ||
render(conn, "edit.html", <%= schema.singular %>: <%= schema.singular %>, changeset: changeset) | ||
end | ||
end | ||
|
||
def delete(conn, %{"id" => id}) do | ||
<%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id) | ||
{:ok, _<%= schema.singular %>} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>) | ||
|
||
conn | ||
|> put_flash(:info, "<%= schema.human_singular %> deleted successfully.") | ||
|> redirect(to: Routes.<%= schema.route_helper %>_path(conn, :index)) | ||
end | ||
end |
Oops, something went wrong.