Packages
igniter
0.5.47
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.30
0.6.29
0.6.28
0.6.27
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.52
0.5.51
0.5.50
0.5.49
0.5.48
0.5.47
0.5.46
0.5.45
0.5.44
0.5.43
0.5.42
0.5.41
0.5.40
0.5.39
0.5.38
0.5.37
0.5.36
0.5.35
0.5.34
0.5.33
0.5.32
0.5.31
0.5.30
0.5.29
0.5.28
0.5.27
0.5.26
0.5.25
0.5.24
0.5.23
0.5.22
0.5.21
0.5.20
0.5.19
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.78
0.3.77
0.3.76
0.3.75
0.3.74
0.3.73
0.3.72
0.3.71
0.3.70
0.3.69
0.3.68
0.3.67
0.3.66
0.3.65
0.3.64
0.3.63
0.3.62
0.3.61
0.3.60
0.3.59
0.3.58
0.3.57
0.3.56
0.3.55
0.3.54
0.3.53
0.3.52
0.3.51
0.3.50
0.3.49
0.3.48
0.3.47
0.3.46
0.3.45
0.3.44
0.3.43
0.3.42
0.3.41
0.3.40
0.3.39
0.3.38
0.3.37
0.3.36
0.3.35
0.3.34
0.3.33
0.3.32
0.3.31
0.3.30
0.3.29
0.3.28
0.3.27
0.3.26
0.3.25
0.3.24
0.3.23
0.3.22
0.3.21
0.3.20
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A code generation and project patching framework
Current section
Files
Jump to
Current section
Files
lib/igniter/util/info.ex
defmodule Igniter.Util.Info do
@moduledoc false
# This is an *extremely* unfortunate hack.
# For dependencies that depend on other common dev dependencies (like `sourceror`)
# our installation process has an issue. Specifically, we first install the dep
# into all environments, and then set it to the right env.
# This causes our conflict resolution logic to set the dependents (like `sourceror`)
# to be for all envs, but then never set back to only specific envs.
# I don't have time to fix this right now, nor can I think of a good way to actually
# do it. So for now, this ridiculous hack will have to do :)
@known_only_options [
smokestack: [:test],
mishka_chelekom: [:dev],
mneme: [:dev, :test],
live_debugger: [:dev]
]
@known_only_keys Keyword.keys(@known_only_options)
require Logger
alias Igniter.Mix.Task.Info
def compose_install_and_validate!(
igniter \\ Igniter.new(),
argv,
schema,
task_name,
opts,
return \\ :options,
insert_before \\ nil,
acc \\ []
) do
schema = %{
schema
| flag_conflicts: flag_groups(schema, task_name),
alias_conflicts: alias_groups(schema, task_name)
}
schema = recursively_compose_schema(schema, argv, task_name, opts)
case schema.installs do
[] ->
names = Enum.map_join(List.wrap(schema.adds_deps), ", ", &elem(&1, 0))
igniter =
igniter
|> add_deps(
List.wrap(schema.adds_deps),
opts
)
|> Igniter.apply_and_fetch_dependencies(
Keyword.put(opts, :operation, "compiling #{names}")
)
if return == :options do
raise_on_conflicts!(schema, argv)
{igniter, Enum.uniq(acc), validate!(argv, schema, task_name)}
else
{igniter, acc, schema}
end
installs ->
schema = %{schema | installs: []}
install_names = Enum.map(installs, &elem(&1, 0))
count = Enum.count(install_names)
names_message =
if count >= 4 do
"#{count} packages"
else
Enum.join(
Enum.uniq(Enum.map(List.wrap(schema.adds_deps), &elem(&1, 0)) ++ install_names),
", "
)
end
installs =
Enum.map(installs, fn
{dep, val} when is_binary(val) and dep in @known_only_keys ->
{dep, val, only: @known_only_options[dep]}
{dep, opts} when is_list(opts) and dep in @known_only_keys ->
{dep, Keyword.put_new(opts, :only, @known_only_options[dep])}
{dep, val, opts} when dep in @known_only_keys ->
{dep, val, Keyword.put_new(opts, :only, @known_only_options[dep])}
other ->
other
end)
igniter =
igniter
|> add_deps(
List.wrap(installs),
opts
)
|> Igniter.apply_and_fetch_dependencies(
Keyword.put(opts, :operation, "compiling #{names_message}")
)
|> maybe_set_dep_options(install_names, argv, task_name)
acc = insert_installs(acc, install_names, insert_before)
adds_deps = schema.adds_deps
{igniter, acc, schema} =
installs
|> Enum.reduce({igniter, acc, schema}, fn install, {igniter, acc, schema} ->
name =
if is_tuple(install) do
elem(install, 0)
else
install
end
compose_install_and_validate!(
igniter,
argv,
%{
schema
| composes: ["#{name}.install"],
installs: [],
adds_deps: []
},
task_name,
Keyword.delete(opts, :only),
:schema,
name,
acc
)
end)
compose_install_and_validate!(
igniter,
argv,
%{
schema
| adds_deps: schema.adds_deps ++ adds_deps
},
task_name,
Keyword.delete(opts, :only),
return,
insert_before,
acc
)
end
end
defp insert_installs(acc, install_names, insert_before) do
if insert_before in acc do
Enum.flat_map(acc, fn item ->
if item == insert_before do
install_names ++ [item]
else
[item]
end
end)
else
install_names ++ acc
end
end
defp raise_on_conflicts!(schema, argv) do
flag_conflicts =
schema.flag_conflicts
|> Map.drop(Keyword.keys(Igniter.Mix.Task.Info.global_options()[:switches]))
|> Enum.filter(fn {_, list} ->
Enum.count(list) > 1
end)
|> Map.new(fn {k, v} ->
{"--" <> String.replace(to_string(k), "_", "-"), v}
end)
alias_conflicts =
schema.alias_conflicts
|> Map.drop(Keyword.keys(Igniter.Mix.Task.Info.global_options()[:aliases]))
|> Enum.flat_map(fn {k, list} ->
case Enum.uniq_by(list, &elem(&1, 0)) do
[] -> []
[_] -> []
list -> [{k, Enum.uniq(Enum.map(list, &elem(&1, 1)))}]
end
end)
|> Map.new(fn {k, v} ->
{"-" <> String.replace(to_string(k), "_", "-"), v}
end)
if Enum.empty?(flag_conflicts) and Enum.empty?(alias_conflicts) do
:ok
else
Enum.each(argv, fn arg ->
cond do
conflicting = flag_conflicts[arg] ->
Mix.shell().error("""
Ambiguous flag provided: `#{arg}`
The tasks or task groups `#{Enum.join(conflicting, ", ")}` all define the flag `#{arg}`.
To disambiguate, provide the arg as `--<prefix>.#{String.trim_leading(arg, "--")}`,
where `<prefix>` is the task or task group name.
For example:
`--#{Enum.at(conflicting, 0)}.#{String.trim_leading(arg, "--")}`
""")
exit({:shutdown, 2})
conflicting = alias_conflicts[arg] ->
Mix.shell().error("""
Ambiguous flag provided: `#{arg}`
The tasks or task groups `#{Enum.join(conflicting, ", ")}` all define the flag `#{arg}`.
To disambiguate, provide the arg as `-<prefix>.#{String.trim_leading(arg, "-")}`,
where `<prefix>` is the task or task group name.
For example:
`--#{Enum.at(conflicting, 0)}.#{String.trim_leading(arg, "-")}`
""")
exit({:shutdown, 2})
true ->
:ok
end
end)
end
end
defp maybe_set_dep_options(igniter, install_names, argv, parent) do
Enum.reduce(install_names, igniter, fn install, igniter ->
composing_task = "#{install}.install"
with composing_task when not is_nil(composing_task) <- Mix.Task.get(composing_task),
true <- function_exported?(composing_task, :info, 2),
composing_schema when not is_nil(composing_schema) <-
composing_task.info(argv, parent) do
options =
if composing_schema.only do
Keyword.put(composing_schema.dep_opts, :only, composing_schema.only)
else
composing_schema.dep_opts
end
if options == [] do
igniter
else
Enum.reduce(options, igniter, fn {key, val}, igniter ->
val =
if key == :only do
List.wrap(val)
else
val
end
Igniter.Project.Deps.set_dep_option(igniter, install, key, val)
end)
end
else
_ ->
igniter
end
end)
end
defp add_deps(igniter, add_deps, opts) do
Enum.reduce(add_deps, igniter, fn dependency, igniter ->
with {name, _, dep_opts} <- dependency,
only when not is_nil(only) <- dep_opts[:only],
false <- Mix.env() in only do
igniter
|> Igniter.assign(
:failed_to_add_deps,
[name | igniter.assigns[:failed_to_add_deps] || []]
)
|> Igniter.add_warning("""
Dependency #{inspect(dependency)} could not be installed,
because it is configured to be installed with `only: #{inspect(only)}`.
Please install it manually, for example:
`MIX_ENV=#{Enum.at(only, 0)} mix igniter.install #{dependency}`.
""")
else
_ ->
new_igniter =
Igniter.Project.Deps.add_dep(
igniter,
dependency,
Keyword.merge(opts, error?: true, notify_on_present?: true, yes?: !!opts[:yes])
)
name =
case dependency do
{name, _} -> name
{name, _, _} -> name
end
if Enum.count(igniter.issues) != Enum.count(new_igniter.issues) ||
Enum.count(igniter.warnings) != Enum.count(new_igniter.warnings) do
Igniter.assign(
new_igniter,
:failed_to_add_deps,
[name | igniter.assigns[:failed_to_add_deps] || []]
)
else
new_igniter
end
end
end)
end
def validate!(argv, schema, task_name)
def validate!(_argv, nil, _task_name), do: {[], []}
def validate!(argv, schema, task_name) do
group = group(schema, task_name)
argv = args_for_group(argv, group)
merged_schema = recursively_compose_schema(schema, argv, task_name, [])
options_key =
if merged_schema.extra_args? do
:switches
else
:strict
end
OptionParser.parse!(
argv,
[
{options_key, clean_csv(merged_schema.schema || [])},
{:aliases, merged_schema.aliases || []}
]
)
end
defp clean_csv(schema) do
Enum.map(schema, fn
{k, :csv} ->
{k, :keep}
{k, v} ->
{k, v}
end)
end
@doc false
def args_for_group(argv, group) do
case argv do
["--" <> arg, "-" <> v2 | rest] ->
with true <- String.starts_with?(arg, "#{group}."),
[_, actual_arg] <- String.split(arg, "#{group}.", parts: 2) do
["--" <> actual_arg] ++ args_for_group(["-" <> v2 | rest], group)
else
_ ->
rest_args = args_for_group(["-" <> v2 | rest], group)
if String.contains?(arg, ".") do
rest_args
else
["--#{arg}"] ++ rest_args
end
end
["-" <> arg, "-" <> v2 | rest] ->
with true <- String.starts_with?(arg, "#{group}."),
[_, actual_arg] <- String.split(arg, "#{group}.", parts: 2) do
["-" <> actual_arg] ++ args_for_group(["-" <> v2 | rest], group)
else
_ ->
rest_args = args_for_group(["-" <> v2 | rest], group)
if String.contains?(arg, ".") do
rest_args
else
["-#{arg}"] ++ rest_args
end
end
["--" <> arg | rest] ->
with true <- String.starts_with?(arg, "#{group}."),
[_, actual_arg] <- String.split(arg, "#{group}.", parts: 2) do
["--" <> actual_arg] ++ args_for_group(rest, group)
else
_ ->
rest_args = args_for_group(rest, group)
if String.contains?(arg, ".") do
rest_args
else
["--#{arg}"] ++ rest_args
end
end
["-" <> arg | rest] ->
with true <- String.starts_with?(arg, "#{group}."),
[_, actual_arg] <- String.split(arg, "#{group}.", parts: 2) do
["-" <> actual_arg] ++ args_for_group(rest, group)
else
_ ->
rest_args = args_for_group(rest, group)
if String.contains?(arg, ".") do
rest_args
else
["-#{arg}"] ++ rest_args
end
end
[v1 | rest] ->
[v1] ++ args_for_group(rest, group)
[] ->
[]
end
end
defp recursively_compose_schema(%Info{composes: []} = schema, _argv, _parent, _opts), do: schema
defp recursively_compose_schema(%Info{composes: [compose | rest]} = schema, argv, parent, opts) do
with composing_task when not is_nil(composing_task) <- Mix.Task.get(compose),
true <- function_exported?(composing_task, :info, 2),
composing_schema when not is_nil(composing_schema) <- composing_task.info(argv, parent) do
composing_task_name = Mix.Task.task_name(composing_task)
composing_schema =
if opts[:only] do
%{composing_schema | only: opts[:only]}
else
composing_schema
end
recursively_compose_schema(
%Info{
schema
| schema:
merge_schemas(
schema.schema,
composing_schema.schema
),
aliases:
merge_aliases(
schema.aliases,
composing_schema.aliases
),
flag_conflicts: flag_conflicts(schema, composing_schema, composing_task_name),
alias_conflicts: alias_conflicts(schema, composing_schema, composing_task_name),
composes: rest,
extra_args?: schema.extra_args? || composing_schema.extra_args?,
installs: Keyword.merge(composing_schema.installs, schema.installs),
adds_deps: Keyword.merge(composing_schema.adds_deps, schema.adds_deps)
},
argv,
parent,
Keyword.delete(opts, :only)
)
|> Map.put(:composes, List.wrap(composing_schema.composes))
|> recursively_compose_schema(argv, composing_task_name, Keyword.delete(opts, :only))
else
_ ->
recursively_compose_schema(
%{schema | composes: rest},
argv,
parent,
Keyword.delete(opts, :only)
)
end
end
defp flag_conflicts(schema, composing_schema, composing_task_name) do
Map.merge(
schema.flag_conflicts,
flag_groups(composing_schema, composing_task_name),
fn _key, schema_value, composing_value ->
Enum.uniq(schema_value ++ composing_value)
end
)
end
defp alias_conflicts(schema, composing_schema, composing_task_name) do
Map.merge(
schema.alias_conflicts,
alias_groups(composing_schema, composing_task_name),
fn _key, schema_value, composing_value ->
Enum.uniq(schema_value ++ composing_value)
end
)
end
@doc false
def group(%{group: group}, _task_name) when not is_nil(group),
do: String.replace(to_string(group), "_", "-")
def group(_, task_name), do: String.replace(task_name, "_", "-")
defp merge_schemas(schema, composing_schema) do
schema = schema || []
composing_schema = composing_schema || []
Keyword.merge(composing_schema, schema)
end
defp merge_aliases(aliases, composing_aliases) do
aliases = aliases || []
composing_aliases = composing_aliases || []
Keyword.merge(composing_aliases, aliases)
end
defp flag_groups(schema, task_name) do
Map.new(schema.schema, fn {k, _} ->
{k, [group(schema, task_name)]}
end)
end
defp alias_groups(schema, task_name) do
Map.new(schema.aliases, fn {k, v} ->
{k, [{v, group(schema, task_name)}]}
end)
end
end