forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenstdlib.jl
318 lines (292 loc) · 9.55 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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
using .Markdown
using Base.Markdown: MD
cd(dirname(@__FILE__))
isop(func) = ismatch(r"[^\w@!.]|^!$", func)
ident(mod, x) = "$mod.$(isop(x) ? "(:($x))" : x)"
all_docs = ObjectIdDict()
function add_all_docs(it)
for (k, v) in it
all_docs[v] = k
end
end
function add_all_docs(it, k)
for (_, v) in it
all_docs[v] = k
end
end
function sym_exported(obj::ANY, m, exports)
if isa(obj, Union{Function,DataType,Module})
return symbol(string(obj)) in exports
elseif isa(obj, IntrinsicFunction)
# Only example seems to be cglobal for now
return true
elseif isa(obj, Docs.Binding)
return (obj::Docs.Binding).var in exports
else
return false
end
end
function add_all_docs_meta(m::Module,meta)
exports = names(m)
for (obj, d) in meta
isexported = sym_exported(obj, m, exports)
if isa(d, Base.Docs.FuncDoc) || isa(d, Base.Docs.TypeDoc)
add_all_docs(d.meta, (obj, isexported))
else
all_docs[d] = (obj, isexported)
end
end
end
mod_added = ObjectIdDict()
function add_all_docs_mod(m::Module)
mod_added[m] = m
try
add_all_docs_meta(m,Docs.meta(m))
end
for name in names(m)
try
sub_m = getfield(m,name)
isa(sub_m, Module) || continue
if !(sub_m in keys(mod_added))
add_all_docs_mod(sub_m)
end
end
end
end
add_all_docs_mod(Base)
# Most of the keywords are not functions and they are easy to check by hand
# add_all_docs(Docs.keywords)
println("Collect $(length(all_docs)) Docs")
function find_docs(v)
docs = []
for mod in keys(mod_added)
try
meta = Docs.meta(mod)[v]
if isa(meta, Base.Docs.FuncDoc) || isa(meta, Base.Docs.TypeDoc)
append!(docs, collect(values(meta.meta)))
else
push!(docs, meta)
end
end
end
if isempty(docs)
error("Cannot find doc for $v")
end
return docs
end
function getdoc(mod, x)
try
x = unescape_string(x)
if symbol(x) in keys(Docs.keywords)
return Any[Docs.keywords[symbol(x)]]
end
if x[1] != '@'
v = eval(parse(ident(mod, x)))
isa(v, Colon) && (v = Base.colon)
return find_docs(v)
else
for m in [eval(parse(mod)); collect(keys(mod_added));]
try
v = Docs.Binding(m, symbol(x))
return find_docs(v)
end
end
error("Cannot find doc for $x")
end
catch e
println(e)
warn("Mod $mod $x")
end
[]
end
flat_content(md) = md
flat_content(xs::Vector) = reduce((xs, x) -> vcat(xs,flat_content(x)), [], xs)
flat_content(md::MD) = flat_content(md.content)
flatten(md::MD) = MD(flat_content(md))
isrst(md) =
length(flatten(md).content) == 1 &&
isa(flatten(md).content[1], Markdown.Code) &&
flatten(md).content[1].language == "rst"
function tryrst(md, remove_decl)
try
if remove_decl && isa(md.content[1], Markdown.Code) && md.content[1].language == ""
shift!(md.content)
end
return Markdown.rst(md)
catch e
warn("Error converting docstring:")
# display(md)
println(e)
return
end
end
torst(md,remove_decl) = isrst(md) ? flatten(md).content[1].code : tryrst(md, remove_decl)
function split_decl_rst(md, decl)
if isrst(md)
rst_text = flatten(md).content[1].code
ls = split(rst_text, "\n")
body_start = 1
if startswith(ls[1], ".. ") && !endswith(ls[1], "::")
decl = ".. function:: " * replace(ls[1], r"^.. *", "")
body_start += 1
while startswith(ls[body_start], " ")
decl *= replace(ls[body_start], r"^ *", "\n ")
body_start += 1
end
while ls[body_start] == ""
body_start += 1
end
return decl, join(ls[body_start:end], "\n")
end
return decl, rst_text
else
if isa(md.content[1], Markdown.Code) && md.content[1].language == ""
decl = ".. function:: " * replace(shift!(md.content).code, "\n",
"\n ")
end
return decl, Markdown.rst(md)
end
end
# Disable by default since it is hard to eliminate false positives
const warn_doc_between_func = "JULIA_WARN_DOC_BETWEEN_FUNC" in keys(ENV)
function translate(file)
@assert(isfile(file))
ls = split(readall(file), "\n")[1:end-1]
doccing = false
func = nothing
mod = "Base"
modidx = -1
open(file, "w+") do io
has_func_doc = false
missing_func_doc = []
cur_func = ""
function warn_missing_func_doc()
if (warn_doc_between_func && has_func_doc &&
!isempty(missing_func_doc))
doc_str = join(missing_func_doc, "\n")
warn("Possible missing document for `$cur_func` in `$file`:")
info(doc_str)
end
missing_func_doc = []
end
function start_new_section()
warn_missing_func_doc()
has_func_doc = false
end
function start_func_doc(func_line)
warn_missing_func_doc()
cur_func = func_line
has_func_doc = true
end
function push_non_func_line(l)
has_func_doc || return
if (startswith(l, ".. data:: ") ||
startswith(l, ".. _") ||
ismatch(r"^[A-Z][a-z]* implemented", l))
start_new_section()
elseif (startswith(l, "----") ||
startswith(l, "====") ||
startswith(l, "~~~~"))
if !isempty(missing_func_doc)
pop!(missing_func_doc)
start_new_section()
end
end
has_func_doc || return
push!(missing_func_doc, l)
end
for (i,l) in enumerate(ls)
if ismatch(r"^\.\. (current)?module::", l)
start_new_section()
mod = match(r"^\.\. (current)?module:: ([\w\.]+)", l).captures[2]
modidx = i
println(io, l)
elseif startswith(l, ".. function::")
func = match(r"^\.\. function:: (@?[^\(\s\{]+)(.*)", l)
func == nothing && (warn("bad function $l"); continue)
funcname = func.captures[1]
full = funcname * func.captures[2]
if !('(' in full || '@' in full)
ex = parse(full)
if isa(ex, Expr)
if (ex.head == :(||) || ex.head == :(&&))
funcname = string(ex.head)
elseif ex.head == :macrocall
funcname = string(ex.args[1])
end
end
end
doc = nothing
for mdoc in getdoc(mod, funcname)
trst = tryrst(mdoc, false)
trst !== nothing || continue
if contains(replace(trst, r"[\n ][\n ]+", " "),
" " * replace(full, r"[\n ][\n ]+", " "))
if doc != nothing
error("duplicate $full $l")
end
doc = mdoc
else
#@show trst full
end
end
if doc == nothing || torst(doc, false) == nothing
info("no docs for $full in $mod")
println(io, l)
doccing = false
start_new_section()
continue
end
delete!(all_docs, doc)
doccing = true
start_func_doc(full)
decl, body = split_decl_rst(doc, l)
println(io, decl)
println(io)
println(io, " .. Docstring generated from Julia source\n")
for l in split(body, "\n")
ismatch(r"^\s*$", l) ? println(io) : println(io, " ", l)
end
isrst(doc) && println(io)
elseif doccing && (ismatch(r"^\s+", l) || ismatch(r"^\s*$", l))
modidx == i-1 && println(io)
else
doccing = false
push_non_func_line(l)
println(io, l)
end
end
end
end
for folder in ["stdlib", "manual", "devdocs"]
println("\nConverting $folder/\n")
for file in readdir("$folder")
translate("$folder/$file")
end
end
exported_missing_count = 0
unexported_missing_count = 0
for (d, v) in all_docs
if length(v) == 2
val,isexported = v
else
val,isexported = v,true
end
isa(val, ObjectIdDict) && continue # No idea what these are
isa(val, Int) && continue # We don't document `0` as a function
if isexported
warn("Exported method missing doc for $val")
exported_missing_count += 1
else
info("Unexported method missing doc for $val")
unexported_missing_count += 1
end
# println(tryrst(d, false))
# # Generate todo list ;-p
# println("- [ ] `$v`")
end
if (exported_missing_count + unexported_missing_count) > 0
println()
warn("Missing $exported_missing_count exported doc strings")
info("Missing $unexported_missing_count unexported doc strings")
end