example.rs

  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}