Skip to content
Snippets Groups Projects
Main.hs 5.19 KiB
Newer Older
Robert Atkey's avatar
Robert Atkey committed
import System.Environment (getArgs)
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
import System.Directory (doesFileExist)
Robert Atkey's avatar
Robert Atkey committed
import JSON
import JSONInput
import JSONOutput
import JSONTransformer
import Result

Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
{-| Main function that It gets filenames from the command line, reads and parses the JSON data and 
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
    then enters a loop to process queries on the data.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
-}
Robert Atkey's avatar
Robert Atkey committed
main :: IO ()
main = do
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  -- | Get the JSON filename to read from the command line.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  filenames <- getFilenames

  -- | Read the raw data in from the files.
  rawTexts <- mapM readFile filenames

Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  -- | Parse the raw text and handle errors.
  inputJSONs <- mapM (abortOnError . stringToJSON) rawTexts  
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed

Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  -- | Keep asking for queries and applying them until users types anything but y.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  processqueries inputJSONs

{-| Function to repeatedly ask for queries and process them.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
    It processes each query on the data and either prints the results 
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
    or prompts the user to enter a new query if no results are found.
-}
processqueries :: [JSON] -> IO ()
processqueries inputJSONs = do
  query <- getQuery

  -- Run the query and collect the output.
  let outputJSONs = concatMap query inputJSONs 

  if null outputJSONs
    then do
      putStrLn "No results found."
      -- Ask the user for another query
      processqueries inputJSONs
    else do
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
      -- Print the output
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
      mapM_ (putStrLn . renderJSON) outputJSONs
      putStrLn "Do you want to enter another query (y/n)"
      continue <- getLine
      if continue == "y" || continue == "Y"
        then processqueries inputJSONs  
        else putStrLn "Exiting Program."

Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
-- | Function to get a query from the user.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
--   It loops until a valid query is entered by the user.
getQuery :: IO Transformer
getQuery = do
  getValidQuery

-- | Helper function to repeatedly prompt the user until a valid query is entered.
getValidQuery :: IO Transformer
getValidQuery = do
  putStrLn "Enter the query expression for example (Height > 1000 or Country = S)"
  queryStr <- getLine
  let query = parseQuery queryStr
  case query of
    Nothing -> do
      putStrLn "Invalid query format. Please use 'field operator value' for example (Height > 1000)"
      getValidQuery  
    Just transformer -> return transformer  

-- | Function to manually parse query expressions into a transformer.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
-- Supports operators like '=', '>', '<', '>=', '<=','sub'.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
parseQuery :: String -> Maybe Transformer
parseQuery queryStr =
  case words queryStr of
    [field, "=", value] -> 
      -- | Check if the value is a quoted string.
      if isQuoted value then
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
        let stringv = read value :: String
        in Just $ getElements `pipe` select (binaryOp equal (getField field) (string stringv))
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
      else
        case reads value of
          [(v, "")] -> Just $ getElements `pipe` select (binaryOp equal (getField field) (integer v))
          _         -> Nothing

    [field, ">", value] -> 
      case reads value of
        [(v, "")] -> Just $ getElements `pipe` select (binaryOp greaterThan (getField field) (integer v))
        _         -> Nothing

    [field, "<", value] -> 
      case reads value of
        [(v, "")] -> Just $ getElements `pipe` select (binaryOp lessThan (getField field) (integer v))
        _         -> Nothing

    [field, ">=", value] -> 
      case reads value of
        [(v, "")] -> Just $ getElements `pipe` select (binaryOp greaterEqual (getField field) (integer v))
        _         -> Nothing

    [field, "<=", value] -> 
      case reads value of
        [(v, "")] -> Just $ getElements `pipe` select (binaryOp lessEqual (getField field) (integer v))
        _         -> Nothing

    [field, "sub", value] -> 
      if isQuoted value then
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
        let stringv = read value :: String
        in Just $ getElements `pipe` select (binaryOp containsSubstring (getField field) (string stringv))
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
      else
        case reads value of
          [(v, "")] -> Just $ getElements `pipe` select (binaryOp equal (getField field) (integer v))
          _         -> Nothing

    _ -> Nothing  

-- | Helper function to check if a string is quoted
isQuoted :: String -> Bool
isQuoted ('"':xs) | last xs == '"' = True
isQuoted _ = False

-- | Function to safely get the filenames, with error handling.
-- It checks whether the files exist and prompts the user if any files are invalid.
getFilenames :: IO [String]
getFilenames = do
  args <- getArgs
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  if null args then askfiles else checkFilesExist args
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed

-- | Function to check if the given list of files exists.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
-- If any files do not exist it prints an error and asks for filenames again.
-- zip puts two seperate lists into one this list has a filename and true or false if doesnt exists 
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
checkFilesExist :: [String] -> IO [String]
checkFilesExist filenames = do
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  list <- mapM doesFileExist filenames
  let invalidFiles = [f | (f, False) <- zip filenames list]
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  if null invalidFiles
    then return filenames
    else do
      putStrLn $ "Error: The following files do not exist: " ++ unwords invalidFiles
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
      askfiles
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed

-- | Function to repeatedly ask the user for valid filenames.
-- If invalid files are entered, it will prompt the user to try again.
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
-- words allow me to get the filename for each space
askfiles :: IO [String]
askfiles = do
Haroon Akhter CS2022's avatar
Haroon Akhter CS2022 committed
  putStrLn "Please enter the filenames (separate with spaces):"
  filenames <- words <$> getLine
  checkFilesExist filenames