packages feed

ten-0.1.0.0: src/Data/Ten/Internal.hs

-- Copyright 2018-2021 Google LLC
--
-- 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.

-- | Internal utilities used by multiple modules.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeOperators #-}

module Data.Ten.Internal where

import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics ((:*:)(..))

import Data.Portray (Portrayal(..), infixr_)

-- | The names of a lens and field selector, or @coerce@/@_Wrapped@.
--
-- Used in deriving 'Show'/'Text.PrettyPrint.HughesPJClass.Pretty' instances
-- for field selector newtypes.
data PathComponent
  = NewtypeIso
    -- ^ Zooming in on the contents of a newtype with @coerce@ or @_Wrapped@.
  | NamedField !Text !Text
    -- ^ Zooming in on a record field with the given named selector/lens.

-- | Convert a 'PathComponent' to a 'String', with a suffix.
showPathComponent :: PathComponent -> ShowS
showPathComponent NewtypeIso = showString "coerce"
showPathComponent (NamedField selectorName _lensName) =
  showString (T.unpack selectorName)

-- | Convert a list of 'PathComponent's to a 'String', a la 'showsPrec'.
showsPath :: Int -> [PathComponent] -> ShowS
showsPath p path = case reverse path of
  -- If the path ends up empty, that means either there's a bug, or we've added
  -- support to GHC for a new Generics representation type equivalent to Ap10,
  -- and we're looking at it as a standalone GFieldPaths00 instance.  Since
  -- that'll be a newtype, we'll represent it as "coerce", since that should
  -- work regardless of what it ends up being called.
  []     -> showString "coerce"
  [x]    -> showPathComponent x
  (x:xs) -> showParen (p > 9) $
    showPathComponent x .
    flip (foldr (\y -> showString " . " . showPathComponent y)) xs

-- | Pretty-print a 'PathComponent'.
portrayPathComponent :: PathComponent -> Portrayal
portrayPathComponent NewtypeIso = "coerce"
portrayPathComponent (NamedField selectorName _) = Atom selectorName

-- | Pretty-print a field path.
portrayPath :: [PathComponent] -> Portrayal
portrayPath path = go $ reverse path
 where
  go [] = "coerce"
  go [x] = portrayPathComponent x
  go (x:xs) =
    Binop "." (infixr_ 9) (portrayPathComponent x) $
    go xs

-- | Guess the name of the lens corresponding to a field.
dropUnderscore :: String -> String
dropUnderscore ('_':x) = x
dropUnderscore x = x

-- | Access the left side of a (':*:').
starFst :: (f :*: g) m -> f m
starFst (f :*: _) = f

-- | Access the right side of a (':*:').
starSnd :: (f :*: g) m -> g m
starSnd (_ :*: g) = g

-- | Modify the left side of a (':*:').
mapStarFst :: (f m -> f m) -> (f :*: g) m -> (f :*: g) m
mapStarFst h (f :*: g) = h f :*: g

-- | Modify the right side of a (':*:').
mapStarSnd :: (g m -> g m) -> (f :*: g) m -> (f :*: g) m
mapStarSnd h (f :*: g) = f :*: h g