Skip to content

Commit

Permalink
refactor!: use Cow<'_, str> instead of String
Browse files Browse the repository at this point in the history
  • Loading branch information
decahedron1 committed Nov 8, 2024
1 parent 09b2f53 commit 88e9252
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 163 deletions.
61 changes: 39 additions & 22 deletions src/audio.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use alloc::{
string::{String, ToString},
vec::Vec
};
use alloc::{borrow::Cow, string::ToString, vec::Vec};
use core::fmt::{self, Display, Write};

use crate::{
Expand All @@ -28,27 +25,24 @@ pub enum AudioRepeat {
/// with synthesized speech output.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Audio {
src: String,
desc: Option<String>,
alternate: Vec<Element>,
pub struct Audio<'s> {
src: Cow<'s, str>,
desc: Option<Cow<'s, str>>,
alternate: Vec<Element<'s>>,
clip: (Option<TimeDesignation>, Option<TimeDesignation>),
repeat: Option<AudioRepeat>,
sound_level: Option<Decibels>,
speed: Option<f32>
}

impl Audio {
impl<'s> Audio<'s> {
/// Creates a new [`Audio`] element with an audio source URI.
///
/// ```
/// ssml::audio("https://example.com/Congratulations_You_Won.wav");
/// ```
pub fn new(src: impl ToString) -> Self {
Audio {
src: src.to_string(),
..Audio::default()
}
pub fn new(src: impl Into<Cow<'s, str>>) -> Self {
Audio { src: src.into(), ..Audio::default() }
}

/// Appends alternate (fallback) elements. Alternate elements will be spoken or displayed if the audio document
Expand All @@ -59,7 +53,7 @@ impl Audio {
/// ```
/// ssml::audio("cat_purr.ogg").with_alternate(["PURR (sound didn't load)"]);
/// ```
pub fn with_alternate<S: Into<Element>, I: IntoIterator<Item = S>>(mut self, elements: I) -> Self {
pub fn with_alternate<S: Into<Element<'s>>, I: IntoIterator<Item = S>>(mut self, elements: I) -> Self {
self.alternate.extend(elements.into_iter().map(|f| f.into()));
self
}
Expand All @@ -69,8 +63,8 @@ impl Audio {
/// ```
/// ssml::audio("cat_purr.ogg").with_desc("a purring cat");
/// ```
pub fn with_desc(mut self, desc: impl ToString) -> Self {
self.desc = Some(desc.to_string());
pub fn with_desc(mut self, desc: impl Into<Cow<'s, str>>) -> Self {
self.desc = Some(desc.into());
self
}

Expand Down Expand Up @@ -144,17 +138,40 @@ impl Audio {
}

/// Returns a reference to the elements contained in this `audio` element's alternate/fallback section.
pub fn alternate(&self) -> &[Element] {
pub fn alternate(&self) -> &[Element<'s>] {
&self.alternate
}

/// Returns a reference to the elements contained in this `audio` element's alternate/fallback section.
pub fn alternate_mut(&mut self) -> &mut [Element] {
pub fn alternate_mut(&mut self) -> &mut [Element<'s>] {
&mut self.alternate
}

pub fn to_owned(&self) -> Audio<'static> {
self.clone().into_owned()
}

pub fn into_owned(self) -> Audio<'static> {
Audio {
src: match self.src {
Cow::Borrowed(b) => Cow::Owned(b.to_string()),
Cow::Owned(b) => Cow::Owned(b)
},
desc: match self.desc {
Some(Cow::Borrowed(b)) => Some(Cow::Owned(b.to_string())),
Some(Cow::Owned(b)) => Some(Cow::Owned(b)),
None => None
},
alternate: self.alternate.into_iter().map(Element::into_owned).collect(),
clip: self.clip,
repeat: self.repeat,
sound_level: self.sound_level,
speed: self.speed
}
}
}

impl Serialize for Audio {
impl<'s> Serialize for Audio<'s> {
fn serialize_xml<W: Write>(&self, writer: &mut XmlWriter<W>, options: &SerializeOptions) -> crate::Result<()> {
if options.perform_checks {
if options.flavor == Flavor::GoogleCloudTextToSpeech && self.src.is_empty() {
Expand All @@ -174,7 +191,7 @@ impl Serialize for Audio {
}

writer.element("audio", |writer| {
writer.attr("src", &self.src)?;
writer.attr("src", &*self.src)?;

writer.attr_opt("clipBegin", self.clip.0.as_ref())?;
writer.attr_opt("clipEnd", self.clip.1.as_ref())?;
Expand Down Expand Up @@ -206,7 +223,7 @@ impl Serialize for Audio {
/// ```
/// ssml::audio("https://example.com/Congratulations_You_Won.wav");
/// ```
pub fn audio(src: impl ToString) -> Audio {
pub fn audio<'s>(src: impl Into<Cow<'s, str>>) -> Audio<'s> {
Audio::new(src)
}

Expand Down
71 changes: 45 additions & 26 deletions src/element.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use alloc::{boxed::Box, string::ToString, vec::Vec};
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
use core::fmt::{Debug, Write};

use dyn_clone::DynClone;
Expand All @@ -8,28 +8,28 @@ use crate::{Audio, Break, Emphasis, Mark, Meta, Serialize, SerializeOptions, Tex
macro_rules! el {
(
$(#[$outer:meta])*
pub enum $name:ident {
pub enum $name:ident<'s> {
$(
$(#[$innermeta:meta])*
$variant:ident($inner:ty)
),*
}
) => {
$(#[$outer])*
pub enum $name {
pub enum $name<'s> {
$(
$(#[$innermeta])*
$variant($inner)
),*
}

$(impl From<$inner> for $name {
fn from(val: $inner) -> $name {
$(impl<'s> From<$inner> for $name<'s> {
fn from(val: $inner) -> $name<'s> {
$name::$variant(val)
}
})*

impl $crate::Serialize for $name {
impl<'s> $crate::Serialize for $name<'s> {
fn serialize_xml<W: ::core::fmt::Write>(&self, writer: &mut $crate::XmlWriter<W>, options: &$crate::SerializeOptions) -> $crate::Result<()> {
match self {
$($name::$variant(inner) => inner.serialize_xml(writer, options),)*
Expand All @@ -45,15 +45,15 @@ el! {
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum Element {
Text(Text),
Audio(Audio),
Voice(Voice),
Meta(Meta),
pub enum Element<'s> {
Text(Text<'s>),
Audio(Audio<'s>),
Voice(Voice<'s>),
Meta(Meta<'s>),
Break(Break),
Emphasis(Emphasis),
Mark(Mark),
FlavorMSTTS(crate::mstts::Element),
Emphasis(Emphasis<'s>),
Mark(Mark<'s>),
FlavorMSTTS(crate::mstts::Element<'s>),
/// A dyn element can be used to implement your own custom elements outside of the `ssml` crate. See
/// [`DynElement`] for more information and examples.
Dyn(Box<dyn DynElement>)
Expand All @@ -68,8 +68,27 @@ el! {
}
}

impl<'s> Element<'s> {
pub fn to_owned(&self) -> Element<'static> {
self.clone().into_owned()
}

pub fn into_owned(self) -> Element<'static> {
match self {
Self::Text(el) => Element::Text(el.into_owned()),
Self::Audio(el) => Element::Audio(el.into_owned()),
Self::Voice(el) => Element::Voice(el.into_owned()),
Self::Meta(el) => Element::Meta(el.into_owned()),
Self::Break(el) => Element::Break(el),
Self::Emphasis(el) => Element::Emphasis(el.into_owned()),
Self::Mark(el) => Element::Mark(el.into_owned()),
_ => panic!()
}
}
}

#[cfg(feature = "serde")]
impl<'a> serde::Deserialize<'a> for Element {
impl<'s, 'a> serde::Deserialize<'a> for Element<'s> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>
Expand Down Expand Up @@ -163,12 +182,12 @@ impl<'a> serde::Deserialize<'a> for Element {
}

#[doc(hidden)]
struct Visitor<'de> {
marker: PhantomData<Element>,
struct Visitor<'s, 'de> {
marker: PhantomData<Element<'s>>,
lifetime: PhantomData<&'de ()>
}
impl<'de> serde::de::Visitor<'de> for Visitor<'de> {
type Value = Element;
impl<'s, 'de> serde::de::Visitor<'de> for Visitor<'s, 'de> {
type Value = Element<'s>;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("enum Element")
Expand Down Expand Up @@ -199,9 +218,9 @@ impl<'a> serde::Deserialize<'a> for Element {
}
}

impl<T: ToString> From<T> for Element {
impl<'s, T: Into<Cow<'s, str>>> From<T> for Element<'s> {
fn from(value: T) -> Self {
Element::Text(Text(value.to_string()))
Element::Text(Text::from(value))
}
}

Expand All @@ -216,21 +235,21 @@ impl<T: ToString> From<T> for Element {
/// #[cfg_attr(feature = "serde", derive(serde::Serialize))]
/// pub struct TomfooleryElement {
/// value: f32,
/// children: Vec<Element>
/// children: Vec<Element<'static>>
/// }
///
/// impl TomfooleryElement {
/// // Increase the tomfoolery level of a section of elements.
/// // ...
/// pub fn new<S: Into<Element>, I: IntoIterator<Item = S>>(value: f32, elements: I) -> Self {
/// pub fn new<'s, S: Into<Element<'s>>, I: IntoIterator<Item = S>>(value: f32, elements: I) -> Self {
/// Self {
/// value,
/// children: elements.into_iter().map(|f| f.into()).collect()
/// children: elements.into_iter().map(|f| f.into().into_owned()).collect()
/// }
/// }
///
/// // not required, but makes your code much cleaner!
/// pub fn into_dyn(self) -> Element {
/// pub fn into_dyn(self) -> Element<'static> {
/// Element::Dyn(Box::new(self))
/// }
/// }
Expand All @@ -242,7 +261,7 @@ impl<T: ToString> From<T> for Element {
/// options: &SerializeOptions
/// ) -> ssml::Result<()> {
/// writer.element("tomfoolery", |writer| {
/// writer.attr("influence", self.value.to_string())?;
/// writer.attr("influence", self.value)?;
/// ssml::util::serialize_elements(writer, &self.children, options)
/// })
/// }
Expand Down
31 changes: 21 additions & 10 deletions src/emphasis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,52 @@ pub enum EmphasisLevel {

#[derive(Clone, Default, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Emphasis {
pub struct Emphasis<'s> {
level: EmphasisLevel,
pub(crate) children: Vec<Element>
pub(crate) children: Vec<Element<'s>>
}

impl Emphasis {
pub fn new<S: Into<Element>, I: IntoIterator<Item = S>>(level: EmphasisLevel, elements: I) -> Self {
impl<'s> Emphasis<'s> {
pub fn new<S: Into<Element<'s>>, I: IntoIterator<Item = S>>(level: EmphasisLevel, elements: I) -> Self {
Self {
level,
children: elements.into_iter().map(|f| f.into()).collect()
}
}

pub fn push(&mut self, element: impl Into<Element>) {
pub fn push(&mut self, element: impl Into<Element<'s>>) {
self.children.push(element.into());
}

pub fn extend<S: Into<Element>, I: IntoIterator<Item = S>>(&mut self, elements: I) {
pub fn extend<S: Into<Element<'s>>, I: IntoIterator<Item = S>>(&mut self, elements: I) {
self.children.extend(elements.into_iter().map(|f| f.into()));
}

pub fn level(&self) -> &EmphasisLevel {
&self.level
}

pub fn children(&self) -> &[Element] {
pub fn children(&self) -> &[Element<'s>] {
&self.children
}

pub fn children_mut(&mut self) -> &mut [Element] {
pub fn children_mut(&mut self) -> &mut [Element<'s>] {
&mut self.children
}

pub fn to_owned(&self) -> Emphasis<'static> {
self.clone().into_owned()
}

pub fn into_owned(self) -> Emphasis<'static> {
Emphasis {
level: self.level,
children: self.children.into_iter().map(Element::into_owned).collect()
}
}
}

impl Serialize for Emphasis {
impl<'s> Serialize for Emphasis<'s> {
fn serialize_xml<W: Write>(&self, writer: &mut XmlWriter<W>, _: &SerializeOptions) -> crate::Result<()> {
writer.element("emphasis", |writer| {
writer.attr("level", match self.level {
Expand All @@ -62,6 +73,6 @@ impl Serialize for Emphasis {
}
}

pub fn emphasis<S: Into<Element>, I: IntoIterator<Item = S>>(level: EmphasisLevel, elements: I) -> Emphasis {
pub fn emphasis<'s, S: Into<Element<'s>>, I: IntoIterator<Item = S>>(level: EmphasisLevel, elements: I) -> Emphasis<'s> {
Emphasis::new(level, elements)
}
Loading

0 comments on commit 88e9252

Please sign in to comment.