diff --git a/Cargo.toml b/Cargo.toml
index 25f07e8..9cf386d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,3 +22,19 @@ jid = { version = "*" }
[dev-dependencies]
syntect = "5.0"
diff = "0.1"
+scansion = { version = "*" }
+
+[build-dependencies]
+jid = { version = "*", features = ["minidom"] }
+minidom = { version = "*" }
+quote = "1.0"
+rand = "0.8"
+scansion = { version = "*" }
+
+[patch.crates-io]
+sasl = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
+jid = { git = "https://code.bouah.net/pep/xmpp-rs.git", branch = "jid-quote" }
+minidom = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
+xmpp-parsers = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
+tokio-xmpp = { git = "https://code.bouah.net/pep/xmpp-rs.git" }
+scansion = { git = "https://code.bouah.net/pep/scansion-rs", branch = "quote" }
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..f07018a
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,201 @@
+// Copyright (C) 2023-2099 The crate authors.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published by the
+// Free Software Foundation, either version 3 of the License, or (at your
+// option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
+// for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+use std::ffi::OsString;
+use std::fmt::Display;
+use std::fs::File;
+use std::io::{self, BufWriter, Read, Write};
+use std::path::{Path, PathBuf};
+
+use minidom::Error as MinidomError;
+use quote::{format_ident, quote, TokenStreamExt};
+use scansion::{read_actions_component, read_spec, Spec};
+
+fn generate_spec_tokens(spec: Spec) -> Result {
+ println!("FOO0");
+
+ let docstr = {
+ let mut tmp = String::new();
+ if let Some(ref meta) = spec.metadata {
+ tmp.push_str(meta.title.as_str());
+ tmp.push('\n');
+
+ if let Some(ref desc) = meta.description {
+ tmp.push_str(desc.as_str());
+ tmp.push('\n');
+ }
+
+ for tag in &meta.tags {
+ tmp.push_str("tag: ");
+ tmp.push_str(tag.as_str());
+ tmp.push('\n');
+ }
+ } else {
+ tmp.push_str("No metadata");
+ }
+
+ tmp
+ };
+
+ let context = spec.context.clone();
+ let actions = read_actions_component(spec, &context).unwrap();
+
+ let stanzas_in = actions
+ .inbound
+ .into_iter()
+ .map(|elem| {
+ // TODO: Prevent having to parse elements again.
+ // We do need some kind of structure already though to be able to verify the presence
+ // of attrs and all before the generation.
+ let elem_str = String::from(&elem);
+ quote! { ScanElement::new(#elem_str.parse::().unwrap()).apply_context(&context) }
+ })
+ .collect::>();
+
+ let stanzas_out = actions
+ .outbound
+ .into_iter()
+ .map(|elem| {
+ let elem_str = String::from(&elem);
+ quote! {
+ component.expect(
+ #elem_str.parse::().unwrap().apply_context(&context)
+ );
+ }
+ })
+ .collect::>();
+
+ println!("FOO1");
+
+ let (context_keys, context_values) = context
+ .into_iter()
+ .fold((vec![], vec![]), |(mut keys, mut vals), (k, v)| {
+ println!("BAR0");
+ keys.push(quote! { #k });
+ println!("BAR1");
+ vals.push(quote! { #v });
+ println!("BAR2: {:?}; {:?}", keys, vals);
+ (keys, vals)
+ });
+
+ println!("FOO2");
+
+ Ok(quote! {
+ #![doc = #docstr]
+ use crate::component::TestComponent;
+ use crate::handlers::handle_stanza;
+ use crate::room::Room;
+
+ use ::std::collections::HashMap;
+ use ::xmpp_parsers::{Jid, BareJid, FullJid, Element};
+ use ::scansion::{ScanElement, Entity, Client};
+
+ #[tokio::test]
+ async fn spec() {
+ let context: ::scansion::Context = {
+ let tmp = ::std::collections::HashMap::new();
+ let (keys, values) = (vec![#(#context_keys),*], vec![#(#context_values),*]);
+ for (k, v) in keys.iter().zip(values.iter()) {
+ tmp.insert(String::from(*k), v.clone());
+ }
+ tmp
+ };
+
+ let stanzas_in = vec![#(#stanzas_in),*];
+ let mut component = TestComponent::new_scan(stanzas_in);
+ let mut rooms: HashMap = HashMap::new();
+
+ #(#stanzas_out)*
+
+ println!("FOO: {component:?}");
+ handle_stanza(&mut component, &mut rooms).await.unwrap();
+ }
+ })
+}
+
+fn generate_mod_tokens(modfiles: Vec) -> impl TokenStreamExt + Display {
+ let modfiles: Vec<_> = modfiles
+ .into_iter()
+ .map(|s| format_ident!("{}", s.into_string().unwrap().strip_suffix(".rs").unwrap()))
+ .collect();
+ quote! {
+ /// Scansion tests module.
+ /// These tests are generated by the build script, DO NOT EDIT.
+
+ #(#[cfg(test)] mod #modfiles;)*
+ }
+}
+
+fn read_input_dir(indir: PathBuf, outdir: PathBuf) -> io::Result> {
+ // Will be used to generate mod.rs
+ let mut modfiles: Vec = Vec::new();
+
+ for entry in indir.read_dir()? {
+ let mut p = entry?.path();
+ match p.extension() {
+ Some(ext) if ext == "scs" => (),
+ _ => continue,
+ }
+
+ let mut infile = File::open(p.clone())?;
+ let mut contents = String::new();
+ infile.read_to_string(&mut contents)?;
+
+ let spec = read_spec(&contents);
+ match spec {
+ Ok(_) => println!("Path: {p:?}: \x1b[32m OK\x1b[0m"),
+ Err(err) => {
+ println!("Path: {p:?}: \x1b[31mERR\x1b[0m\n{err:?}");
+ continue;
+ }
+ }
+
+ // Path is now only going to be used as .rs
+ p.set_extension("rs");
+
+ let filename = OsString::from(p.file_name().unwrap());
+ let outpath = outdir.join(filename.clone());
+ println!("Outpath: {outpath:?}");
+
+ let tokens = generate_spec_tokens(spec.unwrap()).unwrap();
+ let mut output = BufWriter::new(File::create(&outpath)?);
+ write!(output, "{}", tokens)?;
+
+ // Add to the set of files for which generation succeeded to then generate mod.rs
+ modfiles.push(filename);
+ }
+
+ Ok(modfiles)
+}
+
+fn main() -> io::Result<()> {
+ let indir = Path::new("./specs");
+ let outdir = Path::new("./src/tests/scansion");
+
+ if !indir.is_dir() || !outdir.is_dir() {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Input and output paths must be directories.",
+ ));
+ }
+
+ let modfiles = read_input_dir(indir.to_path_buf(), outdir.to_path_buf())?;
+
+ let mut modout = BufWriter::new(File::create(outdir.join("mod.rs"))?);
+ let tokens = generate_mod_tokens(modfiles);
+ write!(modout, "{}", tokens)?;
+
+ Ok(())
+}
diff --git a/src/component/test.rs b/src/component/test.rs
index c6184cb..8e32ebc 100644
--- a/src/component/test.rs
+++ b/src/component/test.rs
@@ -28,6 +28,7 @@ use async_trait::async_trait;
use diff;
use futures::{task::Poll, Stream};
use log::debug;
+use scansion::ScanElement;
use syntect::{
easy::HighlightLines,
highlighting::{Style, ThemeSet},
@@ -59,17 +60,23 @@ impl fmt::Debug for Expect {
}
}
-#[derive(Clone, Eq, PartialEq)]
-pub struct TestElement(pub Element);
+#[derive(Clone)]
+pub struct TestElement(pub ScanElement);
impl Deref for TestElement {
- type Target = Element;
+ type Target = ScanElement;
fn deref(&self) -> &Self::Target {
&self.0
}
}
+impl PartialEq for TestElement {
+ fn eq(&self, other: &Element) -> bool {
+ self.0 == *other
+ }
+}
+
impl fmt::Debug for TestElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from(&self.0))
@@ -90,31 +97,43 @@ impl From<&TestElement> for String {
impl From for TestElement {
fn from(elem: Element) -> Self {
- Self(elem)
+ Self(ScanElement::new(elem))
}
}
impl From for Element {
fn from(elem: TestElement) -> Self {
- elem.0
+ elem.0.elem
}
}
impl From for TestElement {
fn from(elem: Iq) -> Self {
- Self(Element::from(elem))
+ Self(Element::from(elem).into())
}
}
impl From for TestElement {
fn from(elem: Presence) -> Self {
- Self(Element::from(elem))
+ Self(Element::from(elem).into())
}
}
impl From for TestElement {
fn from(elem: Message) -> Self {
- Self(Element::from(elem))
+ Self(Element::from(elem).into())
+ }
+}
+
+impl From for TestElement {
+ fn from(scan: ScanElement) -> Self {
+ Self(scan)
+ }
+}
+
+impl From for ScanElement {
+ fn from(elem: TestElement) -> ScanElement {
+ ScanElement::new(elem.into())
}
}
@@ -132,7 +151,21 @@ impl Component {
in_buffer: VecDeque::from(
in_buffer
.into_iter()
- .map(|el| TestElement(el))
+ .map(|elem| TestElement::from(elem))
+ .collect::>(),
+ ),
+ expect_buffer: HashMap::new(),
+ }
+ }
+
+ pub fn new_scan(in_buffer: Vec) -> Self {
+ let _ = env_logger::builder().is_test(true).try_init();
+
+ Component {
+ in_buffer: VecDeque::from(
+ in_buffer
+ .into_iter()
+ .map(|elem| TestElement::from(elem))
.collect::>(),
),
expect_buffer: HashMap::new(),
@@ -358,7 +391,7 @@ impl Stream for Component {
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll