Skip to content

Commit

Permalink
add plantuml
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelb committed Jul 20, 2024
1 parent 0d58135 commit e7fac8e
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## v1.3.15
- Add PlantUML support (ascii output)

## v1.3.14
- Improve Lua\_nvim's handling of 'local' requires in REPL mode

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions doc/sources/interpreters/Plantuml_original.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## PlantUML original

This interpreter relies on `plantuml`, which needs to be available on the $PATH



This interpreter supports the following options:

```lua
require'sniprun'.setup({
interpreter_options = {
Plantuml_original = {
output_mode = "-tutxt", --# one of the options allowed by plantuml
compiler = "/home/user/my_custom_plantuml_install/plantuml"
}
}
}
})
```
You can add options to the 'compiler' key, but do not set "-pipe" (or it'll break output), and "-failfast2", "-nbthread auto" are already set.


4 changes: 3 additions & 1 deletion lua/sniprun/display.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ function M.fw_open(row, column, message, ok, temp)
vim.api.nvim_buf_set_lines(bufnr,h,h+1,false,{line})
vim.api.nvim_buf_add_highlight(bufnr, namespace_id, hl, h,0,-1) -- highlight lines in floating window
end
M.fw_handle = vim.api.nvim_open_win(bufnr, false, {relative='win', width=w+1, height=h, bufpos=bp, focusable=false, style='minimal',border=M.borders})
if h ~= 0 then
M.fw_handle = vim.api.nvim_open_win(bufnr, false, {relative='win', width=w+1, height=h, bufpos=bp, focusable=false, style='minimal',border=M.borders})
end
end

function M.term_set_window_handle()
Expand Down
4 changes: 4 additions & 0 deletions ressources/install_all_compilers_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ then
sudo apt-get install clojure
fi

if ! command -v plantuml &> /dev/null
then
sudo apt-get install plantuml
fi


if ! command -v go &> /dev/null
Expand Down
265 changes: 265 additions & 0 deletions src/interpreters/Plantuml_original.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// Be sure to read the CONTRIBUTING.md file :-)

#[derive(Clone)]
#[allow(non_camel_case_types)]
// For example, Rust_original is a good name for the first rust interpreter
pub struct Plantuml_original {
support_level: SupportLevel,
data: DataHolder,
code: String,

language_work_dir: String,
main_file_path: String,
output_mode: String,
}

//necessary boilerplate, you don't need to implement that if you want a Bloc support level
//interpreter (the easiest && most common)
impl ReplLikeInterpreter for Plantuml_original {}

impl Interpreter for Plantuml_original {
fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box<Plantuml_original> {
//create a subfolder in the cache folder
let lwd = data.work_dir.clone() + "/plantuml_original";
let mut builder = DirBuilder::new();
builder.recursive(true);
builder
.create(&lwd)
.expect("Could not create directory for example");

let mfp = lwd.clone() + "/main.uml";
Box::new(Plantuml_original {
data,
support_level,
code: String::new(),
language_work_dir: lwd,
main_file_path: mfp,
output_mode: String::new(),
})
}

fn get_supported_languages() -> Vec<String> {
vec![
String::from("PlantUML"), // in 1st position of vector, used for info only
//':set ft?' in nvim to get the filetype of opened file
String::from("puml"),
String::from("uml"),
String::from("pu"),
String::from("iuml"),
String::from("plantuml"),
]
}

fn get_name() -> String {
// get your interpreter name
String::from("Plantuml_original")
}

fn get_current_level(&self) -> SupportLevel {
self.support_level
}
fn set_current_level(&mut self, level: SupportLevel) {
self.support_level = level;
}

fn get_data(&self) -> DataHolder {
self.data.clone()
}

fn default_for_filetype() -> bool {
true
}

fn behave_repl_like_default() -> bool {
false
}

fn has_repl_capability() -> bool {
false
}

fn get_max_support_level() -> SupportLevel {
//define the max level support of the interpreter (see readme for definitions)
SupportLevel::Bloc
}

fn fetch_code(&mut self) -> Result<(), SniprunError> {
let nvim_instance = self.data.nvim_instance.clone().unwrap();
let mut real_nvim_instance = nvim_instance.lock().unwrap();
//note: you probably don't have to modify, or even understand this function

//check if we not on boundary of block, if so replace self.code with whole bloc
let line_n = self.data.range[0];
if self
.data
.current_line
.trim_start()
.to_lowercase()
.starts_with("@startuml")
{
let end_line = real_nvim_instance
.get_current_buf()
.unwrap()
.line_count(&mut real_nvim_instance)
.unwrap();
let capped_end_line = std::cmp::min(line_n + 400, end_line); // in case there is a very long file, don't search for nothing up to line 500k
let it = line_n + 1..capped_end_line + 1;

let mut code_bloc = vec![];
for i in it {
let line_i = real_nvim_instance
.get_current_buf()
.unwrap()
.get_lines(&mut real_nvim_instance, i - 1, i, false)
.unwrap()
.join("");
if line_i.trim_start().to_lowercase().starts_with("@enduml") {
//found end of bloc
info!("found end of bloc at line {}", i);
self.data.current_bloc = code_bloc.join("\n");
} else {
info!("adding line {} to current bloc", i);
code_bloc.push(line_i.to_string());
}
}
}
//
//add code from data to self.code
if !self
.data
.current_bloc
.replace(&[' ', '\t', '\n', '\r'][..], "")
.is_empty()
&& self.support_level >= SupportLevel::Bloc
{
// if bloc is not pseudo empty and has Bloc current support level,
// add fetched code to self
self.code = self.data.current_bloc.clone();

// if there is only data on current line / or Line is the max support level
} else if !self.data.current_line.replace(" ", "").is_empty()
&& self.support_level >= SupportLevel::Line
{
self.code = self.data.current_line.clone();
} else {
// no code was retrieved
self.code = String::from("");
}

// now self.code contains the line or bloc of code wanted :-)
Ok(())
}

fn add_boilerplate(&mut self) -> Result<(), SniprunError> {
if !self.code.contains("@startuml") {
self.code = String::from("@startuml\n") + &self.code;
}
if !self.code.contains("@enduml") {
self.code = self.code.clone() + "\n@enduml\n";
}
Ok(())
}

fn build(&mut self) -> Result<(), SniprunError> {
let mut _file = File::create(&self.main_file_path)
.expect("Failed to create file for plantuml_original");
// IO errors can be ignored, or handled into a proper SniprunError
// If you panic, it should not be too dangerous for anyone
write(&self.main_file_path, &self.code)
.expect("Unable to write to file for plantuml_original");

self.output_mode = String::from("-tutxt");

if let Some(config_value) =
Plantuml_original::get_interpreter_option(&self.data, "output_mode")
{
if let Some(config_value_valid_string) = config_value.as_str() {
self.output_mode = config_value_valid_string.to_string();
}
}
let allowed_output_modes = [
"-tutxt",
"-thtml",
"-tsvg",
"-tpng",
"-tpdf",
"-teps",
"-teps:text",
"-tlatex",
"-tlatex:nopreamble",
"-ttxt",
];
if !allowed_output_modes.contains(&self.output_mode.as_str()) {
return Err(SniprunError::CustomError(format!(
"invalid output mode {}, allowed modes are {:?}",
self.output_mode, allowed_output_modes
)));
}

let compiler = Plantuml_original::get_compiler_or(&self.data, "plantuml");
//compile it (to the bin_path that already points to the rigth path)
let output = Command::new(compiler.split_whitespace().next().unwrap())
.args(compiler.split_whitespace().skip(1))
.arg("-o")
.arg(&self.language_work_dir)
.arg(&self.output_mode)
.arg("-nbthread")
.arg("auto")
.arg("-failfast2")
.arg(self.main_file_path.clone())
.output()
.expect("Unable to start process");
if output.status.success() {
//return stdout
return Ok(());
} else {
// return stderr
return Err(SniprunError::CompilationError(
String::from_utf8(output.stderr).unwrap(),
));
}
}

fn execute(&mut self) -> Result<String, SniprunError> {
let extension = String::from(".") + &self.output_mode[2..];
let content = std::fs::read_to_string(&self.main_file_path.replace(".uml", &extension));
if let Ok(content) = content {
Ok(content)
} else {
return Err(SniprunError::RuntimeError(
format!("could not read output file {}", &self.main_file_path.replace(".uml", &extension) ),
));
}
}
}

// You can add tests if you want to
#[cfg(test)]
mod test_language_original {
use super::*;
use serial_test::serial;

#[test]
#[serial] // multiple test of the same interpreter should run sequentially:
// after all, they write and read to the same dir/files
fn simple_print() {
let mut data = DataHolder::new();

//inspired from Rust syntax
data.current_bloc = String::from("@startuml\nparticipant Bob");
let mut interpreter = Plantuml_original::new(data);
let res = interpreter.run();

// -> should panic if not an Ok()
let string_result = res.unwrap();

// -> compare result with predicted
assert!(string_result.contains("Bob"));
}

#[test]
#[serial]
fn another_test() {
//another test
}
}

0 comments on commit e7fac8e

Please sign in to comment.