1use std::{
2 fmt::{self, Display},
3 fs::File,
4 io::{Read, Write},
5 os::unix::ffi::OsStrExt,
6 path::{Path, PathBuf},
7};
8
9use anyhow::Result;
10use clap::ValueEnum;
11use serde::{Deserialize, Serialize};
12
13pub struct NamedExample {
14 name: String,
15 example: Example,
16}
17
18#[derive(Serialize, Deserialize)]
19pub struct Example {
20 repository_url: String,
21 commit: String,
22 edit_history: Vec<String>,
23 expected_hunks: Vec<String>,
24 expected_patch: String,
25 expected_excerpts: Vec<ExpectedExcerpt>,
26}
27
28#[derive(Serialize, Deserialize)]
29pub struct ExpectedExcerpt {
30 path: PathBuf,
31 text: String,
32}
33
34#[derive(ValueEnum, Debug, Clone)]
35pub enum ExampleFormat {
36 Json,
37 Toml,
38 Md,
39}
40
41impl NamedExample {
42 pub fn load(path: impl AsRef<Path>) -> Result<Self> {
43 let path = path.as_ref();
44 let mut file = File::open(path)?;
45 let ext = path.extension();
46
47 match ext.map(|s| s.as_bytes()) {
48 Some(b"json") => Ok(Self {
49 name: path.file_name().unwrap_or_default().display().to_string(),
50 example: serde_json::from_reader(file)?,
51 }),
52 Some(b"toml") => {
53 let mut content = String::new();
54 file.read_to_string(&mut content)?;
55 Ok(Self {
56 name: path.file_name().unwrap_or_default().display().to_string(),
57 example: toml::from_str(&content)?,
58 })
59 }
60 Some(b"md") => {
61 let mut content = String::new();
62 file.read_to_string(&mut content)?;
63 anyhow::bail!("md todo");
64 }
65 Some(_) => {
66 anyhow::bail!("Unrecognized example extension: {}", ext.unwrap().display());
67 }
68 None => {
69 anyhow::bail!(
70 "Failed to determine example type since the file does not have an extension."
71 );
72 }
73 }
74 }
75
76 pub fn parse_md(input: &str) -> Result<Self> {
77 // use pulldown_cmark::{Event, Parser};
78
79 // let parser = Parser::new(input);
80
81 // for event in parser {
82 // match event {
83 // Event::Start(tag) => {}
84 // Event::End(tag_end) => {}
85 // Event::Text(cow_str) => {}
86 // Event::Code(cow_str) => {
87 // dbg!(cow_str);
88 // }
89 // Event::InlineMath(cow_str) => {}
90 // Event::DisplayMath(cow_str) => {}
91 // Event::Html(cow_str) => {}
92 // Event::InlineHtml(cow_str) => {}
93 // Event::FootnoteReference(cow_str) => {}
94 // Event::SoftBreak => {}
95 // Event::HardBreak => {}
96 // Event::Rule => {}
97 // Event::TaskListMarker(_) => {}
98 // }
99 // }
100
101 todo!();
102 }
103
104 pub fn write(&self, format: ExampleFormat, mut out: impl Write) -> Result<()> {
105 match format {
106 ExampleFormat::Json => Ok(serde_json::to_writer(out, &self.example)?),
107 ExampleFormat::Toml => {
108 Ok(out.write_all(toml::to_string_pretty(&self.example)?.as_bytes())?)
109 }
110 ExampleFormat::Md => Ok(write!(out, "{}", self)?),
111 }
112 }
113}
114
115impl Display for NamedExample {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 write!(f, "# {}\n\n", self.name)?;
118 write!(f, "respository_url = {}\n", self.example.repository_url)?;
119 write!(f, "commit = {}\n\n", self.example.commit)?;
120 write!(f, "## Edit history\n\n")?;
121
122 if !self.example.edit_history.is_empty() {
123 write!(f, "`````diff\n")?;
124 for item in &self.example.edit_history {
125 write!(f, "{item}")?;
126 }
127 write!(f, "`````\n")?;
128 }
129
130 if !self.example.expected_hunks.is_empty() {
131 write!(f, "\n## Expected Hunks\n\n`````diff\n")?;
132 for hunk in &self.example.expected_hunks {
133 write!(f, "{hunk}")?;
134 }
135 write!(f, "`````\n")?;
136 }
137
138 if !self.example.expected_patch.is_empty() {
139 write!(
140 f,
141 "\n## Expected Patch\n\n`````diff\n{}`````\n",
142 self.example.expected_patch
143 )?;
144 }
145
146 if !self.example.expected_excerpts.is_empty() {
147 write!(f, "\n## Expected Excerpts\n\n")?;
148
149 for excerpt in &self.example.expected_excerpts {
150 write!(
151 f,
152 "`````{}path={}\n{}`````\n\n",
153 excerpt
154 .path
155 .extension()
156 .map(|ext| format!("{} ", ext.to_string_lossy().to_string()))
157 .unwrap_or_default(),
158 excerpt.path.display(),
159 excerpt.text
160 )?;
161 }
162 }
163
164 Ok(())
165 }
166}