on master help sign in
Merge pull request #343 from hSloan/is-obelisk-unset-lang-fix

Fix: 'ob init' crashes when LANG is unset

commit588bf940cdfbbd5e614019c4f4f47c196039b0d2 authorAli Abrar <aliabrar@gmail.com> dateWed 27 Mar 2019, 10:51 (8 years ago) parentb519441d8a7c8a
5 files changed+77−19
lib/cliapp/obelisk-cliapp.cabal+1 lib/cliapp/src/Obelisk/CliApp/Logging.hs+26−2 lib/cliapp/src/Obelisk/CliApp/Spinner.hs+18−17 lib/cliapp/src/Obelisk/CliApp/Theme.hs+29 lib/cliapp/src/Obelisk/CliApp/Types.hs+3
lib/cliapp/obelisk-cliapp.cabal+1
@@ -29,5 +29,6 @@ library
2929 Obelisk.CliApp.Process
3030 Obelisk.CliApp.Spinner
3131 Obelisk.CliApp.TerminalString
32+ Obelisk.CliApp.Theme
3233 Obelisk.CliApp.Types
3334 ghc-options: -Wall -fobject-code
lib/cliapp/src/Obelisk/CliApp/Logging.hs+26−2
@@ -39,17 +39,20 @@ import Control.Monad.Log (Severity (..), WithSeverity (..), logMessage, runLoggi
3939import Control.Monad.Loops (iterateUntil)
4040import Control.Monad.Reader (MonadIO, ReaderT (..))
4141import Data.IORef (atomicModifyIORef', newIORef, readIORef, writeIORef)
42+import Data.Maybe
4243import Data.Semigroup ((<>))
4344import Data.Text (Text)
4445import qualified Data.Text as T
4546import qualified Data.Text.IO as T
47+import GHC.IO.Encoding.Types
4648import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
4749 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
4850 SGR (SetColor, SetConsoleIntensity), clearLine)
4951import System.Exit (ExitCode (..))
50−import System.IO (BufferMode (NoBuffering), hFlush, hReady, hSetBuffering, stderr, stdin, stdout)
52+import System.IO
5153
5254import qualified Obelisk.CliApp.TerminalString as TS
55+import Obelisk.CliApp.Theme
5356import Obelisk.CliApp.Types
5457
5558newCliConfig
@@ -63,7 +66,11 @@ newCliConfig sev noColor noSpinner errorLogExitCode = do
6366 lock <- newMVar False
6467 tipDisplayed <- newIORef False
6568 stack <- newIORef ([], [])
66− return $ CliConfig level noColor noSpinner lock tipDisplayed stack errorLogExitCode
69+ textEncoding <- hGetEncoding stdout
70+ let theme = if fromMaybe False $ supportsUnicode <$> textEncoding
71+ then unicodeTheme
72+ else noUnicodeTheme
73+ return $ CliConfig level noColor noSpinner lock tipDisplayed stack errorLogExitCode theme
6774
6875runCli :: MonadIO m => CliConfig e -> CliT e m a -> m a
6976runCli c =
@@ -226,3 +233,20 @@ fork :: (HasCliConfig e m, MonadIO m) => CliT e IO () -> m ThreadId
226233fork f = do
227234 c <- getCliConfig
228235 liftIO $ forkIO $ runCli c f
236+
237+-- | Conservatively determines whether the encoding supports Unicode.
238+--
239+-- Currently this uses a whitelist of known-to-work encodings. In principle it
240+-- could test dynamically by opening a file with this encoding, but it doesn't
241+-- look like base exposes any way to determine this in a pure fashion.
242+supportsUnicode :: TextEncoding -> Bool
243+supportsUnicode enc = any ((textEncodingName enc ==) . textEncodingName)
244+ [ utf8
245+ , utf8_bom
246+ , utf16
247+ , utf16be
248+ , utf16le
249+ , utf32
250+ , utf32be
251+ , utf32le
252+ ]
lib/cliapp/src/Obelisk/CliApp/Spinner.hs+18−17
@@ -24,6 +24,7 @@ import System.Console.ANSI (Color (Blue, Cyan, Green, Red))
2424
2525import Obelisk.CliApp.Logging (allowUserToMakeLoggingVerbose, fork, putLog)
2626import Obelisk.CliApp.TerminalString (TerminalString (..), enquiryCode)
27+import Obelisk.CliApp.Theme
2728import Obelisk.CliApp.Types (CliLog, CliConfig (..), HasCliConfig, Output (..), getCliConfig)
2829
2930-- | Run an action with a CLI spinner.
@@ -51,7 +52,8 @@ withSpinner'
5152 -> m a
5253 -> m a
5354withSpinner' msg mkTrail action = do
54− noSpinner <- _cliConfig_noSpinner <$> getCliConfig
55+ cliConf <- getCliConfig
56+ let noSpinner = _cliConfig_noSpinner cliConf
5557 if noSpinner
5658 then putLog Notice msg >> action
5759 else bracket' run cleanup $ const action
@@ -61,8 +63,11 @@ withSpinner' msg mkTrail action = do
6163 modifyStack pushSpinner >>= \case
6264 True -> do -- Top-level spinner; fork a thread to manage output of anything on the stack
6365 ctrleThread <- fork $ allowUserToMakeLoggingVerbose enquiryCode
66+ cliConf <- getCliConfig
67+ let theme = _cliConfig_theme cliConf
68+ spinner = coloredSpinner $ _cliTheme_spinner theme
6469 spinnerThread <- fork $ runSpinner spinner $ \c -> do
65− logs <- renderSpinnerStack c . snd <$> readStack
70+ logs <- renderSpinnerStack theme c . snd <$> readStack
6671 logMessage $ Output_Overwrite logs
6772 pure [ctrleThread, spinnerThread]
6873 False -> -- Sub-spinner; nothing to do.
@@ -70,13 +75,15 @@ withSpinner' msg mkTrail action = do
7075 cleanup tids resultM = do
7176 liftIO $ mapM_ killThread tids
7277 logMessage Output_ClearLine
73− logsM <- modifyStack $ popSpinner $ case resultM of
78+ cliConf <- getCliConfig
79+ let theme = _cliConfig_theme cliConf
80+ logsM <- modifyStack $ (popSpinner theme) $ case resultM of
7481 Nothing ->
75− ( TerminalString_Colorized Red "✖"
82+ ( TerminalString_Colorized Red $ _cliTheme_failed $ _cliConfig_theme cliConf
7683 , Just msg -- Always display final message if there was an exception.
7784 )
7885 Just result ->
79− ( TerminalString_Colorized Green "✔"
86+ ( TerminalString_Colorized Green $ _cliTheme_done $ _cliConfig_theme cliConf
8087 , mkTrail <*> pure result
8188 )
8289 -- Last message, finish off with newline.
@@ -87,10 +94,10 @@ withSpinner' msg mkTrail action = do
8794 )
8895 where
8996 isTemporary = isNothing mkTrail
90− popSpinner (mark, trailMsgM) (flag, old) =
97+ popSpinner theme (mark, trailMsgM) (flag, old) =
9198 ( (newFlag, new)
9299 -- With final trail spinner message to render
93− , renderSpinnerStack mark . (: new) . TerminalString_Normal <$> (
100+ , renderSpinnerStack theme mark . (: new) . TerminalString_Normal <$> (
94101 if inTemporarySpinner then Nothing else trailMsgM
95102 )
96103 )
@@ -102,19 +109,19 @@ withSpinner' msg mkTrail action = do
102109 =<< fmap _cliConfig_spinnerStack getCliConfig
103110 modifyStack f = liftIO . flip atomicModifyIORef' f
104111 =<< fmap _cliConfig_spinnerStack getCliConfig
105− spinner = coloredSpinner defaultSpinnerTheme
106112
107113-- | How nested spinner logs should be displayed
108114renderSpinnerStack
109− :: TerminalString -- ^ That which comes before the final element in stack
115+ :: CliTheme
116+ -> TerminalString -- ^ That which comes before the final element in stack
110117 -> [TerminalString] -- ^ Spinner elements in reverse order
111118 -> [TerminalString]
112−renderSpinnerStack mark = L.intersperse space . go . L.reverse
119+renderSpinnerStack theme mark = L.intersperse space . go . L.reverse
113120 where
114121 go [] = []
115122 go (x:[]) = mark : [x]
116123 go (x:xs) = arrow : x : go xs
117− arrow = TerminalString_Colorized Blue "⇾"
124+ arrow = TerminalString_Colorized Blue $ _cliTheme_arrow theme
118125 space = TerminalString_Normal " "
119126
120127-- | A spinner is simply an infinite list of strings that supplant each other in a delayed loop, creating the
@@ -130,12 +137,6 @@ runSpinner spinner f = forM_ spinner $ f >=> const delay
130137 where
131138 delay = liftIO $ threadDelay 100000 -- A shorter delay ensures that we update promptly.
132139
133−type SpinnerTheme = [Text]
134−
135−-- Find more spinners at https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json
136−defaultSpinnerTheme :: SpinnerTheme
137−defaultSpinnerTheme = ["◐", "◓", "◑", "◒"]
138−
139140-- | Like `bracket` but the `release` function can know whether an exception was raised
140141bracket' :: MonadMask m => m a -> (a -> Maybe c -> m b) -> (a -> m c) -> m c
141142bracket' acquire release use = mask $ \unmasked -> do
lib/cliapp/src/Obelisk/CliApp/Theme.hsnew file+29
@@ -0,0 +1,29 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Obelisk.CliApp.Theme where
3+
4+import Data.Text (Text)
5+
6+data CliTheme = CliTheme
7+ { _cliTheme_done :: Text
8+ , _cliTheme_failed :: Text
9+ , _cliTheme_arrow :: Text
10+ , _cliTheme_spinner :: SpinnerTheme
11+ }
12+
13+type SpinnerTheme = [Text]
14+
15+unicodeTheme :: CliTheme
16+unicodeTheme = CliTheme
17+ { _cliTheme_done = "✔"
18+ , _cliTheme_failed = "✖"
19+ , _cliTheme_arrow = "⇾"
20+ , _cliTheme_spinner = ["◐", "◓", "◑", "◒"]
21+ }
22+
23+noUnicodeTheme :: CliTheme
24+noUnicodeTheme = CliTheme
25+ { _cliTheme_done = "DONE"
26+ , _cliTheme_failed = "FAILED"
27+ , _cliTheme_arrow = "->"
28+ , _cliTheme_spinner = ["|", "/", "-", "\\"]
29+ }
lib/cliapp/src/Obelisk/CliApp/Types.hs+3
@@ -23,6 +23,7 @@ import Data.Text (Text)
2323import System.Exit (ExitCode (..), exitWith)
2424
2525import Obelisk.CliApp.TerminalString (TerminalString)
26+import Obelisk.CliApp.Theme (CliTheme)
2627
2728--------------------------------------------------------------------------------
2829
@@ -88,6 +89,8 @@ data CliConfig e = CliConfig
8889 _cliConfig_spinnerStack :: IORef ([Bool], [TerminalString])
8990 , -- | Failure handler. How to log error and what exit status to use.
9091 _cliConfig_errorLogExitCode :: e -> (Text, Int)
92+ , -- | Theme strings for spinners
93+ _cliConfig_theme :: CliTheme
9194 }
9295
9396class Monad m => HasCliConfig e m | m -> e where