1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-05-24 11:18:29 +00:00

updated parser

This commit is contained in:
Luro02 2019-09-10 11:05:20 +02:00
parent 91c6698f16
commit 1a35463185
15 changed files with 289 additions and 175 deletions

View file

@ -17,6 +17,7 @@ codecov = {repository = "sile/hls_m3u8"}
[dependencies]
trackable = "0.2"
getset = "0.0.8"
[dev-dependencies]
clap = "2"

View file

@ -141,7 +141,7 @@ impl MediaPlaylistBuilder {
// CHECK: `#EXT-X-BYTE-RANGE`
if let Some(tag) = s.byte_range_tag() {
if tag.range().start.is_none() {
if tag.to_range().start().is_none() {
let last_uri = track_assert_some!(last_range_uri, ErrorKind::InvalidInput);
track_assert_eq!(last_uri, s.uri(), ErrorKind::InvalidInput);
} else {

View file

@ -1,8 +1,10 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
/// [4.3.1.1. EXTM3U]
///
/// [4.3.1.1. EXTM3U]: https://tools.ietf.org/html/rfc8216#section-4.3.1.1
@ -27,8 +29,8 @@ impl fmt::Display for ExtM3u {
impl FromStr for ExtM3u {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput);
fn from_str(input: &str) -> Result<Self, Self::Err> {
tag(input, Self::PREFIX)?;
Ok(ExtM3u)
}
}

View file

@ -1,8 +1,10 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::Error;
/// [4.3.1.2. EXT-X-VERSION]
///
/// [4.3.1.2. EXT-X-VERSION]: https://tools.ietf.org/html/rfc8216#section-4.3.1.2
@ -37,10 +39,8 @@ impl fmt::Display for ExtXVersion {
impl FromStr for ExtXVersion {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let suffix = s.split_at(Self::PREFIX.len()).1;
let version = track!(suffix.parse())?;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let version = tag(input, Self::PREFIX)?.parse()?;
Ok(ExtXVersion::new(version))
}
}

View file

@ -1,22 +1,35 @@
use std::fmt;
use std::str::FromStr;
use getset::{Getters, MutGetters, Setters};
use crate::attribute::AttributePairs;
use crate::types::{DecimalResolution, HdcpLevel, ProtocolVersion};
use crate::utils::parse_u64;
use crate::utils::{quote, unquote};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use crate::utils::{quote, tag, unquote};
use crate::{Error, ErrorKind};
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]
///
/// [4.3.4.3. EXT-X-I-FRAME-STREAM-INF]: https://tools.ietf.org/html/rfc8216#section-4.3.4.3
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Getters, Setters, MutGetters, Debug, Clone, PartialEq, Eq, Hash)]
#[get = "pub"]
#[set = "pub"]
#[get_mut = "pub"]
pub struct ExtXIFrameStreamInf {
/// The URI, that identifies the associated media playlist.
uri: String,
/// The peak segment bit rate of the variant stream.
bandwidth: u64,
/// The average segment bit rate of the variant stream.
average_bandwidth: Option<u64>,
/// A string that represents the list of codec types contained the variant stream.
codecs: Option<String>,
/// The optimal pixel resolution at which to display all the video in the variant stream.
resolution: Option<DecimalResolution>,
/// The HDCP level of the variant stream.
hdcp_level: Option<HdcpLevel>,
/// The group identifier for the video in the variant stream.
video: Option<String>,
}
@ -36,41 +49,6 @@ impl ExtXIFrameStreamInf {
}
}
/// Returns the URI that identifies the associated media playlist.
pub const fn uri(&self) -> &String {
&self.uri
}
/// Returns the peak segment bit rate of the variant stream.
pub const fn bandwidth(&self) -> u64 {
self.bandwidth
}
/// Returns the average segment bit rate of the variant stream.
pub const fn average_bandwidth(&self) -> Option<u64> {
self.average_bandwidth
}
/// Returns a string that represents the list of codec types contained the variant stream.
pub fn codecs(&self) -> Option<&String> {
self.codecs.as_ref()
}
/// Returns the optimal pixel resolution at which to display all the video in the variant stream.
pub const fn resolution(&self) -> Option<DecimalResolution> {
self.resolution
}
/// Returns the HDCP level of the variant stream.
pub const fn hdcp_level(&self) -> Option<HdcpLevel> {
self.hdcp_level
}
/// Returns the group identifier for the video in the variant stream.
pub fn video(&self) -> Option<&String> {
self.video.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub const fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
@ -105,8 +83,8 @@ impl fmt::Display for ExtXIFrameStreamInf {
impl FromStr for ExtXIFrameStreamInf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
fn from_str(input: &str) -> Result<Self, Self::Err> {
let input = tag(input, Self::PREFIX)?;
let mut uri = None;
let mut bandwidth = None;
@ -115,7 +93,8 @@ impl FromStr for ExtXIFrameStreamInf {
let mut resolution = None;
let mut hdcp_level = None;
let mut video = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
let attrs = AttributePairs::parse(input);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
@ -167,7 +146,7 @@ mod test {
);
assert_eq!(i_frame_stream_inf.uri(), "foo");
assert_eq!(i_frame_stream_inf.bandwidth(), 1000);
assert_eq!(*i_frame_stream_inf.bandwidth(), 1000);
// TODO: test all the optional fields
}

View file

@ -1,7 +1,7 @@
use crate::attribute::AttributePairs;
use crate::types::{InStreamId, MediaType, ProtocolVersion};
use crate::utils::{parse_yes_or_no, quote, unquote};
use crate::{Error, ErrorKind, Result};
use crate::utils::{parse_yes_or_no, quote, tag, unquote};
use crate::{Error, ErrorKind};
use std::fmt;
use std::str::FromStr;
@ -114,7 +114,7 @@ impl ExtXMediaBuilder {
}
/// Builds a `ExtXMedia` instance.
pub fn finish(self) -> Result<ExtXMedia> {
pub fn finish(self) -> crate::Result<ExtXMedia> {
let media_type = track_assert_some!(self.media_type, ErrorKind::InvalidInput);
let group_id = track_assert_some!(self.group_id, ErrorKind::InvalidInput);
let name = track_assert_some!(self.name, ErrorKind::InvalidInput);
@ -309,11 +309,11 @@ impl fmt::Display for ExtXMedia {
impl FromStr for ExtXMedia {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
fn from_str(input: &str) -> Result<Self, Self::Err> {
let input = tag(input, Self::PREFIX)?;
let mut builder = ExtXMediaBuilder::new();
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
let attrs = AttributePairs::parse(input);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {

View file

@ -1,17 +1,26 @@
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, SessionData};
use crate::utils::{quote, unquote};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use getset::{Getters, MutGetters, Setters};
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, SessionData};
use crate::utils::{quote, tag, unquote};
use crate::{Error, ErrorKind};
/// [4.3.4.4. EXT-X-SESSION-DATA]
///
/// [4.3.4.4. EXT-X-SESSION-DATA]: https://tools.ietf.org/html/rfc8216#section-4.3.4.4
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Getters, MutGetters, Setters, Debug, Clone, PartialEq, Eq, Hash)]
#[get = "pub"]
#[set = "pub"]
#[get_mut = "pub"]
pub struct ExtXSessionData {
/// The identifier of the data.
data_id: String,
/// The session data.
data: SessionData,
/// The language of the data.
language: Option<String>,
}
@ -36,21 +45,6 @@ impl ExtXSessionData {
}
}
/// Returns the identifier of the data.
pub const fn data_id(&self) -> &String {
&self.data_id
}
/// Returns the session data.
pub const fn data(&self) -> &SessionData {
&self.data
}
/// Returns the language of the data.
pub fn language(&self) -> Option<&String> {
self.language.as_ref()
}
/// Returns the protocol compatibility version that this tag requires.
pub const fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V1
@ -75,14 +69,15 @@ impl fmt::Display for ExtXSessionData {
impl FromStr for ExtXSessionData {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
fn from_str(input: &str) -> Result<Self, Self::Err> {
let input = tag(input, Self::PREFIX)?;
let mut data_id = None;
let mut session_value = None;
let mut uri = None;
let mut language = None;
let attrs = AttributePairs::parse(s.split_at(Self::PREFIX.len()).1);
let attrs = AttributePairs::parse(input);
for attr in attrs {
let (key, value) = track!(attr)?;
match key {
@ -119,23 +114,38 @@ mod test {
use super::*;
#[test]
fn ext_x_session_data() {
fn test_display() {
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz");
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#;
assert_eq!(text.parse().ok(), Some(tag.clone()));
assert_eq!(tag.to_string(), text);
}
#[test]
fn test_parser() {
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar""#;
assert_eq!(text.parse::<ExtXSessionData>().unwrap(), tag);
let tag = ExtXSessionData::new("foo", SessionData::Uri("bar".into()));
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",URI="bar""#;
assert_eq!(text.parse::<ExtXSessionData>().unwrap(), tag);
let tag = ExtXSessionData::with_language("foo", SessionData::Value("bar".into()), "baz");
let text = r#"#EXT-X-SESSION-DATA:DATA-ID="foo",VALUE="bar",LANGUAGE="baz""#;
assert_eq!(text.parse::<ExtXSessionData>().unwrap(), tag);
}
#[test]
fn test_requires_version() {
let tag = ExtXSessionData::new("foo", SessionData::Value("bar".into()));
assert_eq!(tag.requires_version(), ProtocolVersion::V1);
}
}

View file

@ -1,8 +1,9 @@
use crate::types::{DecryptionKey, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use crate::types::{DecryptionKey, ProtocolVersion};
use crate::utils::tag;
/// [4.3.4.5. EXT-X-SESSION-KEY]
///
/// [4.3.4.5. EXT-X-SESSION-KEY]: https://tools.ietf.org/html/rfc8216#section-4.3.4.5
@ -37,13 +38,11 @@ impl fmt::Display for ExtXSessionKey {
}
impl FromStr for ExtXSessionKey {
type Err = Error;
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let suffix = s.split_at(Self::PREFIX.len()).1;
let key = track!(suffix.parse())?;
Ok(ExtXSessionKey { key })
fn from_str(input: &str) -> Result<Self, Self::Err> {
let key = tag(input, Self::PREFIX)?.parse()?;
Ok(Self::new(key))
}
}

View file

@ -1,12 +1,13 @@
use std::fmt;
use std::str::FromStr;
use crate::attribute::AttributePairs;
use crate::types::{
ClosedCaptions, DecimalFloatingPoint, DecimalResolution, HdcpLevel, ProtocolVersion,
SingleLineString,
};
use crate::utils::{parse_u64, quote, unquote};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use crate::utils::{parse_u64, quote, tag, unquote};
use crate::{Error, ErrorKind};
/// [4.3.4.2. EXT-X-STREAM-INF]
///
@ -146,16 +147,15 @@ impl fmt::Display for ExtXStreamInf {
impl FromStr for ExtXStreamInf {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let mut lines = s.splitn(2, '\n');
let first_line = lines.next().expect("Never fails").trim_end_matches('\r');
let second_line = track_assert_some!(lines.next(), ErrorKind::InvalidInput);
fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut lines = input.lines();
let first_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO!
let second_line = lines.next().ok_or(ErrorKind::InvalidInput)?; // TODO!
tag(first_line, Self::PREFIX)?;
track_assert!(
first_line.starts_with(Self::PREFIX),
ErrorKind::InvalidInput
);
let uri = track!(SingleLineString::new(second_line))?;
let mut bandwidth = None;
let mut average_bandwidth = None;
let mut codecs = None;
@ -186,6 +186,7 @@ impl FromStr for ExtXStreamInf {
}
}
}
let bandwidth = track_assert_some!(bandwidth, ErrorKind::InvalidInput);
Ok(ExtXStreamInf {
uri,

View file

@ -1,8 +1,8 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
use crate::types::ProtocolVersion;
use crate::utils::tag;
/// [4.3.3.3. EXT-X-DISCONTINUITY-SEQUENCE]
///
@ -39,12 +39,11 @@ impl fmt::Display for ExtXDiscontinuitySequence {
}
impl FromStr for ExtXDiscontinuitySequence {
type Err = Error;
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self> {
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let seq_num = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXDiscontinuitySequence { seq_num })
fn from_str(input: &str) -> Result<Self, Self::Err> {
let seq_num = tag(input, Self::PREFIX)?.parse().unwrap(); // TODO!
Ok(Self::new(seq_num))
}
}

View file

@ -1,39 +1,72 @@
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
use crate::types::{ByteRange, ProtocolVersion};
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use trackable::error::ErrorKindExt;
/// [4.3.2.2. EXT-X-BYTERANGE]
///
/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXByteRange {
range: ByteRange,
}
pub struct ExtXByteRange(ByteRange);
impl ExtXByteRange {
pub(crate) const PREFIX: &'static str = "#EXT-X-BYTERANGE:";
/// Makes a new `ExtXByteRange` tag.
pub const fn new(range: ByteRange) -> Self {
ExtXByteRange { range }
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXByteRange;
///
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// ```
pub const fn new(length: usize, start: Option<usize>) -> Self {
Self(ByteRange::new(length, start))
}
/// Returns the range of the associated media segment.
pub const fn range(&self) -> ByteRange {
self.range
/// Converts the [ExtXByteRange] to a [ByteRange].
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXByteRange;
/// use hls_m3u8::types::ByteRange;
///
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// let range: ByteRange = byte_range.to_range();
/// ```
pub const fn to_range(&self) -> ByteRange {
self.0
}
/// Returns the protocol compatibility version that this tag requires.
/// # Example
/// ```
/// use hls_m3u8::tags::ExtXByteRange;
/// use hls_m3u8::types::ProtocolVersion;
///
/// let byte_range = ExtXByteRange::new(20, Some(5));
/// assert_eq!(byte_range.requires_version(), ProtocolVersion::V4);
/// ```
pub const fn requires_version(&self) -> ProtocolVersion {
ProtocolVersion::V4
}
}
impl Deref for ExtXByteRange {
type Target = ByteRange;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for ExtXByteRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", Self::PREFIX, self.range)
write!(f, "{}", Self::PREFIX)?;
write!(f, "{}", self.0)?;
Ok(())
}
}
@ -41,9 +74,30 @@ impl FromStr for ExtXByteRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
// check if the string starts with the PREFIX
track_assert!(s.starts_with(Self::PREFIX), ErrorKind::InvalidInput);
let range = may_invalid!(s.split_at(Self::PREFIX.len()).1.parse())?;
Ok(ExtXByteRange { range })
let byte_range = s.split_at(Self::PREFIX.len()).1;
let tokens = byte_range.splitn(2, '@').collect::<Vec<_>>();
if tokens.is_empty() {
Err(ErrorKind::InvalidInput)?;
}
let length = tokens[0]
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e))?;
let start = {
let mut result = None;
if tokens.len() == 2 {
result = Some(
tokens[1]
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e))?,
);
}
result
};
Ok(ExtXByteRange::new(length, start))
}
}
@ -52,21 +106,40 @@ mod test {
use super::*;
#[test]
fn ext_x_byterange() {
let tag = ExtXByteRange::new(ByteRange {
length: 3,
start: None,
});
assert_eq!("#EXT-X-BYTERANGE:3".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3");
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
fn test_display() {
let byte_range = ExtXByteRange::new(0, Some(5));
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:0@5".to_string());
let tag = ExtXByteRange::new(ByteRange {
length: 3,
start: Some(5),
});
assert_eq!("#EXT-X-BYTERANGE:3@5".parse().ok(), Some(tag));
assert_eq!(tag.to_string(), "#EXT-X-BYTERANGE:3@5");
assert_eq!(tag.requires_version(), ProtocolVersion::V4);
let byte_range = ExtXByteRange::new(99999, Some(2));
assert_eq!(
byte_range.to_string(),
"#EXT-X-BYTERANGE:99999@2".to_string()
);
let byte_range = ExtXByteRange::new(99999, None);
assert_eq!(byte_range.to_string(), "#EXT-X-BYTERANGE:99999".to_string());
}
#[test]
fn test_parse() {
let byte_range = ExtXByteRange::new(99999, Some(2));
assert_eq!(
byte_range,
"#EXT-X-BYTERANGE:99999@2".parse::<ExtXByteRange>().unwrap()
);
let byte_range = ExtXByteRange::new(99999, None);
assert_eq!(
byte_range,
"#EXT-X-BYTERANGE:99999".parse::<ExtXByteRange>().unwrap()
);
}
#[test]
fn test_deref() {
let byte_range = ExtXByteRange::new(0, Some(22));
assert_eq!(*byte_range.length(), 0);
assert_eq!(*byte_range.start(), Some(22));
}
}

View file

@ -1,13 +1,16 @@
use crate::types::ProtocolVersion;
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::FromStr;
use crate::types::ProtocolVersion;
use crate::utils::tag;
use crate::{Error, Result};
/// [4.3.2.3. EXT-X-DISCONTINUITY]
///
/// [4.3.2.3. EXT-X-DISCONTINUITY]: https://tools.ietf.org/html/rfc8216#section-4.3.2.3
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtXDiscontinuity;
impl ExtXDiscontinuity {
pub(crate) const PREFIX: &'static str = "#EXT-X-DISCONTINUITY";
@ -25,8 +28,9 @@ impl fmt::Display for ExtXDiscontinuity {
impl FromStr for ExtXDiscontinuity {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
track_assert_eq!(s, Self::PREFIX, ErrorKind::InvalidInput);
fn from_str(input: &str) -> Result<Self> {
tag(input, Self::PREFIX)?;
Ok(ExtXDiscontinuity)
}
}

View file

@ -100,13 +100,7 @@ mod test {
assert_eq!(tag.to_string(), text);
assert_eq!(tag.requires_version(), ProtocolVersion::V6);
let tag = ExtXMap::with_range(
"foo",
ByteRange {
length: 9,
start: Some(2),
},
);
let tag = ExtXMap::with_range("foo", ByteRange::new(9, Some(2)));
let text = r#"#EXT-X-MAP:URI="foo",BYTERANGE="9@2""#;
track_try_unwrap!(ExtXMap::from_str(text));
assert_eq!(text.parse().ok(), Some(tag.clone()));

View file

@ -1,18 +1,32 @@
use crate::{Error, ErrorKind, Result};
use std::fmt;
use std::str::{self, FromStr};
use getset::{Getters, MutGetters, Setters};
use trackable::error::ErrorKindExt;
use crate::{Error, ErrorKind, Result};
/// Byte range.
///
/// See: [4.3.2.2. EXT-X-BYTERANGE]
///
/// [4.3.2.2. EXT-X-BYTERANGE]: https://tools.ietf.org/html/rfc8216#section-4.3.2.2
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Getters, Setters, MutGetters, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[get = "pub"]
#[set = "pub"]
#[get_mut = "pub"]
pub struct ByteRange {
pub length: usize,
pub start: Option<usize>,
/// The length of the range.
length: usize,
/// The start of the range.
start: Option<usize>,
}
impl ByteRange {
/// Creates a new [ByteRange].
pub const fn new(length: usize, start: Option<usize>) -> Self {
Self { length, start }
}
}
impl fmt::Display for ByteRange {
@ -27,20 +41,28 @@ impl fmt::Display for ByteRange {
impl FromStr for ByteRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let mut tokens = s.splitn(2, '@');
let length = tokens.next().expect("Never fails");
let start = if let Some(start) = tokens.next() {
Some(track!(start
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e)))?)
} else {
None
let tokens = s.splitn(2, '@').collect::<Vec<_>>();
if tokens.is_empty() {
Err(ErrorKind::InvalidInput)?;
}
let length = tokens[0]
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e))?;
let start = {
let mut result = None;
if tokens.len() == 2 {
result = Some(
tokens[1]
.parse()
.map_err(|e| ErrorKind::InvalidInput.cause(e))?,
);
}
result
};
Ok(ByteRange {
length: track!(length.parse().map_err(|e| ErrorKind::InvalidInput.cause(e)))?,
start,
})
Ok(ByteRange::new(length, start))
}
}

View file

@ -44,6 +44,19 @@ pub(crate) fn quote<T: ToString>(value: T) -> String {
format!("\"{}\"", value.to_string().replace("\"", ""))
}
/// Checks, if the given tag is at the start of the input. If this is the case, it will remove it
/// return the rest of the input, otherwise it will return an error.
pub(crate) fn tag<T>(input: &str, tag: T) -> crate::Result<&str>
where
T: AsRef<str>,
{
if !input.starts_with(tag.as_ref()) {
Err(ErrorKind::InvalidInput)?; // TODO!
}
let result = input.split_at(tag.as_ref().len()).1;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
@ -74,4 +87,21 @@ mod tests {
assert_eq!(quote("value"), "\"value\"".to_string());
assert_eq!(quote("\"value\""), "\"value\"".to_string());
}
#[test]
fn test_tag() {
let input = "HelloMyFriendThisIsASampleString";
let input = tag(input, "Hello").unwrap();
assert_eq!(input, "MyFriendThisIsASampleString");
let input = tag(input, "My").unwrap();
assert_eq!(input, "FriendThisIsASampleString");
let input = tag(input, "FriendThisIs").unwrap();
assert_eq!(input, "ASampleString");
let input = tag(input, "A").unwrap();
assert_eq!(input, "SampleString");
}
}