-
Notifications
You must be signed in to change notification settings - Fork 11
Implement an initial API #2
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
eede4fc
Implement `definition(method, String)` and add internal data
timholy 8d00f9f
Implement `pkgfiles`
timholy e8242ab
Add callback for looking up methods that have not yet populated cache
timholy 51dfd33
Add .travis.yml
timholy f2d1eec
Add a `show` method for PkgFiles and update the README
timholy 9b9f5cd
Rework API to be around name, uuid rather than unexported Base.PkgId
timholy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| language: julia | ||
|
|
||
| os: | ||
| - linux | ||
| - osx | ||
|
|
||
| julia: | ||
| - 1.0 | ||
| - 1.1 | ||
| - nightly | ||
|
|
||
| branches: | ||
| only: | ||
| - master | ||
| - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ | ||
|
|
||
| notifications: | ||
| email: false | ||
|
|
||
| script: | ||
| - export JULIA_PROJECT="" | ||
| - julia --project -e 'using Pkg; Pkg.build(); Pkg.test();' | ||
|
|
||
| after_success: | ||
| - julia -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,109 @@ | ||
| module CodeTracking | ||
|
|
||
| export whereis | ||
| using Base: PkgId | ||
| using Core: LineInfoNode | ||
| using UUIDs | ||
|
|
||
| # This is just a stub implementation for now | ||
| export PkgFiles | ||
| export whereis, definition, pkgfiles | ||
|
|
||
| include("data.jl") | ||
| include("utils.jl") | ||
|
|
||
| """ | ||
| filepath, line = whereis(method::Method) | ||
| Return the file and line of the definition of `method`. `line` | ||
| is the first line of the method's body. | ||
| """ | ||
| function whereis(method::Method) | ||
| file, line = String(method.file), method.line | ||
| lin = get(method_locations, method.sig, nothing) | ||
| if lin === nothing | ||
| file, line = String(method.file), method.line | ||
| else | ||
| file, line = fileline(lin) | ||
| end | ||
| if !isabspath(file) | ||
| # This is a Base method | ||
| # This is a Base or Core method | ||
| file = Base.find_source_file(file) | ||
| end | ||
| return normpath(file), line | ||
| end | ||
|
|
||
| """ | ||
| src = definition(method::Method, String) | ||
| Return a string with the code that defines `method`. | ||
| Note this may not be terribly useful for methods that are defined inside `@eval` statements; | ||
| see [`definition(method::Method, Expr)`](@ref) instead. | ||
| """ | ||
| function definition(method::Method, ::Type{String}) | ||
| file, line = whereis(method) | ||
| src = read(file, String) | ||
| eol = isequal('\n') | ||
| linestarts = Int[] | ||
| istart = 0 | ||
| for i = 1:line-1 | ||
| push!(linestarts, istart+1) | ||
| istart = findnext(eol, src, istart+1) | ||
| end | ||
| ex, iend = Meta.parse(src, istart) | ||
| if isfuncexpr(ex) | ||
| return src[istart+1:iend-1] | ||
| end | ||
| # The function declaration was presumably on a previous line | ||
| lineindex = lastindex(linestarts) | ||
| while !isfuncexpr(ex) | ||
| istart = linestarts[lineindex] | ||
| ex, iend = Meta.parse(src, istart) | ||
| end | ||
| return src[istart:iend-1] | ||
| end | ||
|
|
||
| """ | ||
| ex = definition(method::Method, Expr) | ||
| ex = definition(method::Method) | ||
| Return an expression that defines `method`. | ||
| """ | ||
| function definition(method::Method, ::Type{Expr}) | ||
| def = get(method_definitions, method.sig, nothing) | ||
| if def === nothing | ||
| f = method_lookup_callback[] | ||
| if f !== nothing | ||
| Base.invokelatest(f, method) | ||
| end | ||
| def = get(method_definitions, method.sig, nothing) | ||
| end | ||
| return def === nothing ? nothing : copy(def) | ||
| end | ||
|
|
||
| definition(method::Method) = definition(method, Expr) | ||
|
|
||
| """ | ||
| info = pkgfiles(name::AbstractString) | ||
| info = pkgfiles(name::AbstractString, uuid::UUID) | ||
| Return a [`PkgFiles`](@ref) structure with information about the files that define the package | ||
| specified by `name` and `uuid`. | ||
| Returns `nothing` if this package has not been loaded. | ||
| """ | ||
| pkgfiles(name::AbstractString, uuid::UUID) = pkgfiles(PkgId(uuid, name)) | ||
| function pkgfiles(name::AbstractString) | ||
| project = Base.active_project() | ||
| uuid = Base.project_deps_get(project, name) | ||
| uuid == false && error("no package ", name, " recognized") | ||
| return pkgfiles(name, uuid) | ||
| end | ||
| pkgfiles(id::PkgId) = get(_pkgfiles, id, nothing) | ||
|
|
||
| """ | ||
| info = pkgfiles(mod::Module) | ||
| Return a [`PkgFiles`](@ref) structure with information about the files that were loaded to | ||
| define the package that defined `mod`. | ||
| """ | ||
| pkgfiles(mod::Module) = pkgfiles(PkgId(mod)) | ||
|
|
||
| end # module | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # The variables here get populated by Revise.jl. | ||
|
|
||
| """ | ||
| PkgFiles encodes information about the current location of a package. | ||
| Fields: | ||
| - `id`: the `PkgId` of the package | ||
| - `basedir`: the current base directory of the package | ||
| - `files`: a list of files (relative path to `basedir`) that define the package. | ||
|
|
||
| Note that `basedir` may be subsequently updated by Pkg operations such as `add` and `dev`. | ||
| """ | ||
| mutable struct PkgFiles | ||
| id::PkgId | ||
| basedir::String | ||
| files::Vector{String} | ||
| end | ||
|
|
||
| PkgFiles(id::PkgId, path::AbstractString) = PkgFiles(id, path, String[]) | ||
| PkgFiles(id::PkgId, ::Nothing) = PkgFiles(id, "") | ||
| PkgFiles(id::PkgId) = PkgFiles(id, normpath(basepath(id))) | ||
| PkgFiles(id::PkgId, files::AbstractVector{<:AbstractString}) = | ||
| PkgFiles(id, normpath(basepath(id)), files) | ||
|
|
||
| # Abstraction interface | ||
| Base.PkgId(info::PkgFiles) = info.id | ||
| srcfiles(info::PkgFiles) = info.files | ||
| basedir(info::PkgFiles) = info.basedir | ||
|
|
||
| function Base.show(io::IO, info::PkgFiles) | ||
| println(io, "PkgFiles(", info.id, "):") | ||
| println(io, " basedir: ", info.basedir) | ||
| print(io, " files: ") | ||
| show(io, info.files) | ||
| end | ||
|
|
||
| const method_locations = IdDict{Type,LineInfoNode}() | ||
|
|
||
| const method_definitions = IdDict{Type,Expr}() | ||
|
|
||
| const _pkgfiles = Dict{PkgId,PkgFiles}() | ||
|
|
||
| const method_lookup_callback = Ref{Any}(nothing) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| function isfuncexpr(ex) | ||
| ex.head == :function && return true | ||
| if ex.head == :(=) | ||
| a = ex.args[1] | ||
| if isa(a, Expr) | ||
| while a.head == :where | ||
| a = a.args[1] | ||
| isa(a, Expr) || return false | ||
| end | ||
| a.head == :call && return true | ||
| end | ||
| end | ||
| return false | ||
| end | ||
|
|
||
| fileline(lin::LineInfoNode) = String(lin.file), lin.line | ||
|
|
||
| function basepath(id::PkgId) | ||
| id.name ∈ ("Main", "Base", "Core") && return "" | ||
| loc = Base.locate_package(id) | ||
| loc === nothing && return "" | ||
| return dirname(dirname(loc)) | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,35 @@ | ||
| using CodeTracking | ||
| using Test | ||
| # Note: ColorTypes needs to be installed, but note the absence of `using` | ||
|
|
||
| include("script.jl") | ||
|
|
||
| @testset "CodeTracking.jl" begin | ||
| # Write your own tests here. | ||
| m = first(methods(f1)) | ||
| file, line = whereis(m) | ||
| @test file == normpath(joinpath(@__DIR__, "script.jl")) | ||
| src = definition(m, String) | ||
| @test src == """ | ||
| function f1(x, y) | ||
| return x + y | ||
| end | ||
| """ | ||
|
|
||
| m = first(methods(f2)) | ||
| src = definition(m, String) | ||
| @test src == """ | ||
| f2(x, y) = x + y | ||
| """ | ||
|
|
||
| info = PkgFiles(Base.PkgId(CodeTracking)) | ||
| @test Base.PkgId(info) === info.id | ||
| @test CodeTracking.basedir(info) == dirname(@__DIR__) | ||
|
|
||
| io = IOBuffer() | ||
| show(io, info) | ||
| str = String(take!(io)) | ||
| @test startswith(str, "PkgFiles(CodeTracking [da1fd8a2-8d9e-5ec2-8556-3022fb5608a2]):\n basedir:") | ||
|
|
||
| @test pkgfiles("ColorTypes") === nothing | ||
| @test_throws ErrorException pkgfiles("NotAPkg") | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| function f1(x, y) | ||
| return x + y | ||
| end | ||
|
|
||
| f2(x, y) = x + y |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to error here or just return
nothinglike when it's a real installed package that just hasn't been loaded yet?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Erroring seems like a good choice to me. And it is at least the conservative choice which can be relaxed later.