mirror of
https://github.com/ZhigalskiiIvan/TextStatisticsProject.git
synced 2024-11-14 11:51:53 +03:00
Compare commits
2 Commits
9ce3f34c1f
...
396df40998
Author | SHA1 | Date | |
---|---|---|---|
396df40998 | |||
d7cea6ac71 |
@ -19,6 +19,7 @@ dependencies {
|
|||||||
implementation("org.slf4j:slf4j-nop:1.7.29")
|
implementation("org.slf4j:slf4j-nop:1.7.29")
|
||||||
implementation("org.jetbrains.lets-plot:lets-plot-image-export:3.1.0")
|
implementation("org.jetbrains.lets-plot:lets-plot-image-export:3.1.0")
|
||||||
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.9'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,55 +1,28 @@
|
|||||||
|
@file:OptIn(ExperimentalCli::class)
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
import org.jetbrains.letsPlot.*
|
import org.jetbrains.letsPlot.*
|
||||||
import org.jetbrains.letsPlot.export.ggsave
|
import org.jetbrains.letsPlot.export.ggsave
|
||||||
import org.jetbrains.letsPlot.geom.*
|
import org.jetbrains.letsPlot.geom.*
|
||||||
import kotlinx.cli.*
|
import kotlinx.cli.*
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import java.io.FileReader
|
||||||
|
|
||||||
|
|
||||||
/** Recognizes the name of the text, which
|
private val DELIMITERS = Regex("[!?.]+\\s+")
|
||||||
* user want to see statistics about and type of output.
|
private val WHITESPACES = Regex("\\s+")
|
||||||
* It calls methods of graphic building or printing data in console,
|
private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?")
|
||||||
* build required for it objects.
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 getStatistics(textData: TextData) {
|
fun buildGraphic(textName: String, listOfSentenceSizes: List<Int>) {
|
||||||
|
val data = mapOf("words count" to listOfSentenceSizes.map { it.toFloat() })
|
||||||
if (!textData.haveText()) {
|
|
||||||
println("No saved texts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val text = textData.getTextData("Input name of a text which you want to see statistics about.")
|
|
||||||
|
|
||||||
var counter = 1
|
|
||||||
val wordsCountsMap = text.getSentencesList().map { counter++ to it.getWordsCount() }
|
|
||||||
|
|
||||||
println(
|
|
||||||
"Print \"console\" if you have see data in console, \"graphic\" if you have see histogram \n" +
|
|
||||||
"and \"both\" if you have see them together:"
|
|
||||||
)
|
|
||||||
|
|
||||||
val request = requestInput(listOf("console", "graphic", "both"))
|
|
||||||
request.first.exe()
|
|
||||||
|
|
||||||
when (request.second) {
|
|
||||||
"console" -> printStatisticsInConsole(text.getName(), wordsCountsMap.toMap())
|
|
||||||
"graphic" -> buildGraphic(text.getName(), wordsCountsMap.toMap())
|
|
||||||
"both" -> {
|
|
||||||
printStatisticsInConsole(text.getName(), wordsCountsMap.toMap())
|
|
||||||
buildGraphic(text.getName(), wordsCountsMap.toMap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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.
|
|
||||||
*/
|
|
||||||
fun buildGraphic(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
|
|
||||||
|
|
||||||
val data = mapOf(
|
|
||||||
"words count" to mapOfSentenceNumToItsSize.map { it.value.toFloat() / mapOfSentenceNumToItsSize.size },
|
|
||||||
)
|
|
||||||
|
|
||||||
val fig = ggplot(data) +
|
val fig = ggplot(data) +
|
||||||
geomBar(
|
geomBar(
|
||||||
@ -66,10 +39,10 @@ fun buildGraphic(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
|
|||||||
|
|
||||||
println("Graphic was save in ${ggsave(fig, "$textName.png")}")
|
println("Graphic was save in ${ggsave(fig, "$textName.png")}")
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
@ -99,38 +72,15 @@ fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<In
|
|||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
)
|
)
|
||||||
println("-".repeat(sectionTitle.length))
|
println("-".repeat(sectionTitle.length))
|
||||||
println("Done!\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Asks the user which where they want to read the text from and
|
/**
|
||||||
* calls special for file- and console- reading methods according the answer.
|
* Read from console the text and calls function of adding new text to data.
|
||||||
|
* @param textData TextData object to add new text to.
|
||||||
|
* @param textName name of new text.
|
||||||
*/
|
*/
|
||||||
fun readNewText(textData: TextData) {
|
fun readFromConsoleAndAddToData(textData: TextData, textName: String) {
|
||||||
|
|
||||||
println("Where do you want to add the text: from the console or a file?")
|
|
||||||
|
|
||||||
val kindOfSource = requestInput(listOf("console", "file"))
|
|
||||||
kindOfSource.first.exe()
|
|
||||||
|
|
||||||
when (kindOfSource.second) {
|
|
||||||
"console" -> readFromConsole(textData)
|
|
||||||
"file" -> readFromFile(textData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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.
|
|
||||||
*/
|
|
||||||
fun readFromConsole(textData: TextData) {
|
|
||||||
|
|
||||||
println("Input name of a text:")
|
|
||||||
if (textData.haveText()) println("Unavailable(existing) names: ${textData.getTextNamesInString()}.")
|
|
||||||
val nameRequest = requestInput(unavailableInputs = textData.getTextNamesList())
|
|
||||||
nameRequest.first.exe()
|
|
||||||
|
|
||||||
val name = nameRequest.second!!
|
|
||||||
|
|
||||||
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 = ""
|
||||||
@ -150,172 +100,169 @@ fun readFromConsole(textData: TextData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Was input data right?[yes, no]:")
|
addTextToData(textData, textName, content)
|
||||||
|
|
||||||
val correctInputRequest = requestInput(listOf("yes", "no"))
|
|
||||||
correctInputRequest.first.exe()
|
|
||||||
|
|
||||||
if (correctInputRequest.second == "yes") addTextToData(textData, name, content)
|
|
||||||
else readFromConsole(textData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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.
|
|
||||||
*/
|
|
||||||
fun readFromFile(textData: TextData) {
|
|
||||||
|
|
||||||
println("Input a name of a text:")
|
|
||||||
val name = readln()
|
|
||||||
|
|
||||||
println("Input path to file:")
|
|
||||||
val contentsFile: File
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
val pathRequest = requestInput()
|
|
||||||
pathRequest.first.exe()
|
|
||||||
val filePath = pathRequest.second!!
|
|
||||||
|
|
||||||
val testFile = File(filePath)
|
|
||||||
|
|
||||||
if (!testFile.exists()) {
|
|
||||||
println("Incorrect path. Repeat the input:")
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
contentsFile = testFile
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val content = contentsFile.readText()
|
|
||||||
|
|
||||||
print("Input was correct?[yes, no]: ")
|
|
||||||
val correctInputRequest = requestInput(listOf("yes", "no"))
|
|
||||||
correctInputRequest.first.exe()
|
|
||||||
|
|
||||||
when (correctInputRequest.second) {
|
|
||||||
"yes" -> addTextToData(textData, name, content)
|
|
||||||
"no" -> readFromFile(textData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls method of TextData class to add new text in set
|
* Read text from file and calls function of adding text to data.
|
||||||
* of tracked texts.
|
* @param textData TextData object to add new text to.
|
||||||
* @param textName name of new text.
|
* @param textName name of new text.
|
||||||
* @param content content of new text.
|
* @param path path to the text file.
|
||||||
|
*/
|
||||||
|
fun readFromFileAndAddToData(textData: TextData, textName: String, path: String) {
|
||||||
|
val contentsFile = File(path)
|
||||||
|
val content = contentsFile.readText()
|
||||||
|
|
||||||
|
addTextToData(textData, textName, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls method of TextData class to add new text in set
|
||||||
|
* of tracked texts.
|
||||||
|
* @param textData TextData object to add new text to.
|
||||||
|
* @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 = mutableListOf<Text>()
|
private val textsList: MutableSet<Text>
|
||||||
|
|
||||||
fun haveText(): Boolean = textsList.isNotEmpty()
|
init {
|
||||||
|
// Initialization of previously saved and saved in JSON file texts
|
||||||
/** Method for removing text from watch list. Asks for a name of a text
|
textsList = JSONReaderWriter.readSavedTexts()
|
||||||
* and if it's being tracked, remove it from the textsList.
|
|
||||||
*/
|
|
||||||
fun removeText() {
|
|
||||||
|
|
||||||
if (!haveText()) {
|
|
||||||
println("No saved texts")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val removingText = getTextData("Input name of text which you want to remove.")
|
|
||||||
textsList.remove(removingText)
|
|
||||||
|
|
||||||
println("Text ${removingText.getName()} removed.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns Text object 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.
|
|
||||||
*/
|
|
||||||
private fun getTextByName(searchingName: String): Text? {
|
|
||||||
|
|
||||||
for (text in textsList) if (text.getName() == searchingName) return text
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return string with names of tracking texts separated by delimiter. */
|
|
||||||
fun getTextNamesInString(delimiter: String = ", "): String {
|
|
||||||
return textsList.joinToString(delimiter) { it.getName() }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** @return list with names of tracking texts. */
|
|
||||||
fun getTextNamesList(): List<String> {
|
|
||||||
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) {
|
|
||||||
textsList.add(TextAnalyzer.getTextObjFromContents(textName, content))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads name of the text to be found and returns it text if
|
* Removes text with name from watch list if it was there previously.
|
||||||
* it exists.
|
* @param name the name of text to be deleted.
|
||||||
* @param message message which prints with calling this method.
|
|
||||||
* @return Text type object
|
|
||||||
*/
|
*/
|
||||||
fun getTextData(message: String = "Input name of a text"): Text {
|
fun removeText(name: String) {
|
||||||
println(message)
|
|
||||||
println("Saved texts: ${getTextNamesInString()}.")
|
|
||||||
|
|
||||||
val nameRequest = requestInput(getTextNamesList())
|
val removingText = getTextByName(name)
|
||||||
nameRequest.first.exe()
|
if (removingText == null) println("No text with name $name")
|
||||||
|
else {
|
||||||
|
textsList.remove(removingText)
|
||||||
|
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<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)"
|
||||||
|
|
||||||
return getTextByName(nameRequest.second!!)!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Object used for getting text from the string information. */
|
/**
|
||||||
private object TextAnalyzer {
|
* 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? {
|
||||||
|
textsList.forEach { if (it.getName() == searchingName) return it }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private val DELIMITERS = Regex("[!?.]+\\s+")
|
/** @return string with names of tracking texts separated by delimiter. */
|
||||||
private val WHITESPACES = Regex("\\s+")
|
fun getTextNamesInString(delimiter: String = ", "): String =
|
||||||
private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?")
|
textsList.joinToString(delimiter) { it.getName() }
|
||||||
|
|
||||||
/**
|
/** @return list with names of tracking texts. */
|
||||||
* Receives text as input and splits it by sentence, calculate its lengths,
|
fun getTextNamesList(): List<String> = textsList.map { it.getName() }
|
||||||
* create list of Sentence objects and returns Text object, created with
|
|
||||||
* this list, input field name, and count of sentences in content.
|
|
||||||
*/
|
|
||||||
fun getTextObjFromContents(name: String, content: String): Text {
|
|
||||||
|
|
||||||
var sentencesCount = 1
|
|
||||||
val listOfSentences = mutableListOf<Text.Sentence>()
|
|
||||||
|
|
||||||
val sentencesStringList = content
|
/** Uses for writing and reading Text objects in JSON. */
|
||||||
.split(DELIMITERS)
|
private object JSONReaderWriter {
|
||||||
.map { it.replace(WHITESPACES, " ") }.toMutableList()
|
private val gsonPretty: Gson = GsonBuilder().setPrettyPrinting().create()
|
||||||
sentencesStringList.removeIf { it.matches(WHITESPACES_OR_EMPTY) }
|
private val savedTextsDirectory: File = File("savedTexts")
|
||||||
|
private val savedTextsFiles: Array<File>
|
||||||
|
|
||||||
for (sentenceText in sentencesStringList) {
|
init {
|
||||||
val wordsList = sentenceText.split(WHITESPACES).toMutableList()
|
// initialization or creation directory with JSON files where Text objects were written.
|
||||||
wordsList.removeIf { it.matches(WHITESPACES_OR_EMPTY) }
|
if (!savedTextsDirectory.exists()) savedTextsDirectory.mkdir()
|
||||||
sentencesCount++
|
savedTextsFiles = savedTextsDirectory.listFiles { _, filename ->
|
||||||
listOfSentences.add(Text.Sentence(wordsList.size))
|
filename.split(".").last() == "json"
|
||||||
}
|
} ?: emptyArray()
|
||||||
|
|
||||||
return Text(name, sentencesCount, listOfSentences.toList())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls method of reading from JSON for each json file from directory with saved texts.
|
||||||
|
* @return set of Text objects
|
||||||
|
*/
|
||||||
|
fun readSavedTexts(): MutableSet<Text> {
|
||||||
|
val textsList = mutableListOf<Text>()
|
||||||
|
for (file in savedTextsFiles) textsList.add(readFromJSON(file))
|
||||||
|
return textsList.toMutableSet()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -325,21 +272,213 @@ 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
|
||||||
|
|
||||||
|
|
||||||
/** Stores information about count of words. */
|
/** Stores information about count of words. */
|
||||||
data class Sentence(private val wordsCount: Int) {
|
data class Sentence(private val wordsCount: Int) {
|
||||||
fun getWordsCount() = wordsCount
|
fun getWordsCount() = wordsCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<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())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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",
|
||||||
|
"Selection of texts to delete"
|
||||||
|
).vararg()
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
if (removingList.isEmpty()) {
|
||||||
|
println("remove command needs arguments to work")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val availableRemovingList = mutableListOf<String>()
|
||||||
|
removingList.forEach {
|
||||||
|
if (it in textData.getTextNamesList()) availableRemovingList.add(it)
|
||||||
|
else println("No text $it in data")
|
||||||
|
}
|
||||||
|
|
||||||
|
availableRemovingList.forEach { textData.removeText(it) }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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() {
|
||||||
|
printTextListInConsole()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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",
|
||||||
|
"Names of texts which you want to show statistics about"
|
||||||
|
).vararg()
|
||||||
|
|
||||||
|
private val outputLocation by option(
|
||||||
|
ArgType.Choice(listOf("graphic", "console", "both"), { it }), "output-location", "o",
|
||||||
|
"Choice where to show the statistics"
|
||||||
|
).default("console")
|
||||||
|
|
||||||
|
|
||||||
|
private fun selectAvailableInputFromInputted(): List<String> {
|
||||||
|
val availableInputs = mutableListOf<String>()
|
||||||
|
|
||||||
|
textNames.forEach {
|
||||||
|
if (it in textData.getTextNamesList()) availableInputs.add(it)
|
||||||
|
else println("No text $it in data")
|
||||||
|
}
|
||||||
|
return availableInputs
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callStatisticsShowingForEach(textNames: List<String>) {
|
||||||
|
when (outputLocation) {
|
||||||
|
"graphic" -> textNames.forEach { callBuildingGraphic(it) }
|
||||||
|
"console" -> textNames.forEach { callConsoleStatisticsShowing(it) }
|
||||||
|
"both" -> textNames.forEach {
|
||||||
|
callBuildingGraphic(it)
|
||||||
|
callConsoleStatisticsShowing(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callBuildingGraphic(textName: String) {
|
||||||
|
val text = textData.getTextByName(textName)!!
|
||||||
|
val listOfSentenceSizes = text.getSentencesList().map { it.getWordsCount() }
|
||||||
|
buildGraphic(textName, listOfSentenceSizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callConsoleStatisticsShowing(textName: String) {
|
||||||
|
val text = textData.getTextByName(textName)!!
|
||||||
|
var counter = 1
|
||||||
|
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 val textName by argument(
|
||||||
|
ArgType.String, "name",
|
||||||
|
"Specifies the name of the new text"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
private fun callReading() = when {
|
||||||
|
path == "console" -> callReadingFromConsole()
|
||||||
|
!File(path).exists() -> throw IncorrectPathException(path)
|
||||||
|
else -> callReadingFromFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callReadingFromConsole() = readFromConsoleAndAddToData(textData, textName)
|
||||||
|
|
||||||
|
private fun callReadingFromFile() = readFromFileAndAddToData(textData, textName, path)
|
||||||
|
|
||||||
|
|
||||||
|
override fun execute() = callReading()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Calls parse function of ArgParser object. */
|
||||||
|
fun parseArgsAndExecute(args: Array<String>) = parser.parse(args)
|
||||||
|
|
||||||
|
/** Calls initialization of subcommands. */
|
||||||
|
fun subcommandsInit() = parser.subcommands(addNewText, showStatistics, showTextList, removeTexts)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Calls parsing of main args or if it is empty, read them from console */
|
||||||
|
fun readAndCallParsing(commandCenter: CommandCenter, mainArgs: Array<String>) {
|
||||||
|
if (mainArgs.isNotEmpty()) commandCenter.parseArgsAndExecute(mainArgs)
|
||||||
|
else {
|
||||||
|
val args = readln().split(WHITESPACES).toTypedArray()
|
||||||
|
try {
|
||||||
|
commandCenter.parseArgsAndExecute(args)
|
||||||
|
} catch (e: IncorrectPathException) {
|
||||||
|
println(e.message)
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 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. */
|
/** Function used for exiting out of program. */
|
||||||
fun exit(): Nothing {
|
fun exit(): Nothing {
|
||||||
println("bye!")
|
println("bye!")
|
||||||
@ -347,155 +486,11 @@ fun exit(): Nothing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Reads commands from the console and storing data about
|
fun main(args: Array<String>) {
|
||||||
* the functions they should execute.
|
|
||||||
*/
|
|
||||||
class CommandCenter(private val textData: TextData) {
|
|
||||||
|
|
||||||
private val exitCommand = Command("exit", ::exit)
|
|
||||||
private val addCommand = Command("add text") { readNewText(textData) }
|
|
||||||
private val showStatisticsCommand = Command("show statistics") { getStatistics(textData) }
|
|
||||||
private val removeTextCommand = Command("remove text") { textData.removeText() }
|
|
||||||
|
|
||||||
private val commandsList = listOf(exitCommand, addCommand, showStatisticsCommand, removeTextCommand)
|
|
||||||
private val commandsNames = commandsList.map { it.name }
|
|
||||||
|
|
||||||
|
|
||||||
private val parser = ArgParser("Text Analyzer", useDefaultHelpShortName = true)
|
|
||||||
|
|
||||||
private val add by parser.option(
|
|
||||||
ArgType.String, "add", "a",
|
|
||||||
"adds new text to data", "Add warn"
|
|
||||||
)
|
|
||||||
private val remove by parser.option(
|
|
||||||
ArgType.String, "remove", "r",
|
|
||||||
"removes text from data", "Remove warn"
|
|
||||||
)
|
|
||||||
private val stat by parser.option(
|
|
||||||
ArgType.String, "statistics", "s",
|
|
||||||
"shows statistics", "Stat warn"
|
|
||||||
)
|
|
||||||
private val exit by parser.option(
|
|
||||||
ArgType.String, "exit", "e",
|
|
||||||
"exit from program", "exit warn"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fun parseCommand(args: Array<String>) {
|
|
||||||
// parser.parse(args)
|
|
||||||
//
|
|
||||||
// when (add) {
|
|
||||||
// "" -> readNewText(textData)
|
|
||||||
// null -> {}
|
|
||||||
// else ->
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
/** Stores command and its name */
|
|
||||||
private class Command(val name: String, val executingFun: () -> Unit)
|
|
||||||
|
|
||||||
/** Prints list of names of available commands, requests the name and
|
|
||||||
* returns corresponding to entered name function.
|
|
||||||
*/
|
|
||||||
fun readCommandFromConsole(): () -> Unit {
|
|
||||||
|
|
||||||
println("Input one of the commands: ${commandsNames.joinToString()}:")
|
|
||||||
|
|
||||||
val commandNameRequest = requestInput(commandsNames)
|
|
||||||
commandNameRequest.first.exe()
|
|
||||||
|
|
||||||
val funName = commandNameRequest.second
|
|
||||||
|
|
||||||
return commandsList.find { it.name == funName }!!.executingFun
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** 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.
|
|
||||||
*/
|
|
||||||
fun workCycle(commandCenter: CommandCenter) {
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
(commandCenter.readCommandFromConsole())()
|
|
||||||
} catch (e: ReturnException) {
|
|
||||||
println("You have been returned in main menu.")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Function 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.
|
|
||||||
*
|
|
||||||
* @param availableInputs list of available values, the choice among which
|
|
||||||
* is requested from the console at some stage of the program.
|
|
||||||
* @param 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.
|
|
||||||
*/
|
|
||||||
fun requestInput(
|
|
||||||
availableInputs: List<Any>? = null,
|
|
||||||
unavailableInputs: List<Any>? = null
|
|
||||||
): Pair<InputOutcomeCommand, String?> {
|
|
||||||
|
|
||||||
val regexAvailableInputs = availableInputs?.joinToString("|")?.toRegex() ?: Regex(".*")
|
|
||||||
val regexUnavailableInputs = unavailableInputs?.joinToString("|")?.toRegex() ?: Regex("")
|
|
||||||
val returnRegex = Regex("return")
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
val input = readln().trim()
|
|
||||||
|
|
||||||
return when {
|
|
||||||
input.matches(regexAvailableInputs) && !input.matches(regexUnavailableInputs) ->
|
|
||||||
ContinueCommand() to input
|
|
||||||
|
|
||||||
input.matches(returnRegex) -> ReturnCommand() to null
|
|
||||||
else -> {
|
|
||||||
println("There isn't this elem in list of available inputs. Try to repeat or enter return to exit in main menu: ")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Interface used to existing commands objects, whose exe value
|
|
||||||
* stores function which calls after requesting from console in
|
|
||||||
* requestInput function.
|
|
||||||
*/
|
|
||||||
interface InputOutcomeCommand {
|
|
||||||
/** Function returned in a Main.requestInput method for returned to
|
|
||||||
* main menu of application or continuation of the process.
|
|
||||||
*/
|
|
||||||
val exe: () -> Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If called exe of this object, program continue executing
|
|
||||||
* without changes.
|
|
||||||
*/
|
|
||||||
class ContinueCommand : InputOutcomeCommand {
|
|
||||||
override val exe: () -> Unit = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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.
|
|
||||||
*/
|
|
||||||
class ReturnCommand : InputOutcomeCommand {
|
|
||||||
override val exe: () -> Unit = throw ReturnException()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Custom exception being thrown if user want to return to the menu. */
|
|
||||||
class ReturnException : Exception()
|
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
|
|
||||||
val textData = TextData()
|
val textData = TextData()
|
||||||
val commandCenter = CommandCenter(textData)
|
val commandCenter = CommandCenter(textData)
|
||||||
|
commandCenter.subcommandsInit()
|
||||||
|
|
||||||
workCycle(commandCenter)
|
readAndCallParsing(commandCenter, args)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user