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\$
1
  • 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\$ Commented Oct 12, 2015 at 11:00

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.