Skip to content

Commit

Permalink
Add fromfile_prefix_chars setting, like Python
Browse files Browse the repository at this point in the history
  • Loading branch information
TotalVerb committed Apr 19, 2016
1 parent 7066c4c commit 836ab37
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 12 deletions.
46 changes: 46 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,49 @@ The ArgParse Julia module is licensed under the MIT License:
> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Portions of this software are derived from Python 3.5.1 code, and therefore is licensed under
the permissive but slightly more strict Python Software Foundation license, and is partially
copyright 2001-2016 Python Software Foundation; All Rights Reserved.

> 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
> the Individual or Organization ("Licensee") accessing and otherwise using Python
> 3.5.1 software in source or binary form and its associated documentation.
>
> 2. Subject to the terms and conditions of this License Agreement, PSF hereby
> grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
> analyze, test, perform and/or display publicly, prepare derivative works,
> distribute, and otherwise use Python 3.5.1 alone or in any derivative
> version, provided, however, that PSF's License Agreement and PSF's notice of
> copyright, i.e., "Copyright © 2001-2016 Python Software Foundation; All Rights
> Reserved" are retained in Python 3.5.1 alone or in any derivative version
> prepared by Licensee.
>
> 3. In the event Licensee prepares a derivative work that is based on or
> incorporates Python 3.5.1 or any part thereof, and wants to make the
> derivative work available to others as provided herein, then Licensee hereby
> agrees to include in any such work a brief summary of the changes made to Python
> 3.5.1.
>
> 4. PSF is making Python 3.5.1 available to Licensee on an "AS IS" basis.
> PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF
> EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
> WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
> USE OF PYTHON 3.5.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
>
> 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.5.1
> FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
> MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.5.1, OR ANY DERIVATIVE
> THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
>
> 6. This License Agreement will automatically terminate upon a material breach of
> its terms and conditions.
>
> 7. Nothing in this License Agreement shall be deemed to create any relationship
> of agency, partnership, or joint venture between PSF and Licensee. This License
> Agreement does not grant permission to use PSF trademarks or trade name in a
> trademark sense to endorse or promote products or services of Licensee, or any
> third party.
>
> 8. By copying, installing or otherwise using Python 3.5.1, Licensee agrees
> to be bound by the terms and conditions of this License Agreement.
12 changes: 7 additions & 5 deletions doc/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ Putting all this together in a file, we can see how a basic command-line interfa
println(" $arg => $val")
end
end

main()
If we save this as a file called ``myprog1.jl``, we can see how a ``--help`` option is added by default,

If we save this as a file called ``myprog1.jl``, we can see how a ``--help`` option is added by default,
and a help message is automatically generated and formatted::

$ julia myprog1.jl --help
Expand Down Expand Up @@ -240,6 +240,8 @@ This is the list of general settings currently available:
to the argument table.
* ``add_version`` (default = ``false``): if ``true``, a ``--version`` option (triggering the ``:show_version`` action) is added
to the argument table.
* ``fromfile_prefix_chars`` (default = ``Set{Char}()``): an argument beginning with one of these characters will specify a file from which
arguments will be read, one argument read per line. Alphanumeric characters and the hyphen-minus ("-") are prohibited.
* ``autofix_names`` (default = ``false``): if ``true``, will try to automatically fix the uses of dashes (``-``) and underscores (``_``)
in option names and destinations: all underscores will be converted to dashes in long option names; also, associated destination names, if
auto-generated (see :ref:`this_section <argparse-argument-names>`), will have dashes replaced with underscores, both for long options and for
Expand All @@ -257,7 +259,7 @@ This is the list of general settings currently available:
during parsing (e.g. an option is not recognized, a required argument is not passed etc.). It takes two arguments:
the ``settings::ArgParseSettings`` object and the ``err::ArgParseError`` exception. The default handler prints the error text
and the usage screen on standard error and exits with error code 1::

function default_handler(settings::ArgParseSettings, err, err_code::Int = 1)
println(STDERR, err.text)
println(STDERR, usage_string(settings))
Expand Down Expand Up @@ -873,7 +875,7 @@ look like options.

When ``nargs`` is ``'+'`` or ``'*'`` and an option is being parsed, then using the ``'='`` character will mark what
follows as an argument (i.e. not an option); all which follows goes under the rules explained above. The same is true
when short option groups are being parsed. For example, if the option in question is ``-x``, then both
when short option groups are being parsed. For example, if the option in question is ``-x``, then both
``-y -x=-2 4 -y`` and ``-yx-2 4 -y`` will parse ``"-2"`` and ``"4"`` as the arguments of ``-x``.

Finally, note that with the `eval_arg` setting expressions are evaluated during parsing, which means that there is no
Expand Down
55 changes: 50 additions & 5 deletions src/ArgParse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,18 @@ type ArgParseTable
subsettings::Dict{AbstractString,Any} # this in fact will be a Dict{AbstractString,ArgParseSettings}
ArgParseTable() = new(ArgParseField[], Dict{AbstractString,Any}())
end
#}}}

# disallow alphanumeric, -
function check_prefix_chars(chars)
result = Set{Char}()
for c in chars
if isalnum(c) || c == '-'
throw(ArgParseError("$c’ is not allowed as prefix character"))
end
push!(result, c)
end
result
end

# ArgParseSettings
#{{{
Expand All @@ -160,6 +171,7 @@ type ArgParseSettings
version::AbstractString
add_help::Bool
add_version::Bool
fromfile_prefix_chars::Set{Char}
autofix_names::Bool
error_on_conflict::Bool
suppress_warnings::Bool
Expand All @@ -177,17 +189,20 @@ type ArgParseSettings
version::AbstractString = "Unspecified version",
add_help::Bool = true,
add_version::Bool = false,
fromfile_prefix_chars = Set{Char}(),
autofix_names::Bool = false,
error_on_conflict::Bool = true,
suppress_warnings::Bool = false,
allow_ambiguous_opts::Bool = false,
commands_are_required::Bool = true,
exc_handler::Function = default_handler
)
return new(prog, description, epilog, usage, version, add_help, add_version,
autofix_names, error_on_conflict, suppress_warnings, allow_ambiguous_opts,
commands_are_required, copy(std_groups), "",
ArgParseTable(), exc_handler)
fromfile_prefix_chars = check_prefix_chars(fromfile_prefix_chars)
return new(
prog, description, epilog, usage, version, add_help, add_version,
fromfile_prefix_chars, autofix_names, error_on_conflict,
suppress_warnings, allow_ambiguous_opts, commands_are_required,
copy(std_groups), "", ArgParseTable(), exc_handler)
end
end

Expand Down Expand Up @@ -925,6 +940,7 @@ function add_command(settings::ArgParseSettings, command::AbstractString, prog_h
ss.version = settings.version
ss.add_help = settings.add_help
ss.add_version = settings.add_version
ss.fromfile_prefix_chars = settings.fromfile_prefix_chars
ss.error_on_conflict = settings.error_on_conflict
ss.suppress_warnings = settings.suppress_warnings
ss.allow_ambiguous_opts = settings.allow_ambiguous_opts
Expand Down Expand Up @@ -1612,8 +1628,37 @@ function preparse(state::ParserState, settings::ArgParseSettings)
end
end

# faithful reproduction of Python 3.5.1 argparse.py
# partially Copyright © 2001-2016 Python Software Foundation; All Rights Reserved
function read_args_from_files(arg_strings, prefixes)
new_arg_strings = AbstractString[]

for arg_string in arg_strings
if isempty(arg_string) || arg_string[1] prefixes
# for regular arguments, just add them back into the list
push!(new_arg_strings, arg_string)
else
# replace arguments referencing files with the file content
open(arg_string[nextind(arg_string, 1):end]) do args_file
arg_strings = AbstractString[]
for arg_line in readlines(args_file)
push!(arg_strings, rstrip(arg_line, '\n'))
end
arg_strings = read_args_from_files(arg_strings, prefixes)
append!(new_arg_strings, arg_strings)
end
end
end

# return the modified argument list
return new_arg_strings
end

function parse_args_unhandled(args_list::Vector, settings::ArgParseSettings, truncated_shopts::Bool=false)
any(x->!isa(x,AbstractString), args_list) && error("malformed args_list")
if !isempty(settings.fromfile_prefix_chars)
args_list = read_args_from_files(args_list, settings.fromfile_prefix_chars)
end

version_added = false
help_added = false
Expand Down
2 changes: 1 addition & 1 deletion test/argparse_test3.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ let s = ap_settings3()
-u provide the answer as floating point
--array ARRAY create an array (type: Array{$Int,1},
default: [7,3,2])
""" * (VERSION < v"0.4-" ?
""" * (VERSION < v"0.4-" || VERSION v"0.5-" ?
"""
--custom CUSTOM the only accepted argument is "custom" (type:
CustomType, default: CustomType())
Expand Down
62 changes: 62 additions & 0 deletions test/argparse_test8.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# test 8: read args from file

function ap_settings8a()

s = ArgParseSettings(fromfile_prefix_chars=['@'])

@add_arg_table s begin
"--opt1" # an option (will take an argument)
"--opt2", "-o" # another option, with short form
"arg1" # a positional argument
end

s.exc_handler = ArgParse.debug_handler

return s
end

function ap_settings8b() # unicode

s = ArgParseSettings(fromfile_prefix_chars="@∘")

@add_arg_table s begin
"--opt1" # an option (will take an argument)
"--opt2", "-o" # another option, with short form
"arg1" # a positional argument
end

s.exc_handler = ArgParse.debug_handler

return s
end

let s = ap_settings8a()
ap_test8(args) = parse_args(args, s)

@compat @test ap_test8(["@args-file1"]) == Dict(
"opt1"=>nothing, "opt2"=>"y", "arg1"=>nothing)
@compat @test ap_test8(["@args-file1", "arg"]) == Dict(
"opt1"=>nothing, "opt2"=>"y", "arg1"=>"arg")
@compat @test ap_test8(["@args-file2"]) == Dict(
"opt1"=>"x", "opt2"=>"y", "arg1"=>nothing)
@compat @test ap_test8(["@args-file2", "arg"]) == Dict(
"opt1"=>"x", "opt2"=>"y", "arg1"=>"arg")
end

let s = ap_settings8b()
ap_test8(args) = parse_args(args, s)

@compat @test ap_test8(["∘args-file1"]) == Dict(
"opt1"=>nothing, "opt2"=>"y", "arg1"=>nothing)
@compat @test ap_test8(["@args-file1", "arg"]) == Dict(
"opt1"=>nothing, "opt2"=>"y", "arg1"=>"arg")
@compat @test ap_test8(["∘args-file2"]) == Dict(
"opt1"=>"x", "opt2"=>"y", "arg1"=>nothing)
@compat @test ap_test8(["@args-file2", "arg"]) == Dict(
"opt1"=>"x", "opt2"=>"y", "arg1"=>"arg")
end

# not allowed
@ap_test_throws ArgParseSettings(fromfile_prefix_chars=['-'])
@ap_test_throws ArgParseSettings(fromfile_prefix_chars=['Å'])
@ap_test_throws ArgParseSettings(fromfile_prefix_chars=['8'])
1 change: 1 addition & 0 deletions test/args-file1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--opt2=y
2 changes: 2 additions & 0 deletions test/args-file2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@args-file1
--opt1=x
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module ArgParseTests

include("common.jl")

for i = 1:7
for i = 1:8
print("\rRunning argparse_test$i")
try
include("argparse_test$i.jl")
Expand Down

0 comments on commit 836ab37

Please sign in to comment.