From 299b12b9a300e22a86ee2e0eca6b3479ac36f06a Mon Sep 17 00:00:00 2001 From: Alexandre ABRIOUX Date: Mon, 11 Nov 2024 17:07:57 +0100 Subject: [PATCH] feat: allow custom item config --- README.md | 31 ++++++++++-- src/main.rs | 127 +++++++++++++++++++++++++++++++++++++++++++------- tests/main.rs | 30 ++++++------ 3 files changed, 152 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 857ea3e..184eb39 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,13 @@ tools: -|Language|Tools|IDEs| -|-|-|-| -|[HTML5](#)|[Git](#)|[VSCodium](#)| -|[CSS3](#)|[GitHub](#)|[JetBrains](#)| -|[JavaScript](#)|[GitHub Actions](#)|[Neovim](#)| + +| Language | Tools | IDEs | +|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------| +| [HTML5](#) | [Git](#) | [VSCodium](#) | +| [CSS3](#) | [GitHub](#) | [JetBrains](#) | +| [JavaScript](#) | [GitHub Actions](#) | [Neovim](#) | + Here is a @@ -102,12 +104,31 @@ You're all set! ## Configuration +### List of tools + This GitHub action uses https://shields.io to generate icons in your profile's README, and `shield.io` itself uses https://simpleicons.org/ provide logos for brands. You can find a list of all available brand names over here: https://github.com/simple-icons/simple-icons/blob/develop/slugs.md. Use the `Brand slug` name to furnish your configuration file. +### Custom tool + +If the tool is not available on [SimpleIcons](https://simpleicons.org/) or +if you'd like to use one icon with a custom name, you can do so with the following: + +```yaml +tools: + ides: + # VSCode not available on SimpleIcons, add the tool without an icon + - label: VSCode + color: 29a9f2 + # RustRover not available on SimpleIcons, use JetBrains icon with a custom label and color + - label: RustRover + icon: jetbrains + color: 34c97a +``` + ## GitHub Action Inputs Use the following input parameters to override defaults and customize the action to your use case: diff --git a/src/main.rs b/src/main.rs index 631d200..ed9db78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use clap::Parser; use markdown_table_formatter::format_tables; use std::fs; use yaml_rust2::yaml::Hash; -use yaml_rust2::YamlLoader; +use yaml_rust2::{Yaml, YamlLoader}; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -70,12 +70,12 @@ fn generate_header(tools: &Hash) -> String { } fn generate_items(tools: &Hash) -> String { - let lines_nb = tools.keys().fold(0, |max_tools_length, category| { - let category_items = tools[category].as_vec().unwrap(); - if category_items.len() > max_tools_length { - return category_items.len(); + let lines_nb = tools.keys().fold(0, |max_items_count, category| { + let category_items_count = tools[category].as_vec().unwrap().len(); + if category_items_count > max_items_count { + return category_items_count; } - return max_tools_length; + return max_items_count; }); let mut items = String::new(); @@ -85,7 +85,7 @@ fn generate_items(tools: &Hash) -> String { let category_items = tools[category].as_vec().unwrap(); if line <= category_items.len() { let item = &category_items[line - 1]; - items.push_str(generate_img_tag(item.as_str().unwrap()).as_str()); + items.push_str(generate_img_tag(item).as_str()); } items.push_str("|"); }); @@ -94,19 +94,85 @@ fn generate_items(tools: &Hash) -> String { items } -fn generate_img_tag(slug: &str) -> String { +struct Item { + label: String, + color: String, + icon: Option, +} + +fn extract_string_from_hash(hash: &Hash, key: &str) -> String { + let value = Yaml::String(key.to_string()); + hash.get(&value) + .expect(format!("missing {key} for item").as_str()) + .as_str() + .expect(format!("{key} is not a string").as_str()) + .to_string() +} + +fn generate_img_tag(item: &Yaml) -> String { + if !item.is_hash() { + return generate_img_tag_from_slug(item.as_str().unwrap()); + } + let item_hash = item.as_hash().unwrap(); + let icon = item_hash + .get(&Yaml::String("icon".to_string())) + .map(|s| s.as_str().unwrap().to_string()); + let mut color_option: Option = None; + if icon.is_some() { + let icon_from_slug = generate_item_from_slug(icon.unwrap().as_str()); + color_option = Some(icon_from_slug.color) + } + if item_hash.get(&Yaml::String("color".to_string())).is_some() { + color_option = Some(extract_string_from_hash(item_hash, "color")) + } + let label = extract_string_from_hash(item_hash, "label"); + let color = color_option + .expect(format!("missing color or icon for item {label}").as_str()) + .to_string(); + let item = Item { + label, + color, + icon: item_hash + .get(&Yaml::String("icon".to_string())) + .map(|s| s.as_str().unwrap().to_string()), + }; + generate_img_tag_from_item(&item) +} + +fn generate_img_tag_from_slug(slug: &str) -> String { + generate_img_tag_from_item(&generate_item_from_slug(&slug)) +} + +fn generate_item_from_slug(slug: &str) -> Item { let icons = &SimpleIcons::global().icons; let icon = icons .get(slug) .expect(format!("Could not find icon for slug {slug}").as_str()); let title = &icon.title; let hex = &icon.hex; - let background = if is_relatively_light_icon_hex(&hex) { + Item { + label: title.to_string(), + color: hex.to_string(), + icon: Some(slug.to_string()), + } +} + +fn generate_img_tag_from_item(item: &Item) -> String { + let mut tag = String::new(); + let label = &item.label; + let color = &item.color; + let background = if is_relatively_light_icon_hex(&color) { "black" } else { "white" }; - format!("[\"{title}\"](#)") + tag.push_str(format!(r#"[{label}](#)"#); + tag } fn update_readme(readme_path: &String, toolbox_markdown: &String) { @@ -144,11 +210,11 @@ tools: let markdown = generate_toolbox(&input.to_string()); assert_eq!( markdown, - "| ides | languages | + r###"| ides | languages | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| [\"JetBrains\"](#) | [\"JavaScript\"](#) | -| [\"Neovim\"](#) | [\"Rust\"](#) | -" +| [JetBrains](#) | [JavaScript](#) | +| [Neovim](#) | [Rust](#) | +"### ); } @@ -164,11 +230,36 @@ tools: let markdown = generate_toolbox(&input.to_string()); assert_eq!( markdown, - "| ides | languages | + r#"| ides | languages | | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -| [\"Neovim\"](#) | [\"JavaScript\"](#) | -| | [\"Rust\"](#) | -" +| [Neovim](#) | [JavaScript](#) | +| | [Rust](#) | +"# + ); + } + + #[test] + fn should_handle_tools_with_custom_config() { + let input = " +tools: + ides: + - label: VSCode + color: 29a9f2 + - label: RustRover + icon: jetbrains + - label: RustRover + icon: jetbrains + color: feab02 +"; + let markdown = generate_toolbox(&input.to_string()); + assert_eq!( + markdown, + r#"| ides | +| --------------------------------------------------------------------------------------------------------------------------- | +| [VSCode](#) | +| [RustRover](#) | +| [RustRover](#) | +"# ); } } diff --git a/tests/main.rs b/tests/main.rs index 4a180f8..cbe8ca0 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -21,7 +21,8 @@ mod tests { fn should_generate_toolbox() -> Result<(), Box> { let config_file = assert_fs::NamedTempFile::new("config.yaml")?; config_file.write_str( - "tools: + " +tools: ides: - jetbrains - neovim @@ -34,13 +35,13 @@ mod tests { let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("--config").arg(config_file.path()); - cmd.assert().success().stdout(predicate::eq("| ides | languages | + cmd.assert().success().stdout(predicate::eq(r#"| ides | languages | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| [\"JetBrains\"](#) | [\"JavaScript\"](#) | -| [\"Neovim\"](#) | [\"C++\"](#) | -| | [\"Rust\"](#) | -| | [\"PHP\"](#) | -")); +| [JetBrains](#) | [JavaScript](#) | +| [Neovim](#) | [C++](#) | +| | [Rust](#) | +| | [PHP](#) | +"#)); Ok(()) } @@ -50,7 +51,8 @@ mod tests { let config_file = assert_fs::NamedTempFile::new("config.yaml")?; let readme_file = assert_fs::NamedTempFile::new("README.md")?; config_file.write_str( - "tools: + " +tools: ides: - jetbrains - neovim @@ -59,7 +61,8 @@ mod tests { - cplusplus", )?; readme_file.write_str( - "# Title + " +# Title ## Toolbox @@ -75,20 +78,21 @@ Lorem ipsum dolor sit amet cmd.assert().success(); readme_file.assert(predicate::eq( - "# Title + r#" +# Title ## Toolbox | ides | languages | | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| [\"JetBrains\"](#) | [\"JavaScript\"](#) | -| [\"Neovim\"](#) | [\"C++\"](#) | +| [JetBrains](#) | [JavaScript](#) | +| [Neovim](#) | [C++](#) | ## Other things Lorem ipsum dolor sit amet -", +"#, )); Ok(())