Initial support for about(::Module)

Co-authored-by: Sasha Demin <demin@lix.polytechnique.fr>
This commit is contained in:
TEC 2024-05-02 02:19:24 +08:00
parent 4967bcd976
commit 966b91b7c0
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
4 changed files with 170 additions and 5 deletions

View File

@ -8,7 +8,14 @@ InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
JuliaSyntaxHighlighting = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011"
StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
[weakdeps]
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
[extensions]
PkgExt = "Pkg"
[compat]
InteractiveUtils = "1.11.0"
JuliaSyntaxHighlighting = "1.11.0"
Pkg = "1.11.0"
StyledStrings = "1.11.0"

78
ext/PkgExt.jl Normal file
View File

@ -0,0 +1,78 @@
module PkgExt
using Pkg
using StyledStrings
import About: about_pkg, columnlist
function about_pkg(io::IO, pkg::Base.PkgId, mod::Module)
isnothing(pkgversion(mod)) ||
print(io, styled" Version {about_module:$(pkgversion(mod))}")
srcdir = pkgdir(mod)
if isnothing(srcdir)
print(io, styled" (builtin)")
else
srcdir = Base.fixup_stdlib_path(srcdir)
srcdir = something(Base.find_source_file(srcdir), srcdir)
srcdir = contractuser(srcdir)
print(io, styled" loaded from {light,underline:$srcdir}")
end
println(io)
isnothing(srcdir) && return
manifest_file = Pkg.Types.manifestfile_path(pkgdir(mod))
thedeps = if !isnothing(manifest_file) && isfile(manifest_file)
Pkg.Types.read_manifest(manifest_file).deps
else
Pkg.dependencies()
end
directdeps = if haskey(thedeps, pkg.uuid)
listdeps(thedeps, pkg.uuid)
else
collect(keys(thedeps))
end
isempty(directdeps) && return
depstrs = map(directdeps) do dep
nindirect = length(alldeps(thedeps, dep))
if nindirect > 0
styled"$(thedeps[dep].name) {shadow:(+$nindirect)}"
else
styled"$(thedeps[dep].name)"
end
end
indirect_depcount = length(alldeps(thedeps, pkg.uuid) directdeps) - length(depstrs)
indirect_info = if indirect_depcount > 0
styled" {shadow:(+$indirect_depcount indirectly)}"
else styled"" end
println(io, styled"\n{bold:Directly depends on {emphasis:$(length(directdeps))} \
package$(ifelse(length(directdeps) == 1, \"\", \"s\"))}$indirect_info:")
columnlist(io, depstrs)
end
function listdeps(deps::Dict{Base.UUID, Pkg.Types.PackageEntry}, pkg::Base.UUID)
if haskey(deps, pkg)
collect(values(deps[pkg].deps))
else
Base.UUID[]
end
end
function listdeps(deps::Dict{Base.UUID, Pkg.API.PackageInfo}, pkg::Base.UUID)
if haskey(deps, pkg)
collect(values(deps[pkg].dependencies))
else
Base.UUID[]
end
end
function alldeps(deps::Dict{Base.UUID, <:Union{Pkg.Types.PackageEntry, Pkg.API.PackageInfo}}, pkg::Base.UUID)
depcheck = listdeps(deps, pkg)
depcollection = Set{Base.UUID}()
while !isempty(depcheck)
id = popfirst!(depcheck)
id in depcollection && continue
append!(depcheck, listdeps(deps, id))
push!(depcollection, id)
end
collect(depcollection)
end
end

View File

@ -21,6 +21,9 @@ function cpad(s, n::Integer, pad::Union{AbstractString, AbstractChar}=' ', r::Ro
rpad(lpad(s, div(n+textwidth(s), 2, r), pad), n, pad)
end
splural(n::Int) = ifelse(n == 1, "", "s")
splural(c::Vector) = splural(length(c))
function struncate(str::AbstractString, maxwidth::Int, joiner::AbstractString = "", mode::Symbol = :center)
textwidth(str) <= maxwidth && return str
left, right = firstindex(str) - 1, lastindex(str) + 1
@ -41,16 +44,18 @@ end
function columnlist(io::IO, entries::Vector{<:AbstractString};
maxcols::Int=8, maxwidth::Int=last(displaysize(io)),
prefix::AbstractString = S"{emphasis:•} ", spacing::Int=2)
isempty(entries) && return
thecolumns = Vector{eltype(entries)}[]
thecolwidths = Int[]
for ncols in 1:maxcols
columns = Vector{eltype(entries)}[]
for col in Iterators.partition(entries, length(entries) ÷ ncols)
for col in Iterators.partition(entries, div(length(entries), ncols, RoundUp))
push!(columns, collect(col))
end
widths = map.(textwidth, columns)
colwidths = map(maximum, widths)
if sum(colwidths) + ncols * textwidth(prefix) + (1 - ncols) * spacing > maxwidth
layoutwidth = sum(colwidths) + ncols * textwidth(prefix) + (ncols - 1) * spacing
if layoutwidth > maxwidth
break
else
thecolumns, thecolwidths = columns, colwidths
@ -115,6 +120,8 @@ function wraplines(content::Union{Annot, SubString{<:Annot}}, width::Integer = 8
most_recent_break_opportunity = lastwrap + nextbreak
end
i = most_recent_break_opportunity
else
i = nextind(s, most_recent_break_opportunity)
end
push!(lines, content[nextind(s, lastwrap):prevind(s, most_recent_break_opportunity)])
lastwrap = most_recent_break_opportunity

View File

@ -110,6 +110,82 @@ function memorylayout(io::IO, value::T) where {T}
memorylayout(io, T)
end
# ------------------
# Modules
# ------------------
function about(io::IO, mod::Module)
pkg = nothing
for (bpkg, m) in Base.loaded_modules
if m == mod
pkg = bpkg
break
end
end
!isnothing(pkg) && !applicable(about_pkg, io, pkg, mod) &&
Base.require(Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"))
print(io, S"{bold:Module {about_module:$mod}}")
if !isnothing(pkg)
println(io, S" {shadow:[$(something(pkg.uuid, \"no uuid\"))]}")
Base.invokelatest(about_pkg, io, pkg, mod)
else
println(io)
end
function classify(m::Module, name::Symbol)
val = getglobal(mod, name)
order, kind, face, parent = if val isa Module
0, :module, :about_module, val
elseif val isa Function && first(String(name)) == '@'
1, :macro, :julia_macro, parentmodule(val)
elseif val isa Function
2, :function, :julia_funcall, parentmodule(val)
elseif val isa Type
3, :type, :julia_type, if val isa UnionAll || val isa Union
m else parentmodule(val) end
else
4, :value, :julia_identifier, if Base.issingletontype(typeof(val))
parentmodule(typeof(m))
else
m
end
end
while parentmodule(parent) (parent, Main)
parent = parentmodule(parent)
end
(; name, str = S"{code,$face:$name}", kind, parent, order)
end
classify(m::Module, names::Vector{Symbol}) =
sort(map(Base.Fix1(classify, m), names), by=x->x.order)
allnames = classify(mod, names(mod))
exports = similar(allnames, 0)
reexports = similar(allnames, 0)
publics = similar(allnames, 0)
for exp in allnames
if exp.parent === mod && Base.isexported(mod, exp.name)
push!(exports, exp)
elseif exp.parent === mod && Base.ispublic(mod, exp.name)
push!(publics, exp)
elseif exp.parent !== mod
push!(reexports, exp)
end
end
if !isempty(exports)
println(io, S"\n{bold:Exports {emphasis:$(length(exports))} name$(splural(exports)):}")
columnlist(io, map(x->x.str, exports))
end
if !isempty(reexports)
parents = join(sort(map(p->S"{about_module:$p}", unique(map(x->x.parent, reexports)))), ", ")
println(io, S"\n{bold:Re-exports {emphasis:$(length(reexports))} name$(splural(reexports))} (from $parents){bold::}")
columnlist(io, map(x->x.str, reexports))
end
if !isempty(publics)
println(io, S"\n{bold:Public API ({emphasis:$(length(publics))} name$(splural(publics))):}")
columnlist(io, map(x->x.str, publics))
end
end
function about_pkg end # Implemented in `../ext/PkgExt.jl`
# ------------------
# Numeric types
# ------------------
@ -375,7 +451,4 @@ function elaboration(io::IO, char::Char)
end
end
end
end
# TODO struct