use std::env;
use std::fs;
use std::path::Path;
use lo_base::{database_from_csv, execute_select};
use lo_calc::{evaluate_formula, save_ods, workbook_from_csv_opts};
use lo_core::{CellValue, Result, Sheet, TextDocument};
use lo_draw::demo_drawing;
use lo_impress::demo_presentation;
use lo_math::{from_latex, to_mathml_string};
use lo_uno::{EchoService, ServiceRegistry, UnoValue};
use lo_writer::{from_markdown, save_odt};
use lo_zip::list_entries;
fn main() {
if let Err(error) = run() {
eprintln!("error: {error}");
std::process::exit(1);
}
}
fn run() -> Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
print_usage();
return Ok(());
}
match args[1].as_str() {
"writer" => writer_command(&args[2..]),
"calc" => calc_command(&args[2..]),
"impress" => impress_command(&args[2..]),
"draw" => draw_command(&args[2..]),
"math" => math_command(&args[2..]),
"base" => base_command(&args[2..]),
"package" => package_command(&args[2..]),
"uno" => uno_command(&args[2..]),
"help" | "--help" | "-h" => {
print_usage();
Ok(())
}
other => Err(lo_core::LoError::InvalidInput(format!(
"unknown command: {other}"
))),
}
}
fn writer_command(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("new") => {
let output = required_positional(args, 1, "output path")?;
let title = flag_value(args, "--title").unwrap_or_else(|| "Document".to_string());
let text = flag_value(args, "--text").unwrap_or_else(|| "Hello from libreoffice-rs".to_string());
let mut doc = TextDocument::new(title);
doc.push_paragraph(text);
save_odt(output, &doc)
}
Some("markdown-to-odt") => {
let input = required_positional(args, 1, "input markdown path")?;
let output = required_positional(args, 2, "output odt path")?;
let title = flag_value(args, "--title").unwrap_or_else(|| file_stem_or_default(input, "Document"));
let markdown = fs::read_to_string(input)?;
let doc = from_markdown(title, &markdown);
save_odt(output, &doc)
}
_ => Err(lo_core::LoError::InvalidInput(
"writer commands: new, markdown-to-odt".to_string(),
)),
}
}
fn calc_command(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("csv-to-ods") => {
let input = required_positional(args, 1, "input csv path")?;
let output = required_positional(args, 2, "output ods path")?;
let sheet_name = flag_value(args, "--sheet").unwrap_or_else(|| "Sheet1".to_string());
let title = flag_value(args, "--title").unwrap_or_else(|| file_stem_or_default(output, "Workbook"));
let has_header = has_flag(args, "--has-header");
let csv = fs::read_to_string(input)?;
let workbook = workbook_from_csv_opts(title, &sheet_name, &csv, has_header)?;
save_ods(output, &workbook)
}
Some("eval") => {
let formula = required_positional(args, 1, "formula")?;
let has_header = has_flag(args, "--has-header");
let sheet = if let Some(csv_path) = flag_value(args, "--csv") {
let csv = fs::read_to_string(csv_path)?;
let workbook = workbook_from_csv_opts("Eval", "Sheet1", &csv, has_header)?;
workbook.sheets[0].clone()
} else {
let mut sheet = Sheet::new("Sheet1");
sheet.set(lo_core::CellAddr::new(0, 0), CellValue::Number(1.0));
sheet.set(lo_core::CellAddr::new(1, 0), CellValue::Number(2.0));
sheet.set(lo_core::CellAddr::new(2, 0), CellValue::Number(3.0));
sheet
};
let value = evaluate_formula(formula, &sheet)?;
println!("{value:?}");
Ok(())
}
_ => Err(lo_core::LoError::InvalidInput(
"calc commands: csv-to-ods, eval".to_string(),
)),
}
}
fn impress_command(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("demo") => {
let output = required_positional(args, 1, "output odp path")?;
let title = flag_value(args, "--title").unwrap_or_else(|| "Demo Deck".to_string());
let presentation = demo_presentation(&title);
lo_impress::save_odp(output, &presentation)
}
_ => Err(lo_core::LoError::InvalidInput(
"impress commands: demo".to_string(),
)),
}
}
fn draw_command(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("demo") => {
let output = required_positional(args, 1, "output odg path")?;
let title = flag_value(args, "--title").unwrap_or_else(|| "Diagram".to_string());
let drawing = demo_drawing(&title);
lo_draw::save_odg(output, &drawing)
}
_ => Err(lo_core::LoError::InvalidInput(
"draw commands: demo".to_string(),
)),
}
}
fn math_command(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("latex-to-mathml") => {
let input = required_positional(args, 1, "input formula path")?;
let latex = fs::read_to_string(input)?;
let doc = from_latex("Formula", &latex)?;
println!("{}", to_mathml_string(&doc.root));
Ok(())
}
Some("latex-to-odf") => {
let input = required_positional(args, 1, "input formula path")?;
let output = required_positional(args, 2, "output odf path")?;
let title = flag_value(args, "--title").unwrap_or_else(|| "Formula".to_string());
let latex = fs::read_to_string(input)?;
let doc = from_latex(title, &latex)?;
lo_odf::save_formula_document(output, &doc)
}
_ => Err(lo_core::LoError::InvalidInput(
"math commands: latex-to-mathml, latex-to-odf".to_string(),
)),
}
}
fn base_command(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("csv-to-odb") => {
let input = required_positional(args, 1, "input csv path")?;
let table = required_positional(args, 2, "table name")?;
let output = required_positional(args, 3, "output odb path")?;
let title = flag_value(args, "--title").unwrap_or_else(|| file_stem_or_default(output, "Database"));
let csv = fs::read_to_string(input)?;
let database = database_from_csv(title, table, &csv)?;
lo_base::save_odb(output, &database)
}
Some("query") => {
let input = required_positional(args, 1, "input csv path")?;
let table = required_positional(args, 2, "table name")?;
let sql = args.iter().skip(3).cloned().collect::<Vec<_>>().join(" ");
if sql.trim().is_empty() {
return Err(lo_core::LoError::InvalidInput("missing SQL query".to_string()));
}
let csv = fs::read_to_string(input)?;
let database = database_from_csv("Query", table, &csv)?;
let result = execute_select(&database, &sql)?;
println!("{}", result.columns.join("\t"));
for row in result.rows {
let cells = row
.into_iter()
.map(|value| match value {
lo_core::DbValue::Null => String::new(),
lo_core::DbValue::Integer(v) => v.to_string(),
lo_core::DbValue::Float(v) => v.to_string(),
lo_core::DbValue::Text(v) => v,
lo_core::DbValue::Bool(v) => v.to_string(),
})
.collect::<Vec<_>>();
println!("{}", cells.join("\t"));
}
Ok(())
}
_ => Err(lo_core::LoError::InvalidInput(
"base commands: csv-to-odb, query".to_string(),
)),
}
}
fn package_command(args: &[String]) -> Result<()> {
match args.first().map(String::as_str) {
Some("inspect") => {
let path = required_positional(args, 1, "package path")?;
for entry in list_entries(path)? {
println!(
"{}\t{} bytes",
entry.name, entry.uncompressed_size
);
}
Ok(())
}
_ => Err(lo_core::LoError::InvalidInput(
"package commands: inspect".to_string(),
)),
}
}
fn uno_command(args: &[String]) -> Result<()> {
let mut registry = ServiceRegistry::default();
registry.register(Box::new(EchoService));
match args.first().map(String::as_str) {
Some("list") => {
for service in registry.list_services() {
println!("{service}");
}
Ok(())
}
Some("demo") => {
let response = registry.invoke(
"com.libreoffice_rs.Echo",
"echo",
&[UnoValue::string("hello from libreoffice-rs")],
)?;
println!("{response:?}");
Ok(())
}
_ => Err(lo_core::LoError::InvalidInput(
"uno commands: list, demo".to_string(),
)),
}
}
fn required_positional<'a>(args: &'a [String], index: usize, name: &str) -> Result<&'a str> {
args.get(index)
.map(String::as_str)
.ok_or_else(|| lo_core::LoError::InvalidInput(format!("missing {name}")))
}
fn flag_value(args: &[String], flag: &str) -> Option<String> {
args.iter()
.position(|arg| arg == flag)
.and_then(|index| args.get(index + 1).cloned())
}
fn has_flag(args: &[String], flag: &str) -> bool {
args.iter().any(|arg| arg == flag)
}
fn file_stem_or_default(path: &str, fallback: &str) -> String {
Path::new(path)
.file_stem()
.and_then(|value| value.to_str())
.map(|value| value.to_string())
.unwrap_or_else(|| fallback.to_string())
}
fn print_usage() {
println!(
"Usage:\n libreoffice-rs writer new <out.odt> [--title TITLE] [--text TEXT]\n libreoffice-rs writer markdown-to-odt <in.md> <out.odt> [--title TITLE]\n libreoffice-rs calc csv-to-ods <in.csv> <out.ods> [--sheet NAME] [--title TITLE] [--has-header]\n libreoffice-rs calc eval <formula> [--csv input.csv] [--has-header]\n libreoffice-rs impress demo <out.odp> [--title TITLE]\n libreoffice-rs draw demo <out.odg> [--title TITLE]\n libreoffice-rs math latex-to-mathml <formula.txt>\n libreoffice-rs math latex-to-odf <formula.txt> <out.odf> [--title TITLE]\n libreoffice-rs base csv-to-odb <in.csv> <table> <out.odb> [--title TITLE]\n libreoffice-rs base query <in.csv> <table> <SQL...>\n libreoffice-rs package inspect <file>\n libreoffice-rs uno list\n libreoffice-rs uno demo"
);
}