Skip to content
7 changes: 2 additions & 5 deletions src/Cli/OptionsParser.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
83 changes: 36 additions & 47 deletions src/Cli/Program.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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 _ _ ->
Expand All @@ -741,7 +741,6 @@ parserToJsonSchemaFromTsTypes includeBoilerplate programName parser =
_ ->
Nothing
)
|> List.head

-- Build $cli schema (only subcommand + positional)
cliSubProperties : List ( String, Encode.Value )
Expand Down Expand Up @@ -855,8 +854,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 =
Expand Down Expand Up @@ -1143,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 _ ->
Expand All @@ -1159,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 =
Expand All @@ -1184,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: "
++ "`"
Expand All @@ -1202,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 ->
Expand All @@ -1217,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
Expand Down Expand Up @@ -1335,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 ->
Expand All @@ -1348,38 +1346,29 @@ 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\"."

else
let
missingFieldReasons : List String
missingFieldReasons =
reasons
|> List.filterMap
(\reason ->
case reason of
MissingRequiredKeywordArg { name } ->
Just ("Missing required field: \"" ++ name ++ "\"")

MissingRequiredPositionalArg { name } ->
Just ("Missing required field: \"" ++ name ++ "\"")
reasons
|> List.Extra.findMap
(\reason ->
case reason of
MissingRequiredKeywordArg { name } ->
Just ("Missing required field: \"" ++ name ++ "\"")

MissingExpectedFlag { name } ->
Just ("Missing required field: \"" ++ name ++ "\"")
MissingRequiredPositionalArg { name } ->
Just ("Missing required field: \"" ++ name ++ "\"")

_ ->
Nothing
)
in
case missingFieldReasons of
first :: _ ->
first
MissingExpectedFlag { name } ->
Just ("Missing required field: \"" ++ name ++ "\"")

[] ->
"No matching command found for JSON input."
_ ->
Nothing
)
|> Maybe.withDefault "No matching command found for JSON input."
11 changes: 5 additions & 6 deletions src/Cli/UsageSpec.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions src/Fuzzy.elm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module Fuzzy exposing

-}

import List.Extra
import String


Expand Down Expand Up @@ -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 ->
Expand All @@ -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
Expand Down
27 changes: 12 additions & 15 deletions src/Internal/OptionsParser.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -200,7 +201,7 @@ normalizeCliJson usageSpecs blob =
restArgsName : Maybe String
restArgsName =
usageSpecs
|> List.filterMap
|> List.Extra.findMap
(\spec ->
case spec of
UsageSpec.RestArgs restName _ ->
Expand All @@ -209,7 +210,6 @@ normalizeCliJson usageSpecs blob =
_ ->
Nothing
)
|> List.head

restFields : List ( String, Encode.Value )
restFields =
Expand Down Expand Up @@ -259,28 +259,25 @@ 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 =
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 ->
Ok 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 ->
Err _ ->
[]
in
unexpectedTopLevelFields ++ unexpectedCliFields
Expand Down Expand Up @@ -353,9 +350,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.length (List.filter 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
Expand Down
6 changes: 2 additions & 4 deletions src/TypoSuggestion.elm
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,12 @@ 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
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))
Loading