{- Copyright 2015-2017 Markus Ongyerth, Stephan Guenther This file is part of Monky. Monky is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Monky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Monky. If not, see . -} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE CPP #-} {-| Module : Monky.Utility Description : Provides utility functions Maintainer : ongy, moepi Stability : testing Portability : Linux This module provides utility functions used in monky modules -} module Monky.Utility ( readValue , readValueI , readValues , fopen , fclose , File , readLine , readContent , findLine , splitAtEvery , maybeOpenFile , sdivBound , sdivUBound , listDirectory , C.UName , getUname , getKernelVersion ) where import Data.Word (Word) import qualified Monky.CUtil as C import System.IO import System.IO.Unsafe (unsafePerformIO) import Data.List (isPrefixOf) import Data.Maybe (fromMaybe) import qualified Data.Text as T import qualified Data.Text.Read as T import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BS #if MIN_VERSION_base(4,9,0) import System.Directory (listDirectory) #else import System.Directory (getDirectoryContents) listDirectory :: String -> IO [String] listDirectory = fmap (filter (not . ("." `isPrefixOf`))) . getDirectoryContents #endif class LineReadable a where hGetReadable :: Handle -> IO a instance LineReadable String where hGetReadable = hGetLine instance LineReadable ByteString where hGetReadable = BS.hGetLine class FileReadable a where hGetFile :: Handle -> IO [a] instance FileReadable String where hGetFile = readStringLines instance FileReadable ByteString where hGetFile = readBSLines -- |type alias to distinguish system functions from utility newtype File = File Handle deriving (Show, Eq) -- |Find a line in a list of Strings findLine :: Eq a => [a] -> [[a]] -> Maybe [a] findLine y (x:xs) = if y `isPrefixOf` x then Just x else findLine y xs findLine _ [] = Nothing -- |Read the first line of the file and convert it into an 'Int' readValue :: File -> IO Int readValue (File h) = do hSeek h AbsoluteSeek 0 line <- hGetReadable h let value = fmap fst $ BS.readInt line return . fromMaybe (error ("Failed to read value from file:" ++ show h)) $ value -- |Read the first line of the file and convert it into an 'Integer' readValueI :: File -> IO Integer readValueI (File h) = do hSeek h AbsoluteSeek 0 line <- hGetReadable h let value = fmap fst $ BS.readInteger line return . fromMaybe (error ("Failed to read value from file:" ++ show h)) $ value -- |Read the first line of the file and convert the words in it into 'Int's readValues :: File -> IO [Int] readValues (File h) = do hSeek h AbsoluteSeek 0 line <- hGetReadable h let value = mapM (fmap fst . BS.readInt) $ BS.words line return . fromMaybe (error ("Failed to read values from file:" ++ show h)) $ value -- |Read the first line of the file readLine :: LineReadable a => File -> IO a readLine (File h) = do hSeek h AbsoluteSeek 0 hGetReadable h -- |Read a File as String line by line readStringLines :: Handle -> IO [String] readStringLines h = do eof <- hIsEOF h if eof then return [] else do l <- hGetReadable h fmap (l:) $ readStringLines h -- |Read a File as ByteString line by line readBSLines :: Handle -> IO [ByteString] readBSLines h = fmap (BS.lines . BS.concat) $ readLines' [] where readLines' ls = do ret <- BS.hGet h 512 if ret == BS.empty then return $ reverse ls else readLines' (ret:ls) -- |Rewind the file descriptor and read the complete file as lines readContent :: FileReadable a => File -> IO [a] readContent (File h) = do hSeek h AbsoluteSeek 0 hGetFile h -- |open a file read only fopen :: String -> IO File fopen = fmap File . flip openFile ReadMode -- |Close a file opened by 'fopen' fclose :: File -> IO () fclose (File h) = hClose h -- |Split ys at every occurence of xs splitAtEvery :: String -> String -> [String] splitAtEvery s str = splitAtEvery' s str [] where splitAtEvery' _ [] zs = [zs] splitAtEvery' xs (y:ys) zs = if xs `isPrefixOf` (y:ys) then zs:splitAtEvery' xs (cut ys) [] else splitAtEvery' xs ys (zs ++ [y]) where cut = drop (length xs -1) -- |fmap fopen would give Maybe (IO File), this fixes that maybeOpenFile :: Maybe String -> IO (Maybe File) maybeOpenFile Nothing = return Nothing maybeOpenFile (Just x) = fmap Just . fopen $ x -- |0 save divide, uses maxbound for default value sdivBound :: (Integral a, Bounded a) => a -> a -> a sdivBound _ 0 = maxBound sdivBound x y = x `div` y -- |0 save divide, uses default value sdivUBound :: Integral a => a -> a -> a -> a sdivUBound _ 0 d = d sdivUBound x y _ = x `div` y infixl 7 `sdivBound`, `sdivUBound` -- This should never change while we are on the same kernel -- | Get the kernel name/version/release and so forth getUname :: C.UName getUname = unsafePerformIO C.uname -- | Get the kernel version as (major, minor) getKernelVersion :: (Word, Word) getKernelVersion = let txt = T.splitOn (T.pack ".") . C._uRelease $ getUname (Right major: Right minor:_) = fmap T.decimal txt in (fst major, fst minor)