use YAML parser for sidecar files

The syntax so far is (compatible with) YAML.
This commit is contained in:
Johann150 2021-02-10 21:17:39 +01:00
parent 8f2cfe7a8f
commit 544f577b59
No known key found for this signature in database
GPG key ID: 9EE6577A2A06F8F1
4 changed files with 94 additions and 54 deletions

16
Cargo.lock generated
View file

@ -14,6 +14,7 @@ dependencies = [
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"url", "url",
"yaml-rust",
] ]
[[package]] [[package]]
@ -141,6 +142,12 @@ version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.14"
@ -539,3 +546,12 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View file

@ -22,6 +22,7 @@ once_cell = "1.5"
percent-encoding = "2.1" percent-encoding = "2.1"
rustls = "0.19.0" rustls = "0.19.0"
url = "2.2" url = "2.2"
yaml-rust = "0.4"
[profile.release] [profile.release]
lto = true lto = true

View file

@ -30,11 +30,12 @@ fn main() -> Result {
.init(); .init();
} }
Runtime::new()?.block_on(async { Runtime::new()?.block_on(async {
let mimetypes = Arc::new(Mutex::new(FileOptions::new(PresetMeta::Parameters( let default = PresetMeta::Parameters(
ARGS.language ARGS.language
.as_ref() .as_ref()
.map_or(String::new(), |lang| format!(";lang={}", lang)), .map_or(String::new(), |lang| format!(";lang={}", lang)),
)))); );
let mimetypes = Arc::new(Mutex::new(FileOptions::new(default)));
let listener = TcpListener::bind(&ARGS.addrs[..]).await?; let listener = TcpListener::bind(&ARGS.addrs[..]).await?;
log::info!("Listening on {:?}...", ARGS.addrs); log::info!("Listening on {:?}...", ARGS.addrs);
loop { loop {

View file

@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::io::{BufRead, BufReader};
use std::path::PathBuf; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
use yaml_rust::YamlLoader;
static SIDECAR_FILENAME: &str = ".meta"; static SIDECAR_FILENAME: &str = ".meta";
@ -108,60 +108,82 @@ impl FileOptions {
db.push(SIDECAR_FILENAME); db.push(SIDECAR_FILENAME);
let db = db.as_path(); let db = db.as_path();
if let Ok(file) = std::fs::File::open(db) { if let Ok(contents) = std::fs::read_to_string(db) {
let r = BufReader::new(file); let docs = match YamlLoader::load_from_str(&contents) {
r.lines() Ok(docs) => docs,
// discard any I/O errors Err(e) => {
.filter_map(|line| line.ok()) log::error!("Invalid YAML document in {:?}: {}", db, e);
// filter out comment lines return;
.filter(|line| !line.trim_start().starts_with('#')) }
.for_each(|line| { };
// split line at colon if let Some(files) = docs.get(0).and_then(|hash| hash.as_hash()) {
let parts = line.splitn(2, ':').collect::<Vec<_>>(); for (rel_path, header) in files {
// only continue if line fits the format // from YAML to Rust types
if parts.len() == 2 { let rel_path = if let Some(rel_path) = rel_path.as_str() {
// generate workspace-unique path rel_path
let mut path = db_dir.clone(); } else {
path.push(parts[0].trim()); log::error!(
// parse the line "Expected string filename, but got {:?} in {:?}",
let header = parts[1].trim(); rel_path,
db
);
continue;
};
let header = if let Some(header) = header.as_str() {
header
} else {
log::error!("Expected string contents, but got {:?} in {:?}", header, db);
continue;
};
let preset = if header.is_empty() || header.starts_with(';') { // generate workspace-unique path
PresetMeta::Parameters(header.to_string()) let mut path = db_dir.clone();
} else if matches!(header.chars().next(), Some('1'..='6')) { path.push(rel_path);
if header.len() < 3
|| !header.chars().nth(1).unwrap().is_ascii_digit() // parse the preset
|| !header.chars().nth(2).unwrap().is_whitespace() let preset = if header.is_empty() || header.starts_with(';') {
{ PresetMeta::Parameters(header.to_string())
log::error!("Line for {:?} starts like a full header line, but it is incorrect; ignoring it.", path); } else if matches!(header.chars().next(), Some('1'..='6')) {
return; if header.len() < 3
} || !header.chars().nth(1).unwrap().is_ascii_digit()
let separator = header.chars().nth(2).unwrap(); || !header.chars().nth(2).unwrap().is_whitespace()
if separator != ' ' { {
// the Gemini specification says that the third log::error!("Line for {:?} starts like a full header line, but it is incorrect; ignoring it.", path);
// character has to be a space, so correct any return;
// other whitespace to it (e.g. tabs) }
log::warn!("Full Header line for {:?} has an invalid character, treating {:?} as a space.", path, separator); let separator = header.chars().nth(2).unwrap();
} if separator != ' ' {
let status = header.chars() // the Gemini specification says that the third
.take(2) // character has to be a space, so correct any
.collect::<String>() // other whitespace to it (e.g. tabs)
.parse::<u8>() log::warn!("Full Header line for {:?} has an invalid character, treating {:?} as a space.", path, separator);
// unwrap since we alread checked it's a number }
.unwrap(); let status = header
// not taking a slice here because the separator .chars()
// might be a whitespace wider than a byte .take(2)
let meta = header.chars().skip(3).collect::<String>(); .collect::<String>()
PresetMeta::FullHeader(status, meta) .parse::<u8>()
} else { // unwrap since we alread checked it's a number
// must be a MIME type, but without status code .unwrap();
PresetMeta::FullMime(header.to_string()) // not taking a slice here because the separator
}; // might be a whitespace wider than a byte
self.file_meta.insert(path, preset); 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())
};
self.file_meta.insert(path, preset);
}
} else {
log::error!("no YAML document {:?}", db);
return;
};
self.databases_read self.databases_read
.insert(db_dir.clone(), SystemTime::now()); .insert(db_dir.clone(), SystemTime::now());
} else {
log::error!("could not read configuration file {:?}", db);
} }
} }