Skip to content

Commit 0c231fb

Browse files
committed
Add targeted build rendering
Add build-only render selection from --only and workspace [render].only, deduping proposal numbers into ResolvedExecution. Build OnlyRenderPlan during prepared runtime setup, rewrite omitted proposal links and requires entries to public URLs, and prune unselected proposal content before Zola runs. Restrict targeted rendering to local dirty build mode for now, leaving targeted serve sync to the next PR. Remove the eipw lint step from the prepared runtime pipeline so linting is reached only through editorial commands.
1 parent 30ca66c commit 0c231fb

7 files changed

Lines changed: 668 additions & 27 deletions

File tree

src/cli.rs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};
1111
use clap::{Parser, Subcommand};
1212
use url::Url;
1313

14-
use crate::{lint, print};
14+
use crate::{lint, print, proposal::ProposalNumber};
1515

1616
/// Build script for Ethereum EIPs and ERCs.
1717
#[derive(Parser, Debug)]
@@ -66,6 +66,13 @@ pub(crate) struct CleanCliArgs {
6666
pub(crate) clean: bool,
6767
}
6868

69+
#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)]
70+
pub(crate) struct OnlyCliArgs {
71+
/// Render only the selected proposal number(s)
72+
#[arg(long, value_name = "NUMBER", value_parser = ProposalNumber::parse_cli_selector, num_args = 1..)]
73+
pub(crate) only: Vec<ProposalNumber>,
74+
}
75+
6976
#[derive(Debug, Clone, Subcommand)]
7077
pub(crate) enum Operation {
7178
/// Print linter schema metadata and lint configuration
@@ -81,6 +88,9 @@ pub(crate) enum Operation {
8188

8289
#[command(flatten)]
8390
clean: CleanCliArgs,
91+
92+
#[command(flatten)]
93+
only: OnlyCliArgs,
8494
},
8595

8696
/// Serve the existing built output without rebuilding it
@@ -153,7 +163,7 @@ pub(crate) enum ProfiledOperation {
153163
base_url: BaseUrlCliArgs,
154164
},
155165

156-
/// Build the project and launch a web server to preview it
166+
/// Build a fresh temporary site, serve it locally, and watch tracked edits
157167
Serve {
158168
#[command(flatten)]
159169
server: ServerCliArgs,
@@ -272,6 +282,22 @@ impl Operation {
272282
}
273283
}
274284

285+
pub(crate) fn only_cli_args(&self) -> Option<&OnlyCliArgs> {
286+
match self {
287+
Self::Build { only, .. } => Some(only),
288+
Self::Print { .. }
289+
| Self::Preview { .. }
290+
| Self::Serve { .. }
291+
| Self::Clean
292+
| Self::Check { .. }
293+
| Self::Changed { .. }
294+
| Self::Editorial { .. }
295+
| Self::Init { .. }
296+
| Self::Doctor
297+
| Self::Parity { .. } => None,
298+
}
299+
}
300+
275301
pub(crate) fn is_plain_site_command(&self) -> bool {
276302
matches!(
277303
self,
@@ -315,14 +341,14 @@ impl ProfiledOperation {
315341
fn server_cli_args(&self) -> ServerCliArgs {
316342
match self {
317343
Self::Serve { server, .. } => server.clone(),
318-
Self::Build { .. } | Self::Check { .. } => ServerCliArgs::default(),
344+
Self::Build { .. } | Self::Check => ServerCliArgs::default(),
319345
}
320346
}
321347

322348
fn base_url_cli_args(&self) -> BaseUrlCliArgs {
323349
match self {
324350
Self::Build { base_url, .. } | Self::Serve { base_url, .. } => base_url.clone(),
325-
Self::Check { .. } => BaseUrlCliArgs::default(),
351+
Self::Check => BaseUrlCliArgs::default(),
326352
}
327353
}
328354

@@ -382,6 +408,8 @@ impl EditorialSelectorArgs {
382408
mod tests {
383409
use clap::Parser;
384410

411+
use crate::proposal::ProposalNumber;
412+
385413
use super::{Args, EditorialCommand, Operation, ProfiledOperation, RuntimeOperation};
386414

387415
fn parse_args(arguments: &[&str]) -> Args {
@@ -420,6 +448,54 @@ mod tests {
420448
.contains(&format!("unexpected argument '{removed_flag}'")));
421449
}
422450

451+
#[test]
452+
fn only_flag_parses_one_or_more_proposal_numbers_on_build() {
453+
let one = parse_args(&["build-eips", "build", "--only", "00555"]);
454+
let many = parse_args(&["build-eips", "build", "--only", "555", "678", "897"]);
455+
456+
match one.operation {
457+
Operation::Build { only, .. } => {
458+
assert_eq!(only.only, vec![ProposalNumber::from_u32(555).unwrap()]);
459+
}
460+
other => panic!("unexpected operation: {other:?}"),
461+
}
462+
match many.operation {
463+
Operation::Build { only, .. } => {
464+
assert_eq!(
465+
only.only,
466+
vec![
467+
ProposalNumber::from_u32(555).unwrap(),
468+
ProposalNumber::from_u32(678).unwrap(),
469+
ProposalNumber::from_u32(897).unwrap(),
470+
]
471+
);
472+
}
473+
other => panic!("unexpected operation: {other:?}"),
474+
}
475+
}
476+
477+
#[test]
478+
fn only_flag_rejects_invalid_selectors_and_non_build_commands() {
479+
for selector in [
480+
"+555",
481+
"0",
482+
"-555",
483+
"abc",
484+
"555,678",
485+
"content/00555.md",
486+
"4294967296",
487+
] {
488+
assert!(
489+
Args::try_parse_from(["build-eips", "build", "--only", selector]).is_err(),
490+
"expected `{selector}` to be rejected"
491+
);
492+
}
493+
494+
assert!(Args::try_parse_from(["build-eips", "serve", "--only", "555"]).is_err());
495+
assert!(Args::try_parse_from(["build-eips", "check", "--only", "555"]).is_err());
496+
assert!(Args::try_parse_from(["build-eips", "parity", "build", "--only", "555"]).is_err());
497+
}
498+
423499
#[test]
424500
fn base_url_flags_parse_on_build_and_serve_forms() {
425501
let cases: &[(&[&str], &str)] = &[

src/config.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
1414
use snafu::{Backtrace, IntoError, OptionExt, ResultExt, Snafu};
1515
use url::{Position, Url};
1616

17+
use crate::proposal::ProposalNumber;
18+
1719
pub const LOCAL_CONFIG_FILE: &str = ".build-eips.toml";
1820
pub const REPO_MANIFEST_FILE: &str = ".build-eips.repo.toml";
1921
pub const DEFAULT_BUILD_ROOT_BASE: &str = ".local-build";
@@ -468,17 +470,31 @@ pub struct WorkspaceConfig {
468470
/// Local rendered-site URL defaults for build and serve commands.
469471
#[serde(default)]
470472
pub site: SiteSettings,
473+
474+
/// Local render filtering defaults.
475+
#[serde(default)]
476+
pub render: RenderSettings,
471477
}
472478

473479
impl WorkspaceConfig {
474480
fn starter() -> Self {
475481
Self {
476482
server: ServerSettings::default(),
477483
site: SiteSettings::starter(),
484+
render: RenderSettings::default(),
478485
}
479486
}
480487
}
481488

489+
/// Local render filtering settings.
490+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
491+
#[serde(default, deny_unknown_fields)]
492+
pub struct RenderSettings {
493+
/// Proposal numbers to render for applicable local build commands.
494+
#[serde(default)]
495+
pub only: Vec<ProposalNumber>,
496+
}
497+
482498
/// Workspace-local bind address defaults for local server commands.
483499
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
484500
#[serde(default, deny_unknown_fields)]
@@ -642,6 +658,10 @@ impl LoadedWorkspaceConfig {
642658
&self.config.site
643659
}
644660

661+
pub fn render_settings(&self) -> &RenderSettings {
662+
&self.config.render
663+
}
664+
645665
pub fn local_theme_path(&self) -> PathBuf {
646666
self.workspace_root.join(DEFAULT_THEME_DIR)
647667
}
@@ -689,6 +709,7 @@ mod tests {
689709
RepoManifestError, ServerBinding, ServerSettings, WorkspaceError, DEFAULT_SERVER_HOST,
690710
DEFAULT_SERVER_PORT, DEFAULT_SITE_BASE_URL, LOCAL_CONFIG_FILE, REPO_MANIFEST_FILE,
691711
};
712+
use crate::proposal::ProposalNumber;
692713

693714
struct TestRepo {
694715
tempdir: TempDir,
@@ -941,9 +962,10 @@ base_url = "https://staging.example.test/ERCs/"
941962
assert!(original.contains("port = 1111"));
942963
assert!(original.contains("[site]"));
943964
assert!(original.contains(&format!("base_url = \"{DEFAULT_SITE_BASE_URL}\"")));
965+
assert!(original.contains("[render]"));
966+
assert!(original.contains("only = []"));
944967
assert!(!original.contains("default_profile"));
945968
assert!(!original.contains("[profiles"));
946-
assert!(!original.contains("[render]"));
947969
}
948970

949971
#[test]
@@ -1074,6 +1096,72 @@ base_url = "http://127.0.0.1:1111"
10741096

10751097
assert_eq!(config.server_settings(), &ServerSettings::default());
10761098
assert!(config.site_settings().base_url.is_none());
1099+
assert!(config.render_settings().only.is_empty());
1100+
}
1101+
1102+
#[test]
1103+
fn parses_workspace_config_render_only_settings() {
1104+
let repo = TestRepo::new();
1105+
let config_path = repo.write_file(
1106+
LOCAL_CONFIG_FILE,
1107+
r#"
1108+
[render]
1109+
only = [555, 678, 555]
1110+
"#,
1111+
);
1112+
1113+
let config = LoadedWorkspaceConfig::from_path(&config_path).unwrap();
1114+
1115+
assert_eq!(
1116+
config.render_settings().only,
1117+
vec![
1118+
ProposalNumber::from_u32(555).unwrap(),
1119+
ProposalNumber::from_u32(678).unwrap(),
1120+
ProposalNumber::from_u32(555).unwrap(),
1121+
]
1122+
);
1123+
}
1124+
1125+
#[test]
1126+
fn missing_render_missing_only_and_empty_only_disable_filtering() {
1127+
let cases = [
1128+
("missing render", ""),
1129+
("missing only", "[render]\n"),
1130+
("empty only", "[render]\nonly = []\n"),
1131+
];
1132+
1133+
for (name, contents) in cases {
1134+
let repo = TestRepo::new();
1135+
let config_path = repo.write_file(LOCAL_CONFIG_FILE, contents);
1136+
let config = LoadedWorkspaceConfig::from_path(&config_path).unwrap();
1137+
1138+
assert!(
1139+
config.render_settings().only.is_empty(),
1140+
"expected `{name}` to disable render filtering"
1141+
);
1142+
}
1143+
}
1144+
1145+
#[test]
1146+
fn workspace_config_render_only_rejects_non_positive_and_non_integer_values() {
1147+
let cases = [
1148+
("zero", "only = [0]"),
1149+
("negative", "only = [-555]"),
1150+
("quoted", "only = [\"555\"]"),
1151+
("overflow", "only = [4294967296]"),
1152+
];
1153+
1154+
for (name, contents) in cases {
1155+
let repo = TestRepo::new();
1156+
let config_path =
1157+
repo.write_file(LOCAL_CONFIG_FILE, &format!("[render]\n{contents}\n"));
1158+
let error = LoadedWorkspaceConfig::from_path(&config_path).unwrap_err();
1159+
1160+
assert!(
1161+
matches!(error, WorkspaceError::Parse { .. }),
1162+
"expected `{name}` render only config to fail, got {error:?}"
1163+
);
1164+
}
10771165
}
10781166

10791167
#[test]

src/editorial.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ base_url = "https://staging.example.test/{sibling_id}/"
573573
other_repos: Default::default(),
574574
},
575575
theme_path: Some(PathBuf::from("/workspace/theme")),
576+
only: None,
576577
source_materialization: crate::git::SourceMaterialization::Clean,
577578
server_binding: ServerBinding::default(),
578579
base_url_override: None,
@@ -756,6 +757,7 @@ base_url = "https://staging.example.test/{sibling_id}/"
756757
other_repos: Default::default(),
757758
},
758759
theme_path: Some(PathBuf::from("/workspace/theme")),
760+
only: None,
759761
source_materialization: crate::git::SourceMaterialization::Clean,
760762
server_binding: ServerBinding::default(),
761763
base_url_override: None,

0 commit comments

Comments
 (0)