use std::fmt;
use std::fs::{read_to_string, write};
use std::io::{self, Error as IoError, IsTerminal, Read, Result as IoResult, Write};
use std::path::PathBuf;
use std::str::FromStr;
use wmap_renderer::{Configuration, StageType, render_to_png, render_to_svg};
use thiserror::Error;
#[derive(Debug)]
enum ImageType {
Png,
Svg,
}
impl fmt::Display for ImageType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ImageType::Png => write!(f, "png"),
ImageType::Svg => write!(f, "svg"),
}
}
}
#[derive(Error, Debug)]
pub enum WmapError {
#[error("unknown image type.")]
UnknownImageType,
#[error("unknown stage type.")]
UnknownStageType,
}
impl FromStr for ImageType {
type Err = WmapError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"png" => Ok(ImageType::Png),
"svg" => Ok(ImageType::Svg),
_ => Err(Self::Err::UnknownImageType),
}
}
}
fn stage_from_str(s: &str) -> Option<StageType> {
match s.to_lowercase().as_str() {
"activities" => Some(StageType::Activities),
"behavior" => Some(StageType::Behavior),
"certainty" => Some(StageType::Certainty),
"comparison" => Some(StageType::Comparison),
"cynefin" => Some(StageType::Cynefin),
"data" => Some(StageType::Data),
"decision_drivers" => Some(StageType::DecisionDrivers),
"efficiency" => Some(StageType::Efficiency),
"failure" => Some(StageType::Failure),
"focus_of_value" => Some(StageType::FocusOfValue),
"knowledge" => Some(StageType::Knowledge),
"knowledge_management" => Some(StageType::KnowledgeManagement),
"market" => Some(StageType::Market),
"market_action" => Some(StageType::MarketAction),
"market_perception" => Some(StageType::MarketPerception),
"perception_in_industry" => Some(StageType::PerceptionInIndustry),
"practice" => Some(StageType::Practice),
"publication_types" => Some(StageType::PublicationTypes),
"ubiquity" => Some(StageType::Ubiquity),
"understanding" => Some(StageType::Understanding),
"user_perception" => Some(StageType::UserPerception),
"evolution_stage" => Some(StageType::EvolutionStage),
_ => None,
}
}
struct Arguments {
input: Option<PathBuf>,
output: PathBuf,
write_to_stdout: bool,
stage: StageType,
image_type: ImageType,
}
fn print_help() {
println!(
"Usage: wmap [-t|--type IMAGE_TYPE] [-s|--stage STAGE] [-o|--output OUTPUT_FILE] INPUT_FILE"
);
}
fn parse_arguments() -> Result<Arguments, lexopt::Error> {
use lexopt::prelude::*;
let mut input = None;
let mut output: String = String::new();
let mut write_to_stdout: bool = false;
let mut stage = StageType::Activities;
let mut image_type = ImageType::Png;
let mut argument_parser = lexopt::Parser::from_env();
while let Some(argument) = argument_parser.next()? {
match argument {
Short('t') | Long("type") => {
image_type = argument_parser.value()?.parse()?;
}
Short('s') | Long("stage") => {
let stage_string: String = argument_parser.value()?.parse()?;
stage = stage_from_str(&stage_string).ok_or("Invalid stage name")?;
}
Short('o') | Long("output") => {
output = argument_parser.value()?.parse()?;
}
Value(value) if input.is_none() => {
input = Some(value.string()?);
}
Short('h') | Long("help") => {
print_help();
std::process::exit(0);
}
Short('v') | Long("version") => {
let version = env!("CARGO_PKG_VERSION");
println!("{version}");
std::process::exit(0);
}
_ => return Err(argument.unexpected()),
}
}
let input = input.map(PathBuf::from);
let output = if output.is_empty() {
if io::stdout().is_terminal() {
match &input {
Some(path) => path.with_extension(image_type.to_string()),
None => PathBuf::from(format!("./map.{image_type}")),
}
} else {
write_to_stdout = true;
PathBuf::new()
}
} else {
PathBuf::from(output)
};
Ok(Arguments {
input,
output,
write_to_stdout,
stage,
image_type,
})
}
fn run() -> IoResult<()> {
let arguments = parse_arguments().map_err(|_| IoError::other("Unable to parse arguments"))?;
let source = if let Some(input_path) = arguments.input {
input_path.try_exists()?;
read_to_string(input_path)?
} else {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
buffer
};
let map = wmap_parser::parse(&source);
let configuration = Configuration::default();
let image_data = match arguments.image_type {
ImageType::Png => render_to_png(&map, arguments.stage, &configuration)
.map_err(|_| IoError::other("Unable to render PNG"))?,
ImageType::Svg => render_to_svg(&map, arguments.stage, &configuration)
.map_err(|_| IoError::other("Unable to render SVG"))?
.into_bytes(),
};
if arguments.write_to_stdout {
io::stdout().write_all(&image_data)?;
} else {
write(arguments.output, image_data)?;
}
Ok(())
}
fn main() -> IoResult<()> {
let result = run();
if cfg!(debug_assertions) {
result
} else {
match result {
Ok(()) => Ok(()),
Err(e) => {
eprintln!("Error: {e}");
eprintln!();
print_help();
std::process::exit(1);
}
}
}
}