Skip to content

Commit

Permalink
feat: add markdown format for accents and flavors
Browse files Browse the repository at this point in the history
  • Loading branch information
backwardspy committed May 29, 2024
1 parent 9d1bb66 commit 32d222d
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 128 deletions.
126 changes: 90 additions & 36 deletions whiskers/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,16 @@ fn override_matrix(
Ok(())
}

#[allow(clippy::too_many_lines)]
fn list_functions(format: OutputFormat) {
let functions = templating::all_functions();
let filters = templating::all_filters();
println!(
"{}",
match format {
OutputFormat::Json | OutputFormat::Yaml => {
let output = serde_json::json!({
"functions": templating::all_functions(),
"filters": templating::all_filters()
"functions": functions,
"filters": filters,
});

if matches!(format, OutputFormat::Json) {
Expand All @@ -249,15 +250,19 @@ fn list_functions(format: OutputFormat) {
}
}
OutputFormat::Markdown | OutputFormat::MarkdownTable => {
markdown::display_functions_as_table()
format!(
"{}\n\n{}",
markdown::display_as_table(&functions, "Functions"),
markdown::display_as_table(&filters, "Filters")
)
}
OutputFormat::Plain => {
let mut list = templating::all_filters()
let mut list = filters
.iter()
.map(|f| f.name.clone())
.collect::<Vec<String>>();

list.extend(templating::all_functions().iter().map(|f| f.name.clone()));
list.extend(functions.iter().map(|f| f.name.clone()));

list.join("\n")
}
Expand All @@ -266,71 +271,120 @@ fn list_functions(format: OutputFormat) {
}

fn list_flavors(format: OutputFormat) {
let format = match format {
OutputFormat::Markdown | OutputFormat::MarkdownTable => {
eprintln!("warning: Markdown output is not yet supported for listing flavors, reverting to `plain`");
OutputFormat::Plain
// we want all the flavor info minus the colors
#[derive(serde::Serialize)]
struct FlavorInfo {
identifier: String,
name: String,
emoji: char,
order: u32,
dark: bool,
}

impl markdown::TableDisplay for FlavorInfo {
fn table_headings() -> Box<[String]> {
vec![
"Identifier".to_string(),
"Name".to_string(),
"Dark".to_string(),
"Emoji".to_string(),
]
.into_boxed_slice()
}
other => other,
};

let output = catppuccin::PALETTE
fn table_row(&self) -> Box<[String]> {
vec![
self.identifier.clone(),
self.name.clone(),
self.dark.to_string(),
self.emoji.to_string(),
]
.into_boxed_slice()
}
}

let flavors = catppuccin::PALETTE
.all_flavors()
.map(catppuccin::Flavor::identifier);
.into_iter()
.map(|f| FlavorInfo {
identifier: f.identifier().to_string(),
name: f.name.to_string(),
emoji: f.emoji,
order: f.order,
dark: f.dark,
})
.collect::<Vec<_>>();

println!(
"{}",
match format {
// for structured data, we output the full flavor info objects
OutputFormat::Json | OutputFormat::Yaml => {
let output = serde_json::json!(output);

if matches!(format, OutputFormat::Json) {
serde_json::to_string_pretty(&output).expect("output is guaranteed to be valid")
serde_json::to_string_pretty(&flavors)
.expect("flavors are guaranteed to be valid json")
} else {
serde_yaml::to_string(&output).expect("output is guaranteed to be valid")
serde_yaml::to_string(&flavors)
.expect("flavors are guaranteed to be valid yaml")
}
}
// for plain output, we just list the flavor identifiers
OutputFormat::Plain => {
output.join("\n")
flavors.iter().map(|f| &f.identifier).join("\n")
}
// and finally for human-readable markdown, we list the flavor names
OutputFormat::Markdown | OutputFormat::MarkdownTable => {
markdown::display_as_table(&flavors, "Flavors")
}
_ => todo!(),
}
);
}

fn list_accents(format: OutputFormat) {
let format = match format {
OutputFormat::Markdown | OutputFormat::MarkdownTable => {
eprintln!("warning: Markdown output is not yet supported for listing accents, reverting to `plain`");
OutputFormat::Plain
}
other => other,
};

let output = catppuccin::PALETTE
let accents = catppuccin::PALETTE
.latte
.colors
.all_colors()
.into_iter()
.filter_map(|c| if c.accent { Some(c.identifier()) } else { None })
.filter(|c| c.accent)
.collect::<Vec<_>>();

println!(
"{}",
match format {
// for structured data, we can include both name and identifier of each color
OutputFormat::Json | OutputFormat::Yaml => {
let output = serde_json::json!(output);

let accents = accents
.into_iter()
.map(|c| {
serde_json::json!({
"name": c.name,
"identifier": c.identifier(),
})
})
.collect::<Vec<_>>();
if matches!(format, OutputFormat::Json) {
serde_json::to_string_pretty(&output).expect("output is guaranteed to be valid")
serde_json::to_string_pretty(&accents)
.expect("accents are guaranteed to be valid json")
} else {
serde_yaml::to_string(&output).expect("output is guaranteed to be valid")
serde_yaml::to_string(&accents)
.expect("accents are guaranteed to be valid yaml")
}
}
// for plain output, we just list the identifiers
OutputFormat::Plain => {
output.join("\n")
accents
.into_iter()
.map(catppuccin::Color::identifier)
.join("\n")
}
// and finally for human-readable markdown, we list the names
OutputFormat::Markdown | OutputFormat::MarkdownTable => {
markdown::display_as_list(
&accents.into_iter().map(|c| c.name).collect::<Vec<_>>(),
"Accents",
)
}
_ => todo!(),
}
);
}
Expand Down
105 changes: 14 additions & 91 deletions whiskers/src/markdown.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
use itertools::Itertools as _;
use std::fmt::Display;

use crate::templating::{self, Filter, Function};
use itertools::Itertools as _;

pub trait TableDisplay {
fn table_headings() -> Box<[String]>;
fn table_row(&self) -> Box<[String]>;
}

#[must_use]
pub fn display_functions_as_table() -> String {
let mut result = String::new();
result.push_str("### Functions\n\n");
result.push_str(display_as_table(&templating::all_functions()).as_str());
result.push_str("\n### Filters\n\n");
result.push_str(display_as_table(&templating::all_filters()).as_str());
result
pub fn display_as_list<T: Display>(items: &[T], heading: &str) -> String {
let items = items.iter().map(|item| format!("* {item}")).join("\n");
format!("### {heading}\n\n{items}")
}

pub fn display_as_table<T: TableDisplay>(items: &[T]) -> String {
pub fn display_as_table<T: TableDisplay>(items: &[T], heading: &str) -> String {
let mut result = String::new();
let rows = items.iter().map(T::table_row).collect::<Vec<_>>();

Expand All @@ -30,13 +25,17 @@ pub fn display_as_table<T: TableDisplay>(items: &[T]) -> String {
let max_width = rows
.iter()
.map(|row| row[i].len())
.chain(std::iter::once(heading.len()))
.max()
.unwrap_or(heading.len());
(heading, max_width)
})
.collect::<Vec<_>>();

// print the headings
// add the section heading
result.push_str(&format!("### {heading}\n\n"));

// add the table headings
result.push_str(&format!(
"| {} |\n",
headings
Expand All @@ -45,7 +44,7 @@ pub fn display_as_table<T: TableDisplay>(items: &[T]) -> String {
.join(" | ")
));

// print the separator
// add the separator
result.push_str(&format!(
"| {} |\n",
headings
Expand All @@ -54,7 +53,7 @@ pub fn display_as_table<T: TableDisplay>(items: &[T]) -> String {
.join(" | ")
));

// print the rows
// add the rows
for row in rows {
result.push_str(&format!(
"| {} |\n",
Expand All @@ -68,81 +67,5 @@ pub fn display_as_table<T: TableDisplay>(items: &[T]) -> String {
));
}

result
}

impl TableDisplay for Function {
fn table_headings() -> Box<[String]> {
Box::new([
"Name".to_string(),
"Description".to_string(),
"Examples".to_string(),
])
}

fn table_row(&self) -> Box<[String]> {
Box::new([
format!("`{}`", self.name),
self.description.clone(),
if self.examples.is_empty() {
"None".to_string()
} else {
self.examples.first().map_or_else(String::new, |example| {
format!(
"`{name}({input})` ⇒ `{output}`",
name = self.name,
input = example
.inputs
.iter()
.map(|(k, v)| format!("{k}={v}"))
.join(", "),
output = example.output
)
})
},
])
}
}

impl TableDisplay for Filter {
fn table_headings() -> Box<[String]> {
Box::new([
"Name".to_string(),
"Description".to_string(),
"Examples".to_string(),
])
}

fn table_row(&self) -> Box<[String]> {
Box::new([
format!("`{}`", self.name),
self.description.clone(),
if self.examples.is_empty() {
"None".to_string()
} else {
self.examples.first().map_or_else(String::new, |example| {
if example.inputs.is_empty() {
format!(
"`{value} \\| {name}` ⇒ `{output}`",
value = example.value,
name = self.name,
output = example.output
)
} else {
format!(
"`{value} \\| {name}({input})` ⇒ `{output}`",
value = example.value,
name = self.name,
input = example
.inputs
.iter()
.map(|(k, v)| format!("{k}={v}"))
.join(", "),
output = example.output
)
}
})
},
])
}
result.trim().to_string()
}
Loading

0 comments on commit 32d222d

Please sign in to comment.