language-conf (empty) → 0.1.1.0
raw patch · 11 files changed
+538/−0 lines, 11 filesdep +QuickCheckdep +basedep +deepseqsetup-changed
Dependencies added: QuickCheck, base, deepseq, directory, filepath, hspec, hspec-megaparsec, language-conf, megaparsec, pretty, scientific, semigroups, text, transformers, unordered-containers
Files
- LICENSE +21/−0
- Setup.hs +2/−0
- language-conf.cabal +92/−0
- src/ConfFmt.hs +20/−0
- src/Data/Conf.hs +40/−0
- src/Data/Conf/Internal.hs +77/−0
- src/Data/Conf/PrettyPrint.hs +55/−0
- src/Data/Conf/Types.hs +23/−0
- test/Data/ConfSpec.hs +201/−0
- test/SanitySpec.hs +6/−0
- test/Spec.hs +1/−0
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2016 Pedro Tacla Yamada++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ language-conf.cabal view
@@ -0,0 +1,92 @@+-- This file has been generated from package.yaml by hpack version 0.14.0.+--+-- see: https://github.com/sol/hpack++name: language-conf+version: 0.1.1.0+synopsis: Conf parsers and pretty-printers for the Haskell programming language.+description: @language-conf@ contains @.conf@ (e.g. nginx configuration) parsers and pretty-printers for the Haskell programming language.+ "Data.Conf" exports all the API surface in the package.+ "ConfFmt" is a @.conf@ file formatter that serves as an example; it's built as @conffmt@ by the cabal configuration. You can see its' source-code <https://github.com/beijaflor-io/language-hcl/src/ConfFmt.hs here>+category: Data+homepage: https://github.com/beijaflor-io/haskell-language-conf#readme+bug-reports: https://github.com/beijaflor-io/haskell-language-conf/issues+author: Pedro Tacla Yamada+maintainer: tacla.yamada@gmail.com+copyright: Copyright (c) 2016 Pedro Tacla Yamada+license: MIT+license-file: LICENSE+build-type: Simple+cabal-version: >= 1.10++source-repository head+ type: git+ location: https://github.com/beijaflor-io/haskell-language-conf++flag conffmt+ description: Build the conffmt utility+ manual: False+ default: False++library+ hs-source-dirs:+ src+ build-depends:+ base >=4.8 && <5+ , deepseq+ , directory >=1.2.2.0+ , filepath >=1.4.0.0+ , megaparsec >=5+ , pretty+ , scientific >=0.3.4.6+ , semigroups >=0.18.1+ , text >=1.2.2.1+ , unordered-containers+ exposed-modules:+ Data.Conf+ Data.Conf.PrettyPrint+ Data.Conf.Types+ Data.Conf.Internal+ default-language: Haskell2010++executable conffmt+ main-is: ConfFmt.hs+ hs-source-dirs:+ src+ build-depends:+ base+ , text+ , megaparsec+ , language-conf+ , pretty+ if !(flag(conffmt))+ buildable: False+ other-modules:+ Data.Conf+ Data.Conf.Internal+ Data.Conf.PrettyPrint+ Data.Conf.Types+ default-language: Haskell2010++test-suite hspec+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ hs-source-dirs:+ test+ build-depends:+ QuickCheck+ , base+ , directory+ , filepath+ , hspec+ , language-conf+ , megaparsec+ , hspec-megaparsec+ , pretty+ , semigroups >=0.18.1+ , text+ , transformers >=0.4.2.0+ other-modules:+ Data.ConfSpec+ SanitySpec+ default-language: Haskell2010
+ src/ConfFmt.hs view
@@ -0,0 +1,20 @@+import qualified Data.Conf as Conf+import qualified Data.Conf.PrettyPrint as Conf+import qualified Data.Text.IO as Text (readFile)+import System.Environment (getArgs)+import System.Exit (exitFailure)+import System.IO+import qualified Text.Megaparsec as Megaparsec++main :: IO ()+main = do+ as <- getArgs+ case as of+ (filePath:_) -> do+ contents <- Text.readFile filePath+ case Megaparsec.parse Conf.conf filePath contents of+ Left err -> hPrint stderr err+ Right ast -> print (Conf.pPrint ast)+ _ -> do+ hPutStrLn stderr "Usage: conffmt <conf-file>"+ exitFailure
+ src/Data/Conf.hs view
@@ -0,0 +1,40 @@+{-|+Module: Data.Conf+Description: Exports a @.conf@ Megaparsec parser through 'conf'+Copyright: (c) Copyright Pedro Tacla Yamada 2016+License: MIT+Maintainer: tacla.yamada@gmail.com+Stability: experimental+Portability: unknown++This modules contains the 'conf' Megaparsec parser for @.conf@ files and Pretty+instance.+-}+module Data.Conf+ (+ -- * Entry-points+ conf+ , pPrintConf+ , runParser+ -- * Types+ , Conf (..)+ , ConfStatement (..)+ , Block (..)+ , Comment (..)+ , Expression (..)+ -- * Parser+ , confStatement+ , confStatementLines+ , block+ , expression+ , argument+ -- * Pretty-printer+ , Pretty(..)+ , pPrint+ )+ where++import Data.Conf.Internal+import Data.Conf.PrettyPrint+import Data.Conf.Types+import Text.Megaparsec (runParser)
+ src/Data/Conf/Internal.hs view
@@ -0,0 +1,77 @@+module Data.Conf.Internal+ where++import Control.Monad (void)+import Data.Conf.Types+import Data.Either+import Data.Maybe (catMaybes, fromMaybe)+import Data.Text (Text)+import qualified Data.Text as Text+import Text.Megaparsec+import Text.Megaparsec.Text++-- | The .conf parser+--+-- See "Text.Megaparsec"+conf :: Parser Conf+conf = label "conf" $+ postProcessStatementLines <$> manyTill confStatementLines eof++-- ** Support functions+skipSpace :: Parser ()+skipSpace = void $ many spaceChar++confStatement :: Parser ConfStatement+confStatement = label "confStatement" $+ try (ConfStatementComment <$> comment)+ <|> try (ConfStatementBlock <$> block)+ <|> ConfStatementExpression <$> expression++confStatementLines :: Parser [ConfStatement]+confStatementLines = do+ skipSpace+ s <- confStatement+ -- If we're after a comment, the parser consumed a EOL. Anywhere else we+ -- should look for two ends of lines repeated (the end of the current+ -- expression, plus an empty one)+ me <- optional $ try $ case s of+ ConfStatementComment _ -> eol >> return ConfStatementEmptyLine+ _ -> eol >> eol >> return ConfStatementEmptyLine+ skipSpace+ return $ catMaybes [Just s, me]++postProcessStatementLines :: [[ConfStatement]] -> [ConfStatement]+postProcessStatementLines = removeTrailing . concat+ where+ removeTrailing [] = []+ removeTrailing xs | last xs == ConfStatementEmptyLine = init xs+ | otherwise = xs++comment :: Parser Comment+comment = label "comment" $ do+ _ <- string "#"+ -- Comments always start with a space+ c <- fromMaybe ' ' <$> optional (char ' ')+ Comment . Text.pack . (c:) <$> manyTill anyChar eol++block :: Parser Block+block = label "block" $ do+ s <- flip someTill (char '{') $ do+ k <- Text.pack <$>+ some (noneOf ['{', ' ', '}', ';'])+ skipSpace+ return k+ skipSpace+ Block s . postProcessStatementLines <$>+ manyTill confStatementLines (char '}')++expression :: Parser Expression+expression = label "expression" $ do+ s <- manyTill (letterChar <|> char '_' <|> char '-') spaceChar+ skipSpace+ as <- manyTill argument (char ';')+ return $ Expression (Text.pack s) as++argument :: Parser Text+argument = label "argument" $+ Text.pack <$> some (noneOf [';'])
+ src/Data/Conf/PrettyPrint.hs view
@@ -0,0 +1,55 @@+{-|+Module: Data.Conf.PrettyPrint+Description: Pretty-printer for 'Data.Conf'+Copyright: (c) Copyright Pedro Tacla Yamada 2016+License: MIT+Maintainer: tacla.yamada@gmail.com+Stability: experimental+Portability: unknown++Pretty-printer for "Data.Conf". Declares a 'Pretty' instance for+'ConfStatement'.+-}+{-# LANGUAGE OverloadedStrings #-}+module Data.Conf.PrettyPrint+ (+ pPrintConf+ , Pretty (..)+ , Doc (..)+ )+ where++import Data.Conf.Types+import Data.Text (Text)+import qualified Data.Text as Text+import Text.PrettyPrint.HughesPJClass++-- | Pretty-prints a 'Conf' to a 'Doc'+--+-- 'pPrint' restricted to 'Conf'+--+-- @+-- print (pPrintConf c)+-- @+--+-- See "Text.PrettyPrint"+pPrintConf = pPrint :: Conf -> Doc++instance Pretty ConfStatement where+ pPrint s = case s of+ ConfStatementEmptyLine -> text ""+ ConfStatementComment (Comment c) ->+ "#" <> ttext c+ ConfStatementBlock (Block ks ss) ->+ thsep ks <+> "{"+ $+$ nest 2 (pPrintList (PrettyLevel 0) ss) $+$+ "}"+ ConfStatementExpression (Expression t ts) ->+ ttext t <+> thsep ts <> ";"+ pPrintList _ ss = foldl ($+$) empty (map pPrint ss)++thsep :: [Text] -> Doc+thsep = hsep . map ttext++ttext :: Text -> Doc+ttext = text . Text.unpack
+ src/Data/Conf/Types.hs view
@@ -0,0 +1,23 @@+module Data.Conf.Types+ where++import Data.Text (Text)++type Conf = [ConfStatement]++data ConfStatement = ConfStatementComment Comment+ | ConfStatementBlock Block+ | ConfStatementEmptyLine+ -- ^ We store empty lines while parsing so we can+ -- reconstruct the document when pretty-printing+ | ConfStatementExpression Expression+ deriving(Eq, Show)++data Block = Block [Text] [ConfStatement]+ deriving(Eq, Show)++data Comment = Comment Text+ deriving(Eq, Show)++data Expression = Expression Text [Text]+ deriving(Eq, Show)
+ test/Data/ConfSpec.hs view
@@ -0,0 +1,201 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.ConfSpec where++import Data.Conf+import Data.Conf.Internal+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import Test.Hspec+import Test.Hspec.Megaparsec+import Text.Megaparsec++spec :: Spec+spec = do+ describe "comment" $+ it "parses a comment" $ do+ let inp = Text.unlines [ "# something here"+ , ""+ , "# there"+ ]+ parse comment "" inp `shouldParse` Comment " something here"++ describe "expression" $ do+ it "parses an expression" $ do+ let inp = Text.unlines [ "worker_processes 1;"]+ parse expression "" inp `shouldParse` Expression "worker_processes" ["1"]++ it "parses a single-line expression" $ do+ let inp = "worker_processes 1;"+ parse expression "" inp `shouldParse` Expression "worker_processes" ["1"]++ describe "block" $ do+ it "parses a block" $ do+ let inp = Text.unlines [ "http {"+ , " listen 8989;"+ , " proxy_pass something;"+ , "}"+ ]+ parse block "" inp `shouldParse` Block ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]++ it "parses a nested block" $ do+ let inp = Text.unlines [ "http {"+ , " location / {"+ , " proxy_pass something;"+ , " }"+ , "}"+ ]+ parse block "" inp `shouldParse` Block ["http"]+ [ ConfStatementBlock (Block+ ["location", "/"]+ [ConfStatementExpression (Expression "proxy_pass" ["something"])])+ ]++ it "parses empty lines" $ do+ let inp = Text.unlines [ "http {"+ , " listen 8989;"+ , ""+ , " proxy_pass something;"+ , "}"+ ]+ parse block "" inp `shouldParse` Block ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]++ it "parses skips more than one empty line" $ do+ let inp = Text.unlines [ "http {"+ , " listen 8989;"+ , ""+ , ""+ , " proxy_pass something;"+ , "}"+ ]+ parse block "" inp `shouldParse` Block ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]++ it "ignores one trailing line" $ do+ let inp = Text.unlines [ "http {"+ , " listen 8989;"+ , ""+ , ""+ , " proxy_pass something;"+ , "}"+ , ""+ ]+ parse block "" inp `shouldParse` Block ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]++ it "ignores multiple trailing lines" $ do+ let inp = Text.unlines [ "http {"+ , " listen 8989;"+ , ""+ , ""+ , " proxy_pass something;"+ , "}"+ , ""+ , ""+ , ""+ ]+ parse block "" inp `shouldParse` Block ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]++ describe "conf" $ do+ it "parses nginx configuration successfully" $ do+ inp <- Text.readFile "default.conf"+ parse conf "" `shouldSucceedOn` inp++ it "ignores one leading line" $ do+ let inp = Text.unlines [ ""+ , "http {"+ , " listen 8989;"+ , ""+ , ""+ , " proxy_pass something;"+ , "}"+ , ""+ , ""+ , ""+ ]+ parse conf "" inp `shouldParse` [ ConfStatementBlock $ Block+ ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]+ ]++ it "ignores multiple leading lines" $ do+ let inp = Text.unlines [ ""+ , ""+ , "http {"+ , " listen 8989;"+ , ""+ , ""+ , " proxy_pass something;"+ , "}"+ , ""+ , ""+ , ""+ ]+ parse conf "" inp `shouldParse` [ ConfStatementBlock $ Block+ ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]+ ]++ it "ignores multiple trailing lines" $ do+ let inp = Text.unlines [ "http {"+ , " listen 8989;"+ , ""+ , ""+ , " proxy_pass something;"+ , "}"+ , ""+ , ""+ , ""+ ]+ parse conf "" inp `shouldParse` [ ConfStatementBlock $ Block+ ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]+ ]++ it "parses comments" $ do+ let inp = Text.unlines [ " # something"+ , ""+ , "http {"+ , " listen 8989;"+ , ""+ , ""+ , " proxy_pass something;"+ , "}"+ , ""+ , ""+ , ""+ ]+ parse conf "" inp `shouldParse` [ ConfStatementComment $ Comment " something"+ , ConfStatementEmptyLine+ , ConfStatementBlock $ Block+ ["http"]+ [ ConfStatementExpression (Expression "listen" ["8989"])+ , ConfStatementEmptyLine+ , ConfStatementExpression (Expression "proxy_pass" ["something"])+ ]+ ]
+ test/SanitySpec.hs view
@@ -0,0 +1,6 @@+module SanitySpec where++import Test.Hspec++spec = describe "when I have tests" $+ it "I have sanity" $ True `shouldBe` True
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}