diff --git a/lib/earmark_parser.ex b/lib/earmark_parser.ex index 49ef870..6936e41 100644 --- a/lib/earmark_parser.ex +++ b/lib/earmark_parser.ex @@ -1,15 +1,15 @@ defmodule EarmarkParser do @type ast_meta :: map() - @type ast_tag :: binary() - @type ast_attribute_name :: binary() - @type ast_attribute_value :: binary() + @type ast_tag :: String.t() + @type ast_attribute_name :: String.t() + @type ast_attribute_value :: String.t() @type ast_attribute :: {ast_attribute_name(), ast_attribute_value()} @type ast_attributes :: list(ast_attribute()) @type ast_tuple :: {ast_tag(), ast_attributes(), ast(), ast_meta()} - @type ast_node :: binary() | ast_tuple() + @type ast_node :: String.t() | ast_tuple() @type ast :: list(ast_node()) - @type error :: {atom(), non_neg_integer(), binary()} + @type error :: {atom(), non_neg_integer(), String.t()} @type errors :: list(error()) @type t :: {:ok, ast(), []} | {:error, ast(), errors()} @@ -615,7 +615,7 @@ defmodule EarmarkParser do The AST is exposed in the spirit of [Floki's](https://hex.pm/packages/floki). """ - @spec as_ast(binary() | list(binary()), any()) :: t() + @spec as_ast(String.t() | list(String.t()), any()) :: t() def as_ast(lines, options \\ %Options{}) def as_ast(lines, %Options{} = options) do diff --git a/lib/earmark_parser/ast_renderer.ex b/lib/earmark_parser/ast_renderer.ex index d339ba2..0b4e9d6 100644 --- a/lib/earmark_parser/ast_renderer.ex +++ b/lib/earmark_parser/ast_renderer.ex @@ -12,6 +12,7 @@ defmodule EarmarkParser.AstRenderer do @moduledoc false + @spec render([Block.t()], Context.t(), boolean()) :: Context.t() def render(blocks, context = %Context{options: %Options{}}, loose? \\ true) do _render(blocks, context, loose?) end diff --git a/lib/earmark_parser/context.ex b/lib/earmark_parser/context.ex index ec139ac..83c75ef 100644 --- a/lib/earmark_parser/context.ex +++ b/lib/earmark_parser/context.ex @@ -3,14 +3,14 @@ defmodule EarmarkParser.Context do alias EarmarkParser.Options @type t :: %__MODULE__{ - options: EarmarkParser.Options.t(), + options: Options.t(), links: map(), footnotes: map(), referenced_footnote_ids: MapSet.t(String.t()), value: String.t() | [String.t()] } - defstruct options: %EarmarkParser.Options{}, + defstruct options: %Options{}, links: Map.new(), rules: nil, footnotes: Map.new(), @@ -23,8 +23,8 @@ defmodule EarmarkParser.Context do @doc false def modify_value(%__MODULE__{value: value} = context, fun) do - nv = fun.(value) - %{context | value: nv} + new_value = fun.(value) + %{context | value: new_value} end @doc false diff --git a/lib/earmark_parser/helpers/lookahead_helpers.ex b/lib/earmark_parser/helpers/lookahead_helpers.ex index 7b66c0c..d610435 100644 --- a/lib/earmark_parser/helpers/lookahead_helpers.ex +++ b/lib/earmark_parser/helpers/lookahead_helpers.ex @@ -3,6 +3,9 @@ defmodule EarmarkParser.Helpers.LookaheadHelpers do import EarmarkParser.Helpers.LeexHelpers + @type backtix_string :: String.t() + @type natural :: non_neg_integer() + @doc """ Indicates if the _numbered_line_ passed in leaves an inline code block open. @@ -11,6 +14,7 @@ defmodule EarmarkParser.Helpers.LookaheadHelpers do Otherwise `{nil, 0}` is returned """ + @spec opens_inline_code(EarmarkParser.Line.t()) :: {backtix_string | nil, natural} def opens_inline_code(%{line: line, lnb: lnb}) do case tokenize(line, with: :earmark_parser_string_lexer) |> has_still_opening_backtix(nil) do nil -> {nil, 0} @@ -26,6 +30,7 @@ defmodule EarmarkParser.Helpers.LookaheadHelpers do opening backtix """ # (#{},{_,_}) -> {_,_} + @spec still_inline_code(EarmarkParser.Line.t(), {backtix_string, natural}) :: {backtix_string | nil, natural} def still_inline_code(%{line: line, lnb: lnb}, old = {pending, _pending_lnb}) do case tokenize(line, with: :earmark_parser_string_lexer) |> has_still_opening_backtix({:old, pending}) do nil -> {nil, 0} diff --git a/lib/earmark_parser/line.ex b/lib/earmark_parser/line.ex index cecc888..afd347c 100644 --- a/lib/earmark_parser/line.ex +++ b/lib/earmark_parser/line.ex @@ -94,6 +94,21 @@ defmodule EarmarkParser.Line do initial_indent: 0, list_indent: 0 ) + + @type natural :: non_neg_integer() + + @type t :: %__MODULE__{ + annotation: nil | String.t(), + ial: nil | String.t(), + lnb: natural, + type: :ul | :ol, + line: String.t(), + indent: natural, + bullet: String.t(), + content: String.t(), + initial_indent: natural, + list_indent: natural + } end defmodule SetextUnderlineHeading do diff --git a/lib/earmark_parser/line_scanner/rgx.ex b/lib/earmark_parser/line_scanner/rgx.ex index dfc8185..b201162 100644 --- a/lib/earmark_parser/line_scanner/rgx.ex +++ b/lib/earmark_parser/line_scanner/rgx.ex @@ -159,7 +159,7 @@ defmodule EarmarkParser.LineScanner.Rgx do Regex.run(~r/\A (?:_\s?){3,} \z/x, content) end - @void_tags ~w{area br hr img wbr} + @void_tags ~w{area base br col embed hr img input link meta param source track wbr} def void_tag_rgx do ~r''' ^<( #{Enum.join(@void_tags, "|")} ) diff --git a/lib/earmark_parser/message.ex b/lib/earmark_parser/message.ex index 97ef10e..8aba6f6 100644 --- a/lib/earmark_parser/message.ex +++ b/lib/earmark_parser/message.ex @@ -4,15 +4,17 @@ defmodule EarmarkParser.Message do alias EarmarkParser.Context alias EarmarkParser.Options - @type message_type :: :error | :warning - @type t :: {message_type, number, binary} - @type ts :: list(t) - @type container_type :: Options.t() | Context.t() + @type message_type :: :error | :warning | :deprecated + @type t :: {message_type, non_neg_integer(), String.t()} + @type ts :: [t()] + @type container :: Options.t() | Context.t() + @spec add_messages(container, ts()) :: container def add_messages(container, messages) do Enum.reduce(messages, container, &add_message(&2, &1)) end + @spec add_message(container, t()) :: container def add_message(container, message) def add_message(options = %Options{}, message) do @@ -23,6 +25,7 @@ defmodule EarmarkParser.Message do %{context | options: add_message(context.options, message)} end + @spec get_messages(container) :: ts() def get_messages(container) def get_messages(%Context{options: %{messages: messages}}) do @@ -32,6 +35,7 @@ defmodule EarmarkParser.Message do @doc """ For final output """ + @spec sort_messages(container) :: [t()] def sort_messages(container) do container |> get_messages() diff --git a/lib/earmark_parser/options.ex b/lib/earmark_parser/options.ex index d447efe..8242c7d 100644 --- a/lib/earmark_parser/options.ex +++ b/lib/earmark_parser/options.ex @@ -32,6 +32,7 @@ defmodule EarmarkParser.Options do timeout: nil @type t :: %__MODULE__{ + renderer: module(), all: boolean(), gfm: boolean(), gfm_tables: boolean(), @@ -42,18 +43,19 @@ defmodule EarmarkParser.Options do parse_inline: boolean(), # allow for annotations - annotations: nil | binary(), + annotations: nil | String.t() | Regex.t(), # additional prefies for class of code blocks - code_class_prefix: nil | binary(), + code_class_prefix: nil | String.t(), # Filename and initial line number of the markdown block passed in # for meaningful error messages - file: binary(), - line: number(), + file: String.t(), + line: non_neg_integer(), # [{:error|:warning, lnb, text},...] - messages: MapSet.t(), + messages: MapSet.t(EarmarkParser.Message.t()), pure_links: boolean(), sub_sup: boolean(), + math: boolean(), # deprecated pedantic: boolean(), @@ -105,22 +107,12 @@ defmodule EarmarkParser.Options do ...(1)> options.annotations ~r{\A(.*)(%%.*)} """ + @spec normalize(t() | keyword()) :: t() def normalize(options) def normalize(%__MODULE__{} = options) do - case options.annotations do - %Regex{} -> - options - - nil -> - options - - _ -> - %{ - options - | annotations: Regex.compile!("\\A(.*)(#{Regex.escape(options.annotations)}.*)") - } - end + options + |> _normalize_annotations() |> _set_all_if_applicable() |> _deprecate_old_messages() end @@ -129,6 +121,18 @@ defmodule EarmarkParser.Options do struct(__MODULE__, options) |> normalize() end + defp _normalize_annotations(%__MODULE__{annotations: %Regex{}} = options) do + options + end + + defp _normalize_annotations(%__MODULE__{annotations: nil} = options) do + options + end + + defp _normalize_annotations(%__MODULE__{annotations: annotations} = options) do + %{options | annotations: Regex.compile!("\\A(.*)(#{Regex.escape(annotations)}.*)")} + end + defp _deprecate_old_messages(options) defp _deprecate_old_messages(%__MODULE__{messages: %MapSet{}} = options) do diff --git a/lib/earmark_parser/parser.ex b/lib/earmark_parser/parser.ex index 9b1e874..0adfdd4 100644 --- a/lib/earmark_parser/parser.ex +++ b/lib/earmark_parser/parser.ex @@ -1,6 +1,6 @@ defmodule EarmarkParser.Parser do @moduledoc false - alias EarmarkParser.{Block, Line, LineScanner, Options} + alias EarmarkParser.{Block, Line, LineScanner, Options, Context} import EarmarkParser.Helpers.{AttrParser, LineHelpers, ReparseHelpers} @@ -19,6 +19,7 @@ defmodule EarmarkParser.Parser do The options are a `%EarmarkParser.Options{}` structure. See `as_html!` for more details. """ + @spec parse_markdown([String.t()] | String.t(), Options.t()) :: {[Block.t()], Context.t()} def parse_markdown(lines, options) def parse_markdown(lines, options = %Options{}) when is_list(lines) do @@ -513,7 +514,7 @@ defmodule EarmarkParser.Parser do # Consolidate multiline inline code blocks into an element # ############################################################ @not_pending {nil, 0} - # ([#{},...]) -> {[#{}],[#{}],{'nil' | binary(),number()}} + # ([#{},...]) -> {[#{}],[#{}],{'nil' | String.t(),number()}} # @spec consolidate_para( ts ) :: { ts, ts, {nil | String.t, number} } defp consolidate_para(lines) do _consolidate_para(lines, [], @not_pending, nil) diff --git a/lib/earmark_parser/parser/list_info.ex b/lib/earmark_parser/parser/list_info.ex index 78435fb..bb39cad 100644 --- a/lib/earmark_parser/parser/list_info.ex +++ b/lib/earmark_parser/parser/list_info.ex @@ -1,28 +1,40 @@ defmodule EarmarkParser.Parser.ListInfo do + alias EarmarkParser.{Options, Line, Line.ListItem} + import EarmarkParser.Helpers.LookaheadHelpers, only: [opens_inline_code: 1, still_inline_code: 2] @moduledoc false @not_pending {nil, 0} - defstruct( - indent: 0, - lines: [], - loose?: false, - pending: @not_pending, - options: %EarmarkParser.Options{}, - width: 0 - ) - - def new(%EarmarkParser.Line.ListItem{initial_indent: ii, list_indent: width} = item, options) do + defstruct indent: 0, + lines: [], + loose?: false, + pending: @not_pending, + options: %Options{}, + width: 0 + + @type t :: %__MODULE__{ + indent: non_neg_integer(), + lines: [String.t()], + loose?: boolean(), + pending: {nil | String.t(), non_neg_integer()}, + options: Options.t(), + width: non_neg_integer() + } + + @spec new(ListItem.t(), Options.t()) :: t() + def new(%ListItem{initial_indent: ii, list_indent: width} = item, options) do pending = opens_inline_code(item) %__MODULE__{indent: ii, lines: [item.content], options: options, pending: pending, width: width} end + @spec update_list_info(t(), String.t(), Line.t(), boolean()) :: t() def update_list_info(list_info, line, pending_line, loose? \\ false) do prepend_line(list_info, line) |> _update_rest(pending_line, loose?) end + @spec prepend_line(t(), String.t()) :: t() def prepend_line(%__MODULE__{lines: lines} = list_info, line) do %{list_info | lines: [line | lines]} end diff --git a/lib/earmark_parser/parser/list_parser.ex b/lib/earmark_parser/parser/list_parser.ex index cbe356f..88d8d3e 100644 --- a/lib/earmark_parser/parser/list_parser.ex +++ b/lib/earmark_parser/parser/list_parser.ex @@ -10,6 +10,7 @@ defmodule EarmarkParser.Parser.ListParser do @not_pending {nil, 0} + @spec parse_list([Line.t()], [Block.t()], Options.t()) :: {[Block.t()], [Line.t()], Options.t()} def parse_list(lines, result, options \\ %Options{}) do {items, rest, options1} = _parse_list_items_init(lines, [], options) list = _make_list(items, _empty_list(items))