Packages

A standard library setup based on the Nerves project standard practices.

Current section

Files

Jump to
nstandard lib nstandard igniters.ex
Raw

lib/nstandard/igniters.ex

defmodule Nstandard.Igniters do
@moduledoc """
Igniters for adding the standard parts to a mix project.
"""
@deps [
{:ex_doc, "~> 0.40", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:spellweaver, "~> 0.1", only: [:dev, :test], runtime: false}
]
def add_docs(igniter) do
app_name = Igniter.Project.Application.app_name(igniter)
fancy_name =
app_name
|> to_string()
|> String.replace("_", " ")
|> String.capitalize()
igniter
|> new_project_string([:name], fancy_name)
|> new_project_string(
[:description],
"TODO: write a proper description"
)
|> new_project_function(:docs, main: "readme", extras: ["README.md"])
end
def add_package(igniter) do
app_name = Igniter.Project.Application.app_name(igniter)
igniter
|> new_project_function(:package,
name: app_name,
licenses: ["Apache-2.0"],
links: %{"GitHub" => "https://github.com/TODO/#{app_name}"}
)
end
def add_linters(igniter) do
igniter =
igniter
|> new_project_function(:aliases,
check: [
"hex.audit",
"compile --warnings-as-errors --force",
"format --check-formatted",
"credo",
"deps.unlock --check-unused",
"spellweaver.check",
"dialyzer"
],
precommit: [
"hex.audit",
"compile --warnings-as-errors --force",
"format",
"credo",
"deps.unlock --unused",
"spellweaver.check",
"dialyzer",
"test"
]
)
|> Igniter.Project.MixProject.update(
:cli,
[:preferred_envs, :precommit],
fn _ -> {:ok, {:code, :test}} end
)
|> new_project_function(:dialyzer,
plt_add_apps: [:mix],
ignore_warnings: ".dialyzer_ignore.exs"
)
@deps
|> Enum.reduce(igniter, fn dep, igniter ->
Igniter.Project.Deps.add_dep(igniter, dep, append?: true)
end)
end
def add_credo_config(igniter) do
contents = """
%{
configs: [
%{
name: "default",
files: %{
included: ["lib/"],
},
strict: true,
checks: [
{Credo.Check.Refactor.MapInto, false},
{Credo.Check.Warning.LazyLogging, false},
{Credo.Check.Readability.LargeNumbers, only_greater_than: 86400},
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, parens: true},
{Credo.Check.Readability.Specs, tags: []},
{Credo.Check.Readability.StrictModuleLayout, tags: []}
]
}
]
}
"""
igniter
|> Igniter.create_new_file(".credo.exs", contents, on_exists: :warning)
end
def add_license(igniter) do
license =
:code.priv_dir(:nstandard)
|> Path.join("LICENSE.md")
|> File.read!()
igniter
|> Igniter.create_new_file("LICENSE.md", license, on_exists: :warning)
end
def add_changelog(igniter) do
changelog = """
# Changelog
## v0.1.0
- TODO: write changelog
"""
igniter
|> Igniter.create_new_file("CHANGELOG.md", changelog, on_exists: :warning)
end
def add_dialyzer_ignore(igniter) do
dialyzer_ignore =
:code.priv_dir(:nstandard)
|> Path.join(".dialyzer_ignore.exs")
|> File.read!()
igniter
|> Igniter.create_new_file(".dialyzer_ignore.exs", dialyzer_ignore, on_exists: :warning)
end
def add_bun_config(igniter) do
if Igniter.exists?(igniter, "config/config.exs") do
Igniter.Project.Config.configure_new(
igniter,
"config/config.exs",
:bun,
[:version],
"1.2.2"
)
else
igniter
end
end
def add_cspell_config(igniter) do
contents = """
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"language": "en",
"useGitignore": true,
"dictionaryDefinitions": [
{
"name": "fhunleth-cspell-dictionary",
"path": "https://raw.githubusercontent.com/fhunleth/fhunleth-cspell-dictionary/refs/heads/main/words.txt",
"addWords": false
}
],
"dictionaries": [
"elixir",
"en-gb",
"fhunleth-cspell-dictionary"
],
"ignorePaths": [
"deps",
"_build",
"assets",
"mix.exs",
"priv",
"test"
],
"words": []
}
"""
igniter
|> Igniter.create_new_file(".cspell.json", contents, on_exists: :warning)
end
def add_dependabot(igniter) do
dependabot =
:code.priv_dir(:nstandard)
|> Path.join("github/dependabot.yml")
|> File.read!()
igniter
|> Igniter.mkdir(".github")
|> Igniter.create_new_file(".github/dependabot.yml", dependabot, on_exists: :warning)
end
def add_ci(igniter) do
ci =
:code.priv_dir(:nstandard)
|> Path.join("github/ci.yml")
|> File.read!()
igniter
|> Igniter.mkdir(".github")
|> Igniter.create_new_file(".github/ci.yml", ci, on_exists: :warning)
end
defp new_project_function(igniter, function_name, kv_pairs) do
igniter
|> Igniter.Project.MixProject.update(:project, [function_name], fn zipper ->
if zipper do
{:ok, zipper}
else
{:ok,
{:code,
quote do
unquote(function_name)()
end}}
end
end)
|> add_kv_pairs(function_name, kv_pairs)
end
defp add_kv_pairs(igniter, function_name, kv_pairs) do
# Check if the function exists as defp in mix.exs (e.g., Phoenix projects).
# If so, route through :project which resolves both def and defp.
# Otherwise, use the direct function path which creates the function if needed.
use_project_path = has_defp_in_mix?(igniter, function_name)
kv_pairs
|> Enum.reduce(igniter, fn {key, value}, igniter ->
updater = fn
nil ->
{:ok, {:code, {:__block__, [], [value]}}}
zipper ->
{:ok, zipper}
end
if use_project_path do
Igniter.Project.MixProject.update(igniter, :project, [function_name, key], updater)
else
Igniter.Project.MixProject.update(igniter, function_name, [key], updater)
end
end)
end
defp has_defp_in_mix?(igniter, function_name) do
case Rewrite.source(igniter.rewrite, "mix.exs") do
{:ok, source} ->
source
|> Rewrite.Source.get(:content)
|> String.contains?("defp #{function_name}")
_ ->
false
end
end
defp new_project_string(igniter, path, value) do
igniter
|> Igniter.Project.MixProject.update(:project, path, fn zipper ->
if zipper do
{:ok, zipper}
else
{:ok, {:code, {:__block__, [], [value]}}}
end
end)
end
end