use std::path::Path;
use std::process::Command;
use crate::license::scanner::{LanguageLicenseScanner, PackageLicense};
pub struct GoLicenseScanner;
impl GoLicenseScanner {
pub fn new() -> Self {
Self
}
fn has_go_licenses(&self) -> bool {
Command::new("go-licenses")
.args(["version"])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn scan_with_go_licenses(&self, path: &Path) -> Result<Vec<PackageLicense>, String> {
let output = Command::new("go-licenses")
.args(["csv", "./..."])
.current_dir(path)
.output()
.map_err(|e| format!("Failed to run go-licenses: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("go-licenses failed: {}", stderr));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut packages = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() >= 2 {
let module = parts[0];
let license = parts.get(1).unwrap_or(&"");
let (name, version) = if module.contains('@') {
let split: Vec<&str> = module.split('@').collect();
(
split[0].to_string(),
split.get(1).unwrap_or(&"").to_string(),
)
} else {
(module.to_string(), String::new())
};
packages.push(PackageLicense::new(&name, &version, license, "go"));
}
}
Ok(packages)
}
fn scan_from_go_mod(&self, path: &Path) -> Result<Vec<PackageLicense>, String> {
let output = Command::new("go")
.args(["list", "-m", "-json", "all"])
.current_dir(path)
.output()
.map_err(|e| format!("Failed to run go list: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("go list failed: {}", stderr));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut packages = Vec::new();
for line in stdout.lines() {
if line.trim().is_empty() {
continue;
}
if let Ok(module) = serde_json::from_str::<GoModule>(line) {
if module.main.unwrap_or(false) {
continue;
}
let license = self.detect_license_for_module(&module.path, path);
packages.push(PackageLicense::new(
&module.path,
&module.version.unwrap_or_default(),
&license,
"go",
));
}
}
Ok(packages)
}
fn detect_license_for_module(&self, _module: &str, _project_path: &Path) -> String {
String::new()
}
}
impl Default for GoLicenseScanner {
fn default() -> Self {
Self::new()
}
}
impl LanguageLicenseScanner for GoLicenseScanner {
fn name(&self) -> &str {
"go-licenses"
}
fn language(&self) -> &str {
"go"
}
fn detect(&self, path: &Path) -> bool {
path.join("go.mod").exists()
}
fn scan(&self, path: &Path) -> Result<Vec<PackageLicense>, String> {
if self.has_go_licenses() {
self.scan_with_go_licenses(path)
} else {
self.scan_from_go_mod(path)
}
}
}
#[derive(Debug, serde::Deserialize)]
struct GoModule {
#[serde(rename = "Path")]
path: String,
#[serde(rename = "Version")]
version: Option<String>,
#[serde(rename = "Main")]
main: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_go_scanner_detect() {
let scanner = GoLicenseScanner::new();
let temp_dir = tempfile::tempdir().unwrap();
assert!(!scanner.detect(temp_dir.path()));
std::fs::write(temp_dir.path().join("go.mod"), "module test").unwrap();
assert!(scanner.detect(temp_dir.path()));
}
}