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

[RFC] Make command spec more declarative #509

Merged
merged 99 commits into from
Jul 31, 2018

Conversation

00vareladavid
Copy link
Contributor

Still a WIP, but I'd be interested if anyone thinks this is a worthwhile change?

My end goal would be a more declarative specification of commands (e.g. specify a list of valid flags for a command , specify the number of expected arguments, ... and have the parser check those declarative constraints).

It will certainly be 'heavier' than the current code but it should be insignificant for interactive input sizes.

@codecov-io
Copy link

codecov-io commented Jul 17, 2018

Codecov Report

Merging #509 into master will increase coverage by 0.63%.
The diff coverage is 92.7%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #509      +/-   ##
==========================================
+ Coverage   90.67%   91.31%   +0.63%     
==========================================
  Files          18       18              
  Lines        2896     2948      +52     
==========================================
+ Hits         2626     2692      +66     
+ Misses        270      256      -14
Impacted Files Coverage Δ
src/API.jl 80.81% <100%> (-6.19%) ⬇️
src/REPLMode.jl 92.7% <92.54%> (+11.53%) ⬆️
src/versions.jl 98.06% <0%> (-0.02%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0621d86...7c67885. Read the comment docs.

@KristofferC
Copy link
Member

Yeah, I think this is a good idea and something I have thought about every time I add a new REPL command :).

@00vareladavid
Copy link
Contributor Author

Ok, great!

src/REPLMode.jl Outdated
,("patch", :cmd, :switch)
,("fixed", :cmd, :switch)
,("coverage", :cmd, :switch)
,("name", :cmd, :switch)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leading commas like this are anti-idiomatic in Julia; Julia allows a comma after the last element, so just end every line with a comma. Note that vectors can also have no commas at all and just use newlines, but you have to be consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll go with the trailing commas. (Also, I didn't know about the no commas for vectors. Pretty cool!)

Copy link
Member

@StefanKarpinski StefanKarpinski Jul 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's technically a different syntax: with commas it's a vector construction syntax, without commas it's a vertical concatenation syntax, but in this case the result is the same. If the elements are themselves vectors or matrices, the result can be different, e.g.:

julia> [[1, 2],
        [3, 4, 5],
        [6, 7]]
3-element Array{Array{Int64,1},1}:
 [1, 2]
 [3, 4, 5]
 [6, 7]

julia> [[1, 2]
        [3, 4, 5]
        [6, 7]]
7-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see the difference now: no commas will end up calling vcat. Thanks for explaining

@00vareladavid
Copy link
Contributor Author

Hey guys, I would appreciate any feedback on the status of this PR. The reason I'm asking is that it is becoming painful to keep up to date with the master since REPLMode.jl diverges significantly.

As a convincing example in workflow difference you can compare: my merge
with the original.

@KristofferC
Copy link
Member

Sorry, for lack of comments. This looks good but we need to test if it introduces any significant changes to time for first call (e.g. pkg> st.

Likely need to regenerate some of the precompile statements as well but that's on me.

@00vareladavid
Copy link
Contributor Author

Ok, I think I can test that

@00vareladavid
Copy link
Contributor Author

00vareladavid commented Jul 30, 2018

I ran some basic tests like this:

import Pkg
Pkg.activate("/home/nur0n/pkg/Pkg.jl") # use the same project file
@time Pkg.REPLMode.pkg"st"

I think this should test the right thing as the changes for this PR don't take effect until you hit REPLMode.do_cmd.

I profiled manually 5 times, then took the average:

  • master took about ~5.1 seconds on the first run.
  • refactor/parse took about ~4.7 seconds on the first run.
  • both take ~0.03 seconds for subsequent runs.

Not the most thorough tests, but I find them reassuring (given that they are testing the right thing...).

@fredrikekre
Copy link
Member

I haven't looked at the implementation here, but how would "double" commands like registry add (#453 (comment)) fit into this design?

@StefanKarpinski
Copy link
Member

I wonder if add shouldn't just be an implicit shorthand for package add...

@00vareladavid
Copy link
Contributor Author

00vareladavid commented Jul 30, 2018

Excellent point!

The closest thing right now would be to use options:
registry rm Registry=ae0cb698-197b-42ec-a0a0-4f871aea6013 -> registry --rm Registry=ae0cb698-197b-42ec-a0a0-4f871aea6013.
The declaration would be:

(       CMD_REGISTRY,
        ["registry"],
        do_registry!,
        (ARG_PKG, []),
        [
            ("add", OPT_SWITCH, :cmd => :add),
            ("rm", OPT_SWITCH, :cmd => :rm),
        ],
        md"",
        )

That's the current design though, I hadn't considered explicit support for subcommands. Extending support for them should be easy; it will be my next TODO. What I have in mind is declaring each subcommand:

(       CMD_REGISTRY_ADD,
        ["add"],
        do_registry_add!,
        (ARG_PKG, []),
        [],
        md"",
        ),(
        CMD_REGISTRY_RM,
       ["rm"],
       ...

The parser will check for a super command and a sub command, with "package" being the implicit super command.

@KristofferC
Copy link
Member

Let's merge this to prevent more merge conflicts and then I can work on fixing up the precompilation if needed.

@KristofferC KristofferC merged commit 8c8cd83 into JuliaLang:master Jul 31, 2018
@fredrikekre
Copy link
Member

Thanks for adding the registry placeholders!

@00vareladavid
Copy link
Contributor Author

@fredrikekre welcome!

API.gc(ctx)
function do_cmd!(command::PkgCommand, repl)
meta_opts = APIOptions(command.meta_options, meta_option_specs)
ctx = Context(meta_opts...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will fail sometimes if there is no active project? Thats why the call to do_activate! was called before creating the context before this PR. See #517 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still true @KristofferC ? If so, does the help command not work either? It is called after Context() both before and after this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean when Base.ACTIVE_PROJECT[] == nothing? How would I test that failure case? Maybe:

Base.ACTIVE_PROJECT[] = nothing
Pkg.REPLMode.pkg"activate"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the call to EnvCache that hits

project_file == nothing && error("no active project")

Can be reproduced with:

julia> using Pkg

julia> empty!(LOAD_PATH)
0-element Array{String,1}

julia> Base.ACTIVE_PROJECT[] = nothing

julia> Base.HOME_PROJECT[] = nothing

julia> Pkg.Types.EnvCache()
ERROR: no active project
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] find_project_file(::Nothing) at /home/fredrik/.julia/dev/Pkg/src/Types.jl:237
 [3] Pkg.Types.EnvCache(::Nothing) at /home/fredrik/.julia/dev/Pkg/src/Types.jl:279 (repeats 2 times)
 [4] top-level scope at none:0

julia> pkg"activate"
ERROR: no active project
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] find_project_file(::Nothing) at /home/fredrik/.julia/dev/Pkg/src/Types.jl:237
 [3] Pkg.Types.EnvCache(::Nothing) at /home/fredrik/.julia/dev/Pkg/src/Types.jl:279 (repeats 2 times)
 [4] Pkg.Types.Context() at ./none:0
 [5] do_cmd!(::Pkg.REPLMode.PkgCommand, ::Pkg.REPLMode.MiniREPL) at /home/fredrik/.julia/dev/Pkg/src/REPLMode.jl:523
 [6] #do_cmd#32(::Bool, ::Function, ::Pkg.REPLMode.MiniREPL, ::String) at /home/fredrik/.julia/dev/Pkg/src/REPLMode.jl:507
 [7] (::getfield(Pkg.REPLMode, Symbol("#kw##do_cmd")))(::NamedTuple{(:do_rethrow,),Tuple{Bool}}, ::typeof(Pkg.REPLMode.do_cmd), ::Pkg.REPLMode.MiniREPL, ::String) at ./none:0
 [8] top-level scope at none:0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the help command fails too (which it also did before this PR).

julia> pkg"help"
ERROR: no active project
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] find_project_file(::Nothing) at /home/fredrik/.julia/dev/Pkg/src/Types.jl:237
 [3] Pkg.Types.EnvCache(::Nothing) at /home/fredrik/.julia/dev/Pkg/src/Types.jl:279 (repeats 2 times)
 [4] Pkg.Types.Context() at ./none:0
 [5] do_cmd!(::Pkg.REPLMode.PkgCommand, ::Pkg.REPLMode.MiniREPL) at /home/fredrik/.julia/dev/Pkg/src/REPLMode.jl:523
 [6] #do_cmd#32(::Bool, ::Function, ::Pkg.REPLMode.MiniREPL, ::String) at /home/fredrik/.julia/dev/Pkg/src/REPLMode.jl:507
 [7] (::getfield(Pkg.REPLMode, Symbol("#kw##do_cmd")))(::NamedTuple{(:do_rethrow,),Tuple{Bool}}, ::typeof(Pkg.REPLMode.do_cmd), ::Pkg.REPLMode.MiniREPL, ::String) at ./none:0
 [8] top-level scope at none:0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea that is probably better. Things like registry add won't need the Context either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, nice catch btw!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify; you mean that the do_[]! functions create the Context then?

Copy link
Contributor Author

@00vareladavid 00vareladavid Jul 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be best if devs deal with the context (and APIOptions) in the REPL layer as a Dict{Symbol,Any} (like here or here). With Context! here. This should allow us to deal with an incomplete context before we hit the API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we finally hit the API, I think we will end up passing collect(ctx) or Context!(collect(ctx)) depending on the primary way we want users to use the API (I'm not sure which would be better yet).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants