    -- Copyright 2020 United States Government as represented by the Administrator
    -- of the National Aeronautics and Space Administration. All Rights Reserved.
    --
    -- Disclaimers
    --
    -- Licensed under the Apache License, Version 2.0 (the "License"); you may
    -- not use this file except in compliance with the License. You may obtain a
    -- copy of the License at
    --
    --      https://www.apache.org/licenses/LICENSE-2.0
    --
    -- Unless required by applicable law or agreed to in writing, software
    -- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    -- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    -- License for the specific language governing permissions and limitations
    -- under the License.
    --
    -- | Auxiliary functions for working with values of type 'String'.
    module Data.String.Extra
        (
          -- * Safe I/O
          safeReadFile

          -- * String transformation
        , pascalCase

          -- * String sanitization
        , sanitizeLCIdentifier
        , sanitizeUCIdentifier
        )
      where

    -- External imports
    import Control.Exception ( catch )
    import Data.Char         ( toLower, toUpper )
    import System.IO.Error   ( isDoesNotExistError )

    -- * Safe I/O

    -- | Safely read a file into a 'String', returning a 'Left' error message if
    -- the file cannot be opened.

    -- This function could also be placed in System.IO. However, we decide to
    -- include it in this module for symmetry with an analogous function for
    -- ByteString, included in Data.ByteString.Extra.
    safeReadFile :: FilePath -> IO (Either String String)
    safeReadFile :: String -> IO (Either String String)
    safeReadFile String
    fp =
      IO (Either String String)
    -> (IOError -> IO (Either String String))
    -> IO (Either String String)
    forall e a. Exception e => IO a -> (e -> IO a) -> IO a
    catch (String -> Either String String
    forall a. a -> Either String a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (String -> Either String String)
    -> IO String -> IO (Either String String)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> String -> IO String
    readFile String
    fp) ((IOError -> IO (Either String String))
     -> IO (Either String String))
    -> (IOError -> IO (Either String String))
    -> IO (Either String String)
    forall a b. (a -> b) -> a -> b
    $ \IOError
    e ->
        if IOError -> Bool
    isDoesNotExistError IOError
    e
          then Either String String -> IO (Either String String)
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Either String String -> IO (Either String String))
    -> Either String String -> IO (Either String String)
    forall a b. (a -> b) -> a -> b
    $ String -> Either String String
    forall a b. a -> Either a b
    Left (String -> Either String String) -> String -> Either String String
    forall a b. (a -> b) -> a -> b
    $ String -> String
    strStringFileNotFound String
    fp
          else Either String String -> IO (Either String String)
    forall a. a -> IO a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (Either String String -> IO (Either String String))
    -> Either String String -> IO (Either String String)
    forall a b. (a -> b) -> a -> b
    $ String -> Either String String
    forall a b. a -> Either a b
    Left (String -> Either String String) -> String -> Either String String
    forall a b. (a -> b) -> a -> b
    $ String -> String
    strStringCannotOpenFile String
    fp

    -- ** Error messages

    -- | File-not-found message.
    strStringFileNotFound :: FilePath -> String
    strStringFileNotFound :: String -> String
    strStringFileNotFound String
    fp = String
    "File not found: " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    fp

    -- | Cannot-open-file message.
    strStringCannotOpenFile :: FilePath -> String
    strStringCannotOpenFile :: String -> String
    strStringCannotOpenFile String
    fp = String
    "Error opening file: " String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String
    fp

    -- * Transformation

    -- | Convert a string with underscores to PascalCase.
    pascalCase :: String -> String
    pascalCase :: String -> String
    pascalCase = (String -> String) -> [String] -> String
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap String -> String
    capitalize ([String] -> String) -> (String -> [String]) -> String -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> [String]
    parts
      where
        capitalize :: String -> String
    capitalize (Char
    x:String
    xs) = Char -> Char
    toUpper Char
    x Char -> String -> String
    forall a. a -> [a] -> [a]
    : (Char -> Char) -> String -> String
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    toLower String
    xs
        capitalize [] = []

        parts :: String -> [String]
        parts :: String -> [String]
    parts String
    "" = []
        parts String
    s =
          let (String
    l, String
    r) = (Char -> Bool) -> String -> (String, String)
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    break (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    == Char
    '_') String
    s
          in String
    l String -> [String] -> [String]
    forall a. a -> [a] -> [a]
    : case String
    r of
                   []     -> []
                   (Char
    _:String
    rs) -> String -> [String]
    parts String
    rs

    -- * Sanitization

    -- | Remove extraneous characters from an identifier and make the starting
    -- character lowercase.
    --
    -- This function currently replaces hyphens with underscores.
    sanitizeLCIdentifier :: String -> String
    sanitizeLCIdentifier :: String -> String
    sanitizeLCIdentifier = String -> String
    headToLower (String -> String) -> (String -> String) -> String -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Char -> Char) -> String -> String
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    sanitizeCharacter
      where
        sanitizeCharacter :: Char -> Char
    sanitizeCharacter Char
    '-' = Char
    '_'
        sanitizeCharacter Char
    x   = Char
    x

        headToLower :: [Char] -> [Char]
        headToLower :: String -> String
    headToLower []     = []
        headToLower (Char
    x:String
    xs) = Char -> Char
    toLower Char
    x Char -> String -> String
    forall a. a -> [a] -> [a]
    : String
    xs

    -- | Remove extraneous characters from an identifier and make the starting
    -- character uppercase.
    --
    -- This function currently replaces hyphens with underscores.
    sanitizeUCIdentifier :: String -> String
    sanitizeUCIdentifier :: String -> String
    sanitizeUCIdentifier = String -> String
    headToUpper (String -> String) -> (String -> String) -> String -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Char -> Char) -> String -> String
    forall a b. (a -> b) -> [a] -> [b]
    map Char -> Char
    sanitizeCharacter
      where
        sanitizeCharacter :: Char -> Char
    sanitizeCharacter Char
    '-' = Char
    '_'
        sanitizeCharacter Char
    x   = Char
    x

        headToUpper :: [Char] -> [Char]
        headToUpper :: String -> String
    headToUpper []     = []
        headToUpper (Char
    x:String
    xs) = Char -> Char
    toUpper Char
    x Char -> String -> String
    forall a. a -> [a] -> [a]
    : String
    xs