main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
mod content;
mod errors;
mod handlers;
mod renderer;
mod state;
use tracing_subscriber::prelude::{ * };
use crate::errors::{ Result };
use crate::state::{ ServerState };
use axum::{ Router };
use axum::routing::{ get, post };
use clap::{ Parser };
use include_dir::{ Dir, include_dir };
use kdl::{ KdlDocument };
use tokio::net::{ TcpListener };
use tower_http::services::{ ServeDir };
use tower_http::trace::{ TraceLayer };
use tracing_subscriber::filter::{ EnvFilter, LevelFilter };
use std::path::{ PathBuf };
use std::process::{ ExitCode };
use std::sync::{ Arc };
static DEFAULT_THEME: Dir = include_dir!("themes/default");
/// The server program for the Limetree CMS.
#[derive(Debug, Parser)]
#[command(about, version)]
struct CommandLine {
/// Path to the limetree.kdl configuration file.
#[arg(short, long)]
configuration_file: PathBuf,
}
#[tokio::main]
async fn main() -> ExitCode {
let result = run_server().await;
match result {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
tracing::error!("{}", e);
ExitCode::FAILURE
},
}
}
async fn run_server() -> Result<()> {
let command_line = CommandLine::parse();
let configuration_text = std::fs::read_to_string(&command_line.configuration_file)?;
let configuration = KdlDocument::parse(&configuration_text)?;
let filter_layer = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(filter_layer)
.init();
let site_title = configuration
.get("site")
.and_then(|node| node.children())
.and_then(|document| document.get_arg("title"))
.and_then(|value| value.as_string())
.unwrap_or("Limetree CMS")
.to_string();
let limetree_root = configuration
.get("limetree")
.and_then(|node| node.children())
.and_then(|document| document.get_arg("root"))
.and_then(|value| value.as_string())
.map(|string| PathBuf::from(string))
.unwrap_or(std::env::current_dir()?.join("limetree_data"));
let content_path = limetree_root.join("content");
let metadata_path = limetree_root.join("metadata.json");
let theme_path = limetree_root.join("themes/default");
let theme_static_path = theme_path.join("static");
if !theme_path.is_dir() {
DEFAULT_THEME.extract(&theme_path)?;
}
let server_state = ServerState::new(&site_title, &content_path, &metadata_path, &theme_path)?;
let router = Router::new()
.layer(TraceLayer::new_for_http())
.route("/", get(handlers::index))
.route("/form/page/new", post(handlers::new_page_form))
.route("/form/page/edit/{slug}", post(handlers::edit_page_form))
.route("/list/tagged/{tag}", get(handlers::tagged_list))
.route("/list/tags", get(handlers::tags_list))
.route("/page/new", get(handlers::new_page))
.route("/page/edit/{slug}", get(handlers::edit_page))
.route("/page/view/{slug}", get(handlers::view_page))
.nest_service("/static", ServeDir::new(theme_static_path))
.with_state(Arc::new(server_state));
const HOST: &str = "localhost";
const PORT: u16 = 8081;
let address = format!("{}:{}", HOST, PORT);
let listener = TcpListener::bind(address).await?;
let bound_address = listener.local_addr()?;
tracing::info!("{} now listening on {}", env!("CARGO_BIN_NAME"), bound_address);
axum::serve(listener, router).await?;
Ok(())
}