2
\$\begingroup\$

I've been learning Scala and I'd love to get your feedback in order to improve. As an exercise I made a simple framework for creating command-line user interfaces.

Gist

package com.ronyhe

import java.util.Scanner

import scala.collection.Set

/** A framework for creating command-line user interfaces */
package object cmd {

  /**
   * Present a description to the user and wait for a response
   *
   * This is the basic unit of interacting with the user
   *
   * @return the text the user typed as a response
   */
  def textInput(description: String): String = {
    println(description)
    getUserInput
  }

  /**
   * Pose a yes or no question to the user.
   *
   * This method adds a short explanation of the expected input to the description.
   * Example: "Do you like Scala?" => "Do you like Scala? (y\n)"
   *
   * @return true if the user answered yes, false otherwise
   */
  def yesOrNo(description: String): Boolean = {
    import Strings.{InputsMeaningNo, InputsMeaningYes, YesOrNoSuffix}

    val input = textInput(description + YesOrNoSuffix)

    val isYes = InputsMeaningYes contains input
    val isNo = InputsMeaningNo contains input

    if (isYes)
      true
    else if (isNo)
      false
    else {
      ErrorReporting.inputIsNotYesOrNo()
      yesOrNo(description)
    }

  }

  /**
   * Present the user with options to select from, with a limited number of selections allowed
   *
   * This method adds a short explanation of the expected input to the description if needed.
   * Example: "Select the relevant options" =>
   *  "Select the relevant options" + "\nType the numbers of the desired options separated by commas: '1, 2, 3'"
   *
   * @param description the instruction presented to the user
   * @param options the options to display to the user
   * @param numberOfSelectionsAllowed the maximum amount of options the user can choose.
   * @return a Set[Int] with the indexes of the options the user selected
   */
  def selection(description: String, options: Seq[String], numberOfSelectionsAllowed: Int): Set[Int] = {
    val amountOfOptions = options.length
    require(numberOfSelectionsAllowed > 0 && numberOfSelectionsAllowed <= amountOfOptions)

    val textFunction = numberOfSelectionsAllowed match {
      case 1 => Strings.singleSelectionsText _
      case _ => Strings.multiSelectionText _
    }
    val text = textFunction(description, options)
    val input = textInput(text)

    def tryAgain = selection(description, options, numberOfSelectionsAllowed)

    def splitToIndexes(commaSeparatedListOfInts: String) =
      commaSeparatedListOfInts.split(',').map(_.trim).map(_.toInt - 1).toSet

    val parsed: Set[Int] = try splitToIndexes(input)
      catch {
    case _: Exception =>
      ErrorReporting.inputIsNotCommaSeparatedListOfIntegers()
      tryAgain
      }

    val correctAmount = parsed.nonEmpty && parsed.size <= numberOfSelectionsAllowed
    if (!correctAmount) {
      ErrorReporting.selectedOptionIsOutOfBounds(amountOfOptions)
      return tryAgain
    }

    val allSelectionsAreInBounds = parsed forall { i => i >= 0 && i < amountOfOptions }
    if (!allSelectionsAreInBounds) {
      ErrorReporting.selectedOptionIsOutOfBounds(amountOfOptions)
      return tryAgain
    }

    parsed
  }

  /** Present the user with options to select from */
  def multiSelection(description: String, options: Seq[String]): Set[Int] =
    selection(description, options, options.length)

  /** Present the user with options to select from. Allow only one option to be selected */
  def singleSelection(description: String, options: Seq[String]): Int = selection(description, options, 1).head

  private val scanner = new Scanner(System.in)

  private def print(x: Any) = System.out.print(x)
  private def println(x: Any) = System.out.println(x)

  private def getUserInput = {
    print(Strings.Prompt)
    scanner.nextLine()
  }

  private object ErrorReporting {
    import Strings.ErrorMessages

    def invalidAmountOfOptions(maxAllowed: Int) = {
      val message = ErrorMessages.invalidAmountOfOptionsSelected(maxAllowed)
      println(message)
    }

    def inputIsNotAnInteger() = println(ErrorMessages.NotAnInteger)

    def selectedOptionIsOutOfBounds(amountOfOptions: Int) = {
      val message = ErrorMessages.numberNotInRange(amountOfOptions)
      println(message)
    }

    def inputIsNotYesOrNo() = println(ErrorMessages.InputIsNotYesOrNo)

    def inputIsNotCommaSeparatedListOfIntegers() = println(ErrorMessages.MultiSelectionCouldNotBeParsed)

  }

}
\$\endgroup\$
  • 2
    \$\begingroup\$ Welcome to Code Review! You mentioned you're learning Scala, so if you consider yourself a beginner then feel free to edit the beginner tag onto your question so people can review accordingly. \$\endgroup\$ – SuperBiasedMan Oct 12 '15 at 11:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.