Skip to content
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
2 changes: 1 addition & 1 deletion example/example.f90
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ program subcommand_example
call args%get("message", message)
print *, "Committing: ", trim(message)
end select
end program subcommand_example
end program subcommand_example
2 changes: 1 addition & 1 deletion src/fclap.f90
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module fclap
use fclap_version, only: get_fclap_version, fclap_version_compact, &
fclap_version_string

implicit none
implicit none(type)
private

! Public types - main user-facing API
Expand Down
24 changes: 18 additions & 6 deletions src/fclap/fclap_actions.f90
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,9 @@ subroutine action_execute(self, args, values, num_values, error)
return
end if
if (int_val < int_bound) then
call error%init("value must not be less than " // trim(self%bound_str), values(1))
call error%init( &
"value must not be less than " // trim(self%bound_str), &
values(1))
return
end if
call args%set_integer(self%dest, int_val)
Expand All @@ -427,7 +429,9 @@ subroutine action_execute(self, args, values, num_values, error)
return
end if
if (real_val < real_bound) then
call error%init("value must not be less than " // trim(self%bound_str), values(1))
call error%init( &
"value must not be less than " // trim(self%bound_str), &
values(1))
return
end if
store_real_val = real(real_val)
Expand All @@ -442,7 +446,9 @@ subroutine action_execute(self, args, values, num_values, error)
return
end if
if (str_len < int_bound) then
call error%init("string length must not be less than " // trim(self%bound_str), values(1))
call error%init( &
"string length must not be less than " // trim(self%bound_str), &
values(1))
return
end if
call args%set_string(self%dest, values(1))
Expand All @@ -468,7 +474,9 @@ subroutine action_execute(self, args, values, num_values, error)
return
end if
if (int_val > int_bound) then
call error%init("value must not be bigger than " // trim(self%bound_str), values(1))
call error%init( &
"value must not be bigger than " // trim(self%bound_str), &
values(1))
return
end if
call args%set_integer(self%dest, int_val)
Expand All @@ -485,7 +493,9 @@ subroutine action_execute(self, args, values, num_values, error)
return
end if
if (real_val > real_bound) then
call error%init("value must not be bigger than " // trim(self%bound_str), values(1))
call error%init( &
"value must not be bigger than " // trim(self%bound_str), &
values(1))
return
end if
store_real_val = real(real_val)
Expand All @@ -500,7 +510,9 @@ subroutine action_execute(self, args, values, num_values, error)
return
end if
if (str_len > int_bound) then
call error%init("string length must not be bigger than " // trim(self%bound_str), values(1))
call error%init( &
"string length must not be bigger than " // trim(self%bound_str), &
values(1))
return
end if
call args%set_string(self%dest, values(1))
Expand Down
9 changes: 6 additions & 3 deletions src/fclap/fclap_formatter.f90
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ function format_usage_string(prog, actions, num_actions, &
if (.not. first_in_mutex) mutex_str = mutex_str // " | "
first_in_mutex = .false.
if (actions(m)%num_option_strings > 0) then
mutex_str = mutex_str // trim(actions(m)%option_strings(1))
mutex_str = mutex_str // &
trim(actions(m)%option_strings(1))
else
mutex_str = mutex_str // trim(actions(m)%dest)
end if
Expand Down Expand Up @@ -230,7 +231,8 @@ function format_help_text(prog, description, epilog, actions, num_actions, &

do j = 1, num_groups
if (groups(j)%num_actions > 0) then
help_text = help_text // new_line('A') // trim(groups(j)%title) // ":" // new_line('A')
help_text = help_text // new_line('A') // trim(groups(j)%title) // &
":" // new_line('A')
if (allocated(groups(j)%description)) then
help_text = help_text // " " // groups(j)%description // new_line('A')
end if
Expand Down Expand Up @@ -316,7 +318,8 @@ function format_help_text(prog, description, epilog, actions, num_actions, &

if (has_subparsers .and. num_subparsers > 0) then
if (present(subparser_title)) then
help_text = help_text // new_line('A') // trim(subparser_title) // ":" // new_line('A')
help_text = help_text // new_line('A') // trim(subparser_title) // &
":" // new_line('A')
else
help_text = help_text // new_line('A') // "commands:" // new_line('A')
end if
Expand Down
12 changes: 6 additions & 6 deletions src/fclap/fclap_namespace.f90
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
!> @file fclap_namespace.f90
!> @brief Namespace module for fclap - stores parsed argument values.
!>
!> @details This module defines the Namespace type (similar to Python's
!> argparse.Namespace) which stores the results of parsing command-line
!> @details This module defines the Namespace type (similar to Python's
!> argparse.Namespace) which stores the results of parsing command-line
!> arguments. It provides getter methods for retrieving values by key.
!>
!> The Namespace acts like a dictionary, allowing you to retrieve
Expand All @@ -13,7 +13,7 @@
!> character(len=256) :: filename
!> integer :: count
!> logical :: verbose
!>
!>
!> args = parser%parse_args()
!> call args%get("filename", filename)
!> call args%get("count", count, default=1)
Expand Down Expand Up @@ -522,7 +522,7 @@ end function namespace_get_logical
subroutine namespace_get_string_list(self, key, values, count)
class(Namespace), intent(in) :: self
character(len=*), intent(in) :: key
character(len=*), intent(out) :: values(:)
character(len=MAX_ARG_LEN), intent(out) :: values(:)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Changing values from assumed-length (character(len=*)) to fixed-length character(len=MAX_ARG_LEN) makes the public Namespace%get_string_list interface length-dependent; callers with character(len=...) arrays not exactly MAX_ARG_LEN will fail to compile. Consider keeping character(len=*), intent(out) :: values(:) (or providing an overload) so callers can choose their own buffer length and accept truncation/padding.

Suggested change
character(len=MAX_ARG_LEN), intent(out) :: values(:)
character(len=*), intent(out) :: values(:)

Copilot uses AI. Check for mistakes.
integer, intent(out) :: count
integer :: idx, i

Expand Down Expand Up @@ -620,7 +620,7 @@ end subroutine namespace_show
subroutine namespace_get_sub_string(self, key, value, default)
class(Namespace), intent(in) :: self
character(len=*), intent(in) :: key
character(len=*), intent(out) :: value
character(len=MAX_ARG_LEN), intent(out) :: value
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

namespace_get_sub_string is part of the generic args%get(...) API; switching the output dummy argument to character(len=MAX_ARG_LEN) forces callers to use exactly that length and breaks existing code that uses shorter/longer character(len=...) variables. Keeping character(len=*), intent(out) :: value preserves API compatibility and still allows safe assignment (with truncation/padding).

Suggested change
character(len=MAX_ARG_LEN), intent(out) :: value
character(len=*), intent(out) :: value

Copilot uses AI. Check for mistakes.
character(len=*), intent(in), optional :: default
character(len=:), allocatable :: tmp

Expand Down Expand Up @@ -682,7 +682,7 @@ end subroutine namespace_get_sub_logical
subroutine namespace_get_sub_string_list(self, key, values, count)
class(Namespace), intent(in) :: self
character(len=*), intent(in) :: key
character(len=*), intent(out) :: values(:)
character(len=MAX_ARG_LEN), intent(out) :: values(:)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

Similarly, namespace_get_sub_string_list is used via the generic args%get(key, values, count) interface; making the element length fixed to MAX_ARG_LEN makes the generic call fail to resolve unless the actual argument length matches exactly. Recommend reverting to character(len=*), intent(out) :: values(:) or adding an overload to support arbitrary caller buffer lengths.

Suggested change
character(len=MAX_ARG_LEN), intent(out) :: values(:)
character(len=*), intent(out) :: values(:)

Copilot uses AI. Check for mistakes.
integer, intent(out) :: count

call self%get_string_list(key, values, count)
Expand Down
23 changes: 16 additions & 7 deletions src/fclap/fclap_parser.f90
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
!> args = parser%parse_args()

module fclap_parser
use fclap_constants
use fclap_constants, only: ARG_OPTIONAL, ARG_ZERO_OR_MORE, ARG_ONE_OR_MORE, &
ARG_REMAINDER, ARG_SINGLE, MAX_ARG_LEN, MAX_OPTION_STRINGS, MAX_CHOICES, &
MAX_ACTIONS, MAX_SUBPARSERS, MAX_GROUPS, MAX_GROUP_ACTIONS, MAX_LIST_VALUES, &
TYPE_STRING, TYPE_INTEGER, TYPE_REAL, TYPE_LOGICAL, ACT_STORE, ACT_STORE_TRUE, &
ACT_STORE_FALSE, ACT_COUNT, ACT_APPEND, ACT_HELP, ACT_VERSION, &
ACT_NOT_LESS_THAN, ACT_NOT_BIGGER_THAN, STATUS_ACTIVE, GROUP_STANDARD
use fclap_errors, only: fclap_error
use fclap_namespace, only: Namespace, ValueContainer
use fclap_actions, only: Action, not_less_than, not_bigger_than
Expand Down Expand Up @@ -255,10 +260,10 @@ function get_prog_name(override) result(prog_name)
if (status == 0 .and. length > 0) then
allocate(character(len=length) :: arg0)
call get_command_argument(0, value=arg0, status=status)

idx = scan(arg0, '/', back=.true.)
if (idx == 0) idx = scan(arg0, '\', back=.true.)

if (idx > 0) then
prog_name = arg0(idx+1:)
else
Expand Down Expand Up @@ -962,9 +967,11 @@ subroutine parser_validate_mutex_groups(self, error)
if (j > 1) mutex_names = trim(mutex_names) // " "
do k = 1, self%num_actions
if (allocated(self%actions(k)%dest)) then
if (trim(self%actions(k)%dest) == trim(self%mutex_groups(i)%action_dests(j))) then
if (trim(self%actions(k)%dest) == &
trim(self%mutex_groups(i)%action_dests(j))) then
if (self%actions(k)%num_option_strings > 0) then
mutex_names = trim(mutex_names) // trim(self%actions(k)%option_strings(1))
mutex_names = trim(mutex_names) // &
trim(self%actions(k)%option_strings(1))
else
mutex_names = trim(mutex_names) // trim(self%actions(k)%dest)
end if
Expand Down Expand Up @@ -1116,11 +1123,13 @@ recursive function parser_parse_args_array(self, cmd_args) result(args)
do i = 1, num_remaining
remaining_args(i) = cmd_args(arg_idx + i - 1)
end do
sub_args = self%subparser_parsers(sub_idx)%parse_args_array(remaining_args)
sub_args = self%subparser_parsers(sub_idx)% &
parse_args_array(remaining_args)
deallocate(remaining_args)
else
allocate(remaining_args(0))
sub_args = self%subparser_parsers(sub_idx)%parse_args_array(remaining_args)
sub_args = self%subparser_parsers(sub_idx)% &
parse_args_array(remaining_args)
deallocate(remaining_args)
end if
! Merge subparser results into main namespace
Expand Down
2 changes: 1 addition & 1 deletion src/fclap/utils/accuracy.f90
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ module fclap_utils_accuracy
integer, parameter :: i8 = selected_int_kind(18)


end module fclap_utils_accuracy
end module fclap_utils_accuracy
2 changes: 1 addition & 1 deletion src/fclap/utils/fclap_version.f90
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ subroutine get_fclap_version(major, minor, patch, string)
end subroutine get_fclap_version


end module fclap_version
end module fclap_version
18 changes: 10 additions & 8 deletions test/unit/main.f90
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
!> Unit tests for fclap argument parser
program tester
use fclap
use fclap, only: ArgumentParser, Namespace, STATUS_DEPRECATED
implicit none

logical :: all_passed

all_passed = .true.

call test_basic_parsing(all_passed)
Expand Down Expand Up @@ -348,13 +348,15 @@ subroutine test_mutex_groups(passed)
print *, "Test: mutually exclusive groups..."

call parser%init(prog="test_prog", add_help=.false.)

! Create a mutex group
mutex_idx = parser%add_mutually_exclusive_group(required=.false.)

! Add mutually exclusive options
call parser%add_argument("--foo", action="store_true", help="Foo option", mutex_group_idx=mutex_idx)
call parser%add_argument("--bar", action="store_true", help="Bar option", mutex_group_idx=mutex_idx)
call parser%add_argument("--foo", action="store_true", help="Foo option", &
mutex_group_idx=mutex_idx)
call parser%add_argument("--bar", action="store_true", help="Bar option", &
mutex_group_idx=mutex_idx)

! Test with only one option (should work)
test_args(1) = "--foo"
Expand Down Expand Up @@ -422,10 +424,10 @@ subroutine test_argument_groups(passed)
print *, "Test: argument groups..."

call parser%init(prog="test_prog", add_help=.false.)

! Create an argument group
group_idx = parser%add_argument_group("Input Options", "Options related to input files")

call parser%add_argument("-i", "--input", help="Input file", group_idx=group_idx)
call parser%add_argument("-f", "--format", help="Input format", group_idx=group_idx)

Expand Down