Skip to content

Commit 9766ed7

Browse files
Safety check before running File.rm_rf! in doc gen (#1707)
1 parent 939da1a commit 9766ed7

File tree

7 files changed

+131
-7
lines changed

7 files changed

+131
-7
lines changed

lib/ex_doc/formatter/epub.ex

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ defmodule ExDoc.Formatter.EPUB do
1010
"""
1111
@spec run(list, ExDoc.Config.t()) :: String.t()
1212
def run(project_nodes, config) when is_map(config) do
13+
parent = config.output
1314
config = normalize_config(config)
14-
File.rm_rf!(config.output)
15-
File.mkdir_p!(Path.join(config.output, "OEBPS"))
15+
16+
HTML.setup_output(
17+
parent,
18+
&cleanup_output_dir(&1, config),
19+
&create_output_dir(&1, config)
20+
)
1621

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

@@ -44,6 +49,16 @@ defmodule ExDoc.Formatter.EPUB do
4449
Path.relative_to_cwd(epub)
4550
end
4651

52+
defp create_output_dir(root, config) do
53+
File.mkdir_p!(Path.join(config.output, "OEBPS"))
54+
File.touch!(Path.join(root, ".ex_doc"))
55+
end
56+
57+
defp cleanup_output_dir(docs_root, config) do
58+
File.rm_rf!(config.output)
59+
create_output_dir(docs_root, config)
60+
end
61+
4762
defp normalize_config(config) do
4863
output =
4964
config.output

lib/ex_doc/formatter/html.ex

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ defmodule ExDoc.Formatter.HTML do
1616
config = %{config | output: Path.expand(config.output)}
1717

1818
build = Path.join(config.output, ".build")
19-
output_setup(build, config)
19+
setup_output(config.output, &cleanup_output_dir(&1, config), &create_output_dir(&1, config))
2020

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

149-
defp output_setup(build, config) do
149+
def setup_output(root, cleanup, create) do
150+
safety_path = Path.join(root, ".ex_doc")
151+
152+
cond do
153+
File.exists?(safety_path) and File.exists?(root) ->
154+
cleanup.(root)
155+
156+
not File.exists?(root) ->
157+
create.(root)
158+
159+
File.ls!(root) == [] ->
160+
add_safety_file(root)
161+
:ok
162+
163+
true ->
164+
IO.warn(
165+
"ExDoc is outputting to an existing directory. " <>
166+
"Beware documentation output may be mixed with other entries"
167+
)
168+
end
169+
end
170+
171+
defp create_output_dir(root, _config) do
172+
File.mkdir_p!(root)
173+
add_safety_file(root)
174+
end
175+
176+
defp add_safety_file(root) do
177+
File.touch!(Path.join(root, ".ex_doc"))
178+
end
179+
180+
defp cleanup_output_dir(docs_root, config) do
181+
build = Path.join(docs_root, ".build")
182+
150183
if File.exists?(build) do
151184
build
152185
|> File.read!()
153186
|> String.split("\n", trim: true)
154-
|> Enum.map(&Path.join(config.output, &1))
187+
|> Enum.map(&Path.join(docs_root, &1))
155188
|> Enum.each(&File.rm/1)
156189

157190
File.rm(build)
158191
else
159-
File.rm_rf!(config.output)
160-
File.mkdir_p!(config.output)
192+
File.rm_rf!(docs_root)
193+
create_output_dir(docs_root, config)
161194
end
162195
end
163196

test/ex_doc/formatter/epub_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,33 @@ defmodule ExDoc.Formatter.EPUBTest do
103103
assert File.regular?(tmp_dir <> "/epub/another_dir/#{doc_config(context)[:project]}.epub")
104104
end
105105

106+
test "succeeds if trying to write into an empty existing directory", context do
107+
config = doc_config(context)
108+
109+
new_output = config[:output] <> "/new-dir"
110+
File.mkdir_p!(new_output)
111+
112+
new_config = Keyword.put(config, :output, new_output)
113+
114+
refute ExUnit.CaptureIO.capture_io(:stderr, fn ->
115+
generate_docs(new_config)
116+
end) =~ "ExDoc is outputting to an existing directory"
117+
end
118+
119+
test "warns if trying to write into existing directory with files", context do
120+
config = doc_config(context)
121+
new_output = config[:output] <> "/new-dir"
122+
123+
File.mkdir_p!(new_output)
124+
File.touch!(Path.join(new_output, "dummy-file"))
125+
126+
new_config = Keyword.put(config, :output, new_output)
127+
128+
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
129+
generate_docs(new_config)
130+
end) =~ "ExDoc is outputting to an existing directory"
131+
end
132+
106133
test "generates an EPUB file with a standardized structure", %{tmp_dir: tmp_dir} = context do
107134
generate_docs_and_unzip(context, doc_config(context))
108135

test/ex_doc/formatter/html/erlang_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ defmodule ExDoc.Formatter.HTML.ErlangTest do
55
@moduletag :otp_eep48
66
@moduletag :tmp_dir
77

8+
setup %{tmp_dir: tmp_dir} do
9+
output = tmp_dir <> "/doc"
10+
File.mkdir!(output)
11+
File.touch!("#{output}/.ex_doc")
12+
end
13+
814
test "smoke test", c do
915
erlc(c, :foo, ~S"""
1016
%% @doc

test/ex_doc/formatter/html/search_items_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ defmodule ExDoc.Formatter.HTML.SearchItemsTest do
44

55
@moduletag :tmp_dir
66

7+
setup %{tmp_dir: tmp_dir} do
8+
output = tmp_dir <> "/doc"
9+
File.mkdir!(output)
10+
File.touch!("#{output}/.ex_doc")
11+
end
12+
713
test "Elixir module", c do
814
modules =
915
elixirc(c, ~S'''

test/ex_doc/formatter/html_test.exs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ defmodule ExDoc.Formatter.HTMLTest do
66

77
@moduletag :tmp_dir
88

9+
setup %{tmp_dir: tmp_dir} do
10+
output = tmp_dir <> "/html"
11+
File.mkdir_p!(output)
12+
File.touch!(output <> "/.ex_doc")
13+
end
14+
915
defp read_wildcard!(path) do
1016
[file] = Path.wildcard(path)
1117
File.read!(file)
@@ -174,6 +180,33 @@ defmodule ExDoc.Formatter.HTMLTest do
174180
refute content_module =~ re[:index][:refresh]
175181
end
176182

183+
test "succeeds if trying to write into an empty existing directory", context do
184+
config = doc_config(context)
185+
186+
new_output = config[:output] <> "/new-dir"
187+
File.mkdir_p!(new_output)
188+
189+
new_config = Keyword.put(config, :output, new_output)
190+
191+
refute ExUnit.CaptureIO.capture_io(:stderr, fn ->
192+
generate_docs(new_config)
193+
end) =~ "ExDoc is outputting to an existing directory"
194+
end
195+
196+
test "warns if trying to write into existing directory with files", context do
197+
config = doc_config(context)
198+
new_output = config[:output] <> "/new-dir"
199+
200+
File.mkdir_p!(new_output)
201+
File.touch!(Path.join(new_output, "dummy-file"))
202+
203+
new_config = Keyword.put(config, :output, new_output)
204+
205+
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
206+
generate_docs(new_config)
207+
end) =~ "ExDoc is outputting to an existing directory"
208+
end
209+
177210
test "allows to set the authors of the document", %{tmp_dir: tmp_dir} = context do
178211
generate_docs(doc_config(context, authors: ["John Doe", "Jane Doe"]))
179212
content_index = File.read!(tmp_dir <> "/html/api-reference.html")

test/test_helper.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ defmodule TestHelper do
2222
def elixirc(context, filename \\ "nofile", code) do
2323
dir = context.tmp_dir
2424

25+
output_dir = context.tmp_dir <> "/html"
26+
File.mkdir_p!(output_dir)
27+
File.write!(output_dir <> "/.ex_doc", "")
28+
2529
src_path = Path.join([dir, filename])
2630
src_path |> Path.dirname() |> File.mkdir_p!()
2731
File.write!(src_path, code)

0 commit comments

Comments
 (0)