fastpaper/commands/
read.rs1use super::{CommandResult, emit, failed};
2use crate::cli::{GlobalOpts, OutputFormat, ReadArgs, Section};
3use crate::read as pdf;
4
5pub fn run(args: &ReadArgs, global: &GlobalOpts) -> CommandResult {
6 if !args.path.exists() {
7 return Err(failed(format!(
8 "No such file: {}\n`read` works on a local PDF. To fetch one first: fastpaper download <id>",
9 args.path.display()
10 )));
11 }
12
13 let full_text = pdf::extract_text(&args.path).map_err(failed)?;
14
15 let text = match heading_for(args.section) {
16 None => full_text,
17 Some(heading) => pdf::extract_section(&full_text, heading).ok_or_else(|| {
18 failed(format!(
19 "No '{}' section found in {}",
20 heading,
21 args.path.display()
22 ))
23 })?,
24 };
25
26 let text = match args.max_length {
27 Some(max) => text.chars().take(max).collect::<String>(),
28 None => text,
29 };
30
31 let rendered = match global.format {
32 OutputFormat::Json => serde_json::to_string_pretty(&serde_json::json!({
33 "path": args.path.to_string_lossy(),
34 "section": section_name(args.section),
35 "content": { "full_text": text },
36 }))
37 .unwrap(),
38 _ => text,
39 };
40
41 emit(&rendered, args.output.as_deref())
42}
43
44fn heading_for(section: Section) -> Option<&'static str> {
46 match section {
47 Section::Full => None,
48 Section::Abstract => Some("abstract"),
49 Section::Introduction => Some("introduction"),
50 Section::Methods => Some("methods"),
51 Section::Results => Some("results"),
52 Section::Discussion => Some("discussion"),
53 Section::Conclusion => Some("conclusion"),
54 Section::References => Some("references"),
55 }
56}
57
58fn section_name(section: Section) -> &'static str {
59 heading_for(section).unwrap_or("full")
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn full_means_no_slicing() {
68 assert_eq!(heading_for(Section::Full), None);
69 }
70
71 #[test]
74 fn every_section_value_maps_to_a_known_heading() {
75 for section in [
76 Section::Abstract,
77 Section::Introduction,
78 Section::Methods,
79 Section::Results,
80 Section::Discussion,
81 Section::Conclusion,
82 Section::References,
83 ] {
84 let heading = heading_for(section).expect("should map to a heading");
85 let paper = format!("Title\nAbstract\nx\n{}\ncontent here\n", heading);
86 assert!(
87 crate::read::extract_section(&paper, heading).is_some(),
88 "{:?} maps to '{}' which the extractor does not find",
89 section,
90 heading
91 );
92 }
93 }
94
95 #[test]
96 fn section_name_reports_full_for_whole_document() {
97 assert_eq!(section_name(Section::Full), "full");
98 assert_eq!(section_name(Section::Methods), "methods");
99 }
100}