commit 544f577b59c1a97336d110c87779dda3238b576e
parent 8f2cfe7a8f20370c748d0dd281f20ee0295f1172
Author: Johann150 <johann.galle@protonmail.com>
Date: Wed, 10 Feb 2021 21:17:39 +0100
use YAML parser for sidecar files
The syntax so far is (compatible with) YAML.
Diffstat:
4 files changed, 94 insertions(+), 54 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -14,6 +14,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"url",
+ "yaml-rust",
]
[[package]]
@@ -142,6 +143,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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",
+]
diff --git a/Cargo.toml b/Cargo.toml
@@ -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
diff --git a/src/main.rs b/src/main.rs
@@ -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 {
diff --git a/src/metadata.rs b/src/metadata.rs
@@ -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);
}
}