@@ -368,9 +368,7 @@ func TestParse(t *testing.T) {
368368 }
369369 err := Parse (cmd , nil )
370370 require .Error (t , err )
371- // TODO(mf): consider improving this error message so it's obvious that a "required" flag
372- // was set by the cli author but not registered in the flag set
373- require .ErrorContains (t , err , `command "root": internal error: required flag -some-other-flag not found in flag set` )
371+ require .ErrorContains (t , err , `flag metadata references unknown flag "some-other-flag"` )
374372 })
375373 t .Run ("space in command name" , func (t * testing.T ) {
376374 t .Parallel ()
@@ -569,7 +567,7 @@ func TestParse(t *testing.T) {
569567 }
570568 err := Parse (cmd , []string {"--existing=value" })
571569 require .Error (t , err )
572- require .ErrorContains (t , err , "required flag -nonexistent not found in flag set" )
570+ require .ErrorContains (t , err , ` flag metadata references unknown flag "nonexistent"` )
573571 })
574572 t .Run ("args with special characters" , func (t * testing.T ) {
575573 t .Parallel ()
@@ -696,6 +694,148 @@ func TestParse(t *testing.T) {
696694 })
697695}
698696
697+ func TestShortFlags (t * testing.T ) {
698+ t .Parallel ()
699+
700+ t .Run ("short flag sets value" , func (t * testing.T ) {
701+ t .Parallel ()
702+ cmd := & Command {
703+ Name : "root" ,
704+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
705+ f .Bool ("verbose" , false , "enable verbose output" )
706+ f .String ("output" , "" , "output file" )
707+ }),
708+ FlagsMetadata : []FlagMetadata {
709+ {Name : "verbose" , Short : "v" },
710+ {Name : "output" , Short : "o" },
711+ },
712+ Exec : func (ctx context.Context , s * State ) error { return nil },
713+ }
714+ err := Parse (cmd , []string {"-v" , "-o" , "file.txt" })
715+ require .NoError (t , err )
716+ require .True (t , GetFlag [bool ](cmd .state , "verbose" ))
717+ require .Equal (t , "file.txt" , GetFlag [string ](cmd .state , "output" ))
718+ })
719+
720+ t .Run ("long flag still works with short alias defined" , func (t * testing.T ) {
721+ t .Parallel ()
722+ cmd := & Command {
723+ Name : "root" ,
724+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
725+ f .Bool ("verbose" , false , "enable verbose output" )
726+ }),
727+ FlagsMetadata : []FlagMetadata {
728+ {Name : "verbose" , Short : "v" },
729+ },
730+ Exec : func (ctx context.Context , s * State ) error { return nil },
731+ }
732+ err := Parse (cmd , []string {"-verbose" })
733+ require .NoError (t , err )
734+ require .True (t , GetFlag [bool ](cmd .state , "verbose" ))
735+ })
736+
737+ t .Run ("short flag with subcommand" , func (t * testing.T ) {
738+ t .Parallel ()
739+ child := & Command {
740+ Name : "child" ,
741+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
742+ f .String ("name" , "" , "the name" )
743+ }),
744+ FlagsMetadata : []FlagMetadata {
745+ {Name : "name" , Short : "n" },
746+ },
747+ Exec : func (ctx context.Context , s * State ) error { return nil },
748+ }
749+ root := & Command {
750+ Name : "root" ,
751+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
752+ f .Bool ("verbose" , false , "verbose" )
753+ }),
754+ FlagsMetadata : []FlagMetadata {
755+ {Name : "verbose" , Short : "v" },
756+ },
757+ SubCommands : []* Command {child },
758+ Exec : func (ctx context.Context , s * State ) error { return nil },
759+ }
760+ err := Parse (root , []string {"-v" , "child" , "-n" , "hello" })
761+ require .NoError (t , err )
762+ require .True (t , GetFlag [bool ](root .state , "verbose" ))
763+ require .Equal (t , "hello" , GetFlag [string ](root .state , "name" ))
764+ })
765+
766+ t .Run ("short and long flags are aliases sharing same value" , func (t * testing.T ) {
767+ t .Parallel ()
768+ cmd := & Command {
769+ Name : "root" ,
770+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
771+ f .Int ("count" , 0 , "number of items" )
772+ }),
773+ FlagsMetadata : []FlagMetadata {
774+ {Name : "count" , Short : "c" },
775+ },
776+ Exec : func (ctx context.Context , s * State ) error { return nil },
777+ }
778+ // Use short flag
779+ err := Parse (cmd , []string {"-c" , "42" })
780+ require .NoError (t , err )
781+ // Both short and long name should return the same value
782+ require .Equal (t , 42 , GetFlag [int ](cmd .state , "count" ))
783+ })
784+
785+ t .Run ("metadata references unknown flag" , func (t * testing.T ) {
786+ t .Parallel ()
787+ cmd := & Command {
788+ Name : "root" ,
789+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
790+ f .Bool ("verbose" , false , "enable verbose output" )
791+ }),
792+ FlagsMetadata : []FlagMetadata {
793+ {Name : "vrbose" , Short : "v" }, // typo in Name
794+ },
795+ Exec : func (ctx context.Context , s * State ) error { return nil },
796+ }
797+ err := Parse (cmd , []string {})
798+ require .Error (t , err )
799+ require .Contains (t , err .Error (), `flag metadata references unknown flag "vrbose"` )
800+ })
801+
802+ t .Run ("short alias must be single ASCII letter" , func (t * testing.T ) {
803+ t .Parallel ()
804+ cmd := & Command {
805+ Name : "root" ,
806+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
807+ f .Bool ("verbose" , false , "enable verbose output" )
808+ }),
809+ FlagsMetadata : []FlagMetadata {
810+ {Name : "verbose" , Short : "vv" },
811+ },
812+ Exec : func (ctx context.Context , s * State ) error { return nil },
813+ }
814+ err := Parse (cmd , []string {})
815+ require .Error (t , err )
816+ require .Contains (t , err .Error (), "short alias must be a single ASCII letter" )
817+ })
818+
819+ t .Run ("duplicate short alias" , func (t * testing.T ) {
820+ t .Parallel ()
821+ cmd := & Command {
822+ Name : "root" ,
823+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
824+ f .Bool ("verbose" , false , "enable verbose output" )
825+ f .Bool ("version" , false , "show version" )
826+ }),
827+ FlagsMetadata : []FlagMetadata {
828+ {Name : "verbose" , Short : "v" },
829+ {Name : "version" , Short : "v" },
830+ },
831+ Exec : func (ctx context.Context , s * State ) error { return nil },
832+ }
833+ err := Parse (cmd , []string {})
834+ require .Error (t , err )
835+ require .Contains (t , err .Error (), `duplicate short flag "v"` )
836+ })
837+ }
838+
699839func getCommand (t * testing.T , c * Command ) * Command {
700840 require .NotNil (t , c )
701841 require .NotNil (t , c .state )
0 commit comments