Packages

Generate Anki flashcards from Elixir project documentation using ExDoc.

Current section

Files

Jump to
ex_doc_anki lib mix tasks doc_anki.ex
Raw

lib/mix/tasks/doc_anki.ex

defmodule Mix.Tasks.DocAnki do
use Mix.Task
@shortdoc "Generate Anki flashcards from project documentation"
@moduledoc """
Generates Anki-compatible CSV flashcards from project documentation using ExDoc.
## Usage
mix doc_anki [PACKAGE...]
Without arguments, generates flashcards for the current project.
With package names, generates flashcards for those dependencies.
## Options
* `-o`, `--output` - Output directory (default: `doc`)
* `--no-compile` - Skip compilation before generating docs
## Examples
mix doc_anki # current project
mix doc_anki ecto # only Ecto (even as transitive dep)
mix doc_anki ecto -o cards/ecto # output to cards/ecto
mix doc_anki ecto ecto_sql # multiple packages
"""
@spec run([String.t()]) :: :ok
@impl true
def run(args) do
{packages, opts} = parse_args(args)
if packages == [] do
generate_for_current_project(opts)
else
Enum.each(packages, &generate_for_package(&1, opts))
end
end
defp parse_args(args) do
{opts, packages} =
OptionParser.parse!(args,
strict: [output: :string, no_compile: :boolean],
aliases: [o: :output]
)
{packages, opts}
end
defp generate_for_current_project(opts) do
config = Mix.Project.config()
app = config[:app]
project = config[:name] || format_name(app)
version = config[:version] || "dev"
source_beam = Mix.Project.compile_path()
source_url = config[:source_url]
output_file =
project
|> build_opts(version, source_beam, opts[:output], source_url)
|> ExDocAnki.generate()
Mix.shell().info("Anki deck generated at: #{output_file}")
end
defp generate_for_package(package, opts) do
source_beam = find_beam_path(package)
project = format_name(package)
version = package_version(package)
output = opts[:output] || Path.join("doc", package)
output_file =
project
|> build_opts(version, source_beam, output, nil)
|> ExDocAnki.generate()
Mix.shell().info("Anki deck generated at: #{output_file}")
end
defp build_opts(project, version, source_beam, output, source_url) do
[]
|> Keyword.put(:project, project)
|> Keyword.put(:version, version)
|> Keyword.put(:source_beam, source_beam)
|> Keyword.put(:output, output || "doc")
|> maybe_put(:source_url, source_url)
end
defp package_version(package) do
app = String.to_existing_atom(package)
vsn = Application.spec(app, :vsn)
if vsn, do: to_string(vsn), else: "dev"
rescue
ArgumentError -> "dev"
end
defp find_beam_path(package) do
ebin_path = Path.join([Mix.Project.build_path(), "lib", package, "ebin"])
if File.dir?(ebin_path) do
ebin_path
else
Mix.raise("""
Could not find compiled beams for #{package}.
Make sure the package is compiled first:
mix deps.get && mix compile
Also verify the package exists in your dependency tree with:
mix deps | grep #{package}
""")
end
end
defp format_name(name) when is_atom(name) do
name |> Atom.to_string() |> String.upcase()
end
defp format_name(name) when is_binary(name) do
String.upcase(name)
end
defp maybe_put(keyword, _key, nil), do: keyword
defp maybe_put(keyword, key, value), do: Keyword.put(keyword, key, value)
end