From 37a5413dc5690ad2f3afe947b2eb83aeb223ca1d Mon Sep 17 00:00:00 2001 From: David Varela <00.varela.david@gmail.com> Date: Tue, 5 Nov 2019 16:54:46 -0800 Subject: [PATCH] Introduce special REPL syntax for shared environments --- src/REPLMode/REPLMode.jl | 45 ++++++++++++++++++---------- src/REPLMode/argument_parsers.jl | 22 +++++++++++--- src/REPLMode/command_declarations.jl | 10 +++---- src/REPLMode/completions.jl | 20 ++++++++----- test/new.jl | 28 +++++++++++++++++ 5 files changed, 94 insertions(+), 31 deletions(-) diff --git a/src/REPLMode/REPLMode.jl b/src/REPLMode/REPLMode.jl index 3d678e1294..af27d11081 100644 --- a/src/REPLMode/REPLMode.jl +++ b/src/REPLMode/REPLMode.jl @@ -78,6 +78,7 @@ struct CommandSpec help::Union{Nothing,Markdown.MD} end +default_parser(xs, options) = unwrap(xs) function CommandSpec(;name::Union{Nothing,String} = nothing, short_name::Union{Nothing,String} = nothing, api::Union{Nothing,Function} = nothing, @@ -87,7 +88,7 @@ function CommandSpec(;name::Union{Nothing,String} = nothing, description::Union{Nothing,String} = nothing, completions::Union{Nothing,Function} = nothing, arg_count::Pair = (0=>0), - arg_parser::Function = unwrap, + arg_parser::Function = default_parser, )::CommandSpec @assert name !== nothing "Supply a canonical name" @assert description !== nothing "Supply a description" @@ -357,16 +358,16 @@ Final parsing (and checking) step. This step is distinct from `parse` in that it relies on the command specifications. """ function Command(statement::Statement)::Command + # options + opt_spec = statement.spec.option_specs + enforce_option(statement.options, opt_spec) + options = APIOptions(statement.options, opt_spec) # arguments arg_spec = statement.spec.argument_spec - arguments = arg_spec.parser(statement.arguments) + arguments = arg_spec.parser(statement.arguments, options) if !(arg_spec.count.first <= length(arguments) <= arg_spec.count.second) pkgerror("Wrong number of arguments") end - # options - opt_spec = statement.spec.option_specs - enforce_option(statement.options, opt_spec) - options = APIOptions(statement.options, opt_spec) return Command(statement.spec, options, arguments) end @@ -476,6 +477,26 @@ prev_project_file = nothing prev_project_timestamp = nothing prev_prefix = "" +function projname(project_file::String) + project = try + Types.read_project(project_file) + catch + nothing + end + if project === nothing || project.name === nothing + name = basename(dirname(project_file)) + else + name = project.name + end + for depot in Base.DEPOT_PATH + envdir = joinpath(depot, "environments") + if startswith(abspath(project_file), abspath(envdir)) + return "@" * name + end + end + return name +end + function promptf() global prev_project_timestamp, prev_prefix, prev_project_file project_file = try @@ -488,15 +509,9 @@ function promptf() if prev_project_file == project_file && prev_project_timestamp == mtime(project_file) prefix = prev_prefix else - project = try - Types.read_project(project_file) - catch - nothing - end - if project !== nothing - projname = project.name - name = projname !== nothing ? projname : basename(dirname(project_file)) - prefix = string("(", name, ") ") + project_name = projname(project_file) + if project_name !== nothing + prefix = string("(", project_name, ") ") prev_prefix = prefix prev_project_timestamp = mtime(project_file) prev_project_file = project_file diff --git a/src/REPLMode/argument_parsers.jl b/src/REPLMode/argument_parsers.jl index 7c1fd7d673..66380a955d 100644 --- a/src/REPLMode/argument_parsers.jl +++ b/src/REPLMode/argument_parsers.jl @@ -6,7 +6,7 @@ import ..isdir_windows_workaround """ Parser for PackageSpec objects. """ -function parse_package(args::Vector{QString}; add_or_dev=false)::Vector{PackageSpec} +function parse_package(args::Vector{QString}, options; add_or_dev=false)::Vector{PackageSpec} args::Vector{PackageToken} = map(PackageToken, package_lex(args)) return parse_package_args(args; add_or_dev=add_or_dev) end @@ -95,7 +95,7 @@ end ################ # RegistrySpec # ################ -function parse_registry(raw_args::Vector{QString}; add=false) +function parse_registry(raw_args::Vector{QString}, options; add=false) regs = RegistrySpec[] foreach(x -> push!(regs, parse_registry(x; add=add)), unwrap(raw_args)) return regs @@ -132,8 +132,22 @@ end # # # Other # -function parse_activate(args::Vector{QString})::Vector{String} - return [(x.isquoted ? x.raw : expanduser(x.raw)) for x in args] +function parse_activate(args::Vector{QString}, options) + isempty(args) && return [] # nothing to do + if length(args) == 1 + x = first(args) + if x.isquoted + return [x.raw] + end + x = x.raw + if first(x) == '@' + options[:shared] = true + return [x[2:end]] + else + return [expanduser(x)] + end + end + return args # this is currently invalid input for "activate" end # diff --git a/src/REPLMode/command_declarations.jl b/src/REPLMode/command_declarations.jl index 77fd6d2c65..8f1865e9f6 100644 --- a/src/REPLMode/command_declarations.jl +++ b/src/REPLMode/command_declarations.jl @@ -22,7 +22,7 @@ julia is started with `--startup-file=yes`. :short_name => "?", :api => identity, # dummy API function :arg_count => 0 => Inf, - :arg_parser => identity, + :arg_parser => ((x,y) -> x), :completions => complete_help, :description => "show this message", :help => md""" @@ -86,7 +86,7 @@ as any no-longer-necessary manifest packages due to project package removals. :api => API.add, :should_splat => false, :arg_count => 1 => Inf, - :arg_parser => (x -> parse_package(x; add_or_dev=true)), + :arg_parser => ((x,y) -> parse_package(x,y; add_or_dev=true)), :option_spec => OptionDeclaration[ [:name => "preserve", :takes_arg => true, :api => :preserve => do_preserve], ], @@ -135,7 +135,7 @@ pkg> add Example=7876af07-990d-54b4-ab0e-23690620f79a :api => API.develop, :should_splat => false, :arg_count => 1 => Inf, - :arg_parser => (x -> parse_package(x; add_or_dev=true)), + :arg_parser => ((x,y) -> parse_package(x,y; add_or_dev=true)), :option_spec => OptionDeclaration[ [:name => "strict", :api => :strict => true], [:name => "local", :api => :shared => false], @@ -274,7 +274,7 @@ packages will not be upgraded at all. ],[ :name => "generate", :api => API.generate, :arg_count => 1 => 1, - :arg_parser => x -> map(expanduser, unwrap(x)), + :arg_parser => ((x,y) -> map(expanduser, unwrap(x))), :description => "generate files for a new project", :help => md""" generate pkgname @@ -356,7 +356,7 @@ Redoes the changes from the latest [`undo`](@ref). :api => Registry.add, :should_splat => false, :arg_count => 1 => Inf, - :arg_parser => (x -> parse_registry(x; add = true)), + :arg_parser => ((x,y) -> parse_registry(x,y; add = true)), :description => "add package registries", :help => md""" registry add reg... diff --git a/src/REPLMode/completions.jl b/src/REPLMode/completions.jl index 8f296175a8..146a4945b9 100644 --- a/src/REPLMode/completions.jl +++ b/src/REPLMode/completions.jl @@ -1,16 +1,22 @@ ######################## # Completion Functions # ######################## -function complete_activate(options, partial, i1, i2) +function _shared_envs() possible = String[] + for depot in Base.DEPOT_PATH + envdir = joinpath(depot, "environments") + isdir(envdir) || continue + append!(possible, readdir(envdir)) + end + return possible +end + +function complete_activate(options, partial, i1, i2) shared = get(options, :shared, false) if shared - for depot in Base.DEPOT_PATH - envdir = joinpath(depot, "environments") - isdir(envdir) || continue - append!(possible, readdir(envdir)) - end - return possible + return _shared_envs() + elseif !isempty(partial) && first(partial) == '@' + return "@" .* _shared_envs() else return complete_local_dir(partial, i1, i2) end diff --git a/test/new.jl b/test/new.jl index 71d66878ab..2ec3effd02 100644 --- a/test/new.jl +++ b/test/new.jl @@ -265,6 +265,34 @@ end end end end +# +# # Activate +# +@testset "activate: repl" begin + isolate(loaded_depot=true) do + Pkg.REPLMode.TEST_MODE[] = true + # - activate shared env + api, args, opts = first(Pkg.pkg"activate --shared Foo") + @test api == Pkg.activate + @test args == "Foo" + @test opts == Dict(:shared => true) + # - activate shared env using special syntax + api, args, opts = first(Pkg.pkg"activate @Foo") + @test api == Pkg.activate + @test args == "Foo" + @test opts == Dict(:shared => true) + # - no arg activate + api, opts = first(Pkg.pkg"activate") + @test api == Pkg.activate + @test isempty(opts) + # - regular activate + api, args, opts = first(Pkg.pkg"activate FooBar") + @test api == Pkg.activate + @test args == "FooBar" + @test isempty(opts) + end +end + # # # Add #