Compare commits
2 Commits
017c77366e
...
8fa2bd97af
Author | SHA1 | Date |
---|---|---|
Emma Tebibyte | 8fa2bd97af | |
Emma Tebibyte | ed9cd8edcc |
|
@ -2,6 +2,8 @@
|
||||||
name = "plaque"
|
name = "plaque"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
authors = [ "Emma Tebibyte <emma@tebibyte.media>" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
charsets = { git = "https://git.tebibyte.media/emma/rust-charsets.git", version = "0.3.0" }
|
charsets = { git = "https://git.tebibyte.media/emma/rust-charsets.git", version = "0.3.0" }
|
||||||
|
|
|
@ -51,6 +51,8 @@ pub struct PageMeta {
|
||||||
pub lang: Language,
|
pub lang: Language,
|
||||||
|
|
||||||
pub template: String,
|
pub template: String,
|
||||||
|
|
||||||
|
pub kind: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_path(xdg: &str) -> Result<String, (String, u32)> {
|
pub fn get_path(xdg: &str) -> Result<String, (String, u32)> {
|
||||||
|
@ -58,16 +60,16 @@ pub fn get_path(xdg: &str) -> Result<String, (String, u32)> {
|
||||||
|
|
||||||
if unsafe { libc::geteuid() } == 0 {
|
if unsafe { libc::geteuid() } == 0 {
|
||||||
dir = match xdg {
|
dir = match xdg {
|
||||||
"XDG_CACHE_HOME" => "/var/cms".to_owned(),
|
"XDG_CACHE_HOME" => "/var".to_owned(),
|
||||||
"XDG_CONFIG_HOME" => "/etc/cms".to_owned(),
|
"XDG_CONFIG_HOME" => "/etc".to_owned(),
|
||||||
_ => panic!("nya"),
|
_ => panic!("nya"),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
dir = match env::var(xdg) {
|
dir = match env::var(xdg) {
|
||||||
Ok(val) => val,
|
Ok(var) => var,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
match env::var("HOME") {
|
match env::var("HOME") {
|
||||||
Ok(home) => format!("{}/.config/", home),
|
Ok(home) => format!("{}/.config", home),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err((
|
return Err((
|
||||||
"Unable to determine path to current user’s home directory."
|
"Unable to determine path to current user’s home directory."
|
||||||
|
@ -79,11 +81,8 @@ pub fn get_path(xdg: &str) -> Result<String, (String, u32)> {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
match xdg {
|
|
||||||
"XDG_CACHE_HOME" => Ok(format!("{}/cms", dir)),
|
Ok(format!("{}/plaque", dir))
|
||||||
"XDG_CONFIG_HOME" => Ok(format!("{}/cms", dir)),
|
|
||||||
_ => panic!("nya"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -97,14 +96,13 @@ impl Config {
|
||||||
Err(err) => return Err((format!("{:?}", err), 1)),
|
Err(err) => return Err((format!("{:?}", err), 1)),
|
||||||
};
|
};
|
||||||
|
|
||||||
match config_file.read_to_end(&mut buf) {
|
if let Some(err) = config_file.read_to_end(&mut buf).err() {
|
||||||
Ok(_) => {},
|
return Err((format!("{:?}", err), EX_DATAERR));
|
||||||
Err(err) => return Err((format!("{:?}", err), 1)),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let toml = String::from_utf8(buf).unwrap();
|
let toml = String::from_utf8(buf).unwrap();
|
||||||
|
|
||||||
match Config::deserialize(ValueDeserializer::new(&toml)) {
|
match toml::from_str(&toml) {
|
||||||
Ok(val) => Ok(val),
|
Ok(val) => Ok(val),
|
||||||
Err(err) => Err((format!("{:?}", err), 1)),
|
Err(err) => Err((format!("{:?}", err), 1)),
|
||||||
}
|
}
|
||||||
|
@ -123,7 +121,7 @@ impl PageMeta {
|
||||||
match Self::deserialize(ValueDeserializer::new(&input)) {
|
match Self::deserialize(ValueDeserializer::new(&input)) {
|
||||||
Ok(val) => Ok(val),
|
Ok(val) => Ok(val),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
Err(("Could not deserialized TOML.".to_owned(), EX_DATAERR))
|
Err(("Invalid configuration file.".to_owned(), EX_DATAERR))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
246
src/main.rs
246
src/main.rs
|
@ -19,204 +19,102 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env::args,
|
env::args,
|
||||||
io::{ Read, Write },
|
io::Write,
|
||||||
fs::{ File, ReadDir, read_dir },
|
fs::{ File, read_dir },
|
||||||
};
|
};
|
||||||
|
|
||||||
use config::*;
|
use config::*;
|
||||||
|
use parse::*;
|
||||||
|
|
||||||
use pulldown_cmark::{ Parser, Options, html };
|
use pulldown_cmark::{ Parser, Options, html };
|
||||||
use toml::Value;
|
use tl::Node;
|
||||||
use tl::ParserOptions;
|
|
||||||
use yacexits::*;
|
use yacexits::*;
|
||||||
|
|
||||||
fn get_contents(dir: ReadDir) -> Result<Vec<(String, Value)>, (String, u32)> {
|
|
||||||
let mut out: Vec<(String, Value)> = Vec::new();
|
|
||||||
let mut md: String;
|
|
||||||
let mut toml = String::new();
|
|
||||||
|
|
||||||
for entry in dir {
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
|
|
||||||
let file = match entry {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(_) => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_name = match file.file_name().into_string() {
|
|
||||||
Ok(name) => name,
|
|
||||||
Err(_) => {
|
|
||||||
return Err((format!("Invalid file name."), EX_DATAERR));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let contents = match File::open(&file_name) {
|
|
||||||
Ok(mut md_file) => {
|
|
||||||
md_file.read_to_end(&mut buf).unwrap();
|
|
||||||
String::from_utf8(buf).unwrap()
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
return Err((
|
|
||||||
format!("{}: No such file or directory.", file_name),
|
|
||||||
EX_UNAVAILABLE
|
|
||||||
));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut lines = contents.lines();
|
|
||||||
|
|
||||||
if lines.next() == Some("+++\n") {
|
|
||||||
let mut line = lines.next();
|
|
||||||
|
|
||||||
while line.unwrap() != "+++\n" {
|
|
||||||
toml.push_str(&mut line.unwrap());
|
|
||||||
line = lines.next();
|
|
||||||
}
|
|
||||||
md = lines.collect::<Vec<&str>>().as_mut_slice().join("");
|
|
||||||
|
|
||||||
let parsed_toml = match toml.parse::<Value>() {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(_) => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"{}: Could not parse TOML.",
|
|
||||||
file_name,
|
|
||||||
),
|
|
||||||
EX_DATAERR,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
out.push((md, parsed_toml));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let argv = args().collect::<Vec<String>>();
|
let argv = args().collect::<Vec<String>>();
|
||||||
let mut options = Options::empty();
|
let mut options = Options::empty();
|
||||||
options.insert(Options::all());
|
options.insert(Options::all());
|
||||||
|
|
||||||
let config_path = match get_path("XDG_CONFIG_DIR") {
|
let config = Config::read_config(
|
||||||
Ok(path) => path,
|
get_path("XDG_CONFIG_DIR").unwrap_or_else(|(err, code)| {
|
||||||
Err((err, code)) => {
|
|
||||||
eprintln!("{}: {}", argv[0], err);
|
eprintln!("{}: {}", argv[0], err);
|
||||||
exit(code);
|
exit(code);
|
||||||
},
|
})
|
||||||
};
|
).unwrap_or_else(|(err, code)| {
|
||||||
|
eprintln!("{}: {}", argv[0], err);
|
||||||
let config = match Config::read_config(config_path) {
|
exit(code);
|
||||||
Ok(val) => val,
|
});
|
||||||
Err((err, code)) => {
|
|
||||||
eprintln!("{}: {}", argv[0], err);
|
|
||||||
exit(code);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let content_files = match read_dir(&config.content_dir) {
|
let mut files = get_contents(
|
||||||
Ok(files) => files,
|
read_dir(&config.content_dir).unwrap_or_else(|_| {
|
||||||
Err(_) => {
|
|
||||||
eprintln!("{}: {}: Directory not found.", argv[0], &config.content_dir);
|
|
||||||
exit(EX_UNAVAILABLE);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let files = match get_contents(content_files) {
|
|
||||||
Ok(files) => files,
|
|
||||||
Err((err, code)) => {
|
|
||||||
eprintln!("{}: {}", argv[0], err);
|
|
||||||
exit(code);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for element in files.iter() {
|
|
||||||
let (md, toml) = element;
|
|
||||||
|
|
||||||
let meta_info = match PageMeta::read_meta(toml.to_owned()) {
|
|
||||||
Ok(info) => info,
|
|
||||||
Err((err, code)) => {
|
|
||||||
eprintln!("{}: {}", argv[0], err);
|
|
||||||
exit(code);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut template = match File::open(format!(
|
|
||||||
"{}/{}.html",
|
|
||||||
config.template_dir,
|
|
||||||
meta_info.template,
|
|
||||||
)) {
|
|
||||||
Ok(file) => file,
|
|
||||||
/*
|
|
||||||
* TODO: better matching:
|
|
||||||
* Err(err) => {
|
|
||||||
* match err {
|
|
||||||
* whatever_message => File does not exist
|
|
||||||
* whichever_message => whatever else
|
|
||||||
*/
|
|
||||||
Err(_) => {
|
|
||||||
eprintln!("{}: {}: File does not exist.", argv[0], meta_info.template);
|
|
||||||
exit(EX_OSERR);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut html: Vec<u8> = Vec::new();
|
|
||||||
|
|
||||||
match template.read_to_end(&mut html) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("{}: {}", argv[0], err);
|
|
||||||
exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let parsed_html = match String::from_utf8(html) {
|
|
||||||
Ok(html) => html,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("{}: {}", argv[0], err);
|
|
||||||
exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let dom = match tl::parse(&parsed_html, ParserOptions::default()) {
|
|
||||||
Ok(dom) => dom,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("{}: {}", argv[0], err);
|
|
||||||
exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let parser = dom.parser();
|
|
||||||
|
|
||||||
let mut html_file = match File::create(format!(
|
|
||||||
"{}/{}.html",
|
|
||||||
config.output_dir,
|
|
||||||
meta_info.template,
|
|
||||||
)) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(_) => {
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}: {}: Unable to create output file.",
|
"{}: {}: No such file or directory.",
|
||||||
argv[0],
|
argv[0],
|
||||||
config.output_dir,
|
&config.content_dir
|
||||||
);
|
);
|
||||||
exit(EX_OSERR);
|
exit(EX_UNAVAILABLE);
|
||||||
},
|
})
|
||||||
};
|
).unwrap_or_else(|(err, code)| {
|
||||||
|
eprintln!("{}: {}", argv[0], err);
|
||||||
|
exit(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut templates = get_templates(
|
||||||
|
read_dir(&config.template_dir).unwrap_or_else(|_| {
|
||||||
|
eprintln!(
|
||||||
|
"{}: {}: No such file or directory.",
|
||||||
|
argv[0],
|
||||||
|
&config.template_dir
|
||||||
|
);
|
||||||
|
exit(EX_UNAVAILABLE);
|
||||||
|
})
|
||||||
|
).unwrap_or_else(|(err, code)| {
|
||||||
|
eprintln!("{}: {}", argv[0], err);
|
||||||
|
exit(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
for content_file in files.drain() {
|
||||||
|
let (content_name, (md, toml)) = content_file;
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
|
||||||
html::push_html(&mut output, Parser::new_ext(&md, options));
|
html::push_html(&mut output, Parser::new_ext(&md, options));
|
||||||
|
|
||||||
match html_file.write(output.as_bytes()) {
|
let meta_info = PageMeta::read_meta(
|
||||||
Ok(_) => {},
|
toml.to_owned()
|
||||||
Err(err) => {
|
).unwrap_or_else(|(err, code)| {
|
||||||
eprintln!("{}: {}", argv[0], err);
|
eprintln!("{}: {}", argv[0], err);
|
||||||
exit(EX_SOFTWARE);
|
exit(code);
|
||||||
},
|
});
|
||||||
};
|
|
||||||
|
let dom = templates.get_mut(&meta_info.template).unwrap_or_else(|| {
|
||||||
|
eprintln!(
|
||||||
|
"{}: {}: No such template “{}”.",
|
||||||
|
argv[0],
|
||||||
|
content_name,
|
||||||
|
meta_info.template,
|
||||||
|
);
|
||||||
|
exit(EX_UNAVAILABLE);
|
||||||
|
}).get_mut_ref();
|
||||||
|
|
||||||
|
let mut html_file = File::create(format!(
|
||||||
|
"{}/{}.html",
|
||||||
|
config.output_dir,
|
||||||
|
meta_info.template,
|
||||||
|
)).unwrap_or_else(|_| {
|
||||||
|
eprintln!(
|
||||||
|
"{}: {}: Unable to create output file.",
|
||||||
|
argv[0],
|
||||||
|
config.output_dir,
|
||||||
|
);
|
||||||
|
exit(EX_OSERR);
|
||||||
|
});
|
||||||
|
|
||||||
|
html_file.write(output.as_bytes()).unwrap_or_else(|err| {
|
||||||
|
eprintln!("{}: {}", argv[0], err);
|
||||||
|
exit(EX_SOFTWARE);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This file is part of Plaque.
|
||||||
|
*
|
||||||
|
* Plaque is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU Affero General Public License as published by the Free
|
||||||
|
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
* later version.
|
||||||
|
*
|
||||||
|
* Plaque is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io::{ Read },
|
||||||
|
fs::{ File, ReadDir },
|
||||||
|
};
|
||||||
|
|
||||||
|
use tl::{ ParserOptions, VDomGuard };
|
||||||
|
use toml::Value;
|
||||||
|
use yacexits::*;
|
||||||
|
|
||||||
|
pub fn get_contents(
|
||||||
|
dir: ReadDir,
|
||||||
|
) -> Result<HashMap<String, (String, Value)>, (String, u32)> {
|
||||||
|
let mut out: HashMap<String, (String, Value)> = HashMap::new();
|
||||||
|
let mut md: String;
|
||||||
|
let mut toml = String::new();
|
||||||
|
|
||||||
|
let mut files = match read_files(dir) {
|
||||||
|
Ok(files) => files,
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in files.drain() {
|
||||||
|
let (file_name, contents) = entry;
|
||||||
|
let mut lines = contents.lines();
|
||||||
|
|
||||||
|
if lines.next() == Some("+++\n") {
|
||||||
|
let mut line = lines.next();
|
||||||
|
|
||||||
|
while line.unwrap() != "+++\n" {
|
||||||
|
toml.push_str(&mut line.unwrap());
|
||||||
|
line = lines.next();
|
||||||
|
}
|
||||||
|
md = lines.collect::<Vec<&str>>().as_mut_slice().join("");
|
||||||
|
|
||||||
|
let parsed_toml = match toml.parse::<Value>() {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"{}: Could not parse TOML.",
|
||||||
|
file_name,
|
||||||
|
),
|
||||||
|
EX_DATAERR,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
out.insert(file_name, (md, parsed_toml));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_templates(
|
||||||
|
dir: ReadDir,
|
||||||
|
) -> Result<HashMap<String, VDomGuard>, (String, u32)> {
|
||||||
|
let mut out = HashMap::new();
|
||||||
|
|
||||||
|
let mut files = match read_files(dir) {
|
||||||
|
Ok(files) => files,
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
|
||||||
|
for entry in files.drain() {
|
||||||
|
let (file_name, contents) = entry;
|
||||||
|
|
||||||
|
let dom = match unsafe {
|
||||||
|
tl::parse_owned(contents, ParserOptions::default())
|
||||||
|
} {
|
||||||
|
Ok(dom) => dom,
|
||||||
|
Err(err) => return Err((format!("{:?}", err), EX_DATAERR)),
|
||||||
|
};
|
||||||
|
|
||||||
|
out.insert(file_name, dom);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_files(
|
||||||
|
dir: ReadDir,
|
||||||
|
) -> Result<HashMap<String, String>, (String, u32)> {
|
||||||
|
let mut out = HashMap::new();
|
||||||
|
|
||||||
|
for entry in dir {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let file = match entry {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_name = match file.file_name().into_string() {
|
||||||
|
Ok(name) => name,
|
||||||
|
Err(_) => {
|
||||||
|
return Err((format!("Invalid file name."), EX_DATAERR));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let contents = match File::open(&file_name) {
|
||||||
|
Ok(mut md_file) => {
|
||||||
|
md_file.read_to_end(&mut buf).unwrap();
|
||||||
|
String::from_utf8(buf).unwrap()
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
return Err((
|
||||||
|
format!("No such file or directory."),
|
||||||
|
EX_UNAVAILABLE
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
out.insert(file_name, contents);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
Reference in New Issue