Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions lib/dprint_markdown_formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ defmodule DprintMarkdownFormatter do
`:auto`)
- `:unordered_list_kind` - Unordered list style: `:dashes`, `:asterisks`
(default: `:dashes`)
- `:heading_kind` - Heading style for level 1/2 headings: `:atx` (`#`/`##`),
`:setext` (`===`/`---`) (default: `:atx`)

**Note:** Configuration values can be provided as atoms (`:never`) or strings
(`"never"`). Atoms are preferred for consistency with Elixir conventions.
(`"never"`) via `mix.exs`. Runtime options passed to `format/2` must be atoms.

- `:format_module_attributes` - Configure which module attributes to format.
Supports four input types for maximum flexibility:
Expand Down Expand Up @@ -253,9 +255,14 @@ defmodule DprintMarkdownFormatter do
# Remove dprint options to pass only valid Elixir formatter options
elixir_opts = Keyword.drop(opts, @dprint_opts)

case Code.format_string!(formatted_content, elixir_opts) do
[] -> {:ok, ""}
formatted -> {:ok, IO.iodata_to_binary([formatted, ?\n])}
formatted =
formatted_content
|> Code.format_string!(elixir_opts)
|> IO.iodata_to_binary()

case formatted do
"" -> {:ok, ""}
_non_empty -> {:ok, formatted <> "\n"}
end
end
end
Expand Down
170 changes: 85 additions & 85 deletions lib/dprint_markdown_formatter/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,36 @@ defmodule DprintMarkdownFormatter.Config do
@typep strong_kind_option() :: :asterisks | :underscores
@typep new_line_kind_option() :: :auto | :lf | :crlf
@typep unordered_list_kind_option() :: :dashes | :asterisks
@typep heading_kind_option() :: :atx | :setext
@typep module_attributes_option() :: nil | boolean() | [atom()]

@typep nif_config_map() :: %{
line_width: pos_integer(),
text_wrap: atom(),
emphasis_kind: atom(),
strong_kind: atom(),
new_line_kind: atom(),
unordered_list_kind: atom(),
heading_kind: atom()
}

@atom_choices %{
text_wrap: [:always, :never, :maintain],
emphasis_kind: [:asterisks, :underscores],
strong_kind: [:asterisks, :underscores],
new_line_kind: [:auto, :lf, :crlf],
unordered_list_kind: [:dashes, :asterisks],
heading_kind: [:atx, :setext]
}

typed_structor enforce: true do
field :line_width, pos_integer(), default: 80
field :text_wrap, text_wrap_option(), default: :always
field :emphasis_kind, emphasis_kind_option(), default: :asterisks
field :strong_kind, strong_kind_option(), default: :asterisks
field :new_line_kind, new_line_kind_option(), default: :auto
field :unordered_list_kind, unordered_list_kind_option(), default: :dashes
field :heading_kind, heading_kind_option(), default: :atx
field :format_module_attributes, module_attributes_option(), default: nil
end

Expand All @@ -35,6 +56,7 @@ defmodule DprintMarkdownFormatter.Config do
strong_kind: :asterisks,
new_line_kind: :auto,
unordered_list_kind: :dashes,
heading_kind: :atx,
format_module_attributes: nil
}
"""
Expand All @@ -59,12 +81,8 @@ defmodule DprintMarkdownFormatter.Config do
## Examples

# With valid configuration in mix.exs
iex> DprintMarkdownFormatter.Config.load()
%DprintMarkdownFormatter.Config{line_width: 80, text_wrap: :always}

# With invalid configuration values (logs warnings and uses defaults)
iex> DprintMarkdownFormatter.Config.load()
%DprintMarkdownFormatter.Config{line_width: 80, text_wrap: :always}
DprintMarkdownFormatter.Config.load()
#=> %DprintMarkdownFormatter.Config{line_width: 80, text_wrap: :always}
"""
@spec load() :: t()
def load do
Expand All @@ -87,6 +105,18 @@ defmodule DprintMarkdownFormatter.Config do

iex> DprintMarkdownFormatter.Config.from_keyword([format_module_attributes: true])
%DprintMarkdownFormatter.Config{format_module_attributes: true}

iex> DprintMarkdownFormatter.Config.from_keyword([heading_kind: :setext]).heading_kind
:setext

iex> DprintMarkdownFormatter.Config.from_keyword([heading_kind: "setext"]).heading_kind
:setext

iex> DprintMarkdownFormatter.Config.from_keyword([heading_kind: "atx"]).heading_kind
:atx

iex> DprintMarkdownFormatter.Config.from_keyword([heading_kind: :invalid]).heading_kind
:atx
"""
@spec from_keyword(keyword()) :: t()
def from_keyword(opts) when is_list(opts) do
Expand Down Expand Up @@ -120,6 +150,10 @@ defmodule DprintMarkdownFormatter.Config do
iex> config = %DprintMarkdownFormatter.Config{text_wrap: :always}
iex> DprintMarkdownFormatter.Config.merge(config, [text_wrap: :never, line_width: 120])
%DprintMarkdownFormatter.Config{text_wrap: :never, line_width: 120}

iex> config = %DprintMarkdownFormatter.Config{heading_kind: :atx}
iex> DprintMarkdownFormatter.Config.merge(config, [heading_kind: :setext]).heading_kind
:setext
"""
@spec merge(t(), keyword()) :: t()
def merge(%__MODULE__{} = config, opts) when is_list(opts) do
Expand Down Expand Up @@ -147,24 +181,26 @@ defmodule DprintMarkdownFormatter.Config do

iex> config = %DprintMarkdownFormatter.Config{line_width: 100, text_wrap: :never}
iex> DprintMarkdownFormatter.Config.to_nif_config(config)
%{line_width: 100, text_wrap: :never, emphasis_kind: :asterisks}
%{
line_width: 100,
text_wrap: :never,
emphasis_kind: :asterisks,
strong_kind: :asterisks,
new_line_kind: :auto,
unordered_list_kind: :dashes,
heading_kind: :atx
}
"""
@spec to_nif_config(t()) :: %{
line_width: non_neg_integer(),
text_wrap: atom(),
emphasis_kind: atom(),
strong_kind: atom(),
new_line_kind: atom(),
unordered_list_kind: atom()
}
@spec to_nif_config(t()) :: nif_config_map()
def to_nif_config(%__MODULE__{} = config) do
%{
line_width: config.line_width,
text_wrap: config.text_wrap,
emphasis_kind: config.emphasis_kind,
strong_kind: config.strong_kind,
new_line_kind: config.new_line_kind,
unordered_list_kind: config.unordered_list_kind
unordered_list_kind: config.unordered_list_kind,
heading_kind: config.heading_kind
}
end

Expand Down Expand Up @@ -202,91 +238,55 @@ defmodule DprintMarkdownFormatter.Config do
defp validate_option(:line_width, value),
do: {:error, "must be a positive integer, got: #{inspect(value)}"}

defp validate_option(:text_wrap, value) when value in [:always, :never, :maintain],
do: {:ok, value}
defp validate_option(key, value) when is_map_key(@atom_choices, key),
do: validate_atom_choice(value, @atom_choices[key])

defp validate_option(:text_wrap, value) when is_binary(value) do
case value do
"always" -> {:ok, :always}
"never" -> {:ok, :never}
"maintain" -> {:ok, :maintain}
_invalid_value -> {:error, "must be :always, :never, or :maintain, got: #{inspect(value)}"}
end
end

defp validate_option(:text_wrap, value),
do: {:error, "must be :always, :never, or :maintain, got: #{inspect(value)}"}

defp validate_option(:emphasis_kind, value) when value in [:asterisks, :underscores],
do: {:ok, value}
defp validate_option(:format_module_attributes, nil), do: {:ok, nil}
defp validate_option(:format_module_attributes, value) when is_boolean(value), do: {:ok, value}

defp validate_option(:emphasis_kind, value) when is_binary(value) do
case value do
"asterisks" -> {:ok, :asterisks}
"underscores" -> {:ok, :underscores}
_invalid_value -> {:error, "must be :asterisks or :underscores, got: #{inspect(value)}"}
defp validate_option(:format_module_attributes, value) when is_list(value) do
if Enum.all?(value, &is_atom/1) do
{:ok, value}
else
{:error, "must be a list of atoms, got: #{inspect(value)}"}
end
end

defp validate_option(:emphasis_kind, value),
do: {:error, "must be :asterisks or :underscores, got: #{inspect(value)}"}
defp validate_option(:format_module_attributes, value),
do: {:error, "must be nil, boolean, or list of atoms, got: #{inspect(value)}"}

defp validate_option(:strong_kind, value) when value in [:asterisks, :underscores],
do: {:ok, value}
defp validate_option(key, value),
do: {:error, "unknown configuration option #{key} with value #{inspect(value)}"}

defp validate_option(:strong_kind, value) when is_binary(value) do
case value do
"asterisks" -> {:ok, :asterisks}
"underscores" -> {:ok, :underscores}
_invalid_value -> {:error, "must be :asterisks or :underscores, got: #{inspect(value)}"}
end
defp validate_atom_choice(value, choices) when is_atom(value) do
if value in choices,
do: {:ok, value},
else: {:error, format_choice_error(choices, value)}
end

defp validate_option(:strong_kind, value),
do: {:error, "must be :asterisks or :underscores, got: #{inspect(value)}"}

defp validate_option(:new_line_kind, value) when value in [:auto, :lf, :crlf], do: {:ok, value}

defp validate_option(:new_line_kind, value) when is_binary(value) do
case value do
"auto" -> {:ok, :auto}
"lf" -> {:ok, :lf}
"crlf" -> {:ok, :crlf}
_invalid_value -> {:error, "must be :auto, :lf, or :crlf, got: #{inspect(value)}"}
defp validate_atom_choice(value, choices) when is_binary(value) do
case Enum.find(choices, &(Atom.to_string(&1) == value)) do
nil -> {:error, format_choice_error(choices, value)}
atom -> {:ok, atom}
end
end

defp validate_option(:new_line_kind, value),
do: {:error, "must be :auto, :lf, or :crlf, got: #{inspect(value)}"}

defp validate_option(:unordered_list_kind, value) when value in [:dashes, :asterisks],
do: {:ok, value}
defp validate_atom_choice(value, choices),
do: {:error, format_choice_error(choices, value)}

defp validate_option(:unordered_list_kind, value) when is_binary(value) do
case value do
"dashes" -> {:ok, :dashes}
"asterisks" -> {:ok, :asterisks}
_invalid_value -> {:error, "must be :dashes or :asterisks, got: #{inspect(value)}"}
end
end
defp format_choice_error(choices, value),
do: "must be #{format_choices(choices)}, got: #{inspect(value)}"

defp validate_option(:unordered_list_kind, value),
do: {:error, "must be :dashes or :asterisks, got: #{inspect(value)}"}
defp format_choices(choices) do
inspected = Enum.map(choices, &inspect/1)

defp validate_option(:format_module_attributes, nil), do: {:ok, nil}
defp validate_option(:format_module_attributes, value) when is_boolean(value), do: {:ok, value}
case inspected do
[a, b] ->
"#{a} or #{b}"

defp validate_option(:format_module_attributes, value) when is_list(value) do
if Enum.all?(value, &is_atom/1) do
{:ok, value}
else
{:error, "must be a list of atoms, got: #{inspect(value)}"}
list ->
{butlast, [last]} = Enum.split(list, -1)
"#{Enum.join(butlast, ", ")}, or #{last}"
end
end

defp validate_option(:format_module_attributes, value),
do: {:error, "must be nil, boolean, or list of atoms, got: #{inspect(value)}"}

defp validate_option(key, value),
do: {:error, "unknown configuration option #{key} with value #{inspect(value)}"}
end
6 changes: 4 additions & 2 deletions lib/dprint_markdown_formatter/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ defmodule DprintMarkdownFormatter.Native do
@typep strong_kind_option() :: :asterisks | :underscores
@typep new_line_kind_option() :: :auto | :lf | :crlf
@typep unordered_list_kind_option() :: :dashes | :asterisks
@typep heading_kind_option() :: :atx | :setext

@typep format_options() :: %{
line_width: pos_integer(),
text_wrap: text_wrap_option(),
emphasis_kind: emphasis_kind_option(),
strong_kind: strong_kind_option(),
new_line_kind: new_line_kind_option(),
unordered_list_kind: unordered_list_kind_option()
unordered_list_kind: unordered_list_kind_option(),
heading_kind: heading_kind_option()
}

@doc """
Expand All @@ -53,7 +55,7 @@ defmodule DprintMarkdownFormatter.Native do

## Examples

iex> options = %{line_width: 80, text_wrap: :always, emphasis_kind: :asterisks, strong_kind: :asterisks, new_line_kind: :auto, unordered_list_kind: :dashes}
iex> options = %{line_width: 80, text_wrap: :always, emphasis_kind: :asterisks, strong_kind: :asterisks, new_line_kind: :auto, unordered_list_kind: :dashes, heading_kind: :atx}
iex> DprintMarkdownFormatter.Native.format_markdown("# Hello World", options)
{:ok, "# Hello World\\n"}

Expand Down
5 changes: 5 additions & 0 deletions lib/dprint_markdown_formatter/validator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ defmodule DprintMarkdownFormatter.Validator do
{:ok, _strong_kind} <- validate_strong_kind(config.strong_kind),
{:ok, _new_line_kind} <- validate_new_line_kind(config.new_line_kind),
{:ok, _unordered_list_kind} <- validate_unordered_list_kind(config.unordered_list_kind),
{:ok, _heading_kind} <- validate_heading_kind(config.heading_kind),
{:ok, _format_module_attributes} <-
validate_format_module_attributes(config.format_module_attributes) do
{:ok, config}
Expand Down Expand Up @@ -162,6 +163,7 @@ defmodule DprintMarkdownFormatter.Validator do
defp validate_option_value(:strong_kind, value), do: validate_strong_kind(value)
defp validate_option_value(:new_line_kind, value), do: validate_new_line_kind(value)
defp validate_option_value(:unordered_list_kind, value), do: validate_unordered_list_kind(value)
defp validate_option_value(:heading_kind, value), do: validate_heading_kind(value)
defp validate_option_value(:extension, value), do: validate_extension(value)
defp validate_option_value(:sigil, value), do: validate_sigil(value)

Expand Down Expand Up @@ -207,6 +209,9 @@ defmodule DprintMarkdownFormatter.Validator do
defp validate_unordered_list_kind(value),
do: validate_atom_choice(value, :unordered_list_kind, [:dashes, :asterisks])

defp validate_heading_kind(value),
do: validate_atom_choice(value, :heading_kind, [:atx, :setext])

defp validate_atom_choice(value, field, valid_choices) when is_atom(value) do
if value in valid_choices do
{:ok, value}
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule DprintMarkdownFormatter.MixProject do
{:rustler_precompiled, "~> 0.8"},
{:rustler, "~> 0.36.0", optional: true},
{:sourceror, "~> 1.0"},
{:typed_structor, "~> 0.5.0"},
{:typed_structor, "~> 0.5"},
{:mimic, "~> 1.7", only: :test},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
Expand Down
2 changes: 1 addition & 1 deletion native/dprint_markdown_formatter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ crate-type = ["cdylib"]

[dependencies]
rustler = { version = "0.36.0" }
dprint-plugin-markdown = "0.18.0"
dprint-plugin-markdown = "0.21.1"
dprint-core = "0.67.4"

[features]
Expand Down
Loading
Loading