Skip to content

Instantly share code, notes, and snippets.

@zaphar
Created May 13, 2011 01:28
Show Gist options
  • Save zaphar/969805 to your computer and use it in GitHub Desktop.
Save zaphar/969805 to your computer and use it in GitHub Desktop.
A toy implementation of a readable writable window onto a filesyste file in haskell.
-- Copyright (c) 2011, Jeremy Wall
-- | A moving readable/writable window on an IO Handle.
module FileWindow (
FileWindow
, WindowReadResult(..)
, newFileWindow
, emptyWindow
-- Accessors
, getHandle
, getPath
, getStart
, getEnd
, getSize
, getMax
-- Mutations
, setMaxSize
-- IO
, openWindow
, closeWindow
, withWindow
, readWindow
, readWindowLines
, seekWindow
, writeWindow
) where
import System.FilePath
import System.IO
import Data.Maybe (Maybe)
-- | The Datatype of a Window onto a IO Handle.
data FileWindow = FileWindow {
path :: FilePath
, handle ::Maybe Handle
, wStart :: Integer
, wSize :: Integer
, wMax :: Integer
}
deriving (Show)
-- | The Datatype of the result of reading from a FileWindow.
data WindowReadResult = WindowReadResult FileWindow String
deriving (Show)
-- | The empty FileWindow.
emptyWindow = FileWindow { wStart = 0, wSize = 0, wMax = 0, path = "", handle = Nothing }
-- | Constructor for a FileWindow.
-- Constructs a FileWindow for the FilePath and max size.
newFileWindow :: FilePath -> Integer -> FileWindow
newFileWindow p max = emptyWindow { path = p, wMax = max }
-- | Accessor for the FileWindow handle.
getHandle :: FileWindow -> Maybe Handle
getHandle = handle
-- | Accessor for the FileWindow path.
getPath :: FileWindow -> FilePath
getPath = path
-- | Accessor for the start index of the FileWindow window.
getStart ::FileWindow -> Integer
getStart = wStart
-- | Accessor for the actual size of the FileWindow window.
getSize :: FileWindow -> Integer
getSize = wSize
-- | Accessor for the max size of the FileWindow window.
getMax :: FileWindow -> Integer
getMax = wMax
-- | Accessor for the end index of the FileWindow window.
getEnd :: FileWindow -> Integer
getEnd fw = let s = getStart fw
sz = getSize fw
in s + sz
-- | Sets the max size on a FileWindow to the provided size.
setMaxSize :: FileWindow -> Integer -> FileWindow
setMaxSize fw sz = fw { wMax = sz }
-- | Opens a FileWindow with the IOMode.
-- Seeks immediately to the start index for the window.
openWindow :: FileWindow -> IOMode -> IO FileWindow
openWindow fw mode = do
let path = getPath fw
s = getStart fw
sz = getMax fw
h <- openFile path mode
seekWindow (fw { handle = Just h, wSize = sz }) AbsoluteSeek s
-- | Closes a FileWindow.
closeWindow :: FileWindow -> IO ()
closeWindow fw = do
case getHandle fw of
Just h -> hClose h
Nothing -> return ()
-- | Safely run an action with a FileWindow.
-- Opens the FileWindow for the action and then
-- closes it after the action has run.
withWindow :: FileWindow -> IOMode -> (FileWindow -> IO FileWindow) -> IO FileWindow
withWindow fw mode f = do
opened <- openWindow fw mode
result <- f opened
closeWindow result
return result
-- TODO(jwall): handle the case of an unopened window?
-- | Read the current window from a FileWindow.
-- Reads getMax Bytes from the FileWindow's IO Handle
-- And returns the result.
readWindow :: FileWindow -> IO WindowReadResult
readWindow fw = do
let Just h = getHandle fw
sz = getMax fw
str <- readTo "" h $ getEnd fw
return $ WindowReadResult (fw { wSize = sz }) str
-- | Reads from the FileWindow constraining to full lines.
-- Reads as close to getMax for the FileWindow as it can while
-- still only returning full lines. Sets the size of the FileWindow
-- to the actual size of the window.
readWindowLines :: FileWindow -> IO WindowReadResult
readWindowLines fw = do
WindowReadResult _ str <- readWindow fw
let (s, n) = chopUntilNewLine (reverse str) 0
return $ WindowReadResult (fw { wSize = n }) s
where chopUntilNewLine s n =
if (head s) == '\n'
then (reverse s, n)
else chopUntilNewLine (drop 1 s) $ n + 1
-- | Seeks in the FileWindow's IO Handle to the index with the SeekModeMode.
-- Sets the start of the window in the return to the current
-- position of the IO Handle
seekWindow :: FileWindow -> SeekMode -> Integer -> IO FileWindow
seekWindow fw mode i = do
let Just h = getHandle fw
hSeek h mode i
curr <- hTell h
return fw { wStart = curr }
-- | Writes the string to the FileWindow.
-- Always overwrites the contents of the current window range
-- in the IO Handle with the string.
writeWindow :: FileWindow -> String -> IO ()
writeWindow fw str = do
let Just h = getHandle fw
let s = getStart fw
let e = getEnd fw
size <- hFileSize h
fst <- readRange h 0 (s - 1)
lst <- readRange h (e + 1) size
fw' <- seekWindow fw AbsoluteSeek 0
hSetFileSize h 0
let Just h' = getHandle fw'
hPutStr h' fst
hPutStr h' str
hPutStr h' lst
readRange h s e = do
hSeek h AbsoluteSeek s
readTo "" h e
readTo acc h e = do
eof <- hIsEOF h
curr <- hTell h
if eof || (curr >= e)
then return $ acc ++ ""
else do c <- hGetChar h
readTo (acc ++ [c]) h e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment