lib/nwiki.ex
defmodule Nwiki do
@moduledoc """
Documentation for `Nwiki`.
"""
@doc """
Runs Nwiki
"""
def run(input_path, output_path) when is_binary(input_path) and is_binary(output_path) do
main_css_path = Path.join(["assets", "css", "main.css"])
normalize_css_path = Path.join(["assets", "css", "normalize.css"])
default_css_path = Path.join(["assets", "css", "default.css"])
solarized_dark_css_path = Path.join(["assets", "css", "solarized-dark.css"])
highlight_pack_js_path = Path.join(["assets", "js", "highlight.pack.js"])
article_html_eex = Path.join(["templates", "article.html.eex"])
index_html_eex = Path.join(["templates", "index.html.eex"])
wiki_title = "nikulog"
files =
input_path
|> Path.expand()
|> Path.join("**")
|> Path.wildcard()
{markdowns, others} = Enum.split_with(files, &(Path.extname(&1) === ".md"))
entries =
markdowns
|> Enum.map(fn path ->
url =
path
|> Path.relative_to(Path.expand(input_path))
|> Path.rootname()
|> URI.parse()
{:ok, ast} =
path
|> File.read!()
|> parse()
{url, ast}
end)
|> Enum.into(Map.new())
all_links =
entries
|> Enum.map(fn {k, v} -> {k, collect_link(v)} end)
|> Enum.into(Map.new())
all_linked = collect_linked(all_links)
entries
|> Enum.each(fn {%URI{path: path} = url, ast} ->
write_path = Path.join(output_path, path)
write_path
|> Path.dirname()
|> File.mkdir_p!()
body =
ast
|> add_extname_to_hashed_link_url(".html")
|> Earmark.Transform.transform()
links =
all_links
|> Map.get(url, Map.new())
|> Map.keys()
|> Enum.filter(fn
%URI{host: nil} -> true
_ -> false
end)
linked =
all_linked
|> Map.get(url, Map.new())
|> Enum.map(fn u ->
{u,
all_links
|> Map.get(u, Map.new())
|> Map.keys()}
end)
|> Enum.into(Map.new())
html =
EEx.eval_file(article_html_eex,
title: path,
description: "",
body: body,
links: links,
linked: linked,
ga_tracking_id: nil
)
File.write!(write_path <> ".html", html)
end)
index_html =
EEx.eval_file(index_html_eex,
title: wiki_title,
description: "",
body: entries,
ga_tracking_id: nil
)
File.write!(Path.join(output_path, "index.html"), index_html)
others
|> Enum.each(fn path ->
write_path =
path
|> Path.relative_to(Path.expand(input_path))
|> Path.rootname()
write_path
|> Path.dirname()
|> File.mkdir_p!()
File.cp!(path, Path.join(output_path, write_path))
end)
css_path = Path.join([output_path, "assets", "css"])
File.mkdir_p!(css_path)
File.cp!(main_css_path, Path.join(css_path, "main.css"))
File.cp!(normalize_css_path, Path.join(css_path, "normalize.css"))
File.cp!(default_css_path, Path.join(css_path, "default.css"))
File.cp!(solarized_dark_css_path, Path.join(css_path, "solarized-dark.css"))
js_path = Path.join([output_path, "assets", "js"])
File.mkdir_p!(js_path)
File.cp!(highlight_pack_js_path, Path.join(js_path, "highlight.pack.js"))
end
@doc """
Parses markdown
"""
def parse(markdown) when is_binary(markdown) do
{:ok, ast, []} = Earmark.as_ast(markdown, pure_links: false)
hashed_link_added_ast =
ast
|> EarmarkHashedLink.add_hashed_link()
|> EarmarkRawHtml.melt_raw_html_into_ast()
{:ok, hashed_link_added_ast}
end
@doc """
Collects urls which have link as same as the url
"""
def collect_linked(all_links) when is_map(all_links) do
all_links
|> Enum.flat_map(fn {url, links} ->
Enum.map(links, fn {link_url, _link_text} ->
{link_url, url}
end)
end)
|> Enum.group_by(fn {k, _v} -> k end, fn {_k, v} -> v end)
|> Enum.map(fn {k, v} -> {k, MapSet.new(v)} end)
|> Enum.into(Map.new())
end
@doc """
Collects ASTs which is represented as link (i.e. <a href="..."></a> tags) from AST
"""
def collect_link(ast) when is_list(ast) do
do_collect_link(ast, Map.new())
end
@doc false
def do_collect_link(ast, links)
def do_collect_link([], links), do: links
def do_collect_link([{tag, attr, ast} | rest], links) when tag in ["a", "A"] do
url =
Enum.find_value(attr, fn
{"href", value} ->
value
_ ->
nil
end)
text =
case ast do
[x] when is_binary(x) -> x
# We handle only a text. Other are just ignore right now.
_ -> nil
end
with u when not is_nil(url) <- url,
t when not is_nil(text) <- text do
do_collect_link(
rest,
Map.update(links, URI.parse(u), MapSet.new([t]), &MapSet.put(&1, t))
)
else
_ ->
do_collect_link(rest, links)
end
end
def do_collect_link([{_tag, _attr, ast} | rest], links) do
do_collect_link(rest, do_collect_link(ast, links))
end
def do_collect_link([string | rest], links) when is_binary(string) do
do_collect_link(rest, links)
end
def add_extname_to_hashed_link_url(ast, extname) when is_list(ast) and is_binary(extname) do
do_add_extname_to_hashed_link_url(ast, extname, [])
end
@doc false
def do_add_extname_to_hashed_link_url(ast, extname, new_ast)
def do_add_extname_to_hashed_link_url([], _extname, new_ast), do: Enum.reverse(new_ast)
def do_add_extname_to_hashed_link_url([{tag, attr, ast} | rest], extname, new_ast)
when tag in ["a", "A"] do
text =
case ast do
[x] when is_binary(x) -> x
# We handle only a text. Other are just ignore right now.
_ -> nil
end
new_attr =
attr
|> Enum.map(fn
{"href", value} when is_binary(text) and "#" <> value == text ->
{"href", value <> extname}
x ->
x
end)
do_add_extname_to_hashed_link_url(rest, extname, [
{tag, new_attr, do_add_extname_to_hashed_link_url(ast, extname, [])} | new_ast
])
end
def do_add_extname_to_hashed_link_url([{tag, attr, ast} | rest], extname, new_ast) do
do_add_extname_to_hashed_link_url(rest, extname, [
{tag, attr, do_add_extname_to_hashed_link_url(ast, extname, [])} | new_ast
])
end
def do_add_extname_to_hashed_link_url([string | rest], extname, new_ast)
when is_binary(string) do
do_add_extname_to_hashed_link_url(rest, extname, [string | new_ast])
end
end