swish-0.3.0.0: Swish/RDF/SwishCommands.hs
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : SwishCommands
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- SwishCommands: functions to deal with indivudual Swish command options.
--
--------------------------------------------------------------------------------
module Swish.RDF.SwishCommands
( swishFormat
, swishBase
-- , swishVerbose
, swishInput
, swishOutput
, swishMerge
, swishCompare
, swishGraphDiff
, swishScript
)
where
import Swish.RDF.SwishMonad
( SwishStateIO, SwishState(..), SwishStatus(..)
, setFormat, setBase, setGraph
, resetInfo, resetError, setStatus
-- , setVerbose
, SwishFormat(..)
, swishError
, reportLine
)
import Swish.RDF.SwishScript
( parseScriptFromString
)
import Swish.RDF.GraphPartition
( GraphPartition(..)
, partitionGraph, comparePartitions
, partitionShowP
)
import Swish.RDF.RDFGraph
( RDFGraph, merge )
import qualified Swish.RDF.N3Formatter as N3F
import qualified Swish.RDF.NTFormatter as NTF
import Swish.RDF.N3Parser (parseN3) -- (parseN3fromString)
import Swish.RDF.NTParser (parseNT)
import Swish.RDF.GraphClass
( LDGraph(..)
, Label(..)
)
import Swish.Utils.QName (QName, qnameFromURI, qnameFromFilePath, getQNameURI)
import System.IO
( Handle, openFile, IOMode(..)
, hPutStr, hPutStrLn, hClose, hGetContents
, hIsReadable, hIsWritable
, stdin, stdout
)
import Network.URI (URI,
relativeTo,
parseURI, parseURIReference, uriToString)
import Control.Monad.Trans (MonadTrans(..))
import Control.Monad.State (modify, gets)
import Control.Monad (liftM, when)
import System.IO.Error
------------------------------------------------------------
-- Set file format to supplied value
------------------------------------------------------------
-- the second argument allows for options to be passed along
-- with the format (a la cwm) but this is not supported yet
--
swishFormat :: SwishFormat -> Maybe String -> SwishStateIO ()
swishFormat fmt _ = modify (setFormat fmt)
------------------------------------------------------------
-- Set base URI to supplied value
------------------------------------------------------------
-- the Maybe String argument is ignored (a result of a lack of
-- design with the command-line processing)
--
swishBase :: Maybe QName -> Maybe String -> SwishStateIO ()
swishBase mb _ = modify (setBase mb)
------------------------------------------------------------
-- Read graph from named file
------------------------------------------------------------
swishInput :: Maybe String -> SwishStateIO ()
swishInput fnam =
swishReadGraph fnam >>= maybe (return ()) (modify . setGraph)
------------------------------------------------------------
-- Merge graph from named file
------------------------------------------------------------
swishMerge :: Maybe String -> SwishStateIO ()
swishMerge fnam =
swishReadGraph fnam >>= maybe (return ()) (modify . mergeGraph)
mergeGraph :: RDFGraph -> SwishState -> SwishState
mergeGraph gr state = state { graph = newgr }
where
newgr = merge gr (graph state)
------------------------------------------------------------
-- Compare graph from named file
------------------------------------------------------------
swishCompare :: Maybe String -> SwishStateIO ()
swishCompare fnam =
swishReadGraph fnam >>= maybe (return ()) compareGraph
compareGraph :: RDFGraph -> SwishStateIO ()
compareGraph gr = do
oldGr <- gets graph
let exitCode = if gr == oldGr then SwishSuccess else SwishGraphCompareError
modify $ setStatus exitCode
------------------------------------------------------------
-- Display graph differences from named file
------------------------------------------------------------
swishGraphDiff :: Maybe String -> SwishStateIO ()
swishGraphDiff fnam =
swishReadGraph fnam >>= maybe (return ()) diffGraph
diffGraph :: RDFGraph -> SwishStateIO ()
diffGraph gr = do
oldGr <- gets graph
let p1 = partitionGraph (getArcs oldGr)
p2 = partitionGraph (getArcs gr)
diffs = comparePartitions p1 p2
maybehandleclose <- swishWriteFile Nothing
case maybehandleclose of
Just (h,c) -> do
swishOutputDiffs "" h diffs
when c (lift $ hClose h)
_ -> return ()
swishOutputDiffs :: (Label lb) =>
String -> Handle
-> [(Maybe (GraphPartition lb),Maybe (GraphPartition lb))]
-> SwishStateIO ()
swishOutputDiffs fnam hnd diffs = do
lift $ hPutStrLn hnd ("Graph differences: "++show (length diffs))
mapM_ (swishOutputDiff fnam hnd) (zip [1..] diffs)
swishOutputDiff :: (Label lb) =>
String -> Handle
-> (Int,(Maybe (GraphPartition lb),Maybe (GraphPartition lb)))
-> SwishStateIO ()
swishOutputDiff fnam hnd (diffnum,(part1,part2)) = do
lift $ hPutStrLn hnd ("---- Difference "++show diffnum++" ----")
lift $ hPutStr hnd "Graph 1:"
swishOutputPart fnam hnd part1
lift $ hPutStr hnd "Graph 2:"
swishOutputPart fnam hnd part2
swishOutputPart :: (Label lb) =>
String -> Handle -> Maybe (GraphPartition lb) -> SwishStateIO ()
swishOutputPart _ hnd part =
let out = maybe "\n(No arcs)" (partitionShowP "\n") part
in lift $ hPutStrLn hnd out
------------------------------------------------------------
-- Execute script from named file
------------------------------------------------------------
swishScript :: Maybe String -> SwishStateIO ()
swishScript fnam = swishReadScript fnam >>= mapM_ swishCheckResult
swishReadScript :: Maybe String -> SwishStateIO [SwishStateIO ()]
swishReadScript fnam =
let hdlr (h,i) = do
res <- swishParseScript fnam i
lift $ hClose h
return res
in swishOpenFile fnam >>= maybe (return []) hdlr
{-|
Calculate the base URI to use; it combines the file name
with any user-supplied base.
If both the file name and user-supplied base are Nothing
then the value
http://id.ninebynine.org/2003/Swish/
is used.
Needs some work.
-}
defURI :: QName
defURI = qnameFromURI "http://id.ninebynine.org/2003/Swish/"
calculateBaseURI ::
Maybe FilePath -- ^ file name
-> SwishStateIO QName -- ^ base URI
calculateBaseURI Nothing = maybe defURI id `liftM` gets base
calculateBaseURI (Just fnam) = do
mbase <- gets base
case mbase of
Just buri -> case appendUris (getQNameURI buri) fnam of
Left emsg -> fail emsg -- TODO: think about this ...
Right res -> return $ qnameFromURI $ showURI res
Nothing -> lift $ qnameFromFilePath fnam
-- this is also in N3Parser
showURI :: URI -> String
showURI u = uriToString id u ""
-- this is also in N3Parser
appendUris :: String -> String -> Either String URI
appendUris buri uri =
case parseURI uri of
Just absuri -> Right absuri
_ -> case parseURIReference uri of
Just reluri -> case parseURI buri of
Just baseuri -> case relativeTo reluri baseuri of
Just resuri -> Right resuri
_ -> Left $ "Unable to append <" ++ uri ++ "> to base=<" ++ buri ++ ">"
_ -> Left $ "Invalid base URI: <" ++ buri ++ ">"
_ -> Left $ "Invalid URI: <" ++ uri ++ ">"
swishParseScript ::
Maybe String -- file name (or "stdin" if Nothing)
-> String -- script contents
-> SwishStateIO [SwishStateIO ()]
swishParseScript mfpath inp = do
buri <- calculateBaseURI mfpath
case parseScriptFromString (Just buri) inp of
Left err -> do
let inName = maybe "standard input" ("file " ++) mfpath
swishError ("Script syntax error in " ++ inName ++ ": "++err) SwishDataInputError
return []
Right scs -> return scs
swishCheckResult :: SwishStateIO () -> SwishStateIO ()
swishCheckResult swishcommand = do
swishcommand
er <- gets errormsg
case er of
Just x -> swishError x SwishExecutionError >> modify resetError
_ -> return ()
ms <- gets infomsg
case ms of
Just x -> reportLine x >> modify resetInfo
_ -> return ()
------------------------------------------------------------
-- Output graph to named file
------------------------------------------------------------
swishOutput :: Maybe String -> SwishStateIO ()
swishOutput fnam =
let hdlr (h,c) = swishOutputGraph fnam h >> when c (lift $ hClose h)
in swishWriteFile fnam >>= maybe (return ()) hdlr
swishOutputGraph :: Maybe String -> Handle -> SwishStateIO ()
swishOutputGraph _ hnd = do
fmt <- gets format
let writeOut formatter = do
out <- gets $ formatter . graph
lift $ hPutStrLn hnd (out "")
case fmt of
N3 -> writeOut N3F.formatGraphAsShowS
NT -> writeOut NTF.formatGraphAsShowS
-- _ -> swishError ("Unsupported file format: "++show fmt) SwishArgumentError
------------------------------------------------------------
-- Common input functions
------------------------------------------------------------
--
-- Keep the logic separate for reading file data and
-- parsing it to an RDF graph value.
swishReadGraph :: Maybe String -> SwishStateIO (Maybe RDFGraph)
swishReadGraph fnam =
let reader (h,i) = do
res <- swishParse fnam i
lift $ hClose h
return res
in swishOpenFile fnam >>= maybe (return Nothing) reader
-- Open and read file, returning its handle and content, or Nothing
-- WARNING: the handle must not be closed until input is fully evaluated
--
swishOpenFile :: Maybe String -> SwishStateIO (Maybe (Handle,String))
swishOpenFile Nothing = readFromHandle stdin "standard input."
swishOpenFile (Just fnam) = do
o <- lift $ try $ openFile fnam ReadMode
case o of
Left _ -> do
swishError ("Cannot open file: "++fnam) SwishDataAccessError
return Nothing
Right hnd -> readFromHandle hnd ("file: " ++ fnam)
readFromHandle :: Handle -> String -> SwishStateIO (Maybe (Handle, String))
readFromHandle hdl lbl = do
hrd <- lift $ hIsReadable hdl
if hrd
then do
fc <- lift $ hGetContents hdl
return $ Just (hdl,fc)
else do
-- closing stdin should not be an issue here?
lift $ hClose hdl
swishError ("Cannot read from " ++ lbl) SwishDataAccessError
return Nothing
swishParse ::
Maybe String -- ^ filename (if not stdin)
-> String -- ^ contents of file
-> SwishStateIO (Maybe RDFGraph)
swishParse mfpath inp = do
fmt <- gets format
buri <- calculateBaseURI mfpath
let toError eMsg =
swishError (show fmt ++ " syntax error in " ++ inName ++ ": " ++ eMsg) SwishDataInputError
>> return Nothing
inName = maybe "standard input" ("file " ++) mfpath
readIn reader = case reader inp of
Left eMsg -> toError eMsg
Right res -> return $ Just res
case fmt of
N3 -> readIn (flip parseN3 (Just buri))
NT -> readIn parseNT
{-
_ -> swishError ("Unsupported file format: "++show fmt) SwishArgumentError >>
return Nothing
-}
-- Open file for writing, returning its handle, or Nothing
-- Also returned is a flag indicating whether or not the
-- handled should be closed when writing is done (if writing
-- to standard output, the handle should not be closed as the
-- run-time system should deal with that).
swishWriteFile :: Maybe String -> SwishStateIO (Maybe (Handle,Bool))
swishWriteFile Nothing = do
hwt <- lift $ hIsWritable stdout
if hwt
then return $ Just (stdout, False)
else do
swishError ("Cannot write to standard output") SwishDataAccessError
return Nothing
swishWriteFile (Just fnam) = do
o <- lift $ try $ openFile fnam WriteMode
case o of
Left _ -> do
swishError ("Cannot open file for writing: " ++ fnam) SwishDataAccessError
return Nothing
Right hnd -> do
hwt <- lift $ hIsWritable hnd
if hwt
then return $ Just (hnd, True)
else do
lift $ hClose hnd
swishError ("Cannot write to file: "++fnam) SwishDataAccessError
return Nothing
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish 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 General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------