algebraic-path-0.1: src/ast/AlgebraicPath/Ast/Name.hs
module AlgebraicPath.Ast.Name
( Name,
-- * Constructors
empty,
mapExtensions,
-- * Functors
traverseExtensions,
attoparsecParserOf,
-- * Destructors
null,
toTextBuilder,
toText,
toBase,
toExtensions,
)
where
import AlgebraicPath.Ast.Name.NameSegment qualified as NameSegment
import AlgebraicPath.Util.MonadPlus
import AlgebraicPath.Util.Prelude hiding (empty, null)
import Algorithms.NaturalSort qualified as NaturalSort
import Data.Attoparsec.Text qualified as Attoparsec
import Data.List qualified as List
import Data.Text qualified as Text
import Test.QuickCheck qualified as QuickCheck
import TextBuilder qualified
-- |
-- Structured base of a single component of a path.
data Name = Name
{ -- | Name.
base :: Text,
-- | Extensions in reverse order.
extensions :: [Text]
}
deriving (Eq, Show)
instance QuickCheck.Arbitrary Name where
arbitrary = do
base <-
QuickCheck.oneof
[ NameSegment.toText <$> arbitrary,
pure ""
]
extensions <- fmap NameSegment.toText <$> arbitrary
pure (Name base extensions)
shrink (Name base extensions) =
QuickCheck.shrink
( Text.unpack base,
Text.unpack <$> extensions
)
<&> \(base, extensions) ->
Name
(Text.pack base)
(List.filter (not . Text.null) (Text.pack <$> extensions))
instance Ord Name where
compare l r =
if la == ra
then
if lb < rb
then LT
else
if lb == rb
then EQ
else GT
else
if la < ra
then LT
else GT
where
la = toBaseSortKey l
lb = toExtensionsSortKey l
ra = toBaseSortKey r
rb = toExtensionsSortKey r
instance Hashable Name where
hashWithSalt salt Name {..} =
salt
& extendHash base
& extendHash extensions
where
extendHash = flip hashWithSalt
-- * Constructors
empty :: Name
empty =
Name mempty mempty
mapExtensions :: ([Text] -> [Text]) -> Name -> Name
mapExtensions f = runIdentity . traverseExtensions (Identity . f)
maybeFromText :: Text -> Maybe Name
maybeFromText text =
Attoparsec.parseOnly (attoparsecParserOf <* Attoparsec.endOfInput) text
& either (const Nothing) Just
-- * Functors
traverseExtensions :: (Functor f) => ([Text] -> f [Text]) -> Name -> f Name
traverseExtensions f (Name base extensions) =
f extensions
& fmap
( \list ->
list
& concatMap
( \extension ->
case maybeFromText extension of
Just (Name base' extensions') ->
(extensions' <> [base'])
& filter (not . Text.null)
Nothing -> []
)
& Name base
)
attoparsecParserOf :: Attoparsec.Parser Name
attoparsecParserOf = do
base <- NameSegment.attoparsecParserOf <|> pure ""
extensions <- reverseMany (Attoparsec.char '.' *> NameSegment.attoparsecParserOf)
return (Name base extensions)
-- * Destructors
toBase :: Name -> Text
toBase (Name base _) =
base
toExtensions :: Name -> [Text]
toExtensions (Name _ extensions) =
extensions
toTextBuilder :: Name -> TextBuilder.TextBuilder
toTextBuilder (Name base extensions) =
foldr
(\extension next -> next <> "." <> TextBuilder.text extension)
(TextBuilder.text base)
extensions
toText :: Name -> Text
toText = TextBuilder.toText . toTextBuilder
toBaseSortKey :: Name -> NaturalSort.SortKey
toBaseSortKey = NaturalSort.sortKey . toBase
toExtensionsSortKey :: Name -> [NaturalSort.SortKey]
toExtensionsSortKey = reverse . fmap NaturalSort.sortKey . toExtensions
null :: Name -> Bool
null (Name name extensions) =
Text.null name && List.null extensions