Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Print suggestions upon failing to find a package by name #1257

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Other improvements:
- Fixed empty output for `--verbose-stats` when there are no errors or warnings.
- Added support for `--package-set` options for `spago upgrade`.
- `spago repl` now writes a `.purs-repl` file, unless already there, containing `import Prelude`.
- Added typo suggestions upon failing to find a package by name.

## [0.21.0] - 2023-05-04

Expand Down
14 changes: 14 additions & 0 deletions spago.lock
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ workspace:
- routing-duplex
- spago-core
- strings
- strings-extra
- transformers
- tuples
- unfoldable
Expand Down Expand Up @@ -527,6 +528,7 @@ workspace:
- st
- string-parsers
- strings
- strings-extra
- stringutils
- tailrec
- these
Expand Down Expand Up @@ -674,6 +676,7 @@ workspace:
- st
- string-parsers
- strings
- strings-extra
- stringutils
- tailrec
- these
Expand Down Expand Up @@ -3006,6 +3009,17 @@ packages:
- tuples
- unfoldable
- unsafe-coerce
strings-extra:
type: registry
version: 4.0.0
integrity: sha256-tah7VFvEvFF4tpWXgWwGFIynpWHkYe5tpy6bWi5tMpU=
dependencies:
- arrays
- foldable-traversable
- maybe
- prelude
- strings
- unicode
stringutils:
type: registry
version: 0.0.12
Expand Down
1 change: 1 addition & 0 deletions spago.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ package:
- routing-duplex
- spago-core
- strings
- strings-extra
- transformers
- tuples
- unfoldable
Expand Down
12 changes: 10 additions & 2 deletions src/Spago/Command/Fetch.purs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Data.Int as Int
import Data.Map as Map
import Data.Newtype (wrap)
import Data.Set as Set
import Data.String (joinWith)
import Data.Traversable (sequence)
import Effect.Aff as Aff
import Effect.Ref as Ref
Expand Down Expand Up @@ -532,8 +533,15 @@ getTransitiveDepsFromPackageSet packageSet deps = do
printPackageError :: PackageName -> String
printPackageError p = " - " <> PackageName.print p <> "\n"

printNotInPackageSetError :: PackageName -> String
printNotInPackageSetError p = " - " <> PackageName.print p <> strSuggestions
where
strSuggestions = case typoSuggestions PackageName.print p (Map.keys packageSet) of
[] -> "\n"
suggestions -> " (did you mean: " <> joinWith ", " (PackageName.print <$> suggestions) <> ")\n"

init :: TransitiveDepsResult
init = { packages: (Map.empty :: Map PackageName Package), errors: mempty }
init = { packages: Map.empty, errors: mempty }

go :: Set PackageName -> PackageName -> StateT TransitiveDepsResult (Spago (FetchEnv a)) Unit
go seen dep = do
Expand Down Expand Up @@ -578,7 +586,7 @@ getTransitiveDepsFromPackageSet packageSet deps = do
when (not (Set.isEmpty errors.cycle)) do
die $ "The following packages have circular dependencies:\n" <> foldMap printPackageError (Set.toUnfoldable errors.cycle :: Array PackageName)
when (not (Set.isEmpty errors.notInPackageSet)) do
die $ "The following packages do not exist in your package set:\n" <> foldMap printPackageError errors.notInPackageSet
die $ "The following packages do not exist in your package set:\n" <> foldMap printNotInPackageSetError errors.notInPackageSet
when (not (Set.isEmpty errors.notInIndex)) do
die $ "The following packages do not exist in the package index:\n" <> foldMap printPackageError errors.notInIndex
pure packages
Expand Down
13 changes: 9 additions & 4 deletions src/Spago/Config.purs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ type ReadWorkspaceOptions =

-- | Reads all the configurations in the tree and builds up the Map of local
-- | packages to be integrated in the package set
readWorkspace :: ReadWorkspaceOptions -> Spago (Registry.RegistryEnv _) Workspace
readWorkspace :: ∀ a. ReadWorkspaceOptions -> Spago (Registry.RegistryEnv a) Workspace
readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do
logInfo "Reading Spago workspace configuration..."

Expand Down Expand Up @@ -272,9 +272,14 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do
Nothing -> die
$ [ toDoc $ "Selected package " <> PackageName.print name <> " was not found in the local packages." ]
<> case (Array.fromFoldable $ Map.keys workspacePackages) of
[] -> [ toDoc "No available packages." ]
pkgs -> [ toDoc "Available packages:", indent (toDoc pkgs) ]
Just p -> pure (Just p)
[] ->
[ toDoc "No available packages." ]
pkgs ->
case typoSuggestions PackageName.print name pkgs of
[] -> [ toDoc "All available packages:", indent (toDoc pkgs) ]
suggestions -> [ toDoc "Did you mean:", indent (toDoc suggestions) ]
Just p ->
pure (Just p)

logDebug "Reading the lockfile..."
maybeLockfileContents <- FS.exists "spago.lock" >>= case _ of
Expand Down
21 changes: 19 additions & 2 deletions src/Spago/Prelude.purs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
module Spago.Prelude
( HexString(..)
, OnlineStatus(..)
, isPrefix
, mkTemp
, mkTemp'
, module Spago.Core.Prelude
, parallelise
, parTraverseSpago
, parallelise
, parseLenientVersion
, parseUrl
, partitionEithers
, shaToHex
, typoSuggestions
, unsafeFromRight
, unsafeLog
, unsafeStringify
, withBackoff'
, withForwardSlashes
, isPrefix
) where

import Spago.Core.Prelude
Expand All @@ -29,6 +30,7 @@ import Data.Int as Int
import Data.Maybe as Maybe
import Data.String (Pattern(..), Replacement(..))
import Data.String as String
import Data.String.Extra (levenshtein)
import Data.Traversable (class Traversable)
import Effect.Aff as Aff
import Effect.Now as Now
Expand Down Expand Up @@ -169,3 +171,18 @@ withForwardSlashes = String.replaceAll (Pattern "\\") (Replacement "/")
isPrefix :: String.Pattern -> String -> Boolean
isPrefix p = isJust <<< String.stripPrefix p

typoSuggestions :: ∀ f a. Foldable.Foldable f => (a -> String) -> a -> f a -> Array a
typoSuggestions toString typo allOptions =
allOptions
# foldl pickClosestOnes []
# Array.sortWith snd
# Array.take 5
<#> fst
where
pickClosestOnes acc item =
let distance = levenshtein (toString typo) (toString item)
in
if distance <= 2 then
(item /\ distance) `Array.cons` acc
else
acc
6 changes: 4 additions & 2 deletions test-fixtures/missing-dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ Reading Spago workspace configuration...


❌ The following packages do not exist in your package set:
- bar
- foo
- arrys (did you mean: arrays)
- bar-bar-bar
- effcet (did you mean: effect)
- foo-foo-foo
6 changes: 6 additions & 0 deletions test-fixtures/package-typo-suggestions/1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Reading Spago workspace configuration...

❌ Selected package inder was not found in the local packages.
Did you mean:
finder
binder
5 changes: 5 additions & 0 deletions test-fixtures/package-typo-suggestions/2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Reading Spago workspace configuration...

❌ Selected package flounder was not found in the local packages.
Did you mean:
founder
8 changes: 8 additions & 0 deletions test-fixtures/package-typo-suggestions/3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Reading Spago workspace configuration...

❌ Selected package totally-bogus was not found in the local packages.
All available packages:
binder
finder
founder
root
15 changes: 15 additions & 0 deletions test/Spago/Errors.purs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ module Test.Spago.Errors where
import Test.Prelude

import Data.Array as Array
import Data.Foldable (traverse_)
import Data.String (joinWith)
import Data.String as String
import Spago.FS as FS
import Test.Spec (Spec)
import Test.Spec as Spec
import Test.Spec.Assertions.String (shouldContain)

spec :: Spec Unit
spec = Spec.around withTempDir do
Expand All @@ -20,3 +23,15 @@ spec = Spec.around withTempDir do
Spec.it "fails for package names that are too long" \{ spago, fixture } -> do
let name = String.joinWith "" $ Array.replicate 256 "a"
spago [ "init", "--name", name ] >>= shouldBeFailureErr (fixture "package-name-too-long-stderr.txt")

Spec.it "prints suggested package names when package is not found" \{ spago, fixture } -> do
spago [ "init", "--name", "root" ] >>= shouldBeSuccess

["finder", "binder", "founder"] # traverse_ \name -> do
FS.mkdirp name
FS.writeTextFile (name <> "/spago.yaml") $
"{ package: { name: \"" <> name <> "\", dependencies: [] } }"

spago [ "build", "-p", "inder" ] >>= shouldBeFailureErr (fixture "package-typo-suggestions/1.txt")
spago [ "build", "-p", "flounder" ] >>= shouldBeFailureErr (fixture "package-typo-suggestions/2.txt")
spago [ "build", "-p", "totally-bogus" ] >>= shouldBeFailureErr (fixture "package-typo-suggestions/3.txt")
2 changes: 1 addition & 1 deletion test/Spago/Install.purs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ spec = Spec.around withTempDir do

Spec.it "can't add dependencies that are not in the package set" \{ spago, fixture } -> do
spago [ "init", "--name", "aaaa", "--package-set", "29.3.0" ] >>= shouldBeSuccess
spago [ "install", "foo", "bar" ] >>= shouldBeFailureErr (fixture "missing-dependencies.txt")
spago [ "install", "foo-foo-foo", "bar-bar-bar", "effcet", "arrys" ] >>= shouldBeFailureErr (fixture "missing-dependencies.txt")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Making sure there are no packages accidentally fuzzy-matching "foo" or "bar".

Copy link
Member

Choose a reason for hiding this comment

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

Indeed, brilliant

checkFixture "spago.yaml" (fixture "spago-install-failure.yaml")

Spec.it "does not allow circular dependencies" \{ spago, fixture } -> do
Expand Down
Loading