{-# LANGUAGE CPP #-} -------------------------------------------------- {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE PackageImports #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} -- {-# LANGUAGE DeriveFoldable #-} -- {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -------------------------------------------------- -------------------------------------------------- {-| Pretty-printing and Parsing, with standard casing\/capitalization. TODO separate packages? @simple-print-parse@ re-exports these packages: * @simple-print@; * @simple-parse@, which has more dependencies (like on the @exceptions@ package). == Description Provides utilities (and aliases) for defining simple ad-hoc parsers and (pretty-)printers for types (especially sum types). == Motivation Useful when: * you want your program to print out a human-friendly representations of haskell types, or to be able to consume them in either a consistent format or a versatile format; * but, you don't want to be burdened by a dependency on some parser package. (i.e. by "human-friendly", I mean "more pleasantly readable and writable than with 'Show' \/ 'Read'"). uses of 'Print' include: * error messages. uses of 'Parse' include: * command-line options. == Features Such "formats" include: * several "casing formats", like 'ClassCase' or 'CamelCase'; * several "separator formats", like 'UnderscoreCase' or 'HyphenCase'; * plus, variations of the above. i.e. the user can define custom capitalization (via 'WordCasing') or a custom separator (via 'WordSeparator'). by default, each format's utility functions assume that: * constructors are written in (the conventional) class-case (i.e. @ClassCase@); and * the types are finite (satisfying 'Enum' or 'GEnum'). but, each format's module also provides (more general) versions, which can be given: * some list of values; or, * even manually tokenized strings. == Examples to print out a constructor, the default 'toPrinter' function does the following: * 'show' it; * break up the string shown in its words and/or subwords; * merge that list of strings (back into a single string) via some 'TokenStyle' (by default, 'HyphenCase'). 'HyphenCase' being the most readable, imo. it's most common token style for: command line options, URLs, and so on. == Naming NOTE In this package, the word "print" means "convert to a human-friendly string", not "write to stdout". -} module Prelude.Spiros.Pretty where #include <sboo-base-feature-macros.h> -------------------------------------------------- -------------------------------------------------- import Prelude.Spiros.Classes import Prelude.Spiros.Reexports import Prelude.Spiros.Exception import Prelude.Spiros.Utilities -------------------------------------------------- -------------------------------------------------- ---import qualified Prelude.Spiros.Enriched as T (replace) ---import qualified "text" Data.Text as T hiding (replace) -------------------------------------------------- ---import "case-insensitive" Data.CaseInsensitive ( CI ) import qualified "case-insensitive" Data.CaseInsensitive as CI -------------------------------------------------- import "exceptions" Control.Monad.Catch (MonadThrow) -- ( MonadThrow (throwM) -- , MonadCatch (catch) -- ) -------------------------------------------------- import qualified "base" Data.List.NonEmpty as NonEmpty ---import qualified "base" Data.List as List -------------------------------------------------- import qualified "base" Prelude -------------------------------------------------- -------------------------------------------------- {-| Simple printer. Usage: Here is an example printer, @ printVerbosity :: 'Parse' Verbosity printVerbosity = \case Concise -> \"concise\" Verbose -> \"verbose\" @ for this type @ data Verbosity = Concise | Verbose @ -} type Print a = a -> String -------------------------------------------------- {-| Simple parser. Expansions. @ 'Parse' a ≡ ('MonadThrow' m) => 'ParseM' m a ≡ ('MonadThrow' m) => (String -> m a) @ Specializations. Specializations include: @ 'Parse' a ≡ (String -> 'Maybe' a) 'Parse' a ≡ (String -> [a]) 'Parse' a ≡ (String -> 'Either' 'SomeException' a) 'Parse' a ≡ (String -> 'IO' a) @ Usage: Here is an example parser, @ parseVerbosity :: 'Parse' Verbosity parseVerbosity s = go s where go = \case \"concise\" -> return Concise \"verbose\" -> return Verbose \"Concise\" -> return Concise \"Verbose\" -> return Verbose \"default\" -> return def _ -> throwString s parseVerbosity_Maybe :: 'ParseM' 'Maybe' Verbosity parseVerbosity_Maybe = parseVerbosity @ given @ data Verbosity = Concise | Verbose instance Default Verbosity where def = Concise @ Also see 'ParseM'. -} type Parse a = (forall m. (MonadThrow m) => ParseM m a) ---(forall m. (MonadThrow m) => String -> m a) -------------------------------------------------- {-| Simple parser. Usage: Here is an example parser, @ parseVerbosity :: ('MonadThrow' m) => 'Parse' m Verbosity parseVerbosity s = go s where go = \case \"concise\" -> return Concise \"verbose\" -> return Verbose \"Concise\" -> return Concise \"Verbose\" -> return Verbose \"default\" -> return def _ -> throwString s parseVerbosity_Maybe :: 'Parse' 'Maybe' Verbosity parseVerbosity_Maybe = parseVerbosity @ given @ data Verbosity = Concise | Verbose instance Default Verbosity where def = Concise @ -} type ParseM m a = (String -> m a) -------------------------------------------------- {-| -} newtype SimpleParserM (m :: * -> *) (a :: *) = SimpleParserM { getSimpleParserM :: (String -> m a) } deriving (Functor,Generic) --TODO Cpp for DerivingStrategies -- deriving newtype (Functor,Foldable,Traversable) -- deriving stock (Generic) -- deriving newtype (NFData) -------------------------------------------------- -------------------------------------------------- {-| -} data TokenStyle = TokenStyle { separator :: WordSeparator , casing :: WordCasing } #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- {-| -} newtype WordSeparator = WordSeparator (Maybe Char) #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Lift,Generic) deriving newtype (Eq,Ord) deriving newtype (NFData,Hashable) #endif -------------------------------------------------- {-| -} data WordCasing = WordCasing -- TODO acronyms/abbreviations { firstWord :: SubwordCasing , laterWords :: SubwordCasing } #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif {- TODO acronyms: @\"XMLHttp\"@ @[ \"xml\", \"http\" ]@ either @\"XmlHttp\"@ or @\"XMLHttp\"@? -} -------------------------------------------------- {-| -} data SubwordCasing = LowerCased -- ^ e.g. @"lower"@ | TitleCased -- ^ e.g. @"Title"@ | UpperCased -- ^ e.g. @"UPPER"@ #if HAS_EXTENSION_DerivingStrategies deriving stock (Enum,Bounded,Ix) deriving anyclass (GEnum) deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- -------------------------------------------------- {-| -} data KnownTokenStyle = CamelCase -- ^ e.g. @"camelCase"@ | ClassCase -- ^ e.g. @"ClassCase"@ | ConstCase -- ^ e.g. @"CONST_CASE"@ | PascalCase -- ^ e.g. @"Pascal_Case"@ | SqueezeCase -- ^ e.g. @"squeezecase"@ | UnderscoreCase -- ^ e.g. @"underscore_case"@ | HyphenCase -- ^ e.g. @"hyphen-case"@ | SlashCase -- ^ e.g. @"slash/case"@ | DotCase -- ^ e.g. @"dot.case"@ #if HAS_EXTENSION_DerivingStrategies deriving stock (Enum,Bounded,Ix) deriving anyclass (GEnum) deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- pattern SnakeCase :: KnownTokenStyle pattern SnakeCase = UnderscoreCase pattern KebabCase :: KnownTokenStyle pattern KebabCase = HyphenCase -------------------------------------------------- pattern BashCase :: KnownTokenStyle pattern BashCase = ConstCase pattern PythonCase :: KnownTokenStyle pattern PythonCase = UnderscoreCase pattern LispCase :: KnownTokenStyle pattern LispCase = HyphenCase pattern HaskellCase :: KnownTokenStyle pattern HaskellCase = CamelCase -------------------------------------------------- pattern ModuleCase :: KnownTokenStyle pattern ModuleCase = ClassCase pattern PackageCase :: KnownTokenStyle pattern PackageCase = HyphenCase -- pattern HaskellModuleCase = ClassCase -- pattern HaskellPackageCase = HyphenCase --pattern ModuleCase = DotCase -------------------------------------------------- pattern FilepathCase :: KnownTokenStyle pattern FilepathCase = SlashCase -------------------------------------------------- -------------------------------------------------- -- | type ShowPrinter t a = (Enum a, Show a, IsString t) -------------------------------------------------- -- | type ReadParser t a = (Read a, String ~ t) -------------------------------------------------- {-| -} data PrintConfig t a = PrintConfig { style :: TokenStyle -- ^ The style a constructor is printed as. , showHaskell :: (a -> t) -- ^ How to show a value as a Haskell identifier\/constructor (often 'show'). --, values :: [a] -- } #if HAS_EXTENSION_DerivingStrategies deriving stock (Generic) deriving anyclass (NFData) #endif -------------------------------------------------- -- | @= 'defaultPrintConfig'@ instance (ShowPrinter t a) => Default (PrintConfig t a) where def = defaultPrintConfig {-| @ 'style' = 'fromKnownTokenStyle' 'HyphenCase' @ -} defaultPrintConfig :: (ShowPrinter t a) => PrintConfig t a defaultPrintConfig = PrintConfig { style = fromKnownTokenStyle HyphenCase , showHaskell = fromString . show } -------------------------------------------------- {-| -} data ParseConfig t a = ParseConfig { styles :: [TokenStyle] -- ^ Which styles the paser accepts. , readHaskell :: (t -> Maybe a) -- ^ How to read a value as a Haskell identifier\/constructor (often 'readMay'). } #if HAS_EXTENSION_DerivingStrategies deriving stock (Generic) deriving anyclass (NFData) #endif -------------------------------------------------- -- | @= 'defaultParseConfig'@ instance (ReadParser t a) => Default (ParseConfig t a) where def = defaultParseConfig {-| @ 'styles' = ['fromKnownTokenStyle' 'HyphenCase'] @ -} defaultParseConfig :: (ReadParser t a) => ParseConfig t a defaultParseConfig = ParseConfig { styles = [fromKnownTokenStyle HyphenCase] , readHaskell = readMay } -------------------------------------------------- -------------------------------------------------- {-| -} printer :: (ShowPrinter String a) => Print a printer = printerWith defaultPrintConfig -------------------------------------------------- {-| -} printerWith :: PrintConfig String a -> Print a printerWith PrintConfig{..} = go where go x = munge (showHaskell x) munge s = s -- TODO -------------------------------------------------- -------------------------------------------------- {-| -} parser :: (ReadParser String a) => Parse a parser = parserWith defaultParseConfig -------------------------------------------------- {-| -} parserWith :: ParseConfig String a -> Parse a parserWith ParseConfig{..} = go > maybeMonadThrow where go s = readHaskell (munge s) munge s = s -- TODO -------------------------------------------------- -------------------------------------------------- {-| Under tokenization (i.e. parsing into tokens), some information about capitalization must be preserved. For example, this \"word\": @"GHCVersion" @ is represented as (and should be parsed into): @ [ 'toAcronymToken' \"GHC\" , 'toSubwordToken' \"Version\" ] :: 'Tokens' @ which is equivalent to (i.e. lower-cased and without the smart-constructors): @ [ 'AcronymToken' \"ghc\" , 'SubwordToken' \"version\" ] :: 'Tokens' @ -} newtype Tokens = Tokens (NonEmpty Token) #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Generic) deriving newtype (Eq,Ord,Semigroup) deriving newtype (NFData,Hashable) #endif -------------------------------------------------- {- | @newtype@ wrapping\/unwrapping only. NOTE 'fromList' is /partial/, crashing on an empty list literal (see 'unsafeTokensFromList'). -} instance IsList Tokens where type Item Tokens = Token fromList = unsafeTokensFromList toList = coerce > NonEmpty.toList {- | @≡ (':|' [])@ (i.e. a singleton token.) -} instance IsString Tokens where -- TODO fromString = fromString > (:| []) > Tokens -------------------------------------------------- {-| Dumb constructor. NOTE 'fromList' is /partial/, crashing on an empty list literal. -} unsafeTokensFromList :: [Token] -> Tokens unsafeTokensFromList = \case [] -> Prelude.error message -- TODO `Partial` (t : ts) -> coerce (t :| ts) where message = "« Tokens » the list must be non-empty" -------------------------------------------------- {-| A valid 'Token' MUST be: * case-insensitive (i.e. 'CI.foldCase'); * non-empty (i.e. not @""@ and\/or @[]@). -} data Token = SubwordToken Subword | AcronymToken [Char] | EmptyToken #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Eq,Ord,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- -- | @= 'toSubwordToken'@ -- -- With case-folding via 'CI.foldCase'. instance IsString Token where fromString = toSubwordToken -------------------------------------------------- instance CI.FoldCase Token where foldCase :: Token -> Token foldCase = \case AcronymToken acronym -> toAcronymToken acronym SubwordToken (Subword subword) -> toSubwordToken subword t -> t -- SubwordToken (Subword subword) -> SubwordToken (CI.foldCase subword) -------------------------------------------------- {-| Smart constructor for 'SubwordToken'. Calls 'CI.foldCase' for case-insensitivity. -} toSubwordToken :: String -> Token toSubwordToken = safeSubword > maybe (EmptyToken) SubwordToken -------------------------------------------------- {-| Smart constructor for 'AcronymToken'. Calls 'CI.foldCase' for case-insensitivity. -} toAcronymToken :: String -> Token toAcronymToken = CI.foldCase > AcronymToken -------------------------------------------------- {-| Represents one part of word being tokenized. A valid 'Subword', like a valid 'Token', MUST be: * case-insensitive (i.e. 'CI.foldCase'); * non-empty (i.e. not @""@). NOTE 'Subword' is a 'Semigroup' but /not/ a 'Monoid'. Conceptually, it's one-or-more case-folded characters: @'NonEmpty' ('CI' 'Char') @ -} newtype Subword = Subword String #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Lift,Generic) deriving newtype (Eq,Ord,Semigroup) deriving newtype (NFData,Hashable) #endif -------------------------------------------------- {-| @≡ 'toSubword'@ NOTE 'fromString' is /partial/, crashing on an empty string literal (see 'unsafeSubword''). While 'fromString' may be called directly, it's (idiomatically) called "indirectly" by the compiler, when converting string literals under @-XOverloadedStrings@; thus, many error messages from its partiality /should/ have a stack traces. -} instance IsString Subword where fromString = coerce -------------------------------------------------- {-| Smart constructor for 'Subword'. Besides the constructor itself, it calls: * 'CI.foldCase' for case-insensitivity. -} safeSubword :: String -> Maybe Subword safeSubword = \case "" -> Nothing s -> Just (go s) where go = CI.foldCase > Subword -------------------------------------------------- {-| Dumb constructor, for 'Subword'. See 'safeSubword' (which this wraps). -} unsafeSubword :: String -> Subword unsafeSubword = safeSubword > maybe (Prelude.error message) id -- TODO `Partial` where message = "« Subword » the string must be non-empty" -------------------------------------------------- {-| -} data AcronymStyle = UpperCasedAcronym | TitleCasedAcronym #if HAS_EXTENSION_DerivingStrategies deriving stock (Enum,Bounded,Ix) deriving anyclass (GEnum) deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- -- | @= 'defaultAcronymStyle'@ instance Default AcronymStyle where def = defaultAcronymStyle -- | @= 'UpperCasedAcronym'@ defaultAcronymStyle :: AcronymStyle defaultAcronymStyle = UpperCasedAcronym -------------------------------------------------- -------------------------------------------------- {-| -} data TokenizationConfig = TokenizationConfig { acronymStyle :: AcronymStyle , inputStyle :: TokenStyle , outputStyle :: TokenStyle } #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- {-| -} data PrintTokenConfig = PrintTokenConfig { acronymStyle :: AcronymStyle , tokenStyle :: TokenStyle } #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- {-| -} data ParseTokenConfig = ParseTokenConfig { acronymStyle :: AcronymStyle , tokenStyle :: TokenStyle } #if HAS_EXTENSION_DerivingStrategies deriving stock (Show,Read,Eq,Ord,Lift,Generic) deriving anyclass (NFData,Hashable) #endif -------------------------------------------------- -------------------------------------------------- {-| [Example] (1) We have an @Enum@ whose constructors' names: * are class-cased (the conventional styling); * are suffixed by the name of their type; * and may have acronyms (i.e. an alpha-numeric sequence, with all letters being upper-case). e.g.: @ data Query = WindowIdQuery | WMClassQuery deriving (Enum,Bounded,Show,Read) @ (2) We want to render its constructors' names, as the valid values of a command-line option i.e.: @ printQueryForCmdLn :: Print Query printQueryForCmdLn WindowIdQuery = "window-id printQueryForCmdLn WMClassQuery = "wm-class @ NOTE the acronym @"WM"@ is (correctly) grouped into a single Token; c.f. the (incorrectly) un-grouped @"w-m-class"@, which is less legible. (3) We can also print it as an idiomatic command-line option, by (type) name. i.e.: @ printQueryAsLongOptionAndShortOption :: (String, Char) printQueryAsLongOptionAndShortOption = ("query", 'q') @ e.g. (assuming an executable named @.\/example@): @ $ .\/example --query=window-id $ .\/example -q wm-class @ -} restyleString :: TokenizationConfig -> (String -> String) restyleString TokenizationConfig{..} input = output where output = tokens & printTokens PrintTokenConfig{acronymStyle, tokenStyle = outputStyle} tokens = input & parseTokens ParseTokenConfig{acronymStyle, tokenStyle = inputStyle} -------------------------------------------------- {-| Specializes 'restyleString'; with 'ClassCase', 'HyphenCase', and 'UpperCasedAcronym'. >>> restyleClassCasedToHyphenated "WMClass" "wm-class" >>> restyleClassCasedToHyphenated "WindowId" "window-id" -} restyleClassCasedToHyphenated :: String -> String restyleClassCasedToHyphenated = restyleString config where config = TokenizationConfig { acronymStyle = UpperCasedAcronym , inputStyle = fromKnownTokenStyle ClassCase , outputStyle = fromKnownTokenStyle HyphenCase } -- restyleHaskellContructorTo -------------------------------------------------- -------------------------------------------------- {-| -} printTokens :: PrintTokenConfig -> Tokens -> String printTokens config@PrintTokenConfig{tokenStyle = TokenStyle{separator,casing}} = go where go (Tokens ts) = ts & ( fmap _printToken_ > _capitalizing_ > _separating_ > NonEmpty.toList > concat ) _printToken_ = printToken config _capitalizing_ = capitalizeByCasing casing _separating_ = intersperseBySeparator separator -------------------------------------------------- {-| >>> :set -XOverloadedStrings >>> :set -XOverloadedLists >>> toList (intersperseBySeparator (WordSeparator (Just '-')) ["cabal","new","build"]) ["cabal","-","new","-","build"] >>> toList (intersperseBySeparator (WordSeparator Nothing) ["cabal","new","build"]) ["cabal","new","build"] -} intersperseBySeparator :: WordSeparator -> NonEmpty String -> NonEmpty String intersperseBySeparator = \case WordSeparator (Just c) -> NonEmpty.intersperse [c] _ -> id -------------------------------------------------- {-| -} capitalizeByCasing :: WordCasing -> NonEmpty String -> NonEmpty String capitalizeByCasing (WordCasing {firstWord, laterWords}) = go where go (t :| ts) = (capitalizeHead t :| capitalizeTail ts) capitalizeHead = capitalizeBy firstWord capitalizeTail = fmap (capitalizeBy laterWords) -------------------------------------------------- {-| -} capitalizeBy :: SubwordCasing -> String -> String capitalizeBy = \case -- TODO replace all Strings with Text, for correct unicode operations LowerCased -> lowercaseString TitleCased -> titlecaseString UpperCased -> uppercaseString -------------------------------------------------- -- | lowercaseString :: String -> String lowercaseString = fmap toLower -- | titlecaseString :: String -> String titlecaseString = \case [] -> [] (c : cs) -> toTitle c : uppercaseString cs -- | uppercaseString :: String -> String uppercaseString = fmap toUpper -------------------------------------------------- {-| -} printToken :: PrintTokenConfig -> Token -> String printToken PrintTokenConfig{ acronymStyle, tokenStyle } = go where go _ = "" -------------------------------------------------- -------------------------------------------------- {-| -} parseTokens :: ParseTokenConfig -> String -> Tokens parseTokens ParseTokenConfig{ acronymStyle, tokenStyle } = go where go s = fromString s -------------------------------------------------- {-| -} parseToken :: ParseTokenConfig -> String -> Token parseToken ParseTokenConfig{ acronymStyle, tokenStyle } = go where go s = fromString s -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- {-| -} fromKnownTokenStyle :: KnownTokenStyle -> TokenStyle fromKnownTokenStyle = \case UnderscoreCase -> separatorTokenStyle '_' HyphenCase -> separatorTokenStyle '-' SlashCase -> separatorTokenStyle '/' DotCase -> separatorTokenStyle '.' CamelCase -> TokenStyle { separator = noSeparator , casing = WordCasing { firstWord = LowerCased , laterWords = TitleCased } } ClassCase -> TokenStyle { separator = noSeparator , casing = uniformWordCasing TitleCased } ConstCase -> TokenStyle { separator = charSeparator '_' , casing = uniformWordCasing UpperCased } PascalCase -> TokenStyle { separator = charSeparator '_' , casing = uniformWordCasing TitleCased } SqueezeCase -> emptyTokenStyle -------------------------------------------------- {-| -} separatorTokenStyle :: Char -> TokenStyle separatorTokenStyle c = TokenStyle { separator = charSeparator c , casing = uniformWordCasing LowerCased } -------------------------------------------------- {-| -} emptyTokenStyle :: TokenStyle emptyTokenStyle = TokenStyle { separator = noSeparator , casing = uniformWordCasing LowerCased } -------------------------------------------------- {-| -} uniformWordCasing :: SubwordCasing -> WordCasing uniformWordCasing x = WordCasing { firstWord = x , laterWords = x } -------------------------------------------------- {-| @ ≡ 'Nothing' @ -} noSeparator :: WordSeparator noSeparator = WordSeparator Nothing -- (Nothing :: Maybe Char) -------------------------------------------------- {-| @ ≡ 'Just' @ -} charSeparator :: Char -> WordSeparator charSeparator c = coerce (Just c) -------------------------------------------------- -------------------------------------------------- {- Old Code -------------------------------------------------- {-| -} newtype Casing = Casing (Maybe Char) deriving stock (Show,Read,Lift,Generic) deriving newtype (Eq,Ord,Semigroup,Monoid) deriving newtype (NFData,Hashable) data KnownTokenStyle | TrainCase -- ^ e.g. @"TRAIN-CASE"@ | SqueezeCase -- ^ e.g. @"squeezecase"@ {-| Simple parser. 'ParseM' specialized to « m ~ Maybe ». -} type Parse a = String -> Maybe a -------------------------------------------------- {-| Simple parser. -} type ParseM a = (forall m. (MonadThrow m) => String -> m a) {-| -} detokenizeTokenStyle :: DetokenizeConfig -> TokenStyle -> Tokens -> String detokenizeTokenStyle DetokenizeConfig{..} TokenStyle{..} (Tokens ts) = s where s = "TODO" -------------------------------------------------- {-| -} tokenizeTokenStyle :: TokenizationConfig -> String -> Tokens tokenizeTokenStyle TokenizationConfig{..} s = Tokens ts where ts = ["TODO"] -------------------------------------------------- -------------------------------------------------- -- | @= 'defaultTokenizationConfig '@ instance Default TokenizationConfig where def = defaultTokenizationConfig defaultTokenizationConfig :: TokenizationConfig defaultTokenizationConfig = TokenizationConfig { acronymStyle = def } -------------------------------------------------- {-| -} printWithTokenStyle :: TokenStyle -> String -> String printWithTokenStyle TokenStyle{..} = go where go s = s -------------------------------------------------- {-| -} parseWithTokenStyle :: TokenStyle -> String -> String parseWithTokenStyle TokenStyle{..} = go where go s = s data Token = SubwordToken (CI String) | AcronymToken [CI Char] deriving stock (Show,Read,Eq,Ord,Generic) deriving anyclass (NFData,Hashable) -------------------------------------------------- -- | @= 'SubwordToken'@ instance IsString Token where fromString = CI.mk > SubwordToken -------------------------------------------------- -- | @= 'SubwordToken'@ instance CI.FoldCase Token where foldCase :: Token -> Token foldCase = \case SubwordToken subword -> AcronymToken acronym -> -------------------------------------------------- unsafeTokensFromList = coerce > NonEmpty.fromList -------------------------------------------------- printTokens config@PrintTokenConfig{ acronymStyle, tokenStyle } = \(Tokens ts) -> (go <$> ts) > NonEmpty.intersperse sep where go = printToken config go (Tokens ts) = NonEmpty.toList ts & ( fmap _printToken_ > capitalizeCons > intersperseSeparator > concat ) _printToken_ = printToken config intersperseSeparator = case separator of WordSeparator (Just c) -> intersperse [c] _ -> id -------------------------------------------------- capitalizers :: ( String -> String , String -> String ) capitalizers = ( id , id ) -} -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- {- Notes -------------------------------------------------- https://en.m.wikipedia.org/wiki/Naming_convention_(programming) > In the C standard library, abbreviated names are the most common (e.g. isalnum for a function testing whether a character is alphanumeric), > UpperCamelCase for class names, CAPITALIZED_WITH_UNDERSCORES for constants, and lowercase_separated_by_underscores for other names. https://en.m.wikipedia.org/wiki/Sigil_(computer_programming) > In Common Lisp, special variables (with dynamic scope) are typically surrounded with * in what is dubbed the “earmuff convention”. While this is only convention, and not enforced, the language itself adopts the practice (e.g., *standard-output*). Similarly, some programmers surround constants with +. > In Scheme, by convention, the names of procedures that always return a boolean value usually end in "?". Likewise, the names of procedures that store values into parts of previously allocated Scheme objects (such as pairs, vectors, or strings) usually end in "!". > In Unix shell scripting and in utilities such as Makefiles, the "$" is a unary operator that translates the name of a variable into its contents. While this may seem similar to a sigil, it is properly a unary operator for lexical indirection, similar to the * dereference operator for pointers in C, as noticeable from the fact that the dollar sign is omitted when assigning to a variable. https://en.m.wikipedia.org/wiki/Stropping_(syntax) > -------------------------------------------------- Data.CaseInsensitive This module is intended to be imported qualified. May I suggest: import Data.CaseInsensitive ( CI ) import qualified Data.CaseInsensitive as CI Note that the FoldCase instance for ByteStrings is only guaranteed to be correct for ISO-8859-1 encoded strings! data CI s A CI s provides Case Insensitive comparison for the string-like type s (for example: String, Text, ByteString, etc.). Note that CI s has an instance for IsString which together with the OverloadedStrings language extension allows you to write case insensitive string literals as in: > ("Content-Type" :: CI Text) == ("CONTENT-TYPE" :: CI Text) True instance FoldCase Char where foldCase = toLower foldCaseList = TL.unpack . TL.toCaseFold . TL.pack instance FoldCase T.Text where foldCase = T.toCaseFold instance FoldCase TL.Text where foldCase = TL.toCaseFold instance FoldCase (CI s) where foldCase (CI _ l) = CI l l -------------------------------------------------- -------------------------------------------------- map :: (Char -> Char) -> Text -> Text O(n) map f t is the Text obtained by applying f to each element of t. Example: >>> let message = pack "I am not angry. Not at all." >>> T.map (\c -> if c == '.' then '!' else c) message "I am not angry! Not at all!" Subject to fusion. Performs replacement on invalid scalar values. -------------------------------------------------- intercalate :: Text -> [Text] -> Text O(n) The intercalate function takes a Text and a list of Texts and concatenates the list after interspersing the first argument between each element of the list. Example: >>> T.intercalate "NI!" ["We", "seek", "the", "Holy", "Grail"] "WeNI!seekNI!theNI!HolyNI!Grail" -------------------------------------------------- intersperse :: Char -> Text -> Text O(n) The intersperse function takes a character and places it between the characters of a Text. Example: >>> T.intersperse '.' "SHIELD" "S.H.I.E.L.D" Subject to fusion. Performs replacement on invalid scalar values. -------------------------------------------------- transpose :: [Text] -> [Text] O(n) The transpose function transposes the rows and columns of its Text argument. Note that this function uses pack, unpack, and the list version of transpose, and is thus not very efficient. Examples: >>> transpose ["green","orange"] ["go","rr","ea","en","ng","e"] >>> transpose ["blue","red"] ["br","le","ud","e"] -------------------------------------------------- reverse :: Text -> Text O(n) Reverse the characters of a string. Example: >>> T.reverse "desrever" "reversed" Subject to fusion. -------------------------------------------------- replace :: Text needle to search for. If this string is empty, an error will occur. -> Text replacement to replace needle with. -> Text haystack in which to search. -> Text O(m+n) Replace every non-overlapping occurrence of needle in haystack with replacement. This function behaves as though it was defined as follows: replace needle replacement haystack = intercalate replacement (splitOn needle haystack) As this suggests, each occurrence is replaced exactly once. So if needle occurs in replacement, that occurrence will not itself be replaced recursively: >>> replace "oo" "foo" "oo" "foo" In cases where several instances of needle overlap, only the first one will be replaced: >>> replace "ofo" "bar" "ofofo" "barfo" In (unlikely) bad cases, this function's time complexity degrades towards O(n*m). -------------------------------------------------- Case conversion -------------------------------------------------- When case converting Text values, do not use combinators like map toUpper to case convert each character of a string individually, as this gives incorrect results according to the rules of some writing systems. The whole-string case conversion functions from this module, such as toUpper, obey the correct case conversion rules. As a result, these functions may map one input character to two or three output characters. For examples, see the documentation of each function. Note: In some languages, case conversion is a locale- and context-dependent operation. The case conversion functions in this module are not locale sensitive. Programs that require locale sensitivity should use appropriate versions of the case mapping functions from the text-icu package. -------------------------------------------------- toLower :: Text -> Text O(n) Convert a string to lower case, using simple case conversion. Subject to fusion. The result string may be longer than the input string. For instance, "İ" (Latin capital letter I with dot above, U+0130) maps to the sequence "i" (Latin small letter i, U+0069) followed by " ̇" (combining dot above, U+0307). -------------------------------------------------- toUpper :: Text -> Text O(n) Convert a string to upper case, using simple case conversion. Subject to fusion. The result string may be longer than the input string. For instance, the German "ß" (eszett, U+00DF) maps to the two-letter sequence "SS". -------------------------------------------------- toTitle :: Text -> Text O(n) Convert a string to title case, using simple case conversion. Subject to fusion. The first letter of the input is converted to title case, as is every subsequent letter that immediately follows a non-letter. Every letter that immediately follows another letter is converted to lower case. The result string may be longer than the input string. For example, the Latin small ligature fl (U+FB02) is converted to the sequence Latin capital letter F (U+0046) followed by Latin small letter l (U+006C). Note: this function does not take language or culture specific rules into account. For instance, in English, different style guides disagree on whether the book name "The Hill of the Red Fox" is correctly title cased—but this function will capitalize every word. -------------------------------------------------- -} --------------------------------------------------