ylliX - Online Advertising Network
Vertex AI - Antrophic and Mistral models: Why does it require Imegen access?

Scala missing values in for loop


I need to write a CLI for the find command in scala3 for school. The syntax is: run [path] [--filterName filterParameter]. I currently have it working for one filter at a time, but once it try the command with two or more filters, only the last one takes effect.

So for example, if I run: run src/ --name main I get the output src/main and src/main/scala/find/cli/main.scala, which is correct.

And if I run: run src/ --type scala I get all the scala files correctly.

But if I run: run src/ --name main --type scala, instead of getting src/main/scala/find/cli/main.scala I get the same output as running run src/ --type scala. And if I invert the filters, so run: run src/ --type scala --name main I get the same output as running: run src/ --name main.

cli.scala :

package find
package cli

import scala.collection.mutable
import scala.util.matching.Regex

val usageMap: mutable.LinkedHashMap[String, String] = mutable.LinkedHashMap(
  "name" -> "[--name X] (where X is a string)",
  "type" -> "[--type X] (where X is a string)",
  "size" -> "[--size -Xc|--size Xc|--size +Xc] (where X is a number)",
  "count" -> "[--count X] (where X is an integer)",
  "minDepth" -> "[--minDepth X] (where X is an integer)",
  "maxDepth" -> "[--maxDepth X] (where X is an integer)",
  "mode" -> "[--mode X] (where X is an element of: (and, or, xor). If the mode filter is not given, \"--mode and\" will be used by default)",
  "contains" -> "[--contains X] (where X is a string)",
  "not" -> "[--not F V] (where F is a filter name and V is it's value)",
  "help" -> "[--help X] (where X is optional and a filter name)"
)
      
val SIZE_RE = "size ([+-]?)([0-9]+)c".r
val INT_RE = "([0-9]+)".r
val DEPTH_RE = "(min|max)Depth (.*)".r

var path = ""
var file: cs214.Entry = cs214.MockFile("zef", None, 2)

var failMsg = ""
var minDepth = -1
var maxDepth = -1
var fileCount = -1
var findMode = "and"

/** Command-line interface for the `find` program.
  *
  * Note: this function is provided to you and you do not need to understand how
  * it works yet.
  *
  * @param args
  *   the command-line arguments.
  */
@main def cs214find(args: String*) =
  entryPoint(args)(cs214.open)

def entryPoint(args: Seq[String])(open: String => cs214.Entry): Boolean =
  if args.length < 1 then
    return fail("No path argument given.")

  path = args.head
  val expr = args.tail

  path match
    case "--help" =>
      if expr.isEmpty then
        println("Usages:\n * run [path] " + usageMap("name"))
        usageMap.foreach((filter, usage) => if filter != "name" then println(" " * 14 + usage))
        println(
          usageMap.filter((filter, _) => filter != "help")
            .map((filter, usage) => usage.split("\\(where")(0))
            .toList.foldLeft(" * run [path] ")((res, u) => res + u)
        )
      else if expr.length > 1 then fail(createFailMsg(error = "help takes at most 1 parameter", filter = "help"))
      else
        if !usageMap.contains(expr.head) then fail(createFailMsg(error = f"${expr.head} is not a valid filter"))
        if expr.head.nonEmpty then println("Usage: run [path] " + usageMap(expr.head))
      false
    case _ => 
      file =
        try
          open(path)
        catch
          case _: java.nio.file.NoSuchFileException =>
            return fail(createFailMsg(error = f"Path '$path' does not exist."))
          case e: Exception =>
            return fail(createFailMsg(error = f"Open raised an exception: ${e.getMessage}."))

      var results: List[List[String]] = List()
      var filters = args.mkString(" ").split("--")
      filters(0) = filters(0).replaceAll(path + " ", "")

      // Check for duplicate filters
      filters = filters.filter(f => f.nonEmpty)
      filters
        .flatMap(filter => List(filter.trim.split(" ")(0)))
        .groupBy(identity).map((f, c) => (f, c.length))
        .filter(_._2 > 1)
        .foreach(duplicate => failMsg += f"Cannot have duplicate filter: ${duplicate._1}\n")
      
      if failMsg.nonEmpty then fail(failMsg + "To check the proper usage, use: run --help")
      else
        if filters.isEmpty then showResults(find(file, _ => true))
        else
          filters.filterNot(f => f.matches("-?(name|type|size)\\s?.*")).foreach(filter => getFilterData(filter))
          for filter <- filters.filter(f => f.matches("-?(name|type|size)\\s?.*")) yield
            getFilterCondition(filter) match
              case Nil => None
              case l => results = l :: results

          if failMsg.nonEmpty then fail(failMsg)
          else if results.length == 1 then showResults(results.head)
          else
            showResults(
              findMode match
                case "and" => results.flatten.distinct.filter(elem => results.forall(_.contains(elem)))
                //case "or"  => conditionValues.flatten.distinct
                //case "xor" => conditionValues.reduce(_^_)
                case _     => List()
            )

def showResults(res: List[String]): Boolean =
  for f <- res yield println(f)
  res.nonEmpty

def getFilterData(filter: String) =
  filter match
    case s"count $count" => 
      INT_RE.findFirstIn(count) match
        case None => failMsg += createFailMsg(filter = "count")
        case Some(value) => fileCount = value.toInt
    case DEPTH_RE(minOrMax, depth) => 
      INT_RE.findFirstIn(depth) match
        case None => failMsg += createFailMsg(filter = s"${minOrMax}Depth")
        case Some(value) => if minOrMax == "min" then minDepth = value.toInt else maxDepth = value.toInt
    case s"mode $mode" => 
      if !List("and", "or", "xor").contains(mode) then fail(createFailMsg(filter = "mode"))
      else findMode = mode
    case s"not$p" => ???
    case filter =>
      if filter.startsWith("-") then failMsg += f"Error: $filter is not a valid filter\nUsage:\n * Use run --help to check proper usage\n * Try running this instead: run $path -$filter\n"
      else if usageMap.contains(filter.split(" ")(0)) then failMsg += createFailMsg(error = f"$filter is missing it's required parameter", filter = filter)
      else failMsg += createFailMsg(error = f"$filter is not a valid filter")
      
def getFilterCondition(filter: String): List[String] =
  filter match
    case s"name $name" => find(file, (entry: cs214.Entry) => isValidName(entry, name))
    case s"type $extension" => find(file, (entry: cs214.Entry) => !entry.isDirectory() && entry.name().split("\\.")(1) == `extension`)
    case s"size $size" => 
      SIZE_RE.findFirstMatchIn(size) match
        case None =>
          failMsg += createFailMsg(filter = "size")
          List()
        case Some(matched) => 
          val value = matched.group(2).toLong
          matched.group(1) match
            case "+" => find(file, (entry: cs214.Entry) => !entry.isDirectory() && entry.size() >= value)
            case "-" => find(file, (entry: cs214.Entry) => !entry.isDirectory() && entry.size() <= value)
            case "" => find(file, (entry: cs214.Entry) => !entry.isDirectory() && entry.size() == value)
    case filter =>
      if filter.startsWith("-") then failMsg += f"Error: $filter is not a valid filter\nUsage:\n * Use run --help to check proper usage\n * Try running this instead: run $path -$filter\n"
      else if usageMap.contains(filter.split(" ")(0)) then failMsg += createFailMsg(error = f"$filter is missing it's required parameter", filter = filter)
      else failMsg += createFailMsg(error = f"$filter is not a valid filter")
      List()

def isValidName(entry: cs214.Entry, name: String): Boolean =
  def wildcard(wildName: List[Char], wildEntry: List[Char]): Boolean =
    (wildName, wildEntry) match
      case (Nil, Nil)                 => true
      case (Nil, _)                   => false
      case ('?' :: ntail, _ :: etail) => wildcard(ntail, etail)
      case ('*' :: ntail, Nil)        => wildcard(ntail, wildEntry)
      case ('*' :: ntail, _ :: etail) => wildcard(ntail, wildEntry) || wildcard(wildName, etail)
      case (_ :: ntail, Nil)          => false
      case (n :: ntail, e :: etail)   => n == e && wildcard(ntail, etail)
  
  if name.contains('*') || name.contains('?') then
    (entry.isDirectory() && wildcard(name.toList, entry.name().toList)) || 
    (!entry.isDirectory() && wildcard(name.toList, entry.name().split("\\.").head.toList))
  else
    (entry.isDirectory() && entry.name() == name) || (!entry.isDirectory() && entry.name().split("\\.").head == name)

def createFailMsg(error: String = "", filter: String = ""): String =
  if filter.nonEmpty then
    if error.nonEmpty then f"Error: $error\nTo check the proper usage, use: run --help $filter"
    else f"Error: Incorrect Usage of $filter\nUsage: run $path ${usageMap(filter).replace("[", "").replace("]", "")}"
  else if filter.isEmpty && error.nonEmpty then f"Error: $error\nTo check the proper usage, use: run --help"
  else ""

def fail(msg: String): Boolean =
  System.err.println(msg)
  false

find.scala :

package find

def find(entry: cs214.Entry, f: cs214.Entry => Boolean): List[String] =
  var res: List[String] = List()
  if f(entry) then res = List(entry.path())
  if entry.isDirectory() && entry.hasChildren() then res = find(entry.firstChild(), f) ::: res
  if entry.hasNextSibling() then res = find(entry.nextSibling(), f) ::: res
  res.reverse

The results seem to be lost around here in cli.scala:

for filter <- filters.filter(f => f.matches("-?(name|type|size)\\s?.*")) yield
  getFilterCondition(filter) match  <-- results lost
    case Nil => None
  case l => results = l :: results

And I do not understand how and why.

Any help would be greatly appreciated.

P.S: Some parts are not finished such as the implementation of the not filter and the depth search, this is normal.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *