From a0a8b388ef641aae8570ce1f7cabae39929e9a57 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:02:41 +0200 Subject: [PATCH 01/14] Avoid creating intermediate lists to count items --- src/Cli/OptionsParser.elm | 7 ++----- src/Cli/Program.elm | 3 +-- src/Cli/UsageSpec.elm | 11 +++++------ src/Internal/OptionsParser.elm | 3 ++- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/Cli/OptionsParser.elm b/src/Cli/OptionsParser.elm index 3e7725d..81eeb56 100644 --- a/src/Cli/OptionsParser.elm +++ b/src/Cli/OptionsParser.elm @@ -147,6 +147,7 @@ import Cli.OptionsParser.MatchResult import Cli.UsageSpec as UsageSpec exposing (UsageSpec) import Internal.OptionsParser as OPInternal import Json.Decode +import List.Extra import Occurences exposing (Occurences(..)) import Tokenizer exposing (ParsedOption) import TsJson.Decode as TsDecode @@ -324,11 +325,7 @@ expectedPositionalArgCountOrFail (OPInternal.OptionsParser ({ decoder, usageSpec \({ operands } as stuff) -> if not (UsageSpec.hasRestArgs usageSpecs) - && (operands |> List.length) - > (usageSpecs - |> List.filter UsageSpec.isOperand - |> List.length - ) + && (List.length operands > List.Extra.count UsageSpec.isOperand usageSpecs) then Cli.Decode.MatchError Cli.Decode.ExtraOperand |> Err diff --git a/src/Cli/Program.elm b/src/Cli/Program.elm index f36a737..d930017 100644 --- a/src/Cli/Program.elm +++ b/src/Cli/Program.elm @@ -855,8 +855,7 @@ positionalSchemaProperty positionalArgs maybeRestArgs = requiredCount : Int requiredCount = positionalArgs - |> List.filter (\( _, _, occ ) -> occ == Required) - |> List.length + |> List.Extra.count (\( _, _, occ ) -> occ == Required) restItemSchema : Maybe Encode.Value restItemSchema = diff --git a/src/Cli/UsageSpec.elm b/src/Cli/UsageSpec.elm index ed16426..282c410 100644 --- a/src/Cli/UsageSpec.elm +++ b/src/Cli/UsageSpec.elm @@ -115,19 +115,18 @@ changeUsageSpec possibleValues usageSpec = operandCount : List UsageSpec -> Int operandCount usageSpecs = usageSpecs - |> List.filterMap + |> List.Extra.count (\spec -> case spec of FlagOrKeywordArg _ _ _ _ -> - Nothing + False - Operand operandName _ _ _ -> - Just operandName + Operand _ _ _ _ -> + True RestArgs _ _ -> - Nothing + False ) - |> List.length optionExists : List UsageSpec -> String -> Maybe FlagOrKeywordArg diff --git a/src/Internal/OptionsParser.elm b/src/Internal/OptionsParser.elm index 50ee207..06abfea 100644 --- a/src/Internal/OptionsParser.elm +++ b/src/Internal/OptionsParser.elm @@ -12,6 +12,7 @@ import Cli.OptionsParser.MatchResult import Cli.UsageSpec as UsageSpec exposing (UsageSpec) import Json.Decode import Json.Encode as Encode +import List.Extra import Tokenizer exposing (ParsedOption) import TsJson.Type @@ -355,7 +356,7 @@ extraJsonPositionalErrors usageSpecs blob baseMatchResult = else case Json.Decode.decodeValue (Json.Decode.field "$cli" (Json.Decode.field "positional" (Json.Decode.list Json.Decode.value))) blob of Ok positionalValues -> - if List.length positionalValues > List.length (List.filter UsageSpec.isOperand usageSpecs) then + if List.length positionalValues > List.Extra.count UsageSpec.isOperand usageSpecs then [ Cli.OptionsParser.MatchResult.ExtraOperand ] else From 6237b6008fbcf8ed61f2b1ef4e62950e91300398 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:03:39 +0200 Subject: [PATCH 02/14] Extract variable --- src/Fuzzy.elm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Fuzzy.elm b/src/Fuzzy.elm index 071c409..c6f11b5 100644 --- a/src/Fuzzy.elm +++ b/src/Fuzzy.elm @@ -170,11 +170,14 @@ distance config needle hay = mPenalty = Tuple.first sorted * config.movePenalty + accumulatedLength = + accumulated |> List.length + hPenalty = - (String.length hay - (accumulated |> List.length)) * config.addPenalty + (String.length hay - accumulatedLength) * config.addPenalty nPenalty = - (String.length needle - (accumulated |> List.length)) * config.removePenalty + (String.length needle - accumulatedLength) * config.removePenalty accumulateInsertPenalty elem result = case result of From d6f987d730b36b1cc7f2078b1b2d0ce11a44bc5a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:03:55 +0200 Subject: [PATCH 03/14] Use List.Extra.find --- src/Fuzzy.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fuzzy.elm b/src/Fuzzy.elm index c6f11b5..2e85005 100644 --- a/src/Fuzzy.elm +++ b/src/Fuzzy.elm @@ -17,6 +17,7 @@ module Fuzzy exposing -} +import List.Extra import String @@ -151,8 +152,7 @@ distance config needle hay = String.indexes (String.fromChar c) hay hayIndex = - List.filter (\e -> not (List.member e indexList)) indexes - |> List.head + List.Extra.find (\e -> not (List.member e indexList)) indexes in case hayIndex of Just v -> From 5f2a1c348b24bf6c647a32db0b07a2adca400c7f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:08:00 +0200 Subject: [PATCH 04/14] Decode the length instead of decoding the list then computing the length --- src/Internal/OptionsParser.elm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Internal/OptionsParser.elm b/src/Internal/OptionsParser.elm index 06abfea..84aa5b5 100644 --- a/src/Internal/OptionsParser.elm +++ b/src/Internal/OptionsParser.elm @@ -354,9 +354,9 @@ extraJsonPositionalErrors usageSpecs blob baseMatchResult = [] else - case Json.Decode.decodeValue (Json.Decode.field "$cli" (Json.Decode.field "positional" (Json.Decode.list Json.Decode.value))) blob of - Ok positionalValues -> - if List.length positionalValues > List.Extra.count UsageSpec.isOperand usageSpecs then + case Json.Decode.decodeValue (Json.Decode.field "$cli" (Json.Decode.field "positional" (Json.Decode.field "length" Json.Decode.int))) blob of + Ok positionalValuesCount -> + if positionalValuesCount > List.Extra.count UsageSpec.isOperand usageSpecs then [ Cli.OptionsParser.MatchResult.ExtraOperand ] else From 9266237d183300b24dd7e9395eeecbc6c280bd1a Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:08:50 +0200 Subject: [PATCH 05/14] Use List.Extra.findMap --- src/Cli/Program.elm | 3 +-- src/Internal/OptionsParser.elm | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Cli/Program.elm b/src/Cli/Program.elm index d930017..91f6bbb 100644 --- a/src/Cli/Program.elm +++ b/src/Cli/Program.elm @@ -732,7 +732,7 @@ parserToJsonSchemaFromTsTypes includeBoilerplate programName parser = restArgSpec : Maybe ( UsageSpec, TsJson.Type.Type ) restArgSpec = specsWithTypes - |> List.filterMap + |> List.Extra.findMap (\( spec, ( _, tsType ) ) -> case spec of UsageSpec.RestArgs _ _ -> @@ -741,7 +741,6 @@ parserToJsonSchemaFromTsTypes includeBoilerplate programName parser = _ -> Nothing ) - |> List.head -- Build $cli schema (only subcommand + positional) cliSubProperties : List ( String, Encode.Value ) diff --git a/src/Internal/OptionsParser.elm b/src/Internal/OptionsParser.elm index 84aa5b5..a9316b3 100644 --- a/src/Internal/OptionsParser.elm +++ b/src/Internal/OptionsParser.elm @@ -201,7 +201,7 @@ normalizeCliJson usageSpecs blob = restArgsName : Maybe String restArgsName = usageSpecs - |> List.filterMap + |> List.Extra.findMap (\spec -> case spec of UsageSpec.RestArgs restName _ -> @@ -210,7 +210,6 @@ normalizeCliJson usageSpecs blob = _ -> Nothing ) - |> List.head restFields : List ( String, Encode.Value ) restFields = From 0c1981f3fdd2d69992dd18b1ec3e5d3399807cb5 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:09:51 +0200 Subject: [PATCH 06/14] Use List.Extra.find --- src/Cli/Program.elm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cli/Program.elm b/src/Cli/Program.elm index 91f6bbb..ed57966 100644 --- a/src/Cli/Program.elm +++ b/src/Cli/Program.elm @@ -1182,12 +1182,12 @@ formatNoMatchReasons colorMode programName parserInfo availableSubCommands optio -- But first check: is the "wrong" command actually a valid subcommand? -- If so, the error is something else (like ExtraOperand) let - unknownCommands : List String - unknownCommands = + maybeUnknownCommand : Maybe String + maybeUnknownCommand = wrongSubCommandReasons - |> List.filter (\cmd -> not (List.member cmd availableSubCommands)) + |> List.Extra.find (\cmd -> not (List.member cmd availableSubCommands)) in - case List.head unknownCommands of + case maybeUnknownCommand of Just unknownCommand -> applyRed colorMode "Unknown command: " ++ "`" From ccac330a9a10b24db3288aee48b4cf7127ffddb6 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:10:37 +0200 Subject: [PATCH 07/14] Use List.Extra.find --- src/Cli/Program.elm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Cli/Program.elm b/src/Cli/Program.elm index ed57966..1818330 100644 --- a/src/Cli/Program.elm +++ b/src/Cli/Program.elm @@ -1141,10 +1141,10 @@ formatNoMatchReasons colorMode programName parserInfo availableSubCommands optio -- These should take priority over "subcommand not found" errors -- Note: ExtraOperand is NOT included here because it's often from -- system parsers (help/version) and isn't specific enough - missingArgErrors : List NoMatchReason - missingArgErrors = + missingArgError : Maybe NoMatchReason + missingArgError = otherReasons - |> List.filter + |> List.Extra.find (\reason -> case reason of MissingRequiredPositionalArg _ -> @@ -1157,12 +1157,12 @@ formatNoMatchReasons colorMode programName parserInfo availableSubCommands optio False ) in - case missingArgErrors of - reason :: _ -> + case missingArgError of + Just reason -> -- A parser matched the structure but is missing a required argument formatSingleReason colorMode reason programName optionsParsers - [] -> + Nothing -> let wrongSubCommandReasons : List String wrongSubCommandReasons = From d4df85a44325a75629d5810efed2553d4646d232 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:12:06 +0200 Subject: [PATCH 08/14] Use List.any --- src/Cli/Program.elm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cli/Program.elm b/src/Cli/Program.elm index 1818330..de9da77 100644 --- a/src/Cli/Program.elm +++ b/src/Cli/Program.elm @@ -1200,10 +1200,10 @@ formatNoMatchReasons colorMode programName parserInfo availableSubCommands optio Nothing -> let -- ExtraOperand is only relevant if there are no subcommand-related issues - extraOperandErrors : List NoMatchReason - extraOperandErrors = + hasExtraOperandErrors : Bool + hasExtraOperandErrors = otherReasons - |> List.filter + |> List.any (\reason -> case reason of ExtraOperand -> @@ -1215,7 +1215,7 @@ formatNoMatchReasons colorMode programName parserInfo availableSubCommands optio in -- The command was valid but something else went wrong -- Check for ExtraOperand - if not (List.isEmpty extraOperandErrors) then + if hasExtraOperandErrors then formatSingleReason colorMode ExtraOperand programName optionsParsers else From 44ccb30341fd280b2dce22850250e4d745e7defb Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:14:01 +0200 Subject: [PATCH 09/14] Use List.Extra.findMap --- src/Cli/Program.elm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Cli/Program.elm b/src/Cli/Program.elm index de9da77..7145eb3 100644 --- a/src/Cli/Program.elm +++ b/src/Cli/Program.elm @@ -1333,10 +1333,10 @@ formatFallbackMessage colorMode programName optionsParsers = formatJsonNoMatchReasons : List NoMatchReason -> String formatJsonNoMatchReasons reasons = let - unexpectedFieldReasons : List String - unexpectedFieldReasons = + unexpectedFieldReason : Maybe String + unexpectedFieldReason = reasons - |> List.filterMap + |> List.Extra.findMap (\reason -> case reason of UnexpectedOption name -> @@ -1346,11 +1346,11 @@ formatJsonNoMatchReasons reasons = Nothing ) in - case unexpectedFieldReasons of - first :: _ -> + case unexpectedFieldReason of + Just first -> first - [] -> + Nothing -> if List.member ExtraOperand reasons then "Too many positional arguments in \"$cli.positional\"." From f3d7075abbc0a67d7ac3aa937335567d189d9fb0 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:14:59 +0200 Subject: [PATCH 10/14] Use Maybe.withDefault --- src/Cli/Program.elm | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/Cli/Program.elm b/src/Cli/Program.elm index 7145eb3..16f92fc 100644 --- a/src/Cli/Program.elm +++ b/src/Cli/Program.elm @@ -1355,29 +1355,20 @@ formatJsonNoMatchReasons reasons = "Too many positional arguments in \"$cli.positional\"." else - let - missingFieldReasons : List String - missingFieldReasons = - reasons - |> List.filterMap - (\reason -> - case reason of - MissingRequiredKeywordArg { name } -> - Just ("Missing required field: \"" ++ name ++ "\"") + reasons + |> List.Extra.findMap + (\reason -> + case reason of + MissingRequiredKeywordArg { name } -> + Just ("Missing required field: \"" ++ name ++ "\"") - MissingRequiredPositionalArg { name } -> - Just ("Missing required field: \"" ++ name ++ "\"") + MissingRequiredPositionalArg { name } -> + Just ("Missing required field: \"" ++ name ++ "\"") - MissingExpectedFlag { name } -> - Just ("Missing required field: \"" ++ name ++ "\"") + MissingExpectedFlag { name } -> + Just ("Missing required field: \"" ++ name ++ "\"") - _ -> - Nothing - ) - in - case missingFieldReasons of - first :: _ -> - first - - [] -> - "No matching command found for JSON input." + _ -> + Nothing + ) + |> Maybe.withDefault "No matching command found for JSON input." From ff23b2bfd84016198c329b4118d8eba1417a2676 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:17:35 +0200 Subject: [PATCH 11/14] Avoid a List.map iteration --- src/Internal/OptionsParser.elm | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Internal/OptionsParser.elm b/src/Internal/OptionsParser.elm index a9316b3..00ff613 100644 --- a/src/Internal/OptionsParser.elm +++ b/src/Internal/OptionsParser.elm @@ -267,18 +267,16 @@ rawJsonShapeErrors subCommand usageSpecs blob = unexpectedTopLevelFields : List Cli.OptionsParser.MatchResult.NoMatchReason unexpectedTopLevelFields = topLevelFields - |> List.map Tuple.first - |> List.filter (\fieldName -> not (List.member fieldName (allowedTopLevelFieldNames usageSpecs))) - |> List.map Cli.OptionsParser.MatchResult.UnexpectedOption + |> List.filter (\( fieldName, _ ) -> not (List.member fieldName (allowedTopLevelFieldNames usageSpecs))) + |> List.map (\( fieldName, _ ) -> Cli.OptionsParser.MatchResult.UnexpectedOption fieldName) unexpectedCliFields : List Cli.OptionsParser.MatchResult.NoMatchReason unexpectedCliFields = case cliValue of Just actualCliValue -> jsonObjectFields actualCliValue - |> List.map Tuple.first - |> List.filter (\fieldName -> not (List.member fieldName (allowedCliFieldNames subCommand usageSpecs))) - |> List.map (\fieldName -> Cli.OptionsParser.MatchResult.UnexpectedOption ("$cli." ++ fieldName)) + |> List.filter (\( fieldName, _ ) -> not (List.member fieldName (allowedCliFieldNames subCommand usageSpecs))) + |> List.map (\( fieldName, _ ) -> Cli.OptionsParser.MatchResult.UnexpectedOption ("$cli." ++ fieldName)) Nothing -> [] From 15b1ff1f3f728c293b21531b917bb4a72896be84 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:18:18 +0200 Subject: [PATCH 12/14] Don't convert to Maybe --- src/Internal/OptionsParser.elm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Internal/OptionsParser.elm b/src/Internal/OptionsParser.elm index 00ff613..fa978d0 100644 --- a/src/Internal/OptionsParser.elm +++ b/src/Internal/OptionsParser.elm @@ -259,10 +259,9 @@ rawJsonShapeErrors subCommand usageSpecs blob = topLevelFields = jsonObjectFields blob - cliValue : Maybe Json.Decode.Value + cliValue : Result Json.Decode.Error Json.Decode.Value cliValue = Json.Decode.decodeValue (Json.Decode.field "$cli" Json.Decode.value) blob - |> Result.toMaybe unexpectedTopLevelFields : List Cli.OptionsParser.MatchResult.NoMatchReason unexpectedTopLevelFields = @@ -273,12 +272,12 @@ rawJsonShapeErrors subCommand usageSpecs blob = unexpectedCliFields : List Cli.OptionsParser.MatchResult.NoMatchReason unexpectedCliFields = case cliValue of - Just actualCliValue -> + Ok actualCliValue -> jsonObjectFields actualCliValue |> List.filter (\( fieldName, _ ) -> not (List.member fieldName (allowedCliFieldNames subCommand usageSpecs))) |> List.map (\( fieldName, _ ) -> Cli.OptionsParser.MatchResult.UnexpectedOption ("$cli." ++ fieldName)) - Nothing -> + Err _ -> [] in unexpectedTopLevelFields ++ unexpectedCliFields From a35b19ef02f1f6afc217387e63ea5f96ab5235a8 Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:23:09 +0200 Subject: [PATCH 13/14] Combine filterMap and map --- src/TypoSuggestion.elm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/TypoSuggestion.elm b/src/TypoSuggestion.elm index a1197d2..7daa608 100644 --- a/src/TypoSuggestion.elm +++ b/src/TypoSuggestion.elm @@ -76,8 +76,7 @@ getSuggestions optionsParsers unexpectedOption = buildSubCommandSuggestions : List OptionsParser -> List TypoSuggestion buildSubCommandSuggestions optionsParsers = optionsParsers - |> List.filterMap .subCommand - |> List.map SubCommand + |> List.filterMap (\{ subCommand } -> Maybe.map SubCommand subCommand) optionSuggestions : List OptionsParser -> List TypoSuggestion From 7e4d14b534bdd475969fc743a2497643fc69d29f Mon Sep 17 00:00:00 2001 From: Jeroen Engels Date: Thu, 9 Apr 2026 10:23:50 +0200 Subject: [PATCH 14/14] Combine List.map --- src/TypoSuggestion.elm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/TypoSuggestion.elm b/src/TypoSuggestion.elm index 7daa608..31db7dc 100644 --- a/src/TypoSuggestion.elm +++ b/src/TypoSuggestion.elm @@ -84,5 +84,4 @@ optionSuggestions optionsParsers = optionsParsers |> List.concatMap .usageSpecs |> List.Extra.uniqueBy UsageSpec.name - |> List.map UsageSpec.name - |> List.map Flag + |> List.map (\usageSpec -> Flag (UsageSpec.name usageSpec))