Basic Plugin Support - Syntax Highlighter Added - WIP

This commit is contained in:
Daniel Szabo 2022-05-22 19:26:43 +01:00
parent 1c873d23b5
commit a6a56a11c7
8 changed files with 367 additions and 7 deletions

View file

@ -21,3 +21,4 @@ log = "0.4"
env_logger = "0.9.0"
actix-web-httpauth = "0.6.0"
lazy_static = "1.4.0"
rutie = "0.8.4"

View file

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

View file

@ -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}$$", "<span style='color:#{color}'>");
content.gsub!("$$/#{color}$$", "</span>");
};
return content
end
end
end

78
plugins/MBPlugin.rb Normal file
View file

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

42
plugins/helloWorld.rb Normal file
View file

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

View file

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

View file

@ -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<AppState>, mut payload: Multipart) -> Result<Htt
}
"content" => {
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<AppState>, id: web::Path<String>) -> 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(),
);
}
}

134
src/plugins.rs Normal file
View file

@ -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<String> = init();
}
fn init() -> Vec<String> {
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<String>) {
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::<RString>() {
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<String> {
let paths = fs::read_dir("./plugins").expect("Failed to access ./plugins library.");
let mut plugin_paths: Vec<String> = 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<String>) -> Vec<String> {
let mut plugin_codes: Vec<String> = 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<String>) {
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<String>) -> Vec<String> {
let mut plugin_ids: Vec<String> = Vec::new();
for plugin_path in plugin_paths {
plugin_ids.push(plugin_path.replace("./plugins/", "").replace(".rb", ""))
}
plugin_ids
}