Tutorial#
This tutorial will guide you through using fclap to build command-line interfaces for your Fortran applications. We’ll start with the basics and progress to more advanced features.
Installation#
Using fpm (Fortran Package Manager)#
Add fclap to your fpm.toml file:
[dependencies]
fclap = { git = "https://github.com/your-org/fclap.git" }
Then build your project:
fpm build
Manual Installation#
Clone the repository and include the source files in your project:
git clone https://github.com/your-org/fclap.git
# Copy src/*.f90 to your project's source directory
The Basics#
Creating a Parser#
Every fclap application starts by creating an ArgumentParser object:
program basic_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
! Initialize with program name and description
call parser%init(prog="myapp", &
description="A simple example application")
! Parse the command line
args = parser%parse_args()
end program basic_example
The init subroutine accepts several optional arguments:
prog: The program name (defaults to the actual program name)description: Text displayed before the argument helpepilog: Text displayed after the argument helpadd_help: Whether to add-h/--helpautomatically (default:.true.)version: Version string (adds-V/--versionif provided)
Adding Positional Arguments#
Positional arguments are required and must appear in order:
program positional_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
character(len=256) :: input_file, output_file
call parser%init(prog="converter", &
description="Convert files between formats")
! Add positional arguments
call parser%add_argument("input", help="Input file path")
call parser%add_argument("output", help="Output file path")
args = parser%parse_args()
! Retrieve values
call args%get("input", input_file)
call args%get("output", output_file)
print '(A,A)', "Input: ", trim(input_file)
print '(A,A)', "Output: ", trim(output_file)
end program positional_example
Running this program:
$ ./converter data.txt result.csv
Input: data.txt
Output: result.csv
$ ./converter --help
usage: converter input output
Convert files between formats
positional arguments:
input Input file path
output Output file path
optional arguments:
-h, --help show this help message and exit
Adding Optional Arguments (Flags)#
Optional arguments start with - or -- and can appear in any order:
program optional_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
type :: argsholder
character(len=256) :: filename, output
logical :: verbose
integer :: count
end type argsholder
call parser%init(prog="processor")
! Positional argument
call parser%add_argument("filename", help="File to process")
! Optional arguments
call parser%add_argument("-v", "--verbose", action="store_true", &
help="Enable verbose output")
call parser%add_argument("-o", "--output", default="out.txt", &
help="Output file (default: out.txt)")
call parser%add_argument("-c", "--count", type="int", default="1", &
help="Number of iterations")
args = parser%parse_args()
call args%get("filename", argsholder%filename)
call args%get("output", argsholder%output)
call args%get("verbose", argsholder%verbose)
call args%get("count", argsholder%count)
if (argsholder%verbose) print *, "Verbose mode enabled"
print '(A,A)', "Processing: ", trim(argsholder%filename)
print '(A,A)', "Output to: ", trim(argsholder%output)
print '(A,I0,A)', "Running ", argsholder%count, " iterations"
end program optional_example
Running this program:
$ ./processor input.dat -v -o results.txt --count 5
Verbose mode enabled
Processing: input.dat
Output to: results.txt
Running 5 iterations
Types and Nargs#
Argument Types#
fclap supports four value types specified with the type parameter:
"string"(default): Character strings"int": Integer values"real": Real/floating-point values"logical": Boolean values (typically used with store_true/store_false)
program types_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
character(len=256) :: name
integer :: iterations
real :: threshold
logical :: debug
call parser%init(prog="typed_args")
call parser%add_argument("-n", "--name", type="string", &
help="Name string")
call parser%add_argument("-i", "--iterations", type="int", &
default="10", help="Number of iterations")
call parser%add_argument("-t", "--threshold", type="real", &
default="0.5", help="Threshold value")
call parser%add_argument("-d", "--debug", action="store_true", &
help="Enable debug mode")
args = parser%parse_args()
call args%get("name", name)
call args%get("iterations", iterations)
call args%get("threshold", threshold)
call args%get("debug", debug)
end program types_example
Nargs: Consuming Multiple Values#
The nargs parameter controls how many command-line arguments are consumed:
ARG_OPTIONAL: Zero or one argument
ARG_ZERO_OR_MORE: Zero or more arguments
ARG_ONE_OR_MORE: One or more arguments (required)
Positive integer: Exact number of arguments
program nargs_example
use fclap, only: ArgumentParser, Namespace, ARG_ONE_OR_MORE
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
character(len=256) :: files(64)
integer :: num_files, i
call parser%init(prog="multi_file")
! Accept one or more input files
call parser%add_argument("files", nargs=ARG_ONE_OR_MORE, &
help="Input files to process")
args = parser%parse_args()
! Get the list of files
call args%get_string_list("files", files, num_files)
print '(A,I0,A)', "Processing ", num_files, " files:"
do i = 1, num_files
print '(A,A)', " - ", trim(files(i))
end do
end program nargs_example
Actions#
The action parameter specifies what to do when an argument is encountered:
store (default)#
Stores the argument value:
call parser%add_argument("-n", "--name", action="store", help="Your name")
store_true / store_false#
Stores .true. or .false. when the flag is present:
call parser%add_argument("-v", "--verbose", action="store_true", &
help="Enable verbose mode")
call parser%add_argument("-q", "--quiet", action="store_false", &
help="Disable output")
count#
Counts the number of times a flag appears:
program count_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
integer :: verbosity
call parser%init(prog="verbose_app")
call parser%add_argument("-v", "--verbose", action="count", &
help="Increase verbosity (use multiple times)")
args = parser%parse_args()
call args%get_integer("verbose", verbosity)
print '(A,I0)', "Verbosity level: ", verbosity
end program count_example
$ ./verbose_app -v -v -v
Verbosity level: 3
append#
Appends values to a list (allows repeated use of the same flag):
program append_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
character(len=256) :: include_dirs(64)
integer :: num_dirs, i
call parser%init(prog="compiler")
call parser%add_argument("-I", "--include", action="append", &
help="Add include directory")
args = parser%parse_args()
call args%get_string_list("include", include_dirs, num_dirs)
print '(A,I0,A)', "Include directories (", num_dirs, "):"
do i = 1, num_dirs
print '(A,A)', " ", trim(include_dirs(i))
end do
end program append_example
$ ./compiler -I /usr/include -I ./src -I ../lib
Include directories (3):
/usr/include
./src
../lib
Special Actions#
The not_less_than() and the not_bigger_than() actions allow to check that a int, real and character is not smaller or bigger, respectively than a certain value.
program nargs_example
use fclap, only: ArgumentParser, Namespace, not_bigger_than
implicit none
type(ArgumentParser) :: parser
type(Namespace) :: args
integer :: count_val
call parser%init(prog="multi_file")
! Accept a count that must be >= 5
call parser%add_argument("count", data_type="int", action=not_bigger_than(5), &
help="A count value (must be >= 5)")
args = parser%parse_args()
call args%get("count", count_val)
print '(A,I0)', "Count value: ", count_val
end program nargs_example
Advanced Features#
Argument Groups#
Groups organize related arguments in the help output:
program groups_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
integer :: io_group, proc_group
type(Namespace) :: args
call parser%init(prog="processor", &
description="Data processing application")
! Create input/output group
io_group = parser%add_argument_group("Input/Output Options", &
description="Control input and output files")
call parser%add_argument("-i", "--input", group_idx=io_group, &
help="Input file")
call parser%add_argument("-o", "--output", group_idx=io_group, &
help="Output file")
! Create processing group
proc_group = parser%add_argument_group("Processing Options", &
description="Control processing behavior")
call parser%add_argument("-n", "--iterations", data_type="int", &
group_idx=proc_group, help="Number of iterations")
call parser%add_argument("-t", "--threads", data_type="int", &
group_idx=proc_group, help="Number of threads")
args = parser%parse_args()
end program groups_example
Mutually Exclusive Groups#
Ensure only one option from a group is used:
program mutex_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser
integer :: verbosity_group
type(Namespace) :: args
call parser%init(prog="app")
! Create a mutually exclusive group - user can only pick one
verbosity_group = parser%add_mutually_exclusive_group(required=.true.)
call parser%add_argument("-v", "--verbose", action="store_true", &
mutex_group_idx=verbosity_group, &
help="Verbose output")
call parser%add_argument("-q", "--quiet", action="store_true", &
mutex_group_idx=verbosity_group, &
help="Quiet output")
args = parser%parse_args()
end program mutex_example
$ ./app -v -q
error: argument -q: not allowed with argument -v
Parent Parsers#
Share common arguments across multiple parsers:
program parents_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parent, child
type(Namespace) :: args
! Create parent with common arguments
call parent%init(add_help=.false.)
call parent%add_argument("-v", "--verbose", action="store_true", &
help="Enable verbose mode")
call parent%add_argument("--config", help="Config file path")
! Create child that inherits from parent
call child%init_with_parents([parent], prog="myapp", &
description="Application with inherited args")
! Add child-specific arguments
call child%add_argument("input", help="Input file")
args = child%parse_args()
end program parents_example
Subparsers (Subcommands)#
Create git-style subcommands:
program subcommand_example
use fclap, only: ArgumentParser, Namespace
implicit none
type(ArgumentParser) :: parser, clone_parser, commit_parser
type(Namespace) :: args
character(len=64) :: command
character(len=256) :: url, branch, message
call parser%init(prog="mygit", description="A simple VCS")
! Add a global option (available before the subcommand)
call parser%add_argument("--foo", action="store_true", help="foo help")
! Enable subparsers
call parser%add_subparsers(title="commands", dest="command")
! Create the 'clone' subcommand with its own arguments
call clone_parser%init(prog="mygit clone")
call clone_parser%add_argument("url", help="Repository URL")
call clone_parser%add_argument("-b", "--branch", help="Branch to clone")
call parser%add_parser("clone", clone_parser, help_text="Clone a repository")
! Create the 'commit' subcommand with its own arguments
call commit_parser%init(prog="mygit commit")
call commit_parser%add_argument("-m", "--message", required=.true., &
help="Commit message")
call parser%add_parser("commit", commit_parser, help_text="Commit changes")
args = parser%parse_args()
! Check which command was used
call args%get("command", command)
select case(trim(command))
case("clone")
call args%get("url", url)
print *, "Cloning: ", trim(url)
if (args%has_key("branch")) then
call args%get("branch", branch)
print *, " Branch: ", trim(branch)
end if
case("commit")
call args%get("message", message)
print *, "Committing: ", trim(message)
end select
end program subcommand_example
$ ./mygit --help
usage: mygit [-h] {clone,commit}
A simple VCS
commands:
clone Clone a repository
commit Commit changes
$ ./mygit clone https://github.com/user/repo.git -b main
Cloning repository...
Version Information#
Add version information to your application:
program version_example
use fclap, only: ArgumentParser, Namespace
type(ArgumentParser) :: parser
type(Namespace) :: args
call parser%init(prog="myapp", &
description="My Application", &
version="myapp 1.2.3")
args = parser%parse_args()
end program version_example
$ ./myapp --version
myapp 1.2.3
Error Handling#
fclap automatically handles common errors and displays helpful messages:
# Missing required argument
$ ./app
usage: app [-h] filename
error: the following arguments are required: filename
# Invalid type
$ ./app --count abc
error: argument --count: invalid integer value -- abc
# Unknown argument
$ ./app --unknown
error: unrecognized arguments: --unknown
Complete Example#
Here’s a complete example combining multiple features:
program complete_example
use fclap, only: ArgumentParser, Namespace, ARG_ONE_OR_MORE
type(ArgumentParser) :: parser
type(Namespace) :: args
character(len=256) :: output, files(64), format_str
integer :: num_files, verbosity, i
logical :: force
call parser%init( &
prog="processor", &
description="A comprehensive file processing utility", &
epilog="For more information, visit https://github.com/fclap", &
version="processor 2.0.0" &
)
! Positional: one or more input files
call parser%add_argument("files", nargs=ARG_ONE_OR_MORE, &
metavar="FILE", &
help="Input files to process")
! Optional arguments
call parser%add_argument("-o", "--output", default="output.dat", &
help="Output file (default: output.dat)")
call parser%add_argument("-f", "--format", &
choices=["csv ", "json", "xml "], &
default="csv", &
help="Output format: csv, json, or xml")
call parser%add_argument("-v", "--verbose", action="count", &
help="Increase verbosity (-v, -vv, -vvv)")
call parser%add_argument("--force", action="store_true", &
help="Overwrite existing output file")
args = parser%parse_args()
! Retrieve all values
call args%get("files", files, num_files)
call args%get("output", output)
call args%get("format", format_str)
call args%get("verbose", verbosity)
call args%get("force", force)
! Use the values
if (verbosity >= 1) then
print '(A)', "=== Processing Configuration ==="
print '(A,I0)', "Verbosity level: ", verbosity
print '(A,A)', "Output file: ", trim(output)
print '(A,A)', "Output format: ", trim(format_str)
print '(A,L1)', "Force overwrite: ", force
print '(A,I0)', "Number of input files: ", num_files
end if
if (verbosity >= 2) then
print '(A)', "Input files:"
do i = 1, num_files
print '(A,A)', " - ", trim(files(i))
end do
end if
print '(A)', "Processing complete!"
end program complete_example
$ ./processor -vv --format json -o result.json file1.txt file2.txt
=== Processing Configuration ===
Verbosity level: 2
Output file: result.json
Output format: json
Force overwrite: F
Number of input files: 2
Input files:
- file1.txt
- file2.txt
Processing complete!