Chapter

Parse, compare, and sort the chapter identifiers scanlation sources actually use — into correct reading order.

Chapter "numbers" in the wild aren't numbers. Real identifiers from real sources: 10.5 (sub-chapter), 129e / 62b (letter-suffixed extras and parts), 062 (zero-padded), 430f, v30-c378 (volume-chapter compounds). Naive string sorting breaks instantly ("10" < "9"), naive number parsing chokes on the rest — and wrong ordering silently corrupts real behavior: prev/next navigation, resume pointers, new-chapter notifications, caught-up detection.

Chapter.sort(["10.5", "9", "129e", "10", "129"])
#=> ["9", "10", "10.5", "129", "129e"]
Chapter.gt?("v30-c378", "v30-c377.5") #=> true — is this chapter newer?
Chapter.compare("062", "62") #=> :eq — zero padding insignificant
Chapter.number("v30-c378") #=> 378 — display number
Chapter.last_number(~w(1 2 2.5 3)) #=> 3 — count that survives sub-chapters

Pure functions, zero dependencies. Identifiers stay opaque strings — only their order is interpreted.


Install

def deps do
[
{:chapter, "~> 0.1"}
]
end

API

FunctionAnswers
Chapter.sort_key/1a term that orders correctly under Enum.sort_by / term comparison
Chapter.sort/1,2reading order (:asc / :desc)
Chapter.compare/2:lt / :eq / :gt
Chapter.gt?/2, Chapter.before?/2"is this chapter newer / already read?"
Chapter.number/1display integer ("v30-c378"378)
Chapter.last_number/1highest display number in a list ("133 chapters" that isn't inflated by 10.5s)
Chapter.Status.normalize/1series publication status → ongoing / completed / hiatus / unknown (EN + RU variants)

Ordering semantics

sort_key/1 maps each identifier to a comparison-safe term:

Cross-format comparisons (a v-c compound vs a bare number) are inherently ambiguous; real series stick to one format, and keys stay deterministic either way.

Fits with

Built for comic/manga readers — it's the ordering half of implementing a manhwa / manga Store (list_chapters must be reading-ordered), next to dims for the page-dimensions half.

License

MIT