Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial functionality #1

Merged
merged 6 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>
Copyright (c) 2024 Adrian Hill <[email protected]>, 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 <[email protected]>"]
version = "1.0.0-DEV"
authors = ["Adrian Hill <[email protected]>", "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
Loading