Skip to content

Commit

Permalink
Add support for quoted args in REPL mode (#414)
Browse files Browse the repository at this point in the history
* Add `start` command to REPL mode

* Add support for quoted args in REPL mode

* Add support for quoted args

* Add more tests for quoted args in REPL

* Remove function accidentally commited to master

* Replace accidentaly removed UUID from Project.toml

* Remove double quote from tests (not supported by Windows)
  • Loading branch information
00vareladavid authored and KristofferC committed Jul 3, 2018
1 parent aa34d42 commit 0162855
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 1 deletion.
62 changes: 61 additions & 1 deletion stdlib/Pkg/src/REPLMode.jl
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,74 @@ const lex_re = r"^[\?\./\+\-](?!\-) | ((git|ssh|http(s)?)|(git@[\w\-\.]+))(:(//)
const Token = Union{Command, Option, VersionRange, String, Rev}

function tokenize(cmd::String)::Vector{Vector{Token}}
words = map(m->m.match, eachmatch(lex_re, cmd))
# phase 1: tokenize accoring to whitespace / quotes
chunks = parse_quotes(cmd)
# phase 2: tokenzie unquoted tokens according to pkg REPL syntax
words::Vector{String} = []
for chunk in chunks
is_quoted = chunk[1]
word = chunk[2]
if is_quoted
push!(words, word)
else # break unquoted chunks further according to lexer
# note: space before `$word` is necessary to keep using current `lex_re`
# v
append!(words, map(m->m.match, eachmatch(lex_re, " $word")))
end
end

commands = Vector{Token}[]
while !isempty(words)
push!(commands, tokenize!(words))
end
return commands
end

function parse_quotes(cmd::String)
in_doublequote = false
in_singlequote = false
all_tokens::Array = []
token_in_progress::Array{Char} = []

push_token!(is_quoted) = begin
complete_token = String(token_in_progress)
empty!(token_in_progress)
push!(all_tokens, (is_quoted, complete_token))
end

for c in cmd
if c == '"'
if in_singlequote # raw char
push!(token_in_progress, c)
else # delimiter
in_doublequote = !in_doublequote
push_token!(true)
end
elseif c == '\''
if in_doublequote # raw char
push!(token_in_progress, c)
else # delimiter
in_singlequote = !in_singlequote
push_token!(true)
end
elseif c == ' ' && !(in_doublequote || in_singlequote)
push_token!(false)
else
push!(token_in_progress, c)
end
end
if (in_doublequote || in_singlequote)
ArgumentError("unterminated quote")
else
push_token!(false)
end
# to avoid complexity in the main loop, empty tokens are allowed above and
# filtered out before returning
isnotempty(x) = !isempty(x[2])
filter!(isnotempty, all_tokens)
return all_tokens
end

function tokenize!(words::Vector{<:AbstractString})::Vector{Token}
tokens = Token[]
help_mode = false
Expand Down
100 changes: 100 additions & 0 deletions stdlib/Pkg/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,104 @@ temp_pkg_dir() do project_path; cd(project_path) do
end
end; end

temp_pkg_dir() do project_path
cd(project_path) do
@testset "add/remove using quoted local path" begin
# utils
setup_package(parent_dir, pkg_name) = begin
mkdir(parent_dir)
cd(parent_dir) do
Pkg.generate(pkg_name)
cd(pkg_name) do
repo = LibGit2.init(joinpath(project_path, parent_dir, pkg_name))
LibGit2.add!(repo, "*")
LibGit2.commit(repo, "initial commit"; author=TEST_SIG, committer=TEST_SIG)
end #cd pkg_name
end # cd parent_dir
end

# extract uuid from a Project.toml file
extract_uuid(toml_path) = begin
uuid = ""
for line in eachline(toml_path)
m = match(r"uuid = \"(.+)\"", line)
if m !== nothing
uuid = m.captures[1]
break
end
end
return uuid
end

# testing local dir with space in name
dir_name = "space dir"
pkg_name = "WeirdName77"
setup_package(dir_name, pkg_name)
uuid = extract_uuid("$dir_name/$pkg_name/Project.toml")
Pkg.REPLMode.pkgstr("add \"$dir_name/$pkg_name\"")
@test isinstalled((name=pkg_name, uuid = UUID(uuid)))
Pkg.REPLMode.pkgstr("remove \"$pkg_name\"")
@test !isinstalled((name=pkg_name, uuid = UUID(uuid)))

# testing dir name with significant characters
dir_name = "some@d;ir#"
pkg_name = "WeirdName77"
setup_package(dir_name, pkg_name)
uuid = extract_uuid("$dir_name/$pkg_name/Project.toml")
Pkg.REPLMode.pkgstr("add \"$dir_name/$pkg_name\"")
@test isinstalled((name=pkg_name, uuid = UUID(uuid)))
Pkg.REPLMode.pkgstr("remove '$pkg_name'")
@test !isinstalled((name=pkg_name, uuid = UUID(uuid)))

# more complicated input
## pkg1
dir1 = "two space dir"
pkg_name1 = "name1"
setup_package(dir1, pkg_name1)
uuid1 = extract_uuid("$dir1/$pkg_name1/Project.toml")

## pkg2
dir2 = "two'quote'dir"
pkg_name2 = "name2"
setup_package(dir2, pkg_name2)
uuid2 = extract_uuid("$dir2/$pkg_name2/Project.toml")

Pkg.REPLMode.pkgstr("add '$dir1/$pkg_name1' \"$dir2/$pkg_name2\"")
@test isinstalled((name=pkg_name1, uuid = UUID(uuid1)))
@test isinstalled((name=pkg_name2, uuid = UUID(uuid2)))
Pkg.REPLMode.pkgstr("remove '$pkg_name1' $pkg_name2")
@test !isinstalled((name=pkg_name1, uuid = UUID(uuid1)))
@test !isinstalled((name=pkg_name2, uuid = UUID(uuid2)))

Pkg.REPLMode.pkgstr("add '$dir1/$pkg_name1' \"$dir2/$pkg_name2\"")
@test isinstalled((name=pkg_name1, uuid = UUID(uuid1)))
@test isinstalled((name=pkg_name2, uuid = UUID(uuid2)))
Pkg.REPLMode.pkgstr("remove '$pkg_name1' \"$pkg_name2\"")
@test !isinstalled((name=pkg_name1, uuid = UUID(uuid1)))
@test !isinstalled((name=pkg_name2, uuid = UUID(uuid2)))
end
end
end

@testset "uint test `parse_package`" begin
name = "FooBar"
uuid = "7876af07-990d-54b4-ab0e-23690620f79a"
url = "https://github.com/JuliaLang/Example.jl"
path = "./Foobar"
# valid input
pkg = Pkg.REPLMode.parse_package(name)
@test pkg.name == name
pkg = Pkg.REPLMode.parse_package(uuid)
@test pkg.uuid == UUID(uuid)
pkg = Pkg.REPLMode.parse_package("$name=$uuid")
@test (pkg.name == name) && (pkg.uuid == UUID(uuid))
pkg = Pkg.REPLMode.parse_package(url; add_or_develop=true)
@test (pkg.repo.url == url)
pkg = Pkg.REPLMode.parse_package(path; add_or_develop=true)
@test (pkg.repo.url == path)
# errors
@test_throws CommandError Pkg.REPLMode.parse_package(url)
@test_throws CommandError Pkg.REPLMode.parse_package(path)
end

end # module

0 comments on commit 0162855

Please sign in to comment.