bhcli

A TUI for chatting on LE PHP Chats
git clone https://git.dasho.dev/bhcli.git
Log | Files | Refs | README

github.rs (11848B)


      1 use crate::chatops::{ChatCommand, ChatOpError, ChatOpResult, CommandContext};
      2 use std::process::Command;
      3 
      4 /// GitHub repository information
      5 pub struct GitHubCommand;
      6 
      7 impl ChatCommand for GitHubCommand {
      8    fn name(&self) -> &'static str {
      9        "github"
     10    }
     11    fn description(&self) -> &'static str {
     12        "Get GitHub repository information"
     13    }
     14    fn usage(&self) -> &'static str {
     15        "/github <user>/<repo> [issues|latest|file <path>]"
     16    }
     17    fn aliases(&self) -> Vec<&'static str> {
     18        vec!["gh"]
     19    }
     20 
     21    fn execute(
     22        &self,
     23        args: Vec<String>,
     24        _context: &CommandContext,
     25    ) -> Result<ChatOpResult, ChatOpError> {
     26        if args.is_empty() {
     27            return Err(ChatOpError::MissingArguments(
     28                "Please specify a repository (user/repo)".to_string(),
     29            ));
     30        }
     31 
     32        let repo = &args[0];
     33        if !repo.contains('/') {
     34            return Err(ChatOpError::InvalidSyntax(
     35                "Repository must be in format 'user/repo'".to_string(),
     36            ));
     37        }
     38 
     39        let action = args.get(1).map(|s| s.as_str()).unwrap_or("info");
     40 
     41        match action {
     42            "issues" => Ok(ChatOpResult::Message(format!(
     43                "🐛 GitHub Issues for {}: https://github.com/{}/issues",
     44                repo, repo
     45            ))),
     46            "latest" => Ok(ChatOpResult::Message(format!(
     47                "🏷️ Latest Release for {}: https://github.com/{}/releases/latest",
     48                repo, repo
     49            ))),
     50            "file" => {
     51                if args.len() < 3 {
     52                    return Err(ChatOpError::MissingArguments(
     53                        "Please specify file path".to_string(),
     54                    ));
     55                }
     56                let file_path = &args[2];
     57                Ok(ChatOpResult::Message(format!(
     58                    "📄 File {}: https://github.com/{}/blob/main/{}",
     59                    file_path, repo, file_path
     60                )))
     61            }
     62            _ => {
     63                // Basic repo info
     64                let info = vec![
     65                    format!("📦 **GitHub Repository: {}**", repo),
     66                    format!("🔗 URL: https://github.com/{}", repo),
     67                    format!("📊 Issues: https://github.com/{}/issues", repo),
     68                    format!("🏷️ Releases: https://github.com/{}/releases", repo),
     69                    format!("📋 README: https://github.com/{}#readme", repo),
     70                ];
     71                Ok(ChatOpResult::Block(info))
     72            }
     73        }
     74    }
     75 }
     76 
     77 /// GitHub Gist creation
     78 pub struct GistCommand;
     79 
     80 impl ChatCommand for GistCommand {
     81    fn name(&self) -> &'static str {
     82        "gist"
     83    }
     84    fn description(&self) -> &'static str {
     85        "Create a GitHub Gist (requires gh CLI)"
     86    }
     87    fn usage(&self) -> &'static str {
     88        "/gist <code>"
     89    }
     90 
     91    fn execute(
     92        &self,
     93        args: Vec<String>,
     94        _context: &CommandContext,
     95    ) -> Result<ChatOpResult, ChatOpError> {
     96        if args.is_empty() {
     97            return Err(ChatOpError::MissingArguments(
     98                "Please specify code to create a gist".to_string(),
     99            ));
    100        }
    101 
    102        let code = args.join(" ");
    103 
    104        // Try using GitHub CLI if available
    105        match Command::new("gh")
    106            .args(&["gist", "create", "-"])
    107            .stdin(std::process::Stdio::piped())
    108            .stdout(std::process::Stdio::piped())
    109            .spawn()
    110        {
    111            Ok(mut child) => {
    112                use std::io::Write;
    113                if let Some(stdin) = child.stdin.as_mut() {
    114                    let _ = stdin.write_all(code.as_bytes());
    115                }
    116 
    117                match child.wait_with_output() {
    118                    Ok(output) => {
    119                        if output.status.success() {
    120                            let gist_url =
    121                                String::from_utf8_lossy(&output.stdout).trim().to_string();
    122                            Ok(ChatOpResult::Message(format!(
    123                                "📝 Gist created: {}",
    124                                gist_url
    125                            )))
    126                        } else {
    127                            Ok(ChatOpResult::Message("📝 Failed to create gist. Make sure you're logged in with `gh auth login`".to_string()))
    128                        }
    129                    }
    130                    Err(_) => Ok(ChatOpResult::Message(
    131                        "📝 Failed to create gist".to_string(),
    132                    )),
    133                }
    134            }
    135            Err(_) => {
    136                // Fallback - just show the manual gist creation URL
    137                Ok(ChatOpResult::Message(format!(
    138                    "📝 Create gist manually at: https://gist.github.com/"
    139                )))
    140            }
    141        }
    142    }
    143 }
    144 
    145 /// Rust crates.io information
    146 pub struct CratesCommand;
    147 
    148 impl ChatCommand for CratesCommand {
    149    fn name(&self) -> &'static str {
    150        "crates"
    151    }
    152    fn description(&self) -> &'static str {
    153        "Get Rust crate information from crates.io"
    154    }
    155    fn usage(&self) -> &'static str {
    156        "/crates <crate_name>"
    157    }
    158 
    159    fn execute(
    160        &self,
    161        args: Vec<String>,
    162        _context: &CommandContext,
    163    ) -> Result<ChatOpResult, ChatOpError> {
    164        if args.is_empty() {
    165            return Err(ChatOpError::MissingArguments(
    166                "Please specify a crate name".to_string(),
    167            ));
    168        }
    169 
    170        let crate_name = &args[0];
    171 
    172        // Try to fetch from crates.io API
    173        match Command::new("curl")
    174            .args(&[
    175                "-s",
    176                &format!("https://crates.io/api/v1/crates/{}", crate_name),
    177            ])
    178            .output()
    179        {
    180            Ok(output) => {
    181                if output.status.success() {
    182                    let result = String::from_utf8_lossy(&output.stdout);
    183                    if result.contains("\"crate\"") {
    184                        // Parse basic info (in real implementation, use serde_json)
    185                        let info = vec![
    186                            format!("📦 **Rust Crate: {}**", crate_name),
    187                            format!("🔗 crates.io: https://crates.io/crates/{}", crate_name),
    188                            format!("📚 docs.rs: https://docs.rs/{}", crate_name),
    189                            format!("📋 Add to Cargo.toml: {} = \"latest\"", crate_name),
    190                        ];
    191                        Ok(ChatOpResult::Block(info))
    192                    } else {
    193                        Ok(ChatOpResult::Message(format!(
    194                            "📦 Crate '{}' not found on crates.io",
    195                            crate_name
    196                        )))
    197                    }
    198                } else {
    199                    Ok(ChatOpResult::Message(format!(
    200                        "📦 Failed to fetch info for crate '{}'",
    201                        crate_name
    202                    )))
    203                }
    204            }
    205            Err(_) => Ok(ChatOpResult::Message(format!(
    206                "📦 Check crate manually: https://crates.io/crates/{}",
    207                crate_name
    208            ))),
    209        }
    210    }
    211 }
    212 
    213 /// NPM package information
    214 pub struct NpmCommand;
    215 
    216 impl ChatCommand for NpmCommand {
    217    fn name(&self) -> &'static str {
    218        "npm"
    219    }
    220    fn description(&self) -> &'static str {
    221        "Get NPM package information"
    222    }
    223    fn usage(&self) -> &'static str {
    224        "/npm <package_name>"
    225    }
    226 
    227    fn execute(
    228        &self,
    229        args: Vec<String>,
    230        _context: &CommandContext,
    231    ) -> Result<ChatOpResult, ChatOpError> {
    232        if args.is_empty() {
    233            return Err(ChatOpError::MissingArguments(
    234                "Please specify a package name".to_string(),
    235            ));
    236        }
    237 
    238        let package_name = &args[0];
    239 
    240        // Try using npm view command
    241        match Command::new("npm")
    242            .args(&["view", package_name, "--json"])
    243            .output()
    244        {
    245            Ok(output) => {
    246                if output.status.success() {
    247                    let result = String::from_utf8_lossy(&output.stdout);
    248                    if result.contains("\"name\"") {
    249                        let info = vec![
    250                            format!("📦 **NPM Package: {}**", package_name),
    251                            format!(
    252                                "🔗 npmjs.com: https://www.npmjs.com/package/{}",
    253                                package_name
    254                            ),
    255                            format!("📋 Install: npm install {}", package_name),
    256                            format!("📋 Or: yarn add {}", package_name),
    257                        ];
    258                        Ok(ChatOpResult::Block(info))
    259                    } else {
    260                        Ok(ChatOpResult::Message(format!(
    261                            "📦 Package '{}' not found on NPM",
    262                            package_name
    263                        )))
    264                    }
    265                } else {
    266                    Ok(ChatOpResult::Message(format!(
    267                        "📦 Failed to fetch info for package '{}'",
    268                        package_name
    269                    )))
    270                }
    271            }
    272            Err(_) => Ok(ChatOpResult::Message(format!(
    273                "📦 Check package manually: https://www.npmjs.com/package/{}",
    274                package_name
    275            ))),
    276        }
    277    }
    278 }
    279 
    280 /// Python PyPI package information
    281 pub struct PipCommand;
    282 
    283 impl ChatCommand for PipCommand {
    284    fn name(&self) -> &'static str {
    285        "pip"
    286    }
    287    fn description(&self) -> &'static str {
    288        "Get Python package information from PyPI"
    289    }
    290    fn usage(&self) -> &'static str {
    291        "/pip <package_name>"
    292    }
    293    fn aliases(&self) -> Vec<&'static str> {
    294        vec!["pypi"]
    295    }
    296 
    297    fn execute(
    298        &self,
    299        args: Vec<String>,
    300        _context: &CommandContext,
    301    ) -> Result<ChatOpResult, ChatOpError> {
    302        if args.is_empty() {
    303            return Err(ChatOpError::MissingArguments(
    304                "Please specify a package name".to_string(),
    305            ));
    306        }
    307 
    308        let package_name = &args[0];
    309 
    310        // Try to fetch from PyPI API
    311        match Command::new("curl")
    312            .args(&[
    313                "-s",
    314                &format!("https://pypi.org/pypi/{}/json", package_name),
    315            ])
    316            .output()
    317        {
    318            Ok(output) => {
    319                if output.status.success() {
    320                    let result = String::from_utf8_lossy(&output.stdout);
    321                    if result.contains("\"info\"") && !result.contains("\"message\": \"Not Found\"")
    322                    {
    323                        let info = vec![
    324                            format!("🐍 **Python Package: {}**", package_name),
    325                            format!("🔗 PyPI: https://pypi.org/project/{}/", package_name),
    326                            format!("📋 Install: pip install {}", package_name),
    327                            format!("📋 Or: python -m pip install {}", package_name),
    328                        ];
    329                        Ok(ChatOpResult::Block(info))
    330                    } else {
    331                        Ok(ChatOpResult::Message(format!(
    332                            "🐍 Package '{}' not found on PyPI",
    333                            package_name
    334                        )))
    335                    }
    336                } else {
    337                    Ok(ChatOpResult::Message(format!(
    338                        "🐍 Failed to fetch info for package '{}'",
    339                        package_name
    340                    )))
    341                }
    342            }
    343            Err(_) => Ok(ChatOpResult::Message(format!(
    344                "🐍 Check package manually: https://pypi.org/project/{}/",
    345                package_name
    346            ))),
    347        }
    348    }
    349 }