Implements first version of TodoTxtParse
This commit is contained in:
parent
04165e0068
commit
fb9983c6df
4 changed files with 199 additions and 5 deletions
|
|
@ -10,10 +10,11 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import com.dnsc.plaindo.data.TodoFileRepository
|
import com.dnsc.plaindo.data.TodoFileRepository
|
||||||
import com.dnsc.plaindo.ui.theme.PlaindoTheme
|
import com.dnsc.plaindo.ui.theme.PlaindoTheme
|
||||||
|
|
||||||
|
const val TAG = "MainActivity"
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var todoFileRepository: TodoFileRepository
|
private lateinit var todoFileRepository: TodoFileRepository
|
||||||
private lateinit var openDocumentLauncher: ActivityResultLauncher<Array<String>>
|
private lateinit var openDocumentLauncher: ActivityResultLauncher<Array<String>>
|
||||||
private val TAG = "MainActivity"
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -36,7 +37,11 @@ class MainActivity : ComponentActivity() {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
// TODO: Store uri in shared preferences
|
// TODO: Store uri in shared preferences
|
||||||
// TODO: Persist access to URI
|
// TODO: Persist access to URI
|
||||||
|
todoFileRepository.todoFileUri = uri
|
||||||
todoFileRepository.load(uri)
|
todoFileRepository.load(uri)
|
||||||
|
todoFileRepository.todos.forEach { todo ->
|
||||||
|
Log.d(TAG, todo.toString())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Handle user cancelled selection
|
// TODO: Handle user cancelled selection
|
||||||
Log.d(TAG, "No document selected")
|
Log.d(TAG, "No document selected")
|
||||||
|
|
|
||||||
15
app/src/main/java/com/dnsc/plaindo/data/Todo.kt
Normal file
15
app/src/main/java/com/dnsc/plaindo/data/Todo.kt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.dnsc.plaindo.data
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
data class Todo(
|
||||||
|
val id: Number,
|
||||||
|
val description: String,
|
||||||
|
val completed: Boolean? = false,
|
||||||
|
val priority: Char? = null,
|
||||||
|
val completion: LocalDate? = null,
|
||||||
|
val creation: LocalDate? = null,
|
||||||
|
val projects: List<String>? = null,
|
||||||
|
val contexts: List<String>? = null,
|
||||||
|
val kvTags: MutableMap<String, String>? = null
|
||||||
|
)
|
||||||
|
|
@ -2,23 +2,38 @@ package com.dnsc.plaindo.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
|
||||||
class TodoFileRepository(val context: Context) {
|
class TodoFileRepository(val context: Context) {
|
||||||
|
var todoFileUri: Uri? = null
|
||||||
|
val todos: ArrayList<Todo> = ArrayList()
|
||||||
|
|
||||||
fun load(uri: Uri) {
|
fun load(uri: Uri) {
|
||||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
||||||
var line: String? = reader.readLine()
|
var line: String? = reader.readLine()
|
||||||
while (line != null) {
|
var currentLine = 0
|
||||||
Log.d("Plaindo", line)
|
|
||||||
|
|
||||||
// TODO: Add task to data structure
|
while (line != null) {
|
||||||
|
val todoTxtParser = TodoTxtParser()
|
||||||
|
val todo = todoTxtParser.parse(currentLine, line)
|
||||||
|
todos.add(todo)
|
||||||
|
|
||||||
line = reader.readLine()
|
line = reader.readLine()
|
||||||
|
currentLine++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
if (todoFileUri == null) {
|
||||||
|
// TODO: Show file picker again
|
||||||
|
throw Exception("No todo file selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle more safely
|
||||||
|
load(todoFileUri!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
159
app/src/main/java/com/dnsc/plaindo/data/TodoTxtParser.kt
Normal file
159
app/src/main/java/com/dnsc/plaindo/data/TodoTxtParser.kt
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
package com.dnsc.plaindo.data
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import kotlin.text.isWhitespace
|
||||||
|
|
||||||
|
class TodoTxtParser {
|
||||||
|
fun parse(id: Int, line: String): Todo {
|
||||||
|
val elements = line.split(" ")
|
||||||
|
|
||||||
|
// First rough draft, easy to read
|
||||||
|
// Do not want to go for regexes if possible
|
||||||
|
// Should be more performant to use a single loop here
|
||||||
|
// Will implement an alternative solution if problems
|
||||||
|
// arise
|
||||||
|
val completed = getCompleted(elements)
|
||||||
|
val priority = getPriority(elements, completed)
|
||||||
|
val completion = getCompletion(elements, completed)
|
||||||
|
val creation = getCreation(elements, completed)
|
||||||
|
val projects = getProjects(elements)
|
||||||
|
val contexts = getContexts(elements)
|
||||||
|
val kvTags = getKvTags(elements)
|
||||||
|
val description = getDescription(elements)
|
||||||
|
|
||||||
|
return Todo(
|
||||||
|
id = id,
|
||||||
|
description = description,
|
||||||
|
completed = completed,
|
||||||
|
priority = priority,
|
||||||
|
completion = completion,
|
||||||
|
creation = creation,
|
||||||
|
projects = projects,
|
||||||
|
contexts = contexts,
|
||||||
|
kvTags = kvTags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCompleted(elements: List<String>): Boolean {
|
||||||
|
return elements[0].startsWith('x')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPriority(elements: List<String>, completed: Boolean): Char? {
|
||||||
|
val priorityIndex = if (completed) 1 else 0
|
||||||
|
val hasPriority = isPriorityText(elements[priorityIndex])
|
||||||
|
|
||||||
|
return if (hasPriority) {
|
||||||
|
elements[priorityIndex][1]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCompletion(elements: List<String>, completed: Boolean): LocalDate? {
|
||||||
|
if (completed == false) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val completionIndex = if (isPriorityText(elements[1])) 2 else 1
|
||||||
|
val creationIndex = completionIndex + 1
|
||||||
|
|
||||||
|
Log.d("TodoTxtParser", "creation: ${elements[creationIndex]}")
|
||||||
|
Log.d("TodoTxtParser", "creation isDate: ${isDate(elements[creationIndex])}")
|
||||||
|
|
||||||
|
return if (!isDate(elements[creationIndex])) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
parseDate(elements[completionIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCreation(elements: List<String>, completed: Boolean): LocalDate? {
|
||||||
|
val baseIndex = if (completed) 1 else 0
|
||||||
|
val completionIndex = if (isPriorityText(elements[0])) 2 + baseIndex else 1 + baseIndex
|
||||||
|
val creationIndex = completionIndex + 1
|
||||||
|
|
||||||
|
if (!isDate(elements[completionIndex])) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (isDate(elements[creationIndex])) {
|
||||||
|
parseDate(elements[creationIndex])
|
||||||
|
} else {
|
||||||
|
parseDate(elements[completionIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getProjects(elements: List<String>): List<String> {
|
||||||
|
return elements.filter { it.startsWith('+') }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getContexts(elements: List<String>): List<String> {
|
||||||
|
return elements.filter { it.startsWith('@') }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getKvTags(elements: List<String>): MutableMap<String, String> {
|
||||||
|
val map: MutableMap<String, String> = mutableMapOf()
|
||||||
|
val kvTags = elements.filter { isKvTag(it) }
|
||||||
|
|
||||||
|
kvTags.forEach {
|
||||||
|
map.put(it.split(":")[0], it.split(":")[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDescription(elements: List<String>): String {
|
||||||
|
val filtered = elements.filter {
|
||||||
|
val isPriority = isPriorityText(it)
|
||||||
|
val isCompleted = it[0] == 'x'
|
||||||
|
val isProject = it.startsWith('+')
|
||||||
|
val isContext = it.startsWith('@')
|
||||||
|
val isDate = isDate(it)
|
||||||
|
val isKvTag = it.contains(":")
|
||||||
|
|
||||||
|
return@filter !(isPriority || isCompleted || isProject || isContext || isDate || isKvTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered.joinToString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPriorityText(element: String): Boolean {
|
||||||
|
return element.length == 3 && element.startsWith('(') && element.endsWith(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isKvTag(element: String): Boolean {
|
||||||
|
val separatorIndex = element.indexOf(':')
|
||||||
|
val beforeSeparatorIndex = separatorIndex - 1
|
||||||
|
val afterSeparatorIndex = separatorIndex + 1
|
||||||
|
|
||||||
|
if (beforeSeparatorIndex < 0 || afterSeparatorIndex >= element.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
element[beforeSeparatorIndex].isWhitespace() || element[afterSeparatorIndex].isWhitespace()
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isDate(element: String): Boolean {
|
||||||
|
try {
|
||||||
|
parseDate(element)
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Extend for date time
|
||||||
|
private fun parseDate(dateString: String): LocalDate {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
|
return LocalDate.parse(dateString, formatter)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in a new issue