Packages

Content Management System for static sites, like Jekyll but with elixir/eex rather than liquid templates

Current section

Files

Jump to
dragon lib tools index.ex
Raw

lib/tools/index.ex

defmodule Dragon.Tools do
@moduledoc """
File handling tools.
"""
use Dragon.Context
def put_into(dict, [key], value), do: Map.put(dict, key, value)
def put_into(dict, [key | keys], value) do
case Map.get(dict, key) do
nil -> Map.put(dict, key, put_into(%{}, keys, value))
d when is_map(d) -> Map.replace(dict, key, put_into(d, keys, value))
_ -> raise ArgumentError
end
end
def seek!(fd, offset) do
with {:ok, _} <- :file.position(fd, offset), do: fd
end
def open_file(path) do
case File.open(path, [:utf8, :read]) do
{:ok, fd} ->
{:ok, fd}
{:error, :enoent} ->
abort("Cannot open file '#{path}', cannot continue")
{:error, :eisdir} ->
raise "narf"
abort("Cannot open file '#{path}', it is a folder. Cannot continue")
err ->
IO.inspect(err, label: "File open error")
abort("Cannot open file, cannot continue")
end
end
def with_open_file(path, func) do
with {:ok, fd} <- open_file(path) do
try do
func.(fd)
after
:ok = File.close(fd)
end
end
end
def write_file(dest, content) do
Dragon.Tools.makedirs_for_file(dest)
case File.write(dest, content) do
:ok -> :ok
{:error, err} -> abort("Cannot write file '#{dest}': #{err}")
end
end
def drop_root(root, path) do
if String.slice(path, 0..(String.length(root) - 1)) == root do
String.slice(path, (String.length(root) + 1)..-1)
else
path
end
end
def find_index_file(root, index) do
case {index, File.stat(root)} do
{index, {:ok, %File.Stat{type: :directory}}} when not is_nil(index) ->
find_index_file(Path.join(root, index), nil)
{_, {:ok, %File.Stat{type: :regular}}} ->
{:ok, root}
{_, err} ->
err
end
end
def find_file(root, path) do
with :error <- find_file_variant(root, Path.split(path)),
do: {:error, "File not found (#{Path.join(root, path)})"}
end
## TODO: make "directory ok" flag
def try_variant(root, part, rest, on_error \\ nil) do
case {valid_part(root, part), rest} do
{{:directory, _}, []} ->
:error
{{:directory, path}, more} ->
find_file_variant(path, more)
{{:file, path}, []} ->
{:ok, path}
{{:file, _}, _} ->
:error
_ ->
if on_error do
on_error.()
else
:error
end
end
end
def find_file_variant(root, [part | rest]) do
try_variant(root, part, rest, fn ->
case part do
"_" <> part -> try_variant(root, part, rest)
part -> try_variant(root, "_" <> part, rest)
end
end)
end
def find_file_variant(_, []), do: :error
def valid_part(root, part) do
path = Path.join(root, part)
case File.stat(path) do
{:ok, %{type: :directory}} -> {:directory, path}
{:ok, %{type: :regular}} -> {:file, path}
_ -> :error
end
end
def export_fname(fname) do
if String.at(fname, 0) == "_" do
String.slice(fname, 1..-1)
else
fname
end
|> String.downcase()
end
def makedirs_for_file(dest) do
folder = Path.dirname(dest)
case File.mkdir_p(folder) do
:ok -> :ok
err -> abort("Cannot mkdir '#{folder}': #{inspect(err)}")
end
end
##############################################################################
defp no_match(dragon, path, _) do
notify("Ignoring file '#{path}'")
dragon
end
def walk_tree(dragon, path, cfg) when is_list(cfg),
do:
walk_tree(
dragon,
path,
Map.merge(%{types: %{}, follow_meta: false, no_match: &no_match/3}, Map.new(cfg))
)
def walk_tree(%{root: root} = dragon, path, opts) when is_binary(path) and is_map(opts) do
fname = Path.basename(path)
first = String.at(fname, 0)
cond do
first == "_" and opts.follow_meta != true ->
dragon
## instead, create an "ignore" pattern configuration/mask
first == "." ->
dragon
true ->
fullpath = Path.join(root, path)
case File.stat(fullpath) do
{:ok, %File.Stat{type: :directory}} ->
File.ls!(fullpath)
|> Enum.reduce(dragon, &walk_tree(&2, Path.join(path, &1), opts))
{:ok, %File.Stat{type: :regular}} ->
case opts.types[Path.extname(fname)] do
nil -> opts.no_match.(dragon, fullpath, opts)
handler -> handler.(dragon, fullpath, opts)
end
{:error, reason} ->
abort("Unable to process file '#{fullpath}': #{reason}")
end
end
end
@doc """
Within walk_tree, investigate a file to determin what its type is and store
accordingly. If it begins with the dragon header, consider it a dragon
template. Otherwise look at the file extension.
"""
def scan_file(dragon, path, _args) do
type =
with_open_file(path, fn fd ->
case IO.binread(fd, 10) do
"--- dragon" ->
:dragon
_ ->
case Path.extname(path) do
".scss" -> :scss
_ -> :file
end
end
end)
[_ | rel_path] = Path.split(path)
rel_path = Path.join(rel_path)
put_into(dragon, [:files, type, rel_path], [])
end
end