Remove DUMMY parser

Koitharu 8 months ago
parent 6ca07aeff7
commit fe5534b006
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -63,12 +63,14 @@ All members of the `MangaParser` class are documented. Pay attention to some pec
- You can use _asserts_ to check some optional fields. For example, the `Manga.author` field is not required, but if - You can use _asserts_ to check some optional fields. For example, the `Manga.author` field is not required, but if
your source provides this information, add `assert(it != null)`. This will not have any effect on production but help your source provides this information, add `assert(it != null)`. This will not have any effect on production but help
to find issues during unit testing. to find issues during unit testing.
- Your parser may also implement the `Interceptor` interface for additional manipulation of all network requests and
responses, including image loading.
- If your source website (or its API) uses pages for pagination instead of offset you should extend `PagedMangaParser` - If your source website (or its API) uses pages for pagination instead of offset you should extend `PagedMangaParser`
instead of `MangaParser`. instead of `MangaParser`.
- If your source website (or its API) does not provide pagination (has only one page of content) you should extend - If your source website (or its API) does not provide pagination (has only one page of content) you should extend
`SinglePageMangaParser` instead of `MangaParser` or `PagedMangaParser`. `SinglePageMangaParser` instead of `MangaParser` or `PagedMangaParser`.
- Your parser may also implement the `Interceptor` interface for additional manipulation of all network requests and
responses, including image loading. ![parser_classes.png](docs/parser_classes.png)
## Development process ## Development process

@ -53,11 +53,9 @@ JVM and Android applications.
`mangaLoaderContext` is an implementation of the `MangaLoaderContext` class. `mangaLoaderContext` is an implementation of the `MangaLoaderContext` class.
See examples See examples
of [Android](https://github.com/KotatsuApp/Kotatsu/blob/devel/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt) of [Android](https://github.com/KotatsuApp/Kotatsu/blob/devel/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt)
and [Non-Android](https://github.com/KotatsuApp/kotatsu-dl/blob/master/src/jvmMain/kotlin/org/koitharu/kotatsu_dl/logic/MangaLoaderContextImpl.kt) and [Non-Android](https://github.com/KotatsuApp/kotatsu-dl/blob/master/src/main/kotlin/org/koitharu/kotatsu/dl/parsers/MangaLoaderContextImpl.kt)
implementation. implementation.
Note that the `MangaParserSource.DUMMY` parsers cannot be instantiated.
## Projects that use the library ## Projects that use the library
- [Kotatsu](https://github.com/KotatsuApp/Kotatsu) - [Kotatsu](https://github.com/KotatsuApp/Kotatsu)

@ -98,7 +98,6 @@ class ParserProcessor(
factoryWriter?.write( factoryWriter?.write(
""" """
MangaParserSource.DUMMY -> throw NotImplementedError("Manga parser ${'$'}name cannot be instantiated")
}.let { }.let {
require(it.source == this) { require(it.source == this) {
"Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}" "Cannot instantiate manga parser: ${'$'}name mapped to ${'$'}{it.source}"
@ -109,7 +108,6 @@ class ParserProcessor(
) )
sourcesWriter?.write( sourcesWriter?.write(
""" """
DUMMY("Dummy", "", ContentType.OTHER, false),
; ;
} }
""".trimIndent(), """.trimIndent(),

@ -1,8 +1,8 @@
package org.koitharu.kotatsu.parsers package org.koitharu.kotatsu.parsers
import org.junit.jupiter.params.provider.EnumSource import org.junit.jupiter.params.provider.EnumSource
import org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
@EnumSource(MangaParserSource::class, names = ["DUMMY"], mode = EXCLUDE) // Change 'names' to test specified parsers
@EnumSource(MangaParserSource::class, names = [], mode = EnumSource.Mode.INCLUDE)
internal annotation class MangaSources internal annotation class MangaSources

@ -12,61 +12,61 @@ import java.util.*
class MangaSearchQueryCapabilitiesTest { class MangaSearchQueryCapabilitiesTest {
private val capabilities = MangaSearchQueryCapabilities( private val capabilities = MangaSearchQueryCapabilities(
capabilities = setOf( capabilities = setOf(
SearchCapability(TITLE_NAME, setOf(Match::class), isMultiple = false, isExclusive = true), SearchCapability(TITLE_NAME, setOf(Match::class), isMultiple = false, isExclusive = true),
SearchCapability(TAG, setOf(Include::class, Exclude::class), isMultiple = true, isExclusive = false), SearchCapability(TAG, setOf(Include::class, Exclude::class), isMultiple = true, isExclusive = false),
SearchCapability(PUBLICATION_YEAR, setOf(Range::class), isMultiple = false, isExclusive = false), SearchCapability(PUBLICATION_YEAR, setOf(Range::class), isMultiple = false, isExclusive = false),
SearchCapability(STATE, setOf(Include::class), isMultiple = false, isExclusive = false), SearchCapability(STATE, setOf(Include::class), isMultiple = false, isExclusive = false),
), ),
) )
@Test @Test
fun validateValidSingleCriterionQuery() { fun validateValidSingleCriterionQuery() {
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Match(TITLE_NAME, "title")) .criterion(Match(TITLE_NAME, "title"))
.build() .build()
assertDoesNotThrow { capabilities.validate(query) } assertDoesNotThrow { capabilities.validate(query) }
} }
@Test @Test
fun validateUnsupportedFieldThrowsException() { fun validateUnsupportedFieldThrowsException() {
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.ENGLISH))) .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.ENGLISH)))
.build() .build()
assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) } assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
} }
@Test @Test
fun validateUnsupportedMultiValueThrowsException() { fun validateUnsupportedMultiValueThrowsException() {
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Include(STATE, setOf(MangaState.ONGOING, MangaState.FINISHED))) .criterion(Include(STATE, setOf(MangaState.ONGOING, MangaState.FINISHED)))
.build() .build()
assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) } assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
} }
@Test @Test
fun validateMultipleCriteriaWithOtherCriteriaAllowed() { fun validateMultipleCriteriaWithOtherCriteriaAllowed() {
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Include(TAG, setOf(buildTag("tag1"), buildTag("tag2")))) .criterion(Include(TAG, setOf(buildTag("tag1"), buildTag("tag2"))))
.criterion(Exclude(TAG, setOf(buildTag("tag3")))) .criterion(Exclude(TAG, setOf(buildTag("tag3"))))
.build() .build()
assertDoesNotThrow { capabilities.validate(query) } assertDoesNotThrow { capabilities.validate(query) }
} }
@Test @Test
fun validateMultipleCriteriaWithStrictCapabilityThrowsException() { fun validateMultipleCriteriaWithStrictCapabilityThrowsException() {
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Match(TITLE_NAME, "title")) .criterion(Match(TITLE_NAME, "title"))
.criterion(Range(PUBLICATION_YEAR, 1990, 2000)) .criterion(Range(PUBLICATION_YEAR, 1990, 2000))
.build() .build()
assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) } assertThrows(IllegalArgumentException::class.java) { capabilities.validate(query) }
} }
private fun buildTag(name: String) = MangaTag(title = name, key = "${name}Key", source = MangaParserSource.DUMMY) private fun buildTag(name: String) = MangaTag(title = name, key = "${name}Key", source = MangaParserSource.MANGADEX)
} }

@ -8,33 +8,30 @@ import java.io.File
class IntentFilterGenerator { class IntentFilterGenerator {
@Test @Test
fun generateIntentFilter() { fun generateIntentFilter() {
val output = File("out/test/resources/intent-filter.xml") val output = File("out/test/resources/intent-filter.xml")
output.printWriter(Charsets.UTF_8).use { writer -> output.printWriter(Charsets.UTF_8).use { writer ->
writer.appendLine("<intent-filter android:autoVerify=\"false\">") writer.appendLine("<intent-filter android:autoVerify=\"false\">")
writer.appendTab().appendLine("<action android:name=\"android.intent.action.VIEW\" />") writer.appendTab().appendLine("<action android:name=\"android.intent.action.VIEW\" />")
writer.appendLine() writer.appendLine()
writer.appendTab().appendLine("<category android:name=\"android.intent.category.DEFAULT\" />") writer.appendTab().appendLine("<category android:name=\"android.intent.category.DEFAULT\" />")
writer.appendTab().appendLine("<category android:name=\"android.intent.category.BROWSABLE\" />") writer.appendTab().appendLine("<category android:name=\"android.intent.category.BROWSABLE\" />")
writer.appendLine() writer.appendLine()
writer.appendTab().appendLine("<data android:scheme=\"http\" />") writer.appendTab().appendLine("<data android:scheme=\"http\" />")
writer.appendTab().appendLine("<data android:scheme=\"https\" />") writer.appendTab().appendLine("<data android:scheme=\"https\" />")
writer.appendLine() writer.appendLine()
for (source in MangaParserSource.entries) { for (source in MangaParserSource.entries) {
if (source == MangaParserSource.DUMMY) { val parser = source.newParser(MangaLoaderContextMock)
continue parser.configKeyDomain.presetValues.forEach { domain ->
} writer.appendTab().append("<data android:host=\"").append(domain).appendLine("\" />")
val parser = source.newParser(MangaLoaderContextMock) }
parser.configKeyDomain.presetValues.forEach { domain -> }
writer.appendTab().append("<data android:host=\"").append(domain).appendLine("\" />") writer.appendLine()
} writer.appendLine("</intent-filter>")
} }
writer.appendLine() println(output.absolutePath)
writer.appendLine("</intent-filter>") }
}
println(output.absolutePath)
}
private fun Appendable.appendTab() = append('\t') private fun Appendable.appendTab() = append('\t')
} }

@ -13,65 +13,65 @@ import java.util.*
class ListFilterToSearchQueryConverterTest { class ListFilterToSearchQueryConverterTest {
@Test @Test
fun convertToMangaSearchQueryTest() { fun convertToMangaSearchQueryTest() {
val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2")) val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
val excludedTags = setOf(buildMangaTag("exclude_tag")) val excludedTags = setOf(buildMangaTag("exclude_tag"))
val states = setOf(MangaState.ONGOING) val states = setOf(MangaState.ONGOING)
val contentRatings = setOf(ContentRating.SAFE) val contentRatings = setOf(ContentRating.SAFE)
val contentTypes = setOf(MANGA, MANHUA) val contentTypes = setOf(MANGA, MANHUA)
val demographics = setOf(SEINEN) val demographics = setOf(SEINEN)
val filter = MangaListFilter( val filter = MangaListFilter(
query = "title_name", query = "title_name",
tags = tags, tags = tags,
tagsExclude = excludedTags, tagsExclude = excludedTags,
locale = Locale.ENGLISH, locale = Locale.ENGLISH,
originalLocale = Locale.JAPANESE, originalLocale = Locale.JAPANESE,
states = states, states = states,
contentRating = contentRatings, contentRating = contentRatings,
types = contentTypes, types = contentTypes,
demographics = demographics, demographics = demographics,
year = 2020, year = 2020,
yearFrom = 1997, yearFrom = 1997,
yearTo = 2024, yearTo = 2024,
) )
val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter) val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
val expectedQuery = MangaSearchQuery.Builder() val expectedQuery = MangaSearchQuery.Builder()
.offset(0) .offset(0)
.order(SortOrder.NEWEST) .order(SortOrder.NEWEST)
.criterion(Match(TITLE_NAME, "title_name")) .criterion(Match(TITLE_NAME, "title_name"))
.criterion(Include(TAG, tags)) .criterion(Include(TAG, tags))
.criterion(Exclude(TAG, excludedTags)) .criterion(Exclude(TAG, excludedTags))
.criterion(Include(LANGUAGE, setOf(Locale.ENGLISH))) .criterion(Include(LANGUAGE, setOf(Locale.ENGLISH)))
.criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE))) .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE)))
.criterion(Include(STATE, states)) .criterion(Include(STATE, states))
.criterion(Include(CONTENT_RATING, contentRatings)) .criterion(Include(CONTENT_RATING, contentRatings))
.criterion(Include(CONTENT_TYPE, contentTypes)) .criterion(Include(CONTENT_TYPE, contentTypes))
.criterion(Include(DEMOGRAPHIC, demographics)) .criterion(Include(DEMOGRAPHIC, demographics))
.criterion(Range(PUBLICATION_YEAR, 1997, 2024)) .criterion(Range(PUBLICATION_YEAR, 1997, 2024))
.criterion(Match(PUBLICATION_YEAR, 2020)) .criterion(Match(PUBLICATION_YEAR, 2020))
.build() .build()
assertEquals(expectedQuery, searchQuery) assertEquals(expectedQuery, searchQuery)
} }
@Test @Test
fun convertToMangaSearchQueryWithEmptyFieldsTest() { fun convertToMangaSearchQueryWithEmptyFieldsTest() {
val filter = MangaListFilter() val filter = MangaListFilter()
val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter) val searchQuery = convertToMangaSearchQuery(0, SortOrder.NEWEST, filter)
assertEquals(MangaSearchQuery.Builder().offset(0).order(SortOrder.NEWEST).build(), searchQuery) assertEquals(MangaSearchQuery.Builder().offset(0).order(SortOrder.NEWEST).build(), searchQuery)
} }
private fun buildMangaTag(name: String): MangaTag { private fun buildMangaTag(name: String): MangaTag {
return MangaTag( return MangaTag(
key = "${name}Key", key = "${name}Key",
title = name, title = name,
source = MangaParserSource.DUMMY, source = MangaParserSource.MANGADEX,
) )
} }
} }

@ -17,78 +17,78 @@ import java.util.*
class ConvertToMangaListFilterTest { class ConvertToMangaListFilterTest {
@Test @Test
fun convertToMangaListFilterTest() { fun convertToMangaListFilterTest() {
val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2")) val tags = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
val excludedTags = setOf(buildMangaTag("exclude_tag")) val excludedTags = setOf(buildMangaTag("exclude_tag"))
val states = setOf(MangaState.ONGOING) val states = setOf(MangaState.ONGOING)
val contentRatings = setOf(ContentRating.SAFE) val contentRatings = setOf(ContentRating.SAFE)
val contentTypes = setOf(MANGA, MANHUA) val contentTypes = setOf(MANGA, MANHUA)
val demographics = setOf(SEINEN) val demographics = setOf(SEINEN)
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Match(TITLE_NAME, "title_name")) .criterion(Match(TITLE_NAME, "title_name"))
.criterion(Include(TAG, tags)) .criterion(Include(TAG, tags))
.criterion(Exclude(TAG, excludedTags)) .criterion(Exclude(TAG, excludedTags))
.criterion(Include(LANGUAGE, setOf(Locale.ENGLISH))) .criterion(Include(LANGUAGE, setOf(Locale.ENGLISH)))
.criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE))) .criterion(Include(ORIGINAL_LANGUAGE, setOf(Locale.JAPANESE)))
.criterion(Include(STATE, states)) .criterion(Include(STATE, states))
.criterion(Include(CONTENT_RATING, contentRatings)) .criterion(Include(CONTENT_RATING, contentRatings))
.criterion(Include(CONTENT_TYPE, contentTypes)) .criterion(Include(CONTENT_TYPE, contentTypes))
.criterion(Include(DEMOGRAPHIC, demographics)) .criterion(Include(DEMOGRAPHIC, demographics))
.criterion(Range(PUBLICATION_YEAR, 1997, 2024)) .criterion(Range(PUBLICATION_YEAR, 1997, 2024))
.criterion(Match(PUBLICATION_YEAR, 2020)) .criterion(Match(PUBLICATION_YEAR, 2020))
.build() .build()
val listFilter = convertToMangaListFilter(query) val listFilter = convertToMangaListFilter(query)
assertEquals(listFilter.query, "title_name") assertEquals(listFilter.query, "title_name")
assertEquals(listFilter.tags, tags) assertEquals(listFilter.tags, tags)
assertEquals(listFilter.tagsExclude, excludedTags) assertEquals(listFilter.tagsExclude, excludedTags)
assertEquals(listFilter.locale, Locale.ENGLISH) assertEquals(listFilter.locale, Locale.ENGLISH)
assertEquals(listFilter.originalLocale, Locale.JAPANESE) assertEquals(listFilter.originalLocale, Locale.JAPANESE)
assertEquals(listFilter.states, states) assertEquals(listFilter.states, states)
assertEquals(listFilter.contentRating, contentRatings) assertEquals(listFilter.contentRating, contentRatings)
assertEquals(listFilter.types, contentTypes) assertEquals(listFilter.types, contentTypes)
assertEquals(listFilter.demographics, demographics) assertEquals(listFilter.demographics, demographics)
assertEquals(listFilter.year, 2020) assertEquals(listFilter.year, 2020)
assertEquals(listFilter.yearFrom, 1997) assertEquals(listFilter.yearFrom, 1997)
assertEquals(listFilter.yearTo, 2024) assertEquals(listFilter.yearTo, 2024)
} }
@Test @Test
fun convertToMangaListFilterWithMultipleTagsIncludeTest() { fun convertToMangaListFilterWithMultipleTagsIncludeTest() {
val tags1 = setOf(buildMangaTag("tag1"), buildMangaTag("tag2")) val tags1 = setOf(buildMangaTag("tag1"), buildMangaTag("tag2"))
val tags2 = setOf(buildMangaTag("tag3"), buildMangaTag("tag4")) val tags2 = setOf(buildMangaTag("tag3"), buildMangaTag("tag4"))
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Include(TAG, tags1)) .criterion(Include(TAG, tags1))
.criterion(Include(TAG, tags2)) .criterion(Include(TAG, tags2))
.build() .build()
val listFilter = convertToMangaListFilter(query) val listFilter = convertToMangaListFilter(query)
assertEquals(listFilter.tags, tags1 union tags2) assertEquals(listFilter.tags, tags1 union tags2)
} }
@Test @Test
fun convertToMangaListFilterWithUnsupportedFieldTest() { fun convertToMangaListFilterWithUnsupportedFieldTest() {
val query = MangaSearchQuery.Builder() val query = MangaSearchQuery.Builder()
.criterion(Include(AUTHOR, setOf(buildMangaTag("author")))) .criterion(Include(AUTHOR, setOf(buildMangaTag("author"))))
.build() .build()
val exception = assertThrows<IllegalArgumentException> { val exception = assertThrows<IllegalArgumentException> {
convertToMangaListFilter(query) convertToMangaListFilter(query)
} }
assert(exception.message!!.contains("Unsupported field for Include criterion: AUTHOR")) assert(exception.message!!.contains("Unsupported field for Include criterion: AUTHOR"))
} }
private fun buildMangaTag(name: String): MangaTag { private fun buildMangaTag(name: String): MangaTag {
return MangaTag( return MangaTag(
key = "${name}Key", key = "${name}Key",
title = name, title = name,
source = MangaParserSource.DUMMY, source = MangaParserSource.MANGADEX,
) )
} }
} }

Loading…
Cancel
Save