--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : SwishMain
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- Swish: Semantic Web Inference Scripting in Haskell
--
-- This program is a simple skeleton for constructing Semantic Web [1]
-- inference tools in Haskell, using the RDF graph and several RDF
-- parsers (at present Notation 3 and NTriples).
--
-- It might be viewed as a kind of embroyonic CWM [2] in Haskell, except
-- that the intent is that Haskell will be used as a primary language for
-- defining inferences. As such, Swish is an open-ended toolkit for
-- constructing new special-purpose Semantic Web applications rather
-- than a closed, self-contained general-purpose SW application.
-- I anticipate that this module may be used as a starting point for
-- creating new programs rathar then as a complete program in its own right.
-- The functionality built into this code is selected with a view to testing
-- the Haskell modules for handling RDF rather than for performing any
-- particular application processing (though development as a tool with
-- some broader utility is not ruled out). As such, it is part of
-- another experiment along the lines described in [3].
--
-- (1) Semantic web:
--
-- (2) CWM:
--
-- (3) Motivation:
--
-- (4) Notation 3:
--
-- (5) RDF:
--
--------------------------------------------------------------------------------
-- TODO:
--
-- * Add RDF/XML input and output
--
-- * Add Turtle and related formats for input and output
--
module Swish.RDF.SwishMain (
SwishStatus(..), SwishAction,
runSwish,
runSwishActions,
displaySwishHelp,
splitArguments,
validateCommands
) where
import Swish.RDF.SwishCommands
( swishFormat
, swishBase
, swishInput
, swishOutput
, swishMerge
, swishCompare
, swishGraphDiff
, swishScript
)
import Swish.RDF.SwishMonad
( SwishStateIO, SwishState(..), SwishStatus(..)
, emptyState
, SwishFormat(..)
)
import Swish.Utils.QName (qnameFromURI)
import Swish.Utils.ListHelpers (breakAll)
import Control.Monad.State (execStateT)
import Control.Monad (liftM)
import Network.URI (parseURI)
import Data.Char (isSpace)
import Data.Either (partitionEithers)
import System.Exit (ExitCode(ExitSuccess, ExitFailure))
------------------------------------------------------------
-- Command line description
------------------------------------------------------------
-- we do not display the version in the help file to avoid having
-- to include the Paths_swish module (so that we can use this from
-- an interactive environment).
--
usageText :: [String]
usageText =
[ "Swish: Read, merge, write, compare and process RDF graphs."
, ""
, "Usage: swish option option ..."
, ""
, "where the options are processed from left to right, and may be"
, "any of the following:"
, "-h display this message."
, "-? display this message."
, "-v display Swish version and quit."
, "-q do not display Swish version on start up."
, "-nt use Ntriples format for subsequent input and output."
, "-n3 use Notation3 format for subsequent input and output (default)"
, "-i[=file] read file in selected format into the graph workspace,"
, " replacing any existing graph."
, "-m[=file] merge file in selected format with the graph workspace."
, "-c[=file] compare file in selected format with the graph workspace."
, "-d[=file] show graph differences between the file in selected"
, " format and the graph workspace. Differences are displayed"
, " to the standard output stream."
, "-o[=file] write the graph workspace to a file in the selected format."
, "-s[=file] read and execute Swish script commands from the named file."
, "-b[=base] set or clear the base URI. The semantics of this are not"
, " fully defined yet."
, ""
, " If an optional filename value is omitted, the standard input"
, " or output stream is used, as appropriate."
, ""
, "Exit status codes:"
, "Success - operation completed successfully/graphs compare equal"
, "1 - graphs compare different"
, "2 - input data format error"
, "3 - file access problem"
, "4 - command line error"
, "5 - script file execution error"
, ""
, "Examples:"
, ""
, "swish -i=file"
, " read file as Notation3, and report any syntax errors."
, "swish -i=file1 -o=file2"
, " read file1 as Notation3, report any syntax errors, and output the"
, " resulting graph as reformatted Notation3 (the output format"
, " is not perfect but may be improved)."
, "swish -nt -i=file -n3 -o"
, " read file as NTriples and output as Notation3 to the screen."
, "swich -i=file1 -c=file2"
, " read file1 and file2 as notation3, report any syntax errors, and"
, " if both are OK, compare the resulting graphs to indicate whether"
, " or not they are equivalent."
]
-- | Write out the help for Swish
displaySwishHelp :: IO ()
displaySwishHelp = mapM_ putStrLn usageText
------------------------------------------------------------
-- Swish command line interpreter
------------------------------------------------------------
--
-- This is a composite monad combining some state with an IO
-- Monad. lift allows a pure IO monad to be used as a step
-- of the computation.
--
-- | Return any arguments that need processing immediately, namely
-- the \"help\", \"quiet\" and \"version\" options.
--
splitArguments :: [String] -> ([String], [String])
splitArguments = partitionEithers . map splitArgument
splitArgument :: String -> Either String String
splitArgument "-?" = Left "-h"
splitArgument "-h" = Left "-h"
splitArgument "-v" = Left "-v"
splitArgument "-q" = Left "-q"
splitArgument x = Right x
-- | Represent an Swish action, with an optional argument and
-- the Swish routine to act on that argument.
--
-- At present this type is a black box to external modules.
newtype SwishAction = SA (Maybe String, Maybe String -> SwishStateIO ())
-- | Given a list of command-line arguments create the list of actions
-- to perform or a string and status value indicating an input error.
validateCommands :: [String] -> Either (String, SwishStatus) [SwishAction]
validateCommands args =
let (ls, rs) = partitionEithers (map validateCommand args)
in case ls of
(e:_) -> Left e
[] -> Right rs
-- This allows you to say "-nt=foo" and currently ignores the values
-- passed through. This may change
--
validateCommand :: String -> Either (String, SwishStatus) SwishAction
validateCommand cmd =
let (nam,more) = break (=='=') cmd
arg = drop 1 more
marg = if null arg then Nothing else Just arg
wrap f = Right $ SA (marg, f)
in case nam of
"-nt" -> wrap $ swishFormat NT
"-n3" -> wrap $ swishFormat N3
"-i" -> wrap swishInput
"-m" -> wrap swishMerge
"-c" -> wrap swishCompare
"-d" -> wrap swishGraphDiff
"-o" -> wrap swishOutput
"-b" -> validateBase marg
"-s" -> wrap swishScript
_ -> Left ("Invalid command line argument: "++cmd, SwishArgumentError)
-- | Execute the given set of actions.
swishCommands :: [SwishAction] -> SwishStateIO ()
swishCommands = mapM_ swishCommand
-- | Execute an action.
swishCommand :: SwishAction -> SwishStateIO ()
swishCommand (SA (marg,act)) = act marg
validateBase :: Maybe String -> Either (String, SwishStatus) SwishAction
validateBase Nothing = Right $ SA (Nothing, swishBase Nothing)
validateBase (Just b) =
case parseURI b of
Just _ -> Right $ SA (Nothing, swishBase (Just (qnameFromURI b)))
_ -> Left ("Invalid base URI <" ++ b ++ ">", SwishArgumentError)
------------------------------------------------------------
-- Interactive test function (e.g. for use in Hugs)
------------------------------------------------------------
-- this ignores the "flags" options, namely
-- -q / -h / -? / -v
-- | Parse and run the given string as if given at the command
-- line. The \"quiet\", \"version\" and \"help\" options are
-- ignored.
--
runSwish :: String -> IO ExitCode
runSwish cmdline = do
let args = breakAll isSpace cmdline
(_, cmds) = splitArguments args
case validateCommands cmds of
Left (emsg, ecode) -> do
putStrLn $ "Swish exit: " ++ emsg
return $ ExitFailure $ fromEnum ecode
Right acts -> do
ec <- runSwishActions acts
case ec of
SwishSuccess -> return ExitSuccess
_ -> do
putStrLn $ "Swish exit: " ++ show ec
return $ ExitFailure $ fromEnum ec
-- | Execute the given set of actions.
runSwishActions :: [SwishAction] -> IO SwishStatus
runSwishActions acts = exitcode `liftM` execStateT (swishCommands acts) emptyState
--------------------------------------------------------------------------------
--
-- 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
--
--------------------------------------------------------------------------------