From a77e9f58f02ea284fa40bbc5ea859616aea16e40 Mon Sep 17 00:00:00 2001 From: YouKnow Date: Tue, 27 Aug 2024 01:52:58 +0330 Subject: [PATCH 01/10] feat(utils): add Render util for recreating formatted text - Add new `render` module in `crates/teloxide/src/utils.rs` - Update CHANGELOG.md to document the new Render utility - Expose ESCAPE_CHARS in markdown.rs for reuse in render module This new Render utility allows recreating rendered HTML or Markdown formatted text from text/caption and MessageEntity. It enhances the library's text formatting capabilities, as discussed in PR #1152. --- CHANGELOG.md | 1 + crates/teloxide/src/utils.rs | 1 + crates/teloxide/src/utils/markdown.rs | 8 +- crates/teloxide/src/utils/render/helper.rs | 61 +++++ crates/teloxide/src/utils/render/html.rs | 71 ++++++ crates/teloxide/src/utils/render/markdown.rs | 74 ++++++ crates/teloxide/src/utils/render/mod.rs | 255 +++++++++++++++++++ crates/teloxide/src/utils/render/tag.rs | 156 ++++++++++++ 8 files changed, 623 insertions(+), 4 deletions(-) create mode 100644 crates/teloxide/src/utils/render/helper.rs create mode 100644 crates/teloxide/src/utils/render/html.rs create mode 100644 crates/teloxide/src/utils/render/markdown.rs create mode 100644 crates/teloxide/src/utils/render/mod.rs create mode 100644 crates/teloxide/src/utils/render/tag.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ac09cb91..d0ff1d08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait - Add `filter_mention_command` filter to `HandlerExt` trait ([issue #494](https://github.com/teloxide/teloxide/issues/494)) - Add `filter_business_connection`, `filter_business_message`, `filter_edited_business_message`, and `filter_deleted_business_messages` filters to update filters ([PR 1146](https://github.com/teloxide/teloxide/pull/1146)) +- Add `Render` util to recreate rendered Html or Markdown formatted text from text/caption and `MessageEntity` ([PR 1152](https://github.com/teloxide/teloxide/pull/1152)) ### Changed diff --git a/crates/teloxide/src/utils.rs b/crates/teloxide/src/utils.rs index c7738637..d9aeb979 100644 --- a/crates/teloxide/src/utils.rs +++ b/crates/teloxide/src/utils.rs @@ -3,6 +3,7 @@ pub mod command; pub mod html; pub mod markdown; +pub mod render; pub(crate) mod shutdown_token; pub use teloxide_core::net::client_from_env; diff --git a/crates/teloxide/src/utils/markdown.rs b/crates/teloxide/src/utils/markdown.rs index ec007c29..68de26a1 100644 --- a/crates/teloxide/src/utils/markdown.rs +++ b/crates/teloxide/src/utils/markdown.rs @@ -4,6 +4,9 @@ use teloxide_core::types::{User, UserId}; +pub(super) const ESCAPE_CHARS: [char; 18] = + ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']; + /// Applies the bold font style to the string. /// /// Passed string will not be automatically escaped because it can contain @@ -119,11 +122,8 @@ pub fn code_inline(s: &str) -> String { #[must_use = "This function returns a new string, rather than mutating the argument, so calling it \ without using its output does nothing useful"] pub fn escape(s: &str) -> String { - const CHARS: [char; 18] = - ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']; - s.chars().fold(String::with_capacity(s.len()), |mut s, c| { - if CHARS.contains(&c) { + if ESCAPE_CHARS.contains(&c) { s.push('\\'); } s.push(c); diff --git a/crates/teloxide/src/utils/render/helper.rs b/crates/teloxide/src/utils/render/helper.rs new file mode 100644 index 00000000..d827e890 --- /dev/null +++ b/crates/teloxide/src/utils/render/helper.rs @@ -0,0 +1,61 @@ +//! A helprt trait for rendering text/caption and entities back to html or +//! markdown + +use teloxide_core::types::Message; + +use super::Render; + +/// The [`RenderMessageTextHelper`] trait provides methods to generate HTML and +/// Markdown representations of the text and captions in a Telegram message. +pub trait RenderMessageTextHelper { + /// Returns the HTML representation of the message text, if the message + /// contains text. This method will parse the text and any entities + /// (such as bold, italic, links, etc.) and return the HTML-formatted + /// string. + #[must_use] + fn html_text(&self) -> Option; + /// Returns the Markdown representation of the message text, if the message + /// contains text. This method will parse the text and any entities + /// (such as bold, italic, links, etc.) and return the + /// Markdown-formatted string. + #[must_use] + fn markdown_text(&self) -> Option; + /// Returns the HTML representation of the message caption, if the message + /// contains caption. This method will parse the caption and any + /// entities (such as bold, italic, links, etc.) and return the + /// HTML-formatted string. + #[must_use] + fn html_caption(&self) -> Option; + /// Returns the Markdown representation of the message caption, if the + /// message contains caption. This method will parse the caption and any + /// entities (such as bold, italic, links, etc.) and return the + /// Markdown-formatted string. + #[must_use] + fn markdown_caption(&self) -> Option; +} + +impl RenderMessageTextHelper for Message { + fn html_text(&self) -> Option { + self.text() + .zip(self.entities()) + .map(|(text, entities)| Render::new(text, entities).as_html()) + } + + fn markdown_text(&self) -> Option { + self.text() + .zip(self.entities()) + .map(|(text, entities)| Render::new(text, entities).as_markdown()) + } + + fn html_caption(&self) -> Option { + self.caption() + .zip(self.caption_entities()) + .map(|(text, entities)| Render::new(text, entities).as_html()) + } + + fn markdown_caption(&self) -> Option { + self.caption() + .zip(self.caption_entities()) + .map(|(text, entities)| Render::new(text, entities).as_markdown()) + } +} diff --git a/crates/teloxide/src/utils/render/html.rs b/crates/teloxide/src/utils/render/html.rs new file mode 100644 index 00000000..8e006b81 --- /dev/null +++ b/crates/teloxide/src/utils/render/html.rs @@ -0,0 +1,71 @@ +use std::fmt::Write; + +use super::{ComplexTag, Kind, Place, SimpleTag, Tag, TagWriter}; + +pub static HTML: TagWriter = TagWriter { + bold: SimpleTag::new("", ""), + blockquote: SimpleTag::new("
", "
"), + italic: SimpleTag::new("", ""), + underline: SimpleTag::new("", ""), + strikethrough: SimpleTag::new("", ""), + spoiler: SimpleTag::new("", ""), + code: SimpleTag::new("", ""), + pre_no_lang: SimpleTag::new("
", "
"), + pre: ComplexTag::new("
", "
"), + text_link: ComplexTag::new("", ""), + text_mention: ComplexTag::new("", ""), + custom_emoji: ComplexTag::new("", ""), + + write_tag_fn: write_tag, + write_char_fn: write_char, +}; + +fn write_tag(tag: &Tag, buf: &mut String) { + match tag.kind { + Kind::Bold => buf.push_str(HTML.bold.get_tag(tag.place)), + Kind::Blockquote => buf.push_str(HTML.blockquote.get_tag(tag.place)), + Kind::Italic => buf.push_str(HTML.italic.get_tag(tag.place)), + Kind::Underline => buf.push_str(HTML.underline.get_tag(tag.place)), + Kind::Strikethrough => buf.push_str(HTML.strikethrough.get_tag(tag.place)), + Kind::Spoiler => buf.push_str(HTML.spoiler.get_tag(tag.place)), + Kind::Code => buf.push_str(HTML.code.get_tag(tag.place)), + Kind::Pre(lang) => match tag.place { + Place::Start => match lang { + Some(lang) => write!(buf, "{}{}{}", HTML.pre.start, lang, HTML.pre.middle).unwrap(), + None => buf.push_str(HTML.pre_no_lang.start), + }, + Place::End => buf.push_str(lang.map_or(HTML.pre_no_lang.end, |_| HTML.pre.end)), + }, + Kind::TextLink(url) => match tag.place { + Place::Start => { + write!(buf, "{}{}{}", HTML.text_link.start, url, HTML.text_link.middle).unwrap() + } + Place::End => buf.push_str(HTML.text_link.end), + }, + Kind::TextMention(id) => match tag.place { + Place::Start => { + write!(buf, "{}{}{}", HTML.text_mention.start, id, HTML.text_mention.middle) + .unwrap() + } + Place::End => buf.push_str(HTML.text_mention.end), + }, + Kind::CustomEmoji(custom_emoji_id) => match tag.place { + Place::Start => write!( + buf, + "{}{}{}", + HTML.custom_emoji.start, custom_emoji_id, HTML.custom_emoji.middle + ) + .unwrap(), + Place::End => buf.push_str(HTML.custom_emoji.end), + }, + } +} + +fn write_char(ch: char, buf: &mut String) { + match ch { + '&' => buf.push_str("&"), + '<' => buf.push_str("<"), + '>' => buf.push_str(">"), + c => buf.push(c), + } +} diff --git a/crates/teloxide/src/utils/render/markdown.rs b/crates/teloxide/src/utils/render/markdown.rs new file mode 100644 index 00000000..5b94c192 --- /dev/null +++ b/crates/teloxide/src/utils/render/markdown.rs @@ -0,0 +1,74 @@ +use std::fmt::Write; + +use crate::utils::markdown::ESCAPE_CHARS; + +use super::{ComplexTag, Kind, Place, SimpleTag, Tag, TagWriter}; + +pub static MARKDOWN: TagWriter = TagWriter { + bold: SimpleTag::new("**", "**"), + blockquote: SimpleTag::new(">", ""), + italic: SimpleTag::new("_\r", "_\r"), + underline: SimpleTag::new("__\r", "__\r"), + strikethrough: SimpleTag::new("~", "~"), + spoiler: SimpleTag::new("||", "||"), + code: SimpleTag::new("`", "`"), + pre_no_lang: SimpleTag::new("```\n", "```\n"), + pre: ComplexTag::new("```", "\n", "```\n"), + text_link: ComplexTag::new("[", "](", ")"), + text_mention: ComplexTag::new("[", "](tg://user?id=", ")"), + custom_emoji: ComplexTag::new("[", "](tg://emoji?id=", ")"), + + write_tag_fn: write_tag, + write_char_fn: write_char, +}; + +fn write_tag(tag: &Tag, buf: &mut String) { + match tag.kind { + Kind::Bold => buf.push_str(MARKDOWN.bold.get_tag(tag.place)), + Kind::Blockquote => buf.push_str(MARKDOWN.blockquote.get_tag(tag.place)), + Kind::Italic => buf.push_str(MARKDOWN.italic.get_tag(tag.place)), + Kind::Underline => buf.push_str(MARKDOWN.underline.get_tag(tag.place)), + Kind::Strikethrough => buf.push_str(MARKDOWN.strikethrough.get_tag(tag.place)), + Kind::Spoiler => buf.push_str(MARKDOWN.spoiler.get_tag(tag.place)), + Kind::Code => buf.push_str(MARKDOWN.code.get_tag(tag.place)), + Kind::Pre(lang) => match tag.place { + Place::Start => match lang { + Some(lang) => { + write!(buf, "{}{}{}", MARKDOWN.pre.start, lang, MARKDOWN.pre.middle).unwrap() + } + None => buf.push_str(MARKDOWN.pre_no_lang.start), + }, + Place::End => buf.push_str(lang.map_or(MARKDOWN.pre_no_lang.end, |_| MARKDOWN.pre.end)), + }, + Kind::TextLink(url) => match tag.place { + Place::Start => buf.push_str(MARKDOWN.text_link.start), + Place::End => { + write!(buf, "{}{}{}", MARKDOWN.text_link.middle, url, MARKDOWN.text_link.end) + .unwrap() + } + }, + Kind::TextMention(id) => match tag.place { + Place::Start => buf.push_str(MARKDOWN.text_mention.start), + Place::End => { + write!(buf, "{}{}{}", MARKDOWN.text_mention.middle, id, MARKDOWN.text_mention.end) + .unwrap() + } + }, + Kind::CustomEmoji(custom_emoji_id) => match tag.place { + Place::Start => buf.push_str(MARKDOWN.custom_emoji.start), + Place::End => write!( + buf, + "{}{}{}", + MARKDOWN.custom_emoji.middle, custom_emoji_id, MARKDOWN.custom_emoji.end + ) + .unwrap(), + }, + } +} + +fn write_char(ch: char, buf: &mut String) { + if ESCAPE_CHARS.contains(&ch) { + buf.push('\\'); + } + buf.push(ch); +} diff --git a/crates/teloxide/src/utils/render/mod.rs b/crates/teloxide/src/utils/render/mod.rs new file mode 100644 index 00000000..2f42a89a --- /dev/null +++ b/crates/teloxide/src/utils/render/mod.rs @@ -0,0 +1,255 @@ +//! Util for rendering texts and message entities to html and markdown string + +use teloxide_core::types::{MessageEntity, MessageEntityKind as MEK}; + +use tag::*; + +pub use helper::RenderMessageTextHelper; + +mod helper; +mod html; +mod markdown; +mod tag; + +/// The [`Render`] struct is responsible for parsing the text and entities to +/// produce the final formatted output. +#[derive(Clone, Eq, PartialEq)] +pub struct Render<'a> { + text: &'a str, + tags: Vec>, +} + +impl<'a> Render<'a> { + /// Creates a new `Render` instance with the given text and entities. + /// + /// The `Render` is responsible for parsing the text and entities to + /// produce the final formatted output. This constructor sets up the + /// initial state needed for the parsing process. + /// + /// # Arguments + /// + /// - `text`: The input text to be parsed. + /// - `entities`: The message entities (formatting, links, etc.) to be + /// applied to the text. + /// + /// # Returns + /// + /// A new [`Render`] instance. + #[must_use] + pub fn new(text: &'a str, entities: &'a [MessageEntity]) -> Self { + // get the needed size for the new tags that we want to parse from entities + let needed_size: usize = entities + .iter() + .filter(|e| { + matches!( + e.kind, + MEK::Bold + | MEK::Blockquote + | MEK::Italic + | MEK::Underline + | MEK::Strikethrough + | MEK::Spoiler + | MEK::Code + | MEK::Pre { .. } + | MEK::TextLink { .. } + | MEK::TextMention { .. } + | MEK::CustomEmoji { .. } + ) + }) + .count() + * 2; // 2 because we inseret two tag for each entity + + let mut tags = Vec::with_capacity(needed_size); + + for (index, entity) in entities.iter().enumerate() { + let kind = match &entity.kind { + MEK::Bold => Kind::Bold, + MEK::Blockquote => Kind::Blockquote, + MEK::Italic => Kind::Italic, + MEK::Underline => Kind::Underline, + MEK::Strikethrough => Kind::Strikethrough, + MEK::Spoiler => Kind::Spoiler, + MEK::Code => Kind::Code, + MEK::Pre { language } => Kind::Pre(language.as_ref().map(String::as_str)), + MEK::TextLink { url } => Kind::TextLink(url.as_str()), + MEK::TextMention { user } => Kind::TextMention(user.id.0), + MEK::CustomEmoji { custom_emoji_id } => Kind::CustomEmoji(custom_emoji_id), + _ => continue, + }; + + // FIXME: maybe instead of clone store all the `kind`s in a seperate + // vector and then just store the index here? + tags.push(Tag::start(kind.clone(), entity.offset, index)); + tags.push(Tag::end(kind, entity.offset + entity.length, index)); + } + + tags.sort_unstable(); + + Self { text, tags } + } + + /// Renders the text with the given [`TagWriter`]. + /// + /// This method iterates through the text and the associated position tags, + /// and writes the text with the appropriate tags to a buffer. The + /// resulting buffer is then returned as a `String`. + /// + /// If input have no tags we just return the original text as-is. + #[must_use] + fn format(&self, writer: &TagWriter) -> String { + if self.tags.is_empty() { + return self.text.to_owned(); + } + + let mut buffer = String::with_capacity(self.text.len() + writer.get_tags_sizes(&self.tags)); + let mut tags = self.tags.iter(); + let mut current_tag = tags.next(); + + let mut prev_point = None; + + for (idx, point) in self.text.encode_utf16().enumerate() { + loop { + match current_tag { + Some(tag) if tag.offset == idx => { + (writer.write_tag_fn)(tag, &mut buffer); + current_tag = tags.next(); + } + _ => break, + } + } + + let ch = if let Some(previous) = prev_point.take() { + char::decode_utf16([previous, point]).next().unwrap().unwrap() + } else { + match char::decode_utf16([point]).next().unwrap() { + Ok(c) => c, + Err(unpaired) => { + prev_point = Some(unpaired.unpaired_surrogate()); + continue; + } + } + }; + + (writer.write_char_fn)(ch, &mut buffer); + } + + for tag in current_tag.into_iter().chain(tags) { + (writer.write_tag_fn)(tag, &mut buffer); + } + + buffer + } + + /// Render and return the text as **Html-formatted** string. + #[must_use] + pub fn as_html(&self) -> String { + self.format(&html::HTML) + } + + /// Render and return the text as **Markdown-formatted** string. + #[must_use] + pub fn as_markdown(&self) -> String { + self.format(&markdown::MARKDOWN) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_render_simple() { + let text = "Bold italic Bold italic <underline_"); + assert_eq!(render.as_markdown(), "**Bold** _\ritalic_\r __\rpre, normal and
rusty
code", + ); + assert_eq!( + render.as_markdown(), + "Some ```\npre```\n, `normal` and ```rust\nrusty```\n code", + ); + } + + #[test] + fn test_render_nested() { + let text = "Some bold both italics"; + let entities = vec![ + MessageEntity { kind: MEK::Bold, offset: 5, length: 9 }, + MessageEntity { kind: MEK::Italic, offset: 10, length: 12 }, + ]; + + let render = Render::new(text, &entities); + + assert_eq!(render.as_html(), "Some bold both italics"); + assert_eq!(render.as_markdown(), "Some **bold _\rboth** italics_\r"); + } + + #[test] + fn test_render_complex() { + let text = "Hi how are you?\nnested entities are cool\nIm in a Blockquote!"; + let entities = vec![ + MessageEntity { kind: MEK::Bold, offset: 0, length: 2 }, + MessageEntity { kind: MEK::Italic, offset: 3, length: 3 }, + MessageEntity { kind: MEK::Underline, offset: 7, length: 3 }, + MessageEntity { kind: MEK::Strikethrough, offset: 11, length: 3 }, + MessageEntity { kind: MEK::Bold, offset: 16, length: 1 }, + MessageEntity { kind: MEK::Bold, offset: 17, length: 5 }, + MessageEntity { kind: MEK::Underline, offset: 17, length: 4 }, + MessageEntity { kind: MEK::Strikethrough, offset: 17, length: 4 }, + MessageEntity { + kind: MEK::TextLink { url: reqwest::Url::parse("https://t.me/").unwrap() }, + offset: 23, + length: 8, + }, + MessageEntity { + kind: MEK::TextLink { url: reqwest::Url::parse("tg://user?id=1234567").unwrap() }, + offset: 32, + length: 3, + }, + MessageEntity { kind: MEK::Code, offset: 36, length: 4 }, + MessageEntity { kind: MEK::Blockquote, offset: 41, length: 19 }, + ]; + + let render = Render::new(text, &entities); + + assert_eq!( + render.as_html(), + "Hi how are you?\nnested \ + entities are cool\n\ +
Im in a Blockquote!
" + ); + assert_eq!( + render.as_markdown(), + "**Hi** _\rhow_\r __\rare__\r ~you~?\n**n****__\r~este~__\rd** [entities](https://t.me/) \ + [are](tg://user?id=1234567) `cool`\n>Im in a Blockquote\\!" + ); + } +} diff --git a/crates/teloxide/src/utils/render/tag.rs b/crates/teloxide/src/utils/render/tag.rs new file mode 100644 index 00000000..096b5d05 --- /dev/null +++ b/crates/teloxide/src/utils/render/tag.rs @@ -0,0 +1,156 @@ +use std::cmp::Ordering; + +#[derive(Clone)] +pub struct Tag<'a> { + pub place: Place, + pub kind: Kind<'a>, + pub offset: usize, + pub index: usize, +} + +impl<'a> Tag<'a> { + pub const fn start(kind: Kind<'a>, offset: usize, index: usize) -> Self { + Self { place: Place::Start, kind, offset, index } + } + + pub const fn end(kind: Kind<'a>, offset: usize, index: usize) -> Self { + Self { place: Place::End, kind, offset, index } + } +} + +impl<'a> Eq for Tag<'a> {} + +impl<'a> PartialEq for Tag<'a> { + fn eq(&self, other: &Self) -> bool { + // We don't check kind here + self.place == other.place && self.offset == other.offset && self.index == other.index + } +} + +impl<'a> Ord for Tag<'a> { + fn cmp(&self, other: &Self) -> Ordering { + self.offset.cmp(&other.offset).then_with(|| self.place.cmp(&other.place)).then_with(|| { + match other.place { + Place::Start => self.index.cmp(&other.index), + Place::End => other.index.cmp(&self.index), + } + }) + } +} + +impl<'a> PartialOrd for Tag<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Place { + // HACK: `End` needs to be first because of the `Ord` Implementation. + // the reason is when comparing tags we want the `End` to be first if the offset + // is the same. + End, + Start, +} + +#[derive(Clone, PartialEq, Eq)] +pub enum Kind<'a> { + Bold, + Blockquote, + Italic, + Underline, + Strikethrough, + Spoiler, + Code, + Pre(Option<&'a str>), + TextLink(&'a str), + TextMention(u64), + CustomEmoji(&'a str), +} + +pub struct SimpleTag { + pub start: &'static str, + pub end: &'static str, +} + +impl SimpleTag { + pub const fn new(start: &'static str, end: &'static str) -> Self { + Self { start, end } + } + + /// Get tag size based on place + pub const fn get_tag(&self, place: Place) -> &'static str { + match place { + Place::Start => self.start, + Place::End => self.end, + } + } +} + +pub struct ComplexTag { + pub start: &'static str, + pub middle: &'static str, + pub end: &'static str, +} + +impl ComplexTag { + pub const fn new(start: &'static str, middle: &'static str, end: &'static str) -> Self { + Self { start, middle, end } + } +} + +pub struct TagWriter { + pub bold: SimpleTag, + pub blockquote: SimpleTag, + pub italic: SimpleTag, + pub underline: SimpleTag, + pub strikethrough: SimpleTag, + pub spoiler: SimpleTag, + pub code: SimpleTag, + pub pre_no_lang: SimpleTag, + pub pre: ComplexTag, + pub text_link: ComplexTag, + pub text_mention: ComplexTag, + pub custom_emoji: ComplexTag, + + /// Write the tag to buffer + pub write_tag_fn: fn(&Tag, buf: &mut String), + /// Write the char to buffer and escape characters if needed + pub write_char_fn: fn(char, buf: &mut String), +} + +impl TagWriter { + /// Get the extra size needed for tags + pub fn get_tags_sizes(&self, tags: &[Tag]) -> usize { + tags.iter() + .map(|tag| match tag.kind { + Kind::Bold => self.bold.get_tag(tag.place).len(), + Kind::Blockquote => self.blockquote.get_tag(tag.place).len(), + Kind::Italic => self.italic.get_tag(tag.place).len(), + Kind::Underline => self.underline.get_tag(tag.place).len(), + Kind::Strikethrough => self.strikethrough.get_tag(tag.place).len(), + Kind::Spoiler => self.spoiler.get_tag(tag.place).len(), + Kind::Code => self.code.get_tag(tag.place).len(), + Kind::Pre(lang) => match tag.place { + Place::Start => lang + .map_or(self.pre_no_lang.start.len(), |l| self.pre.start.len() + l.len()), + Place::End => lang.map_or(self.pre_no_lang.end.len(), |_| { + self.pre.middle.len() + self.pre.end.len() + }), + }, + Kind::TextLink(url) => match tag.place { + Place::Start => self.text_link.start.len() + url.len(), + Place::End => self.text_link.middle.len() + self.text_link.end.len(), + }, + Kind::TextMention(id) => match tag.place { + Place::Start => self.text_mention.start.len() + id.ilog10() as usize + 1, + Place::End => self.text_mention.middle.len() + self.text_mention.end.len(), + }, + Kind::CustomEmoji(custom_emoji_id) => match tag.place { + Place::Start => self.custom_emoji.start.len() + custom_emoji_id.len(), + Place::End => self.custom_emoji.middle.len() + self.custom_emoji.end.len(), + }, + }) + .sum() + } +} From 0c63375c03e3d846ffaac2a4a56be1ffc21edd64 Mon Sep 17 00:00:00 2001 From: YouKnow Date: Tue, 27 Aug 2024 05:53:37 +0330 Subject: [PATCH 02/10] perf(render): optimize tag and rendering functions with inline attributes - Add #[inline] attribute to `as_html` and `as_markdown` methods in Render struct - Add #[inline(always)] attribute to `start` and `end` methods in Tag struct - Add #[inline] attribute to `new` methods in SimpleTag and ComplexTag structs --- crates/teloxide/src/utils/render/mod.rs | 2 ++ crates/teloxide/src/utils/render/tag.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/crates/teloxide/src/utils/render/mod.rs b/crates/teloxide/src/utils/render/mod.rs index 2f42a89a..b8ffb8a1 100644 --- a/crates/teloxide/src/utils/render/mod.rs +++ b/crates/teloxide/src/utils/render/mod.rs @@ -142,12 +142,14 @@ impl<'a> Render<'a> { /// Render and return the text as **Html-formatted** string. #[must_use] + #[inline] pub fn as_html(&self) -> String { self.format(&html::HTML) } /// Render and return the text as **Markdown-formatted** string. #[must_use] + #[inline] pub fn as_markdown(&self) -> String { self.format(&markdown::MARKDOWN) } diff --git a/crates/teloxide/src/utils/render/tag.rs b/crates/teloxide/src/utils/render/tag.rs index 096b5d05..feff4d1c 100644 --- a/crates/teloxide/src/utils/render/tag.rs +++ b/crates/teloxide/src/utils/render/tag.rs @@ -9,10 +9,12 @@ pub struct Tag<'a> { } impl<'a> Tag<'a> { + #[inline(always)] pub const fn start(kind: Kind<'a>, offset: usize, index: usize) -> Self { Self { place: Place::Start, kind, offset, index } } + #[inline(always)] pub const fn end(kind: Kind<'a>, offset: usize, index: usize) -> Self { Self { place: Place::End, kind, offset, index } } @@ -74,6 +76,7 @@ pub struct SimpleTag { } impl SimpleTag { + #[inline] pub const fn new(start: &'static str, end: &'static str) -> Self { Self { start, end } } @@ -94,6 +97,7 @@ pub struct ComplexTag { } impl ComplexTag { + #[inline] pub const fn new(start: &'static str, middle: &'static str, end: &'static str) -> Self { Self { start, middle, end } } From 9b3d920819387ef5846e619fb6055e4d31c25618 Mon Sep 17 00:00:00 2001 From: YouKnow Date: Wed, 4 Sep 2024 05:04:37 +0330 Subject: [PATCH 03/10] added '\' to markdown escape characters --- crates/teloxide/src/utils/markdown.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide/src/utils/markdown.rs b/crates/teloxide/src/utils/markdown.rs index 68de26a1..303622fa 100644 --- a/crates/teloxide/src/utils/markdown.rs +++ b/crates/teloxide/src/utils/markdown.rs @@ -4,8 +4,8 @@ use teloxide_core::types::{User, UserId}; -pub(super) const ESCAPE_CHARS: [char; 18] = - ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']; +pub(super) const ESCAPE_CHARS: [char; 19] = + ['\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']; /// Applies the bold font style to the string. /// From 4f5dcbf4c2e3f42777fc674923daf3de2a7b68d7 Mon Sep 17 00:00:00 2001 From: YouKnow Date: Wed, 4 Sep 2024 05:14:38 +0330 Subject: [PATCH 04/10] fixed the escape test --- crates/teloxide/src/utils/markdown.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/teloxide/src/utils/markdown.rs b/crates/teloxide/src/utils/markdown.rs index 303622fa..36e96beb 100644 --- a/crates/teloxide/src/utils/markdown.rs +++ b/crates/teloxide/src/utils/markdown.rs @@ -4,8 +4,9 @@ use teloxide_core::types::{User, UserId}; -pub(super) const ESCAPE_CHARS: [char; 19] = - ['\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']; +pub(super) const ESCAPE_CHARS: [char; 19] = [ + '\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!', +]; /// Applies the bold font style to the string. /// @@ -247,10 +248,11 @@ mod tests { #[test] fn test_escape() { + assert_eq!(escape("\\!"), r"\\\!"); assert_eq!(escape("* foobar *"), r"\* foobar \*"); assert_eq!( escape(r"_ * [ ] ( ) ~ \ ` > # + - = | { } . !"), - r"\_ \* \[ \] \( \) \~ \ \` \> \# \+ \- \= \| \{ \} \. \!", + r"\_ \* \[ \] \( \) \~ \\ \` \> \# \+ \- \= \| \{ \} \. \!", ); } From e96139634dac709a283be328ea4395b8c97a1e1b Mon Sep 17 00:00:00 2001 From: YouKnow Date: Wed, 4 Sep 2024 19:38:53 +0330 Subject: [PATCH 05/10] fixed clippy errors for rust nightly --- .../teloxide-core/src/types/keyboard_button_request_chat.rs | 2 ++ .../teloxide-core/src/types/keyboard_button_request_users.rs | 2 ++ crates/teloxide/src/stop.rs | 5 +---- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs index 47f9c4a3..8ff9e10b 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize}; use crate::types::{ChatAdministratorRights, RequestId}; /// This object defines the criteria used to request a suitable chat. +/// +/// /// Information about the selected chat will be shared with the bot when the /// corresponding button is pressed. The bot will be granted requested rights in /// the chat if appropriate. [More about requesting chats »] diff --git a/crates/teloxide-core/src/types/keyboard_button_request_users.rs b/crates/teloxide-core/src/types/keyboard_button_request_users.rs index 8c38b831..0ba4632c 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_users.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_users.rs @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize}; use crate::types::RequestId; /// This object defines the criteria used to request a suitable users. +/// +/// /// Information about the selected users will be shared with the bot when the /// corresponding button is pressed. More about requesting users » /// diff --git a/crates/teloxide/src/stop.rs b/crates/teloxide/src/stop.rs index e7b185ba..9caae098 100644 --- a/crates/teloxide/src/stop.rs +++ b/crates/teloxide/src/stop.rs @@ -53,9 +53,6 @@ impl Future for StopFlag { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { - self.project().0.poll(cx).map(|res| match res { - Err(_aborted) => (), - Ok(unreachable) => match unreachable {}, - }) + self.project().0.poll(cx).map(|_res| ()) } } From adb9830c9334b943f66ba273d5a09b02adaceedd Mon Sep 17 00:00:00 2001 From: YouKnow Date: Sun, 20 Oct 2024 02:26:18 +0330 Subject: [PATCH 06/10] Improve documentation and code comments --- CHANGELOG.md | 22 +++++++++++++------ .../src/types/keyboard_button_request_chat.rs | 1 - .../types/keyboard_button_request_users.rs | 1 - crates/teloxide/src/utils/render/helper.rs | 7 ++++-- crates/teloxide/src/utils/render/html.rs | 1 - crates/teloxide/src/utils/render/markdown.rs | 1 - crates/teloxide/src/utils/render/mod.rs | 15 +++++-------- crates/teloxide/src/utils/render/tag.rs | 7 +----- 8 files changed, 27 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ff1d08..0e6fea15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,19 +8,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `filter_boost_added` and `filter_reply_to_story` filters to `MessageFilterExt` trait -- Add `filter_mention_command` filter to `HandlerExt` trait ([issue #494](https://github.com/teloxide/teloxide/issues/494)) -- Add `filter_business_connection`, `filter_business_message`, `filter_edited_business_message`, and `filter_deleted_business_messages` filters to update filters ([PR 1146](https://github.com/teloxide/teloxide/pull/1146)) -- Add `Render` util to recreate rendered Html or Markdown formatted text from text/caption and `MessageEntity` ([PR 1152](https://github.com/teloxide/teloxide/pull/1152)) +- `filter_boost_added` and `filter_reply_to_story` filters to the `MessageFilterExt` trait ([PR 1131](https://github.com/teloxide/teloxide/pull/1131)) +- `filter_mention_command` filter to the `HandlerExt` trait ([issue 494](https://github.com/teloxide/teloxide/issues/494)) +- `filter_business_connection`, `filter_business_message`, `filter_edited_business_message`, and `filter_deleted_business_messages` filters to the `UpdateFilterExt` trait ([PR 1146](https://github.com/teloxide/teloxide/pull/1146)) +- Syntax sugar for making requests ([issue 1143](https://github.com/teloxide/teloxide/issues/1143)): + - `bot.forward`, `bot.edit_live_location`, `bot.stop_live_location`, `bot.set_reaction`, `bot.pin`, `bot.unpin`, `bot.edit_text`, `bot.edit_caption`, `bot.edit_media`, `bot.edit_reply_markup`, `bot.stop_poll_message`, `bot.delete` and `bot.copy` methods to the new `crate::sugar::bot::BotMessagesExt` trait + - `req.reply_to` method to the new `crate::sugar::request::RequestReplyExt` trait + - `req.disable_link_preview` method to the new `crate::sugar::request::RequestLinkPreviewExt` trait +- Add `Render` utilities to recreate rendered Html or Markdown formatted text from text/caption and a list of `MessageEntity` ([PR 1152](https://github.com/teloxide/teloxide/pull/1152)) ### Changed -- Environment bumps: ([#1147][pr1147]) +- Environment bumps: ([PR 1147](https://github.com/teloxide/teloxide/pull/1147)) - MSRV (Minimal Supported Rust Version) was bumped from `1.70.0` to `1.80.0` - Some dependencies was bumped: `sqlx` to `0.8.1`, `tower` to `0.5.0`, `reqwest` to `0.12.7` - `tokio` version was explicitly specified as `1.39` -[pr1147]: https://github.com/teloxide/teloxide/pull/1147 +### Fixed + +- Now Vec in requests serializes into [number] instead of [ {message_id: number} ], `forward_messages`, `copy_messages` and `delete_messages` now work properly +- Now `InlineQueryResultsButton` serializes properly ([issue 1181](https://github.com/teloxide/teloxide/issues/1181)) +- Now `ThreadId` is able to serialize in multipart requests ([PR 1179](https://github.com/teloxide/teloxide/pull/1179)) ## 0.13.0 - 2024-08-16 @@ -553,4 +561,4 @@ This release was yanked because it accidentally [breaks backwards compatibility] ## 0.1.0 - 2020-02-19 ### Added -- This project. +- This project. \ No newline at end of file diff --git a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs index 8ff9e10b..94644aa4 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs @@ -4,7 +4,6 @@ use crate::types::{ChatAdministratorRights, RequestId}; /// This object defines the criteria used to request a suitable chat. /// -/// /// Information about the selected chat will be shared with the bot when the /// corresponding button is pressed. The bot will be granted requested rights in /// the chat if appropriate. [More about requesting chats »] diff --git a/crates/teloxide-core/src/types/keyboard_button_request_users.rs b/crates/teloxide-core/src/types/keyboard_button_request_users.rs index 0ba4632c..e7b86f3b 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_users.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_users.rs @@ -4,7 +4,6 @@ use crate::types::RequestId; /// This object defines the criteria used to request a suitable users. /// -/// /// Information about the selected users will be shared with the bot when the /// corresponding button is pressed. More about requesting users » /// diff --git a/crates/teloxide/src/utils/render/helper.rs b/crates/teloxide/src/utils/render/helper.rs index d827e890..1b9e1c96 100644 --- a/crates/teloxide/src/utils/render/helper.rs +++ b/crates/teloxide/src/utils/render/helper.rs @@ -1,5 +1,5 @@ -//! A helprt trait for rendering text/caption and entities back to html or -//! markdown +//! A helpful trait for rendering text/caption and entities back to HTML or +//! Markdown. use teloxide_core::types::Message; @@ -14,18 +14,21 @@ pub trait RenderMessageTextHelper { /// string. #[must_use] fn html_text(&self) -> Option; + /// Returns the Markdown representation of the message text, if the message /// contains text. This method will parse the text and any entities /// (such as bold, italic, links, etc.) and return the /// Markdown-formatted string. #[must_use] fn markdown_text(&self) -> Option; + /// Returns the HTML representation of the message caption, if the message /// contains caption. This method will parse the caption and any /// entities (such as bold, italic, links, etc.) and return the /// HTML-formatted string. #[must_use] fn html_caption(&self) -> Option; + /// Returns the Markdown representation of the message caption, if the /// message contains caption. This method will parse the caption and any /// entities (such as bold, italic, links, etc.) and return the diff --git a/crates/teloxide/src/utils/render/html.rs b/crates/teloxide/src/utils/render/html.rs index 8e006b81..95f76070 100644 --- a/crates/teloxide/src/utils/render/html.rs +++ b/crates/teloxide/src/utils/render/html.rs @@ -15,7 +15,6 @@ pub static HTML: TagWriter = TagWriter { text_link: ComplexTag::new("", ""), text_mention: ComplexTag::new("", ""), custom_emoji: ComplexTag::new("", ""), - write_tag_fn: write_tag, write_char_fn: write_char, }; diff --git a/crates/teloxide/src/utils/render/markdown.rs b/crates/teloxide/src/utils/render/markdown.rs index 5b94c192..822274fb 100644 --- a/crates/teloxide/src/utils/render/markdown.rs +++ b/crates/teloxide/src/utils/render/markdown.rs @@ -17,7 +17,6 @@ pub static MARKDOWN: TagWriter = TagWriter { text_link: ComplexTag::new("[", "](", ")"), text_mention: ComplexTag::new("[", "](tg://user?id=", ")"), custom_emoji: ComplexTag::new("[", "](tg://emoji?id=", ")"), - write_tag_fn: write_tag, write_char_fn: write_char, }; diff --git a/crates/teloxide/src/utils/render/mod.rs b/crates/teloxide/src/utils/render/mod.rs index b8ffb8a1..d27f4b0f 100644 --- a/crates/teloxide/src/utils/render/mod.rs +++ b/crates/teloxide/src/utils/render/mod.rs @@ -1,4 +1,4 @@ -//! Util for rendering texts and message entities to html and markdown string +//! Utilities for rendering texts and message entities to HTML and Markdown. use teloxide_core::types::{MessageEntity, MessageEntityKind as MEK}; @@ -31,10 +31,6 @@ impl<'a> Render<'a> { /// - `text`: The input text to be parsed. /// - `entities`: The message entities (formatting, links, etc.) to be /// applied to the text. - /// - /// # Returns - /// - /// A new [`Render`] instance. #[must_use] pub fn new(text: &'a str, entities: &'a [MessageEntity]) -> Self { // get the needed size for the new tags that we want to parse from entities @@ -57,7 +53,7 @@ impl<'a> Render<'a> { ) }) .count() - * 2; // 2 because we inseret two tag for each entity + * 2; // 2 because we insert two tags for each entity let mut tags = Vec::with_capacity(needed_size); @@ -101,7 +97,8 @@ impl<'a> Render<'a> { return self.text.to_owned(); } - let mut buffer = String::with_capacity(self.text.len() + writer.get_tags_sizes(&self.tags)); + let mut buffer = + String::with_capacity(self.text.len() + writer.get_extra_size_for_tags(&self.tags)); let mut tags = self.tags.iter(); let mut current_tag = tags.next(); @@ -140,14 +137,14 @@ impl<'a> Render<'a> { buffer } - /// Render and return the text as **Html-formatted** string. + /// Render and return the text as an **HTML-formatted** string. #[must_use] #[inline] pub fn as_html(&self) -> String { self.format(&html::HTML) } - /// Render and return the text as **Markdown-formatted** string. + /// Render and return the text as a **Markdown-formatted** string. #[must_use] #[inline] pub fn as_markdown(&self) -> String { diff --git a/crates/teloxide/src/utils/render/tag.rs b/crates/teloxide/src/utils/render/tag.rs index feff4d1c..2d250e2a 100644 --- a/crates/teloxide/src/utils/render/tag.rs +++ b/crates/teloxide/src/utils/render/tag.rs @@ -81,7 +81,6 @@ impl SimpleTag { Self { start, end } } - /// Get tag size based on place pub const fn get_tag(&self, place: Place) -> &'static str { match place { Place::Start => self.start, @@ -116,16 +115,12 @@ pub struct TagWriter { pub text_link: ComplexTag, pub text_mention: ComplexTag, pub custom_emoji: ComplexTag, - - /// Write the tag to buffer pub write_tag_fn: fn(&Tag, buf: &mut String), - /// Write the char to buffer and escape characters if needed pub write_char_fn: fn(char, buf: &mut String), } impl TagWriter { - /// Get the extra size needed for tags - pub fn get_tags_sizes(&self, tags: &[Tag]) -> usize { + pub fn get_extra_size_for_tags(&self, tags: &[Tag]) -> usize { tags.iter() .map(|tag| match tag.kind { Kind::Bold => self.bold.get_tag(tag.place).len(), From e91db758c8e925b2816945d9a2a4c19101ce2f67 Mon Sep 17 00:00:00 2001 From: hirrolot Date: Mon, 21 Oct 2024 02:53:32 -0400 Subject: [PATCH 07/10] Adjust wording --- CHANGELOG.md | 3 ++- crates/teloxide/src/utils/render/helper.rs | 4 ++-- crates/teloxide/src/utils/render/mod.rs | 23 +++++++++------------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e6fea15..1db58a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -561,4 +561,5 @@ This release was yanked because it accidentally [breaks backwards compatibility] ## 0.1.0 - 2020-02-19 ### Added -- This project. \ No newline at end of file + +- This project. diff --git a/crates/teloxide/src/utils/render/helper.rs b/crates/teloxide/src/utils/render/helper.rs index 1b9e1c96..09e08851 100644 --- a/crates/teloxide/src/utils/render/helper.rs +++ b/crates/teloxide/src/utils/render/helper.rs @@ -5,8 +5,8 @@ use teloxide_core::types::Message; use super::Render; -/// The [`RenderMessageTextHelper`] trait provides methods to generate HTML and -/// Markdown representations of the text and captions in a Telegram message. +/// Generates HTML and Markdown representations of text and captions in a +/// Telegram message. pub trait RenderMessageTextHelper { /// Returns the HTML representation of the message text, if the message /// contains text. This method will parse the text and any entities diff --git a/crates/teloxide/src/utils/render/mod.rs b/crates/teloxide/src/utils/render/mod.rs index d27f4b0f..27922efa 100644 --- a/crates/teloxide/src/utils/render/mod.rs +++ b/crates/teloxide/src/utils/render/mod.rs @@ -1,4 +1,4 @@ -//! Utilities for rendering texts and message entities to HTML and Markdown. +//! Utils for rendering HTML and Markdown output. use teloxide_core::types::{MessageEntity, MessageEntityKind as MEK}; @@ -11,8 +11,7 @@ mod html; mod markdown; mod tag; -/// The [`Render`] struct is responsible for parsing the text and entities to -/// produce the final formatted output. +/// Parses text and message entities to produce the final formatted output. #[derive(Clone, Eq, PartialEq)] pub struct Render<'a> { text: &'a str, @@ -20,11 +19,7 @@ pub struct Render<'a> { } impl<'a> Render<'a> { - /// Creates a new `Render` instance with the given text and entities. - /// - /// The `Render` is responsible for parsing the text and entities to - /// produce the final formatted output. This constructor sets up the - /// initial state needed for the parsing process. + /// Creates a new [`Render`] instance with given text and message entities. /// /// # Arguments /// @@ -84,11 +79,11 @@ impl<'a> Render<'a> { Self { text, tags } } - /// Renders the text with the given [`TagWriter`]. + /// Renders text with a given [`TagWriter`]. /// - /// This method iterates through the text and the associated position tags, - /// and writes the text with the appropriate tags to a buffer. The - /// resulting buffer is then returned as a `String`. + /// This method iterates through the text and the associated position tags + /// and writes the text with the appropriate tags to a buffer, which is then + /// returned as a `String`. /// /// If input have no tags we just return the original text as-is. #[must_use] @@ -137,14 +132,14 @@ impl<'a> Render<'a> { buffer } - /// Render and return the text as an **HTML-formatted** string. + /// Renders and returns the text as an **HTML-formatted** string. #[must_use] #[inline] pub fn as_html(&self) -> String { self.format(&html::HTML) } - /// Render and return the text as a **Markdown-formatted** string. + /// Renders and returns the text as a **Markdown-formatted** string. #[must_use] #[inline] pub fn as_markdown(&self) -> String { From 0e7582225fa85e375a780eb59b2911463ad4251a Mon Sep 17 00:00:00 2001 From: hirrolot Date: Mon, 21 Oct 2024 02:55:04 -0400 Subject: [PATCH 08/10] Use `src/utils/render.rs` instead of `mod.rs` --- crates/teloxide/src/utils/{render/mod.rs => render.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename crates/teloxide/src/utils/{render/mod.rs => render.rs} (100%) diff --git a/crates/teloxide/src/utils/render/mod.rs b/crates/teloxide/src/utils/render.rs similarity index 100% rename from crates/teloxide/src/utils/render/mod.rs rename to crates/teloxide/src/utils/render.rs From c1873e0b18ff7ac915fc7c9e13ec646df2f716ee Mon Sep 17 00:00:00 2001 From: YouKnow Date: Tue, 22 Oct 2024 03:04:18 +0330 Subject: [PATCH 09/10] renamed `Render` to `Renderer` --- crates/teloxide/src/utils/render.rs | 15 ++++++++------- crates/teloxide/src/utils/render/helper.rs | 10 +++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/teloxide/src/utils/render.rs b/crates/teloxide/src/utils/render.rs index 27922efa..a609b233 100644 --- a/crates/teloxide/src/utils/render.rs +++ b/crates/teloxide/src/utils/render.rs @@ -13,13 +13,14 @@ mod tag; /// Parses text and message entities to produce the final formatted output. #[derive(Clone, Eq, PartialEq)] -pub struct Render<'a> { +pub struct Renderer<'a> { text: &'a str, tags: Vec>, } -impl<'a> Render<'a> { - /// Creates a new [`Render`] instance with given text and message entities. +impl<'a> Renderer<'a> { + /// Creates a new [`Renderer`] instance with given text and message + /// entities. /// /// # Arguments /// @@ -160,7 +161,7 @@ mod test { MessageEntity { kind: MEK::Underline, offset: 12, length: 10 }, ]; - let render = Render::new(text, &entities); + let render = Renderer::new(text, &entities); assert_eq!(render.as_html(), "Bold italic <underline_"); assert_eq!(render.as_markdown(), "**Bold** _\ritalic_\r __\rbold both italics"); assert_eq!(render.as_markdown(), "Some **bold _\rboth** italics_\r"); @@ -232,7 +233,7 @@ mod test { MessageEntity { kind: MEK::Blockquote, offset: 41, length: 19 }, ]; - let render = Render::new(text, &entities); + let render = Renderer::new(text, &entities); assert_eq!( render.as_html(), diff --git a/crates/teloxide/src/utils/render/helper.rs b/crates/teloxide/src/utils/render/helper.rs index 09e08851..929c5752 100644 --- a/crates/teloxide/src/utils/render/helper.rs +++ b/crates/teloxide/src/utils/render/helper.rs @@ -3,7 +3,7 @@ use teloxide_core::types::Message; -use super::Render; +use super::Renderer; /// Generates HTML and Markdown representations of text and captions in a /// Telegram message. @@ -41,24 +41,24 @@ impl RenderMessageTextHelper for Message { fn html_text(&self) -> Option { self.text() .zip(self.entities()) - .map(|(text, entities)| Render::new(text, entities).as_html()) + .map(|(text, entities)| Renderer::new(text, entities).as_html()) } fn markdown_text(&self) -> Option { self.text() .zip(self.entities()) - .map(|(text, entities)| Render::new(text, entities).as_markdown()) + .map(|(text, entities)| Renderer::new(text, entities).as_markdown()) } fn html_caption(&self) -> Option { self.caption() .zip(self.caption_entities()) - .map(|(text, entities)| Render::new(text, entities).as_html()) + .map(|(text, entities)| Renderer::new(text, entities).as_html()) } fn markdown_caption(&self) -> Option { self.caption() .zip(self.caption_entities()) - .map(|(text, entities)| Render::new(text, entities).as_markdown()) + .map(|(text, entities)| Renderer::new(text, entities).as_markdown()) } } From fb1f71476fa911ec833807cf66c69ac72ef773ac Mon Sep 17 00:00:00 2001 From: Lewis Pearson Date: Mon, 21 Oct 2024 19:53:43 -0400 Subject: [PATCH 10/10] Reword the changelog slightly --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1db58a00..d493d66d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `bot.forward`, `bot.edit_live_location`, `bot.stop_live_location`, `bot.set_reaction`, `bot.pin`, `bot.unpin`, `bot.edit_text`, `bot.edit_caption`, `bot.edit_media`, `bot.edit_reply_markup`, `bot.stop_poll_message`, `bot.delete` and `bot.copy` methods to the new `crate::sugar::bot::BotMessagesExt` trait - `req.reply_to` method to the new `crate::sugar::request::RequestReplyExt` trait - `req.disable_link_preview` method to the new `crate::sugar::request::RequestLinkPreviewExt` trait -- Add `Render` utilities to recreate rendered Html or Markdown formatted text from text/caption and a list of `MessageEntity` ([PR 1152](https://github.com/teloxide/teloxide/pull/1152)) +- `utils::render` module to render HTML/Markdown-formatted output ([PR 1152](https://github.com/teloxide/teloxide/pull/1152)) ### Changed