packages feed

hwm-0.1.0: src/HWM/Core/Parsing.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}

module HWM.Core.Parsing
  ( parseField,
    fromByteString,
    firstWord,
    removeHead,
    unconsM,
    sepBy,
    fromToString,
    SourceText,
    genUrl,
    Parse (..),
    parseOptions,
    parsePkgString,
    ParseCLI (..),
    flag,
    ignoreSpaces,
    parseLS,
  )
where

import Data.ByteString.Char8 (unpack)
import Data.Char (isSeparator)
import Data.Text
  ( break,
    drop,
    intercalate,
    pack,
    singleton,
    splitOn,
    strip,
    uncons,
  )
import qualified Data.Text as T
import Options.Applicative (Mod, Parser, help, long, short, strOption, switch)
import Options.Applicative.Builder (OptionFields)
import Relude hiding
  ( break,
    drop,
    head,
    intercalate,
    isPrefixOf,
    null,
    uncons,
    words,
  )

type SourceText = Text

flag :: Char -> String -> String -> Parser Bool
flag s l h = switch (long l <> short s <> help h)

parseOptions :: Mod OptionFields Text -> Parser [Text]
parseOptions x = fmap (>>= parseLS) (many (strOption x))

parseLS :: Text -> [Text]
parseLS = map T.strip . T.splitOn ","

parseField :: SourceText -> (SourceText, SourceText)
parseField = second (strip . drop 1) . breakAt (== ':')

firstWord :: SourceText -> (SourceText, SourceText)
firstWord = breakAt isSeparator

fromByteString :: ByteString -> SourceText
fromByteString = pack . unpack

ignoreSpaces :: SourceText -> SourceText
ignoreSpaces = T.filter (not . isSeparator)

breakAt :: (Char -> Bool) -> SourceText -> (SourceText, SourceText)
breakAt f = bimap strip strip . break f . strip

sepBy :: (MonadFail m, Parse a) => SourceText -> SourceText -> m [a]
sepBy sep = traverse parse . splitOn sep . ignoreSpaces

removeHead :: Char -> SourceText -> (Bool, SourceText)
removeHead should txt = maybe (False, txt) has (uncons txt)
  where
    has (x, xs)
      | x == should = (True, xs)
      | otherwise = (False, txt)

unconsM :: (MonadFail m) => String -> SourceText -> m (SourceText, SourceText)
unconsM m x = first singleton <$> maybe (fail $ m <> "<>: " <> toString x) pure (uncons x)

fromToString :: (ToString a) => a -> SourceText
fromToString = pack . toString

genUrl :: Text -> [Text] -> Text
genUrl domain = intercalate "/" . (domain :)

class Parse a where
  parse :: (MonadFail m) => Text -> m a

class ParseCLI a where
  parseCLI :: Parser a

instance Parse Int where
  parse t =
    maybe (fail $ "Could not parse Int: '" <> toString t <> "'!") pure (readMaybe $ toString t)

parsePkgString :: Text -> (Text, Text)
parsePkgString input =
  let base = T.takeWhile (/= '@') input
      (namePart, versionPart) = T.breakOnEnd "-" base
   in if T.null versionPart || T.null namePart
        then (base, "")
        else (T.dropEnd 1 namePart, versionPart)