defmodule Uniform.Blueprint.BeforeCompile do
@moduledoc false
defmacro __before_compile__(env) do
templates_dir = Module.get_attribute(env.module, :templates_dir)
templates =
if templates_dir do
templates_dir
|> Path.join("**/*.eex")
|> Path.wildcard(match_dot: true)
else
[]
end
template_functions =
for path <- templates do
quoted = EEx.compile_file(path)
relative_path = Path.relative_to(path, templates_dir)
quote do
@file unquote(path)
@external_resource unquote(Path.expand(path))
def unquote(String.to_atom(relative_path))(var!(app)) do
_ = var!(app)
unquote(quoted)
end
end
end
quote do
def __modifiers__, do: @modifiers
def __preserve__, do: @preserve
unquote(template_functions)
end
end
end
defmodule Uniform.Blueprint do
@moduledoc ~S"""
Defines the ejection blueprint for your project.
When used, the blueprint accepts the `:templates` option, which is not
required. For example, the blueprint:
defmodule Blueprint do
use Uniform.Blueprint, templates: "priv/uniform-templates"
end
Would search for templates in the `priv/uniform-templates` directory. See
`template/2` for more information.
## The `base_files` Section
The `base_files` section specifies files that should be ejected which aren't
in `lib/my_app`. (When running `mix uniform.eject my_app`.)
defmodule Blueprint do
use Uniform.Blueprint
base_files do
file "my_main_app/application.ex"
cp_r "assets"
# ...
end
end
See `base_files/1` for more information.
## Files that are always ejected
There are a handful of files that are automatically ejected. You do not need
to specify these in the `base_files` section.
```bash
mix.exs
mix.lock
.gitignore
.formatter.exs
test/test_helper.exs
```
## The `deps` Section
Besides the `base_files` section, the blueprint can also contain a `deps`
section to configure dependencies.
defmodule Blueprint do
use Uniform.Blueprint
deps do
always do
mix :phoenix
lib :my_component_library
end
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
end
end
See `deps/1` for more information.
## Modifying files programmatically with `modify`
Lastly, `modify` can be used whenever you want to transform file contents
during ejection. You can specify a specific filepath or use a regex to match
multiple files.
defmodule Blueprint do
use Uniform.Blueprint
modify "assets/js/app.js", fn file, app ->
String.replace(file, "SOME_VALUE_PER_APP", app.extra[:some_value])
end
modify ~r/_worker.ex/, &MyApp.Modify.modify_workers/1
See `modify/2` for more information.
## Preserving files
Before `mix uniform.eject` copies any files, the contents in the destination
directory are deleted – except for the `.git`, `deps`, and `_build`
directories.
If there are any other files or directories *in the project's root folder*
that you would like to preserve (by not deleting them), specify them with
`@preserve`.
# .env will not be deleted immediately before ejection
@preserve [
".env"
]
## Full Example
Below is an example `Blueprint` module that shows off a majority of the
features that can be used.
defmodule MyApp.Uniform.Blueprint do
use Uniform.Blueprint, templates: "lib/uniform/templates"
# do not delete this root file when clearing the destination
@preserve [".env"]
@impl Uniform.Blueprint
def extra(app) do
theme =
case app.name.underscore do
"empire_" <> _ -> :empire
"rebel_" <> _ -> :rebel
end
# set `app.extra[:theme]`
[theme: theme]
end
@impl Uniform.Blueprint
def target_path(path, app) do
if is_web_file?(path) do
# modify the path to put it in `lib/some_app_web`
String.replace(path, "lib/#{app.name.underscore}/", "lib/#{app.name.underscore}_web/")
else
path
end
end
# files to eject in every app, which are outside `lib/that_app`
base_files do
# copy these directories wholesale; do NOT run them through code modifiers
cp_r "assets"
# eject these files which aren't in lib/the_app_directory
file ".credo.exs"
file ".github/workflows/elixir.yml"
file "priv/static/#{app.extra[:theme]}-favicon.ico"
file "lib/my_app_web.ex"
# eject a file from an EEx template at "lib/uniform/templates/config/runtime.exs.eex"
# configure the templates directory on line 2
template "config/runtime.exs"
# conditionally eject some files
if deploys_to_aws?(app) do
file "file/required/by/aws"
template "dynamic/file/required/by/aws"
end
if depends_on?(app, :lib, :some_lib) do
template "dynamic/file/required/by/some_lib"
end
end
# run the file contents through this modifier if the file is ejected
modify "lib/my_app_web/templates/layout/root.html.heex", file, app do
file
|> String.replace("empire-favicon.ico", "#{app.extra[:theme]}-favicon.ico")
|> String.replace("empire-apple-touch-icon.png", "#{app.extra[:theme]}-apple-touch-icon.png")
end
# configure dependencies from mix.exs and `lib/`
deps do
# always eject the dependencies in the `always` section;
# don't require adding them to uniform.exs
always do
lib :my_app do
# only eject the following files in `lib/my_app`
only ["lib/my_app/application.ex"]
end
lib :my_app_web do
# only eject the following files in `lib/my_app_web`
only [
"lib/my_app_web/endpoint.ex",
"lib/my_app_web/router.ex",
"lib/my_app_web/channels/user_socket.ex",
"lib/my_app_web/views/error_view.ex",
"lib/my_app_web/templates/layout/root.html.heex",
"lib/my_app_web/templates/layout/app.html.heex",
"lib/my_app_web/templates/layout/live.html.heex"
]
end
# always include these mix dependencies
mix :credo
mix :ex_doc
mix :phoenix
mix :phoenix_html
end
# if absinthe is included, also include absinthe_plug and dataloader
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
lib :my_data_lib do
# if my_data_lib is included, also include other_lib, faker, and norm
lib_deps [:other_lib]
mix_deps [:faker, :norm]
# if my_data_lib is included, also eject these files
file "priv/my_data_repo/**/*.exs", match_dot: true
file "test/support/fixtures/my_data_lib/**/*.ex"
end
end
end
"""
@doc """
A callback to add data to `app.extra`.
This callback exists for scenarios when you want to add an `extra` key to
many or all apps, and it can be programmatically determined from the
information in the `app`.
`extra` data that only applies to a single app usually belongs in the
[Uniform Manifest](uniform-manifests-uniform-exs.html).
## Example
You may want to set the theme based on the name of the ejectable app. Return
a keyword list with a `theme` key. It will be available via
`app.extra[:theme]` in `modify/2`, `base_files/1`, and
[templates](building-files-from-eex-templates.html).
def extra(app) do
theme =
case app.name.underscore do
"rebel_" <> _ -> :rebel
"empire_" <> _ -> :empire
_ -> raise "App name must start with rebel_ or empire_ to derive theme."
end
[theme: theme]
end
This prevents you from having to redundantly set
[
extra: [theme: :rebel]
]
In every `uniform.exs` manifest.
> #### uniform.exs has precedence {: .info}
>
> If `uniform.exs` is `[extra: [key: :manifest]]`, `app.extra[:key]`
> will (unsurprisingly) be `:manifest` in the `c:extra/1` callback.
>
> However, if `c:extra/1` returns `[key: :callback]`, `app.extra[:key]`
> will still be `:manifest` in `modify/2`, `base_files/1`, and
> [templates](building-files-from-eex-templates.html).
>
> In other words, `uniform.exs` "has precedence over" the `c:extra/1`
> callback.
"""
@callback extra(app :: Uniform.App.t()) :: keyword
@doc ~S"""
Use this optional callback to change the path of files in the ejected
codebase.
It will be called for every ejected file, with the exception that `cp_r/2`
will only call it once for the entire directory.
If you don't want to modify the `path`, just return it.
## Example
You may want to place certain files in `lib/ejected_app_web` instead of
`lib/ejected_app`. Let's say you have an `is_web_file?` function that
identifies whether the file belongs in the `_web` directory. `target_path`
might look like this:
def target_path(path, app) do
if is_web_file?(path) do
# modify the path to put it in `lib/some_app_web`
String.replace(path, "lib/#{app.name.underscore}/", "lib/#{app.name.underscore}_web/")
else
path
end
end
"""
@callback target_path(path :: Path.t(), app :: Uniform.App.t()) :: Path.t()
@doc ~S"""
This callback works like the `except/1` instruction for Lib Dependencies,
except that it applies to the `lib` folder of the ejected app itself.
When running `mix uniform.eject my_app`, any files in `lib/my_app` or
`test/my_app` which match the paths or regexes returned by `app_lib_except`
will **not** be ejected.
def app_lib_except(app) do
["lib/#{app.name.underscore}/hidden_file.ex"]
end
"""
@callback app_lib_except(app :: Uniform.App.t()) :: [Path.t() | Regex.t()]
@optional_callbacks extra: 1, target_path: 2, app_lib_except: 1
@doc false
defmacro __using__(opts) do
quote do
@behaviour Uniform.Blueprint
@before_compile Uniform.Blueprint.BeforeCompile
# default value of @preserve
@preserve []
import Uniform.Blueprint, only: [modify: 2, deps: 1, base_files: 1]
import Uniform.App, only: [depends_on?: 3]
import Uniform.Modifiers, only: [eject_fences: 3, eject_fences: 4]
require EEx
Module.register_attribute(__MODULE__, :lib_deps, accumulate: true)
Module.register_attribute(__MODULE__, :mix_deps, accumulate: true)
Module.register_attribute(__MODULE__, :modifiers, accumulate: true)
Module.put_attribute(__MODULE__, :templates_dir, unquote(opts[:templates]))
def __templates_dir__, do: @templates_dir
end
end
#
# Modifying the contents of a file
#
@doc """
Modify the contents of one or more files during ejection.
Takes a transformation function which returns the new file contents as a
string.
The first argument is either the relative path of a file in your Base
Project or a `Regex`.
# modify a specific test
modify "tests/path/to/specific_test.exs", fn file -> ... end
# modify all `_test.exs` files
modify ~r/_test.exs$/, fn file -> ... end
The second argument is either a function capture
modify ~r/.+_test.exs/, &MyApp.Modify.modify_tests/1
modify ~r/.+_test.exs/, &MyApp.Modify.modify_tests/2
Or an anonymous function
modify ~r/.+_test.exs/, fn file ->
# ...
end
modify ~r/.+_test.exs/, fn file, app ->
# ...
end
If the function is 1-arity, it will receive the file contents. If it's
2-arity, it will receive the file contents and the `Uniform.App` struct.
## Examples
modify "config/config.exs", file do
file <>
~S'''
if config_env() in [:dev, :test] do
import_config "#\{config_env()}.exs"
end
'''
end
modify ~r/.+_worker.ex/, fn file, app ->
String.replace(file, "SOME_VALUE_PER_APP", app.extra[:some_value])
end
"""
@spec modify(
pattern :: Path.t() | Regex.t(),
function ::
(file :: String.t(), Uniform.App.t() -> String.t())
| (file :: String.t() -> String.t())
) :: term
defmacro modify(path_or_regex, {:&, _, [{:/, _, _}]} = function) do
quote do
Uniform.Blueprint.validate_path_or_regex!(unquote(path_or_regex))
Module.put_attribute(__MODULE__, :modifiers, {unquote(path_or_regex), unquote(function)})
end
end
defmacro modify(path_or_regex, {call, _, _} = function) when call in [:fn, :&] do
# anonymous functions cannot be saved into module attributes, so create a
# named function
fn_name = String.to_atom("__modify_line_#{__CALLER__.line}__")
quote do
Uniform.Blueprint.validate_path_or_regex!(unquote(path_or_regex))
Module.put_attribute(
__MODULE__,
:modifiers,
{unquote(path_or_regex), Function.capture(__MODULE__, unquote(fn_name), 2)}
)
def unquote(fn_name)(file, app) do
f = unquote(function)
case f do
f when is_function(f, 1) -> f.(file)
f when is_function(f, 2) -> f.(file, app)
end
end
end
end
defmacro modify(path_or_regex, function) do
quote do
raise ArgumentError,
message: """
modify/2 expects an anonymous function of a function capture as the 2nd argument.
Received:
#{inspect(unquote(function))}
Instead, either pass an anonymous function:
modify #{inspect(unquote(path_or_regex))}, fn file ->
# ...
end
modify #{inspect(unquote(path_or_regex))}, fn file, app ->
# ...
end
Or pass a function capture:
modify #{inspect(unquote(path_or_regex))}, &modify_tests/1
modify #{inspect(unquote(path_or_regex))}, &Modifiers.modify_other_file/2
modify #{inspect(unquote(path_or_regex))}, & foo(&1, &2)
"""
end
end
@doc false
def validate_path_or_regex!(path_or_regex) do
case path_or_regex do
path when is_binary(path) ->
:ok
%Regex{} ->
:ok
_ ->
raise ArgumentError,
message: """
modify/2 expects a (string) path or a regex (~r/.../) as the first argument. Received #{inspect(path_or_regex)}
"""
end
end
#
# Configuring which files to eject outside of the files in the app's `lib` directory.
#
@doc ~S"""
The `base_files` section is where you specify files outside of an ejected
app's `lib/my_app` and `test/my_app` directories which should always be
ejected.
This section has access to an [`app`](`t:Uniform.App.t/0`) variable which can
be used to build the instructions or conditionally include certain files with
`if`.
base_files do
# interpolating the app name into a path dynamically
cp "priv/static/images/#{app.name.underscore}_logo.png"
# conditional instructions
if deploys_to_fly_io?(app) do
template "fly.toml"
end
end
**Note that `if` conditionals cannot be nested here.**
## API Reference
- `file/2` ejects a single file or list of files
- `template/2` creates a new file on ejection from an EEx template
- `cp/2` copies a file (like `file/2`) without running it through [Code
Transformations](code-transformations.html). This is useful for binary
files such as images or executable.
- `cp_r/2` copies an entire **directory** of files without [Code
Transformations](code-transformations.html).
## Example
base_files do
file ".credo.exs"
file "config/**/*.exs"
template "config/runtime.exs"
cp "bin/some-executable", chmod: 0o555
cp_r "assets"
end
## Files in `lib`
Typically, the `base_files` section only contains files that aren't in `lib`,
since files in `lib/app_being_ejected` and `lib/required_lib_dependency` are
ejected automatically.
# ❌ Don't do this
base_files do
file "lib/my_lib/some_file.ex"
end
# ✅ Instead, do this (lib/my_app/uniform.exs)
[
lib_deps: [:my_lib]
]
## Files outside `lib` but tied to Lib Dependencies
If a file or template should only be ejected in the case that a certain Lib
Dependency is included, we recommend placing that inside the `deps` section
instead of in `base_files`. (See [Associated
files](#lib/2-associated-files).)
# ❌ Don't do this
base_files do
if depends_on?(app, :lib, :my_lib) do
file "some_file"
end
end
# ✅ Instead, do this
deps do
lib :my_lib do
file "some_file"
end
end
"""
defmacro base_files([do: block] = _files) do
items = block_contents(block)
items =
Enum.map(items, fn
{:if, meta, [condition, [do: {:__block__, [], items}]]} ->
{:if, meta, [condition, [do: items]]}
item ->
item
end)
# inject magic `app` variable
app = quote generated: true, do: var!(app)
quote do
try do
import Uniform.Blueprint, except: [base_files: 1, only: 1]
def __base_files__(unquote(app)),
do: unquote(items) |> List.flatten() |> Enum.reject(&is_nil/1)
after
:ok
end
end
end
#
# Dependencies
#
@doc """
Uniform automatically catalogs all Mix deps by looking into `mix.exs` to
discover all Mix deps. It also catalogs all Lib deps by scanning the `lib/`
directory.
If you need to configure anything about a Mix or Lib dep, such as other
dependencies that must be bundled along with it, use the `deps` block.
See `mix/2`, `lib/2`, and `always/1` for more details.
## Example
deps do
always do
lib :my_app do
only ["lib/my_app/application.ex"]
end
mix :phoenix
end
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
lib :my_custom_aws_lib do
lib_deps [:my_utilities_lib]
mix_deps [:ex_aws, :ex_aws_ec2]
end
end
"""
@spec deps(mix_and_lib_deps :: term) :: term
defmacro deps(_mix_and_lib_deps = [do: block]) do
prelude =
quote do
try do
import Uniform.Blueprint, only: [lib: 1, lib: 2, mix: 1, mix: 2, always: 1]
@deps_always_block false
unquote(block)
after
:ok
end
end
postlude =
quote unquote: false do
lib_deps = @lib_deps |> Enum.reverse()
mix_deps = @mix_deps |> Enum.reverse()
def __deps__(:lib), do: unquote(Macro.escape(lib_deps))
def __deps__(:mix), do: unquote(Macro.escape(mix_deps))
end
quote do
unquote(prelude)
unquote(postlude)
end
end
@doc """
Inside of a `deps do` block, any Mix or Lib dependencies that should be
included in every ejected app should be wrapped in an `always do` block:
deps do
always do
# always eject the contents of `lib/some_lib`
lib :some_lib
# always eject the some_mix Mix dependency
mix :some_mix
end
end
"""
defmacro always(_deps = [do: block]) do
quote do
try do
@deps_always_block true
unquote(block)
after
@deps_always_block false
end
end
end
@doc """
[Lib Dependencies](dependencies.html#lib-dependencies) are shared code
libraries in the `lib` directory of your project. Uniform scans `lib/`, so
you don't need to inform it about them.
However, there are four scenarios where you do need to list them in your
Blueprint.
## 1. Specifying which Lib Dependencies should _always_ be ejected
deps do
always do
# every app will have lib/utilities
lib :utilities
end
end
See `always/1`.
## 2. When a Lib Dependency has other Lib or Mix Dependencies
deps do
# if `lib/auth` is included, tesla and `lib/utils` will be included
lib :auth do
mix_deps [:tesla]
lib_deps [:utils]
end
end
See `mix_deps/1` and `lib_deps/1`.
## 3. Including files outside of `lib`
Some libraries require other files outside of `lib/that_library`.
For example:
deps do
# when `lib/my_data_source is included...
lib :my_data_source do
# files in priv/my_data_source_repo will be included
file "priv/my_data_source_repo/**", match_dot: true
# .ex files in `test/support/my_data_source` will be included
file "test/support/my_data_source/**/*.ex"
# `priv/my_data_source_file` will be included from a template
template "priv/my_data_source_file"
end
end
See `file/2`, `template/2`, `cp/1`, and `cp_r/1`.
## 4. When certain files should be excluded from a Lib Dependency upon ejection
deps do
always do
# every app will have lib/mix, but only `some_task.ex` will be ejected
lib :mix do
only ["lib/mix/tasks/some_task.ex"]
end
end
# `some_file.ex` will be omitted from `lib/auth` in ejected codebases
lib :auth do
except ["lib/auth/some_file.ex"]
end
end
See `only/1` and `except/1`.
"""
defmacro lib(name, do: block) do
opts = block_contents(block)
quote do
try do
import Uniform.Blueprint
Uniform.Blueprint.__lib__(__MODULE__, unquote(name), unquote(opts), @deps_always_block)
after
:ok
end
end
end
@doc false
defmacro lib(name) do
quote do
Uniform.Blueprint.__lib__(__MODULE__, unquote(name), [], @deps_always_block)
end
end
defp block_contents({:__block__, _meta, contents}), do: contents
defp block_contents(item), do: [item]
@doc false
def __lib__(mod, name, opts, always) when is_atom(name) and is_list(opts) do
associated_files =
for {type, path} <- opts, type in [:text, :template, :cp, :cp_r], do: {type, path}
lib_dep =
Uniform.LibDep.new!(%{
name: name,
lib_deps: opts |> Keyword.get(:lib_deps, []) |> List.wrap(),
mix_deps: opts |> Keyword.get(:mix_deps, []) |> List.wrap(),
always: always,
only: opts[:only],
except: opts[:except],
associated_files: associated_files
})
Module.put_attribute(mod, :lib_deps, lib_dep)
end
@doc """
Uniform automatically catalogues the deps in `mix.exs`, so there are only two
scenarios where you need to list them in your Blueprint.
## 1. Specifying deps that should _always_ be ejected.
deps do
always do
# every app will have credo and ex_doc
mix :credo
mix :ex_doc
end
end
See `always/1`.
## 2. When a Mix Dependency has other Mix Dependencies.
deps do
mix :oban do
# any app that is ejected with oban will also have oban_pro and oban_web
mix_deps [:oban_pro, :oban_web]
end
end
See `mix_deps/1`.
"""
defmacro mix(name, do: block) do
opts =
case block do
{:__block__, _meta, opts} -> opts
opt -> [opt]
end
quote do
try do
import Uniform.Blueprint, only: [mix_deps: 1]
Uniform.Blueprint.__mix__(__MODULE__, unquote(name), unquote(opts), @deps_always_block)
after
:ok
end
end
end
@doc false
defmacro mix(name) do
quote do
Uniform.Blueprint.__mix__(__MODULE__, unquote(name), [], @deps_always_block)
end
end
@doc false
def __mix__(mod, name, opts, always) when is_atom(name) and is_list(opts) do
mix_dep =
Uniform.MixDep.new!(%{
name: name,
always: always,
mix_deps: opts |> Keyword.get(:mix_deps, []) |> List.wrap()
})
Module.put_attribute(mod, :mix_deps, mix_dep)
end
@doc """
Specify transitive [Mix Dependencies](dependencies.html#mix-dependencies) of
other Lib/Mix Dependencies.
Dependencies listed with `mix_deps` will be included in the ejected `mix.exs`
any time the "parent" dependency is included.
## Examples
deps do
# if absinthe is included, absinthe_plug and dataloader will be included
mix :absinthe do
mix_deps [:absinthe_plug, :dataloader]
end
# when `lib/ui_components` is included, surface will be included
lib :ui_components do
mix_deps [:surface]
end
end
"""
defmacro mix_deps(deps), do: {:mix_deps, List.wrap(deps)}
@doc """
Specify transitive [Lib Dependencies](dependencies.html#lib-dependencies) of
other Lib Dependencies.
Libraries listed with `lib_deps` will be included in the ejected codebase any
time the "parent" dependency is included.
## Examples
deps do
# `lib/core_auth` will never be ejected without `lib/oauth_lib`
lib :core_auth do
lib_deps [:oauth_lib]
end
end
"""
defmacro lib_deps(deps), do: {:lib_deps, List.wrap(deps)}
@doc """
In `base_files` and `lib` blocks, `file` is used to specify **files that are
not in a `lib/` directory** which should be ejected in the app or along with
the lib.
## Options
- `chmod` – Sets the `mode` for the given `file` after it's ejected. See the
possible [permission options](https://hexdocs.pm/elixir/File.html#chmod/2-permissions).
- `match_dot` – Forwarded to `Path.wildcard/2`. See "Wildcard Globs" below.
## Glob Expressions
You can use a `glob` expression with [wildcard characters](`Path.wildcard/2`)
to target multiple files. (See "Examples" below.)
This is possible because Uniform internally forwards the `path` and `opts` to
`Path.wildcard/2` to determine which files to eject.
Note that the `*` and `?` "wildcard characters" will not match files starting
with a dot (`.`) unless you provide `match_dot: true`.
## Examples
base_files do
file "assets/js/app.js"
file "some/file", chmod: 0o777
# glob targeting .exs files in config/
file "config/**/*.exs"
# glob targeting .exs files in priv/repo/ – including .formatter.exs
file "priv/repo/**/*.exs", match_dot: true
end
deps do
lib :aws do
# for every app that includes the aws lib dependency,
# some_aws_fixture.xml will also be included
file "test/support/fixtures/some_aws_fixture.xml"
end
end
"""
def file(path, opts \\ []), do: {:text, {path, opts}}
@doc """
In `base_files` and `lib` blocks, `template` is used to specify EEx templates
that should be rendered and then ejected.
## Template Directory and Destination Path
Uniform templates use a "convention over configuration" model that works like
this:
1. At the top of your `Blueprint` module, you specify a template directory
like this:
`use Uniform, templates: "lib/uniform/templates"`
2. Templates must be placed in this directory at the relative path that they
should be placed in, in the ejected directory.
3. Templates must have the destination filename, appended with `.eex`.
> #### Companion guide {: .tip}
>
> Consult [Building files from EEx
> templates](building-files-from-eex-templates.html) for a more detailed look
> at constructing and effectively using templates for ejection.
## Options
`template` takes a `chmod` option, which sets the `mode` for the rendered
file after it's ejected. See the possible [permission
options](https://hexdocs.pm/elixir/File.html#chmod/2-permissions).
## Examples
use Uniform, templates: "priv/uniform-templates"
base_files do
# priv/uniform-templates/config/runtime.exs.eex will be rendered, and the
# result will be placed in `config/runtime.exs` in every ejected app
template "config/runtime.exs"
end
deps do
lib :datadog do
# for every app that includes `lib/datadog`,
# priv/uniform-templates/datadog/prerun.sh.eex will be rendered, and
# the result will be placed in `datadog/prerun.sh`
template "datadog/prerun.sh", chmod: 0o555
end
end
"""
def template(path, opts \\ []), do: {:template, {path, opts}}
@doc """
`cp_r` works like `cp/2`, but for a directory instead of a file. The
directory is copied as-is with `File.cp_r!/3`.
**None of the files are ran through [Code
Transformations](code-transformations.html).**
This is useful for directories that do not require modification, and contain
many files.
For example, the `assets/node_modules` directory in a Phoenix application
would take ages to copy with `file "assets/node_modules/**/*"`. Instead, use
`cp_r "assets/node_modules"`.
## Examples
base_files do
cp_r "assets"
end
deps do
lib :some_lib do
cp_r "priv/files_for_some_lib"
end
end
"""
def cp_r(path, opts \\ []), do: {:cp_r, {path, opts}}
@doc """
`cp` works exactly like `file/2`, except that **no [Code
Transformations](code-transformations.html) are applied to the file**.
The file is copied as-is with `File.cp!/3`.
## Options
`cp` takes a `chmod` option, which sets the `mode` for the file after it's
copied. See the possible [permission
options](https://hexdocs.pm/elixir/File.html#chmod/2-permissions).
## Examples
base_files do
# every ejected app will have bin/some-binary, with the ACL mode changed to 555
cp "bin/some-binary", chmod: 0o555
end
deps do
lib :pdf do
# apps that include the pdf lib will also have bin/convert
cp "bin/convert"
end
end
"""
def cp(path, opts \\ []), do: {:cp, {path, opts}}
@doc """
In the `deps` section of your Blueprint, you can specify that a Lib
Dependency excludes certain files.
This works much like the `except` option that can be given when importing
functions with `import/2`.
In the example below, for any app that depends on `:aws`, every file in
`lib/aws` and `test/aws` will be ejected except for `lib/aws/hidden_file.ex`.
deps do
lib :aws do
except ["lib/aws/hidden_file.ex"]
end
end
"""
def except(paths), do: {:except, List.wrap(paths)}
@doc """
In the `deps` section of your Blueprint, you can specify that a Lib
Dependency only includes certain files.
These work much like the `only` option that can be given when importing
functions with `import/2`.
In the example below, for any app that depends on `:gcp`, only
`lib/gcp/necessary_file.ex` will be ejected. Nothing else from `lib/gcp` or
`test/gcp` will be ejected.
deps do
lib :gcp do
# NOTHING in lib/gcp or test/gcp will be included except these:
only ["lib/gcp/necessary_file.ex"]
end
end
"""
def only(paths), do: {:only, List.wrap(paths)}
end