Skip to content

Commit

Permalink
V0.2
Browse files Browse the repository at this point in the history
Changes for version 0.2.0
Testing, new documentation, new output formats, deprecate @beginarguments
  • Loading branch information
zachmatson authored Sep 13, 2020
1 parent 2a194c8 commit 1d07e54
Show file tree
Hide file tree
Showing 16 changed files with 734 additions and 143 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 zachmatson
Copyright (c) 2020 Zachary Matson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ArgMacros"
uuid = "dbc42088-9de8-42a0-8ec8-2cd114e1ea3e"
authors = ["zachmatson"]
version = "0.1.3"
version = "0.2.0"

[deps]
TextWrap = "b718987f-49a8-5099-9789-dcd902bef87d"
Expand Down
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
push!(LOAD_PATH,"../src/")
prepend!(LOAD_PATH, ["../src/"])

using Documenter
using ArgMacros
Expand Down
153 changes: 127 additions & 26 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Reading through this before using the package is recommended.

!!! note

Version 0.1.x of ArgMacros might be rough around some edge cases.
Version 0.2.x of ArgMacros might be rough around some edge cases.
Make sure to test the interface you build before using it.
If you notice any issues or want to request new features, create an issue on
[GitHub](https://github.com/zachmatson/ArgMacros.jl)
Expand All @@ -14,37 +14,45 @@ Reading through this before using the package is recommended.

ArgMacros is designed for parsing arguments in command-line Julia scripts.
Compilation time is the greatest bottleneck for startup of scripts in Julia,
and is mostly unavoidable. While attention is paid to making
code that compiles relatively quickly, the emphasis of ArgMacros is on quick
parsing and type stability after compilation.
and is mostly unavoidable. ArgMacros provides quick parsing after compilation while
ensuring compilation time fast too.

Variables created using ArgMacros are statically typed,
and available immediately within your main function, without manually
retrieving them from an intermediate data structure. This can also make it
more convenient when writing scripts.
ArgMacros also provides convenience when writing scripts by offering various and easily
interchangeable formats for outputting parsed arguments, a simple interface, and guaranteed
type safety of parsed arguments. Some output formats also provide static typing of the
argument variables.

## Installation

Install ArgMacros using Julia's Pkg package manager. Enter the Pkg prompt
by typing `]` at the REPL and then install:

```julia-repl
(@v1.4) pkg> add ArgMacros
(@v1.5) pkg> add ArgMacros
```

Then load ArgMacros into your script with `using ArgMacros`.

## Argument Format Types

There are four formats for your program or script to receive the parsed arguments with ArgMacros,
all of which use the same interface for argument declaration:
* Inline, typed local variables ([`@inlinearguments`](@ref))
* An automatically generated custom `struct` type ([`@structarguments`](@ref))
* `NamedTuple` ([`@tuplearguments`](@ref))
* `Dict` ([`@dictarguments`](@ref))

## Adding Arguments

All arguments must be declared using the macros provided, and all of the declarations must
exist within the [`@beginarguments`](@ref) block like so:
exist within the [`@inlinearguments`](@ref) block, or other argument macro block, like so:
```julia
@beginarguments begin
@inlinearguments begin
*arguments go here*
end
```

The types of arguments supported are broken down into two main categories:
The types of arguments supported are broken down into two categories:
* Options (`@argument...`) - Marked with flags
- [`@argumentrequired`](@ref)
- [`@argumentdefault`](@ref)
Expand All @@ -68,9 +76,11 @@ For this reason, ALL options must be declared before ANY positionals, required
positionals must be declared before default/optional ones, and positional arguments
must be declared in the order the user is expected to enter them.

You should make your argument types `Symbol`, `String`, or subtypes of `Number`.

Here is an example with some arguments:
```julia
@beginarguments begin
@inlinearguments begin
@argumentrequired String foo "-f" "--foo"
@argumentdefault String "lorem ipsum" bar "--bar"
@argumentflag verbose "-v"
Expand All @@ -94,17 +104,23 @@ of the name of local variable they will be stored in.

## Using Argument Values

Once an argument is decalred, it is statically typed and you can be sure it holds a value of the
Once an argument is decalred, you can be sure it holds a value of the
correct type. [`@argumentoptional`](@ref) and [`@positionaloptional`](@ref) will use the type `Union{T, Nothing}`,
however, and may also contain `nothing`. [`@argumentflag`](@ref) uses `Bool` and [`@argumentcount`](@ref) uses `Int`.
The other macros will all store the type specified. No additional code is required to begin using the
argument value after parsing.
The other macros will all store the type specified.

You should make your argument types `Symbol`, `String`, or subtypes of `Number`.
How exactly you use the values depends on the format used, the following will demonstrate the same arguments
with each of the available formats, and some of the consequences of each of them:

### Inline ([`@inlinearguments`](@ref))

The arguments are stored directly in local variables, which are statically typed. You can use them immediately
without any other boilerplate, but must respect the variable types. These variables, because they are typed, must
always be in local scope. You cannot put this block in a global scope.

```julia
function main()
@beginarguments begin
@inlinearguments begin
@positionalrequired Int x
@positionaldefault Int 5 y
@positionaloptional Int z
Expand All @@ -119,17 +135,98 @@ function main()
end
```

### Custom `struct` ([`@structarguments`](@ref))

A new `struct` type is created to store the arguments, and you can decide if it will be mutable.
The zero-argument constructor function for the new type parses the arguments when it is called.
You must *declare* the arguments in global scope due to the rules for type declarations,
but the constructor can be used anywhere.

The fields of the struct will all be typed.

```julia
# Declare mutable type Args and the arguments it will hold
@structarguments true Args begin
@positionalrequired Int x
@positionaldefault Int 5 y
@positionaloptional Int z
end

function main()
args = Args() # The arguments are parsed here

println(args.x + args.y) # Prints x + y, the variables must be Ints
println(isnothing(args.z)) # z might be nothing, because it was optional

# These assignemnt operations would all fail if we made Args immutable instead
args.z = nothing # It is fine to store values of type Nothing or Int in z now
args.z = 8
args.x = 5.5 # Raises an error, x must hold Int values
args.y = nothing # Raises an error, only optional arguments can hold nothing
end
```

### `NamedTuple` ([`@tuplearguments`](@ref))

A `NamedTuple` is returned containing all of the argument values, keyed by the variable names given. You can use this
version from any scope. All of the fields are typed, and as a `NamedTuple` the returned object will be immutable.

```julia
function main()
args = @tuplearguments begin
@positionalrequired Int x
@positionaldefault Int 5 y
@positionaloptional Int z
end

println(args.x + args.y) # Prints x + y, the variables must be Ints
println(isnothing(args.z)) # z might be nothing, because it was optional

# These assignemnt operations will fail because NamedTuples are always immutable
args.z = nothing
args.z = 8

args.x == 5.5 # Can never be true, args.x is guaranteed to be an Int
isnothing(args.y) # Must be false, y is not optional
end
```

### `Dict` ([`@dictarguments`](@ref))

A `Dict{Symbol, Any}` is returned containing all of the argument variables, keyed by the argument names as *`Symbol`s*. You can use
this version from any scope. The `Dict` type is mutable, and any type can be stored in any of its fields. Therefore, this version
does not provide as strong of a guarantee about types to the compuler when argument values are used later. However, the values
are guaranteed to be of the correct types when the `Dict` is first returned.

```julia
function main()
args = @dictarguments begin
@positionalrequired Int x
@positionaldefault Int 5 y
@positionaloptional Int z
end

println(args[:x] + args[:y]) # Prints x + y, the variable names are available right away and must be Ints at first
println(isnothing(args[:z])) # z might be nothing, because it was optional
args[:z] = nothing # It is fine to store values of any type in z now
args[:z] = 8
args[:x] = 5.5 # Same for x
args[:y] = nothing # And y
args[:a] = "some string" # New entries can even be added later, of any type
end
```

## Validating Arguments

Perhaps you want to impose certain conditions on the values of an argument beyond its type.
You can use the [`@argtest`](@ref) macro, which will exit the program if a specified unary predicate returns
`false` for the argument value.

If using an anonymous function for this, make sure to enclose it in parentheses so it is passed to the
macro as a single expression.
If using an operator function, make sure to enclose it in parentheses so it is passed to the
macro as a separate expression from the first argument.

```julia
@beginarguments begin
@inlinearguments begin
...
@positionalrequired String input "input_file"
@argtest input isfile "The input must be a valid file" # Confirm that the input file really exists
Expand All @@ -146,10 +243,10 @@ When using the [`@arghelp`](@ref) macro, note that it always applies to the last
The [`@helpusage`](@ref) will prepend your usage text with "Usage: ", so do not include this in the string you pass.

It is recommended to place [`@helpusage`](@ref), [`@helpdescription`](@ref), and [`@helpepilog`](@ref) in that order at the
beginning of the [`@beginarguments`](@ref) block, but this is not a requirement.
beginning of the `@...arguments` block, but this is not a requirement.

```julia
@beginarguments begin
@inlinearguments begin
@helpusage "example.jl input_file [output_file] [-f | --foo] [--bar] [-v]"
@helpdescription """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Expand All @@ -172,11 +269,11 @@ end
By default, the program will exit and print a warning if more arguments are given than the program declares.
If you don't want this to happen, include the [`@allowextraarguments`](@ref) macro.

This can occur anywhere inside the [`@beginarguments`](@ref) block, but the recommended placement is at the end,
This can occur anywhere inside the `@...arguments` block, but the recommended placement is at the end,
after all other help, test, and argument declarations.

```julia
@beginarguments begin
@inlinearguments begin
...
@allowextraarguments
end
Expand All @@ -185,7 +282,7 @@ end
## Taking Argument Code out of Main Function

It may be preferable, in some cases, not to declare all of your arguments and help information
inside of your main function. In this case, the [`@beginarguments`](@ref) block can be enclosed
inside of your main function. In this case, the [`@inlinearguments`](@ref) block can be enclosed
in a macro:

```julia
Expand All @@ -203,3 +300,7 @@ function main()
# The argument values will be available here
end
```

The [`@structarguments`](@ref) must be used in a global scope, but its constructor can then be used anywhere.
The other forms which directly return an object can be placed into an external function because they don't rely
on being in the same namespace as the point where the arguments are used, as [`@inlinearguments`](@ref) does.
11 changes: 7 additions & 4 deletions docs/src/macros.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Available Macros

## `@beginarguments`
## `@...arguments`

The `@beginarguments begin ... end` block will hold all of your `ArgMacros` code.
You shouldn't have to worry about this macro as long as you use it to enclose your other code.
The `@...arguments begin ... end` block will hold all of your `ArgMacros` code.
The [Using Argument Values](@ref) section provides a good comparison of the different available macros.
```@docs
@beginarguments
@inlinearguments
@structarguments
@tuplearguments
@dictarguments
```

## Option Arguments
Expand Down
5 changes: 3 additions & 2 deletions src/ArgMacros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ end
using TextWrap
using Base: @kwdef

export @beginarguments
export @beginarguments, @inlinearguments, @structarguments,
@tuplearguments, @dictarguments
export @helpusage, @helpdescription, @helpepilog
export @argumentrequired, @argumentdefault, @argumentoptional,
@argumentflag, @argumentcount
Expand All @@ -35,7 +36,7 @@ results in typed local variables.
Basic usage:
```julia
julia_main()
@beginarguments begin
@inlinearguments begin
@argumentrequired Int foo "-f" "--foo"
@argumentdefault Int 5 bar "-b" "--bar"
...
Expand Down
30 changes: 15 additions & 15 deletions src/constants.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# ArgMacros
# constants.jl

const usage_symbol = Symbol("@helpusage")
const description_symbol = Symbol("@helpdescription")
const epilog_symbol = Symbol("@helpepilog")
const arghelp_symbol = Symbol("@arghelp")
const USAGE_SYMBOL = Symbol("@helpusage")
const DESCRIPTION_SYMBOL = Symbol("@helpdescription")
const EPILOG_SYMBOL = Symbol("@helpepilog")
const ARGHELP_SYMBOL = Symbol("@arghelp")

const argument_required_symbol = Symbol("@argumentrequired")
const argument_default_symbol = Symbol("@argumentdefault")
const argument_optional_symbol = Symbol("@argumentoptional")
const argument_flag_symbol = Symbol("@argumentflag")
const argument_count_symbol = Symbol("@argumentcount")
const flagged_symbols = [argument_required_symbol, argument_default_symbol, argument_optional_symbol,
argument_flag_symbol, argument_count_symbol]
const ARGUMENT_REQUIRED_SYMBOL = Symbol("@argumentrequired")
const ARGUMENT_DEFAULT_SYMBOL = Symbol("@argumentdefault")
const ARGUMENT_OPTIONAL_SYMBOL = Symbol("@argumentoptional")
const ARGUMENT_FLAG_SYMBOL = Symbol("@argumentflag")
const ARGUMENT_COUNT_SYMBOL = Symbol("@argumentcount")
const FLAGGED_SYMBOLS = [ARGUMENT_REQUIRED_SYMBOL, ARGUMENT_DEFAULT_SYMBOL, ARGUMENT_OPTIONAL_SYMBOL,
ARGUMENT_FLAG_SYMBOL, ARGUMENT_COUNT_SYMBOL]

const positional_required_symbol = Symbol("@positionalrequired")
const positional_default_symbol = Symbol("@positionaldefault")
const positional_optional_symbol = Symbol("@positionaloptional")
const positional_optional_symbols = [positional_default_symbol, positional_optional_symbol]
const POSITIONAL_REQUIRED_SYMBOL = Symbol("@positionalrequired")
const POSITIONAL_DEFAULT_SYMBOL = Symbol("@positionaldefault")
const POSITIONAL_OPTIONAL_SYMBOL = Symbol("@positionaloptional")
const POSITIONAL_OPTIONAL_SYMBOLS = [POSITIONAL_DEFAULT_SYMBOL, POSITIONAL_OPTIONAL_SYMBOL]
Loading

2 comments on commit 1d07e54

@zachmatson
Copy link
Owner Author

Choose a reason for hiding this comment

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

@JuliaRegistrator register

Release notes:
New argument output formats: output directly as a struct, NamedTuple, or Dict
Some speed improvements, ~12% on testing benchmark for cold start including Julia 1.5.1 startup time
Old @beginarguments macro deprecated

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/21334

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.0 -m "<description of version>" 1d07e544dbf4c325f904d97094159f8935ba4fbd
git push origin v0.2.0

Please sign in to comment.