diff --git a/lib/earmark.ex b/lib/earmark.ex index 19cfc507..77407109 100644 --- a/lib/earmark.ex +++ b/lib/earmark.ex @@ -266,6 +266,7 @@ defmodule Earmark do """ alias Earmark.Options + alias Earmark.Context import Earmark.Message, only: [emit_messages: 1, sort_messages: 1] @doc """ @@ -306,6 +307,7 @@ defmodule Earmark do Earmark.as_html(original, %Options{smartypants: false}) """ + @spec as_html(String.t | list(String.t), %Options{}) :: {:ok, String.t, list()} | {:error, String.t, list(String.t)} def as_html(lines, options \\ %Options{}) do {context, html} = _as_html(lines, options) case sort_messages(context) do @@ -320,6 +322,7 @@ defmodule Earmark do Otherwise it behaves exactly as `as_html`. """ + @spec as_html!(String.t | list(String.t), %Options{}) :: String.t def as_html!(lines, options \\ %Options{}) def as_html!(lines, options = %Options{}) do {context, html} = _as_html(lines, options) @@ -327,6 +330,7 @@ defmodule Earmark do html end + @spec _as_html(String.t | list(String.t), %Options{}) :: {%Context{}, String.t} defp _as_html(lines, options) do {blocks, context} = parse(lines, options) case blocks do @@ -344,6 +348,7 @@ defmodule Earmark do for more details. """ + @spec parse(String.t | list(String.t), %Options{}) :: { Earmark.Block.ts, %Context{} } def parse(lines, options \\ %Earmark.Options{}) def parse(lines, options = %Options{mapper: mapper}) when is_list(lines) do { blocks, links, options1 } = Earmark.Parser.parse(lines, options, false) @@ -377,6 +382,7 @@ defmodule Earmark do end @doc false + @spec pmap( list(A), (A -> Earmark.Line.t) ) :: Earmark.Line.ts def pmap(collection, func) do collection |> Enum.map(fn item -> Task.async(fn -> func.(item) end) end) diff --git a/lib/earmark/block.ex b/lib/earmark/block.ex index 5cd9bfde..518be2cd 100644 --- a/lib/earmark/block.ex +++ b/lib/earmark/block.ex @@ -51,6 +51,7 @@ defmodule Earmark.Block do # Then extract any id definitions, and build a map from them. Not # for external consumption. + @spec parse( Line.ts, Options.t ) :: {ts, %{}, Options.t} def parse(lines, options) do {blocks, options} = lines |> remove_trailing_blank_lines() |> lines_to_blocks(options) links = links_from_blocks(blocks) @@ -66,6 +67,7 @@ defmodule Earmark.Block do end + @spec _parse(Line.ts, ts, Options.t) :: {ts, Earmark.Message.ts} defp _parse([], result, options), do: {result, options} ################### @@ -331,6 +333,7 @@ defmodule Earmark.Block do # Assign attributes that follow a block to that block # ####################################################### + @spec assign_attributes_to_blocks( ts, ts ) :: ts def assign_attributes_to_blocks([], result), do: Enum.reverse(result) def assign_attributes_to_blocks([ %Ial{attrs: attrs}, block | rest], result) do @@ -349,6 +352,7 @@ defmodule Earmark.Block do # @spec consolidate_para( ts ) :: { ts, ts, {nil | String.t, number} } defp consolidate_para( lines ), do: _consolidate_para( lines, [], @not_pending ) + @spec _consolidate_para( ts, ts, inline_code_continuation ) :: { ts, ts, inline_code_continuation } defp _consolidate_para( [], result, pending ) do {result, [], pending} end @@ -365,6 +369,7 @@ defmodule Earmark.Block do # Consolidate one or more list items into a list # ################################################## + @spec consolidate_list_items( ts, ts ) :: ts defp consolidate_list_items([], result) do result |> Enum.map(&compute_list_spacing/1) # no need to reverse end @@ -405,6 +410,7 @@ defmodule Earmark.Block do # Read in a table (consecutive TableLines with # the same number of columns) + @spec read_table( ts, number, %Table{} ) :: { %Table{}, ts } defp read_table([ %Line.TableLine{columns: cols} | rest ], col_count, table = %Table{}) @@ -426,6 +432,7 @@ defmodule Earmark.Block do end + @spec look_for_alignments( [String.t] ) :: atom defp look_for_alignments([ _first, second | _rest ]) do if Enum.all?(second, fn row -> row =~ ~r{^:?-+:?$} end) do second @@ -451,6 +458,7 @@ defmodule Earmark.Block do visit(blocks, Map.new, &link_extractor/2) end + @spec link_extractor(t, %{}) :: %{} defp link_extractor(item = %IdDef{id: id}, result) do Map.put(result, String.downcase(id), item) end @@ -462,6 +470,7 @@ defmodule Earmark.Block do # Visitor pattern for each block # ################################## + @spec visit(ts, %{}, (t, %{} -> %{})) :: %{} defp visit([], result, _func), do: result # Structural node BlockQuote -> descend @@ -534,6 +543,7 @@ defmodule Earmark.Block do # (_,{'nil' | binary(),number()}) -> #{}jj + @spec inline_or_text?( Line.t, inline_code_continuation ) :: %{pending: String.t, continue: boolean} defp inline_or_text?(line, pending) defp inline_or_text?(line = %Line.Text{}, @not_pending) do pending = opens_inline_code(line) diff --git a/lib/earmark/helpers/attr_parser.ex b/lib/earmark/helpers/attr_parser.ex index 53251036..875968bc 100644 --- a/lib/earmark/helpers/attr_parser.ex +++ b/lib/earmark/helpers/attr_parser.ex @@ -3,8 +3,11 @@ defmodule Earmark.Helpers.AttrParser do import Earmark.Helpers.StringHelpers, only: [ behead: 2 ] import Earmark.Message, only: [add_message: 2] + alias Earmark.Context + @type errorlist :: list(String.t) + @spec parse_attrs(Context.t, String.t, number()) :: {map, errorlist} def parse_attrs(context, attrs, lnb) do { attrs, errors } = _parse_attrs(%{}, attrs, [], lnb) { add_errors(context, errors, lnb), attrs } diff --git a/lib/earmark/helpers/line_helpers.ex b/lib/earmark/helpers/line_helpers.ex index aefed66c..eadd69d5 100644 --- a/lib/earmark/helpers/line_helpers.ex +++ b/lib/earmark/helpers/line_helpers.ex @@ -2,6 +2,7 @@ defmodule Earmark.Helpers.LineHelpers do alias Earmark.Line + @spec blank?(Line.t) :: boolean def blank?(%Line.Blank{}), do: true def blank?(_), do: false diff --git a/lib/earmark/helpers/lookahead_helpers.ex b/lib/earmark/helpers/lookahead_helpers.ex index 71f47c46..8b98af79 100644 --- a/lib/earmark/helpers/lookahead_helpers.ex +++ b/lib/earmark/helpers/lookahead_helpers.ex @@ -14,6 +14,7 @@ defmodule Earmark.Helpers.LookaheadHelpers do Otherwise `{nil, 0}` is returned """ + @spec opens_inline_code(numbered_line) :: inline_code_continuation def opens_inline_code( %{line: line, lnb: lnb} ) do case tokenize(line, with: :string_lexer) |> has_still_opening_backtix(nil) do nil -> {nil, 0} @@ -29,6 +30,7 @@ defmodule Earmark.Helpers.LookaheadHelpers do opening backtix """ # (#{},{_,_}) -> {_,_} + @spec still_inline_code(numbered_line, inline_code_continuation) :: inline_code_continuation def still_inline_code( %{line: line, lnb: lnb}, old = {pending, _pending_lnb} ) do case tokenize(line, with: :string_lexer) |> has_still_opening_backtix({:old, pending}) do nil -> {nil, 0} @@ -56,6 +58,7 @@ defmodule Earmark.Helpers.LookaheadHelpers do ####################################################################################### # read_list_lines ####################################################################################### + @spec read_list_lines( Line.ts, inline_code_continuation, number ) :: {boolean, Line.ts, Line.ts, number, number} @doc """ Called to slurp in the lines for a list item. basically, we allow indents and blank lines, and @@ -69,6 +72,7 @@ defmodule Earmark.Helpers.LookaheadHelpers do @type read_list_info :: %{pending: maybe(String.t), pending_lnb: number, initial_indent: number, min_indent: maybe(number)} + @spec _read_list_lines(Line.ts, Line.ts, read_list_info) :: {boolean, Line.ts, Line.ts, number} # List items with initial_indent + 2 defp _read_list_lines([ line = %Line.ListItem{initial_indent: li_indent} | rest ], result, params=%{pending: nil, initial_indent: initial_indent, min_indent: min_indent}) diff --git a/lib/earmark/helpers/string_helpers.ex b/lib/earmark/helpers/string_helpers.ex index 900bca17..9e4d0379 100644 --- a/lib/earmark/helpers/string_helpers.ex +++ b/lib/earmark/helpers/string_helpers.ex @@ -17,6 +17,7 @@ defmodule Earmark.Helpers.StringHelpers do iex> behead_tuple("prefixpostfix", "prefix") {"prefix", "postfix"} """ + @spec behead_tuple( String.t, String.t ) :: {String.t, String.t} def behead_tuple(str, lead) do {lead, behead(str, lead)} end diff --git a/lib/earmark/line.ex b/lib/earmark/line.ex index 3b2ed97d..1607a8d6 100644 --- a/lib/earmark/line.ex +++ b/lib/earmark/line.ex @@ -75,6 +75,7 @@ defmodule Earmark.Line do # proceeding # (_,atom() | tuple() | #{},_) -> ['Elixir.B'] + @spec scan_lines( list(String.t), %Earmark.Options{}, boolean ) :: ts def scan_lines lines, options \\ %Earmark.Options{}, recursive \\ false def scan_lines lines, options, recursive do lines_with_count( lines, options.line - 1) diff --git a/lib/earmark/message.ex b/lib/earmark/message.ex index e6401f34..06012a11 100644 --- a/lib/earmark/message.ex +++ b/lib/earmark/message.ex @@ -8,9 +8,11 @@ defmodule Earmark.Message do @type ts:: list(t) @type container_type :: Options.t | Context.t + @spec add_messages(container_type, ts) :: container_type def add_messages(container, messages), do: Enum.reduce(messages, container, &(add_message(&2, &1))) + @spec add_message(container_type, t) :: container_type def add_message(container, message) def add_message(options = %Options{}, message) do %{options | messages: [message | options.messages]} @@ -19,14 +21,17 @@ defmodule Earmark.Message do %{context | options: %{context.options | messages: [message | get_messages(context)]}} end + @spec add_messages_from(Context.t, Context.t) :: Context.t def add_messages_from(context, message_container) do add_messages(context, message_container.options.messages) end + @spec get_messages( container_type ) :: ts def get_messages(container) def get_messages(%Context{options: %{messages: messages}}), do: messages def get_messages(%Options{messages: messages}), do: messages + @spec set_messages( Context.t, ts ) :: Context.t def set_messages(container, messages) def set_messages(c = %Context{}, messages), do: put_in(c.options.messages, messages) @@ -49,6 +54,7 @@ defmodule Earmark.Message do defp emit_message(filename, msg), do: IO.puts(:stderr, format_message(filename, msg)) + @spec format_message( String.t, t ) :: String.t defp format_message filename, {type, line, text} do "#{filename}:#{line}: #{type}: #{text}" end diff --git a/lib/earmark/parser.ex b/lib/earmark/parser.ex index 4ab55d04..2c7efa53 100644 --- a/lib/earmark/parser.ex +++ b/lib/earmark/parser.ex @@ -5,6 +5,7 @@ defmodule Earmark.Parser do import Earmark.Message, only: [add_messages: 2] + @spec parse(list(String.t), %Earmark.Options{}, boolean) :: {Block.ts(), %{}} def parse(text_lines), do: parse(text_lines, %Earmark.Options{}, false) def parse(text_lines, options = %Earmark.Options{}, recursive) do @@ -30,9 +31,11 @@ defmodule Earmark.Parser do { blocks, footnotes, options1 } end + @spec footnote_def?( Block.t )::boolean defp footnote_def?(%Block.FnDef{}), do: true defp footnote_def?(_block), do: false + @spec find_footnote_links(Block.t) :: list(String.t) defp find_footnote_links(%Block.Para{lines: lines, lnb: lnb}) do lines |> Enum.zip(Stream.iterate(lnb, &(&1 + 1))) @@ -43,6 +46,7 @@ defmodule Earmark.Parser do end defp find_footnote_links(_), do: [] + @spec extract_footnote_links({String.t, number()}) :: list({String.t, number()}) defp extract_footnote_links({line, lnb}) do Regex.scan(~r{\[\^([^\]]+)\]}, line) |> Enum.map(&tl/1) @@ -50,6 +54,7 @@ defmodule Earmark.Parser do end + @spec get_footnote_numbers( list({String.t, number()} ), Block.ts, %Earmark.Options{} ) :: Block.ts def get_footnote_numbers(refs, footnotes, options) do Enum.reduce(refs, {[], []}, fn({ref, lnb}, {defined, undefined}) -> r = hd(ref) @@ -62,6 +67,7 @@ defmodule Earmark.Parser do end) end + @spec create_footnote_blocks(Block.ts, Block.ts) :: Block.ts defp create_footnote_blocks(blocks, []), do: blocks defp create_footnote_blocks(blocks, footnotes) do diff --git a/lib/earmark/scanner.ex b/lib/earmark/scanner.ex index 74ae9446..08b2b658 100644 --- a/lib/earmark/scanner.ex +++ b/lib/earmark/scanner.ex @@ -36,6 +36,7 @@ defmodule Earmark.Scanner do @type t_continuation :: {token, String.t, boolean()} + @spec scan_line( String.t ) :: tokens @doc """ Scans a line into a list of tokens """ @@ -44,6 +45,7 @@ defmodule Earmark.Scanner do |> Enum.reverse end + @spec scan_line_into_tokens( String.t, tokens, boolean() ) :: tokens # Empty Line defp scan_line_into_tokens "", [], _beg do [] @@ -56,6 +58,7 @@ defmodule Earmark.Scanner do scan_line_into_tokens( rest, [token|tokens], still_at_beg ) end + @spec scan_next_token( String.t, boolean ) :: false | t_continuation defp scan_next_token line, beg_of_line defp scan_next_token line, true do cond do @@ -102,6 +105,7 @@ defmodule Earmark.Scanner do |> Tuple.append( false ) end + @spec scan_token_not_at_beg( String.t ) :: {} | t_continuation defp scan_token_not_at_beg line do cond do matches = Regex.run( @backtix_rgx, line ) -> @@ -119,6 +123,7 @@ defmodule Earmark.Scanner do end end + @spec make_ruler_from( String.t ) :: token defp make_ruler_from type do case type do "*" -> %RulerFat{} @@ -127,6 +132,7 @@ defmodule Earmark.Scanner do end end + @spec make_list_item( String.t ) :: %ListItem{} defp make_list_item bullet do case bullet do "*" -> %ListItem{type: :ul, bullet: "*"} @@ -135,6 +141,7 @@ defmodule Earmark.Scanner do end end + @spec prefixed_with_ws(String.t, String.t) :: false | { %Text{}, String.t, true} defp prefixed_with_ws line, ws do if ws == "" do false