mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 11:30:22 -07:00
Merge pull request #659 from slotThe/x.u.parser
New Module: XMonad.Util.Parser
This commit is contained in:
commit
0010735aca
11
CHANGES.md
11
CHANGES.md
@ -4,6 +4,12 @@
|
|||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
|
* `XMonad.Util.EZConfig`
|
||||||
|
|
||||||
|
- The functions `parseKey`, `parseKeyCombo`, and `parseKeySequence`
|
||||||
|
now return a `Parser` (from `XMonad.Util.Parser`) instead of a
|
||||||
|
`ReadP`.
|
||||||
|
|
||||||
### New Modules
|
### New Modules
|
||||||
|
|
||||||
* `XMonad.Layout.CenteredIfSingle`
|
* `XMonad.Layout.CenteredIfSingle`
|
||||||
@ -21,6 +27,11 @@
|
|||||||
While XMonad provides config to set all window borders at the same
|
While XMonad provides config to set all window borders at the same
|
||||||
width, this extension defines and sets border width for each window.
|
width, this extension defines and sets border width for each window.
|
||||||
|
|
||||||
|
* `XMonad.Util.Parser`
|
||||||
|
|
||||||
|
A wrapper around the 'ReadP' parser combinator, providing behaviour
|
||||||
|
that's closer to the more popular parser combinator libraries.
|
||||||
|
|
||||||
### Bug Fixes and Minor Changes
|
### Bug Fixes and Minor Changes
|
||||||
|
|
||||||
* `XMonad.Prompt`
|
* `XMonad.Prompt`
|
||||||
|
@ -53,12 +53,12 @@ import XMonad.Prelude
|
|||||||
|
|
||||||
import XMonad (X, io)
|
import XMonad (X, io)
|
||||||
import XMonad.Prompt (XPConfig, XPrompt (showXPrompt), mkXPrompt)
|
import XMonad.Prompt (XPConfig, XPrompt (showXPrompt), mkXPrompt)
|
||||||
|
import XMonad.Util.Parser
|
||||||
import XMonad.Util.XSelection (getSelection)
|
import XMonad.Util.XSelection (getSelection)
|
||||||
|
|
||||||
import Data.Time (Day (ModifiedJulianDay), NominalDiffTime, UTCTime (utctDay), addUTCTime, defaultTimeLocale, formatTime, fromGregorian, getCurrentTime, iso8601DateFormat, nominalDay, toGregorian)
|
import Data.Time (Day (ModifiedJulianDay), NominalDiffTime, UTCTime (utctDay), addUTCTime, defaultTimeLocale, formatTime, fromGregorian, getCurrentTime, iso8601DateFormat, nominalDay, toGregorian)
|
||||||
import System.Directory (getHomeDirectory)
|
import System.Directory (getHomeDirectory)
|
||||||
import System.IO (IOMode (AppendMode), hPutStrLn, withFile)
|
import System.IO (IOMode (AppendMode), hPutStrLn, withFile)
|
||||||
import Text.ParserCombinators.ReadP (ReadP, munch, munch1, readP_to_S, skipSpaces, string, (<++))
|
|
||||||
|
|
||||||
{- $usage
|
{- $usage
|
||||||
|
|
||||||
@ -357,13 +357,13 @@ ppNote clp todo = \case
|
|||||||
|
|
||||||
-- | Parse the given string into a 'Note'.
|
-- | Parse the given string into a 'Note'.
|
||||||
pInput :: String -> Maybe Note
|
pInput :: String -> Maybe Note
|
||||||
pInput inp = fmap fst . listToMaybe . (`readP_to_S` inp) . lchoice $
|
pInput inp = (`runParser` inp) . choice $
|
||||||
[ Scheduled <$> getLast "+s" <*> (Time <$> pDate <*> pTimeOfDay)
|
[ Scheduled <$> getLast "+s" <*> (Time <$> pDate <*> pTimeOfDay)
|
||||||
, Deadline <$> getLast "+d" <*> (Time <$> pDate <*> pTimeOfDay)
|
, Deadline <$> getLast "+d" <*> (Time <$> pDate <*> pTimeOfDay)
|
||||||
, NormalMsg <$> munch1 (const True)
|
, NormalMsg <$> munch1 (const True)
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
getLast :: String -> ReadP String
|
getLast :: String -> Parser String
|
||||||
getLast ptn = reverse
|
getLast ptn = reverse
|
||||||
. dropWhile (== ' ') -- trim whitespace at the end
|
. dropWhile (== ' ') -- trim whitespace at the end
|
||||||
. drop (length ptn) -- drop only the last pattern
|
. drop (length ptn) -- drop only the last pattern
|
||||||
@ -371,82 +371,54 @@ pInput inp = fmap fst . listToMaybe . (`readP_to_S` inp) . lchoice $
|
|||||||
. concat
|
. concat
|
||||||
<$> endBy1 (go "") (pure ptn)
|
<$> endBy1 (go "") (pure ptn)
|
||||||
where
|
where
|
||||||
go :: String -> ReadP String
|
go :: String -> Parser String
|
||||||
go consumed = do
|
go consumed = do
|
||||||
str <- munch (/= head ptn)
|
str <- munch (/= head ptn)
|
||||||
word <- munch1 (/= ' ')
|
word <- munch1 (/= ' ')
|
||||||
bool go pure (word == ptn) $ consumed <> str <> word
|
bool go pure (word == ptn) $ consumed <> str <> word
|
||||||
|
|
||||||
-- | Try to parse a 'Time'.
|
-- | Try to parse a 'Time'.
|
||||||
pTimeOfDay :: ReadP (Maybe TimeOfDay)
|
pTimeOfDay :: Parser (Maybe TimeOfDay)
|
||||||
pTimeOfDay = lchoice
|
pTimeOfDay = choice
|
||||||
[ Just <$> (TimeOfDay <$> pInt <* string ":" <*> pInt ) -- HH:MM
|
[ Just <$> (TimeOfDay <$> num <* string ":" <*> num ) -- HH:MM
|
||||||
, Just <$> (TimeOfDay <$> pInt <*> pure 0) -- HH
|
, Just <$> (TimeOfDay <$> num <*> pure 0) -- HH
|
||||||
, pure Nothing
|
, pure Nothing
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | Parse a 'Date'.
|
-- | Parse a 'Date'.
|
||||||
pDate :: ReadP Date
|
pDate :: Parser Date
|
||||||
pDate = skipSpaces *> lchoice
|
pDate = skipSpaces *> choice
|
||||||
[ pString "tod" "ay" Today
|
[ pPrefix "tod" "ay" Today
|
||||||
, pString "tom" "orrow" Tomorrow
|
, pPrefix "tom" "orrow" Tomorrow
|
||||||
, Next <$> pNext
|
, Next <$> pNext
|
||||||
, Date <$> pDate1 <++ pDate2 <++ pDate3
|
, Date <$> pDate'
|
||||||
] <* skipSpaces -- cleanup
|
] <* skipSpaces -- cleanup
|
||||||
where
|
where
|
||||||
pNext :: ReadP DayOfWeek = lchoice
|
pNext :: Parser DayOfWeek = choice
|
||||||
[ pString "m" "onday" Monday , pString "tu" "esday" Tuesday
|
[ pPrefix "m" "onday" Monday , pPrefix "tu" "esday" Tuesday
|
||||||
, pString "w" "ednesday" Wednesday, pString "th" "ursday" Thursday
|
, pPrefix "w" "ednesday" Wednesday, pPrefix "th" "ursday" Thursday
|
||||||
, pString "f" "riday" Friday , pString "sa" "turday" Saturday
|
, pPrefix "f" "riday" Friday , pPrefix "sa" "turday" Saturday
|
||||||
, pString "su" "nday" Sunday
|
, pPrefix "su" "nday" Sunday
|
||||||
]
|
]
|
||||||
|
|
||||||
-- XXX: This is really horrible, but I can't see a way to not have
|
pDate' :: Parser (Int, Maybe Int, Maybe Integer)
|
||||||
-- exponential blowup with ReadP otherwise.
|
pDate' =
|
||||||
pDate1, pDate2, pDate3 :: ReadP (Int, Maybe Int, Maybe Integer)
|
(,,) <$> num
|
||||||
pDate1 = pDate' (fmap Just) (fmap Just)
|
<*> optional (skipSpaces *> choice
|
||||||
pDate2 = pDate' (fmap Just) (const (pure Nothing))
|
[ pPrefix "ja" "nuary" 1 , pPrefix "f" "ebruary" 2
|
||||||
pDate3 = pDate' (const (pure Nothing)) (const (pure Nothing))
|
, pPrefix "mar" "ch" 3 , pPrefix "ap" "ril" 4
|
||||||
pDate'
|
, pPrefix "may" "" 5 , pPrefix "jun" "e" 6
|
||||||
:: (ReadP Int -> ReadP (f Int ))
|
, pPrefix "jul" "y" 7 , pPrefix "au" "gust" 8
|
||||||
-> (ReadP Integer -> ReadP (f Integer))
|
, pPrefix "s" "eptember" 9 , pPrefix "o" "ctober" 10
|
||||||
-> ReadP (Int, f Int, f Integer)
|
, pPrefix "n" "ovember" 11, pPrefix "d" "ecember" 12
|
||||||
pDate' p p' =
|
|
||||||
(,,) <$> pInt
|
|
||||||
<*> p (skipSpaces *> lchoice
|
|
||||||
[ pString "ja" "nuary" 1 , pString "f" "ebruary" 2
|
|
||||||
, pString "mar" "ch" 3 , pString "ap" "ril" 4
|
|
||||||
, pString "may" "" 5 , pString "jun" "e" 6
|
|
||||||
, pString "jul" "y" 7 , pString "au" "gust" 8
|
|
||||||
, pString "s" "eptember" 9 , pString "o" "ctober" 10
|
|
||||||
, pString "n" "ovember" 11, pString "d" "ecember" 12
|
|
||||||
])
|
])
|
||||||
<*> p' (skipSpaces *> pInt >>= \i -> guard (i >= 25) $> i)
|
<*> optional (skipSpaces *> num >>= \i -> guard (i >= 25) $> i)
|
||||||
|
|
||||||
-- | Parse a @start@ and see whether the rest of the word (separated by
|
-- | Parse a prefix and drop a potential suffix up to the next (space
|
||||||
-- spaces) fits the @leftover@.
|
-- separated) word. If successful, return @ret@.
|
||||||
pString :: String -> String -> a -> ReadP a
|
pPrefix :: String -> String -> a -> Parser a
|
||||||
pString start leftover ret = do
|
pPrefix start leftover ret = do
|
||||||
void $ string start
|
void $ string start
|
||||||
l <- munch (/= ' ')
|
l <- munch (/= ' ')
|
||||||
guard (l `isPrefixOf` leftover)
|
guard (l `isPrefixOf` leftover)
|
||||||
pure ret
|
pure ret
|
||||||
|
|
||||||
-- | Parse a number.
|
|
||||||
pInt :: (Read a, Integral a) => ReadP a
|
|
||||||
pInt = read <$> munch1 isDigit
|
|
||||||
|
|
||||||
-- | Like 'choice', but with '(<++)' instead of '(+++)', stopping
|
|
||||||
-- parsing when the left-most parser succeeds.
|
|
||||||
lchoice :: [ReadP a] -> ReadP a
|
|
||||||
lchoice = foldl' (<++) empty
|
|
||||||
|
|
||||||
-- | Like 'Text.ParserCombinators.ReadP.endBy1', but only return the
|
|
||||||
-- parse where @parser@ had the highest number of applications.
|
|
||||||
endBy1 :: ReadP a -> ReadP sep -> ReadP [a]
|
|
||||||
endBy1 parser sep = many1 (parser <* sep)
|
|
||||||
where
|
|
||||||
-- | Like 'Text.ParserCombinators.ReadP.many1', but use '(<++)'
|
|
||||||
-- instead of '(+++)'.
|
|
||||||
many1 :: ReadP a -> ReadP [a]
|
|
||||||
many1 p = (:) <$> p <*> (many1 p <++ pure [])
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{-# LANGUAGE CPP #-}
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
-- |
|
-- |
|
||||||
-- Module : XMonad.Util.EZConfig
|
-- Module : XMonad.Util.EZConfig
|
||||||
@ -34,21 +35,24 @@ module XMonad.Util.EZConfig (
|
|||||||
|
|
||||||
parseKey, -- used by XMonad.Util.Paste
|
parseKey, -- used by XMonad.Util.Paste
|
||||||
parseKeyCombo,
|
parseKeyCombo,
|
||||||
parseKeySequence, readKeySequence
|
parseKeySequence, readKeySequence,
|
||||||
|
#ifdef TESTING
|
||||||
|
functionKeys, specialKeys, multimediaKeys,
|
||||||
|
parseModifier,
|
||||||
|
#endif
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import XMonad
|
import XMonad
|
||||||
import XMonad.Actions.Submap
|
import XMonad.Actions.Submap
|
||||||
import XMonad.Prelude hiding (many)
|
import XMonad.Prelude
|
||||||
|
|
||||||
import XMonad.Util.NamedActions
|
import XMonad.Util.NamedActions
|
||||||
|
import XMonad.Util.Parser
|
||||||
|
|
||||||
import Control.Arrow (first, (&&&))
|
import Control.Arrow (first, (&&&))
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.Ord (comparing)
|
import Data.Ord (comparing)
|
||||||
|
|
||||||
import Text.ParserCombinators.ReadP
|
|
||||||
|
|
||||||
-- $usage
|
-- $usage
|
||||||
-- To use this module, first import it into your @~\/.xmonad\/xmonad.hs@:
|
-- To use this module, first import it into your @~\/.xmonad\/xmonad.hs@:
|
||||||
--
|
--
|
||||||
@ -408,16 +412,15 @@ readKeymap c = mapMaybe (maybeKeys . first (readKeySequence c))
|
|||||||
-- | Parse a sequence of keys, returning Nothing if there is
|
-- | Parse a sequence of keys, returning Nothing if there is
|
||||||
-- a parse failure (no parse, or ambiguous parse).
|
-- a parse failure (no parse, or ambiguous parse).
|
||||||
readKeySequence :: XConfig l -> String -> Maybe [(KeyMask, KeySym)]
|
readKeySequence :: XConfig l -> String -> Maybe [(KeyMask, KeySym)]
|
||||||
readKeySequence c = listToMaybe . parses
|
readKeySequence c = runParser (parseKeySequence c)
|
||||||
where parses = map fst . filter (null.snd) . readP_to_S (parseKeySequence c)
|
|
||||||
|
|
||||||
-- | Parse a sequence of key combinations separated by spaces, e.g.
|
-- | Parse a sequence of key combinations separated by spaces, e.g.
|
||||||
-- @\"M-c x C-S-2\"@ (mod+c, x, ctrl+shift+2).
|
-- @\"M-c x C-S-2\"@ (mod+c, x, ctrl+shift+2).
|
||||||
parseKeySequence :: XConfig l -> ReadP [(KeyMask, KeySym)]
|
parseKeySequence :: XConfig l -> Parser [(KeyMask, KeySym)]
|
||||||
parseKeySequence c = sepBy1 (parseKeyCombo c) (many1 $ char ' ')
|
parseKeySequence c = parseKeyCombo c `sepBy1` many1 (char ' ')
|
||||||
|
|
||||||
-- | Parse a modifier-key combination such as "M-C-s" (mod+ctrl+s).
|
-- | Parse a modifier-key combination such as "M-C-s" (mod+ctrl+s).
|
||||||
parseKeyCombo :: XConfig l -> ReadP (KeyMask, KeySym)
|
parseKeyCombo :: XConfig l -> Parser (KeyMask, KeySym)
|
||||||
parseKeyCombo c = do mods <- many (parseModifier c)
|
parseKeyCombo c = do mods <- many (parseModifier c)
|
||||||
k <- parseKey
|
k <- parseKey
|
||||||
return (foldl' (.|.) 0 mods, k)
|
return (foldl' (.|.) 0 mods, k)
|
||||||
@ -425,23 +428,23 @@ parseKeyCombo c = do mods <- many (parseModifier c)
|
|||||||
-- | Parse a modifier: either M- (user-defined mod-key),
|
-- | Parse a modifier: either M- (user-defined mod-key),
|
||||||
-- C- (control), S- (shift), or M#- where # is an integer
|
-- C- (control), S- (shift), or M#- where # is an integer
|
||||||
-- from 1 to 5 (mod1Mask through mod5Mask).
|
-- from 1 to 5 (mod1Mask through mod5Mask).
|
||||||
parseModifier :: XConfig l -> ReadP KeyMask
|
parseModifier :: XConfig l -> Parser KeyMask
|
||||||
parseModifier c = (string "M-" >> return (modMask c))
|
parseModifier c = (string "M-" $> modMask c)
|
||||||
+++ (string "C-" >> return controlMask)
|
<> (string "C-" $> controlMask)
|
||||||
+++ (string "S-" >> return shiftMask)
|
<> (string "S-" $> shiftMask)
|
||||||
+++ do _ <- char 'M'
|
<> do _ <- char 'M'
|
||||||
n <- satisfy (`elem` ['1'..'5'])
|
n <- satisfy (`elem` ['1'..'5'])
|
||||||
_ <- char '-'
|
_ <- char '-'
|
||||||
return $ indexMod (read [n] - 1)
|
return $ indexMod (read [n] - 1)
|
||||||
where indexMod = (!!) [mod1Mask,mod2Mask,mod3Mask,mod4Mask,mod5Mask]
|
where indexMod = (!!) [mod1Mask,mod2Mask,mod3Mask,mod4Mask,mod5Mask]
|
||||||
|
|
||||||
-- | Parse an unmodified basic key, like @\"x\"@, @\"<F1>\"@, etc.
|
-- | Parse an unmodified basic key, like @\"x\"@, @\"<F1>\"@, etc.
|
||||||
parseKey :: ReadP KeySym
|
parseKey :: Parser KeySym
|
||||||
parseKey = parseRegular +++ parseSpecial
|
parseKey = parseSpecial <> parseRegular
|
||||||
|
|
||||||
-- | Parse a regular key name (represented by itself).
|
-- | Parse a regular key name (represented by itself).
|
||||||
parseRegular :: ReadP KeySym
|
parseRegular :: Parser KeySym
|
||||||
parseRegular = choice [ char s >> return k
|
parseRegular = choice [ char s $> k
|
||||||
| (s,k) <- zip ['!' .. '~' ] -- ASCII
|
| (s,k) <- zip ['!' .. '~' ] -- ASCII
|
||||||
[xK_exclam .. xK_asciitilde]
|
[xK_exclam .. xK_asciitilde]
|
||||||
|
|
||||||
@ -450,13 +453,11 @@ parseRegular = choice [ char s >> return k
|
|||||||
]
|
]
|
||||||
|
|
||||||
-- | Parse a special key name (one enclosed in angle brackets).
|
-- | Parse a special key name (one enclosed in angle brackets).
|
||||||
parseSpecial :: ReadP KeySym
|
parseSpecial :: Parser KeySym
|
||||||
parseSpecial = do _ <- char '<'
|
parseSpecial = do _ <- char '<'
|
||||||
key <- choice [ string name >> return k
|
choice [ k <$ string name <* char '>'
|
||||||
| (name,k) <- keyNames
|
| (name, k) <- keyNames
|
||||||
]
|
]
|
||||||
_ <- char '>'
|
|
||||||
return key
|
|
||||||
|
|
||||||
-- | A list of all special key names and their associated KeySyms.
|
-- | A list of all special key names and their associated KeySyms.
|
||||||
keyNames :: [(String, KeySym)]
|
keyNames :: [(String, KeySym)]
|
||||||
|
215
XMonad/Util/Parser.hs
Normal file
215
XMonad/Util/Parser.hs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE DerivingStrategies #-}
|
||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# LANGUAGE InstanceSigs #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : XMonad.Util.Parser
|
||||||
|
-- Description : A parser combinator library for xmonad
|
||||||
|
-- Copyright : (c) 2021 slotThe <soliditsallgood@mailbox.org>
|
||||||
|
-- License : BSD3
|
||||||
|
-- Maintainer : slotThe <soliditsallgood@mailbox.org>
|
||||||
|
-- Stability : experimental
|
||||||
|
-- Portability : non-portable
|
||||||
|
--
|
||||||
|
-- A small wrapper around the 'ReadP' parser combinator in @base@,
|
||||||
|
-- providing a more intuitive behaviour. While it's theoretically nice
|
||||||
|
-- that 'ReadP' is actually commutative, this makes a lot of parsing
|
||||||
|
-- operations rather awkward—more often than not, one only wants the
|
||||||
|
-- argument that's parsed "first".
|
||||||
|
--
|
||||||
|
-- Due to the left-biased nature of the chosen semigroup implementation,
|
||||||
|
-- using functions like 'many' or 'optional' from "Control.Applicative"
|
||||||
|
-- now yields more consistent behaviour with other parser combinator
|
||||||
|
-- libraries.
|
||||||
|
--
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
module XMonad.Util.Parser (
|
||||||
|
-- * Usage
|
||||||
|
-- $usage
|
||||||
|
|
||||||
|
-- * Running
|
||||||
|
Parser,
|
||||||
|
runParser,
|
||||||
|
|
||||||
|
-- * Primitive Parsers
|
||||||
|
eof,
|
||||||
|
num,
|
||||||
|
char,
|
||||||
|
string,
|
||||||
|
skipSpaces,
|
||||||
|
get,
|
||||||
|
look,
|
||||||
|
|
||||||
|
-- * Combining Parsers
|
||||||
|
satisfy,
|
||||||
|
choice,
|
||||||
|
many1,
|
||||||
|
sepBy,
|
||||||
|
sepBy1,
|
||||||
|
endBy,
|
||||||
|
endBy1,
|
||||||
|
munch,
|
||||||
|
munch1,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import XMonad.Prelude
|
||||||
|
|
||||||
|
import qualified Text.ParserCombinators.ReadP as ReadP
|
||||||
|
|
||||||
|
import Data.Coerce (coerce)
|
||||||
|
import Text.ParserCombinators.ReadP (ReadP, (<++))
|
||||||
|
|
||||||
|
{- $usage
|
||||||
|
|
||||||
|
NOTE: This module is mostly intended for developing of other modules.
|
||||||
|
If you are a users, you probably won't find much use here—you have been
|
||||||
|
warned.
|
||||||
|
|
||||||
|
The high-level API tries to stay as close to 'ReadP' as possible. If
|
||||||
|
you are familiar with that then no functions here should surprise you.
|
||||||
|
|
||||||
|
One notable usability difference when forcing left-biasedness is /when/
|
||||||
|
one wants to disambiguate a parse. For normal 'ReadP' usage this
|
||||||
|
happens after the actual parsing stage by going through the list of
|
||||||
|
successful parses. For 'Parser' it does when constructing the relevant
|
||||||
|
combinators, leading to only one successful parse. As an example,
|
||||||
|
consider the 'ReadP'-based parser
|
||||||
|
|
||||||
|
> pLangle = ReadP.string "<"
|
||||||
|
> pLongerSequence = ReadP.char '<' *> ReadP.string "f" <* ReadP.char '>'
|
||||||
|
> pCombination = pLangle ReadP.+++ pLongerSequence
|
||||||
|
|
||||||
|
Parsing the string @"<f>"@ will return
|
||||||
|
|
||||||
|
>>> ReadP.readP_to_S pCombination "<f>"
|
||||||
|
[("<","f>"),("f","")]
|
||||||
|
|
||||||
|
One would now need to, for example, filter for the second (leftover)
|
||||||
|
string being empty and take the head of the resulting list (which may
|
||||||
|
still have more than one element).
|
||||||
|
|
||||||
|
With 'Parser', the same situation would look like the following
|
||||||
|
|
||||||
|
> pLangle' = string "<"
|
||||||
|
> pLongerSequence' = char '<' *> string "f" <* char '>'
|
||||||
|
> pCombination' = pLongerSequence' <> pLangle'
|
||||||
|
|
||||||
|
Notice how @pLangle'@ and @pLongerSequence'@ have traded places—since we
|
||||||
|
are not forcing @pLangle'@ to consume the entire string and @(<>)@ is
|
||||||
|
left-biased, @pLongerSequence'@ parses a superset of @pLangle'@!
|
||||||
|
Running @runParser pCombination'@ now yields the expected result:
|
||||||
|
|
||||||
|
>>> runParser pCombination' "<f>"
|
||||||
|
Just "f"
|
||||||
|
|
||||||
|
One might also define @pLangle'@ as @string "<" <* eof@, which would
|
||||||
|
enable a definition of @pCombination' = pLangle' <> pLongerSequence'@.
|
||||||
|
|
||||||
|
For example uses, see "XMonad.Util.EZConfig" or "XMonad.Prompt.OrgMode".
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- Parser :: Type -> Type
|
||||||
|
newtype Parser a = Parser (ReadP a)
|
||||||
|
deriving newtype (Functor, Applicative, Monad)
|
||||||
|
|
||||||
|
instance Semigroup (Parser a) where
|
||||||
|
-- | Local, exclusive, left-biased choice: If left parser locally
|
||||||
|
-- produces any result at all, then right parser is not used.
|
||||||
|
(<>) :: Parser a -> Parser a -> Parser a
|
||||||
|
(<>) = coerce ((<++) @a)
|
||||||
|
|
||||||
|
instance Monoid (Parser a) where
|
||||||
|
-- | A parser that always fails.
|
||||||
|
mempty :: Parser a
|
||||||
|
mempty = Parser empty
|
||||||
|
|
||||||
|
instance Alternative Parser where
|
||||||
|
empty :: Parser a
|
||||||
|
empty = mempty
|
||||||
|
|
||||||
|
(<|>) :: Parser a -> Parser a -> Parser a
|
||||||
|
(<|>) = (<>)
|
||||||
|
|
||||||
|
-- | Run a parser on a given string.
|
||||||
|
runParser :: Parser a -> String -> Maybe a
|
||||||
|
runParser (Parser p) = fmap fst . listToMaybe . ReadP.readP_to_S p
|
||||||
|
|
||||||
|
-- | Consume and return the next character. Fails if there is no input
|
||||||
|
-- left.
|
||||||
|
get :: Parser Char
|
||||||
|
get = coerce ReadP.get
|
||||||
|
|
||||||
|
-- | Look-ahead: return the part of the input that is left, without
|
||||||
|
-- consuming it.
|
||||||
|
look :: Parser String
|
||||||
|
look = coerce ReadP.look
|
||||||
|
|
||||||
|
-- | Succeeds if and only if we are at the end of input.
|
||||||
|
eof :: Parser ()
|
||||||
|
eof = coerce ReadP.eof
|
||||||
|
|
||||||
|
-- | Parse an integral number number.
|
||||||
|
num :: (Read a, Integral a) => Parser a
|
||||||
|
num = read <$> munch1 isDigit
|
||||||
|
{-# SPECIALISE num :: Parser Word #-}
|
||||||
|
{-# SPECIALISE num :: Parser Int #-}
|
||||||
|
{-# SPECIALISE num :: Parser Integer #-}
|
||||||
|
|
||||||
|
-- | Parse and return the specified character.
|
||||||
|
char :: Char -> Parser Char
|
||||||
|
char = coerce ReadP.char
|
||||||
|
|
||||||
|
-- | Parse and return the specified string.
|
||||||
|
string :: String -> Parser String
|
||||||
|
string = coerce ReadP.string
|
||||||
|
|
||||||
|
-- | Skip all whitespace.
|
||||||
|
skipSpaces :: Parser ()
|
||||||
|
skipSpaces = coerce ReadP.skipSpaces
|
||||||
|
|
||||||
|
-- | Consume and return the next character if it satisfies the specified
|
||||||
|
-- predicate.
|
||||||
|
satisfy :: (Char -> Bool) -> Parser Char
|
||||||
|
satisfy = coerce ReadP.satisfy
|
||||||
|
|
||||||
|
-- | Combine all parsers in the given list in a left-biased way.
|
||||||
|
choice :: [Parser a] -> Parser a
|
||||||
|
choice = foldl' (<>) mempty
|
||||||
|
|
||||||
|
-- | Parse the first zero or more characters satisfying the predicate.
|
||||||
|
-- Always succeeds; returns an empty string if the predicate returns
|
||||||
|
-- @False@ on the first character of input.
|
||||||
|
munch :: (Char -> Bool) -> Parser String
|
||||||
|
munch = coerce ReadP.munch
|
||||||
|
|
||||||
|
-- | Parse the first one or more characters satisfying the predicate.
|
||||||
|
-- Fails if none, else succeeds exactly once having consumed all the
|
||||||
|
-- characters.
|
||||||
|
munch1 :: (Char -> Bool) -> Parser String
|
||||||
|
munch1 = coerce ReadP.munch1
|
||||||
|
|
||||||
|
-- | @endBy p sep@ parses zero or more occurrences of @p@, separated and
|
||||||
|
-- ended by @sep@.
|
||||||
|
endBy :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
endBy p sep = many (p <* sep)
|
||||||
|
|
||||||
|
-- | @endBy p sep@ parses one or more occurrences of @p@, separated and
|
||||||
|
-- ended by @sep@.
|
||||||
|
endBy1 :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
endBy1 p sep = many1 (p <* sep)
|
||||||
|
|
||||||
|
-- | Parse one or more occurrences of the given parser.
|
||||||
|
many1 :: Parser a -> Parser [a]
|
||||||
|
many1 p = liftA2 (:) p (many p)
|
||||||
|
|
||||||
|
-- | @sepBy p sep@ parses zero or more occurrences of @p@, separated by
|
||||||
|
-- @sep@. Returns a list of values returned by @p@.
|
||||||
|
sepBy :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
sepBy p sep = sepBy1 p sep <> pure []
|
||||||
|
|
||||||
|
-- | @sepBy1 p sep@ parses one or more occurrences of @p@, separated by
|
||||||
|
-- @sep@. Returns a list of values returned by @p@.
|
||||||
|
sepBy1 :: Parser a -> Parser sep -> Parser [a]
|
||||||
|
sepBy1 p sep = liftA2 (:) p (many (sep *> p))
|
@ -28,10 +28,10 @@ import Graphics.X11
|
|||||||
import Graphics.X11.Xlib.Extras (none, setEventType, setKeyEvent)
|
import Graphics.X11.Xlib.Extras (none, setEventType, setKeyEvent)
|
||||||
import Control.Monad.Reader (asks)
|
import Control.Monad.Reader (asks)
|
||||||
import XMonad.Operations (withFocused)
|
import XMonad.Operations (withFocused)
|
||||||
import XMonad.Prelude (isUpper, listToMaybe)
|
import XMonad.Prelude (isUpper, fromMaybe)
|
||||||
import XMonad.Util.XSelection (getSelection)
|
import XMonad.Util.XSelection (getSelection)
|
||||||
import XMonad.Util.EZConfig (parseKey)
|
import XMonad.Util.EZConfig (parseKey)
|
||||||
import Text.ParserCombinators.ReadP (readP_to_S)
|
import XMonad.Util.Parser (runParser)
|
||||||
|
|
||||||
{- $usage
|
{- $usage
|
||||||
|
|
||||||
@ -72,8 +72,8 @@ pasteString = mapM_ (\x -> if isUpper x || x `elem` "~!@#$%^&*()_+{}|:\"<>?" the
|
|||||||
outside ASCII.
|
outside ASCII.
|
||||||
-}
|
-}
|
||||||
pasteChar :: KeyMask -> Char -> X ()
|
pasteChar :: KeyMask -> Char -> X ()
|
||||||
pasteChar m c = sendKey m $ maybe (unicodeToKeysym c) fst
|
pasteChar m c = sendKey m $ fromMaybe (unicodeToKeysym c)
|
||||||
$ listToMaybe $ readP_to_S parseKey [c]
|
$ runParser parseKey [c]
|
||||||
|
|
||||||
-- | Send a key with a modifier to the currently focused window.
|
-- | Send a key with a modifier to the currently focused window.
|
||||||
sendKey :: KeyMask -> KeySym -> X ()
|
sendKey :: KeyMask -> KeySym -> X ()
|
||||||
|
39
tests/EZConfig.hs
Normal file
39
tests/EZConfig.hs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
module EZConfig (spec) where
|
||||||
|
|
||||||
|
import Control.Arrow (first)
|
||||||
|
import Test.Hspec
|
||||||
|
import XMonad
|
||||||
|
import XMonad.Prelude
|
||||||
|
import XMonad.Util.EZConfig
|
||||||
|
import XMonad.Util.Parser
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
context "parseKey" $ do
|
||||||
|
let prepare = unzip . map (first surround)
|
||||||
|
testParseKey (ns, ks) = traverse (runParser parseKey) ns `shouldBe` Just ks
|
||||||
|
it "parses all regular keys" $ testParseKey regularKeys
|
||||||
|
it "parses all function keys" $ testParseKey (prepare functionKeys )
|
||||||
|
it "parses all special keys" $ testParseKey (prepare specialKeys )
|
||||||
|
it "parses all multimedia keys" $ testParseKey (prepare multimediaKeys)
|
||||||
|
context "parseModifier" $ do
|
||||||
|
it "parses all combinations of modifiers" $
|
||||||
|
nub . map sort <$> traverse (runParser (many $ parseModifier def))
|
||||||
|
modifiers
|
||||||
|
`shouldBe` Just [[ shiftMask, controlMask
|
||||||
|
, mod1Mask, mod1Mask -- def M and M1
|
||||||
|
, mod2Mask, mod3Mask, mod4Mask, mod5Mask
|
||||||
|
]]
|
||||||
|
|
||||||
|
regularKeys :: ([String], [KeySym])
|
||||||
|
regularKeys = unzip . map (first (: ""))
|
||||||
|
$ zip ['!' .. '~' ] [xK_exclam .. xK_asciitilde]
|
||||||
|
++ zip ['\xa0' .. '\xff'] [xK_nobreakspace .. xK_ydiaeresis]
|
||||||
|
|
||||||
|
-- | QuickCheck can handle the 8! combinations just fine.
|
||||||
|
modifiers :: [String]
|
||||||
|
modifiers = map concat $
|
||||||
|
permutations ["M-", "C-", "S-", "M1-", "M2-", "M3-", "M4-", "M5-"]
|
||||||
|
|
||||||
|
surround :: String -> String
|
||||||
|
surround s = "<" <> s <> ">"
|
@ -13,6 +13,7 @@ import qualified XPrompt
|
|||||||
import qualified CycleRecentWS
|
import qualified CycleRecentWS
|
||||||
import qualified OrgMode
|
import qualified OrgMode
|
||||||
import qualified GridSelect
|
import qualified GridSelect
|
||||||
|
import qualified EZConfig
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hspec $ do
|
main = hspec $ do
|
||||||
@ -51,3 +52,4 @@ main = hspec $ do
|
|||||||
context "CycleRecentWS" CycleRecentWS.spec
|
context "CycleRecentWS" CycleRecentWS.spec
|
||||||
context "OrgMode" OrgMode.spec
|
context "OrgMode" OrgMode.spec
|
||||||
context "GridSelect" GridSelect.spec
|
context "GridSelect" GridSelect.spec
|
||||||
|
context "EZConfig" EZConfig.spec
|
||||||
|
@ -359,6 +359,7 @@ library
|
|||||||
XMonad.Util.NamedScratchpad
|
XMonad.Util.NamedScratchpad
|
||||||
XMonad.Util.NamedWindows
|
XMonad.Util.NamedWindows
|
||||||
XMonad.Util.NoTaskbar
|
XMonad.Util.NoTaskbar
|
||||||
|
XMonad.Util.Parser
|
||||||
XMonad.Util.Paste
|
XMonad.Util.Paste
|
||||||
XMonad.Util.PositionStore
|
XMonad.Util.PositionStore
|
||||||
XMonad.Util.PureX
|
XMonad.Util.PureX
|
||||||
@ -387,6 +388,7 @@ test-suite tests
|
|||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
other-modules: CycleRecentWS
|
other-modules: CycleRecentWS
|
||||||
|
EZConfig
|
||||||
ExtensibleConf
|
ExtensibleConf
|
||||||
GridSelect
|
GridSelect
|
||||||
Instances
|
Instances
|
||||||
@ -403,6 +405,7 @@ test-suite tests
|
|||||||
XMonad.Actions.GridSelect
|
XMonad.Actions.GridSelect
|
||||||
XMonad.Actions.PhysicalScreens
|
XMonad.Actions.PhysicalScreens
|
||||||
XMonad.Actions.RotateSome
|
XMonad.Actions.RotateSome
|
||||||
|
XMonad.Actions.Submap
|
||||||
XMonad.Actions.SwapWorkspaces
|
XMonad.Actions.SwapWorkspaces
|
||||||
XMonad.Actions.TagWindows
|
XMonad.Actions.TagWindows
|
||||||
XMonad.Actions.WindowBringer
|
XMonad.Actions.WindowBringer
|
||||||
@ -421,12 +424,15 @@ test-suite tests
|
|||||||
XMonad.Prompt.Shell
|
XMonad.Prompt.Shell
|
||||||
XMonad.Util.Dmenu
|
XMonad.Util.Dmenu
|
||||||
XMonad.Util.Dzen
|
XMonad.Util.Dzen
|
||||||
|
XMonad.Util.EZConfig
|
||||||
XMonad.Util.ExtensibleConf
|
XMonad.Util.ExtensibleConf
|
||||||
XMonad.Util.ExtensibleState
|
XMonad.Util.ExtensibleState
|
||||||
XMonad.Util.Font
|
XMonad.Util.Font
|
||||||
XMonad.Util.Image
|
XMonad.Util.Image
|
||||||
XMonad.Util.Invisible
|
XMonad.Util.Invisible
|
||||||
|
XMonad.Util.NamedActions
|
||||||
XMonad.Util.NamedWindows
|
XMonad.Util.NamedWindows
|
||||||
|
XMonad.Util.Parser
|
||||||
XMonad.Util.PureX
|
XMonad.Util.PureX
|
||||||
XMonad.Util.Rectangle
|
XMonad.Util.Rectangle
|
||||||
XMonad.Util.Run
|
XMonad.Util.Run
|
||||||
|
Loading…
x
Reference in New Issue
Block a user