{-# LANGUAGE CPP #-} {-# LANGUAGE NamedFieldPuns #-} {-| Module: System.IO.CodePage Copyright: (C) 2016-2017 Ryan Scott License: BSD-style (see the file LICENSE) Maintainer: Ryan Scott Stability: Provisional Portability: Portable Exports functions which adjust code pages on Windows, and do nothing on other operating systems. -} module System.IO.CodePage ( -- * Adjusting 'CodePage's withCP65001 , withCP1200 , withCP1201 , withCP12000 , withCP12001 , withCP1252 , withCodePage , withCodePageOptions -- * Notable 'CodePage's , CodePage , cp65001 , cp1200 , cp1201 , cp12000 , cp12001 , cp1252 -- * 'Options' , Options , defaultOptions -- ** Record fields of 'Options' , chatty , nonWindowsBehavior -- ** 'NonWindowsBehavior' , NonWindowsBehavior -- ** Constructing 'NonWindowsBehavior' , nonWindowsDoNothing , nonWindowsFallbackCodePageEncoding , defaultFallbackCodePageEncoding ) where import Control.Exception (bracket_) import Control.Monad (when) import Data.Foldable (forM_) import GHC.IO.Encoding (textEncodingName) import System.IO ( TextEncoding, hGetEncoding, hPutStrLn, hSetEncoding , stderr, stdin, stdout ) import System.IO.CodePage.Internal #if MIN_VERSION_base(4,5,0) import GHC.IO.Encoding (getLocaleEncoding, setLocaleEncoding) #endif #ifdef WINDOWS import System.Win32.CodePage hiding (CodePage) #endif -- | Sets the code page for an action to UTF-8 as necessary. withCP65001 :: IO a -> IO a withCP65001 = withCodePage cp65001 -- | Sets the code page for an action to UTF-16LE as necessary. withCP1200 :: IO a -> IO a withCP1200 = withCodePage cp1200 -- | Sets the code page for an action to UTF-16BE as necessary. withCP1201 :: IO a -> IO a withCP1201 = withCodePage cp1201 -- | Sets the code page for an action to UTF-32LE as necessary. withCP12000 :: IO a -> IO a withCP12000 = withCodePage cp12000 -- | Sets the code page for an action to UTF-32BE as necessary. withCP12001 :: IO a -> IO a withCP12001 = withCodePage cp12001 -- | Sets the code page for an action to Latin1 as necessary. withCP1252 :: IO a -> IO a withCP1252 = withCodePage cp1252 -- | Sets the code page for an action as necessary. -- -- On operating systems besides Windows, this will make an effort to change -- the current 'TextEncoding' to something that is equivalent to the supplied -- 'CodePage'. Currently, the only supported 'CodePage's on non-Windows OSes -- are 'cp65001', 'cp1200', 'cp1201', 'cp12000', and 'cp12001'. Supplying any -- other 'CodePage' will result in a runtime error on non-Windows OSes. (If you -- would like to configure this behavior, use 'withCodePageOptions' instead.) withCodePage :: CodePage -> IO a -> IO a withCodePage = withCodePageOptions defaultOptions -- | Sets the code page for an action as necessary. If the 'Bool' argument is 'True', -- this function will emit a warning to @stderr@ indicating that the code page has -- been changed. ('withCodePage' sets this argument to 'False'.) -- Taken from the stack codebase -- (https://github.com/commercialhaskell/stack/blob/21e517ba88b3c6bee475fb00ad95f280e7285a54/src/main/Main.hs#L82-L123) -- which is under a 3-clause BSD license withCodePageOptions :: Options -> CodePage -> IO a -> IO a withCodePageOptions (Options{chatty, nonWindowsBehavior}) cp inner = case nonWindowsBehavior of NonWindowsDoNothing -> inner NonWindowsFallbackCodePageEncoding fallback -> do #ifdef WINDOWS origCPI <- getConsoleCP origCPO <- getConsoleOutputCP #else -- These are never used on non-Windows OSes, -- so their values are irrelevant let origCPI = 0 origCPO = 0 #endif mbOrigStdinEnc <- hGetEncoding stdin mbOrigStdoutEnc <- hGetEncoding stdout mbOrigStderrEnc <- hGetEncoding stderr #if MIN_VERSION_base(4,5,0) origLocaleEnc <- getLocaleEncoding #endif let expected = codePageEncoding' fallback cp expectedName = textEncodingName expected warn typ = when chatty $ hPutStrLn stderr $ concat [ "Setting" , typ , " codepage to " ++ show cp , if expectedName == ("CP" ++ show cp) then "" else " (" ++ expectedName ++ ")" ] #ifdef WINDOWS setInput = origCPI /= cp setOutput = origCPO /= cp #else -- Crude, but the best available option setInput = fmap textEncodingName mbOrigStdinEnc /= Just expectedName setOutput = fmap textEncodingName mbOrigStdoutEnc /= Just expectedName #endif #if MIN_VERSION_base(4,5,0) setLocale = textEncodingName origLocaleEnc /= expectedName #endif fixInput | setInput = bracket_ (do setConsoleCP' cp hSetEncoding stdin expected ) (do setConsoleCP' origCPI forM_ mbOrigStdinEnc $ hSetEncoding stdin ) | otherwise = id fixOutput | setOutput = bracket_ (do setConsoleOutputCP' cp hSetEncoding stdout expected hSetEncoding stderr expected ) (do setConsoleOutputCP' origCPO forM_ mbOrigStdoutEnc $ hSetEncoding stdout forM_ mbOrigStderrEnc $ hSetEncoding stderr ) | otherwise = id fixLocale #if MIN_VERSION_base(4,5,0) | setLocale = bracket_ (do when chatty $ hPutStrLn stderr $ unwords [ "Setting locale encoding to" , expectedName ] setLocaleEncoding expected) (setLocaleEncoding origLocaleEnc) | otherwise #endif = id case (setInput, setOutput) of (False, False) -> return () (True, True) -> warn "" (True, False) -> warn " input" (False, True) -> warn " output" fixInput $ fixOutput $ fixLocale inner codePageEncoding' :: (CodePage -> TextEncoding) -> CodePage -> TextEncoding #ifdef WINDOWS codePageEncoding' _ = codePageEncoding #else codePageEncoding' = id #endif setConsoleCP', setConsoleOutputCP' :: CodePage -> IO () #ifdef WINDOWS setConsoleCP' = setConsoleCP setConsoleOutputCP' = setConsoleOutputCP #else setConsoleCP' _ = return () setConsoleOutputCP' _ = return () #endif