packages feed

dbus-menu-0.1.0.0: src/DBusMenu/Client/Util.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
module DBusMenu.Client.Util
  ( RecordGenerationParams(..)
  , GetTypeForName
  , defaultRecordGenerationParams
  , generateClientFromFile
  ) where

import Control.Monad (forM)
import DBus (ObjectPath)
import DBus.Generation
import qualified DBus.Internal.Types as DBusTypes
import qualified DBus.Introspection as I
import qualified Data.Char as Char
import qualified Data.Coerce as Coerce
import qualified Data.Maybe as Maybe
import qualified Data.Text.IO as TIO
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (addDependentFile, makeRelativeToProject)

type GetTypeForName = String -> DBusTypes.Type -> Maybe Type

data RecordGenerationParams = RecordGenerationParams
  { recordName :: Maybe String
  , recordPrefix :: String
  , recordTypeForName :: GetTypeForName
  }

defaultRecordGenerationParams :: RecordGenerationParams
defaultRecordGenerationParams = RecordGenerationParams
  { recordName = Nothing
  , recordPrefix = "_"
  , recordTypeForName = const $ const Nothing
  }

deriveShowAndEQ :: [DerivClause]
deriveShowAndEQ =
  [DerivClause Nothing [ConT ''Eq, ConT ''Show]]

buildDataFromNameTypePairs :: Name -> [(Name, Type)] -> Dec
buildDataFromNameTypePairs name pairs =
  DataD [] name [] Nothing [RecC name (map mkVarBangType pairs)] deriveShowAndEQ
  where
    mkVarBangType (fieldName, fieldType) =
      ( fieldName
      , Bang NoSourceUnpackedness NoSourceStrictness
      , fieldType
      )

generateGetAllRecord
  :: RecordGenerationParams
  -> GenerationParams
  -> I.Interface
  -> Q [Dec]
generateGetAllRecord
  RecordGenerationParams
    { recordName = recordNameString
    , recordPrefix = prefix
    , recordTypeForName = getTypeForName
    }
  GenerationParams { getTHType = getArgType }
  I.Interface
    { I.interfaceName = interfaceName
    , I.interfaceProperties = properties
    } = do
  let theRecordName =
        mkName $
          maybe
            (map Char.toUpper $ filter Char.isLetter $ Coerce.coerce interfaceName)
            id
            recordNameString
      getPairFromProperty
        I.Property { I.propertyName = propName, I.propertyType = propType } =
          ( mkName $ prefix ++ propName
          , Maybe.fromMaybe (getArgType propType) $
              getTypeForName propName propType
          )
      getAllRecord =
        buildDataFromNameTypePairs theRecordName $
          map getPairFromProperty properties
  pure [getAllRecord]

getIntrospectionObjectFromFile :: FilePath -> ObjectPath -> Q I.Object
getIntrospectionObjectFromFile filepath path = do
  realPath <- makeRelativeToProject filepath
  addDependentFile realPath
  xml <- runIO (TIO.readFile realPath)
  case I.parseXML path xml of
    Nothing -> fail $ "Failed to parse DBus introspection XML: " <> filepath
    Just obj -> pure obj

generateClientFromFile
  :: RecordGenerationParams
  -> GenerationParams
  -> Bool
  -> FilePath
  -> Q [Dec]
generateClientFromFile recordGenerationParams params useObjectPath filepath = do
  obj <- getIntrospectionObjectFromFile filepath "/"
  let actualObjectPath = I.objectPath obj
      realParams =
        if useObjectPath
          then params { genObjectPath = Just actualObjectPath }
          else params
      (<++>) = liftA2 (++)
  fmap concat $ forM (I.objectInterfaces obj) $ \interface -> do
    generateGetAllRecord recordGenerationParams params interface <++>
      generateClient realParams interface <++>
      generateSignalsFromInterface realParams interface