-------------------------------------------------------------------------------- -- See end of this file for licence information. -------------------------------------------------------------------------------- -- | -- Module : Swish -- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012 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. As such, it is part of another experiment along -- the lines described in [3]. -- -- The script format used by Swish is described in -- "Swish.Script". -- -- Users wishing to process RDF data directly may prefer to look at -- the following modules; "Swish.RDF", "Swish.RDF.Parser.Turtle", -- "Swish.RDF.Parser.N3", "Swish.RDF.Parser.NTriples", -- "Swish.RDF.Formatter.Turtle", "Swish.RDF.Formatter.N3" -- and "Swish.RDF.Formatter.NTriples". -- -- (1) Semantic web: -- -- (2) CWM: -- -- (3) Motivation: -- -- (4) Notation 3: -- -- (5) Turtle: -- -- (6) RDF: -- -- Notes -- -- I anticipate that this module may be used as a starting point for -- creating new programs rather 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). -- -- With the following in ghci: -- -- >>> :m + Swish -- >>> :set prompt "swish> " -- -- then we can run a Swish script (format described in "Swish.Script") -- by saying: -- -- >>> runSwish "-s=script.ss" -- ExitSuccess -- -- or convert a file from Turtle to NTriples format with: -- -- >>> runSwish "-ttl -i=foo.ttl -nt -o=foo.nt" -- ExitSuccess -- -- You can also use `validateCommands` by giving it the individual commands, -- such as -- -- >>> let Right cs = validateCommands ["-ttl", "-i=file1.ttl", "-c=file2.ttl"] -- >>> cs -- [SwishAction: -ttl,SwishAction: -i=file1.ttl,SwishAction: -c=file2.ttl] -- >>> st <- runSwishActions cs -- >>> st -- The graphs do not compare as equal. -- -------------------------------------------------------------------------------- module Swish ( SwishStatus(..) , SwishAction , runSwish , runSwishActions , displaySwishHelp , splitArguments , validateCommands ) where import Swish.Commands ( swishFormat , swishBase , swishInput , swishOutput , swishMerge , swishCompare , swishGraphDiff , swishScript ) import Swish.Monad (SwishStateIO, SwishState(..), SwishStatus(..) , SwishFormat(..) , emptyState) import Swish.QName (qnameFromURI) 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." , "-ttl use Turtle 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 a Swish action. At present there is no way to create these -- actions other than 'validateCommands'. -- newtype SwishAction = SA (String, SwishStateIO ()) instance Show SwishAction where show (SA (lbl,_)) = "SwishAction: " ++ lbl -- | 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 (cmd, f marg) wrap1 f = Right $ SA (cmd, f) in case nam of "-ttl" -> wrap1 $ swishFormat Turtle "-nt" -> wrap1 $ swishFormat NT "-n3" -> wrap1 $ swishFormat N3 "-i" -> wrap swishInput "-m" -> wrap swishMerge "-c" -> wrap swishCompare "-d" -> wrap swishGraphDiff "-o" -> wrap swishOutput "-b" -> validateBase cmd 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 (_,act)) = act validateBase :: String -> Maybe String -> Either (String, SwishStatus) SwishAction validateBase arg Nothing = Right $ SA (arg, swishBase Nothing) validateBase arg (Just b) = case parseURI b >>= qnameFromURI of j@(Just _) -> Right $ SA (arg, swishBase j) _ -> Left ("Invalid base URI <" ++ b ++ ">", SwishArgumentError) ------------------------------------------------------------ -- Interactive test function (e.g. for use in ghci) ------------------------------------------------------------ -- 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 -- |Break list into a list of sublists, separated by element -- satisfying supplied condition. breakAll :: (a -> Bool) -> [a] -> [[a]] breakAll _ [] = [] breakAll p s = let (h,s') = break p s in h : breakAll p (drop 1 s') -- | 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, 2012 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 -- --------------------------------------------------------------------------------