Skip to content

Instantly share code, notes, and snippets.

@ronyhe ronyhe/Strings.scala
Last active Oct 12, 2015

Embed
What would you like to do?
A simple framework for creating command-line user interfaces
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)
}
}
package com.ronyhe.cmd
private[cmd] object Strings {
val Prompt = ">>> "
val Tab = "\t"
val Space = " "
val NewLine = {
val Default = "\n"
val platformValue = try System.getProperty("line.separator") catch {
case _: Exception => Default
}
if (platformValue == null)
Default
else
platformValue
}
val YesOrNoSuffix = """ (y\n)"""
val InputsMeaningYes = List("yes", "y")
val InputsMeaningNo = List("no", "n")
private val MultiSelectionInstruction =
"Type the numbers of the desired options separated by commas. Example: '1, 2, 3'"
private val MultiSelectionSuffix = s"$NewLine($MultiSelectionInstruction)"
def singleSelectionsText(description: String, options: Seq[String]): String =
description + NewLine + selectionOptionsText(options)
def multiSelectionText(description: String, options: Seq[String]): String =
description + NewLine + selectionOptionsText(options) + MultiSelectionSuffix
private def selectionOptionsText(options: Seq[String]) =
options.view.zipWithIndex.map(t => s"$Tab${t._2+1} - ${t._1}").mkString(NewLine)
object ErrorMessages {
def invalidAmountOfOptionsSelected(maxAllowed: Int): String = prefix(s"Please select up to $maxAllowed options")
private def prefix(s: String) = "Error: " + s
val NotAnInteger = prefix("Expected an integer" + Strings.NewLine)
val InputIsNotYesOrNo = prefix("Unexpected input. Type 'y' for yes or 'n' for no")
def numberNotInRange(max: Int) =
prefix(s"Expected integers between one and $max. (Both inclusive)" + Strings.NewLine)
val MultiSelectionCouldNotBeParsed = prefix(MultiSelectionInstruction)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.