Make Source less of a hassle to work with

This commit is contained in:
Joscha 2021-11-23 23:59:21 +01:00
parent f39dbdaba7
commit 817732abf6
3 changed files with 51 additions and 35 deletions

View file

@ -13,31 +13,41 @@ mod parse;
#[derive(Debug)] #[derive(Debug)]
struct LoadedFile { struct LoadedFile {
/// Canonical path for this file
path: PathBuf,
// User-readable path for this file
name: PathBuf,
file: File, file: File,
/// Whether this file has been changed
dirty: bool, dirty: bool,
} }
impl LoadedFile { impl LoadedFile {
pub fn new(file: File) -> Self { pub fn new(path: PathBuf, name: PathBuf, file: File) -> Self {
Self { file, dirty: false } Self {
path,
name,
file,
dirty: false,
}
} }
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub struct Source<'a> { pub struct Source {
file: &'a Path, file: usize,
index: usize, command: usize,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct SourcedCommand<'a> { pub struct SourcedCommand<'a> {
pub source: Source<'a>, pub source: Source,
pub command: &'a Command, pub command: &'a Command,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Files { pub struct Files {
files: HashMap<PathBuf, LoadedFile>, files: Vec<LoadedFile>,
timezone: Tz, timezone: Tz,
} }
@ -60,51 +70,58 @@ pub type Result<T> = result::Result<T, Error>;
impl Files { impl Files {
pub fn load(path: &Path) -> Result<Self> { pub fn load(path: &Path) -> Result<Self> {
let mut files = HashMap::new(); let mut paths = HashMap::new();
Self::load_file(&mut files, path)?; let mut files = vec![];
Self::load_file(&mut paths, &mut files, path)?;
let timezone = Self::determine_timezone(&files)?; let timezone = Self::determine_timezone(&files)?;
Ok(Self { files, timezone }) Ok(Self { files, timezone })
} }
fn load_file(files: &mut HashMap<PathBuf, LoadedFile>, path: &Path) -> Result<()> { fn load_file(
let canon_path = path.canonicalize()?; paths: &mut HashMap<PathBuf, usize>,
if files.contains_key(&canon_path) { files: &mut Vec<LoadedFile>,
name: &Path,
) -> Result<()> {
let path = name.canonicalize()?;
if paths.contains_key(&path) {
// We've already loaded this exact file. // We've already loaded this exact file.
return Ok(()); return Ok(());
} }
let content = fs::read_to_string(path)?; let content = fs::read_to_string(name)?;
let file = parse::parse(path, &content)?; // Using `name` instead of `path` for the unwrap below.
let file = parse::parse(name, &content)?;
let includes = file.includes.clone(); let includes = file.includes.clone();
files.insert(canon_path, LoadedFile::new(file)); paths.insert(path.clone(), files.len());
files.push(LoadedFile::new(path, name.to_owned(), file));
for include in includes { for include in includes {
// Since we've successfully opened the file, its name can't be the // Since we've successfully opened the file, its name can't be the
// root directory or empty string and must thus have a parent. // root directory or empty string and must thus have a parent.
let include_path = path.parent().unwrap().join(include); let include_path = name.parent().unwrap().join(include);
Self::load_file(files, &include_path)?; Self::load_file(paths, files, &include_path)?;
} }
Ok(()) Ok(())
} }
fn determine_timezone(files: &HashMap<PathBuf, LoadedFile>) -> Result<Tz> { fn determine_timezone(files: &[LoadedFile]) -> Result<Tz> {
let mut found: Option<(PathBuf, String)> = None; let mut found: Option<(PathBuf, String)> = None;
for file in files.values() { for file in files {
if let Some(file_tz) = &file.file.timezone { if let Some(file_tz) = &file.file.timezone {
if let Some((found_name, found_tz)) = &found { if let Some((found_name, found_tz)) = &found {
if found_tz != file_tz { if found_tz != file_tz {
return Err(Error::TzConflict { return Err(Error::TzConflict {
file1: found_name.clone(), file1: found_name.clone(),
tz1: found_tz.clone(), tz1: found_tz.clone(),
file2: file.file.name.clone(), file2: file.name.clone(),
tz2: file_tz.clone(), tz2: file_tz.clone(),
}); });
} }
} else { } else {
found = Some((file.file.name.clone(), file_tz.clone())); found = Some((file.name.clone(), file_tz.clone()));
} }
} }
} }
@ -117,9 +134,9 @@ impl Files {
} }
pub fn save(&self) -> Result<()> { pub fn save(&self) -> Result<()> {
for (path, file) in &self.files { for file in &self.files {
if file.dirty { if file.dirty {
Self::save_file(path, &file.file)?; Self::save_file(&file.path, &file.file)?;
} }
} }
Ok(()) Ok(())
@ -131,16 +148,19 @@ impl Files {
} }
pub fn mark_all_dirty(&mut self) { pub fn mark_all_dirty(&mut self) {
for (_, file) in self.files.iter_mut() { for file in self.files.iter_mut() {
file.dirty = true; file.dirty = true;
} }
} }
pub fn commands(&self) -> Vec<SourcedCommand<'_>> { pub fn commands(&self) -> Vec<SourcedCommand<'_>> {
let mut result = vec![]; let mut result = vec![];
for (path, file) in &self.files { for (file_index, file) in self.files.iter().enumerate() {
for (index, command) in file.file.commands.iter().enumerate() { for (command_index, command) in file.file.commands.iter().enumerate() {
let source = Source { file: path, index }; let source = Source {
file: file_index,
command: command_index,
};
result.push(SourcedCommand { source, command }); result.push(SourcedCommand { source, command });
} }
} }

View file

@ -1,5 +1,3 @@
use std::path::PathBuf;
use chrono::NaiveDate; use chrono::NaiveDate;
#[derive(Debug)] #[derive(Debug)]
@ -328,7 +326,6 @@ pub enum Command {
#[derive(Debug)] #[derive(Debug)]
pub struct File { pub struct File {
pub name: PathBuf,
pub includes: Vec<String>, pub includes: Vec<String>,
pub timezone: Option<String>, pub timezone: Option<String>,
pub commands: Vec<Command>, pub commands: Vec<Command>,

View file

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf}; use std::path::Path;
use std::result; use std::result;
use chrono::NaiveDate; use chrono::NaiveDate;
@ -720,11 +720,10 @@ fn parse_command(p: Pair<'_, Rule>, file: &mut File) -> Result<()> {
Ok(()) Ok(())
} }
pub fn parse_file(p: Pair<'_, Rule>, name: PathBuf) -> Result<File> { pub fn parse_file(p: Pair<'_, Rule>) -> Result<File> {
assert_eq!(p.as_rule(), Rule::file); assert_eq!(p.as_rule(), Rule::file);
let mut file = File { let mut file = File {
name,
includes: vec![], includes: vec![],
timezone: None, timezone: None,
commands: vec![], commands: vec![],
@ -749,5 +748,5 @@ pub fn parse(path: &Path, input: &str) -> Result<File> {
let file_pair = pairs.next().unwrap(); let file_pair = pairs.next().unwrap();
assert_eq!(pairs.next(), None); assert_eq!(pairs.next(), None);
parse_file(file_pair, path.to_owned()).map_err(|e| e.with_path(&pathstr)) parse_file(file_pair).map_err(|e| e.with_path(&pathstr))
} }