Beginning of rust implementation for tenquestionmarks.

This commit is contained in:
Adrian Malacoda
2017-02-08 03:25:03 -06:00
parent a3bed71cdd
commit a31b060dd3
10 changed files with 254 additions and 0 deletions

9
Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name="tenquestionmarks"
version="0.0.1"
authors=["Adrian Malacoda <adrian.malacoda@monarch-pass.net>"]
[dependencies]
hlua = "0.3"
discord = "0.7.0"
toml = "0.2.1"

View File

@@ -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

43
src/lib.rs Normal file
View File

@@ -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<String, Box<Plugin>>
}
impl Tenquestionmarks {
pub fn with_plugins (plugins: BTreeMap<String, Box<Plugin>>) -> Tenquestionmarks {
Tenquestionmarks {
plugins: plugins
}
}
pub fn from_configuration (configuration: Table) -> Result<Tenquestionmarks, PluginLoaderError> {
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
}

41
src/main.rs Normal file
View File

@@ -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.");
}
}
}

21
src/plugins/discord.rs Normal file
View File

@@ -0,0 +1,21 @@
use plugins::Plugin;
use toml::Table;
pub struct DiscordPlugin {
}
impl DiscordPlugin {
pub fn new (configuration: &Table) -> Box<Plugin> {
match configuration.get("channel") {
Some(channel) => println!("Using channel: {}", channel),
None => println!("No channel specified")
}
Box::new(DiscordPlugin {})
}
}
impl Plugin for DiscordPlugin {
}

23
src/plugins/hello.rs Normal file
View File

@@ -0,0 +1,23 @@
use plugins::Plugin;
use toml::Table;
pub struct Hello {
name: String
}
impl Hello {
pub fn new (configuration: &Table) -> Box<Plugin> {
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 {
}

70
src/plugins/loader.rs Normal file
View File

@@ -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<Plugin>>
}
impl PluginLoader {
pub fn new () -> PluginLoader {
let mut types = BTreeMap::new();
types.insert("hello", Hello::new as fn(&Table) -> Box<Plugin>);
types.insert("discord", DiscordPlugin::new as fn(&Table) -> Box<Plugin>);
types.insert("lua", LuaPlugin::new as fn(&Table) -> Box<Plugin>);
PluginLoader {
types: types
}
}
pub fn load_from_configuration (&self, configuration: Table) -> Result<BTreeMap<String, Box<Plugin>>, 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<Box<Plugin>, 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)
}
}

16
src/plugins/lua.rs Normal file
View File

@@ -0,0 +1,16 @@
use plugins::Plugin;
use toml::Table;
pub struct LuaPlugin {
}
impl LuaPlugin {
pub fn new (configuration: &Table) -> Box<Plugin> {
Box::new(LuaPlugin {})
}
}
impl Plugin for LuaPlugin {
}

14
src/plugins/mod.rs Normal file
View File

@@ -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 {
}

12
tenquestionmarks.toml Normal file
View File

@@ -0,0 +1,12 @@
[hello]
[hello-dave]
type = "hello"
name = "Dave"
[hello-fred]
type = "hello"
name = "Fred"
[discord]
channel = "#testchannelpleaseignore"