Skip to content

Commit

Permalink
Allow editing transactions (#181)
Browse files Browse the repository at this point in the history
Fixes #49.
  • Loading branch information
chvp authored Oct 5, 2024
1 parent 05a5530 commit 1c08d52
Show file tree
Hide file tree
Showing 14 changed files with 1,373 additions and 828 deletions.
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
android:name=".ui.add.AddActivity"
android:windowSoftInputMode="adjustResize"
/>
<activity
android:exported="false"
android:name=".ui.edit.EditActivity"
android:windowSoftInputMode="adjustResize"
/>
</application>

</manifest>
84 changes: 59 additions & 25 deletions app/src/main/java/be/chvp/nanoledger/data/LedgerRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ class LedgerRepository
)
}

suspend fun matches(fileUri: Uri): Boolean {
val result = ArrayList<String>()
fileUri
.let { context.contentResolver.openInputStream(it) }
?.let { BufferedReader(InputStreamReader(it)) }
?.use { reader ->
var line = reader.readLine()
while (line != null) {
result.add(line)
line = reader.readLine()
}
}
return result.equals(fileContents.value)
}

suspend fun deleteTransaction(
fileUri: Uri,
transaction: Transaction,
Expand All @@ -47,19 +62,7 @@ class LedgerRepository
onReadError: suspend (IOException) -> Unit,
) {
try {
val result = ArrayList<String>()
fileUri
.let { context.contentResolver.openInputStream(it) }
?.let { BufferedReader(InputStreamReader(it)) }
?.use { reader ->
var line = reader.readLine()
while (line != null) {
result.add(line)
line = reader.readLine()
}
}

if (!result.equals(fileContents.value)) {
if (!matches(fileUri)) {
onMismatch()
} else {
context.contentResolver.openOutputStream(fileUri, "wt")
Expand All @@ -84,28 +87,59 @@ class LedgerRepository
}
}

suspend fun appendTo(
suspend fun replaceTransaction(
fileUri: Uri,
transaction: Transaction,
text: String,
onFinish: suspend () -> Unit,
onMismatch: suspend () -> Unit,
onWriteError: suspend (IOException) -> Unit,
onReadError: suspend (IOException) -> Unit,
) {
try {
val result = ArrayList<String>()
fileUri
.let { context.contentResolver.openInputStream(it) }
?.let { BufferedReader(InputStreamReader(it)) }
?.use { reader ->
var line = reader.readLine()
while (line != null) {
result.add(line)
line = reader.readLine()
if (!matches(fileUri)) {
onMismatch()
} else {
context.contentResolver.openOutputStream(fileUri, "wt")
?.let { OutputStreamWriter(it) }
?.use {
fileContents.value!!.forEachIndexed { i, line ->
// If we encounter the first line of the transaction, write out the replacement
if (i == transaction.firstLine) {
it.write(text)
return@forEachIndexed
}

// Just skip all the next lines
if (i > transaction.firstLine && i <= transaction.lastLine) {
return@forEachIndexed
}

// If the line after the transaction is empty, consider it a
// divider for the next transaction and skip it as well
if (i == transaction.lastLine + 1 && line == "") {
return@forEachIndexed
}
it.write("${line}\n")
}
}
}
readFrom(fileUri, onFinish, onReadError)
}
} catch (e: IOException) {
onWriteError(e)
}
}

if (!result.equals(fileContents.value)) {
suspend fun appendTo(
fileUri: Uri,
text: String,
onFinish: suspend () -> Unit,
onMismatch: suspend () -> Unit,
onWriteError: suspend (IOException) -> Unit,
onReadError: suspend (IOException) -> Unit,
) {
try {
if (!matches(fileUri)) {
onMismatch()
} else {
context.contentResolver.openOutputStream(fileUri, "w")
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/be/chvp/nanoledger/data/Transaction.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package be.chvp.nanoledger.data

data class Amount(val quantity: String, val currency: String, val original: String)

data class Posting(
val account: String,
val amount: String?,
val amount: Amount?,
) {
fun contains(query: String) = account.contains(query, ignoreCase = true)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package be.chvp.nanoledger.data.parser

import be.chvp.nanoledger.data.Amount
import be.chvp.nanoledger.data.Posting
import be.chvp.nanoledger.data.Transaction

val datePart = "((\\d{4}[-/.])?\\d{1,2}[-/.]\\d{1,2}(=(\\d{4}[-/.])?\\d{1,2}[-/.]\\d{1,2})?)"
val headerRegex = Regex("^$datePart[ \t]*(\\*|!)?([^|]*)(\\|(.*))?$")
val postingRegex = Regex("^[ \t]+\\S.*$")
val postingSplitRegex = Regex("[ \\t]{2,}")
val commentRegex = Regex(";.*$")

fun extractTransactions(lines: List<String>): List<Transaction> {
val result = ArrayList<Transaction>()
Expand All @@ -26,20 +25,52 @@ fun extractTransactions(lines: List<String>): List<Transaction> {

val postings = ArrayList<Posting>()
while (i < lines.size && postingRegex.find(lines[i]) != null) {
val stripped = lines[i].trim().replace(commentRegex, "")
i += 1
if (stripped.length > 0) {
lastLine = i - 1
val components = stripped.split(postingSplitRegex, limit = 2)
if (components.size > 1) {
postings.add(Posting(components[0], components[1]))
} else {
postings.add(Posting(components[0], null))
}
val posting = extractPosting(lines[i])
if (posting != null) {
lastLine = i
postings.add(posting)
}
i += 1
}
result.add(Transaction(firstLine, lastLine, date, status, payee, note, postings))
}
}
return result
}

val commentRegex = Regex(";.*$")
val postingSplitRegex = Regex("[ \\t]{2,}")

fun extractPosting(line: String): Posting? {
val stripped = line.trim().replace(commentRegex, "")
if (stripped.length == 0) {
return null
}

val components = stripped.split(postingSplitRegex, limit = 2)
if (components.size == 1) {
return Posting(components[0], null)
}

return Posting(components[0], extractAmount(components[1].trim()))
}

val assertionRegex = Regex("=.*$")
val costRegex = Regex("@.*$")
val quantityAtStartRegex = Regex("^(-? *[0-9][0-9,.]*)(.*)")
val quantityAtEndRegex = Regex("(-? *[0-9][0-9,.]*)$")

fun extractAmount(string: String): Amount {
val stripped = string.trim().replace(assertionRegex, "").trim().replace(costRegex, "").trim()
val matchForStart = quantityAtStartRegex.find(stripped)
if (matchForStart != null) {
val groups = matchForStart.groups
val quantity = groups[1]!!.value.trim()
val currency = groups[2]!!.value.trim()
return Amount(quantity, currency, string)
}
val quantity = quantityAtEndRegex.find(stripped)!!.value.trim()
val currency = stripped.replace(quantityAtEndRegex, "").trim()

return Amount(quantity, currency, string)
}
Loading

0 comments on commit 1c08d52

Please sign in to comment.