specfold_core/export/
payload.rs1use super::links;
5use crate::corpus::Corpus;
6use crate::graph::Graph;
7use crate::markdown::frontmatter;
8use crate::plugins::payload;
9use crate::translation;
10use serde_json::{Value, json};
11
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub struct Wants {
15 pub body: bool,
16 pub links: bool,
17}
18
19impl Wants {
20 pub fn from(list: &[String]) -> Self {
21 Wants { body: list.iter().any(|w| w == "body"), links: list.iter().any(|w| w == "links") }
22 }
23
24 pub fn any(&self) -> bool {
25 self.body || self.links
26 }
27}
28
29pub fn docs(c: &Corpus, g: &Graph, w: Wants, budget: Option<usize>, only: &[usize]) -> Value {
33 let mut used = 0usize;
34 let root = c.cfg.root.clone();
35 let read = |p: &str| crate::corpus::overlay::read(&root.join(p)).ok();
36 let mut out = Vec::new();
37 for &i in only {
38 let mut v = payload::doc(c, i);
39 if !w.any() {
40 out.push(v);
41 continue;
42 }
43 let d = &c.docs[i];
44 let mut parts = vec![(d.path.clone(), read(&d.path).unwrap_or_default())];
45 let mut chapters = d.chapters.clone();
46 chapters.sort();
47 parts.extend(chapters.into_iter().map(|ch| {
48 let t = read(&ch).unwrap_or_default();
49 (ch, t)
50 }));
51 let bodies: Vec<(String, String)> =
52 parts.iter().map(|(p, t)| (p.clone(), frontmatter::split(t).body.to_string())).collect();
53 let parts_for_rev: Vec<translation::Part> =
54 bodies.iter().map(|(p, b)| translation::Part { label: p.clone(), body: b.clone() }).collect();
55 let m = v.as_object_mut().expect("payload is built as an object");
56 m.insert("hash".into(), json!(translation::rev_of_parts(&parts_for_rev)));
57 let size: usize = bodies.iter().map(|(_, b)| b.len()).sum();
58 let inline = w.body && budget.is_none_or(|b| used + size <= b);
59 if inline {
60 used += size;
61 }
62 let part = |(p, b): &(String, String)| {
63 let mut o = serde_json::Map::new();
64 o.insert("path".into(), json!(p));
65 if w.body {
66 if inline {
67 o.insert("body".into(), json!(b));
68 } else {
69 o.insert("body_path".into(), json!(p));
70 }
71 }
72 if w.links {
73 let ls: Vec<links::Link> = links::of_body(c, g, b).into_iter().map(|l| hide(l, only)).collect();
74 o.insert("links".into(), json!(ls));
75 }
76 Value::Object(o)
77 };
78 let main = part(&bodies[0]);
79 for (k, val) in main.as_object().expect("part() returns an object") {
80 if k != "path" {
81 m.insert(k.clone(), val.clone());
82 }
83 }
84 if bodies.len() > 1 {
85 m.insert("chapter_bodies".into(), Value::Array(bodies[1..].iter().map(part).collect()));
86 }
87 out.push(v);
88 }
89 Value::Array(out)
90}
91
92pub fn visible(c: &Corpus, local: bool) -> Vec<usize> {
94 (0..c.docs.len()).filter(|&i| local || c.docs[i].tier == crate::model::Tier::Committed).collect()
95}
96
97pub fn edges(c: &Corpus, g: &Graph, only: &[usize]) -> Value {
99 Value::Array(
100 g.edges
101 .iter()
102 .filter(|e| only.contains(&e.from) && only.contains(&e.to))
103 .map(|e| json!({"from": c.docs[e.from].id.to_string(), "to": c.docs[e.to].id.to_string(), "kind": e.kind, "anchor": e.anchor}))
104 .collect(),
105 )
106}
107
108fn hide(mut l: links::Link, only: &[usize]) -> links::Link {
109 if let Some(i) = l.idx
110 && !only.contains(&i)
111 {
112 l.target_id = None;
113 l.anchor = None;
114 l.resolved_path = None;
115 l.title = None;
116 l.status = None;
117 l.idx = None;
118 l.reason = Some("not exported".into());
119 }
120 l
121}