language-ninja-0.1.0: library/Language/Ninja/IR/Meta.hs
-- -*- coding: utf-8; mode: haskell; -*-
-- File: library/Language/Ninja/IR/Meta.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 DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
-- |
-- Module : Language.Ninja.IR.Meta
-- Copyright : Copyright 2017 Awake Security
-- License : Apache-2.0
-- Maintainer : opensource@awakesecurity.com
-- Stability : experimental
--
-- A datatype for Ninja top-level variables and other metadata.
--
-- @since 0.1.0
module Language.Ninja.IR.Meta
( -- * @Meta@
Meta, makeMeta, metaReqVersion, metaBuildDir
) where
import Data.Aeson ((.:), (.=))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import Data.Text (Text)
import Control.DeepSeq (NFData)
import Data.Hashable (Hashable)
import GHC.Generics (Generic)
import qualified Test.SmallCheck.Series as SC
import qualified Data.Versions as Ver
import qualified Text.Megaparsec as Mega
import Language.Ninja.Misc.Path (Path)
import qualified Control.Lens as Lens
import Flow ((.>), (|>))
--------------------------------------------------------------------------------
-- | Ninja top-level metadata, as documented
-- <https://ninja-build.org/manual.html#ref_toplevel here>.
--
-- @since 0.1.0
data Meta
= MkMeta
{ _metaReqVersion :: !(Maybe Ver.Version)
, _metaBuildDir :: !(Maybe Path)
}
deriving (Eq, Ord, Show, Generic)
-- | Construct a default 'Meta' value.
--
-- @since 0.1.0
{-# INLINE makeMeta #-}
makeMeta :: Meta
makeMeta = MkMeta
{ _metaReqVersion = Nothing
, _metaBuildDir = Nothing
}
-- | Corresponds to the @ninja_required_version@ top-level variable.
--
-- @since 0.1.0
{-# INLINE metaReqVersion #-}
metaReqVersion :: Lens.Lens' Meta (Maybe Ver.Version)
metaReqVersion = Lens.lens _metaReqVersion
$ \(MkMeta {..}) x -> MkMeta { _metaReqVersion = x, .. }
-- | Corresponds to the @builddir@ top-level variable.
--
-- @since 0.1.0
{-# INLINE metaBuildDir #-}
metaBuildDir :: Lens.Lens' Meta (Maybe Path)
metaBuildDir = Lens.lens _metaBuildDir
$ \(MkMeta {..}) x -> MkMeta { _metaBuildDir = x, .. }
-- | Converts to @{req-version: …, build-dir: …}@.
--
-- @since 0.1.0
instance Aeson.ToJSON Meta where
toJSON (MkMeta {..})
= [ "req-version" .= fmap versionJ _metaReqVersion
, "build-dir" .= _metaBuildDir
] |> Aeson.object
where
versionJ :: Ver.Version -> Aeson.Value
versionJ = Ver.prettyVer .> Aeson.toJSON
-- | Inverse of the 'Aeson.ToJSON' instance.
--
-- @since 0.1.0
instance Aeson.FromJSON Meta where
parseJSON = (Aeson.withObject "Meta" $ \o -> do
_metaReqVersion <- (o .: "req-version") >>= maybeVersionP
_metaBuildDir <- (o .: "build-dir") >>= pure
pure (MkMeta {..}))
where
maybeVersionP :: Maybe Aeson.Value -> Aeson.Parser (Maybe Ver.Version)
maybeVersionP = fmap versionP .> sequenceA
versionP :: Aeson.Value -> Aeson.Parser Ver.Version
versionP = Aeson.withText "Version" (megaparsecToAeson Ver.version')
-- | Default 'Hashable' instance via 'Generic'.
--
-- @since 0.1.0
instance Hashable Meta
-- | Default 'NFData' instance via 'Generic'.
--
-- @since 0.1.0
instance NFData Meta
-- | Default 'SC.Serial' instance via 'Generic'.
--
-- @since 0.1.0
instance ( Monad m
, SC.Serial m Ver.Version
, SC.Serial m Text
) => SC.Serial m Meta
-- | Default 'SC.CoSerial' instance via 'Generic'.
--
-- @since 0.1.0
instance ( Monad m
, SC.CoSerial m Ver.Version
, SC.CoSerial m Text
) => SC.CoSerial m Meta
--------------------------------------------------------------------------------
-- This function converts a @megaparsec@ parser to an @aeson@ parser.
-- Mainly, it handles converting the error output from @megaparsec@ to a
-- string that is appropriate for 'fail'.
megaparsecToAeson :: Mega.Parsec Mega.Dec Text t
-> (Text -> Aeson.Parser t)
megaparsecToAeson parser text = case Mega.runParser parser "" text of
Left e -> fail (Mega.parseErrorPretty e)
Right x -> pure x
--------------------------------------------------------------------------------