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

[WIP] 04ization #19

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 92 additions & 111 deletions src/ArgParse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ using TextWrap
using OptionsMod
using Compat

include("macros.jl")

export
# types
ArgParseSettings,
Expand Down Expand Up @@ -52,19 +54,20 @@ is_command_action(a::Symbol) = a in command_actions

# ArgConsumerType
#{{{
immutable ArgConsumerType
desc::Union(Int,Symbol)
function ArgConsumerType(n::Integer)
n >= 0 || error("nargs can't be negative")
new(n)
end
function ArgConsumerType(s::Symbol)
s in [:A, :?, :*, :+, :R] || error("nargs must be an integer or one of 'A', '?', '*', '+', 'R'")
new(s)
end
immutable ArgConsumerType{T}
desc::T
end
ArgConsumerType(c::Char) = ArgConsumerType(symbol(c))
ArgConsumerType() = ArgConsumerType(:A)
function ArgConsumerType{T<:Integer}(n::T)
n >= 0 || error("nargs can't be negative")
ArgConsumerType{T}(n)
end
function ArgConsumerType(s::Symbol)
s in [:A, :?, :*, :+, :R] || error("nargs must be an integer or one of 'A', '?', '*', '+', 'R'")
ArgConsumerType{Symbol}(s)
end


function show(io::IO, nargs::ArgConsumerType)
print(io, isa(nargs.desc, Int) ? nargs.desc : "'"*string(nargs.desc)*"'")
Expand All @@ -84,12 +87,11 @@ default_action(nargs::ArgConsumerType) = default_action(nargs.desc)

# ArgParseGroup
#{{{
type ArgParseGroup
name::String
desc::String
ArgParseGroup(n::String, d::String) = new(n, d)
immutable ArgParseGroup{T}
name::T
desc::T
end
ArgParseGroup(n::Symbol, d::String) = new(string(n), d)
ArgParseGroup(n, d) = ArgParseGroup(string(n), string(d))

const cmd_group = ArgParseGroup("commands", "commands")
const pos_group = ArgParseGroup("positional", "positional arguments")
Expand All @@ -100,25 +102,22 @@ const std_groups = [cmd_group, pos_group, opt_group]

# ArgParseField
#{{{
type ArgParseField
dest_name::String
long_opt_name::Vector{String}
short_opt_name::Vector{String}
arg_type::Type
action::Symbol
nargs::ArgConsumerType
default
constant
range_tester::Function
required::Bool
help::String
metavar::String
group::String
fake::Bool
function ArgParseField()
return new("", String[], String[], Any, :store_true, ArgConsumerType(),
nothing, nothing, x->true, false, "", "", "", false)
end
#{{{
@default type ArgParseField
dest_name::String = ""
long_opt_name::Vector{String} = String[]
short_opt_name::Vector{String} = String[]
arg_type::Type = Any
action::Symbol = :store_true
nargs::ArgConsumerType = ArgConsumerType()
default = nothing
constant = nothing
range_tester::Function = x -> true
required::Bool = false
help::String = ""
metavar::String = ""
group::String = ""
fake::Bool = false
end

is_flag(arg::ArgParseField) = is_flag_action(arg.action)
Expand All @@ -143,52 +142,31 @@ end

# ArgParseTable
#{{{
type ArgParseTable
fields::Vector{ArgParseField}
subsettings::Dict{String,Any} # this in fact will be a Dict{String,ArgParseSettings}
ArgParseTable() = new(ArgParseField[], Dict{String,Any}())
@default type ArgParseTable
fields::Vector{ArgParseField} = ArgParseField[]
subsettings::Dict{String,Any} = Dict{String,Any}() # this in fact will be a Dict{String,ArgParseSettings}
end
#}}}

# ArgParseSettings
#{{{
type ArgParseSettings
prog::String
description::String
epilog::String
usage::String
version::String
add_help::Bool
add_version::Bool
autofix_names::Bool
error_on_conflict::Bool
suppress_warnings::Bool
allow_ambiguous_opts::Bool
commands_are_required::Bool
args_groups::Vector{ArgParseGroup}
default_group::String
args_table::ArgParseTable
exc_handler::Function

function ArgParseSettings(;prog::String = Base.source_path() != nothing ? basename(Base.source_path()) : "",
description::String = "",
epilog::String = "",
usage::String = "",
version::String = "Unspecified version",
add_help::Bool = true,
add_version::Bool = false,
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)
end
@default type ArgParseSettings
prog::String = Base.source_path() != nothing ? basename(Base.source_path()) : ""
description::String = ""
epilog::String = ""
usage::String = ""
version::String = "Unspecified version"
add_help::Bool = true
add_version::Bool = false
autofix_names::Bool = false
error_on_conflict::Bool = true
suppress_warnings::Bool = false
allow_ambiguous_opts::Bool = false
commands_are_required::Bool = true
args_groups::Vector{ArgParseGroup} = copy(std_groups)
default_group::String = ""
args_table::ArgParseTable = ArgParseTable()
exc_handler::Function = default_handler
end

# the "add_help" is kept for backwards compatibility and is now undocumented
Expand All @@ -205,8 +183,6 @@ function show(io::IO, s::ArgParseSettings)
print(io, str)
end

typealias ArgName{T<:String} Union(T, Vector{T})

getindex(s::ArgParseSettings, c::String) = s.args_table.subsettings[c]
haskey(s::ArgParseSettings, c::String) = haskey(s.args_table.subsettings, c)
setindex!(s::ArgParseSettings, x::ArgParseSettings, c::String) = setindex!(s.args_table.subsettings, x, c)
Expand All @@ -215,16 +191,16 @@ setindex!(s::ArgParseSettings, x::ArgParseSettings, c::String) = setindex!(s.arg

# fields declarations sanity checks
#{{{
function check_name_format(name::ArgName)
isempty(name) && error("empty name")
isa(name, Vector) || return true
function check_name_format{T<:String}(name::Vector{T})
for n in name
isempty(n) && error("empty name")
check_name_format(n)
startswith(n, '-') || error("only options can have multiple names")
end
return true
end

check_name_format(name::String) = isempty(name) && error("empty name") || true

function check_type(opt, T::Type, message::String)
isa(opt, T) || error(message)
return true
Expand Down Expand Up @@ -453,45 +429,50 @@ end

# add_arg_table and related
#{{{
function name_to_fieldnames(name::ArgName, settings::ArgParseSettings)
function name_to_fieldnames{T<:String}(name::Vector{T}, settings::ArgParseSettings)
pos_arg = ""
long_opts = String[]
short_opts = String[]
r(n) = settings.autofix_names ? replace(n, '_', '-') : n
if isa(name, Vector)
for n in name
if startswith(n, "--")
n == "--" && error("illegal option name: --")
long_opt_name = r(n[3:end])
check_long_opt_name(long_opt_name, settings)
push!(long_opts, long_opt_name)
else
@assert startswith(n, '-')
n == "-" && error("illegal option name: -")
short_opt_name = n[2:end]
check_short_opt_name(short_opt_name, settings)
push!(short_opts, short_opt_name)
end
end
else
if startswith(name, "--")
name == "--" && error("illegal option name: --")
long_opt_name = r(name[3:end])
for n in name
if startswith(n, "--")
n == "--" && error("illegal option name: --")
long_opt_name = r(n[3:end])
check_long_opt_name(long_opt_name, settings)
push!(long_opts, long_opt_name)
elseif startswith(name, '-')
name == "-" && error("illegal option name: -")
short_opt_name = name[2:end]
else
@assert startswith(n, '-')
n == "-" && error("illegal option name: -")
short_opt_name = n[2:end]
check_short_opt_name(short_opt_name, settings)
push!(short_opts, short_opt_name)
else
check_arg_name(name)
pos_arg = name
end
end
return pos_arg, long_opts, short_opts
end

function name_to_fieldnames(name::String, settings::ArgParseSettings)
pos_arg = ""
long_opts = String[]
short_opts = String[]
r(n) = settings.autofix_names ? replace(n, '_', '-') : n
if startswith(name, "--")
name == "--" && error("illegal option name: --")
long_opt_name = r(name[3:end])
check_long_opt_name(long_opt_name, settings)
push!(long_opts, long_opt_name)
elseif startswith(name, '-')
name == "-" && error("illegal option name: -")
short_opt_name = name[2:end]
check_short_opt_name(short_opt_name, settings)
push!(short_opts, short_opt_name)
else
check_arg_name(name)
pos_arg = name
end
return pos_arg, long_opts, short_opts
end

function auto_dest_name(pos_arg::String, long_opts::Vector{String}, short_opts::Vector{String}, autofix_names::Bool)
r(n) = autofix_names ? replace(n, '-', '_') : n
isempty(pos_arg) || return r(pos_arg)
Expand All @@ -513,11 +494,11 @@ function get_cmd_prog_hint(arg::ArgParseField)
end


function add_arg_table(settings::ArgParseSettings, table::Union(ArgName, Options)...)
function add_arg_table{T<:String}(settings::ArgParseSettings, table::Union(T, Vector{T}, Options)...)
has_name = false
for i = 1:length(table)
!has_name && isa(table[i], Options) && error("option field must be preceded by the arg name")
has_name = isa(table[i], ArgName)
has_name = isa(table[i], String) || isa(table[i], Vector)
end
i = 1
while i <= length(table)
Expand Down Expand Up @@ -630,7 +611,7 @@ end
get_group_name(group::String, arg::ArgParseField, settings::ArgParseSettings) =
get_group(group, arg, settings).name

function add_arg_field(settings::ArgParseSettings, name::ArgName, desc::Options)
function add_arg_field{T<:String}(settings::ArgParseSettings, name::Union(T, Vector{T}), desc::Options)
check_name_format(name)

supplied_opts = keys(desc.key2index)
Expand Down Expand Up @@ -1082,7 +1063,7 @@ end

# ArgParseError
#{{{
type ArgParseError <: Exception
immutable ArgParseError <: Exception
text::String
end

Expand Down
58 changes: 58 additions & 0 deletions src/macros.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
@default type Foo
x::Int64=6
y::Float64=7
end
yields...
type Foo
x::Int64
y::Float64
end
function Foo(;x=6, y=7)
Foo(x,y)
end
"""
macro default(expr)
if expr.head != symbol("type")
error("@default only works on composite types")
else

field_name(a::Symbol) = a
field_name(a::Expr) = a.args[1]
type_symbol(a::Symbol) = a
type_symbol(a::Expr) = a.args[1]

type_name = type_symbol(expr.args[2])

for i = 3:length(expr.args)
block = expr.args[i]
if block.head == :(block)
field_names = Any[]
defaults = Any[]
fields = Any[]
for arg in block.args
if arg.head == :(=)
val = arg.args[2]
field = field_name(arg.args[1])
push!(field_names, field)
push!(defaults, :($field = $val))
push!(fields, arg.args[1])
end
end
# replace block with just fields names and type annotations
block.args = fields
for default in defaults
default.head = :kw
end
kwarg_func = :(function $type_name(;$(defaults...))
$type_name($(field_names...))
end)
default_type = :(begin
$expr
$kwarg_func
end)
return esc(default_type)
end
end
end
end