feat: Forgot to commit lol

Now it's too much stuff and it's impossible to discern what I did
This commit is contained in:
Dendy 2024-09-19 08:11:33 +02:00
parent 8ee77d392a
commit 6983603a77
8 changed files with 238 additions and 96 deletions

7
Cargo.lock generated
View File

@ -175,6 +175,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"askama", "askama",
"axum", "axum",
"glob",
"serde", "serde",
"tokio", "tokio",
"toml", "toml",
@ -240,6 +241,12 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.14.5"

View File

@ -6,6 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
askama = "0.12.1" askama = "0.12.1"
axum = "0.7.5" axum = "0.7.5"
glob = "0.3.1"
serde = { version = "1.0.210", features = ['derive'] } serde = { version = "1.0.210", features = ['derive'] }
tokio = "1.40.0" tokio = "1.40.0"
toml = "0.8.19" toml = "0.8.19"

21
src/config.rs Normal file
View File

@ -0,0 +1,21 @@
use serde::Deserialize;
use std::fs::File;
use std::io::Read;
#[derive(Deserialize)]
pub struct Config {
pub settings: Settings,
}
#[derive(Deserialize)]
pub struct Settings {
pub base_directory: String,
}
pub fn read_config() -> Result<Config, Box<dyn std::error::Error>> {
let mut file = File::open("config.toml")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let config: Config = toml::from_str(&contents)?;
Ok(config)
}

11
src/handlers.rs Normal file
View File

@ -0,0 +1,11 @@
use crate::templates::HtmlTemplate;
use crate::templates::ListTemplate;
use crate::utils::find_files;
use axum::response::IntoResponse;
pub async fn list_files(base_path: String) -> impl IntoResponse {
let template = ListTemplate {
files: find_files(&base_path).unwrap(),
};
HtmlTemplate(template)
}

View File

@ -1,29 +1,13 @@
use std::{ mod config;
fs::{self, File}, mod handlers;
io::Read, mod templates;
path::Path, mod utils;
};
use axum::{ use crate::config::read_config;
http::StatusCode, use crate::handlers::list_files;
response::{Html, IntoResponse}, use axum::routing::get;
routing::get, use axum::Router;
Router, use tokio::net::TcpListener;
};
use serde::Deserialize;
use askama::Template;
#[derive(Deserialize)]
struct Config {
settings: Settings,
}
#[derive(Deserialize)]
struct Settings {
base_directory: String,
}
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
@ -34,73 +18,8 @@ async fn main() {
get(move || list_files(config.settings.base_directory.clone())), get(move || list_files(config.settings.base_directory.clone())),
); );
let listener = tokio::net::TcpListener::bind("127.0.0.1:3004") let listener = TcpListener::bind("127.0.0.1:3004").await.unwrap();
.await
.unwrap();
println!("Listenening on {}", listener.local_addr().unwrap()); println!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
fn read_config() -> Result<Config, Box<dyn std::error::Error>> {
let mut file = File::open("config.toml")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let config: Config = toml::from_str(&contents)?;
Ok(config)
}
#[derive(Template)]
#[template(path = "list.html")]
struct ListTemplate {
paths: Vec<String>,
}
struct HtmlTemplate<T>(T);
impl<T> IntoResponse for HtmlTemplate<T>
where
T: Template,
{
fn into_response(self) -> axum::response::Response {
match self.0.render() {
Ok(html) => Html(html).into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to render template. Error: {err}"),
)
.into_response(),
}
}
}
async fn list_files(base_path: String) -> impl IntoResponse {
let template = ListTemplate {
paths: find_ora_files(&base_path).unwrap(),
};
HtmlTemplate(template)
}
fn find_ora_files(dir: &str) -> Result<Vec<String>, std::io::Error> {
let mut ora_files = Vec::new();
let base_path = Path::new(dir);
if base_path.is_dir() {
for entry in fs::read_dir(base_path)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if let Ok(mut subdir_files) = find_ora_files(path.to_str().unwrap()) {
ora_files.append(&mut subdir_files);
}
} else if let Some(extension) = path.extension() {
if extension == "ora" {
ora_files.push(path.to_string_lossy().to_string());
}
}
}
}
Ok(ora_files)
}

27
src/templates.rs Normal file
View File

@ -0,0 +1,27 @@
use askama::Template;
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse};
#[derive(Template)]
#[template(path = "list.html")]
pub struct ListTemplate {
pub files: Vec<crate::utils::FileEntry>,
}
pub struct HtmlTemplate<T>(pub T);
impl<T> IntoResponse for HtmlTemplate<T>
where
T: Template,
{
fn into_response(self) -> axum::response::Response {
match self.0.render() {
Ok(html) => Html(html).into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to render template. Error: {err}"),
)
.into_response(),
}
}
}

110
src/utils.rs Normal file
View File

@ -0,0 +1,110 @@
use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use std::collections::HashMap;
use glob::glob;
#[derive(Deserialize, Debug)]
struct TagFile {
#[serde(skip_serializing)]
basedir: String,
#[serde(flatten)]
entries: HashMap<String, FileTags>,
}
#[derive(Deserialize, Debug)]
struct FileTags {
tags: Vec<String>,
}
#[derive(Clone, Debug)]
pub struct FileEntry {
pub path: String,
pub tags: Vec<String>,
}
fn load_tag_file(path: &PathBuf) -> Result<TagFile, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(TagFile {
basedir: path.parent().unwrap().to_str().unwrap().to_string(),
entries: toml::from_str(&contents)?,
})
}
fn match_files(pattern: &str, base_path: &Path) -> Vec<String> {
let pattern = base_path.join(pattern).to_str().unwrap().to_string();
let mut matched_files = Vec::new();
for entry in glob(&pattern).expect("Failed to read glob pattern") {
if let Ok(path) = entry {
if path.is_file() {
matched_files.push(path.to_string_lossy().to_string());
}
}
}
matched_files
}
fn find_tag_files(dir: &str) -> Result<Vec<TagFile>, Box<dyn std::error::Error>> {
let base_path = Path::new(dir);
let mut tag_files = Vec::new();
if base_path.is_dir() {
for entry in fs::read_dir(base_path)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if let Ok(mut subdir_files) = find_tag_files(path.to_str().unwrap()) {
tag_files.append(&mut subdir_files);
}
} else if let Some(filename) = path.file_name() {
if filename != ".tags.toml" {
continue;
}
let tag_file = load_tag_file(&path).unwrap();
tag_files.push(tag_file);
}
}
}
Ok(tag_files)
}
pub fn find_files(dir: &str) -> Result<Vec<FileEntry>, Box<dyn std::error::Error>> {
let mut files = HashMap::new();
for tag_file in find_tag_files(dir)? {
for (key, value) in tag_file.entries.into_iter() {
// Create a copy of the list (append needs it to be mutable) and
// ignore .tags.toml files
let matched_files: Vec<String> = match_files(&key, Path::new(&tag_file.basedir))
.iter_mut()
.filter(|x| !x.ends_with(".tags.toml"))
.map(|x| x.to_owned())
.collect();
for matched_file in matched_files {
files
.entry(matched_file.clone())
.and_modify(|file_entry: &mut FileEntry| {
file_entry.tags.extend(value.tags.to_owned());
})
.or_insert_with(|| FileEntry {
path: matched_file,
tags: value.tags.to_owned(),
});
}
}
}
Ok(files.values().cloned().collect())
}

View File

@ -1,12 +1,58 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<style>
ul, p, h1, h2, h3, h4 {
margin: 0px;
}
li {
list-style: none;
margin: 0px;
}
html {
background-color: #222;
}
body {
background-color: black;
color: white;
font-family: mono;
margin: auto;
width: 80vw;
padding: 40px;
}
</style>
</head> </head>
<body> <body>
<h1>Drawing list</h1> <h1 style="margin-bottom: 40px;">Drawing list</h1>
<ul> <ul style="display: flex; flex-direction: column; gap: 20px;">
{% for path in paths -%} {% for file in files -%}
<li>{{ path }}</li> <li>
<p>{{ file.path }}<p>
<ul
style="
display: flex;
gap: 10px;
margin-top: 5px;
flex-wrap: wrap;
"
>
{% for tag in file.tags -%}
<li
style="
list-style: none;
background-color: #555;
padding: 2px 7px;
margin: 0px;
"
>
{{ tag }}
</li>
{% endfor %}
</ul>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
</body> </body>