Packages

Parses and loads CASC config files -- a hierarchical, extensible config language with imports, variables, interpolation, loops, and consumer-registered resolvers/tags -- into native Elixir terms, secrets redacted by default.

Current section

Files

Jump to
cooper mix.exs
Raw

mix.exs

defmodule Cooper.MixProject do
  use Mix.Project

  @version "0.4.0"

  def project do
    [
      app: :cooper,
      version: @version,
      elixir: "~> 1.19",
      start_permanent: Mix.env() == :prod,
      elixirc_paths: elixirc_paths(Mix.env()),
      deps: deps(),
      description: description(),
      package: package(),
      name: "Cooper",
      docs: docs(),
      aliases: aliases(),
      test_coverage: [tool: ExCoveralls],
      # `:ichor` (`runtime: false`) is invisible to dialyxir's automatic
      # PLT app discovery -- that walks the OTP *runtime*-dependency
      # graph, which `runtime: false` deliberately excludes it from,
      # even though it's compiled and genuinely called by
      # `test/support/vm_parity.ex` under :test. Added explicitly so
      # `Aether.Parser`/`Grammar.Analysis`/`Grammar.VM.*` are in the PLT
      # dialyzer checks that module against.
      dialyzer: [plt_add_apps: [:mix, :ichor], ignore_warnings: ".dialyzer_ignore.exs"]
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  # `mix precommit` chains "test" alongside "format"/"compile"/"credo"/
  # "sobelow"/"dialyzer" -- unlike a bare `mix test`, Mix doesn't
  # special-case the env for a task run *through* an alias, so without
  # this the "test" step in the chain fails outright ("mix test is
  # running in the dev environment"). Running the *whole* alias under
  # :test is also what makes credo/dialyzer see `test/support/`
  # (`Cooper.Test.VMParity`) -- `elixirc_paths/1` above only adds it for
  # :test in the first place.
  def cli do
    [preferred_envs: [precommit: :test]]
  end

  # Run "mix help compile.app" to learn about applications.
  #
  # `mod:` -- new alongside `Cooper.load_file/2`'s default caching:
  # starts `Cooper.Cache`'s GenServer+ETS table, the only thing under
  # `lib/` that runs as a process. Idle (no work, no timers) until the
  # first `load_file/2` call.
  def application do
    [
      extra_applications: [:logger],
      mod: {Cooper.Application, []}
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  #
  # `ichor_runtime` is the only actual runtime dependency -- the ~24
  # modules a `mix ichor.gen`-generated parser calls at runtime (capture
  # dispatch, error formatting, the compiled Tokenizer/Parser, LR/GLR
  # runtime). `ichor` proper (the Aether front-end, `Grammar.Analysis`,
  # both codegen backends -- the bulk of the library) is only ever
  # needed by `mix ichor.gen` itself and by the `Grammar.VM`-backed
  # parity backend under `test/support/` -- `only: [:dev, :test]` keeps
  # it out of a `mix release` build entirely, `runtime: false` keeps it
  # out of the release's applications list even if something pulled it
  # in as a compile-time-only dep. Both are independently published,
  # own repository/release cadence -- `ichor_runtime` is not a
  # subdirectory of `ichor`'s own repo (see its CHANGELOG.md's 0.2.0
  # entry) -- so no `override:`/`git:`/`sparse:` wiring is needed to
  # pull in a matching pair the way there was pre-Hex-publish.
  defp deps do
    [
      # === CODE QUALITY & STATIC ANALYSIS ===
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
      {:sobelow, "~> 0.14", only: [:dev, :test], runtime: false},
      {:excoveralls, "~> 0.18", only: [:dev, :test], runtime: false},
      # Credo is invoked via `MIX_ENV=test mix credo`
      # Dialyzer is invoked via `MIX_ENV=test mix dialyzer`
      # Sobelow is invoked via `MIX_ENV=test mix sobelow`
      # Coveralls is invoked via `MIX_ENV=test mix coveralls

      # === TESTING ===
      {:mox, "~> 1.2", only: [:dev, :test]},
      {:faker, "~> 0.19", only: [:test]},
      {:stream_data, "~> 1.4", only: [:test]},

      # === DEVELOPMENT TOOLING ===
      # Mix, and Hex are built-in (no deps needed)
      {:ex_doc, "~> 0.40", only: [:dev], runtime: false},
      # ExDoc is invoked via `MIX_ENV=dev mix docs`

      # === RUNTIME ===
      {:ichor_runtime, "~> 0.2"},
      {:ichor, "~> 0.3", only: [:dev, :test], runtime: false},
      # `optional: true` -- `Cooper.Dotenv` calls into it, but only when
      # `.env` loading actually runs (the default), so an app that never
      # ends up on that path shouldn't be forced to install it. It's
      # deliberately *not* `only: [:dev, :test]`: `.env.prod` loading is
      # meant to work in a real release too.
      {:dotenvy, "~> 1.1", optional: true},
      # Not optional, unlike dotenvy -- `:telemetry.execute/3` is safe
      # (near-zero cost) to call with zero attached handlers, so there's
      # no real "app that never needs it" case to spare from the
      # dependency the way there is for dotenvy's actual file I/O. Tiny,
      # no transitive deps of its own, the de facto standard for this
      # exact "emit events, let 0-to-N handlers attach" pattern across
      # the Elixir ecosystem.
      {:telemetry, "~> 1.2"}
    ]
  end

  # Fast/cheap checks first so a broken commit fails quickly; dialyzer
  # (slowest, especially its first PLT build) runs last.
  defp aliases do
    [
      precommit: [
        "format",
        "compile --warnings-as-errors",
        "credo --strict",
        "sobelow",
        "test",
        "dialyzer"
      ]
    ]
  end

  defp description do
    "Parses and loads CASC config files -- a hierarchical, extensible config " <>
      "language with imports, variables, interpolation, loops, and consumer-" <>
      "registered resolvers/tags -- into native Elixir terms, secrets redacted " <>
      "by default."
  end

  defp package do
    [
      licenses: ["MIT"],
      links: %{
        "GitHub" => "https://github.com/joetjen/cooper",
        "Docs" => "https://joetjen.github.io/cooper"
      },
      files: ~w(lib priv/grammar guides .formatter.exs mix.exs README.md CHANGELOG.md LICENSE)
    ]
  end

  # `homepage_url` is the GitHub Pages site `.github/workflows/docs.yml`
  # deploys this same `mix docs` output to on every push to `main` --
  # distinct from the HexDocs copy Hex publishes automatically from the
  # `package/0` metadata above, and worth pointing at explicitly so
  # ExDoc's generated sidebar link doesn't dangle.
  defp docs do
    [
      main: "readme",
      source_url: "https://github.com/joetjen/cooper",
      homepage_url: "https://joetjen.github.io/cooper",
      extras: extras(),
      groups_for_extras: groups_for_extras(),
      groups_for_modules: groups_for_modules()
    ]
  end

  defp extras do
    [
      "README.md",
      "guides/TUTORIAL.md",
      "guides/EXAMPLES.md",
      "guides/CHEATSHEET.md",
      "guides/casc/TUTORIAL.md",
      "guides/casc/CASC.md",
      "guides/casc/CASC_EXAMPLES.md",
      "guides/casc/CASC_CHEATSHEET.md",
      "CHANGELOG.md",
      "CONTRIBUTING.md",
      "LICENSE"
    ]
  end

  defp groups_for_extras do
    [
      CASC: Path.wildcard("guides/casc/*.md")
    ]
  end

  defp groups_for_modules do
    [
      "Public API": [
        Cooper,
        Cooper.Cache,
        Cooper.Secret,
        Cooper.IPv4,
        Cooper.IPv6
      ],
      "Unresolved AST (seen only if you call a pipeline stage directly)": [
        Cooper.Op,
        Cooper.VarDecl,
        Cooper.Block,
        Cooper.Interp.Text,
        Cooper.Ref.Var,
        Cooper.Ref.Env,
        Cooper.Ref.Config,
        Cooper.Ref.Resolver,
        Cooper.Ref.Tagged,
        Cooper.Merge.Layered
      ],
      Pipeline: [
        Cooper.Dotenv,
        Cooper.Grammar,
        Cooper.Actions,
        Cooper.Loop,
        Cooper.Loader,
        Cooper.Merge,
        Cooper.Resolver,
        Cooper.InterpGrammar,
        Cooper.InterpActions,
        Cooper.NativeGrammar,
        Cooper.NativeInterpGrammar
      ],
      Support: [
        Cooper.Literals,
        Cooper.Display,
        Cooper.RefCommon,
        Cooper.CIDR,
        Cooper.Native.ResolverRef
      ]
    ]
  end
end