pub struct Strategy {}
-use std::path::PathBuf;
+use std::fs::{create_dir_all, read_to_string, File as IOFile};
+use std::io::Write;
+use std::path::Path;
-use crate::file_handler::{File, FileType, FileHandlerStrategy};
+use crate::file_handler::{File, FileHandlerStrategy, FileType};
+use crate::gemini_parser::parse;
+use crate::html_renderer::render_html;
+
+impl Strategy {
+ fn is_title(&self, line: &str) -> bool {
+ line.starts_with("--- title:")
+ }
+
+ fn is_description(&self, line: &str) -> bool {
+ line.starts_with("--- description:")
+ }
+
+ fn get_title<'a>(&self, line: &'a str) -> &'a str {
+ line.split_once("--- title:").unwrap().1
+ }
+
+ fn get_description<'a>(&self, line: &'a str) -> &'a str {
+ line.split_once("--- description:").unwrap().1
+ }
+}
impl FileHandlerStrategy for Strategy {
- fn is(&self, path: &PathBuf) -> bool {
+ fn is(&self, path: &Path) -> bool {
if let Some(extension) = path.extension() {
- return !path.is_dir() && extension == "gmi"
+ return !path.is_dir() && extension == "gmi";
}
false
}
}
fn can_handle(&self, file_type: &FileType) -> bool {
- match file_type {
- FileType::Gemini => true,
- _ => false,
+ matches!(file_type, FileType::Gemini)
+ }
+
+ fn handle_html(&self, source: &Path, destination: &Path, file: &File, layout: &str) {
+ let gemini_contents = read_to_string(&file.path).unwrap();
+
+ // Front matter extraction
+ let lines: Vec<&str> = gemini_contents.split("\n").collect();
+ let mut lines_found = 0;
+ let mut title = "";
+ let mut description = "";
+ if let Some(slice) = lines.get(..2) {
+ for line in slice.iter() {
+ if self.is_title(line) {
+ title = self.get_title(line).trim();
+ lines_found += 1;
+ continue;
+ }
+ if self.is_description(line) {
+ description = self.get_description(line).trim();
+ lines_found += 1;
+ continue;
+ }
+ }
}
+
+ let gemini_source = lines[lines_found..].join("\n");
+ let content_html = render_html(parse(&gemini_source[..]));
+
+ let generated_html = layout
+ .replace("{{ title }}", title)
+ .replace("{{ description }}", description)
+ .replace("{{ content }}", &content_html[..]);
+
+ let relative_path = file.path.strip_prefix(source).unwrap();
+ let mut complete_destination = destination.join(relative_path);
+ complete_destination.set_extension("html");
+ let destination_parent = complete_destination.parent().unwrap();
+ create_dir_all(destination_parent).unwrap();
+
+ let mut destination_file = IOFile::create(&complete_destination).unwrap();
+ destination_file
+ .write_all(generated_html.as_bytes())
+ .unwrap();
+ }
+
+ fn handle_gemini(&self, source: &Path, destination: &Path, file: &File) {
+ let gemini_contents = read_to_string(&file.path).unwrap();
+
+ // Front matter extraction
+ let lines: Vec<&str> = gemini_contents.split("\n").collect();
+ let mut lines_found = 0;
+ if let Some(slice) = lines.get(..2) {
+ for line in slice.iter() {
+ if self.is_title(line) {
+ lines_found += 1;
+ continue;
+ }
+ if self.is_description(line) {
+ lines_found += 1;
+ continue;
+ }
+ }
+ }
+
+ let gemini_source = lines[lines_found..].join("\n");
+
+ let relative_path = file.path.strip_prefix(source).unwrap();
+ let complete_destination = destination.join(relative_path);
+ let destination_parent = complete_destination.parent().unwrap();
+ create_dir_all(destination_parent).unwrap();
+
+ let mut destination_file = IOFile::create(&complete_destination).unwrap();
+ destination_file
+ .write_all(gemini_source.as_bytes())
+ .unwrap();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs::create_dir_all;
+
+ use super::*;
+
+ use test_utilities::*;
+
+ #[test]
+ fn detects_title() {
+ let strategy = Strategy {};
+ assert!(strategy.is_title("--- title: Hello!"));
+ }
+
+ #[test]
+ fn does_not_detect_other_keys_as_title() {
+ let strategy = Strategy {};
+ assert!(!strategy.is_title("--- description: Hello!"));
}
- fn handle(&self, source: &PathBuf, destination: &PathBuf, file: &File) {
- println!("Should parse and copy {}", file.path.display())
+ #[test]
+ fn detects_description() {
+ let strategy = Strategy {};
+ assert!(strategy.is_description("--- description: What is this?"));
+ }
+
+ #[test]
+ fn does_not_detect_other_keys_as_description() {
+ let strategy = Strategy {};
+ assert!(!strategy.is_description("--- title: What is this?"));
+ }
+
+ #[test]
+ fn extracts_title() {
+ let strategy = Strategy {};
+ assert_eq!(strategy.get_title("--- title: Hello!").trim(), "Hello!");
+ }
+
+ #[test]
+ fn extracts_description() {
+ let strategy = Strategy {};
+ assert_eq!(
+ strategy
+ .get_description("--- description: What is this?")
+ .trim(),
+ "What is this?"
+ );
+ }
+
+ #[test]
+ fn identifies_gemini_file() {
+ let test_dir = setup_test_dir();
+ create_test_file(&test_dir.join("test.gmi"), "");
+ let strategy = Strategy {};
+ assert!(strategy.is(&test_dir.join("test.gmi")));
+ }
+
+ #[test]
+ fn rejects_non_gemini_file() {
+ let test_dir = setup_test_dir();
+ create_test_file(&test_dir.join("_layout.html"), "");
+ create_test_file(&test_dir.join("image.png"), "");
+ let strategy = Strategy {};
+ assert!(!strategy.is(&test_dir.join("_layout.html")));
+ assert!(!strategy.is(&test_dir.join("image.png")));
+ assert!(!strategy.is(&test_dir));
+ }
+
+ #[test]
+ fn identifies_gemini_type() {
+ let strategy = Strategy {};
+ assert!(matches!(strategy.identify(), FileType::Gemini));
+ }
+
+ #[test]
+ fn handles_gemini_type() {
+ let strategy = Strategy {};
+ assert!(strategy.can_handle(&FileType::Gemini));
+ }
+
+ #[test]
+ fn rejects_non_gemini_types() {
+ let strategy = Strategy {};
+ assert!(!strategy.can_handle(&FileType::Layout));
+ assert!(!strategy.can_handle(&FileType::File));
+ assert!(!strategy.can_handle(&FileType::Unknown));
+ }
+
+ #[test]
+ fn handles_html_generation() {
+ let test_dir = setup_test_dir();
+ let source_dir = test_dir.join("source");
+ let output_dir = test_dir.join("output");
+ create_dir_all(&source_dir).expect("Could not create source test directory");
+ create_dir_all(&output_dir).expect("Could not create output test directory");
+ let layout = "\
+<html>
+<head>
+<title>{{ title }}</title>
+<meta name=\"description\" content=\"{{ description }}\">
+</head>
+<body>{{ content }}</body>
+</html>
+";
+ create_test_file(
+ &source_dir.join("test.gmi"),
+ "\
+--- title: Page Is Cool!
+--- description: My Description
+# Test
+Hello world
+",
+ );
+
+ let strategy = Strategy {};
+ let file = File {
+ path: source_dir.join("test.gmi"),
+ file_type: FileType::Gemini,
+ };
+
+ strategy.handle_html(&source_dir, &output_dir, &file, &layout);
+
+ let html_output = output_dir.join("test.html");
+ assert!(html_output.exists());
+ assert_file_contents(
+ &html_output,
+ "\
+<html>
+<head>
+<title>Page Is Cool!</title>
+<meta name=\"description\" content=\"My Description\">
+</head>
+<body><section class=\"h1\">
+<h1> Test</h1>
+<p>Hello world</p>
+</section>
+</body>
+</html>
+",
+ );
+ }
+
+ #[test]
+ fn handles_gemini_generation() {
+ let test_dir = setup_test_dir();
+ let source_dir = test_dir.join("source");
+ let output_dir = test_dir.join("output");
+ create_dir_all(&source_dir).expect("Could not create source test directory");
+ create_dir_all(&output_dir).expect("Could not create output test directory");
+ create_test_file(
+ &source_dir.join("test.gmi"),
+ "\
+--- title: Page Is Cool!
+--- description: My Description
+# Test
+Hello world
+",
+ );
+
+ let strategy = Strategy {};
+ let file = File {
+ path: source_dir.join("test.gmi"),
+ file_type: FileType::Gemini,
+ };
+
+ strategy.handle_gemini(&source_dir, &output_dir, &file);
+
+ let gemini_output = output_dir.join("test.gmi");
+ assert!(gemini_output.exists());
+ assert_file_contents(
+ &gemini_output,
+ "\
+# Test
+Hello world
+",
+ );
+ }
+
+ #[test]
+ fn handles_nested_structure() {
+ let test_dir = setup_test_dir();
+ let source_dir = test_dir.join("source");
+ let output_dir = test_dir.join("output");
+ create_dir_all(&source_dir.join("nested")).expect("Could not create source test directory");
+ create_dir_all(&output_dir).expect("Could not create output test directory");
+ let layout = "\
+<html>
+<head>
+<title>{{ title }}</title>
+<meta name=\"description\" content=\"{{ description }}\">
+</head>
+<body>{{ content }}</body>
+</html>
+";
+ create_test_file(
+ &source_dir.join("nested/test.gmi"),
+ "\
+--- title: Page Is Cool!
+--- description: My Description
+# Test
+Hello world
+",
+ );
+
+ let strategy = Strategy {};
+ let file = File {
+ path: source_dir.join("nested/test.gmi"),
+ file_type: FileType::Gemini,
+ };
+
+ strategy.handle_html(&source_dir, &output_dir, &file, &layout);
+
+ let html_output = output_dir.join("nested/test.html");
+ assert!(html_output.exists());
+ assert_file_contents(
+ &html_output,
+ "\
+<html>
+<head>
+<title>Page Is Cool!</title>
+<meta name=\"description\" content=\"My Description\">
+</head>
+<body><section class=\"h1\">
+<h1> Test</h1>
+<p>Hello world</p>
+</section>
+</body>
+</html>
+",
+ );
}
}