morley-1.19.1: src/Morley/Util/Text.hs
-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA
module Morley.Util.Text
( headToLower
, surround
, dquotes
, stripFieldPrefix
, dropPrefix
, toCamel
, Manip.toPascal
, toSnake
, toSpinal
, lowerCaseCluster
) where
import Data.Char (isLower, isUpper, toLower)
import Data.Text qualified as T
import Data.Text.Manipulate qualified as Manip
-- | Leads first character of text to lower case.
--
-- For empty text this will throw an error.
headToLower :: HasCallStack => Text -> Text
headToLower txt = case T.uncons txt of
Nothing -> error "Empty text"
Just (c, cs) -> T.cons (toLower c) cs
surround :: Semigroup a => a -> a -> a -> a
surround pre post content = pre <> content <> post
dquotes :: (Semigroup a, IsString a) => a -> a
dquotes = surround "\"" "\""
-- | Drops the field name prefix from a field.
--
-- We assume a convention of the prefix always being non-uppercase, and the
-- first letter of the actual field name being uppercase.
dropPrefix :: Text -> Text
dropPrefix = T.dropWhile (not . isUpper)
-- | Transform text to @camelCase@.
--
-- If the text starts with a single uppercase letter, it is lowercased. If it
-- starts with a cluster of non-lowercase letters, all but the last in the
-- cluster are lowercased.
--
-- >>> toCamel "MyField"
-- "myField"
-- >>> toCamel "USPosition"
-- "usPosition"
-- >>> toCamel "FA2Config"
-- "fa2Config"
toCamel :: Text -> Text
toCamel = Manip.toCamel . lowerCaseCluster
-- | Transform text to @snake_case@.
--
-- Non-lowercase clusters starting with an uppercase letter are treated as
-- separate words, except the last letter in the cluster.
--
-- >>> toSnake "MyField"
-- "my_field"
-- >>> toSnake "USPosition"
-- "us_position"
-- >>> toSnake "FA2Config"
-- "fa2_config"
-- >>> toSnake "MyUSPosition"
-- "my_us_position"
-- >>> toSnake "MyFie123d"
-- "my_fie123d"
toSnake :: Text -> Text
toSnake = T.intercalate "_" . fmap (Manip.toSnake . lowerCaseCluster) . Manip.splitWords
-- | Transform text to @spinal-case@.
--
-- Non-lowercase clusters starting with an uppercase letter are treated as
-- separate words, except the last letter in the cluster.
--
-- >>> toSpinal "MyField"
-- "my-field"
-- >>> toSpinal "USPosition"
-- "us-position"
-- >>> toSpinal "FA2Config"
-- "fa2-config"
-- >>> toSpinal "MyUSPosition"
-- "my-us-position"
-- >>> toSpinal "MyFie123d"
-- "my-fie123d"
toSpinal :: Text -> Text
toSpinal = T.intercalate "-" . fmap (Manip.toSpinal . lowerCaseCluster) . Manip.splitWords
-- | If text starts with a cluster of non-lowercase letters, lowercase them except
-- the last one.
--
-- >>> lowerCaseCluster "USPosition"
-- "usPosition"
-- >>> lowerCaseCluster "Position"
-- "Position"
-- >>> lowerCaseCluster "FA2Config"
-- "fa2Config"
lowerCaseCluster :: Text -> Text
lowerCaseCluster x = fromMaybe x do
(c1, xs) <- T.uncons x
(c2, _) <- T.uncons xs
if notLower c1 && notLower c2
then do
let (ucs, rest) = T.span notLower x
(ucs', lastuc) <- T.unsnoc ucs
Just $ T.map toLower ucs' <> T.cons lastuc rest
else Nothing
where
notLower = not . isLower
-- | Cut fields prefixes which we use according to the style guide.
--
-- >>> stripFieldPrefix "cmMyField"
-- "myField"
stripFieldPrefix :: Text -> Text
stripFieldPrefix = toCamel . dropPrefix