diff --git a/src/cli.rs b/src/cli.rs index b0e7ad2..0e1f877 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,8 +7,8 @@ use clap::{CommandFactory, Parser}; use mlua::{Lua, Result}; use crate::{ - lune::{console::Console, fs::Fs, net::Net, process::Process}, - utils::GithubClient, + globals::{console::Console, fs::Fs, net::Net, process::Process}, + utils::github::Client as GithubClient, }; /// Lune CLI diff --git a/src/lune/console.rs b/src/globals/console.rs similarity index 98% rename from src/lune/console.rs rename to src/globals/console.rs index 91a3b74..00828bd 100644 --- a/src/lune/console.rs +++ b/src/globals/console.rs @@ -1,6 +1,6 @@ use mlua::{Lua, MultiValue, Result, UserData, UserDataMethods}; -use crate::utils::{ +use crate::utils::formatting::{ flush_stdout, pretty_format_multi_value, print_color, print_label, print_style, }; diff --git a/src/lune/fs.rs b/src/globals/fs.rs similarity index 100% rename from src/lune/fs.rs rename to src/globals/fs.rs diff --git a/src/lune/mod.rs b/src/globals/mod.rs similarity index 100% rename from src/lune/mod.rs rename to src/globals/mod.rs diff --git a/src/lune/net.rs b/src/globals/net.rs similarity index 98% rename from src/lune/net.rs rename to src/globals/net.rs index 26c4c2f..f624d26 100644 --- a/src/lune/net.rs +++ b/src/globals/net.rs @@ -6,7 +6,7 @@ use reqwest::{ Method, }; -use crate::utils::get_github_user_agent_header; +use crate::utils::github::get_github_user_agent_header; pub struct Net(); diff --git a/src/lune/process.rs b/src/globals/process.rs similarity index 100% rename from src/lune/process.rs rename to src/globals/process.rs diff --git a/src/main.rs b/src/main.rs index 699946d..9227d1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,11 @@ use clap::Parser; use mlua::Result; mod cli; -mod lune; +mod globals; mod utils; use cli::Cli; -use utils::{pretty_print_luau_error, print_label}; +use utils::formatting::{pretty_print_luau_error, print_label}; #[tokio::main] async fn main() -> Result<()> { diff --git a/src/utils.rs b/src/utils/formatting.rs similarity index 55% rename from src/utils.rs rename to src/utils/formatting.rs index 4879fce..1c33ad6 100644 --- a/src/utils.rs +++ b/src/utils/formatting.rs @@ -1,172 +1,42 @@ use std::{ - env::current_dir, - fmt::Write, - io::{self, Write as IoWrite}, + fmt::Write as _, + io::{self, Write as _}, }; -use anyhow::{bail, Context, Result}; use mlua::{MultiValue, Value}; -use reqwest::{ - header::{HeaderMap, HeaderValue}, - Client, -}; -use serde::{Deserialize, Serialize}; const MAX_FORMAT_DEPTH: usize = 4; const INDENT: &str = " "; -const COLOR_RESET: &str = "\x1B[0m"; -const COLOR_BLACK: &str = "\x1B[30m"; -const COLOR_RED: &str = "\x1B[31m"; -const COLOR_GREEN: &str = "\x1B[32m"; -const COLOR_YELLOW: &str = "\x1B[33m"; -const COLOR_BLUE: &str = "\x1B[34m"; -const COLOR_PURPLE: &str = "\x1B[35m"; -const COLOR_CYAN: &str = "\x1B[36m"; -const COLOR_WHITE: &str = "\x1B[37m"; +pub const COLOR_RESET: &str = "\x1B[0m"; +pub const COLOR_BLACK: &str = "\x1B[30m"; +pub const COLOR_RED: &str = "\x1B[31m"; +pub const COLOR_GREEN: &str = "\x1B[32m"; +pub const COLOR_YELLOW: &str = "\x1B[33m"; +pub const COLOR_BLUE: &str = "\x1B[34m"; +pub const COLOR_PURPLE: &str = "\x1B[35m"; +pub const COLOR_CYAN: &str = "\x1B[36m"; +pub const COLOR_WHITE: &str = "\x1B[37m"; -const STYLE_RESET: &str = "\x1B[22m"; -const STYLE_BOLD: &str = "\x1B[1m"; -const STYLE_DIM: &str = "\x1B[2m"; - -#[derive(Clone, Deserialize, Serialize)] -pub struct GithubReleaseAsset { - id: u64, - url: String, - name: Option, - label: Option, - content_type: String, - size: u64, -} - -#[derive(Clone, Deserialize, Serialize)] -pub struct GithubRelease { - id: u64, - url: String, - tag_name: String, - name: Option, - body: Option, - draft: bool, - prerelease: bool, - assets: Vec, -} - -pub struct GithubClient { - client: Client, - github_owner: String, - github_repo: String, -} - -impl GithubClient { - pub fn new() -> Result { - let (github_owner, github_repo) = get_github_owner_and_repo(); - let mut headers = HeaderMap::new(); - headers.insert( - "User-Agent", - HeaderValue::from_str(&get_github_user_agent_header())?, - ); - headers.insert( - "Accept", - HeaderValue::from_static("application/vnd.github+json"), - ); - headers.insert( - "X-GitHub-Api-Version", - HeaderValue::from_static("2022-11-28"), - ); - let client = Client::builder().default_headers(headers).build()?; - Ok(Self { - client, - github_owner, - github_repo, - }) - } - - pub async fn fetch_releases(&self) -> Result> { - let release_api_url = format!( - "https://api.github.com/repos/{}/{}/releases", - &self.github_owner, &self.github_repo - ); - let response_bytes = self - .client - .get(release_api_url) - .send() - .await - .context("Failed to send releases request")? - .bytes() - .await - .context("Failed to get releases response bytes")?; - let response_body: Vec = serde_json::from_slice(&response_bytes)?; - Ok(response_body) - } - - pub async fn fetch_release_for_this_version(&self) -> Result { - let release_version_tag = format!("v{}", env!("CARGO_PKG_VERSION")); - let all_releases = self.fetch_releases().await?; - all_releases - .iter() - .find(|release| release.tag_name == release_version_tag) - .map(ToOwned::to_owned) - .with_context(|| format!("Failed to find release for version {release_version_tag}")) - } - - pub async fn fetch_release_asset( - &self, - release: &GithubRelease, - asset_name: &str, - ) -> Result<()> { - if let Some(asset) = release - .assets - .iter() - .find(|asset| matches!(&asset.name, Some(name) if name == asset_name)) - { - let file_path = current_dir()?.join(asset_name); - let file_bytes = self - .client - .get(&asset.url) - .header("Accept", "application/octet-stream") - .send() - .await - .context("Failed to send asset download request")? - .bytes() - .await - .context("Failed to get asset download response bytes")?; - tokio::fs::write(&file_path, &file_bytes) - .await - .with_context(|| { - format!("Failed to write file at path '{}'", &file_path.display()) - })?; - } else { - bail!( - "Failed to find release asset '{}' for release '{}'", - asset_name, - &release.tag_name - ) - } - Ok(()) - } -} - -pub fn get_github_owner_and_repo() -> (String, String) { - let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY") - .strip_prefix("https://github.com/") - .unwrap() - .split_once('/') - .unwrap(); - (github_owner.to_owned(), github_repo.to_owned()) -} - -pub fn get_github_user_agent_header() -> String { - let (github_owner, github_repo) = get_github_owner_and_repo(); - format!("{github_owner}-{github_repo}-cli") -} - -// TODO: Separate utils out into github & formatting +pub const STYLE_RESET: &str = "\x1B[22m"; +pub const STYLE_BOLD: &str = "\x1B[1m"; +pub const STYLE_DIM: &str = "\x1B[2m"; pub fn flush_stdout() -> mlua::Result<()> { io::stdout().flush().map_err(mlua::Error::external) } +fn can_be_plain_lua_table_key(s: &mlua::String) -> bool { + let str = s.to_string_lossy().to_string(); + let first_char = str.chars().next().unwrap(); + if first_char.is_alphabetic() { + str.chars().all(|c| c == '_' || c.is_alphanumeric()) + } else { + false + } +} + pub fn print_label>(s: S) -> mlua::Result<()> { print!( "{}[{}{}{}{}]{} ", @@ -230,17 +100,7 @@ pub fn print_color>(s: S) -> mlua::Result<()> { Ok(()) } -fn can_be_plain_lua_table_key(s: &mlua::String) -> bool { - let str = s.to_string_lossy().to_string(); - let first_char = str.chars().next().unwrap(); - if first_char.is_alphabetic() { - str.chars().all(|c| c == '_' || c.is_alphanumeric()) - } else { - false - } -} - -fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) -> anyhow::Result<()> { +pub fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) -> anyhow::Result<()> { // TODO: Handle tables with cyclic references // TODO: Handle other types like function, userdata, ... match &value { diff --git a/src/utils/github.rs b/src/utils/github.rs new file mode 100644 index 0000000..4c9f47c --- /dev/null +++ b/src/utils/github.rs @@ -0,0 +1,134 @@ +use std::env::current_dir; + +use anyhow::{bail, Context, Result}; +use reqwest::header::{HeaderMap, HeaderValue}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Deserialize, Serialize)] +pub struct ReleaseAsset { + id: u64, + url: String, + name: Option, + label: Option, + content_type: String, + size: u64, +} + +#[derive(Clone, Deserialize, Serialize)] +pub struct Release { + id: u64, + url: String, + tag_name: String, + name: Option, + body: Option, + draft: bool, + prerelease: bool, + assets: Vec, +} + +pub struct Client { + client: reqwest::Client, + github_owner: String, + github_repo: String, +} + +impl Client { + pub fn new() -> Result { + let (github_owner, github_repo) = get_github_owner_and_repo(); + let mut headers = HeaderMap::new(); + headers.insert( + "User-Agent", + HeaderValue::from_str(&get_github_user_agent_header())?, + ); + headers.insert( + "Accept", + HeaderValue::from_static("application/vnd.github+json"), + ); + headers.insert( + "X-GitHub-Api-Version", + HeaderValue::from_static("2022-11-28"), + ); + let client = reqwest::Client::builder() + .default_headers(headers) + .build()?; + Ok(Self { + client, + github_owner, + github_repo, + }) + } + + pub async fn fetch_releases(&self) -> Result> { + let release_api_url = format!( + "https://api.github.com/repos/{}/{}/releases", + &self.github_owner, &self.github_repo + ); + let response_bytes = self + .client + .get(release_api_url) + .send() + .await + .context("Failed to send releases request")? + .bytes() + .await + .context("Failed to get releases response bytes")?; + let response_body: Vec = serde_json::from_slice(&response_bytes)?; + Ok(response_body) + } + + pub async fn fetch_release_for_this_version(&self) -> Result { + let release_version_tag = format!("v{}", env!("CARGO_PKG_VERSION")); + let all_releases = self.fetch_releases().await?; + all_releases + .iter() + .find(|release| release.tag_name == release_version_tag) + .map(ToOwned::to_owned) + .with_context(|| format!("Failed to find release for version {release_version_tag}")) + } + + pub async fn fetch_release_asset(&self, release: &Release, asset_name: &str) -> Result<()> { + if let Some(asset) = release + .assets + .iter() + .find(|asset| matches!(&asset.name, Some(name) if name == asset_name)) + { + let file_path = current_dir()?.join(asset_name); + let file_bytes = self + .client + .get(&asset.url) + .header("Accept", "application/octet-stream") + .send() + .await + .context("Failed to send asset download request")? + .bytes() + .await + .context("Failed to get asset download response bytes")?; + tokio::fs::write(&file_path, &file_bytes) + .await + .with_context(|| { + format!("Failed to write file at path '{}'", &file_path.display()) + })?; + } else { + bail!( + "Failed to find release asset '{}' for release '{}'", + asset_name, + &release.tag_name + ) + } + Ok(()) + } +} + +pub fn get_github_owner_and_repo() -> (String, String) { + let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY") + .strip_prefix("https://github.com/") + .unwrap() + .split_once('/') + .unwrap(); + (github_owner.to_owned(), github_repo.to_owned()) +} + +pub fn get_github_user_agent_header() -> String { + let (github_owner, github_repo) = get_github_owner_and_repo(); + format!("{github_owner}-{github_repo}-cli") +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..37ce64f --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod formatting; +pub mod github;