From 2cf3b7dc7ad0f2fda2b9518853b4ccab8da63c36 Mon Sep 17 00:00:00 2001 From: Brad Hanks Date: Sun, 4 Feb 2024 22:47:00 -0700 Subject: [PATCH 1/7] lib/earmark/options.ex: added options type for concise @spec lib/earmark_parser/options.ex: added @spec with options type --- lib/earmark/options.ex | 11 +++++++++++ lib/earmark_parser/options.ex | 1 + 2 files changed, 12 insertions(+) diff --git a/lib/earmark/options.ex b/lib/earmark/options.ex index be9b67eb..95071d7f 100644 --- a/lib/earmark/options.ex +++ b/lib/earmark/options.ex @@ -74,6 +74,13 @@ defmodule Earmark.Options do timeout: nil, wikilinks: false + @type options :: + t() + | Earmark.Parser.Options.t() + | map() + | maybe_improper_list() + | Keyword.t() + @doc ~S""" Make a legal and normalized Option struct from, maps or keyword lists @@ -115,6 +122,9 @@ defmodule Earmark.Options do {:error, [{:error, 0, "footnote_offset option must be numeric"}, {:error, 0, "line option must be numeric"}]} """ + @spec make_options(options()) :: + {:ok, options()} + | {:error, [{atom(), integer(), String.t()}]} def make_options(options \\ []) @@ -191,6 +201,7 @@ defmodule Earmark.Options do "./local.md" """ + @spec relative_filename(options(), String.t()) :: options() def relative_filename(options, filename) def relative_filename(options, filename) when is_list(options) do diff --git a/lib/earmark_parser/options.ex b/lib/earmark_parser/options.ex index 3f6a6522..cc111b63 100644 --- a/lib/earmark_parser/options.ex +++ b/lib/earmark_parser/options.ex @@ -75,6 +75,7 @@ defmodule Earmark.Parser.Options do ...(1)> options.annotations ~r{\A(.*)(%%.*)} """ + @spec normalize(Earmark.Options.options()) :: Earmark.Options.options() def normalize(options) def normalize(%__MODULE__{} = options) do From f7bbeeb85cb6b3454ce6ed1e7755f75eacedeec5 Mon Sep 17 00:00:00 2001 From: Brad Hanks Date: Sun, 4 Feb 2024 22:50:04 -0700 Subject: [PATCH 2/7] lib/earmark/earmark_parser_proxy.ex: added @spec to Earmark.EarmarkParserProxy.as_ast/2 --- lib/earmark/earmark_parser_proxy.ex | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/earmark/earmark_parser_proxy.ex b/lib/earmark/earmark_parser_proxy.ex index 71d93bd5..e378987a 100644 --- a/lib/earmark/earmark_parser_proxy.ex +++ b/lib/earmark/earmark_parser_proxy.ex @@ -14,13 +14,20 @@ defmodule Earmark.EarmarkParserProxy do @doc ~S""" An adapter to `Earmark.Parser.as_ast/*` """ + @spec as_ast([String.t()] | String.t(), Earmark.Options.options()) :: + {:error, binary(), [any()]} | {:ok, binary(), [map()]} def as_ast(input, options) - def as_ast(input, options) when is_list(options) do - Earmark.Parser.as_ast(input, options |> Keyword.delete(:smartypants) |> Keyword.delete(:messages)) + + def as_ast(input, options) when is_list(options) do + Earmark.Parser.as_ast( + input, + options |> Keyword.delete(:smartypants) |> Keyword.delete(:messages) + ) end - def as_ast(input, options) when is_map(options) do + + def as_ast(input, options) when is_map(options) do Earmark.Parser.as_ast(input, options |> Map.delete(:smartypants) |> Map.delete(:messages)) end - end + # SPDX-License-Identifier: Apache-2.0 From 286f092a743e1f671ede4d31f78311563c2bc057 Mon Sep 17 00:00:00 2001 From: Brad Hanks Date: Sun, 4 Feb 2024 22:51:08 -0700 Subject: [PATCH 3/7] lib/earmark/helpers.ex: added @spec to Earmark.Helpers.replace/4 --- lib/earmark/helpers.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/earmark/helpers.ex b/lib/earmark/helpers.ex index 97598af2..dc0b23bf 100644 --- a/lib/earmark/helpers.ex +++ b/lib/earmark/helpers.ex @@ -1,9 +1,8 @@ defmodule Earmark.Helpers do - @doc """ `Regex.replace` with the arguments in the correct order """ - + @spec replace(String.t(), Regex.t(), String.t(), Earmark.Options.options()) :: String.t() def replace(text, regex, replacement, options \\ []) do Regex.replace(regex, text, replacement, options) end From e0331cd34e4801fa1594270b9dcbeb924ab07693 Mon Sep 17 00:00:00 2001 From: Brad Hanks Date: Sun, 4 Feb 2024 22:52:15 -0700 Subject: [PATCH 4/7] lib/earmark_parser.ex: added @spec to functions in Earmark.Parser --- lib/earmark_parser.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/earmark_parser.ex b/lib/earmark_parser.ex index 149ec95e..519c21c5 100644 --- a/lib/earmark_parser.ex +++ b/lib/earmark_parser.ex @@ -586,6 +586,8 @@ defmodule Earmark.Parser do The AST is exposed in the spirit of [Floki's](https://hex.pm/packages/floki). """ + @spec as_ast([String.t()] | String.t(), Earmark.Options.options()) :: + {:error, binary(), [any()]} | {:ok, binary(), [map()]} def as_ast(lines, options \\ %Options{}) def as_ast(lines, %Options{} = options) do From b0c1c29e2897d9be32204ca863fa028f869e6918 Mon Sep 17 00:00:00 2001 From: Brad Hanks Date: Sun, 4 Feb 2024 22:53:32 -0700 Subject: [PATCH 5/7] lib/earmark/transform.ex: added @spec to fucntions in Earmark.Transform --- lib/earmark/transform.ex | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/earmark/transform.ex b/lib/earmark/transform.ex index b376eba6..d90d7d48 100644 --- a/lib/earmark/transform.ex +++ b/lib/earmark/transform.ex @@ -6,8 +6,9 @@ defmodule Earmark.Transform do alias Earmark.EarmarkParserProxy, as: Proxy alias __MODULE__.Pop - @compact_tags ~w[a code em strong del] + @type node_or_string() :: Earmark.ast_node() | String.t() + @compact_tags ~w[a code em strong del] # https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-element @void_elements ~W(area base br col command embed hr img input keygen link meta param source track wbr) @@ -223,7 +224,7 @@ defmodule Earmark.Transform do """ - + @spec make_postprocessor(Options.options()) :: (node_or_string() -> node_or_string()) def make_postprocessor(options) def make_postprocessor(%{postprocessor: nil, registered_processors: rps}), @@ -235,6 +236,9 @@ defmodule Earmark.Transform do @line_end ~r{\n\r?} @doc false + @spec postprocessed_ast([String.t()] | String.t(), Options.options()) :: + {:ok, Earmark.ast_node(), [Earmark.Error.t()]} + | {:error, Earmark.ast_node(), [Earmark.Error.t()]} def postprocessed_ast(lines, options) def postprocessed_ast(lines, options) when is_binary(lines), @@ -258,6 +262,7 @@ defmodule Earmark.Transform do @doc """ Transforms an AST to html, also accepts the result of `map_ast_with` for convenience """ + @spec transform(Earmark.ast_node(), Options.options()) :: String.t() def transform(ast, options \\ %{initial_indent: 0, indent: 2, compact_output: false}) def transform({ast, _}, options), do: transform(ast, options) @@ -297,6 +302,8 @@ defmodule Earmark.Transform do ...(13)> map_ast(ast, &Earmark.AstTools.merge_atts_in_node(&1, class: "private"), true) [{"ul", [{"class", "private"}], [{"li", [{"class", "private"}], ["one"], %{}}, {"li", [{"class", "private"}], ["two"], %{}}], %{}}] """ + @spec map_ast(Earmark.ast_node(), (Earmark.ast_node() -> Earmark.ast_node()), boolean()) :: + Earmark.ast_node() def map_ast(ast, fun, ignore_strings \\ false) do _walk_ast(ast, fun, ignore_strings, []) end @@ -320,6 +327,13 @@ defmodule Earmark.Transform do {[{"ul", [], [{"li", [], ["*"], %{}}], %{}}, {"p", [], ["*"], %{}}, {"ul", [], [{"li", [], ["*"], %{}}], %{}}], 6} """ + @spec map_ast_with( + Earmark.ast(), + any(), + (Earmark.ast_node(), any() -> {Earmark.ast_node(), any()}), + boolean() + ) :: + {Earmark.ast(), any()} def map_ast_with(ast, value, fun, ignore_strings \\ false) do _walk_ast_with(ast, value, fun, ignore_strings, []) end From a746bbb11f851ca73b9b32d67be7258e66f8678d Mon Sep 17 00:00:00 2001 From: Brad Hanks Date: Sun, 4 Feb 2024 22:54:59 -0700 Subject: [PATCH 6/7] lib/earmark/message.ex: Earmark.Message had @spec with Earmark.Options.options() type --- lib/earmark/message.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/earmark/message.ex b/lib/earmark/message.ex index 16dc1d59..1b2eadac 100644 --- a/lib/earmark/message.ex +++ b/lib/earmark/message.ex @@ -1,12 +1,12 @@ defmodule Earmark.Message do - @moduledoc false - alias Earmark.Options + @moduledoc false def emit_messages(messages, %Options{file: file}) do messages |> Enum.each(&emit_message(file, &1)) end + def emit_messages(messages, proplist) when is_list(proplist) do messages |> Enum.each(&emit_message(proplist[:file], &1)) @@ -15,9 +15,11 @@ defmodule Earmark.Message do defp emit_message(filename, msg), do: IO.puts(:stderr, format_message(filename, msg)) defp format_message(filenale, message) + defp format_message(nil, {type, line, text}) do ":#{line}: #{type}: #{text}" end + defp format_message(filename, {type, line, text}) do "#{filename}:#{line}: #{type}: #{text}" end From 4283d5a76772f559ebba06205ac9214e9cb83f85 Mon Sep 17 00:00:00 2001 From: Brad Hanks Date: Sun, 4 Feb 2024 22:57:47 -0700 Subject: [PATCH 7/7] lib/earmark/internal.ex: @spec for Earmark.Internal functions --- lib/earmark/internal.ex | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/earmark/internal.ex b/lib/earmark/internal.ex index 2b233bb1..60d65325 100644 --- a/lib/earmark/internal.ex +++ b/lib/earmark/internal.ex @@ -1,5 +1,4 @@ defmodule Earmark.Internal do - @moduledoc ~S""" All public functions that are internal to Earmark, so that **only** external API functions are public in `Earmark` @@ -21,6 +20,7 @@ defmodule Earmark.Internal do """ def as_ast!(markdown, options \\ []) + def as_ast!(markdown, options) do case Proxy.as_ast(markdown, options) do {:ok, result, _} -> result @@ -32,18 +32,22 @@ defmodule Earmark.Internal do def as_html(lines, options) def as_html(lines, options) when is_list(options) do - case Options.make_options(options) do + case Options.make_options(options) do {:ok, options1} -> as_html(lines, options1) {:error, messages} -> {:error, "", messages} end end def as_html(lines, options) do - {status, ast, messages} = Transform.postprocessed_ast(lines, %{options| messages: MapSet.new([])}) + {status, ast, messages} = + Transform.postprocessed_ast(lines, %{options | messages: MapSet.new([])}) + {status, Transform.transform(ast, options), messages} end + @spec as_html!([String.t()] | String.t(), Options.options()) :: String.t() def as_html!(lines, options \\ []) + def as_html!(lines, options) do {_status, html, messages} = as_html(lines, options) emit_messages(messages, options) @@ -81,9 +85,10 @@ defmodule Earmark.Internal do options_ = options |> Options.relative_filename(filename) + case Path.extname(filename) do ".eex" -> EEx.eval_file(options_.file, include: &include(&1, options_)) - _ -> SysInterface.readlines(options_.file) |> Enum.to_list + _ -> SysInterface.readlines(options_.file) |> Enum.to_list() end end @@ -92,10 +97,11 @@ defmodule Earmark.Internal do ends in `.eex` The returned string is then passed to `as_html` this is used in the escript now and allows - for a simple inclusion mechanism, as a matter of fact an `include` function is passed + for a simple inclusion mechanism, as a matter of fact an `include` function is passed """ def from_file!(filename, options \\ []) + def from_file!(filename, options) do filename |> include(options) @@ -123,6 +129,6 @@ defmodule Earmark.Internal do Error, "#{inspect(task)} has not responded within the set timeout of #{timeout}ms, consider increasing it" ) - end + # SPDX-License-Identifier: Apache-2.0