mirror of
https://github.com/YGGverse/agate.git
synced 2026-04-08 20:45:29 +00:00
use configparser crate
This parser can correctly read globs from configuration keys which allows their use not just in theory in the server logic but in the config file too.
This commit is contained in:
parent
fdca530591
commit
197e4592b9
5 changed files with 126 additions and 147 deletions
204
src/metadata.rs
204
src/metadata.rs
|
|
@ -1,8 +1,8 @@
|
|||
use configparser::ini::Ini;
|
||||
use glob::{glob_with, MatchOptions};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use yaml_rust::YamlLoader;
|
||||
|
||||
static SIDECAR_FILENAME: &str = ".meta";
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ impl FileOptions {
|
|||
};
|
||||
db.push(SIDECAR_FILENAME);
|
||||
|
||||
let should_read = if let Ok(metadata) = db.as_path().metadata() {
|
||||
let should_read = if let Ok(metadata) = db.metadata() {
|
||||
if !metadata.is_file() {
|
||||
// it exists, but it is a directory
|
||||
false
|
||||
|
|
@ -109,122 +109,108 @@ impl FileOptions {
|
|||
fn read_database(&mut self, db: &PathBuf) {
|
||||
log::trace!("reading database {:?}", db);
|
||||
|
||||
if let Ok(contents) = std::fs::read_to_string(db) {
|
||||
self.databases_read
|
||||
.insert(db.to_path_buf(), SystemTime::now());
|
||||
let mut ini = Ini::new_cs();
|
||||
ini.set_default_section("mime");
|
||||
let map = ini
|
||||
.load(db.to_str().expect("config path not UTF-8"))
|
||||
.and_then(|mut sections| {
|
||||
sections
|
||||
.remove("mime")
|
||||
.ok_or_else(|| "no \"mime\" or default section".to_string())
|
||||
});
|
||||
self.databases_read
|
||||
.insert(db.to_path_buf(), SystemTime::now());
|
||||
let files = match map {
|
||||
Ok(section) => section,
|
||||
Err(err) => {
|
||||
log::error!("invalid config file {:?}: {}", db, err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let docs = match YamlLoader::load_from_str(&contents) {
|
||||
Ok(docs) => docs,
|
||||
Err(e) => {
|
||||
log::error!("Invalid YAML document in {:?}: {}", db, e);
|
||||
for (rel_path, header) in files {
|
||||
// treat unassigned keys as if they had an empty value
|
||||
let header = header.unwrap_or_default();
|
||||
|
||||
// generate workspace-relative path
|
||||
let mut path = db.clone();
|
||||
path.pop();
|
||||
path.push(rel_path);
|
||||
|
||||
// parse the preset
|
||||
let preset = if header.is_empty() || header.starts_with(';') {
|
||||
PresetMeta::Parameters(header.to_string())
|
||||
} else if matches!(header.chars().next(), Some('1'..='6')) {
|
||||
if header.len() < 3
|
||||
|| !header.chars().nth(1).unwrap().is_ascii_digit()
|
||||
|| !header.chars().nth(2).unwrap().is_whitespace()
|
||||
{
|
||||
log::error!("Line for {:?} starts like a full header line, but it is incorrect; ignoring it.", path);
|
||||
return;
|
||||
}
|
||||
let separator = header.chars().nth(2).unwrap();
|
||||
if separator != ' ' {
|
||||
// the Gemini specification says that the third
|
||||
// character has to be a space, so correct any
|
||||
// other whitespace to it (e.g. tabs)
|
||||
log::warn!("Full Header line for {:?} has an invalid character, treating {:?} as a space.", path, separator);
|
||||
}
|
||||
let status = header
|
||||
.chars()
|
||||
.take(2)
|
||||
.collect::<String>()
|
||||
.parse::<u8>()
|
||||
// unwrap since we alread checked it's a number
|
||||
.unwrap();
|
||||
// not taking a slice here because the separator
|
||||
// might be a whitespace wider than a byte
|
||||
let meta = header.chars().skip(3).collect::<String>();
|
||||
PresetMeta::FullHeader(status, meta)
|
||||
} else {
|
||||
// must be a MIME type, but without status code
|
||||
PresetMeta::FullMime(header.to_string())
|
||||
};
|
||||
if let Some(files) = docs.get(0).and_then(|hash| hash.as_hash()) {
|
||||
for (rel_path, header) in files {
|
||||
// from YAML to Rust types
|
||||
let rel_path = if let Some(rel_path) = rel_path.as_str() {
|
||||
rel_path
|
||||
} else {
|
||||
log::error!(
|
||||
"Expected string filename, but got {:?} in {:?}",
|
||||
rel_path,
|
||||
db
|
||||
);
|
||||
|
||||
let glob_options = MatchOptions {
|
||||
case_sensitive: true,
|
||||
// so there is a difference between "*" and "**".
|
||||
require_literal_separator: true,
|
||||
// security measure because entries for .hidden files
|
||||
// would result in them being exposed.
|
||||
require_literal_leading_dot: !crate::ARGS.serve_secret,
|
||||
};
|
||||
|
||||
// process filename as glob
|
||||
let paths = if let Some(path) = path.to_str() {
|
||||
match glob_with(path, glob_options) {
|
||||
Ok(paths) => paths.collect::<Vec<_>>(),
|
||||
Err(err) => {
|
||||
log::error!("incorrect glob pattern in {:?}: {}", path, err);
|
||||
continue;
|
||||
};
|
||||
let header = if let Some(header) = header.as_str() {
|
||||
header
|
||||
} else {
|
||||
log::error!("Expected string contents, but got {:?} in {:?}", header, db);
|
||||
continue;
|
||||
};
|
||||
|
||||
// generate workspace-relative path
|
||||
let mut path = db.clone();
|
||||
path.pop();
|
||||
path.push(rel_path);
|
||||
|
||||
// parse the preset
|
||||
let preset = if header.is_empty() || header.starts_with(';') {
|
||||
PresetMeta::Parameters(header.to_string())
|
||||
} else if matches!(header.chars().next(), Some('1'..='6')) {
|
||||
if header.len() < 3
|
||||
|| !header.chars().nth(1).unwrap().is_ascii_digit()
|
||||
|| !header.chars().nth(2).unwrap().is_whitespace()
|
||||
{
|
||||
log::error!("Line for {:?} starts like a full header line, but it is incorrect; ignoring it.", path);
|
||||
return;
|
||||
}
|
||||
let separator = header.chars().nth(2).unwrap();
|
||||
if separator != ' ' {
|
||||
// the Gemini specification says that the third
|
||||
// character has to be a space, so correct any
|
||||
// other whitespace to it (e.g. tabs)
|
||||
log::warn!("Full Header line for {:?} has an invalid character, treating {:?} as a space.", path, separator);
|
||||
}
|
||||
let status = header
|
||||
.chars()
|
||||
.take(2)
|
||||
.collect::<String>()
|
||||
.parse::<u8>()
|
||||
// unwrap since we alread checked it's a number
|
||||
.unwrap();
|
||||
// not taking a slice here because the separator
|
||||
// might be a whitespace wider than a byte
|
||||
let meta = header.chars().skip(3).collect::<String>();
|
||||
PresetMeta::FullHeader(status, meta)
|
||||
} else {
|
||||
// must be a MIME type, but without status code
|
||||
PresetMeta::FullMime(header.to_string())
|
||||
};
|
||||
|
||||
let glob_options = MatchOptions {
|
||||
case_sensitive: true,
|
||||
// so there is a difference between "*" and "**".
|
||||
require_literal_separator: true,
|
||||
// security measure because entries for .hidden files
|
||||
// would result in them being exposed.
|
||||
require_literal_leading_dot: !crate::ARGS.serve_secret,
|
||||
};
|
||||
|
||||
// process filename as glob
|
||||
let paths = if let Some(path) = path.to_str() {
|
||||
match glob_with(path, glob_options) {
|
||||
Ok(paths) => paths.collect::<Vec<_>>(),
|
||||
Err(err) => {
|
||||
log::error!("incorrect glob pattern: {}", err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("path is not UTF-8: {:?}", path);
|
||||
continue;
|
||||
};
|
||||
|
||||
if paths.is_empty() {
|
||||
// probably an entry for a nonexistent file, glob only works for existing files
|
||||
self.file_meta.insert(path, preset);
|
||||
} else {
|
||||
for glob_result in paths {
|
||||
match glob_result {
|
||||
Ok(path) if path.is_dir() => { /* ignore */ }
|
||||
Ok(path) => {
|
||||
self.file_meta.insert(path, preset.clone());
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("could not process glob path: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("no YAML document {:?}", db);
|
||||
log::error!("path is not UTF-8: {:?}", path);
|
||||
continue;
|
||||
};
|
||||
|
||||
if paths.is_empty() {
|
||||
// probably an entry for a nonexistent file, glob only works for existing files
|
||||
self.file_meta.insert(path, preset);
|
||||
} else {
|
||||
for glob_result in paths {
|
||||
match glob_result {
|
||||
Ok(path) if path.is_dir() => { /* ignore */ }
|
||||
Ok(path) => {
|
||||
self.file_meta.insert(path, preset.clone());
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("could not process glob path: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("could not read configuration file {:?}", db);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue