From 396df4099864ded721f91f527fa5518bd2703d60 Mon Sep 17 00:00:00 2001 From: ZhigalskiiIvan <671342i@gmail.com> Date: Fri, 31 Mar 2023 22:47:13 +0300 Subject: [PATCH] Added function for searching similar free name for text and rewrote documentation. --- src/main/kotlin/Main.kt | 328 +++++++++++++++++++++++----------------- 1 file changed, 188 insertions(+), 140 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index c80cf69..9cc78b8 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -11,15 +11,17 @@ import com.google.gson.GsonBuilder import java.io.FileReader -val WHITESPACES = Regex("\\s+") +private val DELIMITERS = Regex("[!?.]+\\s+") +private val WHITESPACES = Regex("\\s+") +private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?") -/** Builds bar chart with data of mapOfSentenceNumToItsSize and saves image in file. - * @param textName name of texts, which user want to see statistics about. - * @param mapOfSentenceNumToItsSize map of pairs of sentence numbers and their words count. +/** + * Builds bar chart with data of listOfSentenceSizes and saves image in file. + * @param textName name of text, which user want to see statistics about. + * @param listOfSentenceSizes list of sentences words count. */ fun buildGraphic(textName: String, listOfSentenceSizes: List) { - val data = mapOf("words count" to listOfSentenceSizes.map { it.toFloat() }) val fig = ggplot(data) + @@ -39,7 +41,8 @@ fun buildGraphic(textName: String, listOfSentenceSizes: List) { fig.show() } -/** Prints statistics according to data from mapOfSentenceNumToItsSize. +/** + * Prints statistics according to data from mapOfSentenceNumToItsSize. * @param textName name of texts, which user want to see statistics about. * @param mapOfSentenceNumToItsSize map of pairs of sentence numbers and their words count. */ @@ -69,15 +72,15 @@ fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map init { + // Initialization of previously saved and saved in JSON file texts textsList = JSONReaderWriter.readSavedTexts() } - private fun haveText(): Boolean = textsList.isNotEmpty() - /** 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. + /** + * Removes text with name from watch list if it was there previously. + * @param name the name of text to be deleted. */ fun removeText(name: String) { @@ -151,114 +155,114 @@ class TextData { println("Text ${removingText.getName()} removed.") JSONReaderWriter.removeFromDirectory(name) } + } + + /** + * Calls method of textAnalyzer for getting Text object from + * textName and content, adds it in textsList and calls method of writing it in JSON. + * @param textName name of text to be added. + * @param content content of text. + */ + fun addNewText(textName: String, content: String) { + val newText = getTextObjFromContents(similarAvailableName(textName), content) + textsList.add(newText) + JSONReaderWriter.writeInJSON(newText) + } + + /** + * If exists text in textList with name textName, chooses similar to it name with addition "(*)". + * @param textName the name to check for a match. + * @return name similar to textName. + */ + private fun similarAvailableName(textName: String): String { + val existingNames = textsList.map { it.getName() } + if (textName !in existingNames) return textName + + val nameRegex = Regex("$textName\\(\\d+\\)") + val busyNumbers = mutableSetOf() + for (name in existingNames) { + if (name.matches(nameRegex)) + busyNumbers.add(name.substring(name.indexOfLast { it == '(' }, name.indexOfFirst { it == ')' }).toInt()) + } + + var minimalAvailable = 1 + while (true) { + if (minimalAvailable in busyNumbers) minimalAvailable++ + else break + } + + return "$textName($minimalAvailable)" } - /** Returns Text object whose name matches searchingName or null + /** + * Returns Text object from textList whose name matches searchingName or null * if there is no text with same name in the textsList. * @param searchingName name of the text to be found. * @return the text with searchingName name or null. */ fun getTextByName(searchingName: String): Text? { - - for (text in textsList) if (text.getName() == searchingName) return text + textsList.forEach { if (it.getName() == searchingName) return it } return null } /** @return string with names of tracking texts separated by delimiter. */ - fun getTextNamesInString(delimiter: String = ", "): String { - return textsList.joinToString(delimiter) { it.getName() } - } - + fun getTextNamesInString(delimiter: String = ", "): String = + textsList.joinToString(delimiter) { it.getName() } /** @return list with names of tracking texts. */ - fun getTextNamesList(): List { - return textsList.map { it.getName() } - } - - - /** Calls method of textAnalyzer for getting Text object from - * textName and content and adds it in textsList. - */ - fun addNewText(textName: String, content: String) { - val newText = TextAnalyzer.getTextObjFromContents(textName, content) - textsList.add(newText) - JSONReaderWriter.writeInJSON(newText) - } + fun getTextNamesList(): List = textsList.map { it.getName() } + /** Uses for writing and reading Text objects in JSON. */ private object JSONReaderWriter { private val gsonPretty: Gson = GsonBuilder().setPrettyPrinting().create() private val savedTextsDirectory: File = File("savedTexts") private val savedTextsFiles: Array init { + // initialization or creation directory with JSON files where Text objects were written. if (!savedTextsDirectory.exists()) savedTextsDirectory.mkdir() - savedTextsFiles = - savedTextsDirectory.listFiles { _, filename -> - filename.split(".").last() == "json" - } ?: emptyArray() + savedTextsFiles = savedTextsDirectory.listFiles { _, filename -> + filename.split(".").last() == "json" + } ?: emptyArray() } - fun removeFromDirectory(name: String) { + /** + * Removes file with name from directory with saved texts. + * @param name name of file to be deleted. + */ + fun removeFromDirectory(name: String) = savedTextsFiles.find { it.name == name }?.delete() ?: println("No file with name $name in directory") - } + /** + * Writes in JSON file text. + * @param text Text object to be written. + */ fun writeInJSON(text: Text) { val jsonFileText = gsonPretty.toJson(text) - val jsonFile = File("${savedTextsDirectory.path}/${text.getName()}.json") + jsonFile.createNewFile() jsonFile.writeText(jsonFileText) } - fun readFromJSON(jsonFile: File): Text { - return gsonPretty.fromJson(FileReader(jsonFile), Text::class.java) - } - - fun readSavedTexts(): MutableSet { - val textsList = mutableListOf() - for (file in savedTextsFiles) { - textsList.add(readFromJSON(file)) - } - return textsList.toMutableSet() - } - - } - - /** Object used for getting text from the string information. */ - private object TextAnalyzer { - - private val DELIMITERS = Regex("[!?.]+\\s+") - private val WHITESPACES = Regex("\\s+") - private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?") + /** + * @param jsonFile file to read from text object. + * @return Text object read from JSON file. + */ + fun readFromJSON(jsonFile: File): Text = gsonPretty.fromJson(FileReader(jsonFile), Text::class.java) /** - * 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. + * Calls method of reading from JSON for each json file from directory with saved texts. + * @return set of Text objects */ - fun getTextObjFromContents(name: String, content: String): Text { - - var sentencesCount = 1 - val listOfSentences = mutableListOf() - - val sentencesStringList = content - .split(DELIMITERS) - .map { it.replace(WHITESPACES, " ") }.toMutableList() - sentencesStringList.removeIf { it.matches(WHITESPACES_OR_EMPTY) } - - for (sentenceText in sentencesStringList) { - val wordsList = sentenceText.split(WHITESPACES).toMutableList() - wordsList.removeIf { it.matches(WHITESPACES_OR_EMPTY) } - sentencesCount++ - listOfSentences.add(Text.Sentence(wordsList.size)) - } - - return Text(name, sentencesCount, listOfSentences.toList()) + fun readSavedTexts(): MutableSet { + val textsList = mutableListOf() + for (file in savedTextsFiles) textsList.add(readFromJSON(file)) + return textsList.toMutableSet() } - } @@ -268,7 +272,6 @@ class TextData { private val sentencesCount: Int, private val sentencesList: List, ) { - fun getName() = name fun getSentencesList() = sentencesList @@ -281,31 +284,58 @@ class TextData { } -/** Function used for exiting out of program. */ -fun exit(): Nothing { - println("bye!") - exitProcess(0) +/** + * 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. + * @param name name of text. + * @param content string content of text. + * @return Text object + */ +private fun TextData.getTextObjFromContents(name: String, content: String): TextData.Text { + + var sentencesCount = 1 + val listOfSentences = mutableListOf() + + val sentencesStringList = content + .split(DELIMITERS) + .map { it.replace(WHITESPACES, " ") }.toMutableList() + sentencesStringList.removeIf { it.matches(WHITESPACES_OR_EMPTY) } + + for (sentenceText in sentencesStringList) { + val wordsList = sentenceText.split(WHITESPACES).toMutableList() + wordsList.removeIf { it.matches(WHITESPACES_OR_EMPTY) } + sentencesCount++ + listOfSentences.add(TextData.Text.Sentence(wordsList.size)) + } + + return TextData.Text(name, sentencesCount, listOfSentences.toList()) } -/** Reads commands from the console and storing data about - * the functions they should execute. +/** + * Parses list of Strings in commands and its arguments, + * checks the correctness of the entered data and calls related functions. */ class CommandCenter(private val textData: TextData) { - + /** + * ArgParser object which can distribute arguments + * over declared options and subcommands. + */ private val parser = ArgParser( "Text Analyzer", useDefaultHelpShortName = true, strictSubcommandOptionsOrder = false ) - + /** Subcommands initialization */ private val addNewText = Add() private val showStatistics = ShowStatistics() private val showTextList = ShowTextsList() private val removeTexts = RemoveTexts() + /** Calls function of removing texts from data. */ private inner class RemoveTexts : Subcommand("remove", "Removing text from saved.") { private val removingList by argument( ArgType.String, "removing list", @@ -328,14 +358,19 @@ class CommandCenter(private val textData: TextData) { } } + /** Prints list of saved texts in console. */ private inner class ShowTextsList : Subcommand("list", "Showing tracking texts.") { + + private fun printTextListInConsole() = println("Saved texts list: ${textData.getTextNamesInString(", ")}") + + override fun execute() { - println("Saved texts list: ${textData.getTextNamesInString(", ")}") + printTextListInConsole() } } - - private inner class ShowStatistics : Subcommand("stat", "Showing statistics.") { + /** Calls functions of graphic building or showing statistics in console. */ + private inner class ShowStatistics : Subcommand("stat", "Showing statistics") { private val textNames by argument( ArgType.String, "text names", @@ -347,18 +382,22 @@ class CommandCenter(private val textData: TextData) { "Choice where to show the statistics" ).default("console") - override fun execute() { - val availableSelectedTextNames = mutableListOf() + private fun selectAvailableInputFromInputted(): List { + val availableInputs = mutableListOf() + textNames.forEach { - if (it in textData.getTextNamesList()) availableSelectedTextNames.add(it) + if (it in textData.getTextNamesList()) availableInputs.add(it) else println("No text $it in data") } + return availableInputs + } + private fun callStatisticsShowingForEach(textNames: List) { when (outputLocation) { - "graphic" -> availableSelectedTextNames.forEach { callBuildingGraphic(it) } - "console" -> availableSelectedTextNames.forEach { callConsoleStatisticsShowing(it) } - "both" -> availableSelectedTextNames.forEach { + "graphic" -> textNames.forEach { callBuildingGraphic(it) } + "console" -> textNames.forEach { callConsoleStatisticsShowing(it) } + "both" -> textNames.forEach { callBuildingGraphic(it) callConsoleStatisticsShowing(it) } @@ -377,65 +416,74 @@ class CommandCenter(private val textData: TextData) { val mapOfSentenceNumToItsSize = text.getSentencesList().associate { counter++ to it.getWordsCount() } printStatisticsInConsole(textName, mapOfSentenceNumToItsSize) } + + + override fun execute() { + val availableSelectedTextNames = selectAvailableInputFromInputted() + callStatisticsShowingForEach(availableSelectedTextNames) + } } - + /** Calls functions of reading and adding text in saved. */ private inner class Add : Subcommand("add", "Adding text from source") { + private val path by option( ArgType.String, "source path", "s", "Inputting path to file for reading or \"console\" to reading from console" ).default("console") - private var textName by argument( + + private val textName by argument( ArgType.String, "name", "Specifies the name of the new text" ) - override fun execute() { - if (textName == "") textName = "untitled" - when { - path == "console" -> callReadingFromConsole() - !File(path).exists() -> throw ReturnException("No file at $path") - else -> callReadingFromFile() - } + private fun callReading() = when { + path == "console" -> callReadingFromConsole() + !File(path).exists() -> throw IncorrectPathException(path) + else -> callReadingFromFile() } - private fun callReadingFromConsole() = readFromConsole(textData, textName) + private fun callReadingFromConsole() = readFromConsoleAndAddToData(textData, textName) - private fun callReadingFromFile() = readFromFile(textData, textName, path) + private fun callReadingFromFile() = readFromFileAndAddToData(textData, textName, path) + + + override fun execute() = callReading() } - fun parseArgsAndExecute(args: Array) { - parser.parse(args) - } + /** Calls parse function of ArgParser object. */ + fun parseArgsAndExecute(args: Array) = parser.parse(args) - fun subcommandsInit() { - parser.subcommands(addNewText, showStatistics, showTextList, removeTexts) - } + /** Calls initialization of subcommands. */ + fun subcommandsInit() = parser.subcommands(addNewText, showStatistics, showTextList, removeTexts) } -/** Function 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. - */ +/** Calls parsing of main args or if it is empty, read them from console */ fun readAndCallParsing(commandCenter: CommandCenter, mainArgs: Array) { if (mainArgs.isNotEmpty()) commandCenter.parseArgsAndExecute(mainArgs) else { val args = readln().split(WHITESPACES).toTypedArray() try { commandCenter.parseArgsAndExecute(args) - } catch (e: ReturnException) { - println("message") + } catch (e: IncorrectPathException) { + println(e.message) exit() } } } -/** Custom exception being thrown if user want to return to the menu. */ -class ReturnException(message: String = "") : Exception(message) +/** Custom exception being thrown if user enter incorrect path to file. */ +class IncorrectPathException(path: String = "") : Exception("File at $path doesn't exist") + +/** Function used for exiting out of program. */ +fun exit(): Nothing { + println("bye!") + exitProcess(0) +} fun main(args: Array) {