diff --git a/Cargo.toml b/Cargo.toml index db022c7..043cd83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ ["package"] name = "amble" description = "First class, scalable rust project generator with batteries included." -version = "0.1.17" +version = "0.1.18" edition = "2021" license = "MIT" authors = ["refcell"] @@ -34,6 +34,7 @@ reqwest = { version = "0.11.22", features = ["blocking", "json"] } chrono = "0.4.31" gitignores = "2.3.2" prettytable = "0.10.0" +lice = "0.1.1" [dev-dependencies] tempfile = "3.8.0" diff --git a/src/license.rs b/src/license.rs index a476431..6314999 100644 --- a/src/license.rs +++ b/src/license.rs @@ -8,22 +8,11 @@ use tracing::instrument; /// The MIT License. pub(crate) const MIT_LICENSE: &str = "MIT License\n\nCopyright (c) [year] [fullname]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"; -/// Builds the contents of the license file, performing template substitutions. -#[instrument(name = "license", skip(license))] -pub(crate) fn build(license: impl AsRef) -> String { - tracing::debug!("Building license file contents"); - #[allow(clippy::match_single_binding)] - match license.as_ref() { - // Default to the MIT License - // todo: Add support for other licenses. - _ => { - // Replace the `[year]` and `[fullname]` placeholders with the current year and the - // current user's full name. - MIT_LICENSE - .replace("[year]", &chrono::Utc::now().year().to_string()) - .replace("[fullname]", &crate::root::get_current_username(&None)) - } - } +/// Helper function to build an MIT License with imputed values. +pub(crate) fn build_mit_license() -> String { + MIT_LICENSE + .replace("[year]", &chrono::Utc::now().year().to_string()) + .replace("[fullname]", &crate::root::get_current_username(&None)) } /// Creates a new license file in the given directory. @@ -42,68 +31,51 @@ pub(crate) fn create( std::fs::create_dir_all(dir)?; } - // Prompt the user that the license is not supported - // and to fall back on the MIT license. - if !dry && license.as_ref() != "mit" { - tracing::warn!("License {} is not supported", license.as_ref()); - if !inquire::Confirm::new(&format!( - "License {} is not supported, do you want to proceed with the MIT License instead?", - license.as_ref(), - )) - .prompt()? - { - return Ok(()); + // Fetch the license in a tokio runtime. + let license = match tokio::runtime::Runtime::new()?.block_on(fetch_license(license.as_ref())) { + Ok(license) => license + .replacen("", &chrono::Utc::now().year().to_string(), 1) + .replacen( + "", + &crate::root::get_current_username(&None), + 1, + ), + Err(_) => { + if !inquire::Confirm::new(&format!( + "Failed to query for license \"{}\", do you want to proceed with the MIT License instead?", + license.as_ref(), + )) + .prompt()? + { + tracing::warn!("User chose not to proceed with the MIT License"); + return Ok(()); + } + build_mit_license() } - } + }; if !dry { tracing::debug!("Writing MIT license to {:?}", dir.join("LICENSE")); let mut file = std::fs::File::create(dir.join("LICENSE"))?; - file.write_all(build("mit").as_bytes())?; + file.write_all(license.as_bytes())?; } tree.map(|t| t.add_empty_child("LICENSE".to_string())); Ok(()) } -/// Fetches the license file from the github api with an mit license fallback. -#[allow(dead_code)] -pub(crate) async fn fetch_with_fallback(name: impl AsRef) -> Result { - match fetch_license(name.as_ref()).await { - Ok(license) => Ok(license), - Err(e) => { - tracing::error!("Failed to fetch license from github api: {}", e); - tracing::error!("Falling back to MIT license"); - if !inquire::Confirm::new(&format!( - "Failed to fetch {} license, do you want to proceed with the MIT License instead?", - name.as_ref(), - )) - .prompt()? - { - return Ok("".to_string()); - } - tracing::debug!("Fetching MIT license from github api"); - fetch_license("mit").await - } - } -} - -/// Fetches the license file from the github api. -#[instrument(name = "license", skip(name))] -pub async fn fetch_license(name: impl AsRef) -> Result { - tracing::debug!("Fetching license from github api"); - let fetch_url = format!("https://api.github.com/licenses/{}", name.as_ref()); - let license = reqwest::get(fetch_url) - .await? - .json::() - .await? - .get("body") - .ok_or(eyre::eyre!("Failed to get body from github api response"))? - .as_str() - .ok_or(eyre::eyre!("Failed to convert github api body to string"))? - .to_string(); - tracing::debug!("Fetched license from github api"); - Ok(license) +/// Fetch a license using [lice]. +pub(crate) async fn fetch_license(name: impl AsRef) -> Result { + tracing::debug!("Fetching license from lice"); + let license = lice::get(name.as_ref()).await.map_err(|e| { + tracing::warn!( + "Failed to find license \"{}\" in SPDX database", + name.as_ref() + ); + eyre::eyre!(e) + })?; + tracing::debug!("Fetched license from lice"); + license.license_text.ok_or(eyre::eyre!("no license text!")) } #[cfg(test)] @@ -113,6 +85,23 @@ mod tests { use std::io::Read; use tempfile::tempdir; + #[tokio::test] + async fn test_fetch_license() { + let license = fetch_license("mit") + .await + .unwrap() + .replacen("", &chrono::Utc::now().year().to_string(), 1) + .replacen( + "", + &crate::root::get_current_username(&None), + 1, + ); + assert_eq!( + license.replace("\n\n", " ").replace("\n", " "), + build_mit_license().replace("\n\n", " ").replace("\n", " ") + ); + } + #[test] fn test_create_license() { let dir = tempdir().unwrap(); @@ -126,6 +115,9 @@ mod tests { let mut file = File::open(package_dir.join("LICENSE")).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); - assert_eq!(contents, build("mit")); + assert_eq!( + contents.replace("\n\n", " ").replace("\n", " "), + build_mit_license().replace("\n\n", " ").replace("\n", " ") + ); } }