DEV Community

Discussion on: Haskell - Enforcing Naming Convention with Parsec

Collapse
 
sshine profile image
Simon Shine • Edited

Excellent!

This is actually a good use-case for regular expressions. In Haskell you have regex-applicative which is also a parser combinator library, except it is not monadic, and for that reason is restricted to regular languages.

Here is how a parser might look like:

{-# LANGUAGE RecordWildCards #-}

module MigratumRegex
  ( MigrationFile
  , prettyMigrationFile
  , parseMigrationFile
  ) where

import Control.Applicative (some)
import Data.Char (isAlphaNum)
import Data.Version (Version, makeVersion, showVersion)

import Text.Regex.Applicative (RE, sym, psym, string, (=~))
import Text.Regex.Applicative.Common (decimal)

data MigrationFile = MigrationFile
  { mfVersion :: Version
  , mfName    :: FilePath
  } deriving (Eq, Show)

type Parser a = RE Char a

prettyMigrationFile :: MigrationFile -> String
prettyMigrationFile MigrationFile{..} =
  "V" <> showVersion mfVersion <> "__" <> mfName <> ".sql"

parseMigrationFile :: String -> Maybe MigrationFile
parseMigrationFile = (=~ migrationFile)

-- V<version number>__<file name>.sql
migrationFile :: Parser MigrationFile
migrationFile =
  MigrationFile
    <$> (sym 'V' *> version)
    <*> (string "__" *> filename <* string ".sql")

version :: Parser Version
version = fmap (makeVersion . pure) decimal

filename :: Parser FilePath
filename = some (psym isAlphaNum)

And here it is in action:

λ> prettyMigrationFile <$> parseMigrationFile "V123__hello.sql"
Just "V123__hello.sql"