moss_core/page_kind.rs
1//! Classification of moss page/source-file kinds.
2//!
3//! moss recognizes exactly three kinds of page-producing entities in a
4//! source tree. This enum is the canonical way to tell them apart — do
5//! NOT use `is_index`, filename checks, or extension sniffing at call
6//! sites. The kind is set once at ingestion (for markdown files) or
7//! synthesis (for asset pages) and read everywhere else.
8//!
9//! # Variants
10//!
11//! * [`PageKind::Article`] — a markdown file that is not a folder index.
12//! Authored prose content. Listed in children at any depth.
13//! * [`PageKind::Folder`] — a markdown file recognized by
14//! [`crate::home::is_home_file`]. Represents the directory it lives in
15//! (e.g. `文字/文字.md`, `docs/index.md`, `blog/readme.md`). Listed in
16//! children only at depth 1 (at depth `all`, its descendants are listed
17//! directly).
18//!
19//! # Why an enum, not a bool
20//!
21//! The previous model used `is_index: bool` and inferred the rest from
22//! context. That made the children-listing filter impossible to get right.
23//! A third `Asset` variant (synthetic per-image pages) existed until the
24//! image-as-page feature was removed in 2026-07; nothing produced it, so it
25//! was dropped rather than left as an unreachable state. See
26//! `moss/docs/reference/page-kinds.md`.
27
28use serde::{Deserialize, Serialize};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum PageKind {
33 /// Markdown file, not a folder index.
34 Article,
35 /// Markdown file recognized as a folder index by
36 /// [`crate::home::is_home_file`].
37 Folder,
38}
39
40impl PageKind {
41 /// True when pages of this kind appear in children listings at
42 /// `children_depth: all`.
43 pub fn is_listable_at_depth_all(self) -> bool {
44 matches!(self, PageKind::Article)
45 }
46
47 /// True when pages of this kind appear in children listings at
48 /// `children_depth: direct`.
49 pub fn is_listable_at_depth_direct(self) -> bool {
50 matches!(self, PageKind::Article | PageKind::Folder)
51 }
52}
53
54impl Default for PageKind {
55 fn default() -> Self {
56 PageKind::Article
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn listable_at_depth_all_is_article_only() {
66 assert!(PageKind::Article.is_listable_at_depth_all());
67 assert!(!PageKind::Folder.is_listable_at_depth_all());
68 }
69
70 #[test]
71 fn listable_at_depth_direct_is_article_and_folder() {
72 assert!(PageKind::Article.is_listable_at_depth_direct());
73 assert!(PageKind::Folder.is_listable_at_depth_direct());
74 }
75
76 #[test]
77 fn default_is_article() {
78 assert_eq!(PageKind::default(), PageKind::Article);
79 }
80}