parent
241dadbeb5
commit
4fef93b7b1
@ -7,6 +7,7 @@
|
|||||||
- reverse list order so latest is on top
|
- reverse list order so latest is on top
|
||||||
- expand the blues selected bar
|
- expand the blues selected bar
|
||||||
- show new latest versions in bright white
|
- show new latest versions in bright white
|
||||||
|
* allow configuration file and settings TUI hotkeys wrt #41
|
||||||
|
|
||||||
## 0.1.11 -- 2020-09-23
|
## 0.1.11 -- 2020-09-23
|
||||||
|
|
||||||
|
42
README.md
42
README.md
@ -13,6 +13,7 @@ Similar in scope to [rustup](https://github.com/rust-lang-nursery/rustup.rs), [p
|
|||||||
* [Manual install](#manual-install)
|
* [Manual install](#manual-install)
|
||||||
* [Vim integration](#vim-integration)
|
* [Vim integration](#vim-integration)
|
||||||
* [Usage](#usage)
|
* [Usage](#usage)
|
||||||
|
* [Configuration](#configuration)
|
||||||
* [Manpages](#manpages)
|
* [Manpages](#manpages)
|
||||||
* [Shell-completion](#shell-completion)
|
* [Shell-completion](#shell-completion)
|
||||||
* [Cross support](#cross-support)
|
* [Cross support](#cross-support)
|
||||||
@ -80,6 +81,47 @@ ghcup upgrade
|
|||||||
Generally this is meant to be used with [`cabal-install`](https://hackage.haskell.org/package/cabal-install), which
|
Generally this is meant to be used with [`cabal-install`](https://hackage.haskell.org/package/cabal-install), which
|
||||||
handles your haskell packages and can demand that [a specific version](https://cabal.readthedocs.io/en/latest/nix-local-build.html#cfg-flag---with-compiler) of `ghc` is available, which `ghcup` can do.
|
handles your haskell packages and can demand that [a specific version](https://cabal.readthedocs.io/en/latest/nix-local-build.html#cfg-flag---with-compiler) of `ghc` is available, which `ghcup` can do.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
A configuration file can be put in `~/.ghcup/config.yaml`. Here is the complete default
|
||||||
|
configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Cache downloads in ~/.ghcup/cache
|
||||||
|
cache: False
|
||||||
|
# Skip tarball checksum verification
|
||||||
|
no-verify: False
|
||||||
|
# enable verbosity
|
||||||
|
verbose: False
|
||||||
|
# When to keep build directories
|
||||||
|
keep-dirs: Errors # Always | Never | Errors
|
||||||
|
# Which downloader to use
|
||||||
|
downloader: Curl # Curl | Wget | Internal
|
||||||
|
|
||||||
|
# TUI key bindings,
|
||||||
|
# see https://hackage.haskell.org/package/vty-5.31/docs/Graphics-Vty-Input-Events.html#t:Key
|
||||||
|
# for possible values.
|
||||||
|
key-bindings:
|
||||||
|
up:
|
||||||
|
KUp: []
|
||||||
|
down:
|
||||||
|
KDown: []
|
||||||
|
quit:
|
||||||
|
KChar: 'q'
|
||||||
|
install:
|
||||||
|
KChar: 'i'
|
||||||
|
uninstall:
|
||||||
|
KChar: 'u'
|
||||||
|
set:
|
||||||
|
KChar: 's'
|
||||||
|
changelog:
|
||||||
|
KChar: 'c'
|
||||||
|
show-all:
|
||||||
|
KChar: 'a'
|
||||||
|
```
|
||||||
|
|
||||||
|
Partial configuration is fine. Command line options always overwrite the config file settings.
|
||||||
|
|
||||||
### Manpages
|
### Manpages
|
||||||
|
|
||||||
For man pages to work you need [man-db](http://man-db.nongnu.org/) as your `man` provider, then issue `man ghc`. Manpages only work for the currently set ghc.
|
For man pages to work you need [man-db](http://man-db.nongnu.org/) as your `man` provider, then issue `man ghc`. Manpages only work for the currently set ghc.
|
||||||
|
@ -193,7 +193,7 @@ validateTarballs dls = do
|
|||||||
where
|
where
|
||||||
downloadAll dli = do
|
downloadAll dli = do
|
||||||
dirs <- liftIO getDirs
|
dirs <- liftIO getDirs
|
||||||
let settings = AppState (Settings True False Never Curl False) dirs
|
let settings = AppState (Settings True False Never Curl False) dirs defaultKeyBindings
|
||||||
let runLogger = myLoggerT LoggerConfig { lcPrintDebug = True
|
let runLogger = myLoggerT LoggerConfig { lcPrintDebug = True
|
||||||
, colorOutter = B.hPut stderr
|
, colorOutter = B.hPut stderr
|
||||||
, rawOutter = (\_ -> pure ())
|
, rawOutter = (\_ -> pure ())
|
||||||
|
@ -36,7 +36,6 @@ import Data.Bool
|
|||||||
import Data.Functor
|
import Data.Functor
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.Char
|
|
||||||
import Data.IORef
|
import Data.IORef
|
||||||
import Data.String.Interpolate
|
import Data.String.Interpolate
|
||||||
import Data.Vector ( Vector
|
import Data.Vector ( Vector
|
||||||
@ -77,33 +76,44 @@ data BrickState = BrickState
|
|||||||
{ appData :: BrickData
|
{ appData :: BrickData
|
||||||
, appSettings :: BrickSettings
|
, appSettings :: BrickSettings
|
||||||
, appState :: BrickInternalState
|
, appState :: BrickInternalState
|
||||||
|
, appKeys :: KeyBindings
|
||||||
}
|
}
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
|
|
||||||
keyHandlers :: [ ( Char
|
keyHandlers :: KeyBindings
|
||||||
|
-> [ ( Vty.Key
|
||||||
, BrickSettings -> String
|
, BrickSettings -> String
|
||||||
, BrickState -> EventM n (Next BrickState)
|
, BrickState -> EventM n (Next BrickState)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
keyHandlers =
|
keyHandlers KeyBindings {..} =
|
||||||
[ ('q', const "Quit" , halt)
|
[ (bQuit, const "Quit" , halt)
|
||||||
, ('i', const "Install" , withIOAction install')
|
, (bInstall, const "Install" , withIOAction install')
|
||||||
, ('u', const "Uninstall", withIOAction del')
|
, (bUninstall, const "Uninstall", withIOAction del')
|
||||||
, ('s', const "Set" , withIOAction set')
|
, (bSet, const "Set" , withIOAction set')
|
||||||
, ('c', const "ChangeLog", withIOAction changelog')
|
, (bChangelog, const "ChangeLog", withIOAction changelog')
|
||||||
, ( 'a'
|
, ( bShowAll
|
||||||
, (\BrickSettings {..} ->
|
, (\BrickSettings {..} ->
|
||||||
if showAll then "Hide old versions" else "Show all versions"
|
if showAll then "Hide old versions" else "Show all versions"
|
||||||
)
|
)
|
||||||
, hideShowHandler
|
, hideShowHandler
|
||||||
)
|
)
|
||||||
|
, (bUp, const "Up", \BrickState {..} -> continue (BrickState { appState = (moveCursor 1 appState Up), .. }))
|
||||||
|
, (bDown, const "Down", \BrickState {..} -> continue (BrickState { appState = (moveCursor 1 appState Down), .. }))
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
hideShowHandler (BrickState {..}) =
|
hideShowHandler (BrickState {..}) =
|
||||||
let newAppSettings = appSettings { showAll = not . showAll $ appSettings }
|
let newAppSettings = appSettings { showAll = not . showAll $ appSettings }
|
||||||
newInternalState = constructList appData newAppSettings (Just appState)
|
newInternalState = constructList appData newAppSettings (Just appState)
|
||||||
in continue (BrickState appData newAppSettings newInternalState)
|
in continue (BrickState appData newAppSettings newInternalState appKeys)
|
||||||
|
|
||||||
|
|
||||||
|
showKey :: Vty.Key -> String
|
||||||
|
showKey (Vty.KChar c) = [c]
|
||||||
|
showKey (Vty.KUp) = "↑"
|
||||||
|
showKey (Vty.KDown) = "↓"
|
||||||
|
showKey key = tail (show key)
|
||||||
|
|
||||||
|
|
||||||
ui :: BrickState -> Widget String
|
ui :: BrickState -> Widget String
|
||||||
@ -122,8 +132,7 @@ ui BrickState { appSettings = as@(BrickSettings {}), ..}
|
|||||||
. txtWrap
|
. txtWrap
|
||||||
. T.pack
|
. T.pack
|
||||||
. foldr1 (\x y -> x <> " " <> y)
|
. foldr1 (\x y -> x <> " " <> y)
|
||||||
. (++ ["↑↓:Navigation"])
|
$ (fmap (\(key, s, _) -> (showKey key <> ":" <> s as)) $ keyHandlers appKeys)
|
||||||
$ (fmap (\(c, s, _) -> (c : ':' : s as)) keyHandlers)
|
|
||||||
header =
|
header =
|
||||||
(minHSize 2 $ emptyWidget)
|
(minHSize 2 $ emptyWidget)
|
||||||
<+> (padLeft (Pad 2) $ minHSize 6 $ str "Tool")
|
<+> (padLeft (Pad 2) $ minHSize 6 $ str "Tool")
|
||||||
@ -261,24 +270,30 @@ dimAttributes = attrMap
|
|||||||
, ("no-bindist", Vty.defAttr `Vty.withStyle` Vty.dim)
|
, ("no-bindist", Vty.defAttr `Vty.withStyle` Vty.dim)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
eventHandler :: BrickState -> BrickEvent n e -> EventM n (Next BrickState)
|
eventHandler :: BrickState -> BrickEvent n e -> EventM n (Next BrickState)
|
||||||
eventHandler st (VtyEvent (Vty.EvResize _ _)) = continue st
|
eventHandler st@(BrickState {..}) ev = do
|
||||||
eventHandler st (VtyEvent (Vty.EvKey (Vty.KChar 'q') _)) = halt st
|
AppState { keyBindings = kb } <- liftIO $ readIORef settings'
|
||||||
eventHandler st (VtyEvent (Vty.EvKey Vty.KEsc _)) = halt st
|
case ev of
|
||||||
eventHandler BrickState {..} (VtyEvent (Vty.EvKey (Vty.KUp) _)) =
|
(MouseDown _ Vty.BScrollUp _ _) ->
|
||||||
continue (BrickState { appState = (moveCursor appState Up), .. })
|
continue (BrickState { appState = moveCursor 1 appState Up, .. })
|
||||||
eventHandler BrickState {..} (VtyEvent (Vty.EvKey (Vty.KDown) _)) =
|
(MouseDown _ Vty.BScrollDown _ _) ->
|
||||||
continue (BrickState { appState = (moveCursor appState Down), .. })
|
continue (BrickState { appState = moveCursor 1 appState Down, .. })
|
||||||
eventHandler as (VtyEvent (Vty.EvKey (Vty.KChar c) _)) =
|
(VtyEvent (Vty.EvResize _ _)) -> continue st
|
||||||
case find (\(c', _, _) -> c' == c) keyHandlers of
|
(VtyEvent (Vty.EvKey Vty.KUp _)) ->
|
||||||
Nothing -> continue as
|
continue (BrickState { appState = (moveCursor 1 appState Up), .. })
|
||||||
Just (_, _, handler) -> handler as
|
(VtyEvent (Vty.EvKey Vty.KDown _)) ->
|
||||||
eventHandler st _ = continue st
|
continue (BrickState { appState = (moveCursor 1 appState Down), .. })
|
||||||
|
(VtyEvent (Vty.EvKey key _)) ->
|
||||||
|
case find (\(key', _, _) -> key' == key) (keyHandlers kb) of
|
||||||
|
Nothing -> continue st
|
||||||
|
Just (_, _, handler) -> handler st
|
||||||
|
_ -> continue st
|
||||||
|
|
||||||
|
|
||||||
moveCursor :: BrickInternalState -> Direction -> BrickInternalState
|
moveCursor :: Int -> BrickInternalState -> Direction -> BrickInternalState
|
||||||
moveCursor ais@(BrickInternalState {..}) direction =
|
moveCursor steps ais@(BrickInternalState {..}) direction =
|
||||||
let newIx = if direction == Down then ix + 1 else ix - 1
|
let newIx = if direction == Down then ix + steps else ix - steps
|
||||||
in case clr !? newIx of
|
in case clr !? newIx of
|
||||||
Just _ -> BrickInternalState { ix = newIx, .. }
|
Just _ -> BrickInternalState { ix = newIx, .. }
|
||||||
Nothing -> ais
|
Nothing -> ais
|
||||||
@ -310,9 +325,10 @@ updateList :: BrickData -> BrickState -> BrickState
|
|||||||
updateList appD (BrickState {..}) =
|
updateList appD (BrickState {..}) =
|
||||||
let newInternalState = constructList appD appSettings (Just appState)
|
let newInternalState = constructList appD appSettings (Just appState)
|
||||||
in BrickState { appState = newInternalState
|
in BrickState { appState = newInternalState
|
||||||
, appData = appD
|
, appData = appD
|
||||||
, appSettings = appSettings
|
, appSettings = appSettings
|
||||||
}
|
, appKeys = appKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
constructList :: BrickData
|
constructList :: BrickData
|
||||||
@ -481,6 +497,7 @@ settings' = unsafePerformIO $ do
|
|||||||
, ..
|
, ..
|
||||||
})
|
})
|
||||||
dirs
|
dirs
|
||||||
|
defaultKeyBindings
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -515,6 +532,8 @@ brickMain s muri l av pfreq' = do
|
|||||||
(BrickState ad
|
(BrickState ad
|
||||||
defaultAppSettings
|
defaultAppSettings
|
||||||
(constructList ad defaultAppSettings Nothing)
|
(constructList ad defaultAppSettings Nothing)
|
||||||
|
(keyBindings s)
|
||||||
|
|
||||||
)
|
)
|
||||||
$> ()
|
$> ()
|
||||||
Left e -> do
|
Left e -> do
|
||||||
|
@ -81,12 +81,12 @@ import qualified Text.Megaparsec.Char as MPC
|
|||||||
data Options = Options
|
data Options = Options
|
||||||
{
|
{
|
||||||
-- global options
|
-- global options
|
||||||
optVerbose :: Bool
|
optVerbose :: Maybe Bool
|
||||||
, optCache :: Bool
|
, optCache :: Maybe Bool
|
||||||
, optUrlSource :: Maybe URI
|
, optUrlSource :: Maybe URI
|
||||||
, optNoVerify :: Bool
|
, optNoVerify :: Maybe Bool
|
||||||
, optKeepDirs :: KeepDirs
|
, optKeepDirs :: Maybe KeepDirs
|
||||||
, optsDownloader :: Downloader
|
, optsDownloader :: Maybe Downloader
|
||||||
-- commands
|
-- commands
|
||||||
, optCommand :: Command
|
, optCommand :: Command
|
||||||
}
|
}
|
||||||
@ -180,13 +180,48 @@ data ChangeLogOptions = ChangeLogOptions
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- https://github.com/pcapriotti/optparse-applicative/issues/148
|
||||||
|
|
||||||
|
-- | A switch that can be enabled using --foo and disabled using --no-foo.
|
||||||
|
--
|
||||||
|
-- The option modifier is applied to only the option that is *not* enabled
|
||||||
|
-- by default. For example:
|
||||||
|
--
|
||||||
|
-- > invertableSwitch "recursive" True (help "do not recurse into directories")
|
||||||
|
--
|
||||||
|
-- This example makes --recursive enabled by default, so
|
||||||
|
-- the help is shown only for --no-recursive.
|
||||||
|
invertableSwitch
|
||||||
|
:: String -- ^ long option
|
||||||
|
-> Char -- ^ short option for the non-default option
|
||||||
|
-> Bool -- ^ is switch enabled by default?
|
||||||
|
-> Mod FlagFields Bool -- ^ option modifier
|
||||||
|
-> Parser (Maybe Bool)
|
||||||
|
invertableSwitch longopt shortopt defv optmod = invertableSwitch' longopt shortopt defv
|
||||||
|
(if defv then mempty else optmod)
|
||||||
|
(if defv then optmod else mempty)
|
||||||
|
|
||||||
|
-- | Allows providing option modifiers for both --foo and --no-foo.
|
||||||
|
invertableSwitch'
|
||||||
|
:: String -- ^ long option (eg "foo")
|
||||||
|
-> Char -- ^ short option for the non-default option
|
||||||
|
-> Bool -- ^ is switch enabled by default?
|
||||||
|
-> Mod FlagFields Bool -- ^ option modifier for --foo
|
||||||
|
-> Mod FlagFields Bool -- ^ option modifier for --no-foo
|
||||||
|
-> Parser (Maybe Bool)
|
||||||
|
invertableSwitch' longopt shortopt defv enmod dismod = optional
|
||||||
|
( flag' True (enmod <> long longopt <> if defv then mempty else short shortopt)
|
||||||
|
<|> flag' False (dismod <> long nolongopt <> if defv then short shortopt else mempty)
|
||||||
|
)
|
||||||
|
where
|
||||||
|
nolongopt = "no-" ++ longopt
|
||||||
|
|
||||||
|
|
||||||
opts :: Parser Options
|
opts :: Parser Options
|
||||||
opts =
|
opts =
|
||||||
Options
|
Options
|
||||||
<$> switch (short 'v' <> long "verbose" <> help "Enable verbosity")
|
<$> invertableSwitch "verbose" 'v' False (help "Enable verbosity (default: disabled)")
|
||||||
<*> switch
|
<*> invertableSwitch "cache" 'c' False (help "Cache downloads in ~/.ghcup/cache (default: disabled)")
|
||||||
(short 'c' <> long "cache" <> help "Cache downloads in ~/.ghcup/cache"
|
|
||||||
)
|
|
||||||
<*> (optional
|
<*> (optional
|
||||||
(option
|
(option
|
||||||
(eitherReader parseUri)
|
(eitherReader parseUri)
|
||||||
@ -198,35 +233,29 @@ opts =
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
<*> switch
|
<*> (fmap . fmap) not (invertableSwitch "verify" 'n' True (help "Disable tarball checksum verification (default: enabled)"))
|
||||||
(short 'n' <> long "no-verify" <> help
|
<*> optional (option
|
||||||
"Skip tarball checksum verification"
|
|
||||||
)
|
|
||||||
<*> option
|
|
||||||
(eitherReader keepOnParser)
|
(eitherReader keepOnParser)
|
||||||
( long "keep"
|
( long "keep"
|
||||||
<> metavar "<always|errors|never>"
|
<> metavar "<always|errors|never>"
|
||||||
<> help
|
<> help
|
||||||
"Keep build directories? (default: errors)"
|
"Keep build directories? (default: errors)"
|
||||||
<> value Errors
|
|
||||||
<> hidden
|
<> hidden
|
||||||
)
|
))
|
||||||
<*> option
|
<*> optional (option
|
||||||
(eitherReader downloaderParser)
|
(eitherReader downloaderParser)
|
||||||
( long "downloader"
|
( long "downloader"
|
||||||
#if defined(INTERNAL_DOWNLOADER)
|
#if defined(INTERNAL_DOWNLOADER)
|
||||||
<> metavar "<internal|curl|wget>"
|
<> metavar "<internal|curl|wget>"
|
||||||
<> help
|
<> help
|
||||||
"Downloader to use (default: internal)"
|
"Downloader to use (default: internal)"
|
||||||
<> value Internal
|
|
||||||
#else
|
#else
|
||||||
<> metavar "<curl|wget>"
|
<> metavar "<curl|wget>"
|
||||||
<> help
|
<> help
|
||||||
"Downloader to use (default: curl)"
|
"Downloader to use (default: curl)"
|
||||||
<> value Curl
|
|
||||||
#endif
|
#endif
|
||||||
<> hidden
|
<> hidden
|
||||||
)
|
))
|
||||||
<*> com
|
<*> com
|
||||||
where
|
where
|
||||||
parseUri s' =
|
parseUri s' =
|
||||||
@ -857,14 +886,44 @@ bindistParser = first show . parseURI strictURIParserOptions . UTF8.fromString
|
|||||||
|
|
||||||
|
|
||||||
toSettings :: Options -> IO AppState
|
toSettings :: Options -> IO AppState
|
||||||
toSettings Options {..} = do
|
toSettings options = do
|
||||||
let cache = optCache
|
|
||||||
noVerify = optNoVerify
|
|
||||||
keepDirs = optKeepDirs
|
|
||||||
downloader = optsDownloader
|
|
||||||
verbose = optVerbose
|
|
||||||
dirs <- getDirs
|
dirs <- getDirs
|
||||||
pure $ AppState (Settings { .. }) dirs
|
userConf <- runE @'[ JSONError ] ghcupConfigFile >>= \case
|
||||||
|
VRight r -> pure r
|
||||||
|
VLeft (V (JSONDecodeError e)) -> do
|
||||||
|
B.hPut stderr ("Error decoding config file: " <> (E.encodeUtf8 . T.pack . show $ e))
|
||||||
|
pure defaultUserSettings
|
||||||
|
_ -> do
|
||||||
|
die "Unexpected error!"
|
||||||
|
pure $ mergeConf options dirs userConf
|
||||||
|
where
|
||||||
|
mergeConf :: Options -> Dirs -> UserSettings -> AppState
|
||||||
|
mergeConf (Options {..}) dirs (UserSettings {..}) =
|
||||||
|
let cache = fromMaybe (fromMaybe False uCache) optCache
|
||||||
|
noVerify = fromMaybe (fromMaybe False uNoVerify) optNoVerify
|
||||||
|
verbose = fromMaybe (fromMaybe False uVerbose) optVerbose
|
||||||
|
keepDirs = fromMaybe (fromMaybe Errors uKeepDirs) optKeepDirs
|
||||||
|
downloader = fromMaybe (fromMaybe defaultDownloader uDownloader) optsDownloader
|
||||||
|
keyBindings = maybe defaultKeyBindings mergeKeys uKeyBindings
|
||||||
|
in AppState (Settings {..}) dirs keyBindings
|
||||||
|
#if defined(INTERNAL_DOWNLOADER)
|
||||||
|
defaultDownloader = Internal
|
||||||
|
#else
|
||||||
|
defaultDownloader = Curl
|
||||||
|
#endif
|
||||||
|
mergeKeys :: UserKeyBindings -> KeyBindings
|
||||||
|
mergeKeys UserKeyBindings {..} =
|
||||||
|
let KeyBindings {..} = defaultKeyBindings
|
||||||
|
in KeyBindings {
|
||||||
|
bUp = fromMaybe bUp kUp
|
||||||
|
, bDown = fromMaybe bDown kDown
|
||||||
|
, bQuit = fromMaybe bQuit kQuit
|
||||||
|
, bInstall = fromMaybe bInstall kInstall
|
||||||
|
, bUninstall = fromMaybe bUninstall kUninstall
|
||||||
|
, bSet = fromMaybe bSet kSet
|
||||||
|
, bChangelog = fromMaybe bChangelog kChangelog
|
||||||
|
, bShowAll = fromMaybe bShowAll kShowAll
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
upgradeOptsP :: Parser UpgradeOpts
|
upgradeOptsP :: Parser UpgradeOpts
|
||||||
@ -948,7 +1007,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
|
|||||||
-- logger interpreter
|
-- logger interpreter
|
||||||
logfile <- flip runReaderT appstate $ initGHCupFileLogging [rel|ghcup.log|]
|
logfile <- flip runReaderT appstate $ initGHCupFileLogging [rel|ghcup.log|]
|
||||||
let loggerConfig = LoggerConfig
|
let loggerConfig = LoggerConfig
|
||||||
{ lcPrintDebug = optVerbose
|
{ lcPrintDebug = verbose settings
|
||||||
, colorOutter = B.hPut stderr
|
, colorOutter = B.hPut stderr
|
||||||
, rawOutter = appendFile logfile
|
, rawOutter = appendFile logfile
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,9 @@ common bz2
|
|||||||
common case-insensitive
|
common case-insensitive
|
||||||
build-depends: case-insensitive >=1.2.1.0
|
build-depends: case-insensitive >=1.2.1.0
|
||||||
|
|
||||||
|
common casing
|
||||||
|
build-depends: casing >=0.1.4.1
|
||||||
|
|
||||||
common concurrent-output
|
common concurrent-output
|
||||||
build-depends: concurrent-output >=1.10.11
|
build-depends: concurrent-output >=1.10.11
|
||||||
|
|
||||||
@ -266,6 +269,7 @@ library
|
|||||||
, bytestring
|
, bytestring
|
||||||
, bz2
|
, bz2
|
||||||
, case-insensitive
|
, case-insensitive
|
||||||
|
, casing
|
||||||
, concurrent-output
|
, concurrent-output
|
||||||
, containers
|
, containers
|
||||||
, cryptohash-sha256
|
, cryptohash-sha256
|
||||||
@ -307,6 +311,7 @@ library
|
|||||||
, utf8-string
|
, utf8-string
|
||||||
, vector
|
, vector
|
||||||
, versions
|
, versions
|
||||||
|
, vty
|
||||||
, word8
|
, word8
|
||||||
, yaml
|
, yaml
|
||||||
, zlib
|
, zlib
|
||||||
|
@ -21,6 +21,7 @@ import URI.ByteString
|
|||||||
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified GHC.Generics as GHC
|
import qualified GHC.Generics as GHC
|
||||||
|
import qualified Graphics.Vty as Vty
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -193,9 +194,59 @@ data URLSource = GHCupURL
|
|||||||
deriving (GHC.Generic, Show)
|
deriving (GHC.Generic, Show)
|
||||||
|
|
||||||
|
|
||||||
|
data UserSettings = UserSettings
|
||||||
|
{ uCache :: Maybe Bool
|
||||||
|
, uNoVerify :: Maybe Bool
|
||||||
|
, uVerbose :: Maybe Bool
|
||||||
|
, uKeepDirs :: Maybe KeepDirs
|
||||||
|
, uDownloader :: Maybe Downloader
|
||||||
|
, uKeyBindings :: Maybe UserKeyBindings
|
||||||
|
}
|
||||||
|
deriving (Show, GHC.Generic)
|
||||||
|
|
||||||
|
defaultUserSettings :: UserSettings
|
||||||
|
defaultUserSettings = UserSettings Nothing Nothing Nothing Nothing Nothing Nothing
|
||||||
|
|
||||||
|
data UserKeyBindings = UserKeyBindings
|
||||||
|
{ kUp :: Maybe Vty.Key
|
||||||
|
, kDown :: Maybe Vty.Key
|
||||||
|
, kQuit :: Maybe Vty.Key
|
||||||
|
, kInstall :: Maybe Vty.Key
|
||||||
|
, kUninstall :: Maybe Vty.Key
|
||||||
|
, kSet :: Maybe Vty.Key
|
||||||
|
, kChangelog :: Maybe Vty.Key
|
||||||
|
, kShowAll :: Maybe Vty.Key
|
||||||
|
}
|
||||||
|
deriving (Show, GHC.Generic)
|
||||||
|
|
||||||
|
data KeyBindings = KeyBindings
|
||||||
|
{ bUp :: Vty.Key
|
||||||
|
, bDown :: Vty.Key
|
||||||
|
, bQuit :: Vty.Key
|
||||||
|
, bInstall :: Vty.Key
|
||||||
|
, bUninstall :: Vty.Key
|
||||||
|
, bSet :: Vty.Key
|
||||||
|
, bChangelog :: Vty.Key
|
||||||
|
, bShowAll :: Vty.Key
|
||||||
|
}
|
||||||
|
deriving (Show, GHC.Generic)
|
||||||
|
|
||||||
|
defaultKeyBindings :: KeyBindings
|
||||||
|
defaultKeyBindings = KeyBindings
|
||||||
|
{ bUp = Vty.KUp
|
||||||
|
, bDown = Vty.KDown
|
||||||
|
, bQuit = Vty.KChar 'q'
|
||||||
|
, bInstall = Vty.KChar 'i'
|
||||||
|
, bUninstall = Vty.KChar 'u'
|
||||||
|
, bSet = Vty.KChar 's'
|
||||||
|
, bChangelog = Vty.KChar 'c'
|
||||||
|
, bShowAll = Vty.KChar 'a'
|
||||||
|
}
|
||||||
|
|
||||||
data AppState = AppState
|
data AppState = AppState
|
||||||
{ settings :: Settings
|
{ settings :: Settings
|
||||||
, dirs :: Dirs
|
, dirs :: Dirs
|
||||||
|
, keyBindings :: KeyBindings
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
data Settings = Settings
|
data Settings = Settings
|
||||||
@ -205,13 +256,14 @@ data Settings = Settings
|
|||||||
, downloader :: Downloader
|
, downloader :: Downloader
|
||||||
, verbose :: Bool
|
, verbose :: Bool
|
||||||
}
|
}
|
||||||
deriving Show
|
deriving (Show, GHC.Generic)
|
||||||
|
|
||||||
data Dirs = Dirs
|
data Dirs = Dirs
|
||||||
{ baseDir :: Path Abs
|
{ baseDir :: Path Abs
|
||||||
, binDir :: Path Abs
|
, binDir :: Path Abs
|
||||||
, cacheDir :: Path Abs
|
, cacheDir :: Path Abs
|
||||||
, logsDir :: Path Abs
|
, logsDir :: Path Abs
|
||||||
|
, confDir :: Path Abs
|
||||||
}
|
}
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
|
@ -33,9 +33,11 @@ import Data.Versions
|
|||||||
import Data.Word8
|
import Data.Word8
|
||||||
import HPath
|
import HPath
|
||||||
import URI.ByteString
|
import URI.ByteString
|
||||||
|
import Text.Casing
|
||||||
|
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import qualified Graphics.Vty as Vty
|
||||||
|
|
||||||
|
|
||||||
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } { fieldLabelModifier = removeLensFieldLabel } ''Architecture
|
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } { fieldLabelModifier = removeLensFieldLabel } ''Architecture
|
||||||
@ -51,6 +53,11 @@ deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Versio
|
|||||||
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadInfo
|
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadInfo
|
||||||
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''GHCupInfo
|
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''GHCupInfo
|
||||||
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Requirements
|
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Requirements
|
||||||
|
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''KeepDirs
|
||||||
|
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Downloader
|
||||||
|
deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "u-") . T.pack . kebab $ str' } ''UserSettings
|
||||||
|
deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "k-") . T.pack . kebab $ str' } ''UserKeyBindings
|
||||||
|
deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''Vty.Key
|
||||||
|
|
||||||
instance ToJSON Tag where
|
instance ToJSON Tag where
|
||||||
toJSON Latest = String "Latest"
|
toJSON Latest = String "Latest"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
@ -14,16 +15,18 @@ Portability : POSIX
|
|||||||
-}
|
-}
|
||||||
module GHCup.Utils.Dirs
|
module GHCup.Utils.Dirs
|
||||||
( getDirs
|
( getDirs
|
||||||
|
, ghcupConfigFile
|
||||||
, ghcupGHCBaseDir
|
, ghcupGHCBaseDir
|
||||||
, ghcupGHCDir
|
, ghcupGHCDir
|
||||||
, parseGHCupGHCDir
|
|
||||||
, mkGhcupTmpDir
|
, mkGhcupTmpDir
|
||||||
, withGHCupTmpDir
|
, parseGHCupGHCDir
|
||||||
, relativeSymlink
|
, relativeSymlink
|
||||||
|
, withGHCupTmpDir
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
|
||||||
|
import GHCup.Errors
|
||||||
import GHCup.Types
|
import GHCup.Types
|
||||||
import GHCup.Types.JSON ( )
|
import GHCup.Types.JSON ( )
|
||||||
import GHCup.Utils.MegaParsec
|
import GHCup.Utils.MegaParsec
|
||||||
@ -34,8 +37,11 @@ import Control.Exception.Safe
|
|||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
import Control.Monad.Trans.Resource
|
import Control.Monad.Trans.Resource
|
||||||
|
import Data.Bifunctor
|
||||||
import Data.ByteString ( ByteString )
|
import Data.ByteString ( ByteString )
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
|
import GHC.IO.Exception ( IOErrorType(NoSuchThing) )
|
||||||
|
import Haskus.Utils.Variant.Excepts
|
||||||
import HPath
|
import HPath
|
||||||
import HPath.IO
|
import HPath.IO
|
||||||
import Optics
|
import Optics
|
||||||
@ -49,8 +55,10 @@ import System.Posix.Env.ByteString ( getEnv
|
|||||||
import System.Posix.FilePath hiding ( (</>) )
|
import System.Posix.FilePath hiding ( (</>) )
|
||||||
import System.Posix.Temp.ByteString ( mkdtemp )
|
import System.Posix.Temp.ByteString ( mkdtemp )
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Lazy as L
|
||||||
import qualified Data.ByteString.UTF8 as UTF8
|
import qualified Data.ByteString.UTF8 as UTF8
|
||||||
import qualified Data.Text.Encoding as E
|
import qualified Data.Text.Encoding as E
|
||||||
|
import qualified Data.Yaml as Y
|
||||||
import qualified System.Posix.FilePath as FP
|
import qualified System.Posix.FilePath as FP
|
||||||
import qualified System.Posix.User as PU
|
import qualified System.Posix.User as PU
|
||||||
import qualified Text.Megaparsec as MP
|
import qualified Text.Megaparsec as MP
|
||||||
@ -84,6 +92,28 @@ ghcupBaseDir = do
|
|||||||
pure (bdir </> [rel|.ghcup|])
|
pure (bdir </> [rel|.ghcup|])
|
||||||
|
|
||||||
|
|
||||||
|
-- | ~/.ghcup by default
|
||||||
|
--
|
||||||
|
-- If 'GHCUP_USE_XDG_DIRS' is set (to anything),
|
||||||
|
-- then uses 'XDG_CONFIG_HOME/ghcup' as per xdg spec.
|
||||||
|
ghcupConfigDir :: IO (Path Abs)
|
||||||
|
ghcupConfigDir = do
|
||||||
|
xdg <- useXDG
|
||||||
|
if xdg
|
||||||
|
then do
|
||||||
|
bdir <- getEnv "XDG_CONFIG_HOME" >>= \case
|
||||||
|
Just r -> parseAbs r
|
||||||
|
Nothing -> do
|
||||||
|
home <- liftIO getHomeDirectory
|
||||||
|
pure (home </> [rel|.config|])
|
||||||
|
pure (bdir </> [rel|ghcup|])
|
||||||
|
else do
|
||||||
|
bdir <- getEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case
|
||||||
|
Just r -> parseAbs r
|
||||||
|
Nothing -> liftIO getHomeDirectory
|
||||||
|
pure (bdir </> [rel|.ghcup|])
|
||||||
|
|
||||||
|
|
||||||
-- | If 'GHCUP_USE_XDG_DIRS' is set (to anything),
|
-- | If 'GHCUP_USE_XDG_DIRS' is set (to anything),
|
||||||
-- then uses 'XDG_BIN_HOME' env var or defaults to '~/.local/bin'
|
-- then uses 'XDG_BIN_HOME' env var or defaults to '~/.local/bin'
|
||||||
-- (which, sadly is not strictly xdg spec).
|
-- (which, sadly is not strictly xdg spec).
|
||||||
@ -142,10 +172,27 @@ getDirs = do
|
|||||||
binDir <- ghcupBinDir
|
binDir <- ghcupBinDir
|
||||||
cacheDir <- ghcupCacheDir
|
cacheDir <- ghcupCacheDir
|
||||||
logsDir <- ghcupLogsDir
|
logsDir <- ghcupLogsDir
|
||||||
|
confDir <- ghcupConfigDir
|
||||||
pure Dirs { .. }
|
pure Dirs { .. }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-------------------
|
||||||
|
--[ GHCup files ]--
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
|
||||||
|
ghcupConfigFile :: (MonadIO m)
|
||||||
|
=> Excepts '[JSONError] m UserSettings
|
||||||
|
ghcupConfigFile = do
|
||||||
|
confDir <- liftIO $ ghcupConfigDir
|
||||||
|
let file = confDir </> [rel|config.yaml|]
|
||||||
|
bs <- liftIO $ handleIO' NoSuchThing (\_ -> pure $ Nothing) $ fmap Just $ readFile file
|
||||||
|
case bs of
|
||||||
|
Nothing -> pure defaultUserSettings
|
||||||
|
Just bs' -> lE' JSONDecodeError . bimap show id . Y.decodeEither' . L.toStrict $ bs'
|
||||||
|
|
||||||
|
|
||||||
-------------------------
|
-------------------------
|
||||||
--[ GHCup directories ]--
|
--[ GHCup directories ]--
|
||||||
-------------------------
|
-------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user