Current section
Files
Jump to
Current section
Files
src/checkmark.erl
-module(checkmark).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/checkmark.gleam").
-export([new/2, document/2, file/2, comments_in/2, should_contain_contents_of/3, should_contain_snippet_from/4, load_snippet_source/2, update/1, check/1, check_or_update/2]).
-export_type([checker/1, file/1, code_snippet_source/0, check_error/1, expectation/0, code_segment/0, snippet/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Link code in files with code blocks in markdown or comments,\n"
" and check that they are up to date, or update them automatically.\n"
).
-opaque checker(HME) :: {checker,
fun((binary()) -> {ok, binary()} | {error, HME}),
fun((binary(), binary()) -> {ok, nil} | {error, HME})}.
-opaque file(HMF) :: {file,
binary(),
checker(HMF),
boolean(),
list(expectation())}.
-opaque code_snippet_source() :: {code_snippet_source,
binary(),
checkmark@internal@code_extractor:file()}.
-type check_error(HMG) :: {could_not_read_file, HMG} |
{could_not_write_file, HMG} |
{tag_not_found, binary()} |
{multiple_tags_found, binary(), list(integer())} |
{content_did_not_match, binary()} |
{failed_to_load_code_segment, binary(), binary()} |
could_not_parse_snippet_source.
-type expectation() :: {contents_of_file, binary(), binary()} |
{code_segment,
binary(),
binary(),
checkmark@internal@code_extractor:file(),
code_segment()}.
-type code_segment() :: {function, binary()} |
{function_body, binary()} |
{type_definition, binary()}.
-type snippet() :: {snippet,
list(binary()),
checkmark@internal@parser:fence(),
gleam@option:option(checkmark@internal@parser:fence())}.
-file("src/checkmark.gleam", 69).
?DOC(" Builds a new checker with the provided file IO functions.\n").
-spec new(
fun((binary()) -> {ok, binary()} | {error, HMH}),
fun((binary(), binary()) -> {ok, nil} | {error, HMH})
) -> checker(HMH).
new(Read_file, Write_file) ->
{checker, Read_file, Write_file}.
-file("src/checkmark.gleam", 83).
?DOC(" Configures a markdown document to be checked or updated.\n").
-spec document(checker(HMQ), binary()) -> file(HMQ).
document(Checker, Filename) ->
{file, Filename, Checker, false, []}.
-file("src/checkmark.gleam", 78).
?DOC(" Configures a markdown file to be checked or updated.\n").
-spec file(checker(HMN), binary()) -> file(HMN).
file(Checker, Filename) ->
document(Checker, Filename).
-file("src/checkmark.gleam", 88).
?DOC(" Configures comments in a gleam source file to be checked or updated.\n").
-spec comments_in(checker(HMT), binary()) -> file(HMT).
comments_in(Checker, Filename) ->
{file, Filename, Checker, true, []}.
-file("src/checkmark.gleam", 109).
?DOC(
" Specify that the file should contain the contents of another file as a code block.\n"
" The tag is what comes after the block fence.\n"
" e.g. \"```gleam 1\" would match the tag \"gleam 1\".\n"
" Whitespace is trimmed off the tag.\n"
" Note that you still need to call `check`, `update` or `check_or_update` after this,\n"
" this function only adds to the configuration.\n"
).
-spec should_contain_contents_of(file(HNB), binary(), binary()) -> file(HNB).
should_contain_contents_of(File, Filename, Tag) ->
{file,
erlang:element(2, File),
erlang:element(3, File),
erlang:element(4, File),
[{contents_of_file, Tag, Filename} | erlang:element(5, File)]}.
-file("src/checkmark.gleam", 126).
?DOC(
" Specify that the file should contain a code snippet from a Gleam source file.\n"
" The tag is what comes after the block fence and `gleam`.\n"
" e.g. \"```gleam 1\" would match the tag \"1\".\n"
" Whitespace is trimmed off the tag.\n"
" Note that you still need to call `check`, `update` or `check_or_update` after this,\n"
" this function only adds to the configuration.\n"
).
-spec should_contain_snippet_from(
file(HNE),
code_snippet_source(),
code_segment(),
binary()
) -> file(HNE).
should_contain_snippet_from(File, Source, Segment, Tag) ->
{file,
erlang:element(2, File),
erlang:element(3, File),
erlang:element(4, File),
[{code_segment,
<<"gleam "/utf8, Tag/binary>>,
erlang:element(2, Source),
erlang:element(3, Source),
Segment} |
erlang:element(5, File)]}.
-file("src/checkmark.gleam", 262).
-spec render_fence(binary(), checkmark@internal@parser:fence()) -> binary().
render_fence(Prefix, Fence) ->
<<<<<<Prefix/binary,
(gleam@string:repeat(<<" "/utf8>>, erlang:element(4, Fence)))/binary>>/binary,
(erlang:element(2, Fence))/binary>>/binary,
(erlang:element(3, Fence))/binary>>.
-file("src/checkmark.gleam", 232).
-spec render_code(
checkmark@internal@parser:fence(),
binary(),
list(binary()),
gleam@option:option(checkmark@internal@parser:fence())
) -> list(binary()).
render_code(Start_fence, Prefix, Content, End_fence) ->
End_fence@1 = gleam@option:map(
End_fence,
fun(_capture) -> render_fence(Prefix, _capture) end
),
Without_end_fence = case {erlang:element(4, Start_fence),
Prefix,
End_fence@1} of
{0, <<""/utf8>>, none} ->
Content;
{0, <<""/utf8>>, {some, End_fence@2}} ->
lists:append(Content, [End_fence@2]);
{Amount, _, _} ->
Indent = <<Prefix/binary,
(gleam@string:repeat(<<" "/utf8>>, Amount))/binary>>,
Content@1 = begin
_pipe = Content,
gleam@list:fold(
_pipe,
[],
fun(Lines, Line) ->
[gleam@string:append(Indent, Line) | Lines]
end
)
end,
Content@2 = case End_fence@1 of
none ->
Content@1;
{some, Fence} ->
[Fence | Content@1]
end,
lists:reverse(Content@2)
end,
[render_fence(Prefix, Start_fence) | Without_end_fence].
-file("src/checkmark.gleam", 212).
-spec render_replacements(
list(checkmark@internal@parser:section()),
gleam@dict:dict(binary(), list(binary()))
) -> binary().
render_replacements(Contents, Replacements) ->
_pipe = Contents,
_pipe@1 = gleam@list:flat_map(_pipe, fun(Section) -> case Section of
{fenced_code, _, Prefix, Lines, Start_fence, End_fence} ->
case gleam_stdlib:map_get(
Replacements,
gleam@string:trim(erlang:element(3, Start_fence))
) of
{ok, Replacement} ->
render_code(
Start_fence,
Prefix,
Replacement,
End_fence
);
{error, _} ->
render_code(Start_fence, Prefix, Lines, End_fence)
end;
{other, _, Lines@1} ->
Lines@1
end end),
gleam@string:join(_pipe@1, <<""/utf8>>).
-file("src/checkmark.gleam", 274).
-spec read_file(checker(HOT), binary()) -> {ok, binary()} |
{error, check_error(HOT)}.
read_file(Checker, Filename) ->
_pipe = (erlang:element(2, Checker))(Filename),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {could_not_read_file, Field@0} end
).
-file("src/checkmark.gleam", 93).
?DOC(" Loads a gleam source file to extract snippets from.\n").
-spec load_snippet_source(checker(HMW), binary()) -> {ok, code_snippet_source()} |
{error, check_error(HMW)}.
load_snippet_source(Checker, Filename) ->
gleam@result:'try'(
read_file(Checker, Filename),
fun(Content) -> _pipe = checkmark@internal@code_extractor:load(Content),
_pipe@1 = gleam@result:replace_error(
_pipe,
could_not_parse_snippet_source
),
gleam@result:map(
_pipe@1,
fun(_capture) -> {code_snippet_source, Filename, _capture} end
) end
).
-file("src/checkmark.gleam", 282).
-spec read_lines(checker(HOY), binary()) -> {ok, list(binary())} |
{error, check_error(HOY)}.
read_lines(Checker, Filename) ->
gleam@result:map(
read_file(Checker, Filename),
fun(Content) -> _pipe = splitter:new([<<"\n"/utf8>>, <<"\r\n"/utf8>>]),
checkmark@internal@lines:to_lines(_pipe, Content, []) end
).
-file("src/checkmark.gleam", 188).
-spec get_expected_lines(checker(HNZ), expectation()) -> {ok, list(binary())} |
{error, check_error(HNZ)}.
get_expected_lines(Checker, Expectation) ->
case Expectation of
{contents_of_file, _, Filename} ->
read_lines(Checker, Filename);
{code_segment, _, _, File, Segment} ->
_pipe = case Segment of
{function, Name} ->
checkmark@internal@code_extractor:extract_function(
File,
Name
);
{function_body, Name@1} ->
checkmark@internal@code_extractor:extract_function_body(
File,
Name@1
);
{type_definition, Name@2} ->
checkmark@internal@code_extractor:extract_type(File, Name@2)
end,
gleam@result:map_error(
_pipe,
fun(E) ->
Reason = case E of
{name_not_found, Name@3} ->
<<"Could not find "/utf8, Name@3/binary>>;
span_extraction_failed ->
<<"Extracting snippet failed, please report a bug!"/utf8>>
end,
{failed_to_load_code_segment,
erlang:element(3, Expectation),
Reason}
end
)
end.
-file("src/checkmark.gleam", 266).
-spec parse_file(file(HOM)) -> {ok, list(checkmark@internal@parser:section())} |
{error, list(check_error(HOM))}.
parse_file(File) ->
_pipe = read_lines(erlang:element(3, File), erlang:element(2, File)),
_pipe@1 = gleam@result:map_error(_pipe, fun gleam@list:wrap/1),
gleam@result:map(
_pipe@1,
fun(_capture) ->
checkmark@internal@parser:parse(_capture, erlang:element(4, File))
end
).
-file("src/checkmark.gleam", 168).
?DOC(" Updates the code blocks in the markdown file or Gleam source file from the specified files.\n").
-spec update(file(HNT)) -> {ok, nil} | {error, list(check_error(HNT))}.
update(File) ->
gleam@result:'try'(
parse_file(File),
fun(Contents) ->
Results = begin
gleam@list:map(
erlang:element(5, File),
fun(Expectation) ->
gleam@result:'try'(
get_expected_lines(
erlang:element(3, File),
Expectation
),
fun(Lines) ->
{ok, {erlang:element(2, Expectation), Lines}}
end
)
end
)
end,
{Replacements, Errors} = gleam@result:partition(Results),
case Errors of
[] ->
Contents@1 = render_replacements(
Contents,
maps:from_list(Replacements)
),
_pipe = (erlang:element(3, erlang:element(3, File)))(
erlang:element(2, File),
Contents@1
),
gleam@result:map_error(
_pipe,
fun(E) -> [{could_not_write_file, E}] end
);
_ ->
{error, Errors}
end
end
).
-file("src/checkmark.gleam", 306).
-spec find_match(
list(checkmark@internal@parser:section()),
binary(),
list({integer(), snippet()})
) -> {ok, snippet()} | {error, check_error(any())}.
find_match(Content, Tag, Found) ->
case Content of
[] ->
case Found of
[] ->
{error, {tag_not_found, Tag}};
[{_, Snippet}] ->
{ok, Snippet};
_ ->
{error,
{multiple_tags_found,
Tag,
begin
_pipe = Found,
_pipe@1 = lists:reverse(_pipe),
gleam@list:map(
_pipe@1,
fun(Pair) -> erlang:element(1, Pair) end
)
end}}
end;
[{fenced_code, Line_number, _, Lines, Start_fence, End_fence} | Rest] ->
case gleam@string:trim(erlang:element(3, Start_fence)) =:= Tag of
true ->
Result = {Line_number,
{snippet, Lines, Start_fence, End_fence}},
find_match(Rest, Tag, [Result | Found]);
false ->
find_match(Rest, Tag, Found)
end;
[_ | Rest@1] ->
find_match(Rest@1, Tag, Found)
end.
-file("src/checkmark.gleam", 290).
-spec check_one(
list(checkmark@internal@parser:section()),
list(binary()),
binary()
) -> {ok, nil} | {error, check_error(any())}.
check_one(Content, Expected, Tag) ->
gleam@result:'try'(
find_match(Content, Tag, []),
fun(Snippet) -> case erlang:element(2, Snippet) =:= Expected of
true ->
{ok, nil};
false ->
{error, {content_did_not_match, Tag}}
end end
).
-file("src/checkmark.gleam", 151).
?DOC(
" Checks that the markdown or Gleam source code file \n"
" contains code blocks that match the content as specified.\n"
).
-spec check(file(HNN)) -> {ok, nil} | {error, list(check_error(HNN))}.
check(File) ->
gleam@result:'try'(
parse_file(File),
fun(Contents) ->
Results = begin
gleam@list:map(
erlang:element(5, File),
fun(Expectation) ->
gleam@result:'try'(
get_expected_lines(
erlang:element(3, File),
Expectation
),
fun(Lines) ->
check_one(
Contents,
Lines,
erlang:element(2, Expectation)
)
end
)
end
)
end,
{_, Errors} = gleam@result:partition(Results),
case Errors of
[] ->
{ok, nil};
_ ->
{error, Errors}
end
end
).
-file("src/checkmark.gleam", 139).
?DOC(" Convenience function for either checking or updating depending on a boolean.\n").
-spec check_or_update(file(HNH), boolean()) -> {ok, nil} |
{error, list(check_error(HNH))}.
check_or_update(File, Should_update) ->
case Should_update of
true ->
update(File);
false ->
check(File)
end.