scaffold: CLI surface with stubbed kernel and patch verbs
Add the spectral command tree -- kernel quest/test and patch check/format/commit/create/submit/update -- with every verb wired through main to a todo!() body, plus the pieces they will share: - config: resolve the kernel tree from $SPECTRAL_KERNEL, falling back to ~/.spectral/linux, patches under ~/.spectral/patches, and confirm a tree really is one before anything uses it - git: a thin wrapper over the git binary, currently only run - error: one thiserror enum, converted once in main - patch/checkpatch and patch/maintainers: seams around the tree's own scripts/checkpatch.pl and scripts/get_maintainer.pl, so spectral never carries a stale copy of the kernel's rules 20 functions are todo!(); the #[allow(dead_code)] sites mark the items that are unreachable only until their caller is written. fmt, clippy -D warnings, and test are all clean.
This commit is contained in:
+149
@@ -0,0 +1,149 @@
|
||||
//! The whole command surface, in one file, so the shape of the CLI reads at a
|
||||
//! glance. Nothing here does any work — every variant is dispatched in `main`.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "spectral",
|
||||
version,
|
||||
about = "A monolithic kernel work wrapper to make it easy.",
|
||||
subcommand_required = true,
|
||||
arg_required_else_help = true
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Find work, build it, boot it
|
||||
Kernel {
|
||||
#[command(subcommand)]
|
||||
command: KernelCommand,
|
||||
},
|
||||
/// Carry a change from working tree to mailing list
|
||||
Patch {
|
||||
#[command(subcommand)]
|
||||
command: PatchCommand,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum KernelCommand {
|
||||
/// Fetch a random open issue to work on
|
||||
Quest(QuestArgs),
|
||||
/// Build the kernel and boot it under qemu
|
||||
Test(TestArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct QuestArgs {
|
||||
/// Print the picked issue as JSON
|
||||
#[arg(long)]
|
||||
pub json: bool,
|
||||
/// Only consider issues whose title contains this text
|
||||
#[arg(long, value_name = "TEXT")]
|
||||
pub filter: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct TestArgs {
|
||||
/// Boot the existing build instead of rebuilding first
|
||||
#[arg(long)]
|
||||
pub no_build: bool,
|
||||
/// Rootfs image to boot with; defaults to the tree's own test image
|
||||
#[arg(long, value_name = "PATH")]
|
||||
pub rootfs: Option<PathBuf>,
|
||||
/// Extra argument passed through to qemu (repeatable)
|
||||
#[arg(long = "qemu-arg", value_name = "ARG", allow_hyphen_values = true)]
|
||||
pub qemu_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum PatchCommand {
|
||||
/// Run checkpatch.pl over the work in progress
|
||||
Check(CheckArgs),
|
||||
/// Auto-fix what checkpatch.pl reports
|
||||
Format(FormatArgs),
|
||||
/// Commit the work in progress with a kernel-style message
|
||||
Commit(CommitArgs),
|
||||
/// Write the diff against the base branch out to <NAME>.patch
|
||||
Create(CreateArgs),
|
||||
/// Send a patch, with To/CC taken from get_maintainer.pl
|
||||
Submit(SubmitArgs),
|
||||
/// Re-roll a patch as v<N>, renaming the file to match
|
||||
Update(UpdateArgs),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct CheckArgs {
|
||||
/// Check this patch file instead of the working tree
|
||||
#[arg(value_name = "PATCH")]
|
||||
pub patch: Option<PathBuf>,
|
||||
/// Pass --strict to checkpatch.pl
|
||||
#[arg(long)]
|
||||
pub strict: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct FormatArgs {
|
||||
/// Fix this patch file instead of the working tree
|
||||
#[arg(value_name = "PATCH")]
|
||||
pub patch: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct CommitArgs {
|
||||
/// Commit message, without the Signed-off-by line
|
||||
#[arg(value_name = "MESSAGE")]
|
||||
pub message: String,
|
||||
/// Append your Signed-off-by, which the kernel requires
|
||||
#[arg(short = 's', long)]
|
||||
pub signoff: bool,
|
||||
/// Amend the previous commit instead of adding one
|
||||
#[arg(long)]
|
||||
pub amend: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct CreateArgs {
|
||||
/// Patch name; `.patch` is appended if you leave it off
|
||||
#[arg(value_name = "NAME")]
|
||||
pub name: String,
|
||||
/// Branch or revision to diff against
|
||||
#[arg(long, value_name = "REV", default_value = "master")]
|
||||
pub base: String,
|
||||
/// Directory to write the patch into; defaults to the patch dir
|
||||
#[arg(long, value_name = "DIR")]
|
||||
pub out: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct SubmitArgs {
|
||||
/// Patch file to send
|
||||
#[arg(value_name = "PATCH")]
|
||||
pub patch: PathBuf,
|
||||
/// Print the recipients and command instead of sending
|
||||
#[arg(long)]
|
||||
pub dry_run: bool,
|
||||
/// Extra address to CC (repeatable)
|
||||
#[arg(long = "cc", value_name = "ADDR")]
|
||||
pub cc: Vec<String>,
|
||||
/// Message-Id of the patch this one re-rolls, so mail threads correctly
|
||||
#[arg(long, value_name = "MSGID")]
|
||||
pub in_reply_to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct UpdateArgs {
|
||||
/// Patch file to re-roll
|
||||
#[arg(value_name = "PATCH")]
|
||||
pub patch: PathBuf,
|
||||
/// Revision to re-roll as; defaults to one past the file's current vN
|
||||
#[arg(short = 'v', long, value_name = "N")]
|
||||
pub revision: Option<u32>,
|
||||
}
|
||||
Reference in New Issue
Block a user