-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
Copy pathgenstdlib.jl
203 lines (182 loc) · 7.19 KB
/
genstdlib.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
module GenStdLib
import Base.Docs: Binding, DocStr
# Constants.
const DOCSTRING_DIRECTIVE = r"^(.. (function|type|variable):: ).*"
# Types.
typealias Signature Tuple{Binding, Type}
type State
files :: Dict{String, Vector{String}}
validdocs :: Dict{String, Tuple{Module, Signature, DocStr}}
baddocs :: Dict{Signature, Tuple{Module, DocStr}}
documented :: Dict{Signature, Tuple{Module, DocStr, String}}
errorlevel :: Int
debug :: Bool
State() = new(Dict(), Dict(), Dict(), Dict(), 0, "JULIA_GENSTDLIB_DEBUG" in keys(ENV))
end
# Documentation Translator.
function translate(dirs::Vector; root = dirname(@__FILE__))
cd(root) do
state = State()
println("\n# GENSTDLIB\n")
# Find all available docstrings within `Base` and its submodules.
info("loading docstrings from modules.")
loaddocs!(state)
# Read in rst pages and update all docstrings.
info("parsing external documentation.")
for dir in dirs, file in readdir(dir)
translate(state, joinpath(dir, file))
end
# Write the newly updated rst docs back to their files -- replaces original content.
if state.errorlevel < 2
info("writing documentation back to file.")
for (file, contents) in state.files
open(file, "w") do io
for line in contents
println(io, line)
end
end
end
else
warn("errors found while generating documentation. Aborting file writing.")
end
# Report statistics about 'Base' docs.
summarise(state)
end
end
function translate(state::State, file::AbstractString)
input, output = split(readstring(file), '\n')[1:end-1], []
while !isempty(input)
if ismatch(DOCSTRING_DIRECTIVE, first(input))
append!(output, getdoc(state, file, input))
else
push!(output, shift!(input))
end
end
state.files[file] = output
return state
end
# Documentation Summary Report.
function summarise(state::State)
println("\n# SUMMARY\n")
println(" ", length(state.validdocs), " docstrings found in modules.")
println(" ", length(state.documented), " docstrings found in external docs.")
# Of the missing docstrings, which are public and which are private?
public, private = [], []
for (sig, (mod, signature, docstr)) in state.validdocs
# The docstring is valid, but not found in the external docs.
if !haskey(state.documented, signature)
binding, typesig = signature
exported = binding.var in names(binding.mod)
push!(exported ? public : private, (binding, typesig))
end
end
println(" ", length(public), " public docstrings missing from external docs.")
state.debug && (println(); foreach(printmissing, public); println())
println(" ", length(private), " private docstrings missing from external docs.")
state.debug && (println(); foreach(printmissing, private); println())
# Expected format is: first element in docstring should be a code block.
println(" ", length(state.baddocs), " docstrings not matching expected format. Skipped.")
println(" ", length(state.files), " files parsed.\n")
state.debug || println("Set 'JULIA_GENSTDLIB_DEBUG' ENV for additional debug info.\n")
end
printmissing(x) = println(" X ", x[1], " :: ", x[2])
# Documentation Loader.
function loaddocs!(state::State)
for mod in Base.Docs.modules
for (binding, multidoc) in Base.Docs.meta(mod)
for (typesig, docstr) in multidoc.docs
loaddocs!(state, mod, binding, typesig, docstr)
end
end
end
for (keyword, docstr) in Base.Docs.keywords
loaddocs!(state, Main, keyword, Union{}, docstr)
end
return state
end
function loaddocs!(state::State, mod, binding::Binding, typesig, docstr)
markdown = Base.Docs.parsedoc(docstr)
if validdocstr(markdown)
code = rstrip(markdown.content[1].code)
if haskey(state.validdocs, code) && state.validdocs[code][3] !== docstr
code = indent(code)
warn("duplicate signature found for '$binding' in module '$mod':\n\n$code\n")
state.errorlevel = 2
else
state.validdocs[code] = (mod, (binding, typesig), docstr)
end
else
if haskey(state.baddocs, (binding, typesig))
warn("duplicate binding '$binding :: $typesig' found in module '$mod'.")
state.errorlevel = 2
else
state.baddocs[(binding, typesig)] = (mod, docstr)
end
end
return state
end
function loaddocs!(state::State, mod, keyword::Symbol, typesig, docstr)
binding = Base.Docs.Binding(mod, keyword)
loaddocs!(state, mod, binding, typesig, docstr)
end
# Retrieve and format docstrings.
function getdoc(state::State, file::AbstractString, input::Vector)
# Capture the lines containing the docstring signature.
output = []
while !isempty(input)
line = rstrip(shift!(input))
push!(output, line)
ismatch(r"^$", line) && break
end
# Recover the unindented version of the signature.
n = length(match(DOCSTRING_DIRECTIVE, first(output))[1])
b = IOBuffer()
for line in output[1:end-1]
println(b, line[(n + 1):end])
end
# The signature may contain `\` characaters, which must be unescaped.
signature = unescape_string(rstrip(takebuf_string(b)))
# Splice the correct docstring into the output after the signature.
if haskey(state.validdocs, signature)
# Push the rst text for the docstring into the output.
mod, (binding, typesig), docstr = state.validdocs[signature]
md = Markdown.MD(Base.Docs.parsedoc(docstr).content[2:end])
rst = Base.Markdown.rst(dropheaders(md))
push!(output, " .. Docstring generated from Julia source", "")
for line in split(rst, '\n')
line = isempty(line) ? "" : string(" "^3, line)
push!(output, rstrip(line))
end
# Consume all indented lines from the current docstring.
while !isempty(input)
line = first(input)
ismatch(r"^[^\s]", line) ? break : shift!(input)
end
# Track which docstrings have been found in the external docs.
state.documented[(binding, typesig)] = (mod, docstr, signature)
else
signature = indent(signature)
warn("missing docs for signature:\n\n$signature\n")
state.errorlevel = 1
end
return output
end
# Replace headers in docs with bold since Sphinx does not allow headers inside docstrings.
dropheaders(md) = Markdown.MD(map(bold, md.content))
bold(x::Markdown.Header) = Markdown.Paragraph(Markdown.Bold(x.text))
bold(other) = other
# Utilities.
function indent(str::AbstractString, indent = 4)
buf = IOBuffer()
for line in split(str, '\n')
println(buf, " "^indent, line)
end
takebuf_string(buf)
end
function validdocstr(markdown::Base.Markdown.MD)
content = markdown.content
!isempty(content) && isa(first(content), Base.Markdown.Code)
end
validdocstr(other) = false
end
GenStdLib.translate(["manual", "stdlib", "devdocs"])