diff --git a/LICENSE.md b/LICENSE.md index 74e08e7..2e3ceaa 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -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. diff --git a/doc/argparse.rst b/doc/argparse.rst index a1bb923..f43e6c7 100644 --- a/doc/argparse.rst +++ b/doc/argparse.rst @@ -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 @@ -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 `), will have dashes replaced with underscores, both for long options and for @@ -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)) @@ -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 diff --git a/src/ArgParse.jl b/src/ArgParse.jl index 2af3e0d..9076cf9 100644 --- a/src/ArgParse.jl +++ b/src/ArgParse.jl @@ -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 #{{{ @@ -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 @@ -177,6 +189,7 @@ 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, @@ -184,10 +197,12 @@ type ArgParseSettings 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 @@ -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 @@ -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 diff --git a/test/argparse_test3.jl b/test/argparse_test3.jl index a278b56..5ebcdbe 100644 --- a/test/argparse_test3.jl +++ b/test/argparse_test3.jl @@ -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()) diff --git a/test/argparse_test8.jl b/test/argparse_test8.jl new file mode 100644 index 0000000..e4b4292 --- /dev/null +++ b/test/argparse_test8.jl @@ -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']) diff --git a/test/args-file1 b/test/args-file1 new file mode 100644 index 0000000..bba194e --- /dev/null +++ b/test/args-file1 @@ -0,0 +1 @@ +--opt2=y diff --git a/test/args-file2 b/test/args-file2 new file mode 100644 index 0000000..d30ad0a --- /dev/null +++ b/test/args-file2 @@ -0,0 +1,2 @@ +@args-file1 +--opt1=x diff --git a/test/runtests.jl b/test/runtests.jl index c5a521b..32b3102 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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")