diff --git a/src/config.rs b/src/config.rs index 2be7164a..5a4923ad 100644 --- a/src/config.rs +++ b/src/config.rs @@ -117,6 +117,7 @@ pub struct Settings { pub toc: TocSettings, } +#[derive(Clone)] pub struct TocSettings { pub position: TocPosition, pub title: TocTitle, @@ -128,11 +129,13 @@ pub struct TocSettings { pub item_format: String, } +#[derive(Clone)] pub enum TocPosition { LEFT, RIGHT, } +#[derive(Clone)] pub enum TocTitle { DEFAULT, CUSTOM, diff --git a/src/error.rs b/src/error.rs index 0a8aa5eb..4f168fb2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ use panic_message::panic_info_message; use std::collections::HashMap; use std::env; +use std::fmt::Write; use std::panic::{set_hook, PanicInfo}; use uuid::Uuid; @@ -63,27 +64,30 @@ where "Unknown" }; - payload.push_str(&format!("Name: {}\n", env!("CARGO_PKG_NAME"))); - payload.push_str(&format!("Version: {}\n", env!("CARGO_PKG_VERSION"))); - payload.push_str(&format!("Operating System: {}\n", os)); + let _ = writeln!(payload, "Name: {}", env!("CARGO_PKG_NAME")); + let _ = writeln!(payload, "Version: {}", env!("CARGO_PKG_VERSION")); + let _ = writeln!(payload, "Operating System: {}", os); - payload.push_str(&format!("Cause: {}.\n", panic_info_message(info))); + let _ = writeln!(payload, "Cause: {}", panic_info_message(info)); match info.location() { - Some(location) => payload.push_str(&format!( - "Panic occurred in file '{}' at line {}\n", - location.file(), - location.line() - )), + Some(location) => { + let _ = writeln!( + payload, + "Panic occurred in file '{}' at line {}", + location.file(), + location.line() + ); + } None => payload.push_str("Panic location unknown.\n"), - } + }; let logs = match std::fs::read_to_string(&CONFIG.logging.log_dir) { Ok(logs) => logs, Err(_) => "No logs available.".to_string(), }; - payload.push_str(&format!("\n\nLogs: \n{}", logs)); + let _ = write!(payload, "\n\nLogs: \n{}", logs); f(Some(path), payload); })) diff --git a/src/ui/article/mod.rs b/src/ui/article/mod.rs index a1de60e1..2ae81528 100644 --- a/src/ui/article/mod.rs +++ b/src/ui/article/mod.rs @@ -1,9 +1,6 @@ use crate::ui::utils::remove_view_from_layout; use crate::wiki::{ - article::{ - parser::{DefaultParser, Parser}, - Article, ArticleBuilder, - }, + article::{parser::DefaultParser, Article, ArticleBuilder}, search::SearchResult, }; use crate::{ @@ -34,8 +31,12 @@ pub fn on_article_submit(siv: &mut Cursive, search_result: &SearchResult) { search_result.title(), search_result.page_id() ); - let article = match ArticleBuilder::new(*search_result.page_id(), None) - .build(&mut DefaultParser::new()) + let article = match ArticleBuilder::new( + *search_result.page_id(), + None, + &CONFIG.api_config.base_url, + ) + .build(&mut DefaultParser::new(&CONFIG.settings.toc)) { Ok(article) => article, Err(error) => { @@ -126,7 +127,9 @@ fn open_link(siv: &mut Cursive, target: String) { // fetch the article log::debug!("fetching the article"); - let article = match ArticleBuilder::new(0, Some(target)).build(&mut DefaultParser::new()) { + let article = match ArticleBuilder::new(0, Some(target), &CONFIG.api_config.base_url) + .build(&mut DefaultParser::new(&CONFIG.settings.toc)) + { Ok(article) => article, Err(error) => { log::warn!("{:?}", error); diff --git a/src/ui/search.rs b/src/ui/search.rs index bd97f3e7..9f6c0b3d 100644 --- a/src/ui/search.rs +++ b/src/ui/search.rs @@ -12,7 +12,7 @@ use cursive::{align::HAlign, utils::markup::StyledString, Cursive}; /// Returns the default SearchBuilder fn build_search() -> SearchBuilder { - SearchBuilder::new() + SearchBuilder::new(&config::CONFIG.api_config.base_url) .info(SearchMetadata::new().total_hits()) .prop(SearchProperties::new().snippet()) .sort(SearchSortOrder::JustMatch) diff --git a/src/wiki/article/builder.rs b/src/wiki/article/builder.rs index ee917c1e..b910534e 100644 --- a/src/wiki/article/builder.rs +++ b/src/wiki/article/builder.rs @@ -1,7 +1,4 @@ -use crate::{ - config::CONFIG, - wiki::article::{compiled_article::Article, parser::Parser}, -}; +use crate::wiki::article::{compiled_article::Article, parser::Parser}; use anyhow::Result; use reqwest::blocking::{get, Response}; @@ -12,13 +9,19 @@ pub struct ArticleBuilder { page_id: i32, /// The optional link of the article to be fetched target: Option, + /// The url of wikipedia + base_url: String, } impl ArticleBuilder { /// Creates a new Articlebuilder - pub fn new(page_id: i32, target: Option) -> ArticleBuilder { + pub fn new(page_id: i32, target: Option, base_url: &str) -> ArticleBuilder { log::debug!("creating a new instance of ArticleBuilder"); - ArticleBuilder { page_id, target } + ArticleBuilder { + page_id, + target, + base_url: base_url.to_string(), + } } /// Fetches the article and parses it with a given parser. Any errors it encounters will be returned @@ -36,8 +39,8 @@ impl ArticleBuilder { /// Creates a url from the link fn build_url(&self) -> String { match self.target { - Some(ref target) => format!("{}{}", CONFIG.api_config.base_url, target), - None => format!("{}?curid={}", CONFIG.api_config.base_url, self.page_id), + Some(ref target) => format!("{}{}", self.base_url, target), + None => format!("{}?curid={}", self.base_url, self.page_id), } } @@ -54,16 +57,18 @@ impl ArticleBuilder { #[cfg(test)] mod tests { + const BASE_URL: &str = "https://en.wikipedia.org/"; + #[test] fn correct_url() { use super::ArticleBuilder; assert_eq!( - ArticleBuilder::new(1234, None).build_url(), - "https://en.wikipedia.org/?curid=1234".to_string() + ArticleBuilder::new(1234, None, BASE_URL).build_url(), + format!("{}?curid=1234", BASE_URL) ); assert_eq!( - ArticleBuilder::new(1234, Some("/wiki/Software".to_string())).build_url(), - "https://en.wikipedia.org//wiki/Software".to_string() + ArticleBuilder::new(1234, Some("/wiki/Software".to_string()), BASE_URL).build_url(), + format!("{}/wiki/Software", BASE_URL) ); } } diff --git a/src/wiki/article/parser.rs b/src/wiki/article/parser.rs index d3161d55..4bb36c23 100644 --- a/src/wiki/article/parser.rs +++ b/src/wiki/article/parser.rs @@ -1,4 +1,4 @@ -use crate::config::{TocTitle, CONFIG}; +use crate::config::{TocSettings, TocTitle, CONFIG}; use crate::wiki::article::{ compiled_article::Article, element::ArticleElement, @@ -17,7 +17,6 @@ use std::io::Read; /// The Parser trait allows for generating an Article from a html source pub trait Parser { - fn new() -> Self; fn parse(&mut self, html: R) -> Result
; } @@ -26,10 +25,21 @@ pub trait Parser { pub struct DefaultParser { /// The elements that have been parsed already elements: Vec, + /// The toc configuration + toc_settings: TocSettings, } impl DefaultParser { - /// This functinon takes generates a TableOfContents from a given document. When no + /// Creates a new DefaultParser with a given toc configuration + pub fn new(toc_settings: &TocSettings) -> Self { + log::debug!("creating a new instance of DefaultParser"); + Self { + elements: Vec::new(), + toc_settings: toc_settings.clone(), + } + } + + /// This function takes generates a TableOfContents from a given document. When no /// TableOfContents can be found in the document, it returns Ok(None). Any errors it /// encounters are returned fn parse_toc(&self, document: &Document) -> Result> { @@ -43,16 +53,15 @@ impl DefaultParser { .context("No table of contents was found")?; // get the title of the toc - let toc_title = match CONFIG.settings.toc.title { + let toc_title = match self.toc_settings.title { TocTitle::DEFAULT => toc_node .find(Class("toctitle")) .next() .context("No toc title was found")? .text(), TocTitle::ARTICLE => self.get_title(document)?, - TocTitle::CUSTOM => CONFIG - .settings - .toc + TocTitle::CUSTOM => self + .toc_settings .title_custom .clone() .unwrap_or_else(|| "NONE".to_string()), @@ -116,7 +125,7 @@ impl DefaultParser { // format the text let text = { - let mut text = CONFIG.settings.toc.item_format.to_string(); + let mut text = self.toc_settings.item_format.to_string(); for (k, v) in &data { text = text.replace(k, v); } @@ -298,14 +307,6 @@ impl DefaultParser { } impl Parser for DefaultParser { - /// Creates a new DefaultParser with the given ParserConfig - fn new() -> Self { - log::debug!("creating a new instance of DefaultParser"); - Self { - elements: Vec::new(), - } - } - /// Tries to parse a given html document into an Article. Any errors it encounters will be /// returned fn parse(&mut self, html: R) -> Result
{ @@ -383,7 +384,7 @@ mod tests { #[test] fn parse_link() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html( "

Github

software development

", @@ -405,7 +406,7 @@ mod tests { #[test] fn parse_text() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html("

Github

is a provider of

"); @@ -424,7 +425,7 @@ mod tests { #[test] fn parse_header() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html( "

Github

History

", @@ -446,7 +447,7 @@ mod tests { #[test] fn parse_bold() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html("

Github

GitHub, Inc.

"); @@ -465,7 +466,7 @@ mod tests { #[test] fn parse_italic() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html("

Github

GitHub, Inc.

"); @@ -484,7 +485,7 @@ mod tests { #[test] fn parse_list() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html( "

Github

", @@ -516,7 +517,7 @@ mod tests { #[test] fn parse_code_block() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html( "

Github

inverse(a, n) t := 0
", @@ -536,7 +537,7 @@ mod tests { #[test] fn incorrect_html() { - let mut parser = DefaultParser::new(); + let mut parser = DefaultParser::new(&CONFIG.settings.toc); let test_html = generate_html("nope"); assert!(parser.parse(test_html.as_bytes()).is_err()) diff --git a/src/wiki/search/builder.rs b/src/wiki/search/builder.rs index 7b077717..52afb311 100644 --- a/src/wiki/search/builder.rs +++ b/src/wiki/search/builder.rs @@ -1,4 +1,3 @@ -use crate::config::CONFIG; use crate::wiki::search::{ compiled_search::Search, info::SearchInfo, metadata::SearchMetadata, properties::SearchProperties, result::SearchResult, sort_order::SearchSortOrder, @@ -27,6 +26,8 @@ pub struct SearchBuilder { prop: SearchProperties, /// Set the sort order of returned results sort: SearchSortOrder, + /// The url of wikipedia + base_url: String, } // NOTE: The following structs are only used for deserializing the json response @@ -111,7 +112,7 @@ macro_rules! build_setter { impl SearchBuilder { /// Creates a new SearchBuilder - pub fn new() -> Self { + pub fn new(base_url: &str) -> Self { log::debug!("creating a new SearchBuilder"); SearchBuilder { query: String::new(), @@ -129,6 +130,7 @@ impl SearchBuilder { .timestamp() .snippet(), sort: SearchSortOrder::default(), + base_url: base_url.to_string(), } } @@ -181,7 +183,7 @@ impl SearchBuilder { // build the url log::debug!("building the url"); - let url = self.build_url(&CONFIG.api_config.base_url)?; + let url = self.build_url()?; // make the request log::debug!("making the request to '{}'", url); @@ -198,7 +200,7 @@ impl SearchBuilder { } /// A helper function that builds the search url. It fails if the query is empty - fn build_url(&self, base_url: &str) -> Result { + fn build_url(&self) -> Result { // if the query is empty, then do nothing if self.query.is_empty() { bail!("the query is empty. we don't do that here!") @@ -207,7 +209,7 @@ impl SearchBuilder { // just build the url, very simple Ok(format!( "{}w/api.php?action=query&format=json&list=search&srsearch={}&srnamespace={}&srlimit={}&sroffset={}{}{}{}", - base_url, + self.base_url, self.query, self.namespace, self.limit, @@ -316,29 +318,22 @@ impl SearchBuilder { } } -impl Default for SearchBuilder { - fn default() -> Self { - Self::new() - } -} - #[cfg(test)] mod tests { const SEARCH_RESPONSE: &str = r#"{"batchcomplete":"","continue":{"sroffset":2,"continue":"-||"},"query":{"searchinfo":{"totalhits":232618,"suggestion":"mening","suggestionsnippet":"mening"},"search":[{"ns":0,"title":"Meaning","pageid":18916,"size":1645,"wordcount":215,"snippet":"Meaning most commonly refers to: Meaning (linguistics), meaning which is communicated through the use of language Meaning (philosophy), definition, elements","timestamp":"2021-10-19T21:30:54Z"},{"ns":0,"title":"The Meaning of Meaning","pageid":1754283,"size":4359,"wordcount":470,"snippet":"The Meaning of Meaning: A Study of the Influence of Language upon Thought and of the Science of Symbolism (1923) is a book by C. K. Ogden and I. A. Richards","timestamp":"2022-01-07T23:20:19Z"}]}}"#; + const BASE_URL: &str = "https://en.wikipedia.org/"; #[test] fn correct_url() { use super::SearchBuilder; - assert!(SearchBuilder::new() - .build_url("https://en.wikipedia.org/") - .is_err()); - assert_eq!(SearchBuilder::new().query("meaning".to_string()).build_url("https://en.wikipedia.org/").unwrap(), "https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=meaning&srnamespace=0&srlimit=10&sroffset=0&srinfo=totalhits|suggestion|rewrittenquery&srprop=size|wordcount|timestamp|snippet&srsort=relevance".to_string()); + assert!(SearchBuilder::new(BASE_URL).build_url().is_err()); + assert_eq!(SearchBuilder::new(BASE_URL).query("meaning".to_string()).build_url().unwrap(), "https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=meaning&srnamespace=0&srlimit=10&sroffset=0&srinfo=totalhits|suggestion|rewrittenquery&srprop=size|wordcount|timestamp|snippet&srsort=relevance".to_string()); } #[test] fn deserialize_correct() -> anyhow::Result<()> { use super::SearchBuilder; - SearchBuilder::new().deserialize_response(SEARCH_RESPONSE.to_string())?; + SearchBuilder::new(BASE_URL).deserialize_response(SEARCH_RESPONSE.to_string())?; Ok(()) } @@ -346,7 +341,7 @@ mod tests { #[test] fn deserialize_missing_fields() { use super::SearchBuilder; - assert!(SearchBuilder::new() + assert!(SearchBuilder::new(BASE_URL) .deserialize_response("{}".to_string()) .is_err()); } @@ -354,17 +349,26 @@ mod tests { #[test] fn namespace_invalid() { use super::SearchBuilder; - assert!(SearchBuilder::new() + assert!(SearchBuilder::new(BASE_URL) .namespace(2304) .invalid_fields() .is_err()); - assert!(SearchBuilder::new().namespace(16).invalid_fields().is_err()); + assert!(SearchBuilder::new(BASE_URL) + .namespace(16) + .invalid_fields() + .is_err()); } #[test] fn limit_invalid() { use super::SearchBuilder; - assert!(SearchBuilder::new().limit(0).invalid_fields().is_err()); - assert!(SearchBuilder::new().limit(501).invalid_fields().is_err()); + assert!(SearchBuilder::new(BASE_URL) + .limit(0) + .invalid_fields() + .is_err()); + assert!(SearchBuilder::new(BASE_URL) + .limit(501) + .invalid_fields() + .is_err()); } }