diff --git a/Cargo.toml b/Cargo.toml
index f212fe1..4907afb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,4 +20,5 @@ sanitize-filename = "0.3.0"
log = "0.4"
env_logger = "0.9.0"
actix-web-httpauth = "0.6.0"
-lazy_static = "1.4.0"
\ No newline at end of file
+lazy_static = "1.4.0"
+rutie = "0.8.4"
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 28be727..9e743d1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -22,5 +22,10 @@ COPY --from=builder /usr/src/microbin/target/release/microbin /usr/local/bin/mic
# copy /static folder containing the stylesheets
COPY --from=builder /usr/src/microbin/static /usr/local/bin/static
+# Install Ruby (no need if you're disabling all plugins)
+RUN \
+ apt-get update && \
+ apt-get install -y ruby
+
# run the binary
CMD ["microbin"]
\ No newline at end of file
diff --git a/plugins/BasicSyntaxHighlighter.rb b/plugins/BasicSyntaxHighlighter.rb
new file mode 100644
index 0000000..db561b9
--- /dev/null
+++ b/plugins/BasicSyntaxHighlighter.rb
@@ -0,0 +1,78 @@
+module MBP
+ module BasicSyntaxHighlighter
+
+ # Plugin Properties
+
+ def self.get_id()
+ "BasicSyntaxHighlighter"
+ end
+
+ def self.get_name()
+ "Basic Syntax Highlighter Plugin"
+ end
+
+ def self.get_version()
+ "1.0.0"
+ end
+
+ def self.get_author()
+ "Daniel Szabo"
+ end
+
+ def self.get_webpage()
+ "https://github.com/szabodanika/microbin"
+ end
+
+ def self.get_description()
+ "This plugin will simply color keywords and special characters in four different colors based on some very basic RegEx - it is meant to univesally make code pastas more readable but is not a robust syntax highlighter solution."
+ end
+
+ # Plugin Event Hooks
+
+ def self.init()
+ # Ignore event
+ "OK"
+ end
+
+ def self.on_pasta_created(content)
+ # We do not modify stored content
+ return content
+ end
+
+ def self.on_pasta_read(content)
+
+ tokens = {
+
+ "orchid" => [/([0-9])/, /([t|T][r|R][u|U][e|E]|[f|F][a|A][l|L][s|S][e|E])/],
+
+ "palevioletred" => ['(', ')', '{', '}', '[', ']'],
+
+ "royalblue" => [/(\s(for|while|do|select|async|await|mut|break|continue|in|as|switch|let|fn|async|if|else|elseif|new|switch|match|case|default|public|protected|private|return|class|interface|static|final|const|var|int|integer|boolean|float|double|module|def|end|void))(?![a-z])/],
+
+ "mediumorchid" => [/(:|\.|;|=|>|<|\?|!|#|%|@|\^|&|\*|\|)/],
+
+ "mediumseagreen" => [/(\".*\")/, /(\'.*\')/]
+
+ };
+
+ tokens.each { | color, tokens |
+ for token in tokens do
+ if(token.class == String)
+ content.gsub!(token, "$$#{color}$$" + token + "$$/#{color}$$")
+ elsif
+ content.gsub!(token, "$$#{color}$$" + '\1' + "$$/#{color}$$")
+ end
+ end
+ };
+
+ tokens.each { | color, tokens |
+ content.gsub!("$$#{color}$$", "");
+ content.gsub!("$$/#{color}$$", "");
+ };
+
+ return content
+
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/plugins/MBPlugin.rb b/plugins/MBPlugin.rb
new file mode 100644
index 0000000..b463c01
--- /dev/null
+++ b/plugins/MBPlugin.rb
@@ -0,0 +1,78 @@
+require 'rutie'
+
+module MB
+
+ class MBPlugin
+ # Plugin Properties
+
+ def self.get_id()
+ "[Enter Plugin ID]"
+ end
+
+ def self.get_name()
+ "[Eenter Plugin Name]"
+ end
+
+ def self.get_version()
+ "1.0.0"
+ end
+
+ def self.get_author()
+ "[Enter Author name]"
+ end
+
+ def self.get_webpage()
+ "[Enter Web URL]"
+ end
+
+ def self.get_description()
+ "[Enter Description]"
+ end
+
+ # Plugin Event Hooks
+
+ def self.init()
+ raise "Operation not supported";
+ end
+
+ def self.on_pasta_deleted(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ def self.on_pasta_expired(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ def self.on_pasta_created(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ def self.on_pasta_read(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ # Rust Function Calls
+
+ def self.init()
+ raise "Operation not supported";
+ end
+
+ def self.P=on_pasta_deleted(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ def self.on_pasta_expired(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ def self.on_pasta_created(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ def self.on_pasta_read(id, content, created, expiration, file)
+ raise "Operation not supported";
+ end
+
+ end
+
+end
\ No newline at end of file
diff --git a/plugins/helloWorld.rb b/plugins/helloWorld.rb
new file mode 100644
index 0000000..b94cd8b
--- /dev/null
+++ b/plugins/helloWorld.rb
@@ -0,0 +1,42 @@
+module MBP
+ class HelloWorld < MBPlugin
+
+ def self.get_id()
+ "HelloWorld"
+ end
+
+ def self.get_name()
+ "Hello World Plugin"
+ end
+
+ def self.get_version()
+ "1.0.0"
+ end
+
+ def self.get_description()
+ "This is just a demo plugin. It does not do anything."
+ end
+
+ def self.get_author()
+ "Daniel Szabo"
+ end
+
+ def self.get_webpage()
+ "https://github.com/szabodanika/microbin"
+ end
+
+ def self.init()
+ # Ignore event
+ "OK"
+ end
+
+ def self.on_pasta_created(content)
+ return content
+ end
+
+ def self.on_pasta_read(content)
+ return content
+ end
+
+ end
+end
diff --git a/src/animalnumbers.rs b/src/animalnumbers.rs
index a94e702..8af5ac9 100644
--- a/src/animalnumbers.rs
+++ b/src/animalnumbers.rs
@@ -24,7 +24,7 @@ pub fn to_animal_names(mut number: u64) -> String {
number -= digit * ANIMAL_NAMES.len().pow(power) as u64;
if power > 0 {
power -= 1;
- } else if power <= 0 || number == 0 {
+ } else if power == 0 || number == 0 {
break;
}
}
diff --git a/src/main.rs b/src/main.rs
index 6b2c8f4..7053d04 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,6 +29,7 @@ use crate::pasta::Pasta;
mod animalnumbers;
mod dbio;
mod pasta;
+mod plugins;
lazy_static! {
static ref ARGS: Args = Args::parse();
@@ -47,6 +48,12 @@ struct Args {
#[clap(short, long, default_value_t = 1)]
threads: u8,
+ #[clap(short, long)]
+ wide: bool,
+
+ #[clap(short, long, default_value_t = 3)]
+ animals: u8,
+
#[clap(long)]
hide_header: bool,
@@ -103,7 +110,7 @@ struct ErrorTemplate<'a> {
}
#[derive(Template)]
-#[template(path = "pasta.html")]
+#[template(path = "pasta.html", escape = "none")]
struct PastaTemplate<'a> {
pasta: &'a Pasta,
args: &'a Args,
@@ -165,7 +172,8 @@ async fn create(data: web::Data, mut payload: Multipart) -> Result {
while let Some(chunk) = field.try_next().await? {
- new_pasta.content = std::str::from_utf8(&chunk).unwrap().to_string();
+ new_pasta.content =
+ plugins::on_pasta_created(std::str::from_utf8(&chunk).unwrap());
new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) {
String::from("url")
} else {
@@ -223,9 +231,23 @@ async fn getpasta(data: web::Data, id: web::Path) -> HttpRespo
for pasta in pastas.iter() {
if pasta.id == id {
- return HttpResponse::Found()
- .content_type("text/html")
- .body(PastaTemplate { pasta, args: &ARGS }.render().unwrap());
+ let pasta_copy = Pasta {
+ id: pasta.id,
+ content: plugins::on_pasta_read(&pasta.content),
+ file: pasta.file.to_string(),
+ created: pasta.created,
+ pasta_type: pasta.pasta_type.to_string(),
+ expiration: pasta.expiration,
+ };
+
+ return HttpResponse::Found().content_type("text/html").body(
+ PastaTemplate {
+ pasta: &pasta_copy,
+ args: &ARGS,
+ }
+ .render()
+ .unwrap(),
+ );
}
}
diff --git a/src/plugins.rs b/src/plugins.rs
new file mode 100644
index 0000000..03e8950
--- /dev/null
+++ b/src/plugins.rs
@@ -0,0 +1,134 @@
+extern crate rutie;
+
+use lazy_static::lazy_static;
+use log::{error, log};
+use rutie::{AnyException, AnyObject, Object, RString, VM};
+use std::fs::File;
+use std::io::{BufReader, Read};
+use std::{fs, io};
+
+const CACHE_PLUGINS: bool = false;
+
+lazy_static! {
+ static ref PLUGIN_IDENTIFIERS: Vec = init();
+}
+
+fn init() -> Vec {
+ VM::init();
+
+ let plugin_paths = load_plugin_paths();
+
+ let plugin_codes = read_plugins(plugin_paths.clone());
+
+ feed_plugins(plugin_codes);
+
+ let identifiers = get_plugin_identifiers(plugin_paths);
+
+ init_plugins(&identifiers);
+
+ identifiers
+}
+
+pub fn pasta_filter(s: &str) -> bool {
+ true
+}
+
+pub fn on_pasta_read(s: &str) -> String {
+ let mut processed_content: String = String::from(s);
+
+ for PLUGIN_IDENTIFIER in PLUGIN_IDENTIFIERS.iter() {
+ processed_content = eval_for_string(PLUGIN_IDENTIFIER, "on_pasta_read", s);
+ }
+
+ processed_content
+}
+
+pub fn on_pasta_created(s: &str) -> String {
+ let mut processed_content: String = String::from(s);
+
+ for PLUGIN_IDENTIFIER in PLUGIN_IDENTIFIERS.iter() {
+ processed_content = eval_for_string(PLUGIN_IDENTIFIER, "on_pasta_created", s);
+ }
+
+ processed_content
+}
+
+pub fn init_plugins(plugin_identifiers: &Vec) {
+ for PLUGIN_IDENTIFIER in plugin_identifiers.iter() {
+ eval_for_string(PLUGIN_IDENTIFIER, "init", "");
+
+ let init_result = eval_for_string(&PLUGIN_IDENTIFIER, "init", "");
+ let id = eval_for_string(&PLUGIN_IDENTIFIER, "get_id", "");
+ let name = eval_for_string(&id, "get_name", "");
+ let version = eval_for_string(&id, "get_version", "");
+
+ log::info!("Initialised plugin {id} - {name} ({version})");
+ }
+}
+
+fn eval_for_string(plugin_id: &str, function: &str, parameter: &str) -> String {
+ match VM::eval(&*format!("MBP::{}::{}({})", plugin_id, function, parameter)) {
+ Ok(result) => match result.try_convert_to::() {
+ Ok(ruby_string) => ruby_string.to_string(),
+ Err(err) => err.to_string(),
+ },
+ Err(err) => {
+ log::error!(
+ "Failed to run function '{}' on plugin {}: {}",
+ function,
+ plugin_id,
+ err
+ );
+ err.to_string()
+ }
+ }
+}
+
+fn load_plugin_paths() -> Vec {
+ let paths = fs::read_dir("./plugins").expect("Failed to access ./plugins library.");
+
+ let mut plugin_paths: Vec = Vec::new();
+
+ for path in paths {
+ plugin_paths.push(path.unwrap().path().to_str().unwrap().parse().unwrap());
+ }
+
+ plugin_paths
+}
+
+fn read_plugins(plugin_paths: Vec) -> Vec {
+ let mut plugin_codes: Vec = Vec::new();
+
+ for plugin_path in plugin_paths {
+ let plugin_code = match fs::read_to_string(&plugin_path) {
+ Ok(result) => result,
+ Err(err) => {
+ log::error!("Failed to read plugin file {}: {}", plugin_path, err);
+ continue;
+ }
+ };
+ plugin_codes.push(plugin_code);
+ }
+
+ plugin_codes
+}
+
+fn feed_plugins(plugin_codes: Vec) {
+ for plugin_code in plugin_codes {
+ match VM::eval(plugin_code.as_str()) {
+ Ok(result) => {}
+ Err(error) => {
+ log::error!("Failed to initialise plugin: {}", error);
+ continue;
+ }
+ }
+ }
+}
+
+fn get_plugin_identifiers(plugin_paths: Vec) -> Vec {
+ let mut plugin_ids: Vec = Vec::new();
+ for plugin_path in plugin_paths {
+ plugin_ids.push(plugin_path.replace("./plugins/", "").replace(".rb", ""))
+ }
+ plugin_ids
+}