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-rustls",
"url",
"yaml-rust",
]
[[package]]
@ -141,6 +142,12 @@ version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "log"
version = "0.4.14"
@ -539,3 +546,12 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
rustls = "0.19.0"
url = "2.2"
yaml-rust = "0.4"
[profile.release]
lto = true

View file

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

View file

@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::time::SystemTime;
use yaml_rust::YamlLoader;
static SIDECAR_FILENAME: &str = ".meta";
@ -108,60 +108,82 @@ impl FileOptions {
db.push(SIDECAR_FILENAME);
let db = db.as_path();
if let Ok(file) = std::fs::File::open(db) {
let r = BufReader::new(file);
r.lines()
// discard any I/O errors
.filter_map(|line| line.ok())
// filter out comment lines
.filter(|line| !line.trim_start().starts_with('#'))
.for_each(|line| {
// split line at colon
let parts = line.splitn(2, ':').collect::<Vec<_>>();
// only continue if line fits the format
if parts.len() == 2 {
// generate workspace-unique path
let mut path = db_dir.clone();
path.push(parts[0].trim());
// parse the line
let header = parts[1].trim();
if let Ok(contents) = std::fs::read_to_string(db) {
let docs = match YamlLoader::load_from_str(&contents) {
Ok(docs) => docs,
Err(e) => {
log::error!("Invalid YAML document in {:?}: {}", db, e);
return;
}
};
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
);
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(';') {
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())
};
self.file_meta.insert(path, preset);
}
});
// generate workspace-unique path
let mut path = db_dir.clone();
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())
};
self.file_meta.insert(path, preset);
}
} else {
log::error!("no YAML document {:?}", db);
return;
};
self.databases_read
.insert(db_dir.clone(), SystemTime::now());
} else {
log::error!("could not read configuration file {:?}", db);
}
}