@@ -85,6 +85,9 @@ class Flag:
8585 max_args: :class:`int`
8686 The maximum number of arguments the flag can accept.
8787 A negative value indicates an unlimited amount of arguments.
88+ positional: :class:`bool`
89+ Whether the flag is positional.
90+ A :class:`FlagConverter` can only handle one positional flag.
8891 override: :class:`bool`
8992 Whether multiple given values overrides the previous value.
9093 """
@@ -95,6 +98,7 @@ class Flag:
9598 annotation : Any = _missing_field_factory ()
9699 default : Any = _missing_field_factory ()
97100 max_args : int = _missing_field_factory ()
101+ positional : bool = _missing_field_factory ()
98102 override : bool = _missing_field_factory ()
99103 cast_to_dict : bool = False
100104
@@ -114,6 +118,7 @@ def flag(
114118 default : Any = MISSING ,
115119 max_args : int = MISSING ,
116120 override : bool = MISSING ,
121+ positional : bool = MISSING ,
117122) -> Any :
118123 """Override default functionality and parameters of the underlying :class:`FlagConverter`
119124 class attributes.
@@ -135,13 +140,16 @@ class attributes.
135140 override: :class:`bool`
136141 Whether multiple given values overrides the previous value. The default
137142 value depends on the annotation given.
143+ positional: :class:`bool`
144+ Whether the flag is positional or not. There can only be one positional flag.
138145 """
139146 return Flag (
140147 name = name ,
141148 aliases = aliases ,
142149 default = default ,
143150 max_args = max_args ,
144151 override = override ,
152+ positional = positional ,
145153 )
146154
147155
@@ -168,6 +176,7 @@ def get_flags(
168176 flags : dict [str , Flag ] = {}
169177 cache : dict [str , Any ] = {}
170178 names : set [str ] = set ()
179+ positional : Flag | None = None
171180 for name , annotation in annotations .items ():
172181 flag = namespace .pop (name , MISSING )
173182 if isinstance (flag , Flag ):
@@ -179,6 +188,14 @@ def get_flags(
179188 if flag .name is MISSING :
180189 flag .name = name
181190
191+ if flag .positional :
192+ if positional is not None :
193+ raise TypeError (
194+ f"{ flag .name !r} positional flag conflicts with { positional .name !r} flag."
195+ )
196+
197+ positional = flag
198+
182199 annotation = flag .annotation = resolve_annotation (
183200 flag .annotation , globals , locals , cache
184201 )
@@ -280,6 +297,7 @@ class FlagsMeta(type):
280297 __commands_flag_case_insensitive__ : bool
281298 __commands_flag_delimiter__ : str
282299 __commands_flag_prefix__ : str
300+ __commands_flag_positional__ : Flag | None
283301
284302 def __new__ (
285303 cls : type [type ],
@@ -340,9 +358,13 @@ def __new__(
340358 delimiter = attrs .setdefault ("__commands_flag_delimiter__" , ":" )
341359 prefix = attrs .setdefault ("__commands_flag_prefix__" , "" )
342360
361+ positional_flag : Flag | None = None
343362 for flag_name , flag in get_flags (attrs , global_ns , local_ns ).items ():
344363 flags [flag_name ] = flag
364+ if flag .positional :
365+ positional_flag = flag
345366 aliases .update ({alias_name : flag_name for alias_name in flag .aliases })
367+ attrs ["__commands_flag_positional__" ] = positional_flag
346368
347369 forbidden = set (delimiter ).union (prefix )
348370 for flag_name in flags :
@@ -542,10 +564,29 @@ def parse_flags(cls, argument: str) -> dict[str, list[str]]:
542564 result : dict [str , list [str ]] = {}
543565 flags = cls .__commands_flags__
544566 aliases = cls .__commands_flag_aliases__
567+ positional_flag = cls .__commands_flag_positional__
545568 last_position = 0
546569 last_flag : Flag | None = None
547570
548571 case_insensitive = cls .__commands_flag_case_insensitive__
572+
573+ if positional_flag is not None :
574+ match = cls .__commands_flag_regex__ .search (argument )
575+ if match is not None :
576+ begin , end = match .span (0 )
577+ value = argument [:begin ].strip ()
578+ else :
579+ value = argument .strip ()
580+ last_position = len (argument )
581+
582+ if value :
583+ name = (
584+ positional_flag .name .casefold ()
585+ if case_insensitive
586+ else positional_flag .name
587+ )
588+ result [name ] = [value ]
589+
549590 for match in cls .__commands_flag_regex__ .finditer (argument ):
550591 begin , end = match .span (0 )
551592 key = match .group ("flag" )
0 commit comments