1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Structured tldr content kept distinct from authoritative manual pages.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// One normalized quick reference included in a query bundle.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct TldrDocument {
/// Page title, normally the command name.
pub title: String,
/// `CommonMark` paragraphs from the page's leading block quote.
///
/// Source-only soft line breaks are normalized to spaces inside each
/// element; distinct paragraphs remain distinct elements.
pub description: Vec<String>,
/// Upstream reference URL extracted from the page, when present.
#[serde(skip_serializing_if = "Option::is_none")]
pub more_information: Option<String>,
/// Command examples in display order.
pub examples: Vec<TldrExample>,
/// tldr platform bucket such as `common`, `linux`, or `windows`.
pub platform: String,
/// BCP-47-like tldr language directory, such as `en` or `zh`.
pub language: String,
/// Stable source path used for diagnostics and attribution.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub source_path: String,
/// Distinguishes community cache data from a document-owned quick reference.
///
/// The field is omitted for tldr-pages data to preserve the established
/// wire representation. Absence therefore means [`TldrOrigin::TldrPages`].
#[serde(default, skip_serializing_if = "TldrOrigin::is_tldr_pages")]
pub origin: TldrOrigin,
}
/// Provenance controls attribution without changing quick-reference rendering.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum TldrOrigin {
/// Content obtained from the community tldr-pages cache.
#[default]
TldrPages,
/// Quick reference embedded in the authoritative Markdown document.
Embedded,
}
impl TldrOrigin {
/// Return whether this origin is the community tldr-pages project.
#[must_use]
pub const fn is_tldr_pages(&self) -> bool {
matches!(self, Self::TldrPages)
}
}
/// Human explanation paired with one shell command example.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct TldrExample {
/// One normalized prose paragraph describing the command example.
pub description: String,
/// Complete shell command with placeholders in their source spelling.
pub command: String,
/// Styled fragments whose concatenation equals [`Self::command`].
pub command_parts: Vec<TldrCommandPart>,
}
/// Styled command fragment used by the TUI to distinguish placeholders.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(
tag = "type",
rename_all = "kebab-case",
rename_all_fields = "camelCase"
)]
pub enum TldrCommandPart {
/// Literal command text.
Text {
/// Literal fragment value.
value: String,
},
/// Replaceable value marked by the tldr placeholder syntax.
Placeholder {
/// Placeholder text without presentation styling.
value: String,
},
}
#[cfg(test)]
mod tests {
use super::{TldrDocument, TldrOrigin};
#[test]
fn origin_is_additive_and_omitted_for_tldr_pages() {
let page = |origin| TldrDocument {
title: "demo".to_owned(),
description: Vec::new(),
more_information: None,
examples: Vec::new(),
platform: "common".to_owned(),
language: "en".to_owned(),
source_path: "/source/demo.md".to_owned(),
origin,
};
let cached = serde_json::to_value(page(TldrOrigin::TldrPages)).expect("cached page");
assert!(cached.get("origin").is_none());
let embedded = serde_json::to_value(page(TldrOrigin::Embedded)).expect("embedded page");
assert_eq!(embedded["origin"], "embedded");
let mut redacted = page(TldrOrigin::TldrPages);
redacted.source_path.clear();
let redacted = serde_json::to_value(redacted).expect("redacted page");
assert!(redacted.get("sourcePath").is_none());
}
}