Compare commits

...

2 Commits

2 changed files with 205 additions and 280 deletions

View File

@ -12,6 +12,7 @@ repositories {
} }
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.5")
implementation("org.jetbrains.lets-plot:lets-plot-kotlin:4.0.0") implementation("org.jetbrains.lets-plot:lets-plot-kotlin:4.0.0")
implementation("org.jetbrains.lets-plot:lets-plot-batik:3.1.0") implementation("org.jetbrains.lets-plot:lets-plot-batik:3.1.0")
implementation("org.jetbrains.lets-plot:lets-plot-jfx:3.1.0") implementation("org.jetbrains.lets-plot:lets-plot-jfx:3.1.0")

View File

@ -3,92 +3,87 @@ 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 kotlin.reflect.typeOf import kotlinx.cli.*
/** Recognizes the name of the text, which
/** Gives statistics of saved texts. * 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.
*/ */
class StatisticBuilder { fun getStatistics(textData: TextData) {
/** 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.
*/
fun getStatistics(textData: TextData) {
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 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())
}
}
if (!textData.haveText()) {
println("No saved texts")
return
} }
/** Builds bar chart with data of mapOfSentenceNumToItsSize and saves image in file. val text = textData.getTextData("Input name of a text which you 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.
*/
private fun buildGraphic(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
val data = mapOf( var counter = 1
"words count" to mapOfSentenceNumToItsSize.map { it.value.toFloat() / mapOfSentenceNumToItsSize.size }, val wordsCountsMap = text.getSentencesList().map { counter++ to it.getWordsCount() }
)
val fig = ggplot(data) + println("Print \"console\" if you have see data in console, \"graphic\" if you have see histogram \n" +
geomBar( "and \"both\" if you have see them together:")
color = "white",
fill = "red"
) { x = "words count" } +
geomArea(
stat = Stat.density(),
color = "white",
fill = "pink",
alpha = 0.4
) { x = "words count" } +
ggsize(1400, 800)
println("Graphic was save in ${ggsave(fig, "$textName.png")}") val request = requestInput(listOf("console", "graphic", "both"))
fig.show() request.first.exe()
} when (request.second) {
"console" -> printStatisticsInConsole(text.getName(), wordsCountsMap.toMap())
/** Prints statistics according to data from mapOfSentenceNumToItsSize. "graphic" -> buildGraphic(text.getName(), wordsCountsMap.toMap())
* @param textName name of texts, which user want to see statistics about. "both" -> {
* @param mapOfSentenceNumToItsSize map of pairs of sentence numbers and their words count. printStatisticsInConsole(text.getName(), wordsCountsMap.toMap())
*/ buildGraphic(text.getName(), wordsCountsMap.toMap())
private fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
val sectionTitle = "Text name: $textName"
println("-".repeat(sectionTitle.length))
println(sectionTitle)
println("-".repeat(sectionTitle.length))
val totalSentencesCount = mapOfSentenceNumToItsSize.size
var totalWordsCount = 0
for (sentInfo in mapOfSentenceNumToItsSize) {
totalWordsCount += sentInfo.value
} }
println( }
""" }
/** 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.
*/
private 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) +
geomBar(
color = "white",
fill = "red"
) { x = "words count" } +
geomArea(
stat = Stat.density(),
color = "white",
fill = "pink",
alpha = 0.4
) { x = "words count" } +
ggsize(1400, 800)
println("Graphic was save in ${ggsave(fig, "$textName.png")}")
fig.show()
}
/** 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.
*/
private fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
val sectionTitle = "Text name: $textName"
println("-".repeat(sectionTitle.length))
println(sectionTitle)
println("-".repeat(sectionTitle.length))
val totalSentencesCount = mapOfSentenceNumToItsSize.size
var totalWordsCount = 0
for (sentInfo in mapOfSentenceNumToItsSize) {
totalWordsCount += sentInfo.value
}
println(
"""
|Statistics: |Statistics:
|[count of sentences]: |[count of sentences]:
|--- $totalSentencesCount |--- $totalSentencesCount
@ -99,125 +94,119 @@ class StatisticBuilder {
|[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}" }}.
""".trimMargin() """.trimMargin()
) )
println("-".repeat(sectionTitle.length)) println("-".repeat(sectionTitle.length))
println("Done!\n") println("Done!\n")
}
} }
/** Used for reading text from a file or console. */ /** Asks the user which where they want to read the text from and
class TextReader { * calls special for file- and console- reading methods according the answer.
*/
fun readNewText(textData: TextData) {
/** Asks the user which where they want to read the text from and println("Where do you want to add the text: from the console or a file?")
* calls special for file- and console- reading methods according the answer.
*/
fun readNewText(textData: TextData) {
println("Where do you want to add the text: from the console or a file?") val kindOfSource = requestInput(listOf("console", "file"))
kindOfSource.first.exe()
val kindOfSource = requestInput(listOf("console", "file")) when (kindOfSource.second) {
kindOfSource.first.exe() "console" -> readFromConsole(textData)
"file" -> readFromFile(textData)
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.
*/
private 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.toString()
println("Input a text content(after input text press enter twice):")
var content = ""
var itWasNewParYet = false
readCycle@ while (true) {
val nextPart = readln()
if (nextPart.isEmpty()) {
if (itWasNewParYet) break@readCycle
else {
content += "\n"
itWasNewParYet = true
}
} else {
itWasNewParYet = false
content += "$nextPart\n"
}
}
println("Was input data right?[yes, no]:")
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.
*/
private fun readFromFile(textData: TextData) {
println("Input a name of a text:")
val name = readln()
println("Input path to file:")
val contentsFile: File
pathReadCycle@ while (true) {
val pathRequest = requestInput<String>()
pathRequest.first.exe()
val filePath = pathRequest.second.toString()
val testFile = File(filePath)
if (!testFile.exists()) {
println("Incorrect path. Repeat the input:")
continue@pathReadCycle
} else {
contentsFile = testFile
break@pathReadCycle
}
}
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
* of tracked texts.
* @param textName name of new text.
* @param content content of new text.
*/
private fun addTextToData(textData: TextData, textName: String, content: String) =
textData.addNewText(textName, content)
} }
/** 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.
*/
private 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):")
var content = ""
var itWasNewParYet = false
while (true) {
val nextPart = readln()
if (nextPart.isEmpty()) {
if (itWasNewParYet) break
else {
content += "\n"
itWasNewParYet = true
}
} else {
itWasNewParYet = false
content += "$nextPart\n"
}
}
println("Was input data right?[yes, no]:")
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.
*/
private 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
* of tracked texts.
* @param textName name of new text.
* @param content content of new text.
*/
private fun addTextToData(textData: TextData, textName: String, content: String) =
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.
*/ */
@ -226,7 +215,6 @@ class TextData {
/** list of monitored texts. */ /** list of monitored texts. */
private val textsList = mutableListOf<Text>() private val textsList = mutableListOf<Text>()
fun haveText(): Boolean = textsList.isNotEmpty() fun haveText(): Boolean = textsList.isNotEmpty()
/** Method for removing text from watch list. Asks for a name of a text /** Method for removing text from watch list. Asks for a name of a text
@ -234,7 +222,6 @@ class TextData {
*/ */
fun removeText() { fun removeText() {
if (!haveText()) { if (!haveText()) {
println("No saved texts") println("No saved texts")
return return
@ -284,20 +271,18 @@ class TextData {
* @return Text type object * @return Text type object
*/ */
fun getTextData(message: String = "Input name of a text"): Text { fun getTextData(message: String = "Input name of a text"): Text {
println(message) println(message)
println("Saved texts: ${getTextNamesInString()}.") println("Saved texts: ${getTextNamesInString()}.")
val nameRequest = requestInput(getTextNamesList()) val nameRequest = requestInput(getTextNamesList())
nameRequest.first.exe() nameRequest.first.exe()
return getTextByName(nameRequest.second.toString())!! return getTextByName(nameRequest.second!!)!!
} }
/** Object used for getting text from the string information. */ /** Object used for getting text from the string information. */
object TextAnalyzer { private object TextAnalyzer {
private val DELIMITERS = Regex("[!?.]+\\s+") private val DELIMITERS = Regex("[!?.]+\\s+")
private val WHITESPACES = Regex("\\s+") private val WHITESPACES = Regex("\\s+")
@ -310,7 +295,6 @@ class TextData {
*/ */
fun getTextObjFromContents(name: String, content: String): Text { fun getTextObjFromContents(name: String, content: String): Text {
var sentencesCount = 1 var sentencesCount = 1
val listOfSentences = mutableListOf<Text.Sentence>() val listOfSentences = mutableListOf<Text.Sentence>()
@ -363,22 +347,18 @@ fun exit(): Nothing {
/** Reads commands from the console and storing data about /** Reads commands from the console and storing data about
* the functions they should execute. * the functions they should execute.
*/ */
class CommandCenter( class CommandCenter(private val textData: TextData) {
private val textData: TextData,
private val textReader: TextReader,
private val statisticBuilder: StatisticBuilder
) {
private val exitCommand = Command("exit", ::exit) private val exitCommand = Command("exit", ::exit)
private val addCommand = Command("add text") { textReader.readNewText(textData) } private val addCommand = Command("add text") { readNewText(textData) }
private val showStatisticsCommand = Command("show statistics") { statisticBuilder.getStatistics(textData) } private val showStatisticsCommand = Command("show statistics") { getStatistics(textData) }
private val removeTextCommand = Command("remove text") { textData.removeText() } private val removeTextCommand = Command("remove text") { textData.removeText() }
private val commandsList = listOf(exitCommand, addCommand, showStatisticsCommand, removeTextCommand) private val commandsList = listOf(exitCommand, addCommand, showStatisticsCommand, removeTextCommand)
private val commandsNames = commandsList.map { it.name } private val commandsNames = commandsList.map { it.name }
/** Stores command and its name */ /** Stores command and its name */
class Command(val name: String, val executingFun: () -> Unit) private class Command(val name: String, val executingFun: () -> Unit)
/** Prints list of names of available commands, requests the name and /** Prints list of names of available commands, requests the name and
* returns corresponding to entered name function. * returns corresponding to entered name function.
@ -390,39 +370,24 @@ class CommandCenter(
val commandNameRequest = requestInput(commandsNames) val commandNameRequest = requestInput(commandsNames)
commandNameRequest.first.exe() commandNameRequest.first.exe()
val funName = commandNameRequest.second.toString() val funName = commandNameRequest.second
return commandsList.find { it.name == funName }!!.executingFun return commandsList.find { it.name == funName }!!.executingFun
} }
} }
/** Custom exception being thrown if user want to return to the menu. */
class ReturnException : Exception()
/** Custom exception being thrown if input can't be converted
* to the requested type.
*/
class InvalidInputTypeException : Exception()
/** Custom exception being thrown if input doesn't match the list
* of possible values.
*/
class InvalidElemInInputException : Exception()
/** Function repeating the process of calling functions returned by /** Function repeating the process of calling functions returned by
* CommandCenter. If ReturnException was thrown, it is caught here, * CommandCenter. If ReturnException was thrown, it is caught here,
* last iteration breaks and new one is called. * last iteration breaks and new one is called.
*/ */
fun workCycle(commandCenter: CommandCenter) { fun workCycle(commandCenter: CommandCenter) {
mainCycle@ while (true) { while (true) {
try { try {
(commandCenter.readCommandFromConsole())() (commandCenter.readCommandFromConsole())()
} catch (e: ReturnException) { } catch (e: ReturnException) {
println("You have been returned in main menu.") println("You have been returned in main menu.")
continue@mainCycle continue
} }
} }
} }
@ -438,73 +403,31 @@ fun workCycle(commandCenter: CommandCenter) {
* @return a pair of function, which could be called after the request from this function to return * @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. * if user want it or continue and value that the user has selected from the list of available.
*/ */
inline fun <reified T> requestInput( fun requestInput(
availableInputs: List<T>? = null, availableInputs: List<Any>? = null,
unavailableInputs: List<T>? = null unavailableInputs: List<Any>? = null
): Pair<InputOutcomeCommand, Any> { ): Pair<InputOutcomeCommand, String?> {
var inputT: Any val regexAvailableInputs = availableInputs?.joinToString("|")?.toRegex() ?: Regex(".*")
val regexUnavailableInputs = unavailableInputs?.joinToString("|")?.toRegex() ?: Regex("")
readingAndChangingTypeCycle@ while (true) { val returnRegex = Regex("return")
while (true) {
val input = readln().trim() val input = readln().trim()
if (input == "return") return Pair(ReturnCommand(), "")
try { return when {
inputT = when (typeOf<T>()) { input.matches(regexAvailableInputs) && !input.matches(regexUnavailableInputs) ->
typeOf<Int>() -> input.toInt() ContinueCommand() to input
typeOf<Double>() -> input.toDouble()
typeOf<Boolean>() -> input.toBoolean()
typeOf<Byte>() -> input.toByte()
typeOf<Long>() -> input.toLong()
typeOf<Float>() -> input.toFloat()
typeOf<Char>() -> {
if (input.trim().length == 1) {
input.trim().toCharArray()[0]
} else throw InvalidInputTypeException()
}
typeOf<String>() -> input input.matches(returnRegex) -> ReturnCommand() to null
else -> throw InvalidInputTypeException() else -> {
println("There isn't this elem in list of available inputs. Try to repeat or enter return to exit in main menu: ")
continue
} }
return when {
availableInputs != null && unavailableInputs != null -> {
if (inputT.toString() in availableInputs.map { it.toString() } &&
inputT.toString() !in unavailableInputs.map { it.toString() }) Pair(ContinueCommand(), inputT)
else throw InvalidElemInInputException()
}
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) {
continue@readingAndChangingTypeCycle
} catch (e: InvalidInputTypeException) {
continue@readingAndChangingTypeCycle
} catch (e: InvalidElemInInputException) {
println(
"There isn't this elem in list of available inputs. " +
"Try to repeat or enter return to exit in main menu: "
)
continue@readingAndChangingTypeCycle
} }
} }
} }
/** Interface used to existing commands objects, whose exe value /** Interface used to existing commands objects, whose exe value
* stores function which calls after requesting from console in * stores function which calls after requesting from console in
* requestInput function. * requestInput function.
@ -531,13 +454,14 @@ class ReturnCommand : InputOutcomeCommand {
override val exe: () -> Unit = throw ReturnException() override val exe: () -> Unit = throw ReturnException()
} }
/** Custom exception being thrown if user want to return to the menu. */
class ReturnException : Exception()
fun main() { fun main() {
val textData = TextData() val textData = TextData()
val statisticBuilder = StatisticBuilder() val commandCenter = CommandCenter(textData)
val textReader = TextReader()
val commandCenter = CommandCenter(textData, textReader, statisticBuilder)
workCycle(commandCenter) workCycle(commandCenter)
} }