packages feed

curly-expander-0.2.0.2: Text/CurlyExpander.hs

{-|
Module      : Text.CurlyExpander
License     : LGPL-3
Maintainer  : p-w@stty.cz
Stability   : testing
Portability : POSIX

This is the main module of the curly-expander package.

-}

{-# LANGUAGE OverloadedStrings #-}

module Text.CurlyExpander (curlyExpand) where

import qualified Data.Text as T
import qualified Data.Text.Lazy as L

import Data.Text.Lazy.Builder.Int (decimal)
import Data.Text.Lazy.Builder (toLazyText)

import Text.Parsec
import Text.Parsec.Text

import Data.Char


cumulatorComma :: Parser [L.Text]
cumulatorComma = do
  atoms <- (try p_range) <|> (try p_char_range) <|> p_atoms
  return atoms 
  
  where
    p_range :: Parser [L.Text]
    p_range = do
      nb1 <- many1 digit
      _ <- string ".."
      nb2 <- many1 digit

      return$ map (toLazyText . decimal) $ get_range (read nb1) (read nb2)
      where
        get_range :: Int -> Int -> [Int]
        get_range n1 n2
          | n1 > n2 = reverse$ get_range n2 n1
          | otherwise = [n1..n2]

    p_char_range :: Parser [L.Text]
    p_char_range = do
      char1 <- anyChar
      _ <- string ".."
      char2 <- anyChar
      
      return [ L.pack [p] | p <- get_range char1 char2 ]
      where
        get_range :: Char -> Char -> [Char]
        get_range c1 c2
          | n1 > n2 = reverse$ get_range c2 c1
          | otherwise = map chr [n1..n2]

          where
            n1 = ord c1
            n2 = ord c2

    p_atoms :: Parser [L.Text]
    p_atoms = do
      molecule <- many1$ try p_atom
      terminal_atom <- innerInputP
      return $ (concat molecule) ++ terminal_atom

    p_atom :: Parser [L.Text]
    p_atom = do

      atom <- innerInputP
      _ <- char ','
      return atom


bracketP :: Parser [L.Text]
bracketP = do

  _ <- char '{'
  ret <- cumulatorComma
  _ <- char '}'

  return$ ret

charP :: Parser [L.Text]
charP = do
  c <- anyChar
  return [L.pack [c]]

nonSpecialCharP :: Parser [L.Text]
nonSpecialCharP = do
  c <- noneOf ",}"
  return [L.pack [c]]

innerNonEmptyInputP :: Parser [L.Text]
innerNonEmptyInputP = do
  molecule <- (try bracketP <|> nonSpecialCharP)
  rest <- innerInputP

  return [ L.append a b | a <- molecule, b <- rest ]

innerInputP :: Parser [L.Text]
innerInputP = (innerNonEmptyInputP <|> emptyInputP)

nonEmptyInputP :: Parser [L.Text]
nonEmptyInputP = do
  molecule <- (try bracketP <|> charP)
  rest <- inputP

  return [ L.append a b | a <- molecule, b <- rest ]

emptyInputP :: Parser [L.Text]
emptyInputP = do
  return [""]

inputP :: Parser [L.Text]
inputP = (nonEmptyInputP <|> emptyInputP)
  

-- | Curly braces (brackets) expand function
--
-- First argument is a `Data.Text`, which you want to expand. Second argument is a list of expanded `Data.Text`s.
--
-- There are given few usage examples:
--
-- >>> curlyExpand "car{A,B}"
-- ["carA","carB"]
--
-- >>> curlyExpand "car{1..5}"
-- ["car1","car2","car3","car4","car5"]
--
-- >>> curlyExpand "car{{A,B},{C,D}}"
-- ["carA", "carB", "carC", "carD"]
--
-- >>> curlyExpand "{car,bus}{A..C}"
-- ["carA", "carB", "carC", "busA", "busB", "busC"]
--
-- Be aware, that these examples will run only with `OverloadedStrings` language extension and proper `Data.Text` imports.

curlyExpand :: T.Text -> [T.Text]
curlyExpand input =
  case parse inputP "bracket expansion"$ input of
    Left _ -> [input]
    Right ret -> map L.toStrict ret