-- -*- coding: utf-8; mode: haskell; -*- -- File: library/Language/Ninja/Pretty.hs -- -- License: -- Copyright 2017 Awake Security -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. {-# LANGUAGE OverloadedStrings #-} -- | -- Module : Language.Ninja.Pretty -- Copyright : Copyright 2017 Awake Security -- License : Apache-2.0 -- Maintainer : opensource@awakesecurity.com -- Stability : experimental -- -- Rudimentary pretty-printers for various types in @language-ninja@. -- -- @since 0.1.0 module Language.Ninja.Pretty ( -- * Pretty-printer for 'Lexer.Lexeme' prettyLexemes , prettyLexeme -- * Pretty-printer for @AST.'AST.Ninja'@ , prettyNinja , prettyExpr , prettyRule , prettySingle , prettyMultiple , prettyPhony , prettyDefault , prettyPool , prettyBind ) where import Control.Arrow (second) import qualified Control.Lens as Lens import qualified Language.Ninja.AST as AST import qualified Language.Ninja.Lexer as Lexer import qualified Data.HashMap.Strict as HM import Data.HashSet (HashSet) import qualified Data.HashSet as HS import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Data.Char (isSpace) import Data.Monoid ((<>)) import Flow ((.>), (|>)) -------------------------------------------------------------------------------- -- | Pretty-print a list of Ninja lexemes. -- -- @since 0.1.0 prettyLexemes :: [Lexer.Lexeme ()] -> Text prettyLexemes = map prettyLexeme .> mconcat -- | Pretty-print a Ninja lexeme. -- -- @since 0.1.0 prettyLexeme :: Lexer.Lexeme () -> Text prettyLexeme = go where go :: Lexer.Lexeme () -> Text go (Lexer.LexDefine _ bind) = ppBind bind <> "\n" go (Lexer.LexBind _ bind) = " " <> ppBind bind <> "\n" go (Lexer.LexInclude _ file) = "include " <> ppFile file <> "\n" go (Lexer.LexSubninja _ file) = "subninja " <> ppFile file <> "\n" go (Lexer.LexBuild _ build) = "build " <> ppBuild build <> "\n" go (Lexer.LexRule _ name) = "rule " <> ppName name <> "\n" go (Lexer.LexPool _ name) = "pool " <> ppName name <> "\n" go (Lexer.LexDefault _ exprs) = mconcat (map ppDefault exprs) ppBind :: Lexer.LBind () -> Text ppBind (Lexer.MkLBind _ name value) = ppName name <> " = " <> prettyExpr value ppFile :: Lexer.LFile () -> Text ppFile (Lexer.MkLFile expr) = prettyExpr expr ppName :: Lexer.LName () -> Text ppName (Lexer.MkLName _ bs) = Text.decodeUtf8 bs ppBuild :: Lexer.LBuild () -> Text ppBuild (Lexer.MkLBuild _ outs rule deps) = [ Text.intercalate " " (map prettyExpr outs) , " : ", ppName rule, " " , Text.intercalate " " (map prettyExpr deps) ] |> mconcat ppDefault :: AST.Expr () -> Text ppDefault expr = "default " <> prettyExpr expr <> "\n" -------------------------------------------------------------------------------- -- | Pretty-print a 'AST.Ninja'. -- -- @since 0.1.0 prettyNinja :: AST.Ninja () -> Text prettyNinja ninja = [ map prettyRule (HM.toList (Lens.view AST.ninjaRules ninja)) , map prettySingle (HM.toList (Lens.view AST.ninjaSingles ninja)) , map prettyMultiple (HM.toList (Lens.view AST.ninjaMultiples ninja)) , map prettyPhony (HM.toList (Lens.view AST.ninjaPhonys ninja)) , map prettyDefault (HS.toList (Lens.view AST.ninjaDefaults ninja)) , map prettyPool (HM.toList (Lens.view AST.ninjaPools ninja)) ] |> mconcat |> mconcat -- | Pretty-print an 'AST.Expr' -- -- @since 0.1.0 prettyExpr :: AST.Expr () -> Text prettyExpr = go .> mconcat where go (AST.Exprs _ es) = map prettyExpr es go (AST.Lit _ text) = [text] go (AST.Var _ name) = ["${", name, "}"] -- | Pretty-print a Ninja @rule@ declaration. -- -- @since 0.1.0 prettyRule :: (Text, AST.Rule ()) -> Text prettyRule (name, rule) = do let binds = Lens.view AST.ruleBind rule |> HM.toList |> map (second prettyExpr .> prettyBind) |> mconcat mconcat ["rule ", name, "\n", binds] -- | Pretty-print a Ninja @build@ declaration with one output. -- -- @since 0.1.0 prettySingle :: (Text, AST.Build ()) -> Text prettySingle (output, build) = prettyMultiple (HS.singleton output, build) -- | Pretty-print a Ninja @build@ declaration with multiple outputs. -- -- @since 0.1.0 prettyMultiple :: (HashSet Text, AST.Build ()) -> Text prettyMultiple (outputs, build) = do let prefixIfThere :: Text -> Text -> Text prefixIfThere pfx rest = if Text.all isSpace rest then "" else pfx <> rest let unwordsSet :: HashSet Text -> Text unwordsSet = HS.toList .> Text.unwords let ruleName = Lens.view AST.buildRule build let deps = Lens.view AST.buildDeps build let binds = Lens.view AST.buildBind build let normal = Lens.view AST.depsNormal deps let implicit = Lens.view AST.depsImplicit deps let orderOnly = Lens.view AST.depsOrderOnly deps mconcat [ "build ", Text.unwords (HS.toList outputs), ": " , ruleName, " ", unwordsSet normal , prefixIfThere " | " (unwordsSet implicit) , prefixIfThere " || " (unwordsSet orderOnly), "\n" , HM.toList binds |> map prettyBind |> mconcat ] -- | Pretty-print a Ninja phony @build@ declaration. -- -- @since 0.1.0 prettyPhony :: (Text, HashSet Text) -> Text prettyPhony (name, inputs) = [ ["build ", name, ": phony ", Text.unwords (HS.toList inputs)] ] |> map mconcat |> Text.unlines -- | Pretty-print a Ninja @default@ declaration. -- -- @since 0.1.0 prettyDefault :: Text -> Text prettyDefault target = [ ["default ", target] ] |> map mconcat |> Text.unlines -- | Pretty-print a Ninja @pool@ declaration. -- -- @since 0.1.0 prettyPool :: (Text, Int) -> Text prettyPool (name, depth) = [ ["pool ", name] , [" depth = ", tshow depth] ] |> map mconcat |> Text.unlines -- | Pretty-print a Ninja indented binding. -- -- @since 0.1.0 prettyBind :: (Text, Text) -> Text prettyBind (name, value) = mconcat [" ", name, " = ", value, "\n"] -------------------------------------------------------------------------------- tshow :: (Show s) => s -> Text tshow = show .> Text.pack --------------------------------------------------------------------------------