diff --git a/app/ghcup/GHCup/OptParse/Config.hs b/app/ghcup/GHCup/OptParse/Config.hs index 7d48cb3..e7de50e 100644 --- a/app/ghcup/GHCup/OptParse/Config.hs +++ b/app/ghcup/GHCup/OptParse/Config.hs @@ -51,7 +51,7 @@ data ConfigCommand = ShowConfig | SetConfig String (Maybe String) | InitConfig - | AddReleaseChannel URI + | AddReleaseChannel Bool URI @@ -74,7 +74,7 @@ configP = subparser showP = info (pure ShowConfig) (progDesc "Show current config (default)") setP = info argsP (progDesc "Set config KEY to VALUE (or specify as single json value)" <> footerDoc (Just $ text configSetFooter)) argsP = SetConfig <$> argument str (metavar "") <*> optional (argument str (metavar "YAML_VALUE")) - addP = info (AddReleaseChannel <$> argument (eitherReader uriParser) (metavar "URI" <> completer fileUri)) + addP = info (AddReleaseChannel <$> switch (long "force" <> help "Delete existing entry (if any) and append instead of failing") <*> argument (eitherReader uriParser) (metavar "URI" <> completer fileUri)) (progDesc "Add a release channel from a URI") @@ -203,22 +203,40 @@ config configCommand settings userConf keybindings runLogger = case configComman pure $ ExitFailure 65 VLeft _ -> pure $ ExitFailure 65 - AddReleaseChannel uri -> do - case urlSource settings of - AddSource xs -> do - doConfig (defaultUserSettings { uUrlSource = Just $ AddSource (xs <> [Right uri]) }) - pure ExitSuccess - GHCupURL -> do - doConfig (defaultUserSettings { uUrlSource = Just $ AddSource [Right uri] }) - pure ExitSuccess - OwnSource xs -> do - doConfig (defaultUserSettings { uUrlSource = Just $ OwnSource (xs <> [Right uri]) }) - pure ExitSuccess - OwnSpec spec -> do - doConfig (defaultUserSettings { uUrlSource = Just $ OwnSource ([Left spec, Right uri]) }) + AddReleaseChannel force uri -> do + r <- runE @'[DuplicateReleaseChannel] $ do + case urlSource settings of + AddSource xs -> do + when (not force && Right uri `elem` xs) $ throwE (DuplicateReleaseChannel uri) + lift $ doConfig (defaultUserSettings { uUrlSource = Just $ AddSource (appendUnique xs (Right uri)) }) + pure () + GHCupURL -> do + lift $ doConfig (defaultUserSettings { uUrlSource = Just $ AddSource [Right uri] }) + pure () + OwnSource xs -> do + when (not force && Right uri `elem` xs) $ throwE (DuplicateReleaseChannel uri) + lift $ doConfig (defaultUserSettings { uUrlSource = Just $ OwnSource (appendUnique xs (Right uri)) }) + pure () + OwnSpec spec -> do + lift $ doConfig (defaultUserSettings { uUrlSource = Just $ OwnSource [Left spec, Right uri] }) + pure () + case r of + VRight _ -> do pure ExitSuccess + VLeft e -> do + runLogger $ logError $ T.pack $ prettyHFError e + pure $ ExitFailure 15 where + -- appends the element to the end of the list, but also removes it from the existing list + appendUnique :: Eq a => [a] -> a -> [a] + appendUnique xs' e = go xs' + where + go [] = [e] + go (x:xs) + | x == e = go xs -- skip + | otherwise = x : go xs + doConfig :: MonadIO m => UserSettings -> m () doConfig usersettings = do let settings' = updateSettings usersettings userConf diff --git a/lib/GHCup/Errors.hs b/lib/GHCup/Errors.hs index d87a227..09e7a31 100644 --- a/lib/GHCup/Errors.hs +++ b/lib/GHCup/Errors.hs @@ -35,6 +35,8 @@ import URI.ByteString import qualified Data.Map.Strict as M import qualified Data.Text as T +import qualified Data.Text.Encoding as E +import qualified Data.Text.Encoding.Error as E import Data.Data (Proxy(..)) @@ -82,6 +84,7 @@ allHFError = unlines allErrors , let proxy = Proxy :: Proxy HadrianNotFound in format proxy , let proxy = Proxy :: Proxy ToolShadowed in format proxy , let proxy = Proxy :: Proxy ContentLengthError in format proxy + , let proxy = Proxy :: Proxy DuplicateReleaseChannel in format proxy , "" , "# high level errors (4000+)" , let proxy = Proxy :: Proxy DownloadFailed in format proxy @@ -640,6 +643,19 @@ instance HFErrorProject ContentLengthError where eBase _ = 340 eDesc _ = "File content length verification failed" +data DuplicateReleaseChannel = DuplicateReleaseChannel URI + deriving Show + +instance HFErrorProject DuplicateReleaseChannel where + eBase _ = 350 + eDesc _ = "Duplicate release channel detected when adding URI.\nGiving up. You can use '--force' to remove and append the duplicate URI (this may change order/semantics)." + +instance Pretty DuplicateReleaseChannel where + pPrint (DuplicateReleaseChannel uri) = + text $ "Duplicate release channel detected when adding: \n " + <> (T.unpack . E.decodeUtf8With E.lenientDecode . serializeURIRef') uri + <> "\nGiving up. You can use '--force' to remove and append the duplicate URI (this may change order/semantics)." + ------------------------- --[ High-level errors ]-- ------------------------- diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs index 8a9b918..bdd58d4 100644 --- a/lib/GHCup/Types.hs +++ b/lib/GHCup/Types.hs @@ -66,7 +66,7 @@ data GHCupInfo = GHCupInfo , _ghcupDownloads :: GHCupDownloads , _globalTools :: Map GlobalTool DownloadInfo } - deriving (Show, GHC.Generic) + deriving (Show, GHC.Generic, Eq) instance NFData GHCupInfo @@ -87,7 +87,7 @@ data Requirements = Requirements { _distroPKGs :: [Text] , _notes :: Text } - deriving (Show, GHC.Generic) + deriving (Show, GHC.Generic, Eq) instance NFData Requirements