added description of the functions and classes, added unavailableInputs in requestInput method, change logic of returning in main menu with exception instead of called new main cycle function

This commit is contained in:
ZhigalskiiIvan 2023-03-29 00:28:00 +03:00
parent 7f38f5a358
commit bfcb7a1483

View File

@ -1,5 +1,4 @@
import java.io.File import java.io.File
import java.util.Scanner
import kotlin.system.exitProcess import kotlin.system.exitProcess
import org.jetbrains.letsPlot.* import org.jetbrains.letsPlot.*
import org.jetbrains.letsPlot.geom.geomBar import org.jetbrains.letsPlot.geom.geomBar
@ -7,12 +6,19 @@ import org.jetbrains.letsPlot.intern.Plot
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
val scanner = Scanner(System.`in`)
object StatisticBuilder { object StatisticBuilder {
/**
* Singleton object which gives statistics of saved texts.
*/
fun askAndExecuteSelfCommands() { fun askAndExecuteSelfCommands() {
/**
* Recognizes the name of the text, which
* user want to see statistics about and type of output.
* It calls methods of graphic building or printing data in console,
* build required for it objects.
*/
if (!TextData.haveText()) { if (!TextData.haveText()) {
println("No saved texts") println("No saved texts")
@ -41,13 +47,29 @@ object StatisticBuilder {
} }
private fun buildGraphic(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) { private fun buildGraphic(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
/**
* It builds bar chart with data of mapOfSentenceNumToItsSize.
* textName: name of texts, which user want to see statistics about.
* mapOfSentenceNumToItsSize: map of pairs of sentence numbers and their words count.
*/
val plot: Plot = val plot: Plot =
ggplot(mapOfSentenceNumToItsSize) + ggsize(1000, 600) + geomBar { x = "sentence number"; y = "words count" } ggplot(mapOfSentenceNumToItsSize) + ggsize(1000, 600) + geomBar { x = "sentence number"; y = "words count" }
TODO("вил би сун")
} }
private fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) { private fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
/**
* Prints statistics according to data from mapOfSentenceNumToItsSize.
*
* textName: name of texts, which user want to see statistics about.
* mapOfSentenceNumToItsSize: map of pairs of sentence numbers and their words count.
*/
val sectionTitle = "Text name: $textName" val sectionTitle = "Text name: $textName"
println("-".repeat(sectionTitle.length)) println("-".repeat(sectionTitle.length))
println(sectionTitle) println(sectionTitle)
@ -63,7 +85,9 @@ object StatisticBuilder {
|Statistics: |Statistics:
|[count of sentences]: |[count of sentences]:
|--- $totalSentencesCount |--- $totalSentencesCount
|[total number of words]: |[total number of sentences]:
|--- ${mapOfSentenceNumToItsSize.size}
||[total number of words]:
|--- $totalWordsCount |--- $totalWordsCount
|[num of sentence: count of words in it]: |[num of sentence: count of words in it]:
|--- ${mapOfSentenceNumToItsSize.toList().joinToString("; ") { "${it.first} contains ${it.second}" }}. |--- ${mapOfSentenceNumToItsSize.toList().joinToString("; ") { "${it.first} contains ${it.second}" }}.
@ -77,7 +101,15 @@ object StatisticBuilder {
object TextReader { object TextReader {
/** Singleton object used for reading text from a file or console. */
fun askAndExecuteSelfCommands() { fun askAndExecuteSelfCommands() {
/**
* Asks the user which where they want to read the text from and
* calls special for file- and console- reading methods according the answer.
*/
println("Where do you want to add the text: from the console or a file?") println("Where do you want to add the text: from the console or a file?")
@ -90,10 +122,20 @@ object TextReader {
} }
} }
private fun readFromConsole() { private fun readFromConsole() {
/**
* Read from console the name of text, checks that it hasn't saved yet and its contents,
* asks if entered text is not correct and re-calls itself or
* calls method of adding received text to data.
*/
println("Input name of a text:") println("Input name of a text:")
val name = readln() if (TextData.haveText()) println("Unavailable(existing) names: ${TextData.getTextNamesInString()}.")
val nameRequest = Main.requestInput(unavailableInputs = TextData.getTextNamesList())
nameRequest.first.exe()
val name = nameRequest.second.toString()
println("Input a text content(after input text press enter twice):") println("Input a text content(after input text press enter twice):")
var content = "" var content = ""
@ -123,6 +165,13 @@ object TextReader {
} }
private fun readFromFile() { private fun readFromFile() {
/**
* Asks for the name of text, path to file to read text from,
* checks for its existing, reads contents and,
* asks if entered names is not correct and re-calls itself or
* calls method of adding received text to data.
*/
println("Input a name of a text:") println("Input a name of a text:")
val name = readln() val name = readln()
@ -157,7 +206,15 @@ object TextReader {
} }
} }
private fun addTextToData(textName: String, content: String) { private fun addTextToData(textName: String, content: String) {
/**
* Calls method of TextData class to add new text in set
* of tracked texts.
* textName: name of new text.
* content: content of new text.
*/
TextData.addNewText(textName, content) TextData.addNewText(textName, content)
} }
@ -165,11 +222,25 @@ object TextReader {
object TextData { object TextData {
/**
* Object used for storing data about tracking texts,
* includes methods for work with them.
* textsList: list of monitored texts.
*/
private val textsList = mutableListOf<Text>() private val textsList = mutableListOf<Text>()
fun haveText(): Boolean = !textsList.isEmpty()
fun haveText(): Boolean = textsList.isNotEmpty()
fun removeText() { fun removeText() {
/**
* Method for removing text from watch list. Asks for a name of a text
* and if it's being tracked, remove it from the textsList.
*/
if (!haveText()) { if (!haveText()) {
println("No saved texts") println("No saved texts")
@ -184,29 +255,49 @@ object TextData {
private fun getTextByName(searchingName: String): Text? { private fun getTextByName(searchingName: String): Text? {
/**
* Returns Text object whose name matches searchingName or null
* if there is no text with same name in the textsList.
* searchingName: name of the text to be found.
* return: the text with searchingName name or null.
*/
for (text in textsList) if (text.getName() == searchingName) return text for (text in textsList) if (text.getName() == searchingName) return text
return null return null
} }
private fun getTextsNamesInString(delimiter: String = ", "): String { fun getTextNamesInString(delimiter: String = ", "): String {
/** Returns string with names of tracking texts separated by delimiter. */
return textsList.joinToString(delimiter) { it.getName() } return textsList.joinToString(delimiter) { it.getName() }
} }
private fun getTextsNamesList(): List<String> { fun getTextNamesList(): List<String> {
/** Returns list with names of tracking texts. */
return textsList.map { it.getName() } return textsList.map { it.getName() }
} }
fun addNewText(textName: String, content: String) { fun addNewText(textName: String, content: String) {
/**
* Calls method of textAnalyzer for getting Text object from
* textName and content and adds it in textsList.
*/
textsList.add(TextAnalyzer.getTextObjFromContents(textName, content)) textsList.add(TextAnalyzer.getTextObjFromContents(textName, content))
} }
fun getTextData(message: String = "Input name of a text"): Text { fun getTextData(message: String = "Input name of a text"): Text {
println(message) /**
println("Saved texts: ${getTextsNamesInString()}") * Reads name of the text to be found and returns it text if
* it exists.
* message: message which prints with calling this method.
* return: text
*/
val nameRequest = Main.requestInput(getTextsNamesList()) println(message)
println("Saved texts: ${getTextNamesInString()}.")
val nameRequest = Main.requestInput(getTextNamesList())
nameRequest.first.exe() nameRequest.first.exe()
return getTextByName(nameRequest.second.toString())!! return getTextByName(nameRequest.second.toString())!!
@ -214,11 +305,19 @@ object TextData {
object TextAnalyzer { object TextAnalyzer {
/** Object used for getting text from the string information. */
private val DELIMITERS = Regex("[!?.]+\\s+") private val DELIMITERS = Regex("[!?.]+\\s+")
private val WHITESPACES = Regex("\\s+") private val WHITESPACES = Regex("\\s+")
private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?") private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?")
fun getTextObjFromContents(name: String, content: String): Text { fun getTextObjFromContents(name: String, content: String): Text {
/**
* Receives text as input and splits it by sentence, calculate its lengths,
* create list of Sentence objects and returns Text object, created with
* this list, input field name, and count of sentences in content.
*/
var sentencesCount = 1 var sentencesCount = 1
val listOfSentences = mutableListOf<Text.Sentence>() val listOfSentences = mutableListOf<Text.Sentence>()
@ -245,14 +344,15 @@ object TextData {
private val sentencesCount: Int, private val sentencesCount: Int,
private val sentencesList: List<Sentence>, private val sentencesList: List<Sentence>,
) { ) {
/** Stores information about conjugated text. */
fun getSentencesCount() = sentencesCount
fun getName() = name fun getName() = name
fun getSentencesList() = sentencesList fun getSentencesList() = sentencesList
data class Sentence(private val wordsCount: Int) { data class Sentence(private val wordsCount: Int) {
/** stores information about count of words. */
fun getWordsCount() = wordsCount fun getWordsCount() = wordsCount
} }
} }
@ -261,14 +361,23 @@ object TextData {
fun exit(): Nothing { fun exit(): Nothing {
/** function used for exiting out of program. */
println("bye!") println("bye!")
exitProcess(0) exitProcess(0)
} }
object CommandCenter { object CommandCenter {
/**
* Singleton used for reading commands from console and storing data about
* the functions they should execute.
*/
private enum class Commands(val executingFun: () -> Unit) { private enum class Commands(val executingFun: () -> Unit) {
/**
* Enumerating of available commands names and functions conjugated
* with them.
*/
EXIT(::exit), EXIT(::exit),
ADD_TEXT({ TextReader.askAndExecuteSelfCommands() }), ADD_TEXT({ TextReader.askAndExecuteSelfCommands() }),
SHOW_STATISTICS({ StatisticBuilder.askAndExecuteSelfCommands() }), SHOW_STATISTICS({ StatisticBuilder.askAndExecuteSelfCommands() }),
@ -276,6 +385,10 @@ object CommandCenter {
} }
fun readCommandFromConsole(): () -> Unit { fun readCommandFromConsole(): () -> Unit {
/**
* Prints list of names of available commands, requests the name and
* returns corresponding to entered name function.
*/
println("Input one of the commands: ${Commands.values().joinToString { it.name }}:") println("Input one of the commands: ${Commands.values().joinToString { it.name }}:")
val commandNameRequest = Main.requestInput(Commands.values().map { it.name }) val commandNameRequest = Main.requestInput(Commands.values().map { it.name })
@ -290,36 +403,64 @@ object CommandCenter {
interface InputOutcomeCommand { interface InputOutcomeCommand {
/**
* Interface used to existing commands objects, whose exe value
* stores function which calls after request due the program.
* exe: function returned in a Main.requestInput method for returned to
* main menu of application or continuation of the process.
*/
val exe: () -> Unit val exe: () -> Unit
} }
object ContinueCommand : InputOutcomeCommand { object ContinueCommand : InputOutcomeCommand {
/** If called exe of this object, program continue executing
* without changes.
*/
override val exe: () -> Unit = {} override val exe: () -> Unit = {}
} }
object ReturnCommand : InputOutcomeCommand { object ReturnCommand : InputOutcomeCommand {
/**
* If called exe of this object, a custom ReturnException is thrown
* and process of executing some called command interrupted. Exception
* catches in mainCycle and program command request is repeated.
*/
override val exe: () -> Unit = { override val exe: () -> Unit = {
throw ReturnException() throw ReturnException()
} }
} }
class ReturnException : Exception() {
class InvalidInputTypeException(expectedType: String) : Exception(expectedType) { /** Custom exception being thrown if user want to return to the menu. */
override val message: String = expectedType
init {
"Was expected type: $message, but it's impossible to convert input in it."
}
} }
class InvalidElemInInputException : Exception() class InvalidInputTypeException : Exception() {
/**
* Custom exception being thrown if input can't be converted
* to the requested type.
*/
}
class ReturnException : Exception() class InvalidElemInInputException : Exception() {
/**
* Custom exception being thrown if input doesn't match the list
* of possible values.
*/
}
object Main { object Main {
/**
* Object which contains request processing method and working body of
* application.
*/
fun workCycle() { fun workCycle() {
/**
* Method repeating the process of calling functions returned by
* CommandCenter. If ReturnException was thrown, it is caught here,
* last iteration breaks and new one is called.
*/
mainCycle@ while (true) { mainCycle@ while (true) {
try { try {
(CommandCenter.readCommandFromConsole())() (CommandCenter.readCommandFromConsole())()
@ -330,7 +471,21 @@ object Main {
} }
} }
inline fun <reified T> requestInput(availableInputs: List<T>? = null): Pair<InputOutcomeCommand, Any> { inline fun <reified T> requestInput(
availableInputs: List<T>? = null,
unavailableInputs: List<T>? = null
): Pair<InputOutcomeCommand, Any> {
/**
* Method reads from console input, check if it isn't available, repeat the request from the console
* in this case and convert input in type T if possible, else throws an exception and repeat the
* request.
*
* availableInputs: list of available values, the choice among which
* is requested from the console at some stage of the program.
* unavailableInputs: list of unavailable values.
* return: a pair of function, which could be called after the request from this function to return
* if user want it or continue and value that the user has selected from the list of available.
*/
var inputT: Any var inputT: Any
@ -350,19 +505,33 @@ object Main {
typeOf<Char>() -> { typeOf<Char>() -> {
if (input.trim().length == 1) { if (input.trim().length == 1) {
input.trim().toCharArray()[0] input.trim().toCharArray()[0]
} else throw InvalidInputTypeException(typeOf<Char>().toString()) } else throw InvalidInputTypeException()
} }
typeOf<String>() -> input typeOf<String>() -> input
else -> throw InvalidInputTypeException(typeOf<T>().toString()) else -> throw InvalidInputTypeException()
} }
return if (availableInputs != null) { return when {
if (inputT.toString() in availableInputs availableInputs != null && unavailableInputs != null -> {
.map { it.toString() } if (inputT.toString() in availableInputs.map { it.toString() } &&
) Pair(ContinueCommand, inputT) inputT.toString() !in unavailableInputs.map { it.toString() }) Pair(ContinueCommand, inputT)
else throw InvalidElemInInputException() else throw InvalidElemInInputException()
} else Pair(ContinueCommand, inputT) }
availableInputs != null -> {
if (inputT.toString() in availableInputs.map { it.toString() }) Pair(ContinueCommand, inputT)
else throw InvalidElemInInputException()
}
unavailableInputs != null -> {
if (inputT.toString() !in unavailableInputs.map { it.toString() }) Pair(ContinueCommand, inputT)
else throw InvalidElemInInputException()
}
else -> Pair(ContinueCommand, inputT)
}
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
continue@readingAndChangingTypeCycle continue@readingAndChangingTypeCycle