Added function for searching similar free name for text and rewrote documentation.

This commit is contained in:
ZhigalskiiIvan 2023-03-31 22:47:13 +03:00
parent d7cea6ac71
commit 396df40998

View File

@ -11,15 +11,17 @@ import com.google.gson.GsonBuilder
import java.io.FileReader 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. * Builds bar chart with data of listOfSentenceSizes and saves image in file.
* @param mapOfSentenceNumToItsSize map of pairs of sentence numbers and their words count. * @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<Int>) { fun buildGraphic(textName: String, listOfSentenceSizes: List<Int>) {
val data = mapOf("words count" to listOfSentenceSizes.map { it.toFloat() }) val data = mapOf("words count" to listOfSentenceSizes.map { it.toFloat() })
val fig = ggplot(data) + val fig = ggplot(data) +
@ -39,7 +41,8 @@ fun buildGraphic(textName: String, listOfSentenceSizes: List<Int>) {
fig.show() 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 textName name of texts, which user want to see statistics about.
* @param mapOfSentenceNumToItsSize map of pairs of sentence numbers and their words count. * @param mapOfSentenceNumToItsSize map of pairs of sentence numbers and their words count.
*/ */
@ -69,15 +72,15 @@ fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<In
""".trimMargin() """.trimMargin()
) )
println("-".repeat(sectionTitle.length)) println("-".repeat(sectionTitle.length))
println("Done!\n")
} }
/** 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 * Read from console the text and calls function of adding new text to data.
* calls method of adding received text to data. * @param textData TextData object to add new text to.
* @param textName name of new text.
*/ */
fun readFromConsole(textData: TextData, textName: String) { fun readFromConsoleAndAddToData(textData: TextData, textName: String) {
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 = ""
@ -100,15 +103,13 @@ fun readFromConsole(textData: TextData, textName: String) {
addTextToData(textData, textName, content) addTextToData(textData, textName, content)
} }
/**
/** Asks for the name of text, path to file to read text from, * Read text from file and calls function of adding text to data.
* checks for its existing, reads contents and, * @param textData TextData object to add new text to.
* asks if entered names is not correct and re-calls itself or * @param textName name of new text.
* calls method of adding received text to data. * @param path path to the text file.
*/ */
fun readFromFile(textData: TextData, textName: String, path: String) { fun readFromFileAndAddToData(textData: TextData, textName: String, path: String) {
println("Input path to file:")
val contentsFile = File(path) val contentsFile = File(path)
val content = contentsFile.readText() val content = contentsFile.readText()
@ -116,31 +117,34 @@ fun readFromFile(textData: TextData, textName: String, path: String) {
} }
/** /**
* Calls method of TextData class to add new text in set * Calls method of TextData class to add new text in set
* of tracked texts. * of tracked texts.
* @param textName name of new text. * @param textData TextData object to add new text to.
* @param content content of new text. * @param textName name of new text.
* @param content content of new text.
*/ */
fun addTextToData(textData: TextData, textName: String, content: String) = fun addTextToData(textData: TextData, textName: String, content: String) =
textData.addNewText(textName, content) textData.addNewText(textName, content)
/** Stores data about tracking texts, /**
* Stores data about tracking texts,
* includes methods for work with them due the adding. * includes methods for work with them due the adding.
*/ */
class TextData { class TextData {
/** list of monitored texts. */ /** List of monitored texts. */
private val textsList: MutableSet<Text> private val textsList: MutableSet<Text>
init { init {
// Initialization of previously saved and saved in JSON file texts
textsList = JSONReaderWriter.readSavedTexts() 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) { fun removeText(name: String) {
@ -151,114 +155,114 @@ class TextData {
println("Text ${removingText.getName()} removed.") println("Text ${removingText.getName()} removed.")
JSONReaderWriter.removeFromDirectory(name) 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<Int>()
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. * if there is no text with same name in the textsList.
* @param searchingName name of the text to be found. * @param searchingName name of the text to be found.
* @return the text with searchingName name or null. * @return the text with searchingName name or null.
*/ */
fun getTextByName(searchingName: String): Text? { fun getTextByName(searchingName: String): Text? {
textsList.forEach { if (it.getName() == searchingName) return it }
for (text in textsList) if (text.getName() == searchingName) return text
return null return null
} }
/** @return string with names of tracking texts separated by delimiter. */ /** @return string with names of tracking texts separated by delimiter. */
fun getTextNamesInString(delimiter: String = ", "): String { fun getTextNamesInString(delimiter: String = ", "): String =
return textsList.joinToString(delimiter) { it.getName() } textsList.joinToString(delimiter) { it.getName() }
}
/** @return list with names of tracking texts. */ /** @return list with names of tracking texts. */
fun getTextNamesList(): List<String> { fun getTextNamesList(): List<String> = textsList.map { it.getName() }
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)
}
/** Uses for writing and reading Text objects in JSON. */
private object JSONReaderWriter { private object JSONReaderWriter {
private val gsonPretty: Gson = GsonBuilder().setPrettyPrinting().create() private val gsonPretty: Gson = GsonBuilder().setPrettyPrinting().create()
private val savedTextsDirectory: File = File("savedTexts") private val savedTextsDirectory: File = File("savedTexts")
private val savedTextsFiles: Array<File> private val savedTextsFiles: Array<File>
init { init {
// initialization or creation directory with JSON files where Text objects were written.
if (!savedTextsDirectory.exists()) savedTextsDirectory.mkdir() if (!savedTextsDirectory.exists()) savedTextsDirectory.mkdir()
savedTextsFiles = savedTextsFiles = savedTextsDirectory.listFiles { _, filename ->
savedTextsDirectory.listFiles { _, filename -> filename.split(".").last() == "json"
filename.split(".").last() == "json" } ?: emptyArray()
} ?: 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") 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) { fun writeInJSON(text: Text) {
val jsonFileText = gsonPretty.toJson(text) val jsonFileText = gsonPretty.toJson(text)
val jsonFile = File("${savedTextsDirectory.path}/${text.getName()}.json") val jsonFile = File("${savedTextsDirectory.path}/${text.getName()}.json")
jsonFile.createNewFile() jsonFile.createNewFile()
jsonFile.writeText(jsonFileText) jsonFile.writeText(jsonFileText)
} }
fun readFromJSON(jsonFile: File): Text { /**
return gsonPretty.fromJson(FileReader(jsonFile), Text::class.java) * @param jsonFile file to read from text object.
} * @return Text object read from JSON file.
*/
fun readSavedTexts(): MutableSet<Text> { fun readFromJSON(jsonFile: File): Text = gsonPretty.fromJson(FileReader(jsonFile), Text::class.java)
val textsList = mutableListOf<Text>()
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+)?")
/** /**
* Receives text as input and splits it by sentence, calculate its lengths, * Calls method of reading from JSON for each json file from directory with saved texts.
* create list of Sentence objects and returns Text object, created with * @return set of Text objects
* this list, input field name, and count of sentences in content.
*/ */
fun getTextObjFromContents(name: String, content: String): Text { fun readSavedTexts(): MutableSet<Text> {
val textsList = mutableListOf<Text>()
var sentencesCount = 1 for (file in savedTextsFiles) textsList.add(readFromJSON(file))
val listOfSentences = mutableListOf<Text.Sentence>() return textsList.toMutableSet()
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())
} }
} }
@ -268,7 +272,6 @@ class TextData {
private val sentencesCount: Int, private val sentencesCount: Int,
private val sentencesList: List<Sentence>, private val sentencesList: List<Sentence>,
) { ) {
fun getName() = name fun getName() = name
fun getSentencesList() = sentencesList fun getSentencesList() = sentencesList
@ -281,31 +284,58 @@ class TextData {
} }
/** Function used for exiting out of program. */ /**
fun exit(): Nothing { * Receives text as input and splits it by sentence, calculate its lengths,
println("bye!") * create list of Sentence objects and returns Text object, created with
exitProcess(0) * 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<TextData.Text.Sentence>()
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) { class CommandCenter(private val textData: TextData) {
/**
* ArgParser object which can distribute arguments
* over declared options and subcommands.
*/
private val parser = ArgParser( private val parser = ArgParser(
"Text Analyzer", "Text Analyzer",
useDefaultHelpShortName = true, useDefaultHelpShortName = true,
strictSubcommandOptionsOrder = false strictSubcommandOptionsOrder = false
) )
/** Subcommands initialization */
private val addNewText = Add() private val addNewText = Add()
private val showStatistics = ShowStatistics() private val showStatistics = ShowStatistics()
private val showTextList = ShowTextsList() private val showTextList = ShowTextsList()
private val removeTexts = RemoveTexts() private val removeTexts = RemoveTexts()
/** Calls function of removing texts from data. */
private inner class RemoveTexts : Subcommand("remove", "Removing text from saved.") { private inner class RemoveTexts : Subcommand("remove", "Removing text from saved.") {
private val removingList by argument( private val removingList by argument(
ArgType.String, "removing list", 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 inner class ShowTextsList : Subcommand("list", "Showing tracking texts.") {
private fun printTextListInConsole() = println("Saved texts list: ${textData.getTextNamesInString(", ")}")
override fun execute() { override fun execute() {
println("Saved texts list: ${textData.getTextNamesInString(", ")}") printTextListInConsole()
} }
} }
/** Calls functions of graphic building or showing statistics in console. */
private inner class ShowStatistics : Subcommand("stat", "Showing statistics.") { private inner class ShowStatistics : Subcommand("stat", "Showing statistics") {
private val textNames by argument( private val textNames by argument(
ArgType.String, "text names", ArgType.String, "text names",
@ -347,18 +382,22 @@ class CommandCenter(private val textData: TextData) {
"Choice where to show the statistics" "Choice where to show the statistics"
).default("console") ).default("console")
override fun execute() {
val availableSelectedTextNames = mutableListOf<String>() private fun selectAvailableInputFromInputted(): List<String> {
val availableInputs = mutableListOf<String>()
textNames.forEach { 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") else println("No text $it in data")
} }
return availableInputs
}
private fun callStatisticsShowingForEach(textNames: List<String>) {
when (outputLocation) { when (outputLocation) {
"graphic" -> availableSelectedTextNames.forEach { callBuildingGraphic(it) } "graphic" -> textNames.forEach { callBuildingGraphic(it) }
"console" -> availableSelectedTextNames.forEach { callConsoleStatisticsShowing(it) } "console" -> textNames.forEach { callConsoleStatisticsShowing(it) }
"both" -> availableSelectedTextNames.forEach { "both" -> textNames.forEach {
callBuildingGraphic(it) callBuildingGraphic(it)
callConsoleStatisticsShowing(it) callConsoleStatisticsShowing(it)
} }
@ -377,65 +416,74 @@ class CommandCenter(private val textData: TextData) {
val mapOfSentenceNumToItsSize = text.getSentencesList().associate { counter++ to it.getWordsCount() } val mapOfSentenceNumToItsSize = text.getSentencesList().associate { counter++ to it.getWordsCount() }
printStatisticsInConsole(textName, mapOfSentenceNumToItsSize) 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 inner class Add : Subcommand("add", "Adding text from source") {
private val path by option( private val path by option(
ArgType.String, "source path", "s", ArgType.String, "source path", "s",
"Inputting path to file for reading or \"console\" to reading from console" "Inputting path to file for reading or \"console\" to reading from console"
).default("console") ).default("console")
private var textName by argument(
private val textName by argument(
ArgType.String, "name", ArgType.String, "name",
"Specifies the name of the new text" "Specifies the name of the new text"
) )
override fun execute() {
if (textName == "") textName = "untitled" private fun callReading() = when {
when { path == "console" -> callReadingFromConsole()
path == "console" -> callReadingFromConsole() !File(path).exists() -> throw IncorrectPathException(path)
!File(path).exists() -> throw ReturnException("No file at $path") else -> callReadingFromFile()
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<String>) { /** Calls parse function of ArgParser object. */
parser.parse(args) fun parseArgsAndExecute(args: Array<String>) = parser.parse(args)
}
fun subcommandsInit() { /** Calls initialization of subcommands. */
parser.subcommands(addNewText, showStatistics, showTextList, removeTexts) fun subcommandsInit() = parser.subcommands(addNewText, showStatistics, showTextList, removeTexts)
}
} }
/** Function repeating the process of calling functions returned by /** Calls parsing of main args or if it is empty, read them from console */
* CommandCenter. If ReturnException was thrown, it is caught here,
* last iteration breaks and new one is called.
*/
fun readAndCallParsing(commandCenter: CommandCenter, mainArgs: Array<String>) { fun readAndCallParsing(commandCenter: CommandCenter, mainArgs: Array<String>) {
if (mainArgs.isNotEmpty()) commandCenter.parseArgsAndExecute(mainArgs) if (mainArgs.isNotEmpty()) commandCenter.parseArgsAndExecute(mainArgs)
else { else {
val args = readln().split(WHITESPACES).toTypedArray() val args = readln().split(WHITESPACES).toTypedArray()
try { try {
commandCenter.parseArgsAndExecute(args) commandCenter.parseArgsAndExecute(args)
} catch (e: ReturnException) { } catch (e: IncorrectPathException) {
println("message") println(e.message)
exit() exit()
} }
} }
} }
/** Custom exception being thrown if user want to return to the menu. */ /** Custom exception being thrown if user enter incorrect path to file. */
class ReturnException(message: String = "") : Exception(message) 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<String>) { fun main(args: Array<String>) {