use std::{fs, path::Path};
pub fn run() -> anyhow::Result<()> {
let routes_dir = Path::new("src/routes");
if !routes_dir.exists() {
anyhow::bail!("src/routes/ not found. Run `rok routes:list` from the project root.");
}
println!("{:<8} {:<40} HANDLER", "METHOD", "PATH");
println!("{}", "-".repeat(80));
let mut found = false;
for entry in fs::read_dir(routes_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let src = fs::read_to_string(&path)?;
extract_routes(&src, &path.display().to_string());
found = true;
}
if !found {
println!(" (no route files found in src/routes/)");
}
Ok(())
}
fn extract_routes(src: &str, file: &str) {
for line in src.lines() {
let trimmed = line.trim();
if !trimmed.starts_with(".route(") {
continue;
}
let inner = trimmed
.trim_start_matches(".route(")
.trim_end_matches(')')
.trim_end_matches(',');
let mut parts = inner.splitn(2, ',');
let path = parts
.next()
.unwrap_or("")
.trim()
.trim_matches('"')
.to_string();
let handlers_str = parts.next().unwrap_or("").trim().to_string();
for (method, handler) in parse_method_handlers(&handlers_str) {
println!("{:<8} {:<40} {} ({})", method, path, handler, file);
}
}
}
fn parse_method_handlers(s: &str) -> Vec<(String, String)> {
let methods = ["get", "post", "put", "patch", "delete", "head", "options"];
let mut result = Vec::new();
let mut remaining = s.trim().to_string();
for method in &methods {
let prefix = format!("{method}(");
while let Some(pos) = remaining.find(&prefix) {
let after = &remaining[pos + prefix.len()..];
let end = match after.find(')') {
Some(e) => e,
None => break,
};
let handler = after[..end].trim().to_string();
result.push((method.to_uppercase(), handler));
remaining = remaining[pos + prefix.len() + end + 1..].to_string();
}
}
if result.is_empty() {
result.push(("*".to_string(), s.trim().to_string()));
}
result
}