-
Notifications
You must be signed in to change notification settings - Fork 30
/
utilities.jl
340 lines (281 loc) · 8.78 KB
/
utilities.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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
#
# Utilities.
#
#
# Method grouping.
#
"""
$(:SIGNATURES)
Group all methods of function `func` with type signatures `typesig` in module `modname`.
Keyword argument `exact = true` matches signatures "exactly" with `==` rather than `<:`.
# Examples
```julia
groups = methodgroups(f, Union{Tuple{Any}, Tuple{Any, Integer}}, Main; exact = false)
```
"""
function methodgroups(func, typesig, modname; exact = true)
# Group methods by file and line number.
local methods = getmethods(func, typesig)
local groups = groupby(Tuple{Symbol, Int}, Vector{Method}, methods) do m
(m.file, m.line), m
end
# Filter out methods from other modules and with non-matching signatures.
local typesigs = alltypesigs(typesig)
local results = Vector{Method}[]
for (key, group) in groups
filter!(group) do m
local ismod = m.module == modname
exact ? (ismod && Base.rewrap_unionall(Base.tuple_type_tail(m.sig), m.sig) in typesigs) : ismod
end
isempty(group) || push!(results, group)
end
# Sort the groups by file and line.
sort!(results, lt = comparemethods, by = first)
return results
end
"""
$(:SIGNATURES)
Compare methods `a` and `b` by file and line number.
"""
function comparemethods(a::Method, b::Method)
comp = a.file < b.file ? -1 : a.file > b.file ? 1 : 0
comp == 0 ? a.line < b.line : comp < 0
end
if isdefined(Base, :UnionAll)
uniontypes(T) = uniontypes!(Any[], T)
function uniontypes!(out, T)
if isa(T, Union)
push!(out, T.a)
uniontypes!(out, T.b)
else
push!(out, T)
end
return out
end
gettype(T::UnionAll) = gettype(T.body)
else
uniontypes(T) = collect(T.types)
end
gettype(other) = other
"""
$(:SIGNATURES)
A helper method for [`getmethods`](@ref) that collects methods in `results`.
"""
function getmethods!(results, f, sig)
if sig == Union{}
append!(results, methods(f))
elseif isa(sig, Union)
for each in uniontypes(sig)
getmethods!(results, f, each)
end
elseif isa(sig, UnionAll)
getmethods!(results, f, Base.unwrap_unionall(sig))
else
append!(results, methods(f, sig))
end
return results
end
"""
$(:SIGNATURES)
Collect and return all methods of function `f` matching signature `sig`.
This is similar to `methods(f, sig)`, but handles type signatures found in `DocStr` objects
more consistently that `methods`.
"""
getmethods(f, sig) = unique(getmethods!(Method[], f, sig))
"""
$(:SIGNATURES)
Is the type `t` a `bitstype`?
"""
isbitstype(t::ANY) = isleaftype(t) && sizeof(t) > 0 && isbits(t)
"""
$(:SIGNATURES)
Is the type `t` an `abstract` type?
"""
isabstracttype(t::ANY) = isa(t, DataType) && getfield(t, :abstract)
"""
$(:SIGNATURES)
Returns a `Vector` of the `Tuple` types contained in `sig`.
"""
function alltypesigs(sig)::Vector{Any}
if sig == Union{}
Any[]
elseif isa(sig, Union)
uniontypes(sig)
elseif isa(sig, UnionAll)
Base.rewrap_unionall.(uniontypes(Base.unwrap_unionall(sig)), sig)
else
Any[sig]
end
end
"""
$(:SIGNATURES)
A helper method for [`groupby`](@ref) that uses a pre-allocated `groups` `Dict`.
"""
function groupby!(f, groups, data)
for each in data
key, value = f(each)
push!(get!(groups, key, []), value)
end
return sort!(collect(groups), by = first)
end
"""
$(:SIGNATURES)
Group `data` using function `f` where key type is specified by `K` and group type by `V`.
The function `f` takes a single argument, an element of `data`, and should return a 2-tuple
of `(computed_key, element)`. See the example below for details.
# Examples
```julia
groupby(Int, Vector{Int}, collect(1:10)) do num
mod(num, 3), num
end
```
"""
groupby(f, K, V, data) = groupby!(f, Dict{K, V}(), data)
"""
$(:SIGNATURES)
Remove the `Pkg.dir` part of a file `path` if it exists.
"""
function cleanpath(path::AbstractString)
local pkgdir = joinpath(Pkg.dir(), "")
return startswith(path, pkgdir) ? first(split(path, pkgdir; keep = false)) : path
end
"""
$(:SIGNATURES)
Parse all docstrings defined within a module `mod`.
"""
function parsedocs(mod::Module)
for (binding, multidoc) in Docs.meta(mod)
for (typesig, docstr) in multidoc.docs
Docs.parsedoc(docstr)
end
end
end
"""
$(:SIGNATURES)
Print a simplified representation of a method signature to `buffer`. Some of these
simplifications include:
* no `TypeVar`s;
* no types;
* no keyword default values;
* `?` printed where `#unused#` arguments are found.
# Examples
```julia
f(x; a = 1, b...) = x
sig = printmethod(Docs.Binding(Main, :f), f, first(methods(f)))
```
"""
function printmethod(buffer::IOBuffer, binding::Docs.Binding, func, method::Method)
# TODO: print qualified?
print(buffer, binding.var)
print(buffer, "(")
join(buffer, arguments(method), ", ")
local kws = keywords(func, method)
if !isempty(kws)
print(buffer, "; ")
join(buffer, kws, ", ")
end
print(buffer, ")")
return buffer
end
printmethod(b, f, m) = String(take!(printmethod(IOBuffer(), b, f, m)))
get_method_source(m::Method) = Base.uncompressed_ast(m)
nargs(m::Method) = m.nargs
"""
$(:SIGNATURES)
Returns the list of keywords for a particular method `m` of a function `func`.
# Examples
```julia
f(x; a = 1, b...) = x
kws = keywords(f, first(methods(f)))
```
"""
function keywords(func, m::Method)
local table = methods(func).mt
if isdefined(table, :kwsorter)
local kwsorter = table.kwsorter
local signature = Base.tuple_type_cons(Vector{Any}, m.sig)
if method_exists(kwsorter, signature)
local method = which(kwsorter, signature)
local template = get_method_source(method)
# `.slotnames` is a `Vector{Any}`. Convert it to the right type.
local args = map(Symbol, template.slotnames[(nargs(method) + 1):end])
# Only return the usable symbols, not ones that aren't identifiers.
filter!(arg -> !contains(string(arg), "#"), args)
# Keywords *may* not be sorted correctly. We move the vararg one to the end.
local index = findfirst(arg -> endswith(string(arg), "..."), args)
if index > 0
args[index], args[end] = args[end], args[index]
end
return args
end
end
return Symbol[]
end
"""
$(:SIGNATURES)
Returns the list of arguments for a particular method `m`.
# Examples
```julia
f(x; a = 1, b...) = x
args = arguments(first(methods(f)))
```
"""
function arguments(m::Method)
local template = get_method_source(m)
if isdefined(template, :slotnames)
local args = map(template.slotnames[1:nargs(m)]) do arg
arg === Symbol("#unused#") ? "?" : arg
end
return filter(arg -> arg !== Symbol("#self#"), args)
end
return Symbol[]
end
#
# Source URLs.
#
# Based on code from https://github.com/JuliaLang/julia/blob/master/base/methodshow.jl.
#
# Customised to handle URLs on travis since the directory is not a Git repo and we must
# instead rely on `TRAVIS_REPO_SLUG` to get the remote repo.
#
"""
$(:SIGNATURES)
Get the URL (file and line number) where a method `m` is defined.
Note that this is based on the implementation of `Base.url`, but handles URLs correctly
on TravisCI as well.
"""
url(m::Method) = url(m.module, string(m.file), m.line)
function url(mod::Module, file::AbstractString, line::Integer)
file = Compat.Sys.iswindows() ? replace(file, '\\', '/') : file
if Base.inbase(mod) && !isabspath(file)
local base = "https://github.com/JuliaLang/julia/tree"
if isempty(Base.GIT_VERSION_INFO.commit)
return "$base/v$VERSION/base/$file#L$line"
else
local commit = Base.GIT_VERSION_INFO.commit
return "$base/$commit/base/$file#L$line"
end
else
if isfile(file)
local d = dirname(file)
return LibGit2.with(LibGit2.GitRepoExt(d)) do repo
LibGit2.with(LibGit2.GitConfig(repo)) do cfg
local u = LibGit2.get(cfg, "remote.origin.url", "")
local m = match(LibGit2.GITHUB_REGEX, u)
u = m === nothing ? get(ENV, "TRAVIS_REPO_SLUG", "") : m.captures[1]
local commit = string(LibGit2.head_oid(repo))
local root = LibGit2.path(repo)
if startswith(file, root) || startswith(realpath(file), root)
local base = "https://github.com/$u/tree"
local filename = file[(length(root) + 1):end]
return "$base/$commit/$filename#L$line"
else
return ""
end
end
end
else
return ""
end
end
end