Skip to content

Commit

Permalink
New list format
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcinMoskala committed Mar 16, 2023
1 parent cea6dfd commit dca0c73
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 70 deletions.
2 changes: 1 addition & 1 deletion src/commonMain/kotlin/Note.kt
Expand Up @@ -39,7 +39,7 @@ sealed class Note {
val type: ListType = ListType.List,
val title: String,
val items: List<Item>,
val generalComment: String = ""
val extra: String = ""
) : Note() {
data class Item(val value: String, val comment: String = "")

Expand Down
37 changes: 21 additions & 16 deletions src/commonMain/kotlin/note/ListDeletionParser.kt
Expand Up @@ -4,11 +4,11 @@ import deckmarkdown.Note
import deckmarkdown.Note.ListDeletion
import deckmarkdown.Note.ListDeletion.ListType
import deckmarkdown.api.ApiNote
import note.MarkdownParser
import deckmarkdown.recognizeKeyValueLines

object ListDeletionParser : FullNoteProcessor<ListDeletion> {
private val LIST_QUESTION_REGEX = "^([LlSs]):([^\\n]+)\\n([^*]*)\\*".toRegex()
private val LIST_ITEM_REGEX = "\\*\\s*([^\\n]*)(\\n([^*]*))?".toRegex()
private val LIST_QUESTION_START_REGEX = "^([LlSs]):([^\\n]+)\\n([^*]*)\\*".toRegex()
private val LIST_ITEM_REGEX = "\\*\\s*([^\\n:]+)((: (([^\\n]*)(\\n([^*]*))))?)".toRegex()

private val API_NOTE_TO_TYPE = mapOf(
"ListDeletion" to ListType.List,
Expand All @@ -17,36 +17,42 @@ object ListDeletionParser : FullNoteProcessor<ListDeletion> {

override fun handlesNote(note: Note): Boolean = note is ListDeletion

override fun recognize(text: String): Boolean = LIST_QUESTION_REGEX in text
override fun recognize(text: String): Boolean = LIST_QUESTION_START_REGEX in text

override fun parse(id: Long?, noteText: String): ListDeletion {
val parsedStart = checkNotNull(LIST_QUESTION_REGEX.find(noteText))
val prefix = parsedStart.groupValues[1]
val question = parsedStart.groupValues[2].trim().trimEnd()
val generalComment = parsedStart.groupValues[3].trim().trimEnd()
val map = noteText.recognizeKeyValueLines() ?: error("There must be a key, at least S: or L:")
val (prefix, titleAndItemsText) = map.toList().first()
val title = titleAndItemsText.substringBefore("\n").trim()
val points = titleAndItemsText.substringAfter("\n")

val listType = when (prefix.lowercase()) {
"s" -> ListType.Set
else -> ListType.List
}

val items = LIST_ITEM_REGEX.findAll(noteText)
val items = LIST_ITEM_REGEX.findAll(points + "\n")
.map {
val value = it.groupValues[1].trim().trimEnd()
val comment = it.groupValues[3].trim().trimEnd()
val comment = it.groupValues[4].trim().trimEnd()
ListDeletion.Item(value, comment)
}
.toList()

return ListDeletion(id, listType, question, items, generalComment)
return ListDeletion(
id = id,
type = listType,
title = title,
items = items,
extra = map["Extra"] ?: map["extra"] ?: ""
)
}

override fun render(note: ListDeletion): String = "${if (note.type == ListType.List) "L" else "S"}: {question}\n"
.replace("{question}", note.title)
.plus(if (note.generalComment.isNotBlank()) "${note.generalComment}\n" else "")
.plus(note.items.joinToString(separator = "\n") {
if (it.comment.isBlank()) "* ${it.value}" else "* ${it.value}\n${it.comment}"
if (it.comment.isBlank()) "* ${it.value}" else "* ${it.value} - ${it.comment}"
})
.plus(if (note.extra.isNotBlank()) "\nExtra: ${note.extra}" else "")

override fun recognizeApiNote(apiNote: ApiNote): Boolean = apiNote.modelName in API_NOTE_TO_TYPE.keys

Expand All @@ -56,8 +62,7 @@ object ListDeletionParser : FullNoteProcessor<ListDeletion> {
modelName = API_NOTE_TO_TYPE.reverseLookup(note.type),
fields = ApiNote.fieldsOf(
"Title" to note.title,
"General Comment" to note.generalComment,
"Extra" to comment
"Extra" to note.extra
) + note.items
.withIndex()
.flatMap { (index, item) ->
Expand All @@ -82,7 +87,7 @@ object ListDeletionParser : FullNoteProcessor<ListDeletion> {
comment
)
},
generalComment = apiNote.field("General Comment")
extra = apiNote.field("Extra")
)

override fun toHtml(note: ListDeletion): String = "TODO"
Expand Down
21 changes: 19 additions & 2 deletions src/commonTest/kotlin/E2ETest.kt
Expand Up @@ -46,13 +46,30 @@ abstract class E2ETest {
protected fun listApi(
title: String,
items: Map<String, String>,
generalComment: String = "",
extra: String = "",
noteId: Long = 0
) = ApiNote(
noteId, deckName, "ListDeletion", mapOf(
"Title" to title,
"General Comment" to generalComment,
"Extra" to extra,
*items.toList().flatMapIndexed { index: Int, (text, comment) ->
val num = index + 1
listOf(
"$num" to text,
"$num comment" to comment
)
}.toTypedArray()
)
)

protected fun setApi(
title: String,
items: Map<String, String>,
extra: String = "",
noteId: Long = 0
) = ApiNote(
noteId, deckName, "SetDeletion", mapOf(
"Title" to title,
"Extra" to extra,
*items.toList().flatMapIndexed { index: Int, (text, comment) ->
val num = index + 1
Expand Down
63 changes: 37 additions & 26 deletions src/commonTest/kotlin/PushingTest.kt
Expand Up @@ -311,44 +311,55 @@ class PushingTest: E2ETest() {
)

@Test
fun listMultilineComments() = testPush(
fun setMultilineCommentsExtra() = testPush(
markdown = """
S: What are HTTP methods?
* GET - The GET method requests a representation of the specified resource.
* GET: The GET method requests a representation of the specified resource.
Requests using GET should only retrieve data.
* HEAD - The HEAD method asks for a response identical to a GET request,
* HEAD: The HEAD method asks for a response identical to a GET request,
but without the response body.
* POST - The POST method submits an entity to the specified resource,
* POST: The POST method submits an entity to the specified resource,
often causing a change in state or side effects on the server.
generalComment: Those are not all the methods, there are also...
* DELETE
* and others
Extra: Those are not all the methods, there are also...
""".trimIndent(),
expectedNotes = listOf(
Note.ListDeletion(
type = Note.ListDeletion.ListType.List,
title = "What is the alphabet?",
items = listOf(
Note.ListDeletion.Item("A"),
Note.ListDeletion.Item("B"),
Note.ListDeletion.Item("C"),
)
)
),
// expectedNotes = listOf(
// Note.ListDeletion(
// type = Note.ListDeletion.ListType.Set,
// title = "What is the alphabet?",
// items = listOf(
// Note.ListDeletion.Item("A"),
// Note.ListDeletion.Item("B"),
// Note.ListDeletion.Item("C"),
// )
// )
// ),
expectedApiNotes = listOf(
listApi(
title = "What is the alphabet?",
setApi(
title = "What are HTTP methods?",
items = mapOf(
"A" to "",
"B" to "",
"C" to "",
)
"GET" to "The GET method requests a representation of the specified resource. \nRequests using GET should only retrieve data.",
"HEAD" to "The HEAD method asks for a response identical to a GET request, \nbut without the response body.",
"POST" to "The POST method submits an entity to the specified resource, \noften causing a change in state or side effects on the server.",
"DELETE" to "",
"and others" to "",
),
extra = "Those are not all the methods, there are also..."
)
),
expectedMarkdown = """
@0
L: What is the alphabet?
* A
* B
* C
S: What are HTTP methods?
* GET - The GET method requests a representation of the specified resource.
Requests using GET should only retrieve data.
* HEAD - The HEAD method asks for a response identical to a GET request,
but without the response body.
* POST - The POST method submits an entity to the specified resource,
often causing a change in state or side effects on the server.
* DELETE
* and others
Extra: Those are not all the methods, there are also...
""".trimIndent()
)

Expand Down
22 changes: 4 additions & 18 deletions src/jvmTest/kotlin/ParseListDeletionTests.kt
Expand Up @@ -12,29 +12,18 @@ class ParseListDeletionTests {
private val parser = DeckParser(processors = listOf(ListDeletionParser))

@Test
fun `Simple List is parsed correctly, second line is treated as a general comment`() {
fun `Simple List is parsed correctly`() {
val text = """
L: First 3 letters
* a
* b
* c
L: First 3 letters
In the English alphabet
* a
* b
* c
""".trimIndent()
val expected = listOf<Note>(
Note.ListDeletion(
title = "First 3 letters",
items = listOf(Item("a"), Item("b"), Item("c"))
),
Note.ListDeletion(
title = "First 3 letters",
generalComment = "In the English alphabet",
items = listOf(Item("a"), Item("b"), Item("c"))
)
)
assertEquals(expected, parser.parseNotes(text))
}
Expand All @@ -43,14 +32,11 @@ In the English alphabet
fun `A lines below item is understood as a comment`() {
val text = """
L: First 3 letters
* a
aaaaa
* a: aaaaa
next
and more
* b
bbbbb
* c
ccccc
* b: bbbbb
* c: ccccc
""".trimIndent()
val expected = listOf<Note>(
Note.ListDeletion(
Expand Down
3 changes: 1 addition & 2 deletions src/jvmTest/kotlin/ProcessTests.kt
Expand Up @@ -28,8 +28,7 @@ class ProcessTests {
@10
L: AAA
* A
* B
Comment
* B - Comment
* C
"""
).map { it.trimIndent() }
Expand Down
3 changes: 1 addition & 2 deletions src/jvmTest/kotlin/WriteTests.kt
Expand Up @@ -71,8 +71,7 @@ a: DDD
@1
L: AAA
* A
* B
Comment
* B - Comment
* C
""".trimIndent()
assertEquals(expected, text)
Expand Down
5 changes: 2 additions & 3 deletions src/jvmTest/kotlin/toanki/AnkiNoteFromListTest.kt
Expand Up @@ -20,7 +20,7 @@ class AnkiNoteFromListTest {
val note = Note.ListDeletion(
id = id,
title = "AAA",
generalComment = "General Comment",
extra = "General Comment",
items = listOf(Item("a"), Item("b", "comment"))
)
val actual = parser.noteToApiNote(note, deckName, comment)
Expand All @@ -30,8 +30,7 @@ class AnkiNoteFromListTest {
modelName = "ListDeletion",
fields = mapOf(
"Title" to "AAA",
"General Comment" to "General Comment",
"Extra" to comment,
"Extra" to "General Comment",
"1" to "a",
"1 comment" to "",
"2" to "b",
Expand Down

0 comments on commit dca0c73

Please sign in to comment.