Initial commit

This commit is contained in:
TEC 2023-09-08 18:09:47 +08:00
commit 09f905f4ab
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
22 changed files with 1691 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Manifest.toml

42
Project.toml Normal file
View File

@ -0,0 +1,42 @@
name = "Setup"
uuid = "1e701ad1-9860-423b-8651-7f81d307e3cf"
authors = ["TEC <git@tecosaur.net>"]
version = "0.1.0"
[deps]
BaseDirs = "18cc8868-cbac-4acf-b575-c8ff214dc66f"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
[weakdeps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
Gadfly = "c91e804a-d5a3-530f-b6f0-dfbca275c004"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
JpegTurbo = "b835a17e-a41a-41e7-81f0-2f016b05efe0"
KittyTerminalImages = "b7fa5abe-5c7d-46c6-a1ae-1026d0d509b9"
Netpbm = "f09324ee-3d7c-5217-9330-fc30815ba969"
OhMyREPL = "5fb14364-9ced-5910-84b2-373655c76a03"
PNGFiles = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
QOI = "4b34888f-f399-49d4-9bb3-47ed5cae4e65"
TiffImages = "731e570b-9d59-4bfa-96dc-6df516fadf69"
[extensions]
cairomakie = "CairoMakie"
gadfly = "Gadfly"
hdf5 = "HDF5"
image_jpeg = "JpegTurbo"
image_netpbm = "Netpbm"
image_png = "PNGFiles"
image_qoi = "QOI"
image_tiff = "TiffImages"
ohmyrepl = "OhMyREPL"
prettycolors = "Colors"
[compat]
julia = "1.7"

85
README.org Normal file
View File

@ -0,0 +1,85 @@
#+title: Setup.jl
#+author: TEC
This is my personal Julia configuration. It has grown far beyond my wildest imagination.
* Installation
#+begin_src shell
julia --startup-file=no -e 'using Pkg; Pkg.add(url="https://git.tecosaur.net/tec/Setup.jl.git"); using Setup; Setup.install()'
#+end_src
* Features
** Autoloading
Instead of loading packages whose functionality might be needed at some point,
load them on-demand. Lazy loading for the win!
The following packages are currently supported:
+ The standard library (=Base64=, =CRC32c=, =Dates=, =Distributions=, =Downloads=,
=LinearAlgebra=, =Markdown=, =Mmap=, =Pkg=, =Printf=, =REPL=, =Random=, =SHA=, =Serialisation=,
=Statistics=, =StatsBase=, =TOML=, =Tar=, =Test=, =Threads=, =UUIDs=)
+ =BaseDirs=
+ =BenchmarkTools=
+ =CSV=
+ =Cthulhu=
+ =DataFrames=
+ =DataToolkit=
+ =Debugger=
+ =Distributions=
+ =JET=
+ =JSON3=
+ =Org=
+ =WhyNotEqual=
** Function/Structure inspection
Get a sense of a function or structure with the =about= function/macro.
** Environment tweaks
*** Shorter activation
Add =a= as a shorthand for =activate=
*** Easier temp environments
Add =-t= as a shorthand for =--temp=, with the activate change this means opening a
temporary environment is now a bit quicker:
#+begin_example
pkg> a -t
#+end_example
instead of
#+begin_example
pkg> activate --temp
#+end_example
*** Environment stack
Load and unload named environments with the new =pkg> (un)stack= set of commands.
** Command puns
For more easily running ~Cmd~s, we now have the following syntactic puns.
#+begin_src julia
~ `cmd` # read to string
! `cmd` # print output
!~ `cmd` # read and print output
`a` | `b` # pipe commands
#+end_src
** Package configuration
Thanks to package extensions, it's fairly straightforward to bundle per-package
customisation code. Currently this covers the following:
+ Setting the Makie theme based on the terminal colours when loading =CairoMakie=
+ Pushing the =Gadfly= display
+ Changing the =HDF5= icons
+ Loading =ImageShow= when =JpegTurbo=, =Netpbm=, =PNGFiles=, =QOI=, or =TiffImages= are loaded
+ Load an appropriate =OhMyREPL= theme on startup
+ Add pretty colour show methods for =Color= types

102
ext/cairomakie.jl Normal file
View File

@ -0,0 +1,102 @@
module cairomakie
using Setup
using CairoMakie
import Pkg
import Setup.theme_term
import CairoMakie.Colors.FixedPointNumbers.N0f8
import CairoMakie.Colors.RGB
import CairoMakie.Makie: Theme, RGBAf, Attributes, Axis, Axis3, Legend, Colorbar
function theme_term()
termcolors = Setup.termcolors()
if isempty(termcolors)
return CairoMakie.Makie.current_default_theme()
end
function tocolor(name::Symbol)
rgb = get(termcolors, name, (r=0x00, g=0x00, b=0x00))
RGB(reinterpret(N0f8, rgb.r),
reinterpret(N0f8, rgb.g),
reinterpret(N0f8, rgb.b))
end
bg, fg, red, green, yellow, blue, magenta, cyan,
lbg, lfg, lred, lgreen, lyellow, lblue, lmagenta, lcyan =
tocolor.((:black, :white, :red, :green, :yellow, :blue,
:magenta, :cyan, :light_black, :light_white,
:light_red, :light_green, :light_yellow,
:light_blue, :light_magenta, :light_cyan))
onecolors(α = 1.0) =
RGBAf.([blue, yellow, green, magenta, red, cyan, lblue,
lyellow, lgreen, lmagenta, lred, lcyan],
α)
Theme(
backgroundcolor = bg,
textcolor = fg,
linecolor = fg,
font = :regular,
fonts = Attributes(
bold = "JetBrains Mono Bold",
bold_italic = "JetBrains Mono Bold Italic",
italic = "JetBrains Mono Italic",
regular = "JetBrains Mono Medium"),
palette = merge(Attributes(
color = onecolors(1.0),
patchcolor = onecolors(0.8)
), CairoMakie.Makie.default_palettes),
Axis = (
backgroundcolor = :transparent,
bottomspinecolor = fg,
topspinecolor = fg,
leftspinecolor = fg,
rightspinecolor = fg,
xgridcolor = RGBAf(1, 1, 1, 0.16),
ygridcolor = RGBAf(1, 1, 1, 0.16),
xtickcolor = fg,
ytickcolor = fg),
Legend = (
framecolor = fg,
bgcolor = bg),
Axis3 = (
xgridcolor = RGBAf(1, 1, 1, 0.16),
ygridcolor = RGBAf(1, 1, 1, 0.16),
zgridcolor = RGBAf(1, 1, 1, 0.16),
xspinecolor_1 = fg,
yspinecolor_1 = fg,
zspinecolor_1 = fg,
xspinecolor_2 = fg,
yspinecolor_2 = fg,
zspinecolor_2 = fg,
xspinecolor_3 = fg,
yspinecolor_3 = fg,
zspinecolor_3 = fg,
xticklabelpad = 3,
yticklabelpad = 3,
zticklabelpad = 6,
xtickcolor = fg,
ytickcolor = fg,
ztickcolor = fg),
Colorbar = (
tickcolor = fg,
spinecolor = fg,
topspinecolor = fg,
bottomspinecolor = fg,
leftspinecolor = fg,
rightspinecolor = fg))
end
function __init__()
if isinteractive()
Setup.load_termimage_pkg()
CairoMakie.set_theme!(theme_term())
end
end
end

9
ext/gadfly.jl Normal file
View File

@ -0,0 +1,9 @@
module gadfly
using Gadfly
function __init__()
pushdisplay(Gadfly.GadflyDisplay())
end
end

22
ext/hdf5.jl Normal file
View File

@ -0,0 +1,22 @@
module hdf5
using HDF5
import HDF5._tree_icon
function __init__()
_tree_icon(::Type{HDF5.Attribute}) =
HDF5.SHOW_TREE_ICONS[] ? "\e[33m\e[39m" : "[A]"
_tree_icon(::Type{HDF5.Group}) =
HDF5.SHOW_TREE_ICONS[] ? "\e[34m\e[39m" : "[G]"
_tree_icon(::Type{HDF5.Dataset}) =
HDF5.SHOW_TREE_ICONS[] ? "\e[35m\e[39m" : "[D]"
_tree_icon(::Type{HDF5.Datatype}) =
HDF5.SHOW_TREE_ICONS[] ? "\e[36m\e[39m" : "[T]"
_tree_icon(::Type{HDF5.File}) =
HDF5.SHOW_TREE_ICONS[] ? "\e[34m\e[39m" : "[F]"
_tree_icon(::Type) =
HDF5.SHOW_TREE_ICONS[] ? "\e[31m❓\e[39m" : "[?]"
end
end

3
ext/image_jpeg.jl Normal file
View File

@ -0,0 +1,3 @@
module image_jpeg
include("images.jl")
end

3
ext/image_netpbm.jl Normal file
View File

@ -0,0 +1,3 @@
module image_netpbm
include("images.jl")
end

3
ext/image_png.jl Normal file
View File

@ -0,0 +1,3 @@
module image_png
include("images.jl")
end

3
ext/image_qoi.jl Normal file
View File

@ -0,0 +1,3 @@
module image_qoi
include("images.jl")
end

3
ext/image_tiff.jl Normal file
View File

@ -0,0 +1,3 @@
module image_tiff
include("images.jl")
end

29
ext/images.jl Normal file
View File

@ -0,0 +1,29 @@
# This is loaded by all other `image_*.jl` files
using Setup
const IMAGE_SHOW_PKG, IMAGE_IO_PKG, IMAGE_MAGICK_PKG = splat(Base.PkgId).([
(Base.UUID("4e3cecfd-b093-5904-9786-8bbb286a6a31"), "ImageShow"),
(Base.UUID("82e4d734-157c-48bb-816b-45c225c6df19"), "ImageIO"),
(Base.UUID("6218d12a-5da1-5696-b52f-db25d2ecc6d1"), "ImageMagick")])
function __init__()
if isinteractive()
Setup.load_termimage_pkg()
if !haskey(Base.loaded_modules, IMAGE_SHOW_PKG) &&
!isnothing(Base.find_package(IMAGE_SHOW_PKG.name)) &&
(!isnothing(Base.find_package(IMAGE_IO_PKG.name)) ||
!isnothing(Base.find_package(IMAGE_MAGICK_PKG.name)))
# Don't to this async because it may affect a `display` call
# that's just about to occur.
if !isnothing(Base.find_package(IMAGE_IO_PKG.name))
Base.require(IMAGE_IO_PKG)
elseif !isnothing(Base.find_package(IMAGE_MAGICK_PKG.name))
Base.require(IMAGE_MAGICK_PKG)
else
return
end
Base.require(IMAGE_SHOW_PKG)
end
end
end

28
ext/ohmyrepl.jl Normal file
View File

@ -0,0 +1,28 @@
module ohmyrepl
using Setup
using OhMyREPL
function __init__()
theme = Setup.termtheme()
if theme == :dark
OhMyREPL.colorscheme!("OneDark")
elseif theme == :light
OhMyREPL.colorscheme!("TomorrowDay")
else theme == :unknown
OhMyREPL.colorscheme!("Monokai16")
end
if Setup.TERM[] == "xterm-kitty"
OhMyREPL.input_prompt!(:green) do
print("\e]133;A\e\\")
"julia> "
end
OhMyREPL.output_prompt!(:red) do
print("\e]133;C\e\\")
""
end
end
end
end

58
ext/prettycolors.jl Normal file
View File

@ -0,0 +1,58 @@
module prettycolors
using Colors
using LinearAlgebra # for the Adjoint type
import Base.show
colorcode(rgb::Vector{<:Integer}, background=false) =
string("\e[", if background "48" else "38" end,
";2;", rgb[1], ";", rgb[2], ";", rgb[3], "m")
colorcode(c::Union{RGB, RGB24}, background=false) =
colorcode(reinterpret.([red(c), green(c), blue(c)]), background)
colorcode(c::RGB{Float64}, background=false) =
colorcode(round.(Int, 255 .* [red(c), green(c), blue(c)]), background)
colorcode(c::Colorant, background=false) =
colorcode(convert(RGB24, c), background)
function show(io::IO, ::MIME"text/plain", c::Colorant)
if get(io, :color, false) && get(ENV, "COLORTERM", nothing) == "truecolor"
print(colorcode(c, true), " \e[0m ")
end
show(io, c)
end
function show(io::IO, ::MIME"text/plain", cs::Adjoint{<:Colorant, <:Vector{<:Colorant}})
summary(io, cs)
print(":\n")
if get(io, :color, false) && get(ENV, "COLORTERM", nothing) == "truecolor"
print(" ", join(colorcode.(cs,true), if length(cs) <= 40 " " else " " end),
if length(cs) <= 40 " \e[0m" else " \e[0m" end)
else
Base.print_array(IOContext(io, :SHOWN_SET => cs), cs)
end
end
function show(io::IO, ::MIME"text/plain", cs::Matrix{<:Colorant})
summary(io, cs)
print(":\n")
if get(io, :color, false) && *(size(cs)...) <= 1000 && get(ENV, "COLORTERM", nothing) == "truecolor"
for csrow in eachrow(cs)
println(" ", join(colorcode.(csrow,true),
if size(cs,2) <= 40 " " else " " end),
if size(cs,2) <= 40 " \e[0m" else " \e[0m" end)
end
else
Base.print_array(IOContext(io, :SHOWN_SET => cs), cs)
end
end
function Base.display(c::Colorant)
show(stdout, MIME("text/plain"), c)
print('\n')
end
end

80
src/Setup.jl Normal file
View File

@ -0,0 +1,80 @@
module Setup
import REPL
import Pkg
export about, @about
@static if VERSION >= v"1.8"
TERM::Ref{String} = get(ENV, "TERM", "")
IS_SSH::Ref{Bool} = Ref(false)
else
TERM = get(ENV, "TERM", "")
IS_SSH = Ref(false)
end
include("install.jl")
include("cmdpuns.jl")
include("termsetup.jl")
include("about.jl")
include("pkgstack.jl")
include("autoloads.jl")
using .Autoloads
include("autoload_data.jl")
include("sessions.jl")
using .Sessions
using .PkgStack
function ensurepkg(pkg::String)
if isnothing(Base.find_package(pkg))
@info "Installing $pkg"
Pkg.add(pkg)
end
end
function theme_term end # For cairomakie.jl
function __init__()
IS_SSH[] = haskey(ENV, "SSH_CLIENT") || haskey(ENV, "SSH_TTY")
if isinteractive()
TERM[] = termcode()
Main.Threads.@spawn if TERM[] == "xterm-kitty"
if isnothing(Base.find_package("KittyTerminalImages"))
@info "Installing KittyTerminalImages"
currentproj = Pkg.project().path
try
Pkg.activate()
# Pkg.add("KittyTerminalImages")
finally
Pkg.activate(currentproj)
end
end
termtitle(pwd())
end
if ENV["TERM"] != "dumb"
replsetup()
end
end
end
precompile(termtitle, ())
precompile(replsetup, ())
precompile(_termcolors, ())
precompile(termcolors, ())
precompile(termtheme, ())
precompile(Autoloads.autoload_loadpkg, (Symbol,))
precompile(Autoloads.autoload_scan!, (Expr,))
precompile(Autoloads.autoload_scan!, (Symbol,))
precompile(Autoloads.autoload_scan!, (Any,))
precompile(Autoloads.read_named_envs!, ())
precompile(Autoloads.repl_scan_autoloads!, (Expr,))
precompile(Autoloads.repl_scan_autoloads!, (Nothing,))
end

256
src/about.jl Normal file
View File

@ -0,0 +1,256 @@
using InteractiveUtils
const colorcycle = [:light_blue, :light_green, :light_yellow, :light_magenta]
function humansize(bytes::Integer)
units = ("B", "kB", "MB", "GB")
magnitude = floor(Int, log(1024, 1 + bytes))
if 10 < bytes < 10*1024^magnitude
round(bytes / 1024^magnitude, digits=1)
else
round(Int, bytes / 1024^magnitude)
end, units[1+magnitude]
end
function about(obj::Any)
print(Base.summary(obj))
size, units = humansize(Base.summarysize(obj))
if ismutable(obj)
print(" (mutable)")
end
print(", ", size, units, "\n\n")
printstyled("Fields:\n", bold=true)
memorylayout(obj)
end
function about(type::Type)
if isprimitivetype(type)
print("Primitive ")
elseif isconcretetype(type)
print("Concrete ")
if Base.datatype_haspadding(type)
printstyled("(padded) ", color=:light_black)
end
elseif isabstracttype(type)
print("Abstract ")
end
if Base.issingletontype(type)
print("singleton ")
end
print(Base.summary(type))
print(" defined in ")
printstyled(type.name.module, color=:light_red)
size, units = humansize(Base.summarysize(type))
selfsize, selfunits = try
st = humansize(sizeof(type))
print(", ", st...)
st
catch _; (0, "") end
if (size, units) != (selfsize, selfunits)
print(" referencing ", size, units)
end
println("\n ", join(string.(supertypes(type)), " \e[31m<:\e[m "))
if isstructtype(type) && fieldcount(type) > 0
println("\nStruct with \e[1m", fieldcount(type), "\e[m fields:")
if type isa DataType
for (i, (fname, ftype)) in enumerate(zip(fieldnames(type), fieldtypes(type)))
color = colorcycle[i % length(colorcycle) + 1]
print("")
printstyled(fname; color)
printstyled("::", string(ftype), color=:light_black)
print('\n')
end
print('\n')
memorylayout(type)
else
println("",
join(string.(fieldnames(type), "\e[90m::",
fieldtypes(type), "\e[m"),
"\n"))
end
end
end
function structinfo(T::DataType)
map(1:fieldcount(T)) do i
size = Int(if i < fieldcount(T) fieldoffset(T, i+1)
else sizeof(T) end - fieldoffset(T, i))
contentsize=try sizeof(fieldtype(T, i)) catch _ 0 end
(; i, offset=fieldoffset(T, i) |> Int,
size, contentsize,
pointer=contentsize == 0,
name=fieldname(T, i),
type=fieldtype(T, i))
end
end
function memorylayout(type::DataType)
cpad(s, n::Integer, p=" ") = rpad(lpad(s,div(n+textwidth(s),2),p),n,p)
si = structinfo(type)
!isempty(si) || return
memstep = memstep = gcd((getfield.(si, :size), getfield.(si, :contentsize)) |>
Iterators.flatten |> collect)
memscale = max(1, floor(Int, 70/(sizeof(type)/memstep)))
print(' ')
for (; i, size, contentsize, pointer) in si
color = colorcycle[i % length(colorcycle) + 1]
contentwidth = memscale * contentsize÷memstep
printstyled('■'^contentwidth; color)
if contentsize < size
paddwidth = memscale * (size - contentsize)÷memstep
printstyled('■'^paddwidth;
color=if pointer; :cyan else :light_black end)
end
end
print("\n ")
for (; i, size, contentsize, pointer) in si
width = memscale * size÷memstep
color = colorcycle[i % length(colorcycle) + 1]
fsize, funits = humansize(size)
if pointer
printstyled(cpad("*", width); color)
elseif contentsize < size
csize, cunits = humansize(contentsize)
psize, punits = humansize(size - contentsize)
csizestr, psizestr = string(csize, cunits), string(psize, punits)
cwidth = div(width + textwidth(csizestr) - textwidth(psizestr) - 1, 2)
pwidth = width - cwidth
printstyled(lpad(csizestr, cwidth); color)
printstyled(rpad('+' * psizestr, pwidth); color=:light_black)
else
printstyled(cpad(string(fsize, funits), width); color)
end
end
if any(getfield.(si, :pointer))
printstyled("\n\n * = Pointer (8B)", color=:cyan)
end
print('\n')
end
function memorylayout(val::T) where {T}
si = structinfo(T)
!isempty(si) || return memorylayout(T)
rows = Vector{NamedTuple}()
for (; name, size, contentsize, pointer, type) in si
fsize, funits = humansize(size)
fbits = if isprimitivetype(type)
bitstring(getfield(val, name)) * '-'^(8*(size-contentsize))
elseif pointer
"Pointer"
else
""
end
fshow = sprint(show, getfield(val, name), context=:compact => true)
push!(rows, (; fname=string(name), fsize=string(fsize), funits,
fbits, fshow, ftype=string(type)))
end
widths = Dict(c => maximum(length.(getfield.(rows, c)))
for c in fieldnames(typeof(first(rows)))) |>
NamedTuple
for (i, (; fname, fsize, funits, fbits, fshow, ftype)) in enumerate(rows)
color = colorcycle[i % length(colorcycle) + 1]
printstyled(' ', lpad(fname, widths.fname), "::",
rpad(ftype, widths.ftype); color)
printstyled(" ", lpad(fsize, widths.fsize),
rpad(funits, widths.funits), ' '; color)
print(replace(rpad(fbits, widths.fbits), r"0|\-" => s"\e[90m\0\e[m"), ' ')
printstyled(rpad(fshow, widths.fshow); color)
print('\n')
end
print('\n')
memorylayout(T)
end
function about(fn::Function)
source = Main.InteractiveUtils.which(Main, Symbol(fn))
methodmodules = getproperty.(methods(fn).ms, :module)
others = setdiff(methodmodules, [source])
print(Base.summary(fn))
print("\n\nDefined in ")
printstyled(source, color=:light_red)
if length(others) > 0
printstyled("(", sum(Ref(source) .=== methodmodules), ")",
color=:light_black)
print(" extended in ")
for (i, oth) in enumerate(others)
printstyled(oth, color=:light_red)
printstyled("(", sum(Ref(oth) .=== methodmodules), ")",
color=:light_black)
if length(others) == 2 && i == 1
print(" and ")
elseif length(others) > 2 && i < length(others)-1
print(", ")
elseif length(others) > 2 && i == length(others)-1
print(", and ")
end
end
end
print(".\n")
end
function about(fn::Union{Function, Type}, typesig::Type{<:Tuple})
about(Base.unwrap_unionall(fn))
print("\n")
matchedmethods = methods(fn, typesig)
printstyled("Matching methods ($(length(matchedmethods.ms))):\n", bold=true)
for method in matchedmethods.ms
println(" ", sprint(show, method, context=IOContext(stdout)))
end
print('\n')
printstyled("Effects:\n", bold=true)
effects = Base.infer_effects(fn, typesig)
trichar(t::UInt8) =
Dict(Core.Compiler.ALWAYS_TRUE => '✔',
# Core.Compiler.TRISTATE_UNKNOWN => '?',
Core.Compiler.ALWAYS_FALSE => '✗')[t]
trichar(b::Bool) = ifelse(b, '✔', '✗')
tricolor(t::UInt8) =
Dict(Core.Compiler.ALWAYS_TRUE => :green,
# Core.Compiler.TRISTATE_UNKNOWN => :yellow,
Core.Compiler.ALWAYS_FALSE => :red)[t]
tricolor(b::Bool) = ifelse(b, :green, :red)
hedge(t::UInt8) =
Dict(Core.Compiler.ALWAYS_TRUE => "guaranteed to",
# Core.Compiler.TRISTATE_UNKNOWN => "might",
Core.Compiler.ALWAYS_FALSE => "not guaranteed to")[t]
hedge(b::Bool) = ifelse(b, "guaranteed to", "not guaranteed to")
for (effect, description) in
[(:consistent, "return or terminate consistently"),
(:effect_free, "be free from externally semantically visible side effects"),
(:nothrow, "never throw an exception"),
(:terminates, "terminate")]
e = getfield(effects, effect)
print(" "); printstyled(trichar(e), ' ', string(effect), color=tricolor(e))
print(" "); printstyled(hedge(e), ' ', description, color=:light_black);
print('\n')
end
if !effects.nonoverlayed
print(" "); printstyled(trichar(Core.Compiler.ALWAYS_FALSE), " nonoverlayed",
color=tricolor(Core.Compiler.ALWAYS_FALSE))
print(" "); printstyled("methods within this method are not defined in an overlayed method table",
color=:light_black);
print('\n')
end
end
macro about(obj::Symbol, types::Union{Symbol, Expr}...)
if Core.eval(Main, obj) isa Function && length(types) == 1 &&
types[1] == :(())
:(about($obj, Tuple{}))
elseif Core.eval(Main, obj) isa Function && length(types) > 0
:(about($obj, $(Tuple{Core.eval.(Main, types)...})))
else
:(about($obj))
end |> esc
end
macro about(qobj::Union{QuoteNode, Expr}, types::Union{Symbol, Expr}...)
if Core.eval(Main, Core.eval(Main, qobj)) isa Function && length(types) == 1 &&
types[1] == :(())
:(about($(Core.eval(Main, qobj)), Tuple{}))
elseif length(types) > 0
:(about($(Core.eval(Main, qobj)), $(Tuple{Core.eval.(Main, types)...})))
else
:(about($(Core.eval(Main, qobj))))
end |> esc
end

258
src/autoload_data.jl Normal file
View File

@ -0,0 +1,258 @@
foreach(add_autoloads!, [
PkgAutoloads(
:(Base.Threads),
exported = Bindings(
macros = ["@spawn", "@threads"],
callables = [
:Atomic, :Event, :SpinLock, :Threads, :atomic_add!,
:atomic_and!, :atomic_cas!, :atomic_fence, :atomic_max!,
:atomic_min!, :atomic_nand!, :atomic_or!, :atomic_sub!,
:atomic_xchg!, :atomic_xor!, :nthreadpools, :nthreads,
:threadid, :threadpool])),
PkgAutoloads(:Base64, exported = Bindings(callables = [
:Base64DecodePipe, :Base64EncodePipe, :base64decode, :base64encode,
:stringmime])),
PkgAutoloads(:BaseDirs),
PkgAutoloads(
:BenchmarkTools,
exported = Bindings(macros = [
"@ballocated", "@belapsed", "@benchmark", "@benchmarkable",
"@benchmarkset", "@bprofile", "@btime", "@case", "@tagged"])),
PkgAutoloads(:CRC32c, exported = Bindings(callables = [:crc32c])),
PkgAutoloads(:CSV),
PkgAutoloads(:Cthulhu, exported = Bindings(
macros = ["@decend", "@decend_code_typed", "@decend_code_warntype"],
callables = [:ascend, :descend, :descend_code_typed, :descend_code_warntype])),
PkgAutoloads(
:DataFrames,
exported = Bindings(callables = [
:AbstractDataFrame, :All, :AsTable, :Between, :ByRow, :Cols,
:DataFrame, :DataFrameRow, :DataFrames, :GroupedDataFrame,
:InvertedIndex, :InvertedIndices, :Missing, :MissingException,
:Missings, :Not, :PrettyTables, :SubDataFrame, :Tables, :aggregate,
:allcombinations, :allowmissing, :allowmissing!, :antijoin, :by,
:coalesce, :colmetadata, :colmetadata!, :colmetadatakeys,
:columnindex, :combine, :completecases, :crossjoin, :delete!,
:deletecolmetadata!, :deletemetadata!, :disallowmissing,
:disallowmissing!, :dropmissing, :dropmissing!, :emptycolmetadata!,
:emptymetadata!, :emptymissing, :fillcombinations, :flatten,
:groupby, :groupcols, :groupindices, :innerjoin, :insertcols,
:insertcols!, :ismissing, :leftjoin, :leftjoin!, :levels, :mapcols,
:mapcols!, :metadata, :metadata!, :metadatakeys, :missing,
:missings, :ncol, :nonmissingtype, :nonunique, :nrow, :order,
:outerjoin, :passmissing, :proprow, :rename, :rename!, :repeat!,
:rightjoin, :rownumber, :select, :select!, :semijoin, :skipmissings,
:subset, :subset!, :transform, :transform!, :unique!, :unstack,
:valuecols])),
PkgAutoloads(
:DataToolkit,
exported = Bindings(
macros = ["@d_str", "@data_cmd", "@import"],
callables = [:DataSet, :DataToolkit, :dataset, :loadcollection!])),
PkgAutoloads(
:Dates,
exported = Bindings(
macros = ["@dateformat_str"],
callables = [
:Apr, :April, :Aug, :August, :Date, :DateFormat, :DatePeriod,
:DateTime, :Dates, :Day, :Dec, :December, :Feb, :February, :Fri,
:Friday, :Hour, :ISODateFormat, :ISODateTimeFormat,
:ISOTimeFormat, :Jan, :January, :Jul, :July, :Jun, :June, :Mar,
:March, :May, :Microsecond, :Millisecond, :Minute, :Mon,
:Monday, :Month, :Nanosecond, :Nov, :November, :Oct, :October,
:Period, :Quarter, :RFC1123Format, :Sat, :Saturday, :Second,
:Sep, :September, :Sun, :Sunday, :Thu, :Thursday, :Time,
:TimePeriod, :TimeType, :TimeZone, :Tue, :Tuesday, :UTC, :Wed,
:Wednesday, :Week, :Year, :adjust, :canonicalize,
:datetime2julian, :datetime2rata, :datetime2unix, :day,
:dayabbr, :dayname, :dayofmonth, :dayofquarter, :dayofweek,
:dayofweekofmonth, :dayofyear, :daysinmonth, :daysinyear,
:daysofweekinmonth, :firstdayofmonth, :firstdayofquarter,
:firstdayofweek, :firstdayofyear, :hour, :isleapyear,
:julian2datetime, :lastdayofmonth, :lastdayofquarter,
:lastdayofweek, :lastdayofyear, :microsecond, :millisecond,
:minute, :month, :monthabbr, :monthday, :monthname, :nanosecond,
:now, :quarterofyear, :rata2datetime, :second, :today, :tofirst,
:tolast, :tonext, :toprev, :unix2datetime, :week, :year,
:yearmonth, :yearmonthday])),
PkgAutoloads(:Debugger, exported = Bindings(macros = ["@enter", "@run"])),
PkgAutoloads(
:Distributed,
exported = Bindings(
macros = ["@distributed", "@everywhere", "@fetch", "@fetchfrom", "@spawnat"],
callables = [
:AbstractWorkerPool, :CachingPool, :ClusterManager, :Future,
:ProcessExitedException, :RemoteChannel, :RemoteException,
:WorkerConfig, :WorkerPool, :addprocs, :channel_from_id,
:check_same_host, :clear!, :cluster_cookie,
:default_worker_pool, :init_worker, :interrupt, :launch,
:manage, :myid, :nprocs, :nworkers, :pmap, :process_messages,
:procs, :remote, :remote_do, :remotecall, :remotecall_fetch,
:remotecall_wait, :remoteref_id, :rmprocs, :start_worker,
:worker_id_from_socket, :workers])),
PkgAutoloads(
:Distributions,
exported = Bindings(callables = [
:AbstractMixtureModel, :AbstractMvNormal, :Arcsine,
:ArrayLikeVariate, :Bernoulli, :BernoulliLogit, :Beta,
:BetaBinomial, :BetaPrime, :Binomial, :Biweight, :Categorical,
:Cauchy, :Chernoff, :Chi, :Chisq, :CholeskyVariate, :Continuous,
:ContinuousDistribution, :ContinuousMatrixDistribution,
:ContinuousMultivariateDistribution,
:ContinuousUnivariateDistribution, :Cosine, :DiagNormal,
:DiagNormalCanon, :Dirac, :Dirichlet, :DirichletMultinomial,
:Discrete, :DiscreteDistribution, :DiscreteMatrixDistribution,
:DiscreteMultivariateDistribution, :DiscreteNonParametric,
:DiscreteUniform, :DiscreteUnivariateDistribution, :Distribution,
:DoubleExponential, :EdgeworthMean, :EdgeworthSum, :EdgeworthZ,
:Epanechnikov, :Erlang, :Estimator, :Exponential, :FDist,
:FisherNoncentralHypergeometric, :Frechet, :FullNormal,
:FullNormalCanon, :Gamma, :GeneralizedExtremeValue,
:GeneralizedPareto, :Geometric, :Gumbel, :Hypergeometric,
:InverseGamma, :InverseGaussian, :InverseWishart, :IsoNormal,
:IsoNormalCanon, :JohnsonSU, :JointOrderStatistics, :KSDist,
:KSOneSided, :Kolmogorov, :Kumaraswamy, :LKJ, :LKJCholesky,
:Laplace, :Levy, :Lindley, :LocationScale, :LogNormal, :LogUniform,
:Logistic, :LogitNormal, :MLEstimator, :MatrixBeta,
:MatrixDistribution, :MatrixFDist, :MatrixNormal, :MatrixReshaped,
:MatrixTDist, :Matrixvariate, :MixtureModel, :Multinomial,
:Multivariate, :MultivariateDistribution, :MultivariateMixture,
:MultivariateNormal, :MvLogNormal, :MvNormal, :MvNormalCanon,
:MvNormalKnownCov, :MvTDist, :NegativeBinomial,
:NonMatrixDistribution, :NoncentralBeta, :NoncentralChisq,
:NoncentralF, :NoncentralHypergeometric, :NoncentralT, :Normal,
:NormalCanon, :NormalInverseGaussian, :OrderStatistic,
:PGeneralizedGaussian, :Pareto, :Poisson, :PoissonBinomial,
:Product, :QQPair, :Rayleigh, :RealInterval, :Rician, :Sampleable,
:Semicircle, :Skellam, :SkewNormal, :SkewedExponentialPower,
:Soliton, :StudentizedRange, :SufficientStats, :SymTriangularDist,
:TDist, :TriangularDist, :Triweight, :Truncated, :TruncatedNormal,
:Uniform, :Univariate, :UnivariateDistribution, :UnivariateGMM,
:UnivariateMixture, :ValueSupport, :VariateForm, :VonMises,
:VonMisesFisher, :WalleniusNoncentralHypergeometric, :Weibull,
:Wishart, :ZeroMeanDiagNormal, :ZeroMeanDiagNormalCanon,
:ZeroMeanFullNormal, :ZeroMeanFullNormalCanon, :ZeroMeanIsoNormal,
:ZeroMeanIsoNormalCanon, :canonform, :ccdf, :cdf, :censored, :cf,
:cgf, :circvar, :component, :components, :componentwise_logpdf,
:componentwise_pdf, :concentration, :convolve, :cquantile,
:estimate, :expected_logdet, :failprob, :fit_mle, :gradlogpdf,
:hasfinitesupport, :insupport, :invcov, :invlogccdf, :invlogcdf,
:invscale, :isbounded, :isleptokurtic, :islowerbounded,
:ismesokurtic, :isplatykurtic, :isprobvec, :isupperbounded,
:location, :location!, :logccdf, :logcdf, :logdetcov, :logdiffcdf,
:logpdf, :logpdf!, :meandir, :meanform, :meanlogx, :mgf,
:ncategories, :ncomponents, :nsamples, :ntrials, :partype, :pdf,
:pdfsquaredL2norm, :probs, :probval, :product_distribution,
:qqbuild, :rate, :sampler, :scale, :scale!, :shape, :sqmahal,
:sqmahal!, :stdlogx, :succprob, :suffstats, :support, :truncated,
:varlogx])),
PkgAutoloads(
:Downloads,
exported = Bindings(callables = [
:Downloader, :RequestError, :Response, :download, :request])),
PkgAutoloads(:JET, exported = Bindings(
macros = ["@report_call", "@report_opt", "@test_call", "@test_opt"],
callables = [:AnyFrameModule, :LastFrameModule, :report_call,
:report_file, :report_opt, :report_package, :report_text,
:test_call, :test_file, :test_opt, :test_package,
:test_text, :watch_file])),
PkgAutoloads(:JSON3),
PkgAutoloads(
:LinearAlgebra,
exported = Bindings(callables = [
:Adjoint, :BLAS, :Bidiagonal, :BunchKaufman, :Cholesky,
:CholeskyPivoted, :ColumnNorm, :Diagonal, :Eigen, :Factorization,
:GeneralizedEigen, :GeneralizedSVD, :GeneralizedSchur, :Hermitian,
:Hessenberg, :I, :LAPACK, :LAPACKException, :LDLt, :LQ, :LU,
:LinearAlgebra, :LowerTriangular, :NoPivot, :PosDefException, :QR,
:QRPivoted, :RankDeficientException, :RowMaximum, :RowNonZero, :SVD,
:Schur, :SingularException, :SymTridiagonal, :Symmetric, :Transpose,
:Tridiagonal, :UniformScaling, :UnitLowerTriangular,
:UnitUpperTriangular, :UpperHessenberg, :UpperTriangular,
:ZeroPivotException, :adjoint!, :axpby!, :axpy!, :bunchkaufman,
:bunchkaufman!, :cholesky, :cholesky!, :cond, :condskeel,
:copy_transpose!, :copyto!, :cross, :det, :diag, :diagind, :diagm,
:dot, :eigen, :eigen!, :eigmax, :eigmin, :eigvals, :eigvals!,
:eigvecs, :factorize, :givens, :hessenberg, :hessenberg!, :isdiag,
:ishermitian, :isposdef, :isposdef!, :issuccess, :issymmetric,
:istril, :istriu, :kron, :kron!, :ldiv!, :ldlt, :ldlt!, :lmul!,
:logabsdet, :logdet, :lowrankdowndate, :lowrankdowndate!,
:lowrankupdate, :lowrankupdate!, :lq, :lq!, :lu, :lu!, :lyap, :mul!,
:norm, :normalize, :normalize!, :nullspace, :opnorm, :ordschur,
:ordschur!, :pinv, :qr, :qr!, :rank, :rdiv!, :reflect!, :rmul!,
:rotate!, :schur, :schur!, :svd, :svd!, :svdvals, :svdvals!,
:sylvester, :tr, :transpose, :transpose!, :tril, :tril!, :triu,
:triu!, :×, :⋅])),
PkgAutoloads(:Markdown, exported =
Bindings(macros = ["@doc_str", "@md_str"],
callables = [:html, :latex])),
PkgAutoloads(:Mmap, exported = Bindings(callables = [:mmap])),
PkgAutoloads(:Org, exported = Bindings(
macros = ["@org_str"],
callables = [:Org, :OrgDoc, :org, :parsetree, :tableofcontents, :term])),
PkgAutoloads(:Pkg, exported = Bindings(macros = ["@pkg_str"])),
PkgAutoloads(:Printf, exported = Bindings(macros = ["@printf"])),
PkgAutoloads(:Random, exported = Bindings(callables = [
:AbstractRNG, :MersenneTwister, :Random, :RandomDevice, :TaskLocalRNG,
:Xoshiro, :bitrand, :rand!, :randcycle, :randcycle!, :randexp,
:randexp!, :randn!, :randperm, :randperm!, :randstring, :randsubseq,
:randsubseq!, :shuffle, :shuffle!])),
PkgAutoloads(:REPL, exported = Bindings(callables =
[:AbstractREPL, :BasicREPL, :LineEditREPL, :StreamREPL])),
PkgAutoloads(:SHA, exported = Bindings(callables =
[:HMAC_CTX, :SHA1_CTX, :SHA224_CTX, :SHA256_CTX, :SHA2_224_CTX,
:SHA2_256_CTX, :SHA2_384_CTX, :SHA2_512_CTX, :SHA384_CTX, :SHA3_224_CTX,
:SHA3_256_CTX, :SHA3_384_CTX, :SHA3_512_CTX, :SHA512_CTX, :digest!,
:hmac_sha1, :hmac_sha224, :hmac_sha256, :hmac_sha2_224, :hmac_sha2_256,
:hmac_sha2_384, :hmac_sha2_512, :hmac_sha384, :hmac_sha3_224,
:hmac_sha3_256, :hmac_sha3_384, :hmac_sha3_512, :hmac_sha512, :sha1,
:sha224, :sha256, :sha2_224, :sha2_256, :sha2_384, :sha2_512, :sha384,
:sha3_224, :sha3_256, :sha3_384, :sha3_512, :sha512, :update!])),
PkgAutoloads(:Serialization, exported = Bindings(callables = [
:AbstractSerializer, :Serializer, :deserialize, :serialize])),
PkgAutoloads(
:Statistics,
exported = Bindings(callables = [
:cor, :cov, :mean, :mean!, :median, :median!, :middle, :quantile,
:quantile!, :std, :stdm, :var, :varm])),
PkgAutoloads(
:StatsBase,
exported = Bindings(callables = [
:AbstractDataTransform, :AbstractHistogram, :AbstractWeights,
:AnalyticWeights, :CoefTable, :ConvergenceException,
:CovarianceEstimator, :CronbachAlpha, :ECDF, :FrequencyWeights,
:Histogram, :L1dist, :L2dist, :Linfdist, :ProbabilityWeights,
:SimpleCovariance, :UnitRangeTransform, :UnitWeights, :Weights,
:ZScoreTransform, :addcounts!, :autocor, :autocor!, :autocov,
:autocov!, :aweights, :competerank, :cor2cov, :corkendall,
:corspearman, :counteq, :countmap, :countne, :counts, :cov2cor,
:cronbachalpha, :crosscor, :crosscor!, :crosscov, :crosscov!,
:crossentropy, :cumulant, :denserank, :ecdf, :entropy, :eweights,
:fweights, :genmean, :genvar, :geomean, :gkldiv, :harmmean,
:indexmap, :indicatormat, :inverse_rle, :iqr, :kldivergence,
:kurtosis, :levelsmap, :mad, :mad!, :maxad, :mean_and_cov,
:mean_and_std, :mean_and_var, :meanad, :midpoints, :mode,
:model_response, :modes, :moment, :msd, :norepeats, :nquantile,
:ordinalrank, :pacf, :pacf!, :partialcor, :percentile,
:percentilerank, :proportionmap, :proportions, :psnr, :pweights,
:quantilerank, :renyientropy, :rle, :rmsd, :sample, :sample!,
:samplepair, :scattermat, :scattermat_zm, :scattermatm, :sem,
:skewness, :span, :sqL2dist, :standardize, :summarystats, :tiedrank,
:totalvar, :trim, :trim!, :trimvar, :uweights, :variation, :winsor,
:winsor!, :wmedian, :wquantile, :wsample, :wsample!, :wsum, :wsum!,
:zscore, :zscore!])),
PkgAutoloads(:TOML),
PkgAutoloads(:Tar),
PkgAutoloads(:Test, exported =
Bindings(macros = ["@inferred", "@test", "@test_broken",
"@test_deprecated", "@test_logs", "@test_nowarn",
"@test_skip", "@test_throws", "@test_warn",
"@testset"],
callables = [:GenericArray, :GenericDict, :GenericOrder,
:GenericSet, :GenericString, :LogRecord,
:TestLogger, :TestSetException,
:detect_ambiguities, :detect_unbound_args])),
PkgAutoloads(:UUIDs, exported = Bindings(callables = [
:uuid1, :uuid4, :uuid5, :uuid_version])),
PkgAutoloads(:WhyNotEqual, exported = Bindings(callables = [:whynot])),
])

270
src/autoloads.jl Normal file
View File

@ -0,0 +1,270 @@
module Autoloads
using TOML
using REPL
export Bindings, AutoDot, PkgAutoloads, add_autoloads!
function __init__()
if isinteractive()
read_named_envs!()
repl_apply_autoloads!()
end
end
struct Bindings
macros::Vector{Symbol}
callables::Vector{Symbol}
variables::Vector{Symbol}
end
struct AutoDot end
Bindings(; macros::Vector{<:Union{Symbol, String}}=Symbol[],
callables::Vector{Symbol}=Symbol[], variables=Symbol[]) =
Bindings(Symbol.(macros), callables, variables)
struct PkgAutoloads
pkg::Union{Symbol, Expr}
exported::Bindings
unexported::Union{Bindings, AutoDot}
partial::Bool
end
PkgAutoloads(pkg::Union{Symbol, Expr};
exported::Bindings = Bindings(Symbol[], Symbol[], Symbol[]),
unexported::Union{Bindings, AutoDot} = AutoDot(),
partial::Bool = false) =
PkgAutoloads(pkg, exported, unexported, partial)
function PkgAutoloads(mod::Module)
hasparent(u::Union, m::Module) =
hasparent(u.a, m) && hasparent(u.b, m)
hasparent(x::Any, m::Module) =
parentmodule(x) === m
macros = Symbol[]
callables = Symbol[]
variables = Symbol[]
for symb in names(mod)
obj = getglobal(mod, symb)
if !hasparent(obj, mod) || obj === mod
elseif startswith(String(symb), '@')
push!(macros, symb)
elseif obj isa Function || obj isa Type
push!(callables, symb)
else
push!(variables, symb)
end
end
PkgAutoloads(nameof(mod),
exported = Bindings(
macros, callables, variables))
end
const AUTOLOADS =
(sources = Dict{Symbol, PkgAutoloads}(),
pkgs = Dict{Symbol, Expr}(),
exported = (
macros = Dict{Symbol, Symbol}(),
callables = Dict{Symbol, Symbol}(),
variables = Dict{Symbol, Symbol}()),
bindings = (
macros = Dict{Symbol, Set{Symbol}}(),
callables = Dict{Symbol, Set{Symbol}}(),
variables = Dict{Symbol, Set{Symbol}}()),
ondotaccess = Set{Symbol}(),
loaded = Set{Symbol}(),
partloaded = Dict{Symbol, Set{Symbol}}())
function add_autoloads!(autos::PkgAutoloads)
!haskey(AUTOLOADS.sources, autos.pkg) ||
error("Autoloads for $(autos.pkg) have already been registered")
pkgsym, pkg = if autos.pkg isa Symbol
autos.pkg, Expr(:., autos.pkg)
elseif autos.pkg isa Expr &&
Meta.isexpr(autos.pkg, :(.)) &&
length(autos.pkg.args) == 2 &&
autos.pkg.args[1] isa Symbol &&
autos.pkg.args[2] isa QuoteNode &&
autos.pkg.args[2].value isa Symbol
autos.pkg.args[2].value,
Expr(:., autos.pkg.args[1], autos.pkg.args[2].value)
end
AUTOLOADS.sources[pkgsym] = autos
AUTOLOADS.pkgs[pkgsym] = pkg
autos.partial && (AUTOLOADS.partloaded[pkgsym] = Set{Symbol}())
AUTOLOADS.exported.variables[pkgsym] = pkgsym
for category in (:macros, :callables, :variables)
autodot = autos.unexported isa AutoDot
bindings = autodot || Set(getfield(autos.unexported, category))
slots = getfield(AUTOLOADS.exported, category)
for binding in getfield(autos.exported, category)
if haskey(slots, binding)
@warn "An autload for $(String(category)[1:end-1]) $binding already exists"
else
slots[binding] = pkgsym
end
autodot || push!(bindings, binding)
end
if !autodot && !isempty(bindings)
getfield(AUTOLOADS.bindings, category)[pkgsym] = bindings
end
end
end
const NAMED_ENVS = Vector{Pair{String, Vector{Symbol}}}()
function read_named_envs!()
empty!(NAMED_ENVS)
for depot in Base.DEPOT_PATH
envdir = joinpath(depot, "environments")
isdir(envdir) || continue
for env in readdir(envdir)
if !isnothing(match(r"^__", env))
elseif !isnothing(match(r"^v\d+\.\d+$", env))
elseif isfile(joinpath(envdir, env, "Project.toml"))
proj = open(TOML.parse, joinpath(envdir, env, "Project.toml"))
pkgs = get(proj, "deps", Dict{String, Any}()) |>
keys |> collect |> sort .|> Symbol
push!(NAMED_ENVS, '@' * env => pkgs)
end
end
end
end
function find_named_env(pkg::Symbol)
for (env, pkgs) in NAMED_ENVS
if pkg in pkgs
return env
end
end
end
function autoload_loadpkg(name::Symbol, thing::Union{Symbol, Nothing}=nothing)
if name AUTOLOADS.loaded && haskey(AUTOLOADS.pkgs, name)
AUTOLOADS.sources[name].partial && thing in AUTOLOADS.partloaded[name] &&
return
pkg = AUTOLOADS.pkgs[name]
if length(pkg.args) == 1 && isnothing(Base.find_package(String(first(pkg.args))))
named_env = find_named_env(name)
if !isnothing(named_env)
autoload_loadpkg_namedenv(name, named_env, thing)
else
@warn "Cannot autoload $name, as it is not availible in the current environment stack"
end
return
end
try
if !isnothing(thing) && AUTOLOADS.sources[name].partial
Core.eval(Main, Expr(:import, Expr(:., pkg.args..., thing)))
push!(AUTOLOADS.partloaded[name], thing)
else
Core.eval(Main, Expr(:using, pkg))
push!(AUTOLOADS.loaded, name)
end
nothing
catch err
@warn "Failed to automatically load $name" err
end
end
end
function autoload_loadpkg_namedenv(name::Symbol, env::String, thing::Union{Symbol, Nothing}=nothing)
pkg = AUTOLOADS.pkgs[name]
if Base.active_project() == Base.load_path_expand("@v#.#")
pushfirst!(LOAD_PATH, env)
try
if !isnothing(thing) && AUTOLOADS.sources[name].partial
Core.eval(Main, Expr(:import, Expr(:., pkg.args..., thing)))
push!(AUTOLOADS.partloaded[name], thing)
else
Core.eval(Main, Expr(:using, pkg))
push!(AUTOLOADS.loaded, name)
end
finally
popfirst!(LOAD_PATH)
end
else
@warn "Cannot autoload $name, but it is availible in the $env environment"
end
end
function autoload_scan!(ast::Expr, localbindings::Vector{Symbol}=Symbol[])
if Meta.isexpr(ast, :macrocall)
pkg = if first(ast.args) isa Symbol && first(ast.args) localbindings
get(AUTOLOADS.exported.macros, first(ast.args), nothing)
elseif Meta.isexpr(first(ast.args), :(.)) &&
length(first(ast.args).args) == 2 &&
(root = first(ast.args).args[1]) isa Symbol &&
first(ast.args).args[2] isa QuoteNode &&
(child = first(ast.args).args[2].value) isa Symbol &&
haskey(AUTOLOADS.bindings.macros, root) &&
haskey(AUTOLOADS.bindings.macros[root], child)
AUTOLOADS.bindings.macros[root]
end
if !isnothing(pkg) autoload_loadpkg(pkg, first(ast.args)) end
elseif Meta.isexpr(ast, :call)
pkg = if first(ast.args) isa Symbol && first(ast.args) localbindings
get(AUTOLOADS.exported.callables, first(ast.args), nothing)
elseif Meta.isexpr(first(ast.args), :(.)) &&
(root = first(ast.args).args[1]) isa Symbol &&
root AUTOLOADS.ondotaccess
root
elseif Meta.isexpr(first(ast.args), :(.)) &&
length(first(ast.args).args) == 2 &&
(root = first(ast.args).args[1]) isa Symbol &&
first(ast.args).args[2] isa QuoteNode &&
(child = first(ast.args).args[2].value) isa Symbol &&
haskey(AUTOLOADS.bindings.callables, root) &&
child AUTOLOADS.bindings.callables[root]
root
end
isnothing(pkg) || autoload_loadpkg(pkg, first(ast.args))
elseif Meta.isexpr(ast, :(.)) &&
length(ast.args) == 2 &&
(root = ast.args[1]) isa Symbol &&
ast.args[2] isa QuoteNode &&
(child = ast.args[2].value) isa Symbol &&
(root AUTOLOADS.ondotaccess ||
(haskey(AUTOLOADS.bindings.variables, root) &&
child AUTOLOADS.bindings.variables[root]) ||
(haskey(AUTOLOADS.bindings.callables, root) &&
child AUTOLOADS.bindings.callables[root]))
autoload_loadpkg(root)
elseif ast.head (:using, :import, :quote)
return
elseif Meta.isexpr(ast, :(=)) && first(ast.args) isa Symbol
push!(localbindings, first(ast.args))
end
for arg in ast.args
autoload_scan!(arg, localbindings)
end
end
function autoload_scan!(var::Symbol, localbindings::Vector{Symbol}=Symbol[])
if var localbindings && var names(Main)
pkg = @something(get(AUTOLOADS.exported.variables, var, nothing),
get(AUTOLOADS.exported.callables, var, nothing),
Some(nothing))
if !isnothing(pkg) autoload_loadpkg(pkg, var) end
end
end
autoload_scan!(::Any, ::Vector{Symbol}=Symbol[]) = nothing
function repl_scan_autoloads!(ast::Expr)
@static if VERSION < v"1.9"
autoload_scan!(ast)
else
if Base.active_module() == Main
autoload_scan!(ast)
end
end
ast
end
repl_scan_autoloads!(val::Any) = val
repl_apply_autoloads!() =
pushfirst!(REPL.repl_ast_transforms, repl_scan_autoloads!)
end

71
src/cmdpuns.jl Normal file
View File

@ -0,0 +1,71 @@
import Base.!, Base.~
"""
~(cmd::AbstractCmd)
Run `cmd` and return the output as a string.
```julia-repl
julia> ~ `ls /home`
"doe"
```
"""
function ~(c::Base.AbstractCmd)
strip(read(c, String), '\n')
end
"""
!(cmd::AbstractCmd)
Run `cmd` and print the output to stdout.
```julia-repl
julia> ! `ls /home`
doe
```
This can also be combined with `~ cmd` to print and return
the output by using `!~ cmd`.
```julia-repl
julia> !~ `ls /home`
doe
"doe"
```
"""
function !(c::Base.AbstractCmd)
println(~c)
end
function !(o::SubString{String})
println(o)
o
end
struct PipeCmds <: Base.AbstractCmd
cmds::Vector{<:Base.AbstractCmd}
PipeCmds(left::Base.AbstractCmd, right::Base.AbstractCmd) = new([left, right])
PipeCmds(left::PipeCmds, right::Base.AbstractCmd) = new([left.cmds; right])
PipeCmds(left::Base.AbstractCmd, right::PipeCmds) = new([left; right.cmds])
end
import Base.|
"""
cmd1::AbstractCmd | cmd2::AbstractCmd
Create a pipleine from `cmd1` to `cmd2`.
```julia-repl
julia> `cat .zshenv` | `wc -l`
PipeCmds(Cmd[`cat .zshenv`, `wc -l`])
When `PipeCmds` are passed to `!` or `~`, the commands contained are
expanded inside a `pipeline` call.
```
"""
function (|)(left::Base.AbstractCmd, right::Base.AbstractCmd)
PipeCmds(left, right)
end
function ~(c::PipeCmds)
strip(read(pipeline(c.cmds...), String), '\n')
end

79
src/install.jl Normal file
View File

@ -0,0 +1,79 @@
const SETUP_GIT_URL = "https://git.tecosaur.net/tec/Startup.jl"
const STARTUP_MANAGED_MESSAGE =
"# !! Managed by the Setup package !!"
const STARTUP_CONTENT = """
$STARTUP_MANAGED_MESSAGE
if VERSION < v"1.7"
@warn "Setup disabled as Julia \$VERSION < 1.7 is unsupported"
else
let pkg_id = Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg")
Pkg = Base.loaded_modules[pkg_id]
isnothing(Base.find_package("Setup")) &&
Pkg.develop(path=joinpath(@__DIR__, "Setup"))
end
using Setup
Setup.ensurepkg("Revise")
@async @eval using Revise
end
"""
function _install_paths()
config = joinpath(first(Base.DEPOT_PATH), "config")
startup = joinpath(config, "startup.jl")
setup = joinpath(config, "Setup")
(; config, startup, setup)
end
function install()
(; config, startup, setup) = _install_paths()
isdir(config) || mkpath(config)
if isfile(startup)
if readline(startup) == STARTUP_MANAGED_MESSAGE
isdir(setup) && update()
else
@info "Moving original startup file to $(startup).old"
mv(startup, startup * ".old")
end
end
write(startup, STARTUP_CONTENT)
if isdir(setup) && !isdir(joinpath(setup, ".git"))
@warn "Setup folder exists, but it is not a git repository. Re-creating..."
rm(setup, recursive=true)
end
if !isdir(setup)
success(`git clone $SETUP_GIT_URL $setup`) ||
@error "Failed to clone $SETUP_GIT_URL"
end
end
function uninstall()
(; startup, setup) = _install_paths()
isfile(startup) && readline(startup) != STARTUP_MANAGED_MESSAGE &&
rm(startup)
isfile(startup * ".old") && !isfile(startup) && mv(startup * ".old", startup)
if isdir(joinpath(setup, ".git")) && !isempty(read(Cmd(`git status --porcelain=v1`, dir=setup)))
@warn "Unstaged changes in $(setup)! Manually fix this and try again."
elseif isdir(setup)
rm(setup, recursive=true, force=true)
end
end
function update()
(; startup, setup) = _install_paths()
if readline(startup) != STARTUP_MANAGED_MESSAGE
@warn "Startup.jl is not managed by Setup, re-installing"
return install()
else
write(startup, STARTUP_CONTENT)
end
if !ispath(setup) || !isdir(joinpath(setup, ".git"))
ispath(setup) || @warn "Setup missing, re-installing"
return install()
end
success(Cmd(`git pull`, dir=setup)) ||
@error "Failed to update Setup"
end

133
src/pkgstack.jl Normal file
View File

@ -0,0 +1,133 @@
module PkgStack
import Pkg
import Markdown: @md_str
function stack(envs)
if isempty(envs)
printstyled(" The current stack:\n", bold=true)
println.(" " .* LOAD_PATH)
else
for env in envs
if env LOAD_PATH
push!(LOAD_PATH, env)
end
end
end
end
const STACK_SPEC = Pkg.REPLMode.CommandSpec(
name = "stack",
api = stack,
help = md"""
stack envs...
Stack another environment.
""",
description = "Stack another environment",
completions = Pkg.REPLMode.complete_activate,
should_splat = false,
arg_count = 0 => Inf)
function unstack(envs)
if isempty(envs)
printstyled(" The current stack:\n", bold=true)
println.(" " .* LOAD_PATH)
else
deleteat!(LOAD_PATH, sort(filter(!isnothing, indexin(envs, LOAD_PATH))))
end
end
const UNSTACK_SPEC = Pkg.REPLMode.CommandSpec(
name = "unstack",
api = unstack,
help = md"""
unstack envs...
Unstack a previously stacked environment.
""",
description = "Unstack an environment",
completions = (_, partial, _, _) ->
filter(p -> startswith(p, partial), LOAD_PATH),
should_splat = false,
arg_count = 0 => Inf)
function environments()
envs = String[]
for depot in Base.DEPOT_PATH
envdir = joinpath(depot, "environments")
isdir(envdir) || continue
for env in readdir(envdir)
if !isnothing(match(r"^__", env))
elseif !isnothing(match(r"^v\d+\.\d+$", env))
else
push!(envs, '@' * env)
end
end
end
envs = Base.DEFAULT_LOAD_PATH LOAD_PATH envs
for env in envs
if env in LOAD_PATH
print(" ", env)
else
printstyled(" ", env, color=:light_black)
if env in Base.DEFAULT_LOAD_PATH
printstyled(" (unloaded)", color=:light_red)
end
end
if env == "@"
printstyled(" [current environment]", color=:light_black)
elseif env == "@v#.#"
printstyled(" [global environment]", color=:light_black)
elseif env == "@stdlib"
printstyled(" [standard library]", color=:light_black)
elseif env in LOAD_PATH
printstyled(" (loaded)", color=:green)
end
print('\n')
end
end
const ENVS_SPEC = Pkg.REPLMode.CommandSpec(
name = "environments",
short_name = "envs",
api = environments,
help = md"""
environments|envs
List all known named environments.
""",
description = "List all known named environments",
arg_count = 0 => 0)
const SPECS = Dict(
"stack" => STACK_SPEC,
"unstack" => UNSTACK_SPEC,
"environments" => ENVS_SPEC,
"envs" => ENVS_SPEC)
function __init__()
# add the commands to the repl
activate = Pkg.REPLMode.SPECS["package"]["activate"]
let temp = Pkg.REPLMode.OptionSpec("temp", "t", :temp => true, false)
activate.option_specs["temp"] = temp
activate.option_specs["t"] = temp
end
activate_modified = Pkg.REPLMode.CommandSpec(
activate.canonical_name,
"a", # Modified entry, short name
activate.api,
activate.should_splat,
activate.argument_spec,
activate.option_specs,
activate.completions,
activate.description,
activate.help)
SPECS["activate"] = activate_modified
SPECS["a"] = activate_modified
Pkg.REPLMode.SPECS["package"] = merge(Pkg.REPLMode.SPECS["package"], SPECS)
# update the help with the new commands
copy!(Pkg.REPLMode.help.content, Pkg.REPLMode.gen_help().content)
end
end

153
src/termsetup.jl Normal file
View File

@ -0,0 +1,153 @@
import Base.cd
"""
termtitle(dir)
Set the terminal title suffix to `dir`.
The home directory is replaced with "~", and trailing slash removed.
"""
function termtitle(dir)
if ENV["TERM"] != "dumb"
print("\e]0;Julia ● " *
replace(replace(dir, homedir() => "~"), r"/$" => "") *
"\a")
end
end
function replsetup()
atreplinit() do repl
if !isdefined(repl, :interface)
repl.interface = REPL.setup_interface(repl)
end
if Main.InteractiveUtils.editor().exec[1] == "e"
Main.InteractiveUtils.define_editor(
r"\be", wait=false) do cmd, path, line
`$cmd +$line $path`
end
Main.InteractiveUtils.define_editor(
r"\be\b.*\s(-w|--wait|-t|--tty)", wait=true) do cmd, path, line
`$cmd +$line $path`
end
end
if get(ENV, "INSIDE_EMACS", nothing) != "vterm"
print("\e[5 q")
end
@static if VERSION >= v"1.9"
# Reset title every prompt
repl.interface.modes[1].prompt = () ->
(termtitle(pwd()); REPL.contextual_prompt(repl, REPL.JULIA_PROMPT)())
end
end
@eval function Base.cd(dir::AbstractString)
err = ccall(:uv_chdir, Cint, (Cstring,), dir)
err < 0 && Base.uv_error("cd($(repr(dir)))", err)
termtitle(dir)
return nothing
end
end
clearterm() = print("\e[2J")
function termcode()
REPL.Terminals.raw!(REPL.TerminalMenus.terminal, true)
print("\eP+q544e\e\\")
output = @task readuntil(stdin, "\e\\")
schedule(output)
Timer(0.05) do _
istaskdone(output) || Base.throwto(output, InterruptException())
end
value = try
fetch(output)
catch
""
end
REPL.Terminals.raw!(REPL.TerminalMenus.terminal, false)
String(hex2bytes(last(split(value, '='))))
end
const TERM_COLORS = Ref(Dict{Symbol, @NamedTuple{r::UInt8, g::UInt8, b::UInt8}}())
function termcolors(; refresh::Bool=false)
!refresh && !isempty(TERM_COLORS[]) && return TERM_COLORS[]
REPL.Terminals.raw!(REPL.TerminalMenus.terminal, true)
output = @task _termcolors()
schedule(output)
Timer(0.5) do _
istaskdone(output) || Base.throwto(output, InterruptException())
end
colors = try
fetch(output)
catch end
REPL.Terminals.raw!(REPL.TerminalMenus.terminal, false)
if !isnothing(colors)
TERM_COLORS[] = colors
end
TERM_COLORS[]
end
function termtheme(; refresh::Bool=false)
colors = termcolors(; refresh)
white = get(colors, :white, (r=0x80, g=0x80, b=0x80))
black = get(colors, :black, (r=0x80, g=0x80, b=0x80))
fglevel, bglevel = sum(values(white)), sum(values(black))
if fglevel > bglevel
:dark
elseif fglevel < bglevel
:light
else
:unknown
end
end
function _termcolors()
RGBTuple = NamedTuple{(:r, :g, :b), NTuple{3, UInt8}}
colors = Dict{Symbol, RGBTuple}()
function readcolor(io::IO, n::Integer)
if read(io, Char) == '\e'
entry = readuntil(io, "\e\\")
m = match(r"^]4;(\d+);rgb:([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)$", entry)
if !isnothing(m) && m.captures[1] == string(n)
r = parse(UInt8, m.captures[2][1:2], base=16)
g = parse(UInt8, m.captures[3][1:2], base=16)
b = parse(UInt8, m.captures[4][1:2], base=16)
RGBTuple((r, g, b))
end
end
end
colornames = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :white,
:light_black, :light_red, :light_green, :light_yellow,
:light_blue, :light_magenta, :light_cyan, :light_white]
for (index, color) in enumerate(colornames)
print(stdout, "\e]4;$(index-1);?\e\\")
flush(stdout)
rgb = readcolor(stdin, index - 1)
if !isnothing(rgb)
colors[color] = rgb
end
end
colors
end
const TERM_IMAGE_PACKAGES = let pkg(uuid, name) = Base.PkgId(Base.UUID(uuid), name)
kitty = (pkg("b7fa5abe-5c7d-46c6-a1ae-1026d0d509b9", "KittyTerminalImages"),
:(KittyTerminalImages.pushKittyDisplay!()))
# TODO: sixel
Dict("xterm-kitty" => kitty)
end
const ATTEMPTED_TERMIMAGE_LOAD = Ref(false)
function load_termimage_pkg()
ATTEMPTED_TERMIMAGE_LOAD[] && return
pkg, pkgsetup = get(TERM_IMAGE_PACKAGES, Setup.TERM[], (nothing, nothing))
if !isnothing(pkg) && !haskey(Base.loaded_modules, pkg)
if !isnothing(Base.find_package(pkg.name))
ATTEMPTED_TERMIMAGE_LOAD[] = true
# Don't to this async because it may affect a `display` call
# that's just about to occur.
Core.eval(Main, :(import $(Symbol(pkg.name)); $pkgsetup))
else
@info "Consider installing $(pkg.name) for better in-terminal image previews"
end
end
end