Skip to content

Commit a0a7604

Browse files
authored
Add copyright command. (#22)
Initial work towards copyright header verification. For now, hardcoded for Prep itself.
1 parent ebb166d commit a0a7604

File tree

9 files changed

+248
-61
lines changed

9 files changed

+248
-61
lines changed

Cargo.lock

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ rust.missing_docs = "warn"
1616
[workspace.dependencies]
1717
anyhow = "1.0.100"
1818
clap = "4.5.56"
19+
time = "0.3.46"

prep/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ workspace = true
2121
[dependencies]
2222
anyhow.workspace = true
2323
clap = { workspace = true, features = ["derive"] }
24+
time.workspace = true

prep/src/cmd/copyright.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2026 the Prep Authors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
use std::process::Command;
5+
6+
use anyhow::{Context, bail, ensure};
7+
use time::UtcDateTime;
8+
9+
use crate::ui;
10+
use crate::ui::style::{ERROR, HEADER, LITERAL, NOTE};
11+
12+
// TODO: Allow configuring the regex
13+
// TODO: Allow excluding files from the check
14+
// TODO: ALlow configuring the project name
15+
// TODO: Allow configuring the license (or fetch it from Cargo.toml per-package?)
16+
17+
const REGEX: &str = r#"^// Copyright (19|20)[\d]{2} (.+ and )?the Prep Authors( and .+)?$\n^// SPDX-License-Identifier: Apache-2\.0 OR MIT$\n\n"#;
18+
19+
/// Verify copyright headers.
20+
pub fn run() -> anyhow::Result<()> {
21+
let mut cmd = Command::new("rg");
22+
let cmd = cmd
23+
.arg(REGEX)
24+
.arg("--files-without-match")
25+
.arg("--multiline")
26+
.args(["-g", "*.rs"])
27+
.arg(".");
28+
29+
ui::print_cmd(cmd);
30+
31+
let output = cmd.output().context("failed to run ripgrep")?;
32+
33+
// ripgrep exits with code 1 in case of no matches, code 2 in case of error
34+
ensure!(
35+
output.status.success() || output.status.code().is_some_and(|code| code == 1),
36+
"ripgrep failed: {}",
37+
output.status
38+
);
39+
40+
if !output.stdout.is_empty() {
41+
print_missing(String::from_utf8(output.stdout).unwrap());
42+
bail!("failed copyright header verification");
43+
}
44+
45+
let h = HEADER;
46+
eprintln!(" {h}Verified{h:#} all source files have correct copyright headers.");
47+
48+
Ok(())
49+
}
50+
51+
fn print_missing(msg: String) {
52+
let (e, l, n) = (ERROR, LITERAL, NOTE);
53+
let year = UtcDateTime::now().year();
54+
55+
eprintln!("{e}The following files lack the correct copyright header:{e:#}");
56+
eprintln!("{l}{msg}{l:#}");
57+
eprintln!("{n}Please add the following header:{n:#}\n");
58+
eprintln!("// Copyright {year} the Prep Authors");
59+
eprintln!("// SPDX-License-Identifier: Apache-2.0 OR MIT");
60+
eprintln!("\n... rest of the file ...\n");
61+
}

prep/src/cmd/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use clap::ValueEnum;
55

66
pub mod ci;
77
pub mod clippy;
8+
pub mod copyright;
89
pub mod format;
910

1011
/// Cargo targets.

prep/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ enum Commands {
3535
#[arg(short, long)]
3636
strict: bool,
3737
},
38+
#[command()]
39+
Copyright,
3840
#[command(alias = "fmt")]
3941
Format {
4042
#[arg(short, long)]
@@ -58,6 +60,7 @@ fn main() -> anyhow::Result<()> {
5860
no_fail_fast,
5961
} => cmd::ci::run(extended, !no_fail_fast),
6062
Commands::Clippy { targets, strict } => cmd::clippy::run(targets, strict),
63+
Commands::Copyright => cmd::copyright::run(),
6164
Commands::Format { check } => cmd::format::run(check),
6265
}
6366
}

prep/src/ui/help.rs

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use clap::Command;
55
use clap::builder::StyledStr;
66

7-
use crate::ui;
7+
use crate::ui::style::{HEADER, LITERAL, PLACEHOLDER};
88

99
/// Sets our custom help messages.
1010
pub fn set(cmd: Command) -> Command {
@@ -18,6 +18,8 @@ pub fn set(cmd: Command) -> Command {
1818
scmd.override_help(format_msg())
1919
} else if name == "clippy" {
2020
scmd.override_help(clippy_msg())
21+
} else if name == "copyright" {
22+
scmd.override_help(copyright_msg())
2123
} else {
2224
panic!("Sub-command '{name}' help message is not implemented");
2325
}
@@ -26,22 +28,23 @@ pub fn set(cmd: Command) -> Command {
2628

2729
/// Returns the main help message.
2830
pub fn root_msg() -> StyledStr {
29-
let (gb, cb, bb) = ui::styles();
31+
let (h, l, p) = (HEADER, LITERAL, PLACEHOLDER);
3032
let help = format!(
3133
"\
3234
Prepare Rust projects for greatness.
3335
34-
{gb}Usage:{gb:#} {cb}prep{cb:#} {bb}[command] [options]{bb:#}
36+
{h}Usage:{h:#} {l}prep{l:#} {p}[command] [options]{p:#}
3537
36-
{gb}Commands:{gb:#}
37-
{cb} ci {cb:#}Verify for CI.
38-
{cb}clp clippy {cb:#}Analyze with Clippy.
39-
{cb}fmt format {cb:#}Format with rustfmt.
40-
{cb} help {cb:#}Print help for the provided command.
38+
{h}Commands:{h:#}
39+
{l} ci {l:#}Verify for CI.
40+
{l}clp clippy {l:#}Analyze with Clippy.
41+
{l} copyright {l:#}Verify copyright headers.
42+
{l}fmt format {l:#}Format with rustfmt.
43+
{l} help {l:#}Print help for the provided command.
4144
42-
{gb}Options:{gb:#}
43-
{cb}-h --help {cb:#}Print help for the provided command.
44-
{cb}-V --version {cb:#}Print version information.
45+
{h}Options:{h:#}
46+
{l}-h --help {l:#}Print help for the provided command.
47+
{l}-V --version {l:#}Print version information.
4548
"
4649
);
4750

@@ -50,64 +53,80 @@ Prepare Rust projects for greatness.
5053

5154
/// Returns the `ci` help message.
5255
fn ci_msg() -> StyledStr {
53-
let (gb, cb, bb) = ui::styles();
54-
56+
let (h, l, p) = (HEADER, LITERAL, PLACEHOLDER);
5557
let help = format!(
5658
"\
5759
Verify the Rust workspace for CI.
5860
59-
{gb}Usage:{gb:#} {cb}prep ci{cb:#} {bb}[options]{bb:#}
61+
{h}Usage:{h:#} {l}prep ci{l:#} {p}[options]{p:#}
6062
61-
{gb}Options:{gb:#}
62-
{cb}-e --extended {cb:#}Run the extended verification suite.
63-
···· ······Good idea for actual CI, rarely useful for local prep.
64-
{cb}-n --no-fail-fast {cb:#}Keep going when encountering an error.
65-
{cb}-h --help {cb:#}Print this help message.
63+
{h}Options:{h:#}
64+
{l}-e --extended {l:#}Run the extended verification suite.
65+
··· ·····Good idea for actual CI, rarely useful for local prep.
66+
{l}-n --no-fail-fast {l:#}Keep going when encountering an error.
67+
{l}-h --help {l:#}Print this help message.
6668
"
6769
)
6870
.replace("·", "");
6971

7072
StyledStr::from(help)
7173
}
7274

73-
/// Returns the `format` help message.
74-
fn format_msg() -> StyledStr {
75-
let (gb, cb, bb) = ui::styles();
75+
/// Returns the `clippy` help message.
76+
fn clippy_msg() -> StyledStr {
77+
let (h, l, p) = (HEADER, LITERAL, PLACEHOLDER);
78+
let help = format!(
79+
"\
80+
Analyze the Rust workspace with Clippy.
81+
82+
{h}Usage:{h:#} {l}prep clp{l:#} {p}[options]{p:#}
83+
··· ····· {l}prep clippy{l:#} {p}[options]{p:#}
7684
85+
{h}Options:{h:#}
86+
{l}-c --crates <val> {l:#}Target specified crates. Possible values:
87+
··· ·····{p}main{p:#} -> Binaries and the main library. (default)
88+
··· ·····{p}aux{p:#} -> Examples, tests, and benches.
89+
··· ·····{p}all{p:#} -> All of the above.
90+
{l}-s --strict {l:#}Treat warnings as errors.
91+
{l}-h --help {l:#}Print this help message.
92+
"
93+
)
94+
.replace("·", "");
95+
96+
StyledStr::from(help)
97+
}
98+
99+
/// Returns the `copyright` help message.
100+
fn copyright_msg() -> StyledStr {
101+
let (h, l) = (HEADER, LITERAL);
77102
let help = format!(
78103
"\
79-
Format the Rust workspace with rustfmt.
104+
Verify that all Rust source files have the correct copyright header.
80105
81-
{gb}Usage:{gb:#} {cb}prep fmt{cb:#} {bb}[options]{bb:#}
82-
···· ······ {cb}prep format{cb:#} {bb}[options]{bb:#}
106+
{h}Usage:{h:#} {l}prep copyright{l:#}
83107
84-
{gb}Options:{gb:#}
85-
{cb}-c --check {cb:#}Verify that the workspace is already formatted.
86-
{cb}-h --help {cb:#}Print this help message.
108+
{h}Options:{h:#}
109+
{l}-h --help {l:#}Print this help message.
87110
"
88111
)
89112
.replace("·", "");
90113

91114
StyledStr::from(help)
92115
}
93116

94-
/// Returns the `clippy` help message.
95-
fn clippy_msg() -> StyledStr {
96-
let (gb, cb, bb) = ui::styles();
117+
/// Returns the `format` help message.
118+
fn format_msg() -> StyledStr {
119+
let (h, l, p) = (HEADER, LITERAL, PLACEHOLDER);
97120
let help = format!(
98121
"\
99-
Analyze the Rust workspace with Clippy.
122+
Format the Rust workspace with rustfmt.
100123
101-
{gb}Usage:{gb:#} {cb}prep clp{cb:#} {bb}[options]{bb:#}
102-
···· ······ {cb}prep clippy{cb:#} {bb}[options]{bb:#}
124+
{h}Usage:{h:#} {l}prep fmt{l:#} {p}[options]{p:#}
125+
··· ····· {l}prep format{l:#} {p}[options]{p:#}
103126
104-
{gb}Options:{gb:#}
105-
{cb}-c --crates <val> {cb:#}Target specified crates. Possible values:
106-
···· ······{bb}main{bb:#} -> Binaries and the main library. (default)
107-
···· ······{bb}aux{bb:#} -> Examples, tests, and benches.
108-
···· ······{bb}all{bb:#} -> All of the above.
109-
{cb}-s --strict {cb:#}Treat warnings as errors.
110-
{cb}-h --help {cb:#}Print this help message.
127+
{h}Options:{h:#}
128+
{l}-c --check {l:#}Verify that the workspace is already formatted.
129+
{l}-h --help {l:#}Print this help message.
111130
"
112131
)
113132
.replace("·", "");

0 commit comments

Comments
 (0)