documentation and `impl TryFrom<&[u8]> for Line`
This commit is contained in:
parent
0c38b5bb80
commit
147eeff8d1
|
@ -1,7 +1,6 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
use irctokens::Line;
|
use irctokens::Line;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
fn criterion_benchmark(c: &mut Criterion) {
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
let line = Line {
|
let line = Line {
|
||||||
|
|
|
@ -2,7 +2,8 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
use irctokens::Line;
|
use irctokens::Line;
|
||||||
|
|
||||||
fn basic() {
|
fn basic() {
|
||||||
Line::tokenise(b"@tag1=tag1value;tag2=;tag3 :source COMMAND arg1 arg2 :arg3 with space").unwrap();
|
Line::tokenise(b"@tag1=tag1value;tag2=;tag3 :source COMMAND arg1 arg2 :arg3 with space")
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn criterion_benchmark(c: &mut Criterion) {
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
|
|
@ -18,6 +18,12 @@ fn tag_encode(input: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
/// Format `self` in to a byte string by [RFC1459] and [IRCv3] protocol rules.
|
||||||
|
///
|
||||||
|
/// [RFC1459]: https://www.rfc-editor.org/rfc/rfc1459#section-2.3
|
||||||
|
/// [IRCv3]: https://ircv3.net/specs/extensions/message-tags.html
|
||||||
|
#[must_use]
|
||||||
pub fn format(&self) -> Vec<u8> {
|
pub fn format(&self) -> Vec<u8> {
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
|
|
||||||
|
@ -45,9 +51,9 @@ impl Line {
|
||||||
|
|
||||||
output.extend_from_slice(self.command.as_bytes());
|
output.extend_from_slice(self.command.as_bytes());
|
||||||
|
|
||||||
for (i, arg) in self.args.iter().enumerate() {
|
for (i, arg) in self.arguments.iter().enumerate() {
|
||||||
output.push(b' ');
|
output.push(b' ');
|
||||||
if i == self.args.len() - 1 {
|
if i == self.arguments.len() - 1 {
|
||||||
output.push(b':');
|
output.push(b':');
|
||||||
}
|
}
|
||||||
output.extend_from_slice(arg);
|
output.extend_from_slice(arg);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod format;
|
mod format;
|
||||||
mod obj;
|
mod obj;
|
||||||
mod tokenise;
|
pub mod tokenise;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use self::obj::{Error, Line};
|
pub use self::obj::Line;
|
||||||
|
|
27
src/obj.rs
27
src/obj.rs
|
@ -1,19 +1,22 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
/// A struct representing all the constituent pieces of an RFC1459/IRCv3 protocol line.
|
||||||
|
///
|
||||||
|
/// `@tagkey=tagvalue :source COMMAND arg1 arg2 :arg3 with space`
|
||||||
pub struct Line {
|
pub struct Line {
|
||||||
// tags are promised to be utf8 encoded
|
/// [Message tags] of an IRC line.
|
||||||
|
/// [`None`] if no message tags were present.
|
||||||
|
/// keys and values are [`String`] because they are promised to be utf8 encoded.
|
||||||
|
///
|
||||||
|
/// [Message tags]: https://ircv3.net/specs/extensions/message-tags.html
|
||||||
pub tags: Option<BTreeMap<String, Option<String>>>,
|
pub tags: Option<BTreeMap<String, Option<String>>>,
|
||||||
|
/// The `:source` of an IRC line, or [`None`] if source is not present.
|
||||||
|
/// This is a [`Vec<u8>`] as it may be unpredictably encoded.
|
||||||
pub source: Option<Vec<u8>>,
|
pub source: Option<Vec<u8>>,
|
||||||
// commands are promised to be ascii encoded
|
/// The `COMMAND` of an IRC line (e.g. `PRIVMSG`.)
|
||||||
|
/// This is a [`String`] because commands are promised to be ascii encoded.
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub args: Vec<Vec<u8>>,
|
/// The arguments of an IRC line.
|
||||||
}
|
/// These are [`Vec<u8>`]s as they may be unpredictably encoded.
|
||||||
|
pub arguments: Vec<Vec<u8>>,
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
Empty,
|
|
||||||
MissingCommand,
|
|
||||||
CommandDecode,
|
|
||||||
TagKeyDecode,
|
|
||||||
TagValueDecode,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,24 @@
|
||||||
use std::collections::{BTreeMap, VecDeque};
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
|
|
||||||
use super::util::TakeWord as _;
|
use super::util::TakeWord as _;
|
||||||
use super::{Error, Line};
|
use super::Line;
|
||||||
|
|
||||||
const TAG_STOP: [&[u8]; 2] = [b"", b"="];
|
const TAG_STOP: [&[u8]; 2] = [b"", b"="];
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// An empty byte array was passed to the tokeniser
|
||||||
|
Empty,
|
||||||
|
/// A line is invalid if it has no `COMMAND` (e.g. `PRIVMSG`)
|
||||||
|
MissingCommand,
|
||||||
|
/// Commands must be ascii encoded
|
||||||
|
CommandDecode,
|
||||||
|
/// Message tag keys must be utf8 encoded
|
||||||
|
TagKeyDecode,
|
||||||
|
/// Message tag values must be utf8 encoded
|
||||||
|
TagValueDecode,
|
||||||
|
}
|
||||||
|
|
||||||
fn tag_decode(input: &str) -> String {
|
fn tag_decode(input: &str) -> String {
|
||||||
let mut escaped = false;
|
let mut escaped = false;
|
||||||
let mut output = String::with_capacity(input.len());
|
let mut output = String::with_capacity(input.len());
|
||||||
|
@ -21,7 +35,7 @@ fn tag_decode(input: &str) -> String {
|
||||||
};
|
};
|
||||||
|
|
||||||
output.push(replace);
|
output.push(replace);
|
||||||
} else if char == 0x5c as char {
|
} else if char == '\\' {
|
||||||
// backslash
|
// backslash
|
||||||
escaped = true;
|
escaped = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,6 +47,11 @@ fn tag_decode(input: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
/// Attempt to tokenise a byte string by [RFC1459] and [IRCv3] protocol rules.
|
||||||
|
///
|
||||||
|
/// [RFC1459]: https://www.rfc-editor.org/rfc/rfc1459#section-2.3
|
||||||
|
/// [IRCv3]: https://ircv3.net/specs/extensions/message-tags.html
|
||||||
pub fn tokenise(mut line: &[u8]) -> Result<Self, Error> {
|
pub fn tokenise(mut line: &[u8]) -> Result<Self, Error> {
|
||||||
let tags = if line.first() == Some(&b'@') {
|
let tags = if line.first() == Some(&b'@') {
|
||||||
let mut tags = &line.take_word(b' ')[1..];
|
let mut tags = &line.take_word(b' ')[1..];
|
||||||
|
@ -78,7 +97,16 @@ impl Line {
|
||||||
tags,
|
tags,
|
||||||
source,
|
source,
|
||||||
command: String::from_utf8(command).map_err(|_| Error::CommandDecode)?,
|
command: String::from_utf8(command).map_err(|_| Error::CommandDecode)?,
|
||||||
args: args.into(),
|
arguments: args.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for Line {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
/// Utility function for [`Line::tokenise()`]
|
||||||
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
Self::tokenise(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ fn basic() {
|
||||||
])),
|
])),
|
||||||
source: Some(b"source".to_vec()),
|
source: Some(b"source".to_vec()),
|
||||||
command: "COMMAND".to_string(),
|
command: "COMMAND".to_string(),
|
||||||
args: Vec::from([
|
arguments: Vec::from([
|
||||||
b"arg1".to_vec(),
|
b"arg1".to_vec(),
|
||||||
b"arg2".to_vec(),
|
b"arg2".to_vec(),
|
||||||
b"arg3 with space".to_vec(),
|
b"arg3 with space".to_vec(),
|
||||||
|
|
|
@ -9,10 +9,10 @@ fn basic() {
|
||||||
assert_eq!(line.source, Some(b"source".to_vec()));
|
assert_eq!(line.source, Some(b"source".to_vec()));
|
||||||
assert_eq!(&line.command, "COMMAND");
|
assert_eq!(&line.command, "COMMAND");
|
||||||
|
|
||||||
assert_eq!(line.args.len(), 3);
|
assert_eq!(line.arguments.len(), 3);
|
||||||
assert_eq!(line.args[0], b"arg1");
|
assert_eq!(line.arguments[0], b"arg1");
|
||||||
assert_eq!(line.args[1], b"arg2");
|
assert_eq!(line.arguments[1], b"arg2");
|
||||||
assert_eq!(line.args[2], b"arg3 with space");
|
assert_eq!(line.arguments[2], b"arg3 with space");
|
||||||
|
|
||||||
let tags = line.tags.unwrap();
|
let tags = line.tags.unwrap();
|
||||||
assert_eq!(tags.len(), 3);
|
assert_eq!(tags.len(), 3);
|
||||||
|
|
Loading…
Reference in New Issue