aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--example/.gitignore1
-rw-r--r--src/error.rs2
-rw-r--r--src/luax/mod.rs21
-rw-r--r--src/main.rs306
-rw-r--r--src/server.rs12
-rw-r--r--templates/index.luax14
-rw-r--r--templates/reluax.luax24
7 files changed, 322 insertions, 58 deletions
diff --git a/example/.gitignore b/example/.gitignore
deleted file mode 100644
index d907c43..0000000
--- a/example/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.lua
diff --git a/src/error.rs b/src/error.rs
index bf44a21..3c8b756 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -5,7 +5,6 @@ pub enum LuaXError {
InvalidStart, // used internally
NonTableChildren,
NonTableAttrs,
- UnexpectedCharacter(char),
NeededToken(String),
ExpectedVar,
ExpectedExpression,
@@ -20,7 +19,6 @@ impl Display for LuaXError {
LuaXError::InvalidStart => write!(f, "Invalid start"),
LuaXError::NonTableChildren => write!(f, "Children must be tables"),
LuaXError::NonTableAttrs => write!(f, "Attrs must be tables"),
- LuaXError::UnexpectedCharacter(c) => write!(f, "Unexpected character: '{}'", c),
LuaXError::NeededToken(token) => write!(f, "Needed token: {}", token),
LuaXError::ExpectedVar => write!(f, "Expected variable"),
LuaXError::ExpectedExpression => write!(f, "Expected expression"),
diff --git a/src/luax/mod.rs b/src/luax/mod.rs
index ddbb2ef..1e87124 100644
--- a/src/luax/mod.rs
+++ b/src/luax/mod.rs
@@ -1,3 +1,5 @@
+use std::path::Path;
+
use rlua::Lua;
use crate::{error::LuaXError, Result};
@@ -91,13 +93,24 @@ pub fn preprocess(s: &str) -> Result<String> {
Ok(s)
}
-pub fn preprocess_dir(path: &std::path::Path) -> Result<usize> {
+pub fn preprocess_dir(path: &Path, output_path: &Path) -> Result<usize> {
let mut preprocessed = 0;
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
- preprocessed += preprocess_dir(&path)?;
+ let output_dir = output_path.join(path.file_name().unwrap());
+ if !output_dir.is_dir() && output_dir.exists() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::AlreadyExists,
+ format!("{} already exists", output_dir.display()),
+ )
+ .into());
+ }
+ if !output_dir.exists() {
+ std::fs::create_dir(&output_dir)?;
+ }
+ preprocessed += preprocess_dir(&path, &output_path.join(path.file_name().unwrap()))?;
} else {
if path.extension().unwrap_or_default() != "luax" {
continue;
@@ -105,7 +118,9 @@ pub fn preprocess_dir(path: &std::path::Path) -> Result<usize> {
let s = std::fs::read_to_string(&path)?;
let s = preprocess(&s)?;
- let out_path = path.with_extension("lua");
+ let out_path = output_path
+ .join(path.file_name().unwrap())
+ .with_extension("lua");
std::fs::write(out_path, s)?;
preprocessed += 1;
diff --git a/src/main.rs b/src/main.rs
index 29f2c88..058ce05 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,7 @@
-use std::path::{Path, PathBuf};
+use std::{
+ io::Write,
+ path::{Path, PathBuf},
+};
use clap::Parser;
use color_eyre::{owo_colors::OwoColorize, Result};
@@ -8,68 +11,189 @@ mod luax;
mod server;
#[derive(Debug, Clone, clap::Parser)]
-struct Args {
- #[clap(short = 'C', long = "current-dir", default_value = ".")]
- current_dir: std::path::PathBuf,
- #[clap(short = 'P', long = "public-dir", default_value = "public")]
- public_dir: std::path::PathBuf,
- #[clap(short = 'p', long = "port", default_value = "4310")]
- port: u16,
- #[clap(short = 'l', long = "local", default_value = "false")]
- local: bool,
+#[clap(about = "⛱️ A LuaX web framework")]
+enum Args {
+ #[clap(
+ name = "serve",
+ about = "Serve a directory of LuaX files in production mode"
+ )]
+ Serve {
+ #[clap(
+ short = 'C',
+ long = "change-dir",
+ default_value = ".",
+ help = "The directory to serve LuaX files from"
+ )]
+ change_dir: std::path::PathBuf,
+ #[clap(
+ short = 'p',
+ long = "port",
+ default_value = "4310",
+ help = "The port to serve on"
+ )]
+ port: u16,
+ #[clap(
+ short = 'l',
+ long = "local",
+ default_value = "false",
+ help = "Do not use a temporary directory for preprocessing"
+ )]
+ local: bool,
+ },
+ #[clap(name = "build", about = "Build a directory of LuaX files")]
+ Build {
+ #[clap(
+ short = 'C',
+ long = "change-dir",
+ default_value = ".",
+ help = "The directory to build LuaX files from"
+ )]
+ change_dir: std::path::PathBuf,
+ #[clap(
+ short = 'o',
+ long = "output",
+ default_value = ".",
+ help = "The directory to output the built files to"
+ )]
+ output_dir: std::path::PathBuf,
+ },
+ #[clap(
+ name = "dev",
+ about = "Serve a directory of LuaX files in development mode"
+ )]
+ Dev {
+ #[clap(
+ short = 'C',
+ long = "change-dir",
+ default_value = ".",
+ help = "The directory to serve LuaX files from"
+ )]
+ change_dir: std::path::PathBuf,
+ #[clap(
+ short = 'P',
+ long = "public-dir",
+ help = "The static files directory to serve"
+ )]
+ public_dir: Option<std::path::PathBuf>,
+ #[clap(
+ short = 'p',
+ long = "port",
+ default_value = "4310",
+ help = "The port to serve on"
+ )]
+ port: u16,
+ #[clap(
+ short = 'l',
+ long = "local",
+ default_value = "false",
+ help = "Do not use a temporary directory for preprocessing"
+ )]
+ local: bool,
+ },
+ #[clap(name = "new", about = "Create a new project")]
+ New {
+ #[clap(help = "The name of the project")]
+ name: String,
+ },
+ #[clap(
+ name = "init",
+ about = "Initialize a new project in the current directory"
+ )]
+ Init,
}
#[tokio::main]
async fn main() -> color_eyre::Result<()> {
color_eyre::install().unwrap();
- let mut args = Args::parse();
+ let args = Args::parse();
- if !args.current_dir.is_dir() {
- return Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- format!("{} is not a directory", args.current_dir.display()),
- )
- .into());
- }
+ match args {
+ Args::Serve {
+ change_dir,
+ port,
+ local,
+ } => {
+ if !change_dir.is_dir() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("{} is not a directory", change_dir.display()),
+ )
+ .into());
+ }
- println!(
- "🌴 Project root: {}",
- args.current_dir.display().bright_yellow()
- );
+ println!("🌴 Project root: {}", change_dir.display().bright_yellow());
- if !args.public_dir.is_dir() {
- return Err(std::io::Error::new(
- std::io::ErrorKind::NotFound,
- format!("{} is not a directory", args.public_dir.display()),
- )
- .into());
- }
+ if local {
+ serve_locally(change_dir, port, None).await
+ } else {
+ serve_from_temp(change_dir, port, None).await
+ }
+ }
+ Args::Build {
+ change_dir,
+ output_dir,
+ } => build(change_dir, output_dir),
+ Args::Dev {
+ change_dir,
+ public_dir,
+ port,
+ local,
+ } => {
+ if !change_dir.is_dir() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("{} is not a directory", change_dir.display()),
+ )
+ .into());
+ }
- println!(
- "🌴 Public directory: {}",
- args.public_dir.display().bright_yellow()
- );
+ println!("🌴 Project root: {}", change_dir.display().bright_yellow());
- args.public_dir = args.public_dir.canonicalize()?;
+ let public_dir = if let Some(public_dir) = public_dir {
+ if !public_dir.is_dir() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("{} is not a directory", public_dir.display()),
+ )
+ .into());
+ }
- if args.local {
- local(args).await
- } else {
- temp(args).await
+ println!(
+ "🌴 Public directory: {}",
+ public_dir.display().bright_yellow()
+ );
+ Some(public_dir.canonicalize()?)
+ } else {
+ println!("🌴 Public directory: {}", "none".bright_yellow());
+ None
+ };
+
+ if local {
+ serve_locally(change_dir, port, public_dir).await
+ } else {
+ serve_from_temp(change_dir, port, public_dir).await
+ }
+ }
+ Args::New { name } => create_project(&name),
+ Args::Init => init_project(),
}
}
-async fn local(args: Args) -> Result<()> {
+async fn serve_locally(change_dir: PathBuf, port: u16, public_dir: Option<PathBuf>) -> Result<()> {
println!("🌴 Running in local mode");
- std::env::set_current_dir(&args.current_dir)?;
+ std::env::set_current_dir(&change_dir)?;
preprocess_current_dir().await?;
ensure_entry_point().await?;
- serve(args).await
+ serve(port, public_dir).await
}
-async fn temp(args: Args) -> Result<()> {
+async fn serve_from_temp(
+ change_dir: PathBuf,
+ port: u16,
+ public_dir: Option<PathBuf>,
+) -> Result<()> {
// Create a /tmp/reluax-XXXXXX directory for the server to pre-process files in.
let tmp_dir = tempfile::Builder::new()
.prefix("reluax-")
@@ -94,7 +218,7 @@ async fn temp(args: Args) -> Result<()> {
);
// copy all files from the current directory to the temporary directory recursively.
- let copied = recurse_copy(&args.current_dir, tmp_dir.path())?;
+ let copied = recurse_copy(&change_dir, tmp_dir.path())?;
println!("⏲️ {} files copied", copied.bright_green());
@@ -104,11 +228,12 @@ async fn temp(args: Args) -> Result<()> {
ensure_entry_point().await?;
- serve(args).await
+ serve(port, public_dir).await
}
async fn preprocess_current_dir() -> Result<()> {
- let preprocessed = luax::preprocess_dir(std::env::current_dir()?.as_path())?;
+ let current_dir = std::env::current_dir()?;
+ let preprocessed = luax::preprocess_dir(current_dir.as_path(), current_dir.as_path())?;
println!(
"⛱️ {} Reluax files preprocessed!",
@@ -132,11 +257,11 @@ async fn ensure_entry_point() -> Result<()> {
Ok(())
}
-async fn serve(args: Args) -> Result<()> {
+async fn serve(port: u16, public_dir: Option<PathBuf>) -> Result<()> {
println!("📦 Building Lua state...");
let lua = luax::prepare_lua()?;
- println!("🛫 Starting server...");
- server::Server::serve(lua, args.port, args.public_dir).await
+ println!("🛫 Starting server on port {}...", port);
+ server::Server::serve(lua, port, public_dir).await
}
fn recurse_copy(from: &Path, to: &Path) -> Result<usize> {
@@ -158,3 +283,88 @@ fn recurse_copy(from: &Path, to: &Path) -> Result<usize> {
Ok(copied)
}
+
+fn create_project(name: &str) -> Result<()> {
+ let dir = PathBuf::from(name);
+
+ if dir.is_dir() {
+ println!("🛑 Directory {} already exists", dir.display().bright_red());
+ return Ok(());
+ }
+
+ std::fs::create_dir(&dir)?;
+
+ std::env::set_current_dir(&dir)?;
+
+ write_templates(name)?;
+
+ println!("🌴 Created project {}", name.bright_yellow());
+
+ Ok(())
+}
+
+fn init_project() -> Result<()> {
+ let dir = std::env::current_dir()?;
+
+ if !dir.is_dir() {
+ println!("🛑 Directory {} does not exist", dir.display().bright_red());
+ return Ok(());
+ }
+
+ let name = dir.file_name().unwrap().to_str().unwrap();
+
+ write_templates(name)?;
+
+ println!("🌴 Initialized project {}", name.bright_yellow());
+
+ Ok(())
+}
+
+fn write_templates(name: &str) -> Result<()> {
+ let mut file = std::fs::File::create("reluax.luax")?;
+ file.write_all(
+ include_str!("../templates/reluax.luax")
+ .replace("PROJECT_NAME", name)
+ .as_bytes(),
+ )?;
+
+ let mut file = std::fs::File::create("index.luax")?;
+ file.write_all(
+ include_str!("../templates/index.luax")
+ .replace("PROJECT_NAME", name)
+ .as_bytes(),
+ )?;
+
+ Ok(())
+}
+
+fn build(change_dir: PathBuf, output_dir: PathBuf) -> Result<()> {
+ if !change_dir.is_dir() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("{} is not a directory", change_dir.display()),
+ )
+ .into());
+ }
+
+ println!("🌴 Project root: {}", change_dir.display().bright_yellow());
+
+ if !output_dir.is_dir() {
+ std::fs::create_dir(&output_dir)?;
+ }
+
+ println!(
+ "🌴 Output directory: {}",
+ output_dir.display().bright_yellow()
+ );
+
+ std::env::set_current_dir(&change_dir)?;
+
+ println!("📦 Preprocessing LuaX files...");
+
+ let built = luax::preprocess_dir(&change_dir, &output_dir)?;
+
+ println!("📦 {} LuaX files preprocessed!", built.bright_green());
+
+ Ok(())
+}
diff --git a/src/server.rs b/src/server.rs
index 05f7f26..16b543e 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -26,11 +26,11 @@ pub struct Server {
#[derive(Clone)]
struct State {
lua: Arc<Mutex<Lua>>,
- public_dir: PathBuf,
+ public_dir: Option<PathBuf>,
}
impl Server {
- pub async fn serve(lua: Lua, port: u16, public_dir: PathBuf) -> Result<()> {
+ pub async fn serve(lua: Lua, port: u16, public_dir: Option<PathBuf>) -> Result<()> {
let state = State {
lua: Arc::new(Mutex::new(lua)),
public_dir,
@@ -140,9 +140,13 @@ impl State {
let status =
StatusCode::from_u16(res.0 as u16).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
- if status == StatusCode::NOT_FOUND {
+ if status == StatusCode::NOT_FOUND && self.public_dir.is_some() {
// try to serve a static file
- let path = self.public_dir.join(path.trim_start_matches('/'));
+ let path = self
+ .public_dir
+ .clone()
+ .unwrap()
+ .join(path.trim_start_matches('/'));
if path.is_file() {
return mk_file_response(path);
diff --git a/templates/index.luax b/templates/index.luax
new file mode 100644
index 0000000..6785491
--- /dev/null
+++ b/templates/index.luax
@@ -0,0 +1,14 @@
+return function()
+ return <html>
+ <head>
+ <title>PROJECT_NAME</title>
+ </head>
+ <body>
+ <h1>PROJECT_NAME</h1>
+ <p>Random number for you: {$ tostring(math.floor(math.random() * 100)) $}</p>
+ <p style={ "font-size: " .. tostring(math.floor(math.random() * 100)) .. 'px' }>
+ What font size is this?
+ </p>
+ </body>
+ </html>
+end
diff --git a/templates/reluax.luax b/templates/reluax.luax
new file mode 100644
index 0000000..8bba507
--- /dev/null
+++ b/templates/reluax.luax
@@ -0,0 +1,24 @@
+local Index = require("index")
+
+math.randomseed(os.time())
+
+local function route(path)
+ if path == '/' then
+ return 200, <Index />
+ end
+
+ return 404, <html>
+ <head>
+ <title>Error 404</title>
+ </head>
+ <body>
+ <h1>Error 404</h1>
+ <p>Page not found.</p>
+ </body>
+ </html>
+end
+
+return {
+ route = route
+}
+