//! An example that shows how to implement a simple custom file database. //! The database uses 32-bit file-ids, which could be useful for optimizing //! memory usage. //! //! To run this example, execute the following command from the top level of //! this repository: //! //! ```sh //! cargo run --example custom_files //! ``` use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use std::ops::Range; fn main() -> anyhow::Result<()> { let mut files = files::Files::new(); let file_id0 = files.add("0.greeting", "hello world!").unwrap(); let file_id1 = files.add("1.greeting", "bye world").unwrap(); let messages = vec![ Message::UnwantedGreetings { greetings: vec![(file_id0, 0..5), (file_id1, 0..3)], }, Message::OverTheTopExclamations { exclamations: vec![(file_id0, 11..12)], }, ]; let writer = StandardStream::stderr(ColorChoice::Always); let config = term::Config::default(); for message in &messages { let writer = &mut writer.lock(); term::emit(writer, &config, &files, &message.to_diagnostic())?; } Ok(()) } /// A module containing the file implementation mod files { use codespan_reporting::files; use std::ops::Range; /// A file that is backed by an `Arc`. #[derive(Debug, Clone)] struct File { /// The name of the file. name: String, /// The source code of the file. source: String, /// The starting byte indices in the source code. line_starts: Vec, } impl File { fn line_start(&self, line_index: usize) -> Result { use std::cmp::Ordering; match line_index.cmp(&self.line_starts.len()) { Ordering::Less => Ok(self .line_starts .get(line_index) .expect("failed despite previous check") .clone()), Ordering::Equal => Ok(self.source.len()), Ordering::Greater => Err(files::Error::LineTooLarge { given: line_index, max: self.line_starts.len() - 1, }), } } } /// An opaque file identifier. #[derive(Copy, Clone, PartialEq, Eq)] pub struct FileId(u32); #[derive(Debug, Clone)] pub struct Files { files: Vec, } impl Files { /// Create a new files database. pub fn new() -> Files { Files { files: Vec::new() } } /// Add a file to the database, returning the handle that can be used to /// refer to it again. pub fn add( &mut self, name: impl Into, source: impl Into, ) -> Option { use std::convert::TryFrom; let file_id = FileId(u32::try_from(self.files.len()).ok()?); let name = name.into(); let source = source.into(); let line_starts = files::line_starts(&source).collect(); self.files.push(File { name, line_starts, source, }); Some(file_id) } /// Get the file corresponding to the given id. fn get(&self, file_id: FileId) -> Result<&File, files::Error> { self.files .get(file_id.0 as usize) .ok_or(files::Error::FileMissing) } } impl<'files> files::Files<'files> for Files { type FileId = FileId; type Name = &'files str; type Source = &'files str; fn name(&self, file_id: FileId) -> Result<&str, files::Error> { Ok(self.get(file_id)?.name.as_ref()) } fn source(&self, file_id: FileId) -> Result<&str, files::Error> { Ok(&self.get(file_id)?.source) } fn line_index(&self, file_id: FileId, byte_index: usize) -> Result { self.get(file_id)? .line_starts .binary_search(&byte_index) .or_else(|next_line| Ok(next_line - 1)) } fn line_range( &self, file_id: FileId, line_index: usize, ) -> Result, files::Error> { let file = self.get(file_id)?; let line_start = file.line_start(line_index)?; let next_line_start = file.line_start(line_index + 1)?; Ok(line_start..next_line_start) } } } /// A Diagnostic message. enum Message { UnwantedGreetings { greetings: Vec<(files::FileId, Range)>, }, OverTheTopExclamations { exclamations: Vec<(files::FileId, Range)>, }, } impl Message { fn to_diagnostic(&self) -> Diagnostic { match self { Message::UnwantedGreetings { greetings } => Diagnostic::error() .with_message("greetings are not allowed") .with_labels( greetings .iter() .map(|(file_id, range)| { Label::primary(*file_id, range.clone()).with_message("a greeting") }) .collect(), ) .with_notes(vec![ "found greetings!".to_owned(), "pleas no greetings :(".to_owned(), ]), Message::OverTheTopExclamations { exclamations } => Diagnostic::error() .with_message("over-the-top exclamations") .with_labels( exclamations .iter() .map(|(file_id, range)| { Label::primary(*file_id, range.clone()).with_message("an exclamation") }) .collect(), ) .with_notes(vec!["ridiculous!".to_owned()]), } } }