code-page-0.2: src/System/IO/CodePage.hs
{-# 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