xdg-desktop-entry 0.1.1.5 → 0.1.1.6
raw patch · 4 files changed
+89/−15 lines, 4 filesdep +bytestringdep −inidep −unordered-containersPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: bytestring
Dependencies removed: ini, unordered-containers
API changes (from Hackage documentation)
- System.Environment.XDG.DesktopEntry: instance GHC.Internal.Read.Read System.Environment.XDG.DesktopEntry.DesktopEntry
- System.Environment.XDG.DesktopEntry: instance GHC.Internal.Read.Read System.Environment.XDG.DesktopEntry.DesktopEntryType
- System.Environment.XDG.DesktopEntry: instance GHC.Internal.Show.Show System.Environment.XDG.DesktopEntry.DesktopEntry
- System.Environment.XDG.DesktopEntry: instance GHC.Internal.Show.Show System.Environment.XDG.DesktopEntry.DesktopEntryType
+ System.Environment.XDG.DesktopEntry: instance GHC.Read.Read System.Environment.XDG.DesktopEntry.DesktopEntry
+ System.Environment.XDG.DesktopEntry: instance GHC.Read.Read System.Environment.XDG.DesktopEntry.DesktopEntryType
+ System.Environment.XDG.DesktopEntry: instance GHC.Show.Show System.Environment.XDG.DesktopEntry.DesktopEntry
+ System.Environment.XDG.DesktopEntry: instance GHC.Show.Show System.Environment.XDG.DesktopEntry.DesktopEntryType
Files
- CHANGELOG.md +8/−0
- src/System/Environment/XDG/DesktopEntry.hs +37/−10
- test/Main.hs +41/−1
- xdg-desktop-entry.cabal +3/−4
CHANGELOG.md view
@@ -1,5 +1,13 @@ # Revision history for xdg-desktop-entry +## 0.1.1.6 -- 2026-09-04++* Replace the `ini` based parser with one that follows the desktop entry+ specification. Localised keys such as `Name[de]` and `GenericName[ar]` no+ longer make `readDesktopEntry` fail, so nearly every real-world desktop file+ now parses. Files are decoded as UTF-8 regardless of the process locale.+* Drop the `ini` and `unordered-containers` dependencies.+ ## 0.1.1.5 -- 2026-05-13 * Clean up parser and test warnings for newer GHC releases.
src/System/Environment/XDG/DesktopEntry.hs view
@@ -39,16 +39,16 @@ import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Except-import Data.Bifunctor (bimap)+import qualified Data.ByteString as BS import Data.Char import Data.Either import Data.Either.Combinators-import qualified Data.HashMap.Strict as HM-import qualified Data.Ini as Ini import Data.List import Data.Maybe import qualified Data.MultiMap as MM-import Data.Text (pack, unpack)+import Data.Text (unpack)+import Data.Text.Encoding (decodeUtf8With)+import Data.Text.Encoding.Error (lenientDecode) import Safe import System.Directory import System.FilePath.Posix@@ -193,19 +193,46 @@ -- | Read a desktop entry from a file. readDesktopEntry :: FilePath -> IO (Either String DesktopEntry) readDesktopEntry filePath = runExceptT $ do- -- let foo1 = join . fmap except . liftIO $ Ini.readIniFile filePath- -- let bar :: ExceptT String IO (HM.HashMap Text [(Text, Text)]) = map Ini.iniSections . liftIO $ Ini.readIniFile filePath- -- sections <- fmap Ini.iniSections . join . fmap except . liftIO $ Ini.readIniFile filePath- sections <- liftIO (Ini.readIniFile filePath) >>= fmap Ini.iniSections . except+ contents <- liftIO $ unpack . decodeUtf8With lenientDecode <$> BS.readFile filePath+ groups <- except $ parseDesktopEntryGroups contents result <-- maybe (throwE "Section [Desktop Entry] not found") (pure . fmap (bimap unpack unpack)) $- HM.lookup (pack "Desktop Entry") sections+ maybe (throwE "Section [Desktop Entry] not found") pure $+ lookup "Desktop Entry" groups return DesktopEntry { deType = fromMaybe Application $ lookup "Type" result >>= readMaybe, deFilename = filePath, deAttributes = result }++-- | Parse the groups of a desktop entry file into association lists, in file+-- order. Keys keep their locale suffix (e.g. @Name[de]@), whitespace around+-- @=@ is ignored, and blank and @#@ comment lines are skipped.+parseDesktopEntryGroups :: String -> Either String [(String, [(String, String)])]+parseDesktopEntryGroups = go Nothing [] . zip [1 :: Int ..] . lines+ where+ go current done [] = Right $ reverse $ finish current done+ go current done ((lineNo, rawLine) : rest)+ | null line || "#" `isPrefixOf` line = go current done rest+ | "[" `isPrefixOf` line && "]" `isSuffixOf` line =+ go (Just (takeWhile (/= ']') $ drop 1 line, [])) (finish current done) rest+ | otherwise =+ case break (== '=') line of+ (rawKey, '=' : rawValue)+ | not (null key) ->+ case current of+ Nothing ->+ Left $ printf "line %d: entry before any group header" lineNo+ Just (name, entries) ->+ go (Just (name, (key, trim rawValue) : entries)) done rest+ where+ key = trim rawKey+ _ -> Left $ printf "line %d: expected a group header or key=value" lineNo+ where+ line = trim rawLine+ finish Nothing done = done+ finish (Just (name, entries)) done = (name, reverse entries) : done+ trim = dropWhileEnd isSpace . dropWhile isSpace -- | Construct a 'MM.Multimap' where each 'DesktopEntry' in the provided -- foldable is indexed by the keys returned from the provided indexing function.
test/Main.hs view
@@ -12,7 +12,27 @@ "[Desktop Entry]\n\ \icon=2", "[desktop entry]\n\- \Icon=3"+ \Icon=3",+ "# Localised keys, comments, padding around '=' and extra groups.\n\+ \[Desktop Entry]\n\+ \Version=1.0\n\+ \Name=Google Chrome\n\+ \# Only KDE 4 seems to use GenericName.\n\+ \GenericName=Web Browser\n\+ \GenericName[ar]=متصفح الشبكة\n\+ \Name[de]=Google Chrome (de)\n\+ \Icon = google-chrome\n\+ \Exec=/usr/bin/google-chrome %U\n\+ \Actions=new-window;\n\+ \\n\+ \[Desktop Action new-window]\n\+ \Name=New Window\n\+ \Exec=/usr/bin/google-chrome",+ "Icon=before-any-group\n\+ \[Desktop Entry]\n\+ \Icon=4",+ "[Desktop Entry]\n\+ \this line has no equals sign" ] main :: IO ()@@ -39,4 +59,24 @@ deIcon deResult `shouldBe` Nothing it "content2 should not work" $ do deResultE <- readDesktopEntry $ filepath 2+ isLeft deResultE `shouldBe` True+ describe "readDesktopEntry" $ do+ it "parses localised keys, comments and extra groups" $ do+ deResultE <- readDesktopEntry $ filepath 3+ case deResultE of+ Left e -> expectationFailure $ show e+ Right deResult -> do+ deIcon deResult `shouldBe` Just "google-chrome"+ deName [] deResult `shouldBe` "Google Chrome"+ deName ["de"] deResult `shouldBe` "Google Chrome (de)"+ deName ["ar"] deResult `shouldBe` "Google Chrome"+ deCommand deResult `shouldBe` Just "/usr/bin/google-chrome"+ lookup "GenericName[ar]" (deAttributes deResult)+ `shouldBe` Just "متصفح الشبكة"+ lookup "Name" (deAttributes deResult) `shouldBe` Just "Google Chrome"+ it "rejects entries before any group header" $ do+ deResultE <- readDesktopEntry $ filepath 4+ isLeft deResultE `shouldBe` True+ it "rejects lines that are neither headers nor key=value" $ do+ deResultE <- readDesktopEntry $ filepath 5 isLeft deResultE `shouldBe` True
xdg-desktop-entry.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: xdg-desktop-entry-version: 0.1.1.5+version: 0.1.1.6 synopsis: Parse files conforming to the xdg desktop entry spec description: Parse files conforming to the xdg desktop entry spec. bug-reports: https://github.com/taffybar/taffybar/issues@@ -22,16 +22,15 @@ library exposed-modules: System.Environment.XDG.DesktopEntry build-depends: base >=4.13 && < 5,+ bytestring >= 0.10 && < 0.13, directory >= 1.3.6 && < 1.4, either >= 5.0.1.1 && < 5.1, filepath >= 1.4.2 && < 1.6,- ini >= 0.4.1 && < 0.6, multimap >= 1.2.1 && < 1.3, safe >= 0.3.19 && < 0.4, text >= 1.2.4 && < 2.2, transformers >= 0.5.6 && < 0.7,- unix >= 2.7.2 && < 2.9,- unordered-containers >= 0.2.10 && < 0.3+ unix >= 2.7.2 && < 2.9 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall