Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to rename hooks or commands #401

Merged
merged 9 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 85 additions & 10 deletions crates/derive/src/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use proc_macro_error::abort_call_site;
use quote::{quote, ToTokens};
use quote::quote;
use syn::DeriveInput;

pub struct Variant {
pub name: Ident,
pub command_name: String,
pub hook_name: String,
}

impl Variant {
pub fn into_tuple(self) -> (Ident, String, String) {
(self.name, self.command_name, self.hook_name)
}

pub fn unzip(variants: impl Iterator<Item = Self>) -> (Vec<Ident>, Vec<String>, Vec<String>) {
let mut names = Vec::new();
let mut command_names = Vec::new();
let mut hook_names = Vec::new();

for variant in variants {
let (name, command_name, hook_name) = variant.into_tuple();
names.push(name);
command_names.push(command_name);
hook_names.push(hook_name);
}

(names, command_names, hook_names)
}
}

pub fn hook_enum(input: DeriveInput) -> TokenStream {
let struct_name = {
let original_ident = &input.ident;
Expand All @@ -18,23 +45,52 @@ pub fn hook_enum(input: DeriveInput) -> TokenStream {
.variants
.iter()
.filter_map(|variant| {
if variant.attrs.iter().any(|attr| match attr.meta {
// TODO: Refactor this to use one attribute i.e #[hook(...)]
let attrs = &variant.attrs;
if attrs.iter().any(|attr| match attr.meta {
syn::Meta::Path(ref p) => p.is_ident("no_hook"),
_ => false,
}) {
None
} else {
Some(variant.ident.to_token_stream())
let mut variant = Variant {
name: variant.ident.clone(),
command_name: heck::AsKebabCase(variant.ident.to_string()).to_string(),
hook_name: heck::AsKebabCase(variant.ident.to_string()).to_string(),
};

for attr in attrs.iter() {
if let syn::Meta::NameValue(ref nv) = attr.meta {
if nv.path.is_ident("hook_name") {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(ref s),
..
}) = nv.value
{
variant.hook_name = s.value();
}
}

if nv.path.is_ident("command_name") {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(ref s),
..
}) = nv.value
{
variant.command_name = s.value();
}
}
}
}

Some(variant)
}
})
.collect::<Vec<_>>(),
_ => abort_call_site!("Can only be derived for enums"),
};

let command_names = variants
.iter()
.map(|variant| heck::AsKebabCase(variant.to_string()).to_string())
.collect::<Vec<_>>();
let (variants, command_names, hook_names) = Variant::unzip(variants.into_iter());

let strum = match crate_name("strum").expect("strum is present in `Cargo.toml`") {
FoundCrate::Itself => Ident::new("strum", Span::call_site()),
Expand All @@ -43,16 +99,35 @@ pub fn hook_enum(input: DeriveInput) -> TokenStream {

quote! {
// TODO: Better way of doing this? or add support for meta in proc macro
#[derive(Debug, Clone, #strum::Display, #strum::IntoStaticStr, #strum::EnumIter, PartialEq, Eq)]
#[strum(serialize_all = "kebab-case")]
#[derive(Debug, Copy, Clone, #strum::EnumIter, PartialEq, Eq)]
pub enum #struct_name {
#(#variants),*
}

impl #struct_name {
pub fn command<'a>(self) -> &'a str {
match self {
#(#struct_name::#variants => #command_names,)*
}
}

pub fn hook<'a>(self) -> &'a str {
match self {
#(#struct_name::#variants => #hook_names,)*
}
}
}

// impl std::fmt::Display for #struct_name {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{}", self.hook())
// }
// }

impl From<String> for #struct_name {
fn from(string: String) -> Self {
match string.as_str() {
#(#command_names => #struct_name::#variants,)*
#(stringify!(#hook_names) => #struct_name::#variants,)*
_ => panic!("Invalid command name: {}", string),
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub fn derive_into_inner(input: TokenStream) -> TokenStream {
inner::into_inner(parse_macro_input!(input as DeriveInput)).into()
}

#[proc_macro_derive(Hooks, attributes(no_hook))]
#[proc_macro_derive(Hooks, attributes(no_hook, hook_name, command_name))]
#[proc_macro_error]
pub fn derive_hook_enum(input: TokenStream) -> TokenStream {
hooks::hook_enum(parse_macro_input!(input as DeriveInput)).into()
Expand Down
7 changes: 0 additions & 7 deletions crates/derive/tests/helpers.rs

This file was deleted.

12 changes: 7 additions & 5 deletions crates/derive/tests/raw_enum.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#![allow(dead_code)]

mod helpers;

use sfsu_derive::Hooks;

use helpers::enum_to_string;
use strum::IntoEnumIterator;

struct DummyStruct;

Expand All @@ -16,7 +14,9 @@ enum EnumWithData {

#[test]
fn has_all_variants() {
let variants = enum_to_string::<EnumWithDataHooks>();
let variants = EnumWithDataHooks::iter()
.map(|v| v.hook())
.collect::<String>();

assert_eq!(variants, "test1test2");
}
Expand All @@ -31,7 +31,9 @@ enum EnumExclude {

#[test]
fn excludes_no_hook_variant() {
let variants = enum_to_string::<EnumExcludeHooks>();
let variants = EnumExcludeHooks::iter()
.map(|v| v.hook())
.collect::<String>();

assert_eq!(variants, "test1test3");
}
1 change: 1 addition & 0 deletions crates/sprinkles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod git;
pub mod output;
pub mod packages;
pub mod progress;
pub mod shell;

mod opt;

Expand Down
45 changes: 45 additions & 0 deletions crates/sprinkles/src/shell.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use clap::ValueEnum;
use strum::Display;

#[derive(Debug, Default, ValueEnum, Copy, Clone, Display, PartialEq, Eq)]
#[strum(serialize_all = "snake_case")]
pub enum Shell {
#[default]
Powershell,
Bash,
Zsh,
Nu,
}

impl Shell {
#[must_use]
pub fn config(self) -> ShellConfig {
ShellConfig::new(self)
}
}

#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct ShellConfig(Shell);

impl ShellConfig {
#[must_use]
pub fn new(shell: Shell) -> Self {
Self(shell)
}

#[must_use]
pub fn path(self) -> &'static str {
match self.0 {
Shell::Powershell => "$PROFILE",
Shell::Bash => "bashrc",
Shell::Zsh => "zshrc",
Shell::Nu => "$nu.config-path",
}
}
}

impl std::fmt::Display for ShellConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path())
}
}
56 changes: 28 additions & 28 deletions src/commands/hook.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
use clap::{Parser, ValueEnum};
use itertools::Itertools;
use strum::{Display, IntoEnumIterator};

#[derive(Debug, Default, ValueEnum, Copy, Clone, Display, PartialEq, Eq)]
#[strum(serialize_all = "snake_case")]
enum Shell {
#[default]
Powershell,
Bash,
Zsh,
Nu,
}
use clap::Parser;
use sprinkles::shell::Shell;
use strum::IntoEnumIterator;

#[derive(Debug, Clone, Parser)]
pub struct Args {
Expand All @@ -24,6 +14,7 @@ pub struct Args {
impl super::Command for Args {
fn runner(self) -> Result<(), anyhow::Error> {
let shell = self.shell;
let shell_config = shell.config();
let enabled_hooks: Vec<super::CommandsHooks> = super::CommandsHooks::iter()
.filter(|variant| !self.disable.contains(variant))
.collect();
Expand All @@ -34,7 +25,11 @@ impl super::Command for Args {

// I would love to make this all one condition, but Powershell doesn't seem to support that elegantly
for command in enabled_hooks {
print!(" '{command}' {{ return sfsu.exe $args }} ");
print!(
" '{hook}' {{ return sfsu.exe {command} }} ",
hook = command.hook(),
command = command.command()
);
}

println!("default {{ scoop.ps1 @args }} }} }}");
Expand All @@ -44,35 +39,40 @@ impl super::Command for Args {
// println!("# Invoke-Expression (&sfsu hook)");
}
Shell::Bash | Shell::Zsh => {
let hook_list = enabled_hooks.iter().format(" | ");

println!(
"SCOOP_EXEC=$(which scoop) \n\
scoop () {{ \n\
case $1 in \n\
({hook_list}) sfsu.exe $@ ;; \n\
(*) $SCOOP_EXEC $@ ;; \n\
case $1 in"
);

for command in enabled_hooks {
println!(
"({hook}) sfsu.exe {command} ${{@:2}} ;;",
hook = command.hook(),
command = command.command()
);
}

println!(
"(*) $SCOOP_EXEC $@ ;; \n\
esac \n\
}} \n\n\
# Add the following to the end of your ~/.{} \n\
# source <(sfsu.exe hook --shell {shell})",
if shell == Shell::Bash {
"bashrc"
} else {
"zshrc"
}
# Add the following to the end of your ~/.{shell_config} \n\
# source <(sfsu.exe hook --shell {shell})"
);
}
Shell::Nu => {
for command in enabled_hooks {
println!(
"def --wrapped \"scoop {command}\" [...rest] {{ sfsu {command} ...$rest }}"
"def --wrapped \"scoop {hook}\" [...rest] {{ sfsu {command} ...$rest }}",
hook = command.hook(),
command = command.command()
);
}

println!(
"\n# To add this to your config, run `sfsu hook --shell {shell} | save ~/.cache/sfsu.nu`\n\
# And then in your main config add the following line to the end:\n\
# And then in your {shell_config} add the following line to the end:\n\
# source ~/.cache/sfsu.nu"
);
}
Expand Down
Loading