From a31b060dd3ccd287095a62494af87cbbed354086 Mon Sep 17 00:00:00 2001 From: Adrian Malacoda Date: Wed, 8 Feb 2017 03:25:03 -0600 Subject: [PATCH] Beginning of rust implementation for tenquestionmarks. --- Cargo.toml | 9 ++++++ README.md | 5 +++ src/lib.rs | 43 ++++++++++++++++++++++++++ src/main.rs | 41 +++++++++++++++++++++++++ src/plugins/discord.rs | 21 +++++++++++++ src/plugins/hello.rs | 23 ++++++++++++++ src/plugins/loader.rs | 70 ++++++++++++++++++++++++++++++++++++++++++ src/plugins/lua.rs | 16 ++++++++++ src/plugins/mod.rs | 14 +++++++++ tenquestionmarks.toml | 12 ++++++++ 10 files changed, 254 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/plugins/discord.rs create mode 100644 src/plugins/hello.rs create mode 100644 src/plugins/loader.rs create mode 100644 src/plugins/lua.rs create mode 100644 src/plugins/mod.rs create mode 100644 tenquestionmarks.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1cdbf4d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name="tenquestionmarks" +version="0.0.1" +authors=["Adrian Malacoda "] + +[dependencies] +hlua = "0.3" +discord = "0.7.0" +toml = "0.2.1" diff --git a/README.md b/README.md index e69de29..a07af43 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,5 @@ +# tenquestionmarks chat bot +tenquestionmarks is an extensible, scriptable chat bot. This iteration is written in rust. + +## Architecture +tenquestionmarks has two basic diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8116cb3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,43 @@ +extern crate toml; + +use std::collections::BTreeMap; +use toml::Table; + +mod plugins; +use plugins::Plugin; +use plugins::loader::PluginLoader; +use plugins::loader::PluginLoaderError; + +pub struct Tenquestionmarks { + plugins: BTreeMap> +} + +impl Tenquestionmarks { + pub fn with_plugins (plugins: BTreeMap>) -> Tenquestionmarks { + Tenquestionmarks { + plugins: plugins + } + } + + pub fn from_configuration (configuration: Table) -> Result { + let loader = PluginLoader::new(); + let plugins = loader.load_from_configuration(configuration)?; + Result::Ok(Tenquestionmarks::with_plugins(plugins)) + } +} + +pub struct Channel { + name: String, + description: String, + topic: String +} + +pub struct User { + name: String +} + +pub struct Message { + sender: String, + target: String, + content: String +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..04585c9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,41 @@ +use std::env; +use std::fs::File; +use std::io::Read; + +extern crate tenquestionmarks; +use tenquestionmarks::Tenquestionmarks; + +extern crate toml; +use toml::Parser; + +fn main () { + let configFileName = env::args().nth(1).unwrap_or("tenquestionmarks.toml".into()); + match File::open(&configFileName) { + Ok(mut file) => { + let mut contents = String::new(); + match file.read_to_string(&mut contents) { + Ok(value) => { + let mut parser = Parser::new(&contents); + match parser.parse() { + Some(configuration) => { + println!("Loaded configuration from: {}", configFileName); + match Tenquestionmarks::from_configuration(configuration) { + Ok(tqm) => println!("loaded tenquestionmarks"), + Err(e) => println!("Failed to initialize tenquestionmarks: {:?}", e) + } + }, + None => { + println!("Failed to parse config file {}: {:?}. Config file must be a valid TOML file.", configFileName, parser.errors); + } + } + }, + Err(e) => { + println!("Failed to open config file {}: {:?}", configFileName, e); + } + } + }, + Err(e) => { + println!("Failed to open config file! Please specify path to a config file."); + } + } +} diff --git a/src/plugins/discord.rs b/src/plugins/discord.rs new file mode 100644 index 0000000..456f6d4 --- /dev/null +++ b/src/plugins/discord.rs @@ -0,0 +1,21 @@ +use plugins::Plugin; +use toml::Table; + +pub struct DiscordPlugin { + +} + +impl DiscordPlugin { + pub fn new (configuration: &Table) -> Box { + match configuration.get("channel") { + Some(channel) => println!("Using channel: {}", channel), + None => println!("No channel specified") + } + + Box::new(DiscordPlugin {}) + } +} + +impl Plugin for DiscordPlugin { + +} diff --git a/src/plugins/hello.rs b/src/plugins/hello.rs new file mode 100644 index 0000000..c676948 --- /dev/null +++ b/src/plugins/hello.rs @@ -0,0 +1,23 @@ +use plugins::Plugin; +use toml::Table; + +pub struct Hello { + name: String +} + +impl Hello { + pub fn new (configuration: &Table) -> Box { + let name = configuration.get("name") + .and_then(|value| value.as_str()) + .unwrap_or("world"); + + println!("Hello, {}!", name); + Box::new(Hello { + name: String::from(name) + }) + } +} + +impl Plugin for Hello { + +} diff --git a/src/plugins/loader.rs b/src/plugins/loader.rs new file mode 100644 index 0000000..5af2115 --- /dev/null +++ b/src/plugins/loader.rs @@ -0,0 +1,70 @@ +use std::collections::BTreeMap; +use std::error::Error; +use std::fmt; + +use toml::Table; + +use plugins::Plugin; +use plugins::hello::Hello; +use plugins::discord::DiscordPlugin; +use plugins::lua::LuaPlugin; + +pub struct PluginLoader { + types: BTreeMap<&'static str, fn(&Table) -> Box> +} + +impl PluginLoader { + pub fn new () -> PluginLoader { + let mut types = BTreeMap::new(); + types.insert("hello", Hello::new as fn(&Table) -> Box); + types.insert("discord", DiscordPlugin::new as fn(&Table) -> Box); + types.insert("lua", LuaPlugin::new as fn(&Table) -> Box); + PluginLoader { + types: types + } + } + + pub fn load_from_configuration (&self, configuration: Table) -> Result>, PluginLoaderError> { + configuration.into_iter().map(|(key, value)| { + match value.as_table() { + Some(table) => { + let plugin = self.load_single_plugin(&key, table)?; + Result::Ok((key, plugin)) + }, + None => Result::Err(PluginLoaderError { message: format!("Bad configuration parameters for plugin instance: {}. Configuration for a plugin must be a table.", key) }) + } + }).collect() + } + + pub fn load_single_plugin (&self, name: &str, configuration: &Table) -> Result, PluginLoaderError> { + /* + * The plugin type defaults to the instance name (in the tenquestionmarks configuration) + * but can explicitly be set by using the special "type" parameter. + */ + let plugin_type: &str = configuration.get("type") + .and_then(|value| value.as_str()) + .unwrap_or(name); + + match self.types.get(plugin_type) { + Some(constructor) => Result::Ok(constructor(configuration)), + None => Result::Err(PluginLoaderError { message: format!("No such plugin type: {}", plugin_type) }) + } + } +} + +#[derive(Debug)] +pub struct PluginLoaderError { + message: String +} + +impl Error for PluginLoaderError { + fn description(&self) -> &str { + &self.message[..] + } +} + +impl fmt::Display for PluginLoaderError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PluginLoaderError: {}", self.message) + } +} diff --git a/src/plugins/lua.rs b/src/plugins/lua.rs new file mode 100644 index 0000000..2c196ff --- /dev/null +++ b/src/plugins/lua.rs @@ -0,0 +1,16 @@ +use plugins::Plugin; +use toml::Table; + +pub struct LuaPlugin { + +} + +impl LuaPlugin { + pub fn new (configuration: &Table) -> Box { + Box::new(LuaPlugin {}) + } +} + +impl Plugin for LuaPlugin { + +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs new file mode 100644 index 0000000..c5e9e68 --- /dev/null +++ b/src/plugins/mod.rs @@ -0,0 +1,14 @@ +use std::collections::HashMap; + +use toml::Value; +use toml::Table; + +pub mod hello; +pub mod lua; +pub mod discord; + +pub mod loader; + +pub trait Plugin { + +} diff --git a/tenquestionmarks.toml b/tenquestionmarks.toml new file mode 100644 index 0000000..027fbec --- /dev/null +++ b/tenquestionmarks.toml @@ -0,0 +1,12 @@ +[hello] + +[hello-dave] +type = "hello" +name = "Dave" + +[hello-fred] +type = "hello" +name = "Fred" + +[discord] +channel = "#testchannelpleaseignore"