Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safety check before running File.rm_rf! in doc gen #1707

Merged
merged 3 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 17 additions & 2 deletions lib/ex_doc/formatter/epub.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ defmodule ExDoc.Formatter.EPUB do
"""
@spec run(list, ExDoc.Config.t()) :: String.t()
def run(project_nodes, config) when is_map(config) do
parent = config.output
config = normalize_config(config)
File.rm_rf!(config.output)
File.mkdir_p!(Path.join(config.output, "OEBPS"))

HTML.setup_output(
parent,
&cleanup_output_dir(&1, config),
&create_output_dir(&1, config)
)

project_nodes = HTML.render_all(project_nodes, ".xhtml", config, highlight_tag: "samp")

Expand Down Expand Up @@ -44,6 +49,16 @@ defmodule ExDoc.Formatter.EPUB do
Path.relative_to_cwd(epub)
end

defp create_output_dir(root, config) do
File.mkdir_p!(Path.join(config.output, "OEBPS"))
File.touch!(Path.join(root, ".ex_doc"))
end

defp cleanup_output_dir(docs_root, config) do
File.rm_rf!(config.output)
create_output_dir(docs_root, config)
end

defp normalize_config(config) do
output =
config.output
Expand Down
43 changes: 38 additions & 5 deletions lib/ex_doc/formatter/html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule ExDoc.Formatter.HTML do
config = %{config | output: Path.expand(config.output)}

build = Path.join(config.output, ".build")
output_setup(build, config)
setup_output(config.output, &cleanup_output_dir(&1, config), &create_output_dir(&1, config))

project_nodes = render_all(project_nodes, ".html", config, [])
extras = build_extras(config, ".html")
Expand Down Expand Up @@ -146,18 +146,51 @@ defmodule ExDoc.Formatter.HTML do
|> ExDoc.DocAST.highlight(language, opts)
end

defp output_setup(build, config) do
def setup_output(root, cleanup, create) do
safety_path = Path.join(root, ".ex_doc")

cond do
File.exists?(safety_path) and File.exists?(root) ->
cleanup.(root)

not File.exists?(root) ->
create.(root)

File.ls!(root) == [] ->
add_safety_file(root)
:ok

true ->
IO.warn(
"ExDoc is outputting to an existing directory. " <>
"Beware documentation output may be mixed with other entries"
)
end
end
viniciusmuller marked this conversation as resolved.
Show resolved Hide resolved

defp create_output_dir(root, _config) do
File.mkdir_p!(root)
add_safety_file(root)
end

defp add_safety_file(root) do
File.touch!(Path.join(root, ".ex_doc"))
end

defp cleanup_output_dir(docs_root, config) do
build = Path.join(docs_root, ".build")

if File.exists?(build) do
build
|> File.read!()
|> String.split("\n", trim: true)
|> Enum.map(&Path.join(config.output, &1))
|> Enum.map(&Path.join(docs_root, &1))
|> Enum.each(&File.rm/1)

File.rm(build)
else
File.rm_rf!(config.output)
File.mkdir_p!(config.output)
File.rm_rf!(docs_root)
create_output_dir(docs_root, config)
end
end

Expand Down
27 changes: 27 additions & 0 deletions test/ex_doc/formatter/epub_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ defmodule ExDoc.Formatter.EPUBTest do
assert File.regular?(tmp_dir <> "/epub/another_dir/#{doc_config(context)[:project]}.epub")
end

test "succeeds if trying to write into an empty existing directory", context do
config = doc_config(context)

new_output = config[:output] <> "/new-dir"
File.mkdir_p!(new_output)

new_config = Keyword.put(config, :output, new_output)

refute ExUnit.CaptureIO.capture_io(:stderr, fn ->
generate_docs(new_config)
end) =~ "ExDoc is outputting to an existing directory"
end

test "warns if trying to write into existing directory with files", context do
config = doc_config(context)
new_output = config[:output] <> "/new-dir"

File.mkdir_p!(new_output)
File.touch!(Path.join(new_output, "dummy-file"))

new_config = Keyword.put(config, :output, new_output)

assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
generate_docs(new_config)
end) =~ "ExDoc is outputting to an existing directory"
end

test "generates an EPUB file with a standardized structure", %{tmp_dir: tmp_dir} = context do
generate_docs_and_unzip(context, doc_config(context))

Expand Down
6 changes: 6 additions & 0 deletions test/ex_doc/formatter/html/erlang_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ defmodule ExDoc.Formatter.HTML.ErlangTest do
@moduletag :otp_eep48
@moduletag :tmp_dir

setup %{tmp_dir: tmp_dir} do
output = tmp_dir <> "/doc"
File.mkdir!(output)
File.touch!("#{output}/.ex_doc")
end

test "smoke test", c do
erlc(c, :foo, ~S"""
%% @doc
Expand Down
6 changes: 6 additions & 0 deletions test/ex_doc/formatter/html/search_items_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ defmodule ExDoc.Formatter.HTML.SearchItemsTest do

@moduletag :tmp_dir

setup %{tmp_dir: tmp_dir} do
output = tmp_dir <> "/doc"
File.mkdir!(output)
File.touch!("#{output}/.ex_doc")
end

test "Elixir module", c do
modules =
elixirc(c, ~S'''
Expand Down
33 changes: 33 additions & 0 deletions test/ex_doc/formatter/html_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ defmodule ExDoc.Formatter.HTMLTest do

@moduletag :tmp_dir

setup %{tmp_dir: tmp_dir} do
output = tmp_dir <> "/html"
File.mkdir_p!(output)
File.touch!(output <> "/.ex_doc")
end

defp read_wildcard!(path) do
[file] = Path.wildcard(path)
File.read!(file)
Expand Down Expand Up @@ -146,6 +152,33 @@ defmodule ExDoc.Formatter.HTMLTest do
refute content_module =~ re[:index][:refresh]
end

test "succeeds if trying to write into an empty existing directory", context do
config = doc_config(context)

new_output = config[:output] <> "/new-dir"
File.mkdir_p!(new_output)

new_config = Keyword.put(config, :output, new_output)

refute ExUnit.CaptureIO.capture_io(:stderr, fn ->
generate_docs(new_config)
end) =~ "ExDoc is outputting to an existing directory"
end

test "warns if trying to write into existing directory with files", context do
config = doc_config(context)
new_output = config[:output] <> "/new-dir"

File.mkdir_p!(new_output)
File.touch!(Path.join(new_output, "dummy-file"))

new_config = Keyword.put(config, :output, new_output)

assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
generate_docs(new_config)
end) =~ "ExDoc is outputting to an existing directory"
end

test "allows to set the authors of the document", %{tmp_dir: tmp_dir} = context do
generate_docs(doc_config(context, authors: ["John Doe", "Jane Doe"]))
content_index = File.read!(tmp_dir <> "/html/api-reference.html")
Expand Down
4 changes: 4 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ defmodule TestHelper do
def elixirc(context, filename \\ "nofile", code) do
dir = context.tmp_dir

output_dir = context.tmp_dir <> "/html"
File.mkdir_p!(output_dir)
File.write!(output_dir <> "/.ex_doc", "")

src_path = Path.join([dir, filename])
src_path |> Path.dirname() |> File.mkdir_p!()
File.write!(src_path, code)
Expand Down