defmodule Autumn do @external_resource "README.md" @moduledoc "README.md" |> File.read!() |> String.split("") |> Enum.fetch!(1) require Logger alias Autumn.Options alias Autumn.Theme @doc """ Returns the list of all available languages. ## Example iex> Autumn.available_languages() %{ "diff" => {"Diff", ["*.diff"]}, "lua" => {"Lua", ["*.lua"]}, "javascript" => {"JavaScript", ["*.cjs", "*.js", "*.mjs", "*.snap", "*.jsx"]}, "elixir" => {"Elixir", ["*.ex", "*.exs"]}, ... } iex> Autumn.available_languages()["elixir"] {"Elixir", ["*.ex", "*.exs"]} """ @spec available_languages() :: %{ (id :: String.t()) => {name :: String.t(), [extension :: String.t()]} } def available_languages, do: Autumn.Native.available_languages() @doc """ Returns the list of all available themes. Use `Autumn.Theme.get/1` to get the actual theme struct. ## Example iex> Autumn.available_themes() ["github_light", "github_dark", "catppuccin_frappe", "catppuccin_latte", "nightfox", ...] """ @spec available_themes() :: [name :: String.t()] def available_themes, do: Autumn.Native.available_themes() @deprecated "Use highlight/2 instead" def highlight(lang_or_file, source, opts) do IO.warn(""" passing the language in the first argument is deprecated, pass a `:language` option instead: Autumn.highlight("import Kernel", language: "elixir") """) {_, opts} = Keyword.get_and_update(opts, :theme, fn nil -> {nil, nil} current -> {current, String.capitalize(current)} end) opts = Keyword.put(opts, :language, lang_or_file) highlight(source, opts) end @deprecated "Use highlight!/2 instead" def highlight!(lang_or_file, source, opts) do IO.warn(""" passing the language in the first argument is deprecated, pass a `:language` option instead: Autumn.highlight!("import Kernel", language: "elixir") """) {_, opts} = Keyword.get_and_update(opts, :theme, fn nil -> {nil, nil} current -> {current, String.capitalize(current)} end) opts = Keyword.put(opts, :language, lang_or_file) highlight!(source, opts) end @doc """ Highlights `source` code and outputs into a formatted string. ## Options * `:language` (`t:Autumn.Options.lang_or_file/0` - default: `nil`) - Optional. The language used to highlight `source`. You can also pass a filename or extension, for eg: `enum.ex` or `ex`. If no `language` is provided, the highlighter will try to guess it based on the content of the given `source` code. Use `Autumn.available_languages/0` to list all available languages. * `:theme` (`t:String.t/0` or `t:Autumn.Theme.t/0` - default: `"onedark"`) - Optional. A theme to apply styles on the highlighted source code. You can pass either the theme name or a `%Autumn.Theme{}` struct. See `Autumn.available_themes/0` to list all available themes. * `:formatter` (`t:Autumn.Options.formatter/0` - default: `:html_inline`) - See the type doc in `Autumn.Options` for more info and examples. ## Examples Defining the language name: iex> Autumn.highlight("Atom.to_string(:elixir)", language: "elixir") {:ok, ~s|
Atom.to_string(:elixir)
       
| } Guessing the language based on the provided source code: iex> Autumn.highlight("#!/usr/bin/env bash\\nID=1") {:ok, ~s|
#!/usr/bin/env bash
       ID=1
       
| } With custom options: iex> Autumn.highlight("Atom.to_string(:elixir)", language: "example.ex", formatter: {:html_inline, pre_class: "example-elixir"}) {:ok, ~s|
Atom.to_string(:elixir)
       
| } Terminal formatter: iex> Autumn.highlight("Atom.to_string(:elixir)", language: "elixir", formatter: :terminal) {:ok, "\e[0m\e[38;2;229;192;123mAtom\e[0m\e[0m\e[38;2;86;182;194m.\e[0m\e[0m\e[38;2;97;175;239mto_string\e[0m\e[0m\e[38;2;198;120;221m(\e[0m\e[0m\e[38;2;224;108;117m:elixir\e[0m\e[0m\e[38;2;198;120;221m)\e[0m"} See https://docs.rs/autumnus/latest/autumnus/fn.highlight.html for more info. """ @spec highlight(String.t(), keyword()) :: {:ok, String.t()} | {:error, term()} def highlight(source, opts \\ []) def highlight(source, opts) when is_binary(source) and is_list(opts) do language = Keyword.get(opts, :language) theme = Keyword.get(opts, :theme) || "onedark" theme = cond do match?(%Theme{}, theme) -> theme String.contains?(theme, " ") -> Logger.warning(""" Helix themes are deprecated, use Neovim theme names instead. See `Autumn.available_themes/0` for a list of available themes. """) theme |> String.downcase() |> String.replace(" ", "") |> Theme.get() is_binary(theme) -> theme |> String.downcase() |> Theme.get() :else -> nil end # backward compatibility pre_class = case Keyword.get(opts, :pre_class) do nil -> nil pre_class -> Logger.warning(""" option `:pre_class` is deprecated, use `:formatter` instead Example: formatter: {:html_inline, [pre_class: "#{pre_class}"]} """) if is_binary(pre_class) do pre_class else Logger.warning(""" `:pre_class` value is invalid, expected a binary Got #{inspect(pre_class)} """) nil end end formatter = case Keyword.get(opts, :inline_style) do nil -> Keyword.get(opts, :formatter, {:html_inline, [pre_class: pre_class]}) # backward compatibility inline_style -> Logger.warning(""" option `:inline_style` is deprecated, use `:formatter` instead Example: formatter: #{if inline_style, do: ":html_inline", else: ":html_linked"} """) if inline_style do {:html_inline, [pre_class: pre_class]} else {:html_linked, [pres_class: pre_class]} end end formatter = case formatter do {name, opts} when name in [:html_inline, :html_linked, :terminal] and is_list(opts) -> opts = Map.merge(%{pre_class: nil, italic: false, include_highlights: false}, Map.new(opts)) {name, opts} name when name in [:html_inline, :html_linked, :terminal] -> {name, %{pre_class: nil, italic: false, include_highlights: false}} _ -> message = """ `:formatter` is invalid, expected a tuple with the formatter name and options or just the formatter name without options Got #{inspect(formatter)} """ raise Autumn.InputError, message: message end options = %Options{lang_or_file: language, theme: theme, formatter: formatter} case Autumn.Native.highlight(source, options) do {:error, error} -> raise Autumn.HighlightError, error: error output -> output end end def highlight(lang_or_file, source) when is_binary(lang_or_file) and is_binary(source) do highlight(source, language: lang_or_file) end @doc """ Same as `highlight/2` but raises in case of failure. """ @spec highlight!(String.t(), keyword()) :: String.t() def highlight!(source, opts \\ []) def highlight!(source, opts) when is_binary(source) and is_list(opts) do case highlight(source, opts) do {:ok, highlighted} -> highlighted {:error, error} -> raise """ failed to highlight source code Got: #{inspect(error)} """ end end def highlight!(lang_or_file, source) when is_binary(lang_or_file) and is_binary(source) do highlight!(source, language: lang_or_file) end end