Skip to content

Commit

Permalink
Initial functionality (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrhill authored Oct 21, 2024
1 parent 4d46306 commit ade23ba
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 10 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 Adrian Hill <gh@adrianhill.de>
Copyright (c) 2024 Adrian Hill <gh@adrianhill.de>, Kristoffer Carlsson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
8 changes: 6 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
name = "MethodURL"
uuid = "461c4225-bb7a-4706-8416-467e5545dbd6"
authors = ["Adrian Hill <gh@adrianhill.de>"]
version = "1.0.0-DEV"
authors = ["Adrian Hill <gh@adrianhill.de>", "Kristoffer Carlsson"]
version = "0.1.0-DEV"

[deps]
RegistryInstances = "2792f1a3-b283-48e8-9a74-f99dce5104f3"

[compat]
RegistryInstances = "0.1"
julia = "1.10"
101 changes: 100 additions & 1 deletion src/MethodURL.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,104 @@
# Based on code by Kristoffer Carlsson:
# /~https://github.com/JuliaLang/julia/issues/47709#issuecomment-2388629772

module MethodURL

# Write your package code here.
using Base: PkgId, UUID, Sys, inbase
using RegistryInstances: reachable_registries, registry_info

export url

function repo_and_path_to_url(repo, version, path, line)
repo = chopsuffix(repo, ".git")
# TODO: Handle more git forges
if startswith(repo, "https://github.com")
# /~https://github.com/owner/Package.jl/blob/v0.1.0/src/foo.jl#L42
return join([repo, "blob", "v" * version, path * "#L$line"], "/")
elseif startswith(repo, "https://gitlab.com")
# https://gitlab.com/owner/Package.jl/-/blob/v0.1.0/src/foo.jl#L42
return join([repo, "-", "blob", "v" * version, path * "#L$line"], "/")
elseif startswith(repo, "https://git.sr.ht")
# https://git.sr.ht/~owner/Package.jl/tree/v0.1.0/item/src/foo.jl#L42
return join([repo, "tree", "v" * version, "item", path * "#L$line"], "/")
else
error("Failed to construct URL for repository $repo.")
end
end

# Find repository in reachable registries by looking up UUID
function repos_package(uuid::UUID)
repos = String[]
for reg in reachable_registries()
entry = get(reg, uuid, nothing)
if entry !== nothing
info = registry_info(entry)
push!(repos, info.repo)
end
end
return repos
end

# Return errors instead of `nothing`
function _uuid(M::Module)
uuid = PkgId(M).uuid
isnothing(uuid) && error("Failed to find UUID of package $M.")
return uuid
end

# Return errors instead of `nothing`
function _pkgdir(M::Module)
dir = pkgdir(M)
isnothing(dir) && error("Failed to find directory of package $M.")
return dir
end

# TODO: is this heuristic sufficient?
instdlib(pkgdir) = contains(pkgdir, Sys.STDLIB)

# TODO: If package is devved use local path
# TODO: If package is added by URL, use that
# TODO: Support monorepos
function url(m::Method)
M = parentmodule(m)
file = String(m.file)
line = m.line

urls = String[]
if inbase(M)
# adapted from /~https://github.com/JuliaLang/julia/blob/8f5b7ca12ad48c6d740e058312fc8cf2bbe67848/base/methodshow.jl#L382-L388
commit = Base.GIT_VERSION_INFO.commit
if isempty(commit)
url = "/~https://github.com/JuliaLang/julia/tree/v$VERSION/base/$file#L$line"
else
url = "/~https://github.com/JuliaLang/julia/tree/$commit/base/$file#L$line"
end
push!(urls, url)
else
uuid = _uuid(M)
repos = repos_package(uuid)
pkgdir = _pkgdir(M)

if isempty(repos) && instdlib(pkgdir) # stdlib package
package, file = match(r"/stdlib/v(?:.*?)/(.*?)/src/(.*)", file).captures
url = "/~https://github.com/JuliaLang/julia/blob/v$VERSION/stdlib/$package/src/$file#L$line"
push!(urls, url)
else # external package
pkg_splitpath = splitpath(pkgdir)
file_splitpath = splitpath(file)
while !isempty(pkg_splitpath) && first(pkg_splitpath) == first(file_splitpath)
popfirst!(pkg_splitpath)
popfirst!(file_splitpath)
end
local_dir = join(file_splitpath, "/")

v = string(pkgversion(M))
for repo in repos
url = repo_and_path_to_url(repo, v, local_dir, line)
push!(urls, url)
end
end
end
return urls
end

end # module
7 changes: 7 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Arxiv = "95bf46a4-16f0-449f-8b01-023b953c38f0"
ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
GPMaxlik = "988d40dc-a58a-4803-bd2c-6d7438fe27fe"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
94 changes: 88 additions & 6 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using MethodURL

# Linting tests
using Test
using JuliaFormatter: JuliaFormatter
using Aqua: Aqua
Expand All @@ -12,32 +14,112 @@ using ExplicitImports:
check_all_qualified_accesses_via_owners,
check_all_qualified_accesses_are_public

# Packages used for testing
using HTTP: request
using InteractiveUtils: @which

# Package to test URLs on
using LinearAlgebra: det
using Statistics: mean
using Plots: Plots # has sub-repositories
using Arxiv: @arXiv_str # hosted on GitLab
using GPMaxlik: gnll # hosted on sourcehut

function url_exists(url)
response = request("GET", url; status_exception=false, redirect=true, retry=true)
if 200 response.status < 400
return true
else
@warn "Failed to request URL" url response.status response
return false
end
end

@testset verbose = true "MethodURL.jl" begin
@testset verbose = true "Linting" begin
@testset "Code formatting (JuliaFormatter.jl)" begin
@testset "JuliaFormatter.jl" begin
@test JuliaFormatter.format(MethodURL; verbose=false, overwrite=false)
end
@testset "Code quality (Aqua.jl)" begin
@testset "Aqua.jl" begin
Aqua.test_all(MethodURL)
end
@testset "Code linting (JET.jl)" begin
@testset "JET.jl" begin
JET.test_package(MethodURL; target_defined_modules=true)
end

@testset "Code imports (ExplicitImports.jl)" begin
@testset "ExplicitImports.jl" begin
@testset "Improper implicit imports" begin
@test isnothing(check_no_implicit_imports(MethodURL))
end
@testset "Improper explicit imports" begin
@test isnothing(check_no_stale_explicit_imports(MethodURL))
@test isnothing(check_all_explicit_imports_via_owners(MethodURL))
@test isnothing(check_all_explicit_imports_are_public(MethodURL))
@test isnothing(
check_all_explicit_imports_are_public(
MethodURL; ignore=(:PkgId, :UUID, :inbase)
),
)
end
@testset "Improper qualified accesses" begin
@test isnothing(check_all_qualified_accesses_via_owners(MethodURL))
@test isnothing(check_no_self_qualified_accesses(MethodURL))
@test isnothing(check_all_qualified_accesses_are_public(MethodURL))
@test isnothing(
check_all_qualified_accesses_are_public(
MethodURL; ignore=(:GIT_VERSION_INFO,)
),
)
end
end
end
@testset verbose = true "URL" begin
@testset "Base" begin
m = @which sqrt(0.0)
u = first(@inferred url(m))
# @test url_exists(u)
end
@testset "Stdlib" begin
@testset "within julialang/julia" begin
m = @which @test true
u = first(@inferred url(m))
# @test url_exists(u)

m = @which det(rand(2, 2))
u = first(@inferred url(m))
# @test url_exists(u)
end
@testset "own repository" begin
m = @which mean(rand(5))
u = first(@inferred url(m))
# @test url_exists(u)
end
end

@testset "External" begin
@testset "GitHub" begin
m = @which Aqua.test_all(MethodURL)
u = first(@inferred url(m))
# @test url_exists(u)
end
@testset "GitHub monorepo" begin
m = first(methods(Plots.RecipesBase.create_kw_body))
u = first(@inferred url(m))
# @test url_exists(u)
end
@testset "GitLab" begin
m = @which arXiv"1234.5678"
u = first(@inferred url(m))
# @test url_exists(u) # no tags in Arxiv.jl
end
@testset "Sourcehut" begin
m = first(methods(gnll))
u = first(@inferred url(m))
# @test url_exists(u) # no tags in GPMaxlik.jl
end
end
# @testset "Local" begin
# m = @which url(@which sqrt(1.0))
# u = first(@inferred url(m))
# # @test url_exists(u)
# end
end
end

0 comments on commit ade23ba

Please sign in to comment.