add BookParser facade with format detection, Book character addressing tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 21:53:53 +01:00
parent c753a3a147
commit e7c9c018f1
2 changed files with 81 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
import Foundation
public enum BookParserError: Error, CustomStringConvertible {
case unsupportedFormat(String)
public var description: String {
switch self {
case .unsupportedFormat(let ext): "unsupported file format: \(ext)"
}
}
}
public struct BookParser {
public static func parse(url: URL) throws -> Book {
switch url.pathExtension.lowercased() {
case "epub":
return try EPUBParser.parse(url: url)
case "txt", "text":
return try PlainTextParser.parse(url: url)
default:
throw BookParserError.unsupportedFormat(url.pathExtension)
}
}
}

View File

@@ -0,0 +1,57 @@
import Testing
import Foundation
@testable import BookParser
@testable import VorleserKit
@Suite("BookParser")
struct BookParserFacadeTests {
@Test func detectsEPUBByExtension() throws {
let url = Bundle.module.url(forResource: "test", withExtension: "epub", subdirectory: "Fixtures")!
let book = try BookParser.parse(url: url)
#expect(book.chapters.count == 2)
}
@Test func detectsTextByExtension() throws {
let tmpFile = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("test.txt")
try "Paragraph one.\n\nParagraph two.".write(to: tmpFile, atomically: true, encoding: .utf8)
defer { try? FileManager.default.removeItem(at: tmpFile) }
let book = try BookParser.parse(url: tmpFile)
#expect(book.chapters.count == 2)
}
}
@Suite("Book character addressing")
struct BookCharacterAddressingTests {
let book = Book(
title: "Test",
author: nil,
chapters: [
Chapter(index: 0, title: "Ch 1", text: "Hello world."),
Chapter(index: 1, title: "Ch 2", text: "Second chapter."),
]
)
@Test func totalCharacters() {
#expect(book.totalCharacters == 27)
}
@Test func chapterAndLocalOffset() {
let first = book.chapterAndLocalOffset(for: 0)
#expect(first?.chapterIndex == 0)
#expect(first?.localOffset == 0)
let second = book.chapterAndLocalOffset(for: 12)
#expect(second?.chapterIndex == 1)
#expect(second?.localOffset == 0)
}
@Test func sentenceIndex() {
let idx = book.sentenceIndex(containing: 0)
#expect(idx != nil)
}
@Test func outOfRangeReturnsNil() {
#expect(book.chapterAndLocalOffset(for: 9999) == nil)
}
}