Skip to content

Commit

Permalink
feat(ssg): [Enhancement]: Built-in development server #36
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienrousseau committed Mar 27, 2023
1 parent d178cba commit 60e399f
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 14 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ building static websites.
- Fast and flexible
- Easy to use
- Written in Rust
- Supports templates and themes
- Generates optimized HTML, CSS, and JavaScript
- Supports templates (YAML, JSON, TOML) and HTML themes
- Generates optimized HTML
- Built-in development server
- Live reloading
- Markdown support
Expand Down Expand Up @@ -118,13 +118,19 @@ Here’s the first command you can enter in your Terminal window to run
`shokunin`:

```shell
ssg --new=my-site --content=content --output=output --template=template
ssg --new=mysite --content=content --output=output --template=template
```

This command will create a new `my-site` project in a directory called
`public/my-site` and generate a static website in the `my-site`
This command will create a new `mysite` project in a directory called
`public/mysite` and generate a static website in the `mysite`
directory.

To run with the development server, you can use the following command:

```shell
ssg --new=mysite --content=content --output=output --template=template --serve=mysite
```

### In your project

To use the `shokunin` library in your project, add the following to your
Expand Down
11 changes: 9 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use clap::{Arg, ArgMatches, Command, Error};
/// - `--new` or `-n`: Creates a new project.
/// - `--content` or `-c`: Specifies the location of the content directory.
/// - `--output` or `-o`: Specifies the location of the output directory.
/// - `--template` or `-t`: Specifies the location of the template directory.
/// - `--serve` or `-s`: Serves the public directory on a local web server.
///
/// If the CLI is successfully built and the command-line arguments are
/// parsed correctly, the function returns an `Ok` result containing the
Expand Down Expand Up @@ -67,13 +69,18 @@ pub fn build() -> Result<ArgMatches, Error> {
.short('t')
.value_name("TEMPLATE"),
)
.arg(
Arg::new("serve")
.help("Serve the public directory on a local web server.")
.long("serve")
.short('s')
.value_name("SERVE")
)

.after_help(
"\x1b[1;4mDocumentation:\x1b[0m\n\n https://shokunin.one\n\n\x1b[1;4mLicense:\x1b[0m\n The project is licensed under the terms of both the MIT license and the Apache License (Version 2.0).",
)
.get_matches();

// println!("Matches: {:?}", matches);

Ok(matches)
}
19 changes: 16 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ pub mod metatags;
/// The `parser` module contains functions for parsing command-line
/// arguments and options.
pub mod parser;
/// The `serve` module contains functions for the development server.
pub mod serve;
/// The `template` module renders the HTML content using the pre-defined
/// template.
pub mod template;
/// The `directory` function ensures that a directory
/// exists.
/// The `directory` function ensures that a directory exists.
pub mod utilities;

#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -146,6 +147,18 @@ pub fn run() -> Result<(), Box<dyn Error>> {
let result = match cli::build() {
Ok(matches) => {
parser::args(&matches)?;
if matches.get_one::<String>("serve").is_some() {
let server_address = "127.0.0.1:8000";
let output_dir =
matches.get_one::<String>("serve").unwrap();
let document_root = format!("public/{}", output_dir);
serve::start(server_address, &document_root)?;
println!(
"\n✅ Server started at http://{}",
server_address
);
return Ok(());
}
Ok(())
}
Err(e) => Err(format!("❌ Error: {}", e)),
Expand Down Expand Up @@ -274,7 +287,7 @@ pub fn compile(
let src_dir = Path::new(src_dir);
let out_dir = Path::new(out_dir);

println!("❯ Generating a new site: \"{}\"", site_name);
println!("\n❯ Generating a new site: \"{}\"", site_name);

// Delete the output directory
println!("\n❯ Deleting any previous directory...");
Expand Down
67 changes: 67 additions & 0 deletions src/serve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::path::Path;
/// Start a web server to serve the public directory.
pub fn start(
server_address: &str,
document_root: &str,
) -> std::io::Result<()> {
let listener = TcpListener::bind(server_address)?;
println!("Server running at http://{}", server_address);

for stream in listener.incoming() {
let stream = stream?;
handle_connection(stream, document_root)?;
}
Ok(())
}

fn handle_connection(
mut stream: TcpStream,
document_root: &str,
) -> std::io::Result<()> {
let mut buffer = [0; 1024];
stream.read(&mut buffer)?;

let request = String::from_utf8_lossy(&buffer[..]);
let request_line = request.lines().next().unwrap_or("");
let mut request_parts = request_line.split_whitespace();
let (_method, path, _version) = (
request_parts.next(),
request_parts.next(),
request_parts.next(),
);

let requested_file = match path {
Some(p) => {
if p == "/" {
"index.html"
} else {
&p[1..] // Remove the leading "/"
}
}
None => "index.html",
};

let file_path = Path::new(document_root).join(requested_file);

let (status_line, contents) = if file_path.exists() {
(
"HTTP/1.1 200 OK\r\n\r\n",
std::fs::read_to_string(&file_path).unwrap_or_default(),
)
} else {
(
"HTTP/1.1 404 NOT FOUND\r\n\r\n",
std::fs::read_to_string(
Path::new(document_root).join("404.html"),
)
.unwrap_or_default(),
)
};

stream.write_all(status_line.as_bytes())?;
stream.write_all(contents.as_bytes())?;
stream.flush()?;
Ok(())
}
5 changes: 1 addition & 4 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ pub fn create_template_folder(
) -> Result<String, TemplateError> {
// Get the current working directory
let current_dir = std::env::current_dir()?;
println!("Current directory: {:?}", current_dir);

// Determine the template directory path based on the provided argument or use the default path
let template_dir_path = match template_path {
Expand Down Expand Up @@ -268,7 +267,7 @@ pub fn create_template_folder(
} else {
// If a local path is provided, use it as the template
// directory path
println!("Using local template directory: {}", path);
// println!("Using local template directory: {}", path);
current_dir.join(path)
}
}
Expand Down Expand Up @@ -300,7 +299,5 @@ pub fn create_template_folder(
template_dir_path
}
};
println!("Template directory path: {:?}", template_dir_path);

Ok(String::from(template_dir_path.to_str().unwrap()))
}

0 comments on commit 60e399f

Please sign in to comment.