Skip to content

Commit

Permalink
macro overhaul
Browse files Browse the repository at this point in the history
  • Loading branch information
Milo123459 committed Feb 13, 2025
1 parent ce63bfd commit 673e3f8
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 38 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ path = "src/main.rs"

[dependencies]
anyhow = "1.0.95"
clap = { version = "4.5.23", features = ["derive", "suggestions"] }
clap = { version = "4.5.23", features = ["derive", "suggestions", "cargo"] }
colored = "2.2.0"
dirs = "5.0.1"
serde = { version = "1.0.217", features = ["derive"] }
Expand Down Expand Up @@ -56,6 +56,7 @@ url = "2.5.4"
futures = { version = "0.3.31", default-features = false, features = [
"compat",
"io-compat",
"executor"
] }
names = { version = "0.14.0", default-features = false }
graphql-ws-client = { version = "0.11.1", features = [
Expand Down
2 changes: 1 addition & 1 deletion src/commands/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub struct Args {
pub async fn command(args: Args, _json: bool) -> Result<()> {
generate(
args.shell,
&mut crate::Args::command(),
&mut self::Args::command(),
"railway",
&mut io::stdout(),
);
Expand Down
6 changes: 6 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ pub(super) use anyhow::{Context, Result};
pub(super) use clap::{Parser, Subcommand};
pub(super) use colored::Colorize;

pub fn get_dynamic_args(cmd: clap::Command) -> clap::Command {
// no-op
cmd
}

pub mod add;
pub mod completion;
pub mod connect;
Expand All @@ -20,6 +25,7 @@ pub mod logs;
pub mod open;
pub mod redeploy;
pub mod run;
pub mod scale;
pub mod service;
pub mod shell;
pub mod starship;
Expand Down
62 changes: 62 additions & 0 deletions src/commands/scale.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::config;
use clap::{Arg, Command};
use futures::executor::block_on;
use serde::Serialize;

use super::{
queries::{
projects::ProjectsProjectsEdgesNode, user_projects::UserProjectsMeProjectsEdgesNode,
},
*,
};

/// List all projects in your Railway account
#[derive(Parser, Debug)]
pub struct Args {}

pub fn get_dynamic_args(cmd: Command) -> Command {
if !std::env::args().any(|f| f.eq_ignore_ascii_case("scale")) {
// if the command has nothing to do with railway scale, dont make the web request.
return cmd;
}
block_on(async move {
let configs = Configs::new().unwrap();
let client = GQLClient::new_authorized(&configs).unwrap();
let regions = post_graphql::<queries::Regions, _>(
&client,
configs.get_backboard(),
queries::regions::Variables,
)
.await
.expect("couldn't get regions");

// Collect region names as owned Strings.
let region_strings = regions
.regions
.iter()
.map(|r| r.name.to_string())
.collect::<Vec<String>>();

// Mutate the command to add each region as a flag.
let mut new_cmd = cmd;
for region in region_strings {
let region_static: &'static str = Box::leak(region.into_boxed_str());
new_cmd = new_cmd.arg(
Arg::new(region_static) // unique identifier
.long(region_static) // --my-region
.help(format!("Number of instances to run on {}", region_static))
.value_name("INSTANCES")
.value_parser(clap::value_parser!(u16))
.action(clap::ArgAction::Set)
);
}
new_cmd
})
}

pub async fn command(args: Args, json: bool) -> Result<()> {
// Args::command().;
println!("hello");
println!("{:?}", args);
Ok(())
}
8 changes: 8 additions & 0 deletions src/gql/queries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,11 @@ pub struct GitHubRepos;
response_derives = "Debug, Serialize, Clone"
)]
pub struct CustomDomainAvailable;

#[derive(GraphQLQuery)]
#[graphql(
schema_path = "src/gql/schema.json",
query_path = "src/gql/queries/strings/Regions.graphql",
response_derives = "Debug, Serialize, Clone"
)]
pub struct Regions;
5 changes: 5 additions & 0 deletions src/gql/queries/strings/Regions.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query Regions {
regions {
name
}
}
76 changes: 59 additions & 17 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
#[macro_export]
macro_rules! commands_enum {
// Case when command has aliases (e.g. add(a, b, c))
($($module:ident $(($($alias:ident),*))?),*) => (
macro_rules! commands {
($($module:ident $(($($alias:ident),*))?),*) => {
paste::paste! {
#[derive(Subcommand)]
enum Commands {
/// Build the global CLI (root command) and attach module subcommands.
pub fn build_args() -> clap::Command {
// Use your desired root name here (for example, "railway" rather than a derived name).
let mut cmd = clap::Command::new("railway")
.about("Railway CLI")
.author(clap::crate_authors!())
.propagate_version(true)
.about(clap::crate_description!())
.long_about(None)
.version(clap::crate_version!())
.arg(
clap::Arg::new("json")
.long("json")
.action(clap::ArgAction::SetTrue)
.help("Output in JSON format")
);
$(
#[clap(
$(visible_aliases = &[$( stringify!($alias) ),*])?
)]
[<$module:camel>]($module::Args),
{
// Get the subcommand as defined by the module.
let sub = <$module::Args as ::clap::CommandFactory>::command();
// Allow the module to add dynamic arguments (if needed) and add any aliases.
let sub = {
let mut s = sub;
$(
s = s.visible_alias($( stringify!($alias) ),*);
)?
#[allow(unused_imports)]
{
// First import any definitions from the module.
// Then import everything (including the fallback).
use $crate::commands::get_dynamic_args;
{
use $crate::commands::$module::*;
s = get_dynamic_args(s);
}
}
s = s.name(stringify!($module));
s
};
// Add this subcommand into the global CLI.
cmd = cmd.subcommand(sub);
}
)*
cmd
}

impl Commands {
async fn exec(cli: Args) -> Result<()> {
match cli.command {
$(
Commands::[<$module:camel>](args) => $module::command(args, cli.json).await?,
)*
/// Dispatches the selected subcommand (after parsing) to its handler.
pub async fn exec_cli(matches: clap::ArgMatches) -> anyhow::Result<()> {
match matches.subcommand() {
$(
Some((stringify!([<$module:snake>]), sub_matches)) => {
let args = <$module::Args as ::clap::FromArgMatches>::from_arg_matches(sub_matches)
.map_err(anyhow::Error::from)?;
$module::command(args, matches.get_flag("json")).await?;
},
)*
_ => {
build_args().print_help()?;
println!();
}
Ok(())
}
Ok(())
}
}
);
};
}

use is_terminal::IsTerminal;
Expand Down
24 changes: 5 additions & 19 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anyhow::Result;
use clap::{Parser, Subcommand};

mod commands;
use commands::*;
Expand All @@ -17,22 +16,9 @@ mod util;
#[macro_use]
mod macros;

/// Interact with 🚅 Railway via CLI
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
pub struct Args {
#[clap(subcommand)]
command: Commands,

/// Output in JSON format
#[clap(global = true, long)]
json: bool,
}

// Generates the commands based on the modules in the commands directory
// Specify the modules you want to include in the commands_enum! macro
commands_enum!(
commands!(
add,
completion,
connect,
Expand All @@ -58,14 +44,14 @@ commands_enum!(
variables,
whoami,
volume,
redeploy
redeploy,
scale
);

#[tokio::main]
async fn main() -> Result<()> {
let cli = Args::parse();

match Commands::exec(cli).await {
let args = build_args().get_matches();
match exec_cli(args).await {
Ok(_) => {}
Err(e) => {
// If the user cancels the operation, we want to exit successfully
Expand Down

0 comments on commit 673e3f8

Please sign in to comment.