diff --git a/.gitignore b/.gitignore index 9c4c004..ade7e26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ .cuddle/ +.DS_Store diff --git a/README.md b/README.md index a7e9234..ea2e913 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Git Now is a utility for easily navigating git projects from common upstream providers. Search, Download, and Enter projects as quickly as you can type. +![example gif](./assets/gifs/example.gif) + How many steps do you normally do to download a project? 1. Navigate to github.com diff --git a/assets/gifs/example.gif b/assets/gifs/example.gif new file mode 100644 index 0000000..4a26305 Binary files /dev/null and b/assets/gifs/example.gif differ diff --git a/crates/gitnow/Cargo.toml b/crates/gitnow/Cargo.toml index e4fdcda..94487d7 100644 --- a/crates/gitnow/Cargo.toml +++ b/crates/gitnow/Cargo.toml @@ -29,3 +29,6 @@ ratatui = "0.28.1" [dev-dependencies] pretty_assertions = "1.4.0" + +[features] +example = [] diff --git a/crates/gitnow/src/commands/root.rs b/crates/gitnow/src/commands/root.rs index b8f3ec6..31613c6 100644 --- a/crates/gitnow/src/commands/root.rs +++ b/crates/gitnow/src/commands/root.rs @@ -19,19 +19,27 @@ impl RootCommand { Self { app } } - pub async fn execute(&mut self, search: Option>) -> anyhow::Result<()> { + pub async fn execute( + &mut self, + search: Option>, + cache: bool, + ) -> anyhow::Result<()> { tracing::debug!("executing"); - let repositories = match self.app.cache().get().await? { - Some(repos) => repos, - None => { - tracing::info!("finding repositories..."); - let repositories = self.app.projects_list().get_projects().await?; + let repositories = if cache { + match self.app.cache().get().await? { + Some(repos) => repos, + None => { + tracing::info!("finding repositories..."); + let repositories = self.app.projects_list().get_projects().await?; - self.app.cache().update(&repositories).await?; + self.app.cache().update(&repositories).await?; - repositories + repositories + } } + } else { + self.app.projects_list().get_projects().await? }; match search { Some(needle) => { diff --git a/crates/gitnow/src/main.rs b/crates/gitnow/src/main.rs index da9849c..e39d44b 100644 --- a/crates/gitnow/src/main.rs +++ b/crates/gitnow/src/main.rs @@ -25,6 +25,9 @@ struct Command { #[arg()] search: Option, + + #[arg(long = "no-cache", default_value = "false")] + no_cache: bool, } #[derive(Subcommand)] @@ -56,7 +59,9 @@ async fn main() -> anyhow::Result<()> { match cli.command { Some(_) => todo!(), None => { - RootCommand::new(app).execute(cli.search.as_ref()).await?; + RootCommand::new(app) + .execute(cli.search.as_ref(), !cli.no_cache) + .await?; } } diff --git a/crates/gitnow/src/projects_list.rs b/crates/gitnow/src/projects_list.rs index ac41fbe..0c8cd71 100644 --- a/crates/gitnow/src/projects_list.rs +++ b/crates/gitnow/src/projects_list.rs @@ -1,112 +1,10 @@ -use crate::{ - app::App, - git_provider::{ - gitea::GiteaProviderApp, github::GitHubProviderApp, Repository, VecRepositoryExt, - }, -}; +#[cfg(not(feature = "example"))] +pub use implementation::*; -pub struct ProjectsList { - app: &'static App, -} +#[cfg(feature = "example")] +pub use example_projects::*; -impl ProjectsList { - pub fn new(app: &'static App) -> Self { - Self { app } - } - - pub async fn get_projects(&self) -> anyhow::Result> { - let mut repositories = Vec::new(); - - repositories.extend(self.get_gitea_projects().await?); - repositories.extend(self.get_github_projects().await?); - - repositories.collect_unique(); - - Ok(repositories) - } - - async fn get_gitea_projects(&self) -> anyhow::Result> { - let gitea_provider = self.app.gitea_provider(); - - let mut repositories = Vec::new(); - for gitea in self.app.config.providers.gitea.iter() { - if let Some(_user) = &gitea.current_user { - let mut repos = gitea_provider - .list_repositories_for_current_user(&gitea.url, gitea.access_token.as_ref()) - .await?; - - repositories.append(&mut repos); - } - - for gitea_user in gitea.users.iter() { - let mut repos = gitea_provider - .list_repositories_for_user( - gitea_user.into(), - &gitea.url, - gitea.access_token.as_ref(), - ) - .await?; - - repositories.append(&mut repos); - } - - for gitea_org in gitea.organisations.iter() { - let mut repos = gitea_provider - .list_repositories_for_organisation( - gitea_org.into(), - &gitea.url, - gitea.access_token.as_ref(), - ) - .await?; - - repositories.append(&mut repos); - } - } - - Ok(repositories) - } - - async fn get_github_projects(&self) -> anyhow::Result> { - let github_provider = self.app.github_provider(); - - let mut repositories = Vec::new(); - for github in self.app.config.providers.github.iter() { - if let Some(_user) = &github.current_user { - let mut repos = github_provider - .list_repositories_for_current_user(github.url.as_ref(), &github.access_token) - .await?; - - repositories.append(&mut repos); - } - - for github_user in github.users.iter() { - let mut repos = github_provider - .list_repositories_for_user( - github_user.into(), - github.url.as_ref(), - &github.access_token, - ) - .await?; - - repositories.append(&mut repos); - } - - for github_org in github.organisations.iter() { - let mut repos = github_provider - .list_repositories_for_organisation( - github_org.into(), - github.url.as_ref(), - &github.access_token, - ) - .await?; - - repositories.append(&mut repos); - } - } - - Ok(repositories) - } -} +use crate::app::App; pub trait ProjectsListApp { fn projects_list(&self) -> ProjectsList; @@ -117,3 +15,121 @@ impl ProjectsListApp for &'static App { ProjectsList::new(self) } } + +mod implementation { + use crate::{ + app::App, + git_provider::{ + gitea::GiteaProviderApp, github::GitHubProviderApp, Repository, VecRepositoryExt, + }, + }; + + pub struct ProjectsList { + app: &'static App, + } + + impl ProjectsList { + pub fn new(app: &'static App) -> Self { + Self { app } + } + + pub async fn get_projects(&self) -> anyhow::Result> { + let mut repositories = Vec::new(); + + repositories.extend(self.get_gitea_projects().await?); + repositories.extend(self.get_github_projects().await?); + + repositories.collect_unique(); + + Ok(repositories) + } + + async fn get_gitea_projects(&self) -> anyhow::Result> { + let gitea_provider = self.app.gitea_provider(); + + let mut repositories = Vec::new(); + for gitea in self.app.config.providers.gitea.iter() { + if let Some(_user) = &gitea.current_user { + let mut repos = gitea_provider + .list_repositories_for_current_user(&gitea.url, gitea.access_token.as_ref()) + .await?; + + repositories.append(&mut repos); + } + + for gitea_user in gitea.users.iter() { + let mut repos = gitea_provider + .list_repositories_for_user( + gitea_user.into(), + &gitea.url, + gitea.access_token.as_ref(), + ) + .await?; + + repositories.append(&mut repos); + } + + for gitea_org in gitea.organisations.iter() { + let mut repos = gitea_provider + .list_repositories_for_organisation( + gitea_org.into(), + &gitea.url, + gitea.access_token.as_ref(), + ) + .await?; + + repositories.append(&mut repos); + } + } + + Ok(repositories) + } + + async fn get_github_projects(&self) -> anyhow::Result> { + let github_provider = self.app.github_provider(); + + let mut repositories = Vec::new(); + for github in self.app.config.providers.github.iter() { + if let Some(_user) = &github.current_user { + let mut repos = github_provider + .list_repositories_for_current_user( + github.url.as_ref(), + &github.access_token, + ) + .await?; + + repositories.append(&mut repos); + } + + for github_user in github.users.iter() { + let mut repos = github_provider + .list_repositories_for_user( + github_user.into(), + github.url.as_ref(), + &github.access_token, + ) + .await?; + + repositories.append(&mut repos); + } + + for github_org in github.organisations.iter() { + let mut repos = github_provider + .list_repositories_for_organisation( + github_org.into(), + github.url.as_ref(), + &github.access_token, + ) + .await?; + + repositories.append(&mut repos); + } + } + + Ok(repositories) + } + } +} + +#[cfg(feature = "example")] +mod example_projects; diff --git a/crates/gitnow/src/projects_list/example_projects.rs b/crates/gitnow/src/projects_list/example_projects.rs new file mode 100644 index 0000000..05423a3 --- /dev/null +++ b/crates/gitnow/src/projects_list/example_projects.rs @@ -0,0 +1,84 @@ +use crate::{app::App, git_provider::Repository}; + +pub struct ProjectsList {} + +impl ProjectsList { + pub fn new(_app: &'static App) -> Self { + Self {} + } + + pub async fn get_projects(&self) -> anyhow::Result> { + Ok(self.from_strings([ + "github.com/kjuulh/gitnow", + "github.com/kjuulh/gitnow-client", + "github.com/kjuulh/crunch", + "git.front.kjuulh.io/kjuulh/gitnow", + "git.front.kjuulh.io/kjuulh/gitnow-client", + "git.front.kjuulh.io/kjuulh/cuddle", + "git.front.kjuulh.io/kjuulh/buckle", + "git.front.kjuulh.io/kjuulh/books", + "git.front.kjuulh.io/kjuulh/blog-deployment", + "git.front.kjuulh.io/kjuulh/blog", + "git.front.kjuulh.io/kjuulh/bitfield", + "git.front.kjuulh.io/kjuulh/bitebuds-deployment", + "git.front.kjuulh.io/kjuulh/bitebuds", + "git.front.kjuulh.io/kjuulh/beerday", + "git.front.kjuulh.io/kjuulh/bearing", + "git.front.kjuulh.io/kjuulh/basic-webserver", + "git.front.kjuulh.io/kjuulh/backup", + "git.front.kjuulh.io/kjuulh/backstage", + "git.front.kjuulh.io/kjuulh/autom8-calendar-integration", + "git.front.kjuulh.io/kjuulh/astronvim", + "git.front.kjuulh.io/kjuulh/artifacts", + "git.front.kjuulh.io/kjuulh/articles", + "git.front.kjuulh.io/kjuulh/acc-server", + "git.front.kjuulh.io/kjuulh/_cargo-index", + "git.front.kjuulh.io/keep-up/keep-up-example", + "git.front.kjuulh.io/keep-up/keep-up", + "git.front.kjuulh.io/experiments/wasm-bin", + "git.front.kjuulh.io/dotfiles/doom", + "git.front.kjuulh.io/danskebank/testssl.sh", + "git.front.kjuulh.io/clank/kubernetes-state", + "git.front.kjuulh.io/clank/kubernetes-init", + "git.front.kjuulh.io/clank/blog", + "git.front.kjuulh.io/cibus/deployments", + "git.front.kjuulh.io/butikkaerlighilsen/client", + "git.front.kjuulh.io/bevy/bevy", + "git.front.kjuulh.io/OpenFood/openfood", + ])) + } + + fn from_strings( + &self, + repos_into: impl IntoIterator>, + ) -> Vec { + let repos = repos_into + .into_iter() + .map(|item| item.into()) + .collect::>(); + + repos + } +} + +impl From<&str> for Repository { + fn from(value: &str) -> Self { + let values = value.split("/").collect::>(); + if values.len() != 3 { + panic!("value: '{value}' isn't a valid provider/owner/repository") + } + + let (provider, owner, name) = ( + values.get(0).unwrap(), + values.get(1).unwrap(), + values.get(2).unwrap(), + ); + + Self { + provider: provider.to_string(), + owner: owner.to_string(), + repo_name: name.to_string(), + ssh_url: format!("ssh://git@{provider}/{owner}/{name}.git"), + } + } +} diff --git a/cuddle.yaml b/cuddle.yaml index 2b62582..68e0033 100644 --- a/cuddle.yaml +++ b/cuddle.yaml @@ -15,3 +15,9 @@ please: api_url: "https://git.front.kjuulh.io" actions: rust: + +scripts: + record: + type: shell + update-gifs: + type: shell diff --git a/scripts/record.sh b/scripts/record.sh new file mode 100755 index 0000000..3a5855d --- /dev/null +++ b/scripts/record.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +set -e + +# Loop through each file in the folder +for file in "vhs"/*; do + # Check if it is a file (not a directory) + if [[ -f "$file" ]]; then + echo "Recording: $file" + + vhs "./$file" + fi +done diff --git a/scripts/update-gifs.sh b/scripts/update-gifs.sh new file mode 100755 index 0000000..7509a83 --- /dev/null +++ b/scripts/update-gifs.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env zsh + +rm -r assets/gifs + +set -e + +cuddle x record +mkdir -p assets/gifs +mv target/vhs/* assets/gifs diff --git a/vhs/example.vhs b/vhs/example.vhs new file mode 100644 index 0000000..cbea923 --- /dev/null +++ b/vhs/example.vhs @@ -0,0 +1,14 @@ +Output "target/vhs/example.gif" +Set Theme "Aardvark Blue" +Set Width 1200 +Set Height 1000 +Hide +Type "cargo run --features example -- --no-cache" +Enter +Sleep 1s +Show +Sleep 2s +Type@500ms "bevy" +Sleep 3s +Enter +Sleep 3s