From d6dda5989c854ab33001f92f31341ed7572e4161 Mon Sep 17 00:00:00 2001 From: Calvin Date: Thu, 3 Jul 2025 08:28:05 +0300 Subject: [PATCH 1/9] chore: rename update_changelog.yml to update-changelog.yml for consistency --- .github/workflows/{update_changelog.yml => update-changelog.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{update_changelog.yml => update-changelog.yml} (100%) diff --git a/.github/workflows/update_changelog.yml b/.github/workflows/update-changelog.yml similarity index 100% rename from .github/workflows/update_changelog.yml rename to .github/workflows/update-changelog.yml From 30ddbdf4f17fc7e9f851b42eebbb8f2e50fc00ac Mon Sep 17 00:00:00 2001 From: Calvin Date: Thu, 3 Jul 2025 09:09:59 +0300 Subject: [PATCH 2/9] Remove the issue template installation script --- scripts/install_issue_template.sh | 73 ------------------------------- 1 file changed, 73 deletions(-) delete mode 100644 scripts/install_issue_template.sh diff --git a/scripts/install_issue_template.sh b/scripts/install_issue_template.sh deleted file mode 100644 index b5204ca..0000000 --- a/scripts/install_issue_template.sh +++ /dev/null @@ -1,73 +0,0 @@ -# ----------------------------------------------------------------------------- -# install_issue_template.sh -# -# This script automates the installation of GitHub issue templates into a local -# repository. It downloads a predefined set of issue template YAML files from a -# remote GitHub repository and places them in the `.github/ISSUE_TEMPLATE` -# directory of the current project. -# -# Usage: -# ./install_issue_template.sh -# -# Features: -# - Creates the `.github/ISSUE_TEMPLATE` directory if it does not exist. -# - Downloads multiple issue template files and a configuration file from a -# specified remote repository. -# - Ensures robust execution with strict error handling (`set -euo pipefail`). -# -# Requirements: -# - curl: Command-line tool for downloading files. -# -# Environment Variables: -# REPO_BASE_URL: Base URL of the remote repository containing the templates. -# -# Output: -# - Issue template files are saved to `.github/ISSUE_TEMPLATE/`. -# - Informational messages are printed to the console. -# -# Author: rafaeljohn9 -# ----------------------------------------------------------------------------- -#!/usr/bin/env bash - -set -euo pipefail - -REPO_BASE_URL="https://raw.githubusercontent.com/RafaelJohn9/gh-templates/main" - -TEMPLATE_DIR=".github/ISSUE_TEMPLATE" -ALL_FILES=( - "01-bug.yml" - "02-feature-request.yml" - "03-documentation.yml" - "04-community-collaboration.yml" - "05-developer-experience-feedback.yml" - "06-support-question.yml" - "07-test.yml" -) - -# If arguments are provided, use them as the list of files to download -if [[ $# -gt 0 ]]; then - FILES=() - for arg in "$@"; do - if [[ " ${ALL_FILES[*]} " == *" $arg "* ]]; then - FILES+=("$arg") - else - echo "⚠️ Warning: '$arg' is not a recognized template. Skipping." - fi - done - if [[ ${#FILES[@]} -eq 0 ]]; then - echo "❌ No valid templates specified. Exiting." - exit 1 - fi -else - FILES=("${ALL_FILES[@]}") -fi - -echo "📁 Creating template directory: $TEMPLATE_DIR" -mkdir -p "$TEMPLATE_DIR" - -for file in "${FILES[@]}"; do - echo "⬇️ Downloading $file" - curl -sSfL "$REPO_BASE_URL/templates/ISSUE_TEMPLATE/$file" -o "$TEMPLATE_DIR/$file" -done - -echo "✅ GitHub issue templates installed to '$TEMPLATE_DIR'." From 089d63bccc07d7ca993d1184afbfb00794e9b033 Mon Sep 17 00:00:00 2001 From: rafaeljohn9 Date: Thu, 3 Jul 2025 17:37:07 +0300 Subject: [PATCH 3/9] feat: add licenses Signed-off-by: rafaeljohn9 --- src/commands/issue.rs | 6 +- src/commands/license.rs | 199 ++++++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 20 ++-- src/main.rs | 30 ++++-- 4 files changed, 239 insertions(+), 16 deletions(-) create mode 100644 src/commands/license.rs diff --git a/src/commands/issue.rs b/src/commands/issue.rs index fe15f48..0390f52 100644 --- a/src/commands/issue.rs +++ b/src/commands/issue.rs @@ -14,7 +14,7 @@ const GITHUB_RAW_BASE: &str = const GITHUB_API_BASE: &str = "https://api.github.com/repos/rafaeljohn9/gh-templates/contents/templates"; -pub fn add(template: &str) -> anyhow::Result<()> { +pub fn add(template: &str, _extra_args: &[String]) -> anyhow::Result<()> { let fetcher = Fetcher::new(); let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); let dest_path = Path::new(OUTPUT_BASE_PATH) @@ -30,7 +30,7 @@ pub fn add(template: &str) -> anyhow::Result<()> { Ok(()) } -pub fn list() -> anyhow::Result<()> { +pub fn list(_extra_args: &[String]) -> anyhow::Result<()> { let fetcher = Fetcher::new(); let pb = ProgressBar::new_spinner(); @@ -88,7 +88,7 @@ pub fn list() -> anyhow::Result<()> { Ok(()) } -pub fn preview(template: &str) -> anyhow::Result<()> { +pub fn preview(template: &str, _extra_args: &[String]) -> anyhow::Result<()> { let fetcher = Fetcher::new(); let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); diff --git a/src/commands/license.rs b/src/commands/license.rs new file mode 100644 index 0000000..3b9a8cb --- /dev/null +++ b/src/commands/license.rs @@ -0,0 +1,199 @@ +use crate::utils::remote::Fetcher; +use anyhow::anyhow; +use indicatif::{ProgressBar, ProgressStyle}; +use std::path::Path; +use std::time::Duration; + +const GITHUB_LICENSES_API: &str = "https://api.github.com/licenses"; + +pub fn add(id: &str, _extra_args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); + + let license_data = fetcher.fetch_json(&url)?; + + let body = license_data + .get("body") + .and_then(|b| b.as_str()) + .ok_or_else(|| anyhow!("License body not found"))?; + + let filename = format!("LICENSE.{}", id.to_uppercase()); + let dest_path = Path::new(&filename); + + std::fs::write(dest_path, body)?; + + println!( + "\x1b[32m✓\x1b[0m Downloaded and added license: {}", + dest_path.display() + ); + Ok(()) +} + +pub fn list(_extra_args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) + .template("{spinner} {msg}") + .unwrap(), + ); + pb.enable_steady_tick(Duration::from_millis(100)); + pb.set_message("Fetching license list..."); + + let licenses = fetcher.fetch_json(GITHUB_LICENSES_API)?; + + pb.finish_with_message("Successfully fetched licenses"); + + if let Some(array) = licenses.as_array() { + println!("\x1b[32m✓\x1b[0m Available licenses:"); + for license in array { + if let (Some(key), Some(name)) = ( + license.get("key").and_then(|k| k.as_str()), + license.get("name").and_then(|n| n.as_str()), + ) { + println!(" \x1b[32m>\x1b[0m {:<15} {}", key, name); + } + } + } else { + println!("No licenses found."); + } + + Ok(()) +} + +pub fn preview(id: &str, extra_args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); + + let license_data = fetcher.fetch_json(&url)?; + + let name = license_data + .get("name") + .and_then(|n| n.as_str()) + .unwrap_or("Unknown License"); + + println!("\x1b[36mLicense Name:\x1b[0m {}\n", name); + + // Parse extra arguments + let mut description = false; + let mut permissions = false; + let mut limitations = false; + let mut conditions = false; + let mut details = false; + + for arg in extra_args { + let arg_str = arg.as_str(); + if arg_str.starts_with('-') && !arg_str.starts_with("--") { + // Handle combined short flags like -pdl + for c in arg_str.chars().skip(1) { + match c { + 'd' => description = true, + 'p' => permissions = true, + 'l' => limitations = true, + 'c' => conditions = true, + 'a' => details = true, + _ => { + return Err(anyhow!("Unknown flag: -{}", c)); + } + } + } + } else { + match arg_str { + "--description" | "-d" => description = true, + "--permissions" | "-p" => permissions = true, + "--limitations" | "-l" => limitations = true, + "--conditions" | "-c" => conditions = true, + "--details" | "-a" => details = true, + _ => { + return Err(anyhow!("Unknown argument: {}", arg)); + } + } + } + } + + if description { + if let Some(desc) = license_data.get("description").and_then(|d| d.as_str()) { + println!("\x1b[36mDescription:\x1b[0m"); + println!("{}", desc); + println!(); + } + } + + if permissions || details { + if let Some(perms) = license_data.get("permissions").and_then(|p| p.as_array()) { + println!("\x1b[32mPermissions:\x1b[0m"); + for perm in perms { + if let Some(perm_str) = perm.as_str() { + println!(" ✓ {}", format_permission(perm_str)); + } + } + println!(); + } + } + + if limitations || details { + if let Some(limits) = license_data.get("limitations").and_then(|l| l.as_array()) { + println!("\x1b[31mLimitations:\x1b[0m"); + for limit in limits { + if let Some(limit_str) = limit.as_str() { + println!(" ✗ {}", format_limitation(limit_str)); + } + } + println!(); + } + } + + if conditions || details { + if let Some(conds) = license_data.get("conditions").and_then(|c| c.as_array()) { + println!("\x1b[33mConditions:\x1b[0m"); + for condition in conds { + if let Some(cond_str) = condition.as_str() { + println!(" ! {}", format_condition(cond_str)); + } + } + println!(); + } + } + + // If no specific flags are set, show the full license text + if !permissions && !limitations && !conditions && !description && !details { + if let Some(body) = license_data.get("body").and_then(|b| b.as_str()) { + println!("\x1b[36mLicense Text:\x1b[0m"); + println!("{}", body); + } + } + + Ok(()) +} + +fn format_permission(perm: &str) -> String { + match perm { + "commercial-use" => "Commercial use".to_string(), + "modifications" => "Modify".to_string(), + "distribution" => "Distribute".to_string(), + "patent-use" => "Patent use".to_string(), + "private-use" => "Private use".to_string(), + _ => perm.replace('-', " "), + } +} + +fn format_limitation(limit: &str) -> String { + match limit { + "liability" => "Liability".to_string(), + "warranty" => "Warranty".to_string(), + "trademark-use" => "Trademark use".to_string(), + _ => limit.replace('-', " "), + } +} + +fn format_condition(cond: &str) -> String { + match cond { + "include-copyright" => "License and copyright notice".to_string(), + "document-changes" => "State changes".to_string(), + "disclose-source" => "Disclose source".to_string(), + "same-license" => "Same license".to_string(), + _ => cond.replace('-', " "), + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index ed89e93..85beb38 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,22 +1,30 @@ pub mod issue; +pub mod license; -pub fn dispatch_add(category: &str, template: &str) -> anyhow::Result<()> { +pub fn dispatch_add(category: &str, template: &str, extra_args: &[String]) -> anyhow::Result<()> { match category { - "issue" => issue::add(template), + "issue" => issue::add(template, extra_args), + "license" => license::add(template, extra_args), _ => Err(anyhow::anyhow!("Unknown category: {}", category)), } } -pub fn dispatch_list(category: &str) -> anyhow::Result<()> { +pub fn dispatch_list(category: &str, extra_args: &[String]) -> anyhow::Result<()> { match category { - "issue" => issue::list(), + "issue" => issue::list(extra_args), + "license" => license::list(extra_args), _ => Err(anyhow::anyhow!("Unknown category: {}", category)), } } -pub fn dispatch_preview(category: &str, template: &str) -> anyhow::Result<()> { +pub fn dispatch_preview( + category: &str, + template: &str, + extra_args: &[String], +) -> anyhow::Result<()> { match category { - "issue" => issue::preview(template), + "issue" => issue::preview(template, extra_args), + "license" => license::preview(template, extra_args), _ => Err(anyhow::anyhow!("Unknown category: {}", category)), } } diff --git a/src/main.rs b/src/main.rs index 4d5a23a..55bafa6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,12 +17,16 @@ enum Commands { category: String, /// Template name (e.g., bug, feature-request) template: String, + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + extra_args: Vec, }, /// List available templates in a category List { /// Category to list (e.g., issue, pr, ci, license, gitignore) category: String, + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + extra_args: Vec, }, /// Preview a template in a category @@ -31,23 +35,35 @@ enum Commands { category: String, /// Template name to preview (e.g., bug, feature-request) template: String, + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + extra_args: Vec, }, } - fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { - Commands::Add { category, template } => { - commands::dispatch_add(&category, &template)?; + Commands::Add { + category, + template, + extra_args, + } => { + commands::dispatch_add(&category, &template, &extra_args)?; } - Commands::List { category } => { - commands::dispatch_list(&category)?; + Commands::List { + category, + extra_args, + } => { + commands::dispatch_list(&category, &extra_args)?; } - Commands::Preview { category, template } => { - commands::dispatch_preview(&category, &template)?; + Commands::Preview { + category, + template, + extra_args, + } => { + commands::dispatch_preview(&category, &template, &extra_args)?; } } From 7cb1452087de2beaf82164d7a2a9ee56e4b30bbc Mon Sep 17 00:00:00 2001 From: rafaeljohn9 Date: Thu, 3 Jul 2025 19:51:25 +0300 Subject: [PATCH 4/9] indicatiff bump\n\nhttps://github.com/console-rs/indicatif/commit/f46ba0ac88ac0ce5f98f5b2bdef3a2a53618d257\n Signed-off-by: rafaeljohn9 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7cb3411..f2075b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ reqwest = { version = "0.11", default-features = false, features = ["json", "gzi serde_json = "1.0.140" syntect = "5.2.0" openssl = { version = "0.10", features = ["vendored"] } -indicatif = "0.17.12" +indicatif = "0.17.11" From e3b0e51ce647f5740c323b0d8e1240988c1ed9dd Mon Sep 17 00:00:00 2001 From: rafaeljohn9 Date: Thu, 3 Jul 2025 20:41:53 +0300 Subject: [PATCH 5/9] feat: #30 allow multiple downloads Signed-off-by: rafaeljohn9 --- src/commands/issue.rs | 75 +++++++++++++++++++++++++++++++++++------ src/commands/license.rs | 36 ++++++++++++-------- src/commands/mod.rs | 6 ++-- src/main.rs | 13 +++---- 4 files changed, 94 insertions(+), 36 deletions(-) diff --git a/src/commands/issue.rs b/src/commands/issue.rs index 0390f52..2113fba 100644 --- a/src/commands/issue.rs +++ b/src/commands/issue.rs @@ -14,19 +14,74 @@ const GITHUB_RAW_BASE: &str = const GITHUB_API_BASE: &str = "https://api.github.com/repos/rafaeljohn9/gh-templates/contents/templates"; -pub fn add(template: &str, _extra_args: &[String]) -> anyhow::Result<()> { +pub fn add(args: &[String]) -> anyhow::Result<()> { let fetcher = Fetcher::new(); - let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); - let dest_path = Path::new(OUTPUT_BASE_PATH) - .join(OUTPUT) - .join(format!("{}.yml", template)); - fetcher.fetch_to_file(&url, &dest_path)?; + if args.is_empty() { + return Err(anyhow::anyhow!( + "No issue template specified. Use --all to download all templates." + )); + } + + // Check if --all flag is provided + if args.contains(&"--all".to_string()) { + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) + .template("{spinner} {msg}") + .unwrap(), + ); + pb.enable_steady_tick(Duration::from_millis(100)); + pb.set_message("Fetching all templates..."); + + let url = format!("{}/issue-templates", GITHUB_API_BASE); + let entries = fetcher.fetch_json(&url)?; + let mut downloaded_templates = Vec::new(); + + if let Some(array) = entries.as_array() { + for entry in array { + if let Some(name) = entry.get("name").and_then(|n| n.as_str()) { + let template_name = match name.rfind('.') { + Some(idx) => &name[..idx], + None => name, + }; + + pb.set_message(format!("Downloading template: {}", template_name)); + + let url = format!("{}/issue-templates/{}", GITHUB_RAW_BASE, name); + let dest_path = Path::new(OUTPUT_BASE_PATH).join(OUTPUT).join(name); + + fetcher.fetch_to_file(&url, &dest_path)?; + downloaded_templates.push(format!("{}.yml", template_name)); + } + } + } + pb.finish_and_clear(); + println!( + "\x1b[32m✓\x1b[0m Downloaded all issue templates to {}/{}", + OUTPUT_BASE_PATH, OUTPUT + ); + for template in downloaded_templates { + println!(" \x1b[32m>\x1b[0m {}", template); + } + } else { + // Download specified templates + for template in args { + let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); + let dest_path = Path::new(OUTPUT_BASE_PATH) + .join(OUTPUT) + .join(format!("{}.yml", template)); + + fetcher.fetch_to_file(&url, &dest_path)?; + + println!( + "\x1b[32m✓\x1b[0m Downloaded and added issue template: {}", + dest_path.display() + ); + } + } - println!( - "\x1b[32m✓\x1b[0m Downloaded and added issue template: {}", - dest_path.display() - ); Ok(()) } diff --git a/src/commands/license.rs b/src/commands/license.rs index 3b9a8cb..2cd7fbd 100644 --- a/src/commands/license.rs +++ b/src/commands/license.rs @@ -6,26 +6,34 @@ use std::time::Duration; const GITHUB_LICENSES_API: &str = "https://api.github.com/licenses"; -pub fn add(id: &str, _extra_args: &[String]) -> anyhow::Result<()> { +pub fn add(args: &[String]) -> anyhow::Result<()> { + if args.is_empty() { + return Err(anyhow!("At least one license ID is required")); + } + let fetcher = Fetcher::new(); - let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); - let license_data = fetcher.fetch_json(&url)?; + for id in args { + let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); - let body = license_data - .get("body") - .and_then(|b| b.as_str()) - .ok_or_else(|| anyhow!("License body not found"))?; + let license_data = fetcher.fetch_json(&url)?; - let filename = format!("LICENSE.{}", id.to_uppercase()); - let dest_path = Path::new(&filename); + let body = license_data + .get("body") + .and_then(|b| b.as_str()) + .ok_or_else(|| anyhow!("License body not found for {}", id))?; - std::fs::write(dest_path, body)?; + let filename = format!("LICENSE.{}", id.to_uppercase()); + let dest_path = Path::new(&filename); + + std::fs::write(dest_path, body)?; + + println!( + "\x1b[32m✓\x1b[0m Downloaded and added license: {}", + dest_path.display() + ); + } - println!( - "\x1b[32m✓\x1b[0m Downloaded and added license: {}", - dest_path.display() - ); Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 85beb38..3e74f6b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,10 +1,10 @@ pub mod issue; pub mod license; -pub fn dispatch_add(category: &str, template: &str, extra_args: &[String]) -> anyhow::Result<()> { +pub fn dispatch_add(category: &str, args: &[String]) -> anyhow::Result<()> { match category { - "issue" => issue::add(template, extra_args), - "license" => license::add(template, extra_args), + "issue" => issue::add(args), + "license" => license::add(args), _ => Err(anyhow::anyhow!("Unknown category: {}", category)), } } diff --git a/src/main.rs b/src/main.rs index 55bafa6..9d871ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,10 +15,9 @@ enum Commands { Add { /// Template category (e.g., issue, pr, ci, license, gitignore) category: String, - /// Template name (e.g., bug, feature-request) - template: String, + /// Template name(s) (e.g., bug, feature-request) #[arg(trailing_var_arg = true, allow_hyphen_values = true)] - extra_args: Vec, + args: Vec, }, /// List available templates in a category @@ -43,12 +42,8 @@ fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { - Commands::Add { - category, - template, - extra_args, - } => { - commands::dispatch_add(&category, &template, &extra_args)?; + Commands::Add { category, args } => { + commands::dispatch_add(&category, &args)?; } Commands::List { From dc062fd7a9f3c91531ee0227a17463392c416030 Mon Sep 17 00:00:00 2001 From: rafaeljohn9 Date: Thu, 3 Jul 2025 22:26:31 +0300 Subject: [PATCH 6/9] fix: surpress dead code warning in remote.rs --- src/utils/remote.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/remote.rs b/src/utils/remote.rs index 052a8e3..50a7177 100644 --- a/src/utils/remote.rs +++ b/src/utils/remote.rs @@ -78,6 +78,7 @@ impl Fetcher { } /// Download binary content and save to file + #[allow(dead_code)] pub fn download_to_file(&self, url: &str, output_path: &Path) -> anyhow::Result<()> { let response = self .client From e0feebf446b3c6e6bb14a93ff7d1d8d8a005b480 Mon Sep 17 00:00:00 2001 From: rafaeljohn9 Date: Thu, 3 Jul 2025 22:27:08 +0300 Subject: [PATCH 7/9] refactor: issue.rs to a dir --- src/commands/issue/add.rs | 81 +++++++++++++++++++++++++++++++++++ src/commands/issue/list.rs | 65 ++++++++++++++++++++++++++++ src/commands/issue/mod.rs | 16 +++++++ src/commands/issue/preview.rs | 14 ++++++ 4 files changed, 176 insertions(+) create mode 100644 src/commands/issue/add.rs create mode 100644 src/commands/issue/list.rs create mode 100644 src/commands/issue/mod.rs create mode 100644 src/commands/issue/preview.rs diff --git a/src/commands/issue/add.rs b/src/commands/issue/add.rs new file mode 100644 index 0000000..b88a3d6 --- /dev/null +++ b/src/commands/issue/add.rs @@ -0,0 +1,81 @@ +use std::path::Path; +use std::time::Duration; + +use indicatif::{ProgressBar, ProgressStyle}; + +use super::{GITHUB_API_BASE, GITHUB_RAW_BASE}; +use crate::utils::remote::Fetcher; + +const OUTPUT_BASE_PATH: &str = ".github"; +const OUTPUT: &str = "ISSUE_TEMPLATE"; + +pub fn add(args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + + if args.is_empty() { + return Err(anyhow::anyhow!( + "No issue template specified. Use --all to download all templates." + )); + } + + // Check if --all flag is provided + if args.contains(&"--all".to_string()) { + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) + .template("{spinner} {msg}") + .unwrap(), + ); + pb.enable_steady_tick(Duration::from_millis(100)); + pb.set_message("Fetching all templates..."); + + let url = format!("{}/issue-templates", GITHUB_API_BASE); + let entries = fetcher.fetch_json(&url)?; + let mut downloaded_templates = Vec::new(); + + if let Some(array) = entries.as_array() { + for entry in array { + if let Some(name) = entry.get("name").and_then(|n| n.as_str()) { + let template_name = match name.rfind('.') { + Some(idx) => &name[..idx], + None => name, + }; + + pb.set_message(format!("Downloading template: {}", template_name)); + + let url = format!("{}/issue-templates/{}", GITHUB_RAW_BASE, name); + let dest_path = Path::new(OUTPUT_BASE_PATH).join(OUTPUT).join(name); + + fetcher.fetch_to_file(&url, &dest_path)?; + downloaded_templates.push(format!("{}.yml", template_name)); + } + } + } + pb.finish_and_clear(); + println!( + "\x1b[32m✓\x1b[0m Downloaded all issue templates to {}/{}", + OUTPUT_BASE_PATH, OUTPUT + ); + for template in downloaded_templates { + println!(" \x1b[32m>\x1b[0m {}", template); + } + } else { + // Download specified templates + for template in args { + let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); + let dest_path = Path::new(OUTPUT_BASE_PATH) + .join(OUTPUT) + .join(format!("{}.yml", template)); + + fetcher.fetch_to_file(&url, &dest_path)?; + + println!( + "\x1b[32m✓\x1b[0m Downloaded and added issue template: {}", + dest_path.display() + ); + } + } + + Ok(()) +} diff --git a/src/commands/issue/list.rs b/src/commands/issue/list.rs new file mode 100644 index 0000000..fecc99e --- /dev/null +++ b/src/commands/issue/list.rs @@ -0,0 +1,65 @@ +use std::time::Duration; + +use indicatif::{ProgressBar, ProgressStyle}; + +use super::{GITHUB_API_BASE, GITHUB_RAW_BASE}; +use crate::utils::get_comment; +use crate::utils::remote::Fetcher; + +pub fn list(_extra_args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) + .template("{spinner} {msg}") + .unwrap(), + ); + pb.enable_steady_tick(Duration::from_millis(100)); + pb.set_message("Fetching template list..."); + + let url = format!("{}/issue-templates", GITHUB_API_BASE); + let entries = fetcher.fetch_json(&url)?; + let mut templates = Vec::new(); + + if let Some(array) = entries.as_array() { + for entry in array { + if let Some(name) = entry.get("name").and_then(|n| n.as_str()) { + let (name_without_ext, extension) = match name.rfind('.') { + Some(idx) => (&name[..idx], &name[idx + 1..]), + None => (name, ""), + }; + + pb.set_message(format!("Reading template: {}", name_without_ext)); + + // Fetch the template file to read the first line comment + let file_url = format!("{}/issue-templates/{}", GITHUB_RAW_BASE, name); + let comment = match fetcher.fetch_content(&file_url) { + Ok(text) => text + .lines() + .next() + .and_then(|line| get_comment::extract_comment(line, extension)), + _ => None, + }; + + templates.push((name_without_ext.to_string(), comment)); + } + } + } + + pb.finish_with_message("Successfully fetched templates"); + + if templates.is_empty() { + println!("No issue templates found."); + } else { + println!("\x1b[32m✓\x1b[0m Available issue templates:"); + for (name, description_opt) in templates { + match description_opt { + Some(description) => println!(" \x1b[32m>\x1b[0m {:<12} {}", name, description), + None => println!(" {}", name), + } + } + } + Ok(()) +} diff --git a/src/commands/issue/mod.rs b/src/commands/issue/mod.rs new file mode 100644 index 0000000..86b1aad --- /dev/null +++ b/src/commands/issue/mod.rs @@ -0,0 +1,16 @@ +// Global constants - these can stay in the main module file +const GITHUB_RAW_BASE: &str = + "https://raw.githubusercontent.com/rafaeljohn9/gh-templates/main/templates"; +const GITHUB_API_BASE: &str = + "https://api.github.com/repos/rafaeljohn9/gh-templates/contents/templates"; + +// Re-export submodules +pub mod add; +pub mod list; +pub mod preview; + +// Re-export the main functions for backward compatibility +pub use add::add; +pub use list::list; +pub use preview::preview; + diff --git a/src/commands/issue/preview.rs b/src/commands/issue/preview.rs new file mode 100644 index 0000000..d2cc63b --- /dev/null +++ b/src/commands/issue/preview.rs @@ -0,0 +1,14 @@ +use super::GITHUB_RAW_BASE; +use crate::utils::pretty_print; +use crate::utils::remote::Fetcher; + +pub fn preview(template: &str, _extra_args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); + + println!("\x1b[32m✓\x1b[0m Previewing issue template: {}", template); + + let content = fetcher.fetch_content(&url)?; + pretty_print::print_highlighted("yml", &content); + Ok(()) +} From 16655c561a12b6dfe041ab155df0354601981f5b Mon Sep 17 00:00:00 2001 From: rafaeljohn9 Date: Thu, 3 Jul 2025 22:27:21 +0300 Subject: [PATCH 8/9] license: issue.rs to a dir --- src/commands/license/add.rs | 36 +++++++++ src/commands/license/list.rs | 39 +++++++++ src/commands/license/mod.rs | 12 +++ src/commands/license/preview.rs | 139 ++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 src/commands/license/add.rs create mode 100644 src/commands/license/list.rs create mode 100644 src/commands/license/mod.rs create mode 100644 src/commands/license/preview.rs diff --git a/src/commands/license/add.rs b/src/commands/license/add.rs new file mode 100644 index 0000000..653c3f8 --- /dev/null +++ b/src/commands/license/add.rs @@ -0,0 +1,36 @@ +use crate::utils::remote::Fetcher; +use anyhow::anyhow; +use std::path::Path; + +use super::GITHUB_LICENSES_API; + +pub fn add(args: &[String]) -> anyhow::Result<()> { + if args.is_empty() { + return Err(anyhow!("At least one license ID is required")); + } + + let fetcher = Fetcher::new(); + + for id in args { + let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); + + let license_data = fetcher.fetch_json(&url)?; + + let body = license_data + .get("body") + .and_then(|b| b.as_str()) + .ok_or_else(|| anyhow!("License body not found for {}", id))?; + + let filename = format!("LICENSE.{}", id.to_uppercase()); + let dest_path = Path::new(&filename); + + std::fs::write(dest_path, body)?; + + println!( + "\x1b[32m✓\x1b[0m Downloaded and added license: {}", + dest_path.display() + ); + } + + Ok(()) +} diff --git a/src/commands/license/list.rs b/src/commands/license/list.rs new file mode 100644 index 0000000..7e3b559 --- /dev/null +++ b/src/commands/license/list.rs @@ -0,0 +1,39 @@ +use crate::utils::remote::Fetcher; +use indicatif::{ProgressBar, ProgressStyle}; +use std::time::Duration; + +use super::GITHUB_LICENSES_API; + +pub fn list(_extra_args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + + let pb = ProgressBar::new_spinner(); + pb.set_style( + ProgressStyle::default_spinner() + .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) + .template("{spinner} {msg}") + .unwrap(), + ); + pb.enable_steady_tick(Duration::from_millis(100)); + pb.set_message("Fetching license list..."); + + let licenses = fetcher.fetch_json(GITHUB_LICENSES_API)?; + + pb.finish_with_message("Successfully fetched licenses"); + + if let Some(array) = licenses.as_array() { + println!("\x1b[32m✓\x1b[0m Available licenses:"); + for license in array { + if let (Some(key), Some(name)) = ( + license.get("key").and_then(|k| k.as_str()), + license.get("name").and_then(|n| n.as_str()), + ) { + println!(" \x1b[32m>\x1b[0m {:<15} {}", key, name); + } + } + } else { + println!("No licenses found."); + } + + Ok(()) +} diff --git a/src/commands/license/mod.rs b/src/commands/license/mod.rs new file mode 100644 index 0000000..df11e39 --- /dev/null +++ b/src/commands/license/mod.rs @@ -0,0 +1,12 @@ +// Global constants - these can stay in the main module file +pub const GITHUB_LICENSES_API: &str = "https://api.github.com/licenses"; + +// Re-export submodules +pub mod add; +pub mod list; +pub mod preview; + +// Re-export the main functions for backward compatibility +pub use add::add; +pub use list::list; +pub use preview::preview; diff --git a/src/commands/license/preview.rs b/src/commands/license/preview.rs new file mode 100644 index 0000000..7a6ab08 --- /dev/null +++ b/src/commands/license/preview.rs @@ -0,0 +1,139 @@ +use crate::utils::remote::Fetcher; +use anyhow::anyhow; + +use super::GITHUB_LICENSES_API; + +pub fn preview(id: &str, extra_args: &[String]) -> anyhow::Result<()> { + let fetcher = Fetcher::new(); + let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); + + let license_data = fetcher.fetch_json(&url)?; + + let name = license_data + .get("name") + .and_then(|n| n.as_str()) + .unwrap_or("Unknown License"); + + println!("\x1b[36mLicense Name:\x1b[0m {}\n", name); + + // Parse extra arguments + let mut description = false; + let mut permissions = false; + let mut limitations = false; + let mut conditions = false; + let mut details = false; + + for arg in extra_args { + let arg_str = arg.as_str(); + if arg_str.starts_with('-') && !arg_str.starts_with("--") { + // Handle combined short flags like -pdl + for c in arg_str.chars().skip(1) { + match c { + 'd' => description = true, + 'p' => permissions = true, + 'l' => limitations = true, + 'c' => conditions = true, + 'a' => details = true, + _ => { + return Err(anyhow!("Unknown flag: -{}", c)); + } + } + } + } else { + match arg_str { + "--description" | "-d" => description = true, + "--permissions" | "-p" => permissions = true, + "--limitations" | "-l" => limitations = true, + "--conditions" | "-c" => conditions = true, + "--details" | "-a" => details = true, + _ => { + return Err(anyhow!("Unknown argument: {}", arg)); + } + } + } + } + + if description { + if let Some(desc) = license_data.get("description").and_then(|d| d.as_str()) { + println!("\x1b[36mDescription:\x1b[0m"); + println!("{}", desc); + println!(); + } + } + + if permissions || details { + if let Some(perms) = license_data.get("permissions").and_then(|p| p.as_array()) { + println!("\x1b[32mPermissions:\x1b[0m"); + for perm in perms { + if let Some(perm_str) = perm.as_str() { + println!(" ✓ {}", format_permission(perm_str)); + } + } + println!(); + } + } + + if limitations || details { + if let Some(limits) = license_data.get("limitations").and_then(|l| l.as_array()) { + println!("\x1b[31mLimitations:\x1b[0m"); + for limit in limits { + if let Some(limit_str) = limit.as_str() { + println!(" ✗ {}", format_limitation(limit_str)); + } + } + println!(); + } + } + + if conditions || details { + if let Some(conds) = license_data.get("conditions").and_then(|c| c.as_array()) { + println!("\x1b[33mConditions:\x1b[0m"); + for condition in conds { + if let Some(cond_str) = condition.as_str() { + println!(" ! {}", format_condition(cond_str)); + } + } + println!(); + } + } + + // If no specific flags are set, show the full license text + if !permissions && !limitations && !conditions && !description && !details { + if let Some(body) = license_data.get("body").and_then(|b| b.as_str()) { + println!("\x1b[36mLicense Text:\x1b[0m"); + println!("{}", body); + } + } + + Ok(()) +} + +fn format_permission(perm: &str) -> String { + match perm { + "commercial-use" => "Commercial use".to_string(), + "modifications" => "Modify".to_string(), + "distribution" => "Distribute".to_string(), + "patent-use" => "Patent use".to_string(), + "private-use" => "Private use".to_string(), + _ => perm.replace('-', " "), + } +} + +fn format_limitation(limit: &str) -> String { + match limit { + "liability" => "Liability".to_string(), + "warranty" => "Warranty".to_string(), + "trademark-use" => "Trademark use".to_string(), + _ => limit.replace('-', " "), + } +} + +fn format_condition(cond: &str) -> String { + match cond { + "include-copyright" => "License and copyright notice".to_string(), + "document-changes" => "State changes".to_string(), + "disclose-source" => "Disclose source".to_string(), + "same-license" => "Same license".to_string(), + _ => cond.replace('-', " "), + } +} From a4cf38e771f9124e57fd19be989d80f33d4cf3eb Mon Sep 17 00:00:00 2001 From: rafaeljohn9 Date: Thu, 3 Jul 2025 22:27:40 +0300 Subject: [PATCH 9/9] refactor: rm issue.rs and license.rs --- src/commands/issue.rs | 155 ------------------------------ src/commands/license.rs | 207 ---------------------------------------- 2 files changed, 362 deletions(-) delete mode 100644 src/commands/issue.rs delete mode 100644 src/commands/license.rs diff --git a/src/commands/issue.rs b/src/commands/issue.rs deleted file mode 100644 index 2113fba..0000000 --- a/src/commands/issue.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::path::Path; -use std::time::Duration; - -use indicatif::{ProgressBar, ProgressStyle}; - -use crate::utils::get_comment; -use crate::utils::pretty_print; -use crate::utils::remote::Fetcher; - -const OUTPUT_BASE_PATH: &str = ".github"; -const OUTPUT: &str = "ISSUE_TEMPLATE"; -const GITHUB_RAW_BASE: &str = - "https://raw.githubusercontent.com/rafaeljohn9/gh-templates/main/templates"; -const GITHUB_API_BASE: &str = - "https://api.github.com/repos/rafaeljohn9/gh-templates/contents/templates"; - -pub fn add(args: &[String]) -> anyhow::Result<()> { - let fetcher = Fetcher::new(); - - if args.is_empty() { - return Err(anyhow::anyhow!( - "No issue template specified. Use --all to download all templates." - )); - } - - // Check if --all flag is provided - if args.contains(&"--all".to_string()) { - let pb = ProgressBar::new_spinner(); - pb.set_style( - ProgressStyle::default_spinner() - .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) - .template("{spinner} {msg}") - .unwrap(), - ); - pb.enable_steady_tick(Duration::from_millis(100)); - pb.set_message("Fetching all templates..."); - - let url = format!("{}/issue-templates", GITHUB_API_BASE); - let entries = fetcher.fetch_json(&url)?; - let mut downloaded_templates = Vec::new(); - - if let Some(array) = entries.as_array() { - for entry in array { - if let Some(name) = entry.get("name").and_then(|n| n.as_str()) { - let template_name = match name.rfind('.') { - Some(idx) => &name[..idx], - None => name, - }; - - pb.set_message(format!("Downloading template: {}", template_name)); - - let url = format!("{}/issue-templates/{}", GITHUB_RAW_BASE, name); - let dest_path = Path::new(OUTPUT_BASE_PATH).join(OUTPUT).join(name); - - fetcher.fetch_to_file(&url, &dest_path)?; - downloaded_templates.push(format!("{}.yml", template_name)); - } - } - } - pb.finish_and_clear(); - println!( - "\x1b[32m✓\x1b[0m Downloaded all issue templates to {}/{}", - OUTPUT_BASE_PATH, OUTPUT - ); - for template in downloaded_templates { - println!(" \x1b[32m>\x1b[0m {}", template); - } - } else { - // Download specified templates - for template in args { - let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); - let dest_path = Path::new(OUTPUT_BASE_PATH) - .join(OUTPUT) - .join(format!("{}.yml", template)); - - fetcher.fetch_to_file(&url, &dest_path)?; - - println!( - "\x1b[32m✓\x1b[0m Downloaded and added issue template: {}", - dest_path.display() - ); - } - } - - Ok(()) -} - -pub fn list(_extra_args: &[String]) -> anyhow::Result<()> { - let fetcher = Fetcher::new(); - - let pb = ProgressBar::new_spinner(); - pb.set_style( - ProgressStyle::default_spinner() - .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) - .template("{spinner} {msg}") - .unwrap(), - ); - pb.enable_steady_tick(Duration::from_millis(100)); - pb.set_message("Fetching template list..."); - - let url = format!("{}/issue-templates", GITHUB_API_BASE); - let entries = fetcher.fetch_json(&url)?; - let mut templates = Vec::new(); - - if let Some(array) = entries.as_array() { - for entry in array { - if let Some(name) = entry.get("name").and_then(|n| n.as_str()) { - let (name_without_ext, extension) = match name.rfind('.') { - Some(idx) => (&name[..idx], &name[idx + 1..]), - None => (name, ""), - }; - - pb.set_message(format!("Reading template: {}", name_without_ext)); - - // Fetch the template file to read the first line comment - let file_url = format!("{}/issue-templates/{}", GITHUB_RAW_BASE, name); - let comment = match fetcher.fetch_content(&file_url) { - Ok(text) => text - .lines() - .next() - .and_then(|line| get_comment::extract_comment(line, extension)), - _ => None, - }; - - templates.push((name_without_ext.to_string(), comment)); - } - } - } - - pb.finish_with_message("Successfully fetched templates"); - - if templates.is_empty() { - println!("No issue templates found."); - } else { - println!("\x1b[32m✓\x1b[0m Available issue templates:"); - for (name, description_opt) in templates { - match description_opt { - Some(description) => println!(" \x1b[32m>\x1b[0m {:<12} {}", name, description), - None => println!(" {}", name), - } - } - } - Ok(()) -} - -pub fn preview(template: &str, _extra_args: &[String]) -> anyhow::Result<()> { - let fetcher = Fetcher::new(); - let url = format!("{}/issue-templates/{}.yml", GITHUB_RAW_BASE, template); - - println!("\x1b[32m✓\x1b[0m Previewing issue template: {}", template); - - let content = fetcher.fetch_content(&url)?; - pretty_print::print_highlighted("yml", &content); - Ok(()) -} diff --git a/src/commands/license.rs b/src/commands/license.rs deleted file mode 100644 index 2cd7fbd..0000000 --- a/src/commands/license.rs +++ /dev/null @@ -1,207 +0,0 @@ -use crate::utils::remote::Fetcher; -use anyhow::anyhow; -use indicatif::{ProgressBar, ProgressStyle}; -use std::path::Path; -use std::time::Duration; - -const GITHUB_LICENSES_API: &str = "https://api.github.com/licenses"; - -pub fn add(args: &[String]) -> anyhow::Result<()> { - if args.is_empty() { - return Err(anyhow!("At least one license ID is required")); - } - - let fetcher = Fetcher::new(); - - for id in args { - let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); - - let license_data = fetcher.fetch_json(&url)?; - - let body = license_data - .get("body") - .and_then(|b| b.as_str()) - .ok_or_else(|| anyhow!("License body not found for {}", id))?; - - let filename = format!("LICENSE.{}", id.to_uppercase()); - let dest_path = Path::new(&filename); - - std::fs::write(dest_path, body)?; - - println!( - "\x1b[32m✓\x1b[0m Downloaded and added license: {}", - dest_path.display() - ); - } - - Ok(()) -} - -pub fn list(_extra_args: &[String]) -> anyhow::Result<()> { - let fetcher = Fetcher::new(); - - let pb = ProgressBar::new_spinner(); - pb.set_style( - ProgressStyle::default_spinner() - .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]) - .template("{spinner} {msg}") - .unwrap(), - ); - pb.enable_steady_tick(Duration::from_millis(100)); - pb.set_message("Fetching license list..."); - - let licenses = fetcher.fetch_json(GITHUB_LICENSES_API)?; - - pb.finish_with_message("Successfully fetched licenses"); - - if let Some(array) = licenses.as_array() { - println!("\x1b[32m✓\x1b[0m Available licenses:"); - for license in array { - if let (Some(key), Some(name)) = ( - license.get("key").and_then(|k| k.as_str()), - license.get("name").and_then(|n| n.as_str()), - ) { - println!(" \x1b[32m>\x1b[0m {:<15} {}", key, name); - } - } - } else { - println!("No licenses found."); - } - - Ok(()) -} - -pub fn preview(id: &str, extra_args: &[String]) -> anyhow::Result<()> { - let fetcher = Fetcher::new(); - let url = format!("{}/{}", GITHUB_LICENSES_API, id.to_lowercase()); - - let license_data = fetcher.fetch_json(&url)?; - - let name = license_data - .get("name") - .and_then(|n| n.as_str()) - .unwrap_or("Unknown License"); - - println!("\x1b[36mLicense Name:\x1b[0m {}\n", name); - - // Parse extra arguments - let mut description = false; - let mut permissions = false; - let mut limitations = false; - let mut conditions = false; - let mut details = false; - - for arg in extra_args { - let arg_str = arg.as_str(); - if arg_str.starts_with('-') && !arg_str.starts_with("--") { - // Handle combined short flags like -pdl - for c in arg_str.chars().skip(1) { - match c { - 'd' => description = true, - 'p' => permissions = true, - 'l' => limitations = true, - 'c' => conditions = true, - 'a' => details = true, - _ => { - return Err(anyhow!("Unknown flag: -{}", c)); - } - } - } - } else { - match arg_str { - "--description" | "-d" => description = true, - "--permissions" | "-p" => permissions = true, - "--limitations" | "-l" => limitations = true, - "--conditions" | "-c" => conditions = true, - "--details" | "-a" => details = true, - _ => { - return Err(anyhow!("Unknown argument: {}", arg)); - } - } - } - } - - if description { - if let Some(desc) = license_data.get("description").and_then(|d| d.as_str()) { - println!("\x1b[36mDescription:\x1b[0m"); - println!("{}", desc); - println!(); - } - } - - if permissions || details { - if let Some(perms) = license_data.get("permissions").and_then(|p| p.as_array()) { - println!("\x1b[32mPermissions:\x1b[0m"); - for perm in perms { - if let Some(perm_str) = perm.as_str() { - println!(" ✓ {}", format_permission(perm_str)); - } - } - println!(); - } - } - - if limitations || details { - if let Some(limits) = license_data.get("limitations").and_then(|l| l.as_array()) { - println!("\x1b[31mLimitations:\x1b[0m"); - for limit in limits { - if let Some(limit_str) = limit.as_str() { - println!(" ✗ {}", format_limitation(limit_str)); - } - } - println!(); - } - } - - if conditions || details { - if let Some(conds) = license_data.get("conditions").and_then(|c| c.as_array()) { - println!("\x1b[33mConditions:\x1b[0m"); - for condition in conds { - if let Some(cond_str) = condition.as_str() { - println!(" ! {}", format_condition(cond_str)); - } - } - println!(); - } - } - - // If no specific flags are set, show the full license text - if !permissions && !limitations && !conditions && !description && !details { - if let Some(body) = license_data.get("body").and_then(|b| b.as_str()) { - println!("\x1b[36mLicense Text:\x1b[0m"); - println!("{}", body); - } - } - - Ok(()) -} - -fn format_permission(perm: &str) -> String { - match perm { - "commercial-use" => "Commercial use".to_string(), - "modifications" => "Modify".to_string(), - "distribution" => "Distribute".to_string(), - "patent-use" => "Patent use".to_string(), - "private-use" => "Private use".to_string(), - _ => perm.replace('-', " "), - } -} - -fn format_limitation(limit: &str) -> String { - match limit { - "liability" => "Liability".to_string(), - "warranty" => "Warranty".to_string(), - "trademark-use" => "Trademark use".to_string(), - _ => limit.replace('-', " "), - } -} - -fn format_condition(cond: &str) -> String { - match cond { - "include-copyright" => "License and copyright notice".to_string(), - "document-changes" => "State changes".to_string(), - "disclose-source" => "Disclose source".to_string(), - "same-license" => "Same license".to_string(), - _ => cond.replace('-', " "), - } -}