🦠 The Definitive Gemini Protocol Toolkit
gemini gemini-protocol gemtext parser zero-dependency toolkit ast converter html markdown cli networking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fmt: update rustfmt.toml

Fuwn 56f53ddc 9f1fcff9

+111 -209
+1 -4
examples/ast_to_gemtext.rs
··· 46 46 That was a link without text."#; 47 47 48 48 fn main() { 49 - println!( 50 - "{}", 51 - germ::ast::Ast::from_string(EXAMPLE_GEMTEXT).to_gemtext() 52 - ); 49 + println!("{}", germ::ast::Ast::from_string(EXAMPLE_GEMTEXT).to_gemtext()); 53 50 }
+7 -8
examples/request_to_gemtext_from_ast.rs
··· 18 18 19 19 fn main() { 20 20 match germ::request::request(&url::Url::parse("gemini://fuwn.me/").unwrap()) { 21 - Ok(response) => 22 - println!( 23 - "{}", 24 - germ::ast::Ast::from_string( 25 - &*response.content().clone().unwrap_or_else(|| "".to_string()) 26 - ) 27 - .to_gemtext() 28 - ), 21 + Ok(response) => println!( 22 + "{}", 23 + germ::ast::Ast::from_string( 24 + &*response.content().clone().unwrap_or_else(|| "".to_string()) 25 + ) 26 + .to_gemtext() 27 + ), 29 28 Err(_) => {} 30 29 } 31 30 }
+10 -14
rustfmt.toml
··· 1 - condense_wildcard_suffixes = true 2 1 edition = "2021" 3 - enum_discrim_align_threshold = 20 2 + enum_discrim_align_threshold = 40 4 3 # error_on_line_overflow = true 5 4 # error_on_unformatted = true 6 5 fn_single_line = true 7 - force_multiline_blocks = true 8 6 format_code_in_doc_comments = true 9 7 format_macro_matchers = true 10 8 format_strings = true 11 - imports_layout = "HorizontalVertical" 12 - license_template_path = ".license_template" 13 - max_width = 80 9 + inline_attribute_width = 80 14 10 match_arm_blocks = false 15 - imports_granularity = "Crate" 11 + max_width = 80 12 + imports_granularity = "One" 13 + merge_imports = true 16 14 newline_style = "Unix" 17 - normalize_comments = true 18 15 normalize_doc_attributes = true 16 + overflow_delimited_expr = true 19 17 reorder_impl_items = true 20 18 group_imports = "StdExternalCrate" 21 - reorder_modules = true 22 - report_fixme = "Always" 23 - # report_todo = "Always" 24 - struct_field_align_threshold = 20 25 - struct_lit_single_line = false 19 + required_version = "1.5.2" 20 + struct_field_align_threshold = 40 26 21 tab_spaces = 2 27 22 use_field_init_shorthand = true 23 + use_small_heuristics = "Max" 28 24 use_try_shorthand = true 29 - where_single_line = true 25 + version = "Two" 30 26 wrap_comments = true
+2 -4
src/ast.rs
··· 21 21 mod container; 22 22 mod node; 23 23 24 - #[cfg(feature = "macros")] 25 - mod macros; 24 + #[cfg(feature = "macros")] mod macros; 26 25 27 - pub use container::Ast; 28 - pub use node::Node; 26 + pub use {container::Ast, node::Node};
+26 -50
src/ast/container.rs
··· 82 82 )); 83 83 } 84 84 85 - Self { 86 - inner: ast 87 - } 85 + Self { inner: ast } 88 86 } 89 87 90 88 #[must_use] ··· 94 92 for node in &self.inner { 95 93 match node { 96 94 Node::Text(text) => gemtext.push_str(&format!("{text}\n")), 97 - Node::Link { 95 + Node::Link { to, text } => gemtext.push_str(&format!( 96 + "=> {}{}\n", 98 97 to, 99 - text, 100 - } => 101 - gemtext.push_str(&format!( 102 - "=> {}{}\n", 103 - to, 104 - text 105 - .clone() 106 - .map_or_else(String::new, |text| format!(" {text}")), 107 - )), 108 - Node::Heading { 109 - level, 110 - text, 111 - } => 112 - gemtext.push_str(&format!( 113 - "{} {}\n", 114 - match level { 115 - 1 => "#", 116 - 2 => "##", 117 - 3 => "###", 118 - _ => "", 119 - }, 120 - text 121 - )), 122 - Node::List(items) => 123 - gemtext.push_str(&format!( 124 - "{}\n", 125 - items 126 - .iter() 127 - .map(|i| format!("* {i}")) 128 - .collect::<Vec<String>>() 129 - .join("\n"), 130 - )), 98 + text.clone().map_or_else(String::new, |text| format!(" {text}")), 99 + )), 100 + Node::Heading { level, text } => gemtext.push_str(&format!( 101 + "{} {}\n", 102 + match level { 103 + 1 => "#", 104 + 2 => "##", 105 + 3 => "###", 106 + _ => "", 107 + }, 108 + text 109 + )), 110 + Node::List(items) => gemtext.push_str(&format!( 111 + "{}\n", 112 + items 113 + .iter() 114 + .map(|i| format!("* {i}")) 115 + .collect::<Vec<String>>() 116 + .join("\n"), 117 + )), 131 118 Node::Blockquote(text) => gemtext.push_str(&format!("> {text}\n")), 132 - Node::PreformattedText { 133 - alt_text, 134 - text, 135 - } => 119 + Node::PreformattedText { alt_text, text } => 136 120 gemtext.push_str(&format!( 137 121 "```{}\n{}```\n", 138 122 alt_text.clone().unwrap_or_default(), ··· 190 174 text: { 191 175 let rest = split.collect::<Vec<String>>().join(" "); 192 176 193 - if rest.is_empty() { 194 - None 195 - } else { 196 - Some(rest) 197 - } 177 + if rest.is_empty() { None } else { Some(rest) } 198 178 }, 199 179 }); 200 180 ··· 263 243 } 264 244 } else { 265 245 nodes.push(Node::PreformattedText { 266 - alt_text: if alt_text.is_empty() { 267 - None 268 - } else { 269 - Some(alt_text) 270 - }, 246 + alt_text: if alt_text.is_empty() { None } else { Some(alt_text) }, 271 247 text: preformatted, 272 248 }); 273 249
+1 -2
src/convert.rs
··· 23 23 mod html; 24 24 mod markdown; 25 25 26 - #[cfg(feature = "macros")] 27 - mod macros; 26 + #[cfg(feature = "macros")] mod macros; 28 27 29 28 /// Different targets to convert Gemtext to 30 29 #[derive(Clone)]
+11 -20
src/convert/html.rs
··· 26 26 for node in source { 27 27 match node { 28 28 Node::Text(text) => html.push_str(&format!("<p>{text}</p>")), 29 - Node::Link { 30 - to, 31 - text, 32 - } => { 29 + Node::Link { to, text } => { 33 30 html.push_str(&format!( 34 31 "<a href=\"{}\">{}</a><br>", 35 32 to, 36 33 text.clone().unwrap_or_else(|| to.clone()) 37 34 )); 38 35 } 39 - Node::Heading { 40 - level, 41 - text, 42 - } => { 36 + Node::Heading { level, text } => { 43 37 html.push_str(&format!( 44 38 "<{}>{}</{0}>", 45 39 match level { ··· 51 45 text 52 46 )); 53 47 } 54 - Node::List(items) => 55 - html.push_str(&format!( 56 - "<ul>{}</ul>", 57 - items 58 - .iter() 59 - .map(|i| format!("<li>{i}</li>")) 60 - .collect::<Vec<String>>() 61 - .join("\n") 62 - )), 48 + Node::List(items) => html.push_str(&format!( 49 + "<ul>{}</ul>", 50 + items 51 + .iter() 52 + .map(|i| format!("<li>{i}</li>")) 53 + .collect::<Vec<String>>() 54 + .join("\n") 55 + )), 63 56 Node::Blockquote(text) => 64 57 html.push_str(&format!("<blockquote>{text}</blockquote>")), 65 - Node::PreformattedText { 66 - text, .. 67 - } => { 58 + Node::PreformattedText { text, .. } => { 68 59 html.push_str(&format!("<pre>{text}</pre>")); 69 60 } 70 61 Node::Whitespace => {}
+14 -25
src/convert/markdown.rs
··· 26 26 for node in source { 27 27 match node { 28 28 Node::Text(text) => markdown.push_str(&format!("{text}\n")), 29 - Node::Link { 30 - to, 31 - text, 32 - } => 33 - markdown.push_str(&text.clone().map_or_else( 34 - || format!("<{to}>\n"), 35 - |text| format!("[{text}]({to})\n"), 36 - )), 37 - Node::Heading { 38 - level, 39 - text, 40 - } => { 29 + Node::Link { to, text } => markdown.push_str(&text.clone().map_or_else( 30 + || format!("<{to}>\n"), 31 + |text| format!("[{text}]({to})\n"), 32 + )), 33 + Node::Heading { level, text } => { 41 34 markdown.push_str(&format!( 42 35 "{} {}\n", 43 36 match level { ··· 49 42 text 50 43 )); 51 44 } 52 - Node::List(items) => 53 - markdown.push_str(&format!( 54 - "{}\n", 55 - items 56 - .iter() 57 - .map(|i| format!("- {i}")) 58 - .collect::<Vec<String>>() 59 - .join("\n"), 60 - )), 45 + Node::List(items) => markdown.push_str(&format!( 46 + "{}\n", 47 + items 48 + .iter() 49 + .map(|i| format!("- {i}")) 50 + .collect::<Vec<String>>() 51 + .join("\n"), 52 + )), 61 53 Node::Blockquote(text) => markdown.push_str(&format!("> {text}\n")), 62 - Node::PreformattedText { 63 - alt_text, 64 - text, 65 - } => { 54 + Node::PreformattedText { alt_text, text } => { 66 55 markdown.push_str(&format!( 67 56 "```{}\n{}```\n", 68 57 alt_text.clone().unwrap_or_default(),
+5 -10
src/lib.rs
··· 29 29 )] 30 30 #![recursion_limit = "128"] 31 31 32 - #[cfg(feature = "ast")] 33 - pub mod ast; 32 + #[cfg(feature = "ast")] pub mod ast; 34 33 35 - #[cfg(feature = "convert")] 36 - pub mod convert; 34 + #[cfg(feature = "convert")] pub mod convert; 37 35 38 - #[cfg(feature = "request")] 39 - pub mod request; 36 + #[cfg(feature = "request")] pub mod request; 40 37 41 - #[cfg(feature = "meta")] 42 - pub mod meta; 38 + #[cfg(feature = "meta")] pub mod meta; 43 39 44 - #[cfg(feature = "quick")] 45 - pub mod quick; 40 + #[cfg(feature = "quick")] pub mod quick;
+3 -7
src/meta.rs
··· 89 89 let mut parameters = HashMap::new(); 90 90 91 91 for parameter in metas { 92 - let key_value = parameter 93 - .trim_start() 94 - .split_at(parameter.find('=').unwrap_or(0)); 92 + let key_value = 93 + parameter.trim_start().split_at(parameter.find('=').unwrap_or(0)); 95 94 96 95 parameters.insert( 97 96 key_value.0.to_string().replace('=', ""), ··· 99 98 ); 100 99 } 101 100 102 - Self { 103 - mime, 104 - parameters, 105 - } 101 + Self { mime, parameters } 106 102 } 107 103 108 104 /// Obtain non-mutable access to the mime of the `Meta`
+3 -8
src/request.rs
··· 22 22 mod status; 23 23 mod verifier; 24 24 25 - #[cfg(feature = "sync")] 26 - pub mod sync; 25 + #[cfg(feature = "sync")] pub mod sync; 27 26 28 27 use std::io::{Read, Write}; 29 28 30 - pub use response::Response; 31 - pub use status::Status; 32 29 pub(crate) use verifier::GermVerifier; 30 + pub use {response::Response, status::Status}; 33 31 34 32 /// Make a request to a Gemini server. The `url` **should** be prefixed with a 35 33 /// scheme (e.g. "gemini://"). ··· 69 67 70 68 tls.read_to_end(&mut plain_text)?; 71 69 72 - Ok(Response::new( 73 - &plain_text, 74 - tls.conn.negotiated_cipher_suite(), 75 - )) 70 + Ok(Response::new(&plain_text, tls.conn.negotiated_cipher_suite())) 76 71 }
+1 -5
src/request/response.rs
··· 16 16 // Copyright (C) 2022-2022 Fuwn <contact@fuwn.me> 17 17 // SPDX-License-Identifier: GPL-3.0-only 18 18 19 - use std::borrow::Cow; 20 - 21 - use rustls::SupportedCipherSuite; 22 - 23 - use crate::request::Status; 19 + use {crate::request::Status, rustls::SupportedCipherSuite, std::borrow::Cow}; 24 20 25 21 #[derive(Debug, Clone)] 26 22 pub struct Response {
+4 -3
src/request/sync.rs
··· 16 16 // Copyright (C) 2022-2022 Fuwn <contact@fuwn.me> 17 17 // SPDX-License-Identifier: GPL-3.0-only 18 18 19 - use tokio::io::{AsyncReadExt, AsyncWriteExt}; 20 - 21 - use crate::request::Response; 19 + use { 20 + crate::request::Response, 21 + tokio::io::{AsyncReadExt, AsyncWriteExt}, 22 + }; 22 23 23 24 /// Make a request to a Gemini server 24 25 ///
+4 -3
src/request/verifier.rs
··· 16 16 // Copyright (C) 2022-2022 Fuwn <contact@fuwn.me> 17 17 // SPDX-License-Identifier: GPL-3.0-only 18 18 19 - use std::time::SystemTime; 20 - 21 - use rustls::{client, client::ServerCertVerified, Certificate}; 19 + use { 20 + rustls::{client, client::ServerCertVerified, Certificate}, 21 + std::time::SystemTime, 22 + }; 22 23 23 24 #[allow(clippy::module_name_repetitions)] 24 25 pub struct GermVerifier;
+14 -29
tests/ast.rs
··· 51 51 52 52 #[test] 53 53 fn build_multi_line_list_with_text() { 54 - assert_eq!( 55 - *Ast::from_string("* item1\n* 2\nhi text").inner(), 56 - vec![ 57 - Node::List(vec!["item1".to_string(), "2".to_string()]), 58 - Node::Text("hi text".to_string()), 59 - ], 60 - ); 54 + assert_eq!(*Ast::from_string("* item1\n* 2\nhi text").inner(), vec![ 55 + Node::List(vec!["item1".to_string(), "2".to_string()]), 56 + Node::Text("hi text".to_string()), 57 + ],); 61 58 } 62 59 63 60 #[test] 64 61 fn build_multi_line_vec() { 65 - assert_eq!( 66 - *Ast::from_string("=> /test hi\nhi there\n> hi").inner(), 67 - vec![ 68 - Node::Link { 69 - to: "/test".to_string(), 70 - text: Some("hi".to_string()), 71 - }, 72 - Node::Text("hi there".to_string()), 73 - Node::Blockquote("hi".to_string()), 74 - ], 75 - ); 62 + assert_eq!(*Ast::from_string("=> /test hi\nhi there\n> hi").inner(), vec![ 63 + Node::Link { to: "/test".to_string(), text: Some("hi".to_string()) }, 64 + Node::Text("hi there".to_string()), 65 + Node::Blockquote("hi".to_string()), 66 + ],); 76 67 } 77 68 78 69 #[test] 79 70 fn build_single_0th_from_vec() { 80 - assert_eq!( 81 - Ast::from_string("=> /test hi").inner(), 82 - &vec![Node::Link { 83 - to: "/test".to_string(), 84 - text: Some("hi".to_string()), 85 - }], 86 - ); 71 + assert_eq!(Ast::from_string("=> /test hi").inner(), &vec![Node::Link { 72 + to: "/test".to_string(), 73 + text: Some("hi".to_string()), 74 + }],); 87 75 } 88 76 89 77 #[test] 90 78 fn build_single_element() { 91 79 assert_eq!( 92 80 Ast::from_string("=> /test hi").inner().get(0).unwrap(), 93 - &Node::Link { 94 - to: "/test".to_string(), 95 - text: Some("hi".to_string()), 96 - }, 81 + &Node::Link { to: "/test".to_string(), text: Some("hi".to_string()) }, 97 82 ); 98 83 } 99 84
+2 -6
tests/convert.rs
··· 20 20 mod test { 21 21 use germ::{ 22 22 convert::{from_string, Target}, 23 - gemini_to_html, 24 - gemini_to_md, 23 + gemini_to_html, gemini_to_md, 25 24 }; 26 25 27 26 #[test] ··· 31 30 32 31 #[test] 33 32 fn convert_from_string_to_html_multi_line() { 34 - assert_eq!( 35 - from_string("hi\n# hi", &Target::HTML), 36 - "<p>hi</p><h1>hi</h1>", 37 - ); 33 + assert_eq!(from_string("hi\n# hi", &Target::HTML), "<p>hi</p><h1>hi</h1>",); 38 34 } 39 35 40 36 #[test]
+3 -11
tests/meta.rs
··· 47 47 fn meta_to_string_without_parameters() { 48 48 let original_string = "text/gemini"; 49 49 50 - assert_eq!( 51 - Meta::from_string(original_string).to_string(), 52 - original_string 53 - ); 50 + assert_eq!(Meta::from_string(original_string).to_string(), original_string); 54 51 } 55 52 56 53 #[test] 57 54 fn meta_to_string_with_parameters() { 58 55 let original_string = "text/gemini; hi=2; hi2=string=2"; 59 56 60 - assert_eq!( 61 - Meta::from_string(original_string).to_string(), 62 - original_string 63 - ); 57 + assert_eq!(Meta::from_string(original_string).to_string(), original_string); 64 58 } 65 59 66 60 #[test] ··· 92 86 #[test] 93 87 fn meta_to_map_length() { 94 88 assert_eq!( 95 - Meta::from_string("text/gemini; hi=2; hi2=string=2") 96 - .parameters() 97 - .len(), 89 + Meta::from_string("text/gemini; hi=2; hi2=string=2").parameters().len(), 98 90 2, 99 91 ); 100 92 }