From 50c6293ebafa99ac44abf5026c9751b3ed1e06ae Mon Sep 17 00:00:00 2001 From: deadvey Date: Mon, 6 Jan 2025 21:17:07 +0000 Subject: [PATCH] initial --- Cargo.toml | 9 +++ src/main.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..38a9d41 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "markdown-web-browser" +version = "0.1.0" +edition = "2021" + +[dependencies] +colored = "2.2.0" +regex = "1.11.1" +url = "2.5.4" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..96f4604 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,176 @@ +use std::process::{Command}; +use std::io::{stdin,stdout,Write}; +use colored::Colorize; +use regex::Regex; +use url::Url; + +fn clear_screen() { + Command::new("clear") + .spawn() + .expect("Failed to clear screen"); +} + +fn parse_markdown(page_content: String) -> (String, Vec) { + let mut parsed_page_content: String = "".to_string(); + let mut hyperlink_number_counter: u64 = 0; + let mut links: Vec = Vec::new(); + + for line in page_content.lines() { + let mut parsed_line: String = line.to_string(); + // Bold + let bold_regex = Regex::new(r"\*\*(.*?)\*\*").unwrap(); + parsed_line = bold_regex.replace_all(&parsed_line, |caps: ®ex::Captures| { + caps[1].bold().to_string() + }).to_string(); + let bold_regex = Regex::new(r"__(.*?)__").unwrap(); + parsed_line = bold_regex.replace_all(&parsed_line, |caps: ®ex::Captures| { + caps[1].bold().to_string() + }).to_string(); + + // Italics + let italic_regex = Regex::new(r"\*(.*?)\*").unwrap(); + parsed_line = italic_regex.replace_all(&parsed_line, |caps: ®ex::Captures| { + caps[1].italic().to_string() + }).to_string(); + let italic_regex = Regex::new(r"_(.*?)_").unwrap(); + parsed_line = italic_regex.replace_all(&parsed_line, |caps: ®ex::Captures| { + caps[1].italic().to_string() + }).to_string(); + + // Block quotes + let block_quotes_regex = Regex::new(r"^>(.*)").unwrap(); + parsed_line = block_quotes_regex.replace_all(&parsed_line, |caps: ®ex::Captures| { + format!(" | {}", &caps[1].on_black()) + }).to_string(); + + // Ordered list + let ordered_list_regex = Regex::new(r"^([0-9]+)\.(.*)").unwrap(); + parsed_line = ordered_list_regex.replace_all(&parsed_line, " $1. $2").to_string(); + + // Unordered list + let unordered_list_regex = Regex::new(r"^(-|\+|\*).(.*)").unwrap(); + parsed_line = unordered_list_regex.replace_all(&parsed_line, " • $2").to_string(); + + // Inline code + let inline_code_regex = Regex::new(r"`(.*)`").unwrap(); + parsed_line = inline_code_regex.replace_all(&parsed_line, |caps: ®ex::Captures| { + format!("{}", &caps[1].magenta()) + }).to_string(); + + // HyperLink + let hyperlink_regex = Regex::new(r"\[(.*?)\]\((.*?)\)").unwrap(); + parsed_line = hyperlink_regex.replace_all(&parsed_line, |caps: ®ex::Captures| { + let result = format!("{}[{}]", &caps[1].blue().underline(),hyperlink_number_counter); + let url = caps[2].to_string(); + links.push(url); + hyperlink_number_counter+=1; + + + result + }).to_string(); + + parsed_page_content+=&(parsed_line + "\n"); + } + + // multiline code + let multiline_code_regex = Regex::new(r"(?ms)%%%((.*?\n)+?)%%%").unwrap(); + parsed_page_content = multiline_code_regex.replace_all(&parsed_page_content, |caps: ®ex::Captures| { + // Capture the code inside the %% blocks + let code_block = &caps[1]; + + // Add a tab to each line in the block + let indented_code = code_block + .lines() + .map(|line| format!("\t{}", line)) // Insert tab at the start of each line + .collect::>() + .join("\n"); + + // Return the formatted block with magenta color + format!("{}", indented_code.magenta()) + }).to_string(); + + return (parsed_page_content, links); +} + +fn fetch_page(host: &String, port: &String, path: &String) -> String { + let full_url_formatted = format!("{}:{}/{}", host, port, path); + + // Call curl using Com, mand + let output = Command::new("curl") + .arg(full_url_formatted) + .output() + .expect("Failed to execute curl command"); + + // Check if the command was successful + if output.status.success() { + let page: String = String::from_utf8_lossy(&output.stdout).to_string(); + return page + } else { + eprintln!("Error:\n{}", String::from_utf8_lossy(&output.stderr)); + let result: String = "error".to_string(); + return result + } +} + +fn render_page(host: String, port: String, path: String) -> Vec { + clear_screen(); + let mut content = fetch_page(&host, &port, &path); + let mut links = Vec::new(); + (content, links) = parse_markdown(content); + print!("{}: {}\n", host, path); + for _i in 0..format!("{}: {}", host, path).len() { + print!("-"); + } + print!("\n\n{}", content); + for _i in 0..format!("{}: {}", host, path).len() { + print!("-"); + } + println!(); + for i in 0..links.len() { + println!("{}: {}", i, links[i].blue().underline()); + } + println!(); + return links; +} + +fn input() -> String{ + let mut s=String::new(); + let _=stdout().flush(); + stdin().read_line(&mut s).expect("Did not enter a correct string"); + if let Some('\n')=s.chars().next_back() { + s.pop(); + } + if let Some('\r')=s.chars().next_back() { + s.pop(); + } + return s; +} + +fn main() { + println!("Enter a url: "); + let mut host: String = input(); + let mut port: String = "3477".to_string(); + let mut path: String = "/".to_string(); + + 'mainloop: loop { + let links = render_page(host.clone(), port.clone(), path.clone()); + + println!("{}:{}/{}", host, port, path); + + println!("Enter link number to follow, or q to quit"); + let link_to_follow = input(); + if link_to_follow == "q" { + break 'mainloop; + } + else { + let number: usize = link_to_follow.parse::().unwrap(); + + let parsed_url = Url::parse(&links[number]).expect("Invalid URL"); + host = parsed_url.host_str().expect("No host found").to_string(); + port = parsed_url.port().unwrap_or(3477).to_string(); + path = parsed_url.path().to_string(); + println!("{}:{}/{}", host, port, path); + } + } +} +