Skip to content
Snippets Groups Projects
Main.hs 5.18 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 
    then enters a loop to process queries on the parsed data.
-}
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 arguments.
  filenames <- getFilenames

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

  -- | Parse the raw text into structured JSON representations and handle errors.
  inputJSONs <- mapM (abortOnError . stringToJSON) rawTexts  -- List of parsed JSON values.

  -- | Keep asking for queries and applying them until the user decides to stop
  processqueries inputJSONs

{-| Function to repeatedly ask for queries and process them.
    It processes each query on the parsed data and either prints the results 
    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
      -- | Print the output
      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."

-- | Function to get a query expression from the user.
--   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.
  -- Supports operators like '=', '>', '<', '>=', '<=','sub'.

parseQuery :: String -> Maybe Transformer
parseQuery queryStr =
  case words queryStr of
    [field, "=", value] -> 
      -- | Check if the value is a quoted string.
      if isQuoted value then
        let strValue = read value :: String
        in Just $ getElements `pipe` select (binaryOp equal (getField field) (string strValue))
      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
        let strValue = read value :: String
        in Just $ getElements `pipe` select (binaryOp containsSubstring (getField field) (string strValue))
      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
  if null args then askForFiles else checkFilesExist args

-- | Function to check if the given list of files exists.
-- If any files do not exist, it prints an error and asks for filenames again.
checkFilesExist :: [String] -> IO [String]
checkFilesExist filenames = do
  fileExistsList <- mapM doesFileExist filenames
  let invalidFiles = [f | (f, False) <- zip filenames fileExistsList]
  if null invalidFiles
    then return filenames
    else do
      putStrLn $ "Error: The following files do not exist: " ++ unwords invalidFiles
      askForFiles

-- | Function to repeatedly ask the user for valid filenames.
-- If invalid files are entered, it will prompt the user to try again.
askForFiles :: IO [String]
askForFiles = do
  putStrLn "Please enter the filenames (separate with spaces):"
  filenames <- words <$> getLine
  checkFilesExist filenames