use std::fs;
use std::io::{self, Read};
use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use log::info;
use plotly_static::{ImageFormat, StaticExporterBuilder};
use serde_json::Value;
#[derive(Parser)]
#[command(name = "plotly-static-export")]
#[command(about = "Export Plotly plots to static images")]
#[command(version)]
struct Cli {
#[arg(short, long, required = true, default_value = "-")]
input: String,
#[arg(short, long, default_value = "output")]
output: PathBuf,
#[arg(short, long, value_enum, default_value_t = ImageFormatArg::PNG)]
format: ImageFormatArg,
#[arg(long, default_value_t = 800)]
width: usize,
#[arg(long, default_value_t = 600)]
height: usize,
#[arg(short, long, default_value_t = 1.0)]
scale: f64,
#[arg(long)]
offline: bool,
}
#[derive(ValueEnum, Clone)]
#[allow(clippy::upper_case_acronyms)]
enum ImageFormatArg {
PNG,
JPEG,
WEBP,
SVG,
PDF,
}
impl std::fmt::Display for ImageFormatArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
ImageFormatArg::PNG => "PNG",
ImageFormatArg::JPEG => "JPEG",
ImageFormatArg::WEBP => "WEBP",
ImageFormatArg::SVG => "SVG",
ImageFormatArg::PDF => "PDF",
}
)
}
}
impl From<ImageFormatArg> for ImageFormat {
fn from(format: ImageFormatArg) -> Self {
match format {
ImageFormatArg::PNG => ImageFormat::PNG,
ImageFormatArg::JPEG => ImageFormat::JPEG,
ImageFormatArg::WEBP => ImageFormat::WEBP,
ImageFormatArg::SVG => ImageFormat::SVG,
ImageFormatArg::PDF => ImageFormat::PDF,
}
}
}
fn read_json_from_stdin() -> Result<Value, Box<dyn std::error::Error>> {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
let json: Value = serde_json::from_str(&buffer)?;
Ok(json)
}
fn read_json_from_file(path: &str) -> Result<Value, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
let json: Value = serde_json::from_str(&content)?;
Ok(json)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let cli = Cli::parse();
let plot_json = if cli.input == "-" {
info!("Reading Plotly JSON from stdin...");
read_json_from_stdin()?
} else {
info!("Reading Plotly JSON from file: {}", cli.input);
read_json_from_file(&cli.input)?
};
if !plot_json.is_object() {
return Err("Invalid JSON: expected an object".into());
}
let mut exporter = StaticExporterBuilder::default()
.offline_mode(cli.offline)
.build()?;
info!(
"Exporting plot to {} format ({}x{} pixels, scale: {})...",
cli.format, cli.width, cli.height, cli.scale
);
exporter.write_fig(
&cli.output,
&plot_json,
cli.format.into(),
cli.width,
cli.height,
cli.scale,
)?;
info!("Successfully exported plot to: {}", cli.output.display());
Ok(())
}