Packages

An Elixir Phoenix Auto Administration Package.

Current section

Files

Jump to
phx_admin lib mix tasks admin.install.ex
Raw

lib/mix/tasks/admin.install.ex

defmodule Mix.Tasks.Admin.Install do
@moduledoc """
Install PhxAdmin supporting files.
mix admin.install
# Defaults:
* assets - copy js, css, and image files
* brunch - append instructions to brunch-config.js
* config - add configuration to config/config.exs
* dashboard - create a default dashboard
* route - display instructions to add the admin routes
## Options:
* --no-brunch - Write assets to priv/static instead of web/static/
* --no-assets - Skip the assets
* --no-config - Skip the config
* --no-dashboard - Skip the dashboard
* --no-route - Skip the route instructions
"""
# @shortdoc "Install PhxAdmin"
use Mix.Task
import Mix.PhxAdmin.Utils
import PhxAdmin.Gettext
@boolean_switchs ~w(assets config route dashboard brunch)a
@switches Enum.map(@boolean_switchs, &({&1, :boolean}))
defmodule Config do
@moduledoc false
defstruct route: true, assets: true, dashboard: true,
package_path: nil, config: true, brunch: true,
brunch_path: nil, assets_path: nil, vendor_path: nil,
phx: false
end
def run(args) do
do_run(args)
end
def do_run(args) do
parse_args(args)
|> do_install()
end
def do_install(config) do
config
|> check_project
|> do_assets
|> do_config
|> do_dashboard
|> do_route
|> do_paging
|> do_assets_instructions
end
def check_project(config) do
config
|> check_config
|> check_assets
end
defp check_config(%{config: true} = config) do
config_path = Path.join ~w(config config.exs)
unless File.exists? config_path do
Mix.raise """
Can't find #{config_path}
"""
end
config
end
defp check_config(config), do: config
defp check_assets(%{assets: true, brunch: true} = config) do
config
|> check_and_set_brunch_path_phx
|> check_and_set_assets_path
end
defp check_assets(%{assets: true} = config) do
check_and_set_assets_path(config)
# path = Path.join ~w(priv static)
# unless File.exists?(path) do
# Mix.raise """
# Can't find #{path}
# """
# end
# config
end
defp check_assets(config), do: config
defp check_and_set_brunch_path_phx(config) do
if File.exists? "assets/brunch-config.js" do
struct(config, brunch_path: "assets/brunch-config.js", phx: true)
else
check_and_set_brunch_path(config)
end
end
defp check_and_set_assets_path(config) do
cond do
File.exists?(Path.join(~w(assets css))) ->
struct config,
vendor_path: Path.join(~w(assets vendor)),
assets_path: Path.join(~w(assets static))
File.exists?(Path.join(~w(web static))) ->
struct config,
vendor_path: Path.join(~w(web static vendor)),
assets_path: Path.join(~w(web static assets))
true ->
Mix.raise """
Can't find assets path!
"""
end
end
def check_and_set_brunch_path(config) do
unless File.exists? "brunch-config.js" do
Mix.raise """
Can't find brunch-config.js
"""
end
struct(config, brunch_path: "brunch-config.js")
end
def do_assets(%Config{assets: true, brunch: true, brunch_path: brunch_path} = config) do
base_path = Path.join(~w(priv static))
File.mkdir_p config.vendor_path
File.mkdir_p Path.join(config.assets_path, "fonts")
File.mkdir_p Path.join([config.assets_path | ~w{images phx_admin datepicker}])
status_msg("creating", "css files")
~w(admin_lte2.css admin_lte2.css.map active_admin.css.css active_admin.css.css.map)
|> Enum.each(&(copy_vendor config, base_path, "css", &1))
status_msg("creating", "js files")
~w(jquery.min.js admin_lte2.js jquery.min.js.map admin_lte2.js.map)
++ ~w(ex_admin_common.js ex_admin_common.js.map)
|> Enum.each(&(copy_vendor config, base_path, "js", &1))
copy_vendor_r(config, base_path, "fonts")
copy_vendor_r(config, base_path, "images")
case File.read brunch_path do
{:ok, file} ->
File.write! brunch_path, file <> brunch_instructions(config)
error ->
Mix.raise """
Could not open brunch-config.js file. #{inspect error}
"""
end
config
end
def do_assets(%Config{assets: true} = config) do
base = ~w(priv static)
base_path = Path.join(base)
Enum.each ~w(fonts css js), &(File.mkdir_p Path.join(base ++ [&1]))
File.mkdir_p Path.join(~w{priv static images phx_admin datepicker})
File.mkdir_p Path.join(~w(web admin))
status_msg("creating", "css files")
~w(admin_lte2.css admin_lte2.css.map active_admin.css.css active_admin.css.css.map)
|> Enum.each(&(copy_file base_path, "css", &1))
status_msg("creating", "js files")
~w(jquery.min.js admin_lte2.js jquery.min.js.map admin_lte2.js.map)
++ ~w(ex_admin_common.js ex_admin_common.js.map)
|> Enum.each(&(copy_file base_path, "js", &1))
copy_r(base_path, "fonts")
copy_r(base_path, "images")
config
end
def do_assets(config) do
config
end
def do_route(%Config{route: true} = config) do
Mix.shell.info """
Add the admin routes to your web/router.ex:
use PhxAdmin.Router
# your app's routes
scope "/admin", PhxAdmin do
pipe_through :browser
admin_routes()
end
"""
config
end
def do_route(config), do: config
def do_config(%Config{config: true} = config) do
status_msg("updating", "config/config.exs")
dest_path = Path.join [File.cwd! | ~w(config)]
dest_file_path = Path.join dest_path, "config.exs"
source = File.read!(dest_file_path)
""
|> config_xain(config, source)
|> config_write(config, dest_file_path, source)
end
def do_config(config), do: config
defp config_xain(append, _config, source) do
unless String.contains? source, ":xain, :after_callback" do
append <> """
config :xain, :after_callback, {Phoenix.HTML, :raw}
"""
else
notice_msg("skipping", "xain config. It already exists.")
append
end
end
defp config_write("", config, _dest_file_path, _source), do: config
defp config_write(append, config, dest_file_path, source) do
File.write! dest_file_path, source <> "\n" <> append
config
end
def do_dashboard(%Config{dashboard: true} = config) do
web_path = web_path()
dest_path = Path.join(web_path, "admin")
dest_file_path = Path.join dest_path, "dashboard.ex"
source = Path.join([config.package_path | ~w(priv templates admin.install dashboard.exs)] )
|> EEx.eval_file([base: get_module(),
title_txt: (gettext "Dashboard"),
welcome_txt: (gettext "Welcome to PhxAdmin. This is the default dashboard page."),
add_txt: (gettext "To add dashboard sections, checkout '${web_path}/admin/dashboards.ex'", web_path: web_path)
])
if File.exists?(dest_file_path) do
notice_msg "skipping", "#{dest_file_path}. It already exists."
else
status_msg "creating", dest_file_path
File.mkdir_p dest_path
File.write! dest_file_path, source
dashboard_instructions(config)
end
config
end
def do_dashboard(config), do: config
def dashboard_instructions(config) do
base = get_module()
module = if config.phx, do: "#{base}.Web", else: base
Mix.shell.info """
Remember to update your config file:
config :phx_admin,
repo: #{base}.Repo,
module: #{module},
modules: [
#{base}.PhxAdmin.Dashboard,
]
"""
end
def do_paging(config) do
base = get_module()
Mix.shell.info """
Add Scrivener paging to your Repo:
defmodule #{base}.Repo do
use Ecto.Repo, otp_app: :#{String.downcase base}
use Scrivener, page_size: 10 # <--- add this
end
"""
config
end
def do_assets_instructions(%{assets: true, brunch: true} = config) do
Mix.shell.info """
Check the bottom of your brunch-config.js file.
Instructions for adding the PhxAdmin assets have been added.
"""
config
end
def do_assets_instructions(config), do: config
defp copy_r(base_path, path) do
File.cp_r Path.join([get_package_path(), base_path, path]),
Path.join([File.cwd!, base_path, path])
base_path
end
defp copy_file(base_path, path, file_name) do
File.cp Path.join([get_package_path(), base_path, path, file_name]),
Path.join([File.cwd!, base_path, path, file_name])
base_path
end
defp copy_vendor(config, from_path, path, filename) do
File.cp Path.join([get_package_path(), from_path, path, filename]),
Path.join(config.vendor_path, filename)
end
defp copy_vendor_r(config, base_path, path) do
File.cp_r Path.join([get_package_path(), base_path, path]),
Path.join(config.assets_path, path)
end
def brunch_instructions(config) do
js_match = if config.phx, do: "js", else: "web\\/static\\/js"
css_match = if config.phx, do: "css", else: "web\\/static\\/css"
css_path = if config.phx, do: "css", else: "web/static/css"
vendor_path = if config.phx, do: "vendor", else: "web/static/vendor"
"""
// To add the PhxAdmin generated assets to your brunch build, do the following:
//
// Replace
//
// javascripts: {
// joinTo: "js/app.js"
// },
//
// With
//
// javascripts: {
// joinTo: {
// "js/app.js": /^(#{js_match})|(node_modules)/,
// "js/ex_admin_common.js": ["#{vendor_path}/ex_admin_common.js"],
// "js/admin_lte2.js": ["#{vendor_path}/admin_lte2.js"],
// "js/jquery.min.js": ["#{vendor_path}/jquery.min.js"],
// }
// },
//
// Replace
//
// stylesheets: {
// joinTo: "css/app.css",
// order: {
// after: ["#{css_path}/app.css"] // concat app.css last
// }
// },
//
// With
//
// stylesheets: {
// joinTo: {
// "css/app.css": /^(#{css_match})/,
// "css/admin_lte2.css": ["#{vendor_path}/admin_lte2.css"],
// "css/active_admin.css.css": ["#{vendor_path}/active_admin.css.css"],
// },
// order: {
// after: ["#{css_path}/app.css"] // concat app.css last
// }
// },
//
"""
end
defp parse_args(args) do
{opts, _values, _} = OptionParser.parse args, switches: @switches
Enum.reduce opts, %Config{package_path: get_package_path()}, fn
{key, value}, config ->
if key in Map.keys(config) do
struct(config, [{key, value}])
else
raise_option key
end
other, _config ->
raise_option inspect(other)
end
end
defp raise_option(option) do
Mix.raise """
Invalid option --#{option}
"""
end
end