{-# LANGUAGE OverloadedStrings, NamedFieldPuns #-} -------------------------------------------------------------------------------- -- | -- Module : Alfred -- Copyright : (c) 2014 Patrick Bahr -- License : BSD3 -- Maintainer : Patrick Bahr -- Stability : experimental -- Portability : non-portable (GHC Extensions) -- -- This module provides utility functions to interact with Alfred -- version 2. It is intended to be used for writing "script filters" -- used in Alfred workflows. -- -- For example the following excerpt defines a script for Google -- search with auto completion: -- -- @ -- import Alfred -- import Alfred.Query -- import qualified Data.Text as T -- import Data.Text (Text) -- -- runQuery :: String -> IO (Text,[Text]) -- runQuery query = jsonQuery suggestURL query -- -- suggestURL = "http://suggestqueries.google.com/complete/search?output=toolbar&client=firefox&hl=en&q=" -- -- mkItems :: (Text, [Text]) -> [Item] -- mkItems = mkSearchItems MkSearch { -- searchURL = \s -> T.concat ["https://www.google.com/search?q=", s], -- notFound = \s -> T.concat ["No suggestion. Google for ", s, "."], -- found = \s -> T.concat ["Search results for ", s]} -- -- main = runScript runQuery mkItems -- @ -- -- -------------------------------------------------------------------------------- module Alfred ( Item (..) , Icon (..) , runScript , runScript' , mkSearchItems , MkSearch (..)) where import Text.XML.Generator import qualified Data.ByteString as B import Data.Text (Text) import Data.ByteString (ByteString) import Data.Monoid import System.Environment import Data.List import Alfred.Query -- | This type represents items that should be rendered by Alfred as -- the result of a script filter. data Item = Item { uid :: Maybe Text , arg :: Text , isFile :: Bool , valid :: Maybe Bool , autocomplete :: Maybe Text , title :: Text , subtitle :: Text , icon :: Maybe Icon } -- | A list of items. type Items = [Item] -- | Represents icons of an item. data Icon = FileIcon Text | FileType Text | IconFile Text -- | Render an icon as XML element. xmlIcon :: Icon -> Xml Elem xmlIcon icon = case icon of FileIcon str -> mk str (xattr "type" "fileicon") FileType str -> mk str (xattr "type" "filetype") IconFile str -> mk str mempty where mk :: Text -> Xml Attr -> Xml Elem mk str f = xelem "icon" (f <#> xtext str) -- | Render an item as XML element. xmlItem :: Item -> Xml Elem xmlItem (Item uid arg file val auto title sub icon) = xelem "item" $ (uid' <> xattr "arg" arg <> val' <> auto' <> file') <#> (xelemWithText "title" title <> xelemWithText "subtitle" sub <> icon') where uid' = case uid of Nothing -> mempty; Just uid -> xattr "uid" uid val' = case val of Nothing -> mempty Just val -> xattr "valid" (if val then "yes" else "no") file' = if file then xattr "type" "file" else mempty auto' = case auto of Nothing -> mempty; Just auto -> xattr "autocomplete" auto icon' = case icon of Nothing -> mempty; Just icon -> xmlIcon icon -- | Render items as an XML element. xmlItems :: Items -> Xml Elem xmlItems = xelem "items" . mconcat . map xmlItem -- | Render items as an XML 'ByteString'. renderItems :: Items -> ByteString renderItems = xrender . xmlItems -- | Print items as XML to stdout. printItems :: Items -> IO () printItems = B.putStr . renderItems -- | This function runs a script consisting of a query function and a -- rendering function. The query function takes string parameters and -- produces an output that is then passed to the rendering function to -- produce items that are then passed to Alfred. runScript' :: ([String] -> IO a) -- ^ query function -> (a -> Items) -- ^ rendering function -> IO () runScript' runQuery mkItems = do args <- getArgs res <- runQuery args printItems $ mkItems res -- | This function runs a script consisting of a query function and a -- rendering function. The query function takes string parameters and -- produces an output that is then passed to the rendering function to -- produce items that are then passed to Alfred. runScript :: (String -> IO a) -- ^ query function -> (a -> Items) -- ^ rendering function -> IO () runScript runQuery = runScript' (runQuery . concat . intersperse " ") -- | This data type represents standard search scripts used by -- 'mkSearchItems'. data MkSearch = MkSearch {searchURL, found, notFound :: Text -> Text} -- | This function produces a rendering function for standard search -- scripts. For example a Google search rendering function is defined -- as follows: -- -- @ -- mkItems :: (Text, [Text]) -> Items -- mkItems = mkSearchItems MkSearch { -- searchURL = \s -> T.concat ["https://www.google.com/search?q=", s], -- notFound = \s -> T.concat ["No suggestion. Google for ", s, "."], -- found = \s -> T.concat ["Search results for ", s]} -- @ -- mkSearchItems :: MkSearch -> (Text, [Text]) -> Items mkSearchItems MkSearch {searchURL, found, notFound} suggs = case suggs of (s,[]) -> [Item {uid=Nothing,arg=searchURL (escapeText s),isFile=False, valid=Nothing,autocomplete=Nothing,title=s, subtitle=notFound s,icon=Just (IconFile "icon.png")}] (s,res@(r:_)) -> first ++ map mkItem res where first = if s == r then [] else [Item {uid=Nothing,arg=searchURL (escapeText s),isFile=False, valid=Nothing,autocomplete=Just r, title=s, subtitle=found s, icon=Just (IconFile "icon.png")}] where mkItem :: Text -> Item mkItem s = Item {uid=Nothing,arg=searchURL (escapeText s),isFile=False,valid=Nothing, autocomplete=Just s, title=s, subtitle=found s,icon=Just (IconFile "icon.png")}