packages feed

unicoder-0.5.0: app/unicoder.hs

import Data.String (IsString(..))

import Data.Default
import Control.Monad

import System.FilePath
import System.Directory
import System.IO
import System.Exit
import System.Console.GetOpt
import System.Environment

import Twitch (Dep, (|>))
import qualified Twitch
import qualified Data.Text.IO as T

import Text.Unicoder
import Paths_unicoder
import Data.Version


main :: IO ()
main = do
        opts <- getOptions
        m_config <- loadConfig =<< locateConfig (optConfig opts)
        config <- case m_config of
            Nothing -> die "bad configuration file"
            Just config -> return config
        case optMode opts of
            StdPipes -> runHandle config (stdin, stdout)
            InPlace files -> mapM_ (runFile config) files
            FileWatch globs ->
                Twitch.defaultMainWithOptions def $
                    forM_ (fromString <$> globs) $ \glob ->
                        glob |> runFile config
        exitSuccess

runFile :: Config -> FilePath -> IO ()
runFile config filename = do
    source <- T.readFile filename
    let result = unicodize config source
    T.writeFile filename result

runHandle :: Config -> (Handle, Handle) -> IO ()
runHandle config (i, o) = do
    source <- T.hGetContents i
    let result = unicodize config source
    T.hPutStr o result

{-| Determine the filesystem location of a config file path.
    If the path does not include a slash, then it is resolved using
    the unicoder built-in locations. If it does include a slash, then
    it is resolved relative to the passed working directory.
-}
locateConfig :: FilePath -- ^ the config path to resolve
             -> IO FilePath -- ^ resolved, absolute path to the file
locateConfig path
    | pathSeparator `elem` path = do
        cwd <- getCurrentDirectory
        return $ normalise (cwd </> path)
    | otherwise = getDataFileName (path <.> "conf")


putErrLn = hPutStrLn stderr


data Mode
    = StdPipes
    | InPlace [FilePath]
    | FileWatch [String]
    deriving (Show)
data Options = Options 
    { optConfig :: FilePath
    , optOutput :: Maybe FilePath
    , optMode :: Mode
    }
    deriving (Show)
instance Default Options where
    def =  Options
            { optConfig = "default"
            , optOutput = Nothing
            , optMode = StdPipes
            }


getOptions :: IO Options
getOptions = do
    (actions, args, errors) <- getOpt Permute options <$> getArgs
    if null errors
      then do
        opts <- foldl (>>=) (return def) actions
        case optMode opts of
            InPlace _ -> return $ opts { optMode = InPlace args }
            FileWatch _ -> return $ opts { optMode = FileWatch args }
            _ | null args -> return opts
              | otherwise -> die $ "unrecognized option: " ++ show (head args)
      else do
        mapM_ putErrLn errors
        exitFailure

options :: [OptDescr (Options -> IO Options)]
options =
    [ Option "i" ["in-place"]
        (NoArg
            (\opt -> return opt {optMode = InPlace (error "uninit'd in-place") }))
            "Run once on listed files."
    , Option "w" ["file-watch"]
        (NoArg
            (\opt -> return opt { optMode = FileWatch (error "uninit'd file-watch") }))
        (unlines [ "Run on files whenever they are changed."
                 , "Glob syntax is supported (including `**`) so that we can watch for new files."
                 , "NOTE: Quote glob patterns whenever your shell already supports glob."
                 ])
    , Option "c" ["config"]
        (ReqArg
            (\arg opt -> return opt { optConfig = arg })
            "file")
        "Specify a configuration (usually a programming language)."

    , Option "V" ["version"]
        (NoArg
            (\_ -> do
                putStrLn (showVersion version)
                exitSuccess))
        "Print version."
    , Option "" ["show-config-dir"]
        (NoArg
            (\_ -> do
                putStrLn =<< getDataFileName ""
                exitSuccess))
        "Print directory where configuration files are stored."
    , Option "h" ["help"]
        (NoArg
            (\_ -> do
                prg <- getProgName
                putStrLn $ concat ["Unicoder v", showVersion version, "-beta"]
                putStrLn "Ease input of unicode glyphs."
                putStrLn "This program is lisenced under the 3-clause BSD lisence."
                putStrLn (usageInfo (prg ++ " [options] [-i files... | -w globs...]") options)
                putStrLn "Replace special sequences of characters (as defined in a config file) with otherwise hard-to-type replacements."
                putStrLn "The idea is to make it easy to insert unicode symbols: type an escape sequence and run this tool over the source."
                putStrLn "--Config Files"
                putStrLn "    Configuration files end in `.conf`. Use `--show-config-dir` to see where they are stored."
                putStrLn "    Passing a file to `-c` that contains a slash will use exactly the file you specify, instead of looking one up."
                putStrLn "    Check out an installed config file to see the syntax."
                putStrLn "--More Info"
                putStrLn "    For more documentation, go to: http://zankoku-okuno.viewdocs.io/unicoder/"
                exitSuccess))
        "Show help"
    ]