git_policy_cli/dependent_version_publishable.rs
1//! What: Read the publishable package names from the generated registry configuration
2//! (`package/config/pnpr/config.yaml`).
3//! Why: Only published packages receive a dependent bump. The generator writes each name as
4//! a single-quoted YAML list item on its own line under `packages:`, so a line scan
5//! reads exactly what it wrote, as the incumbent does (`publishable-names.ts:38-81`).
6//! Lines are trimmed with JavaScript's `String.prototype.trim` set of characters.
7//!
8//! In TS you'd write (pseudocode):
9//! ```ts
10//! // readPublishableNames(configText);
11//! ```
12
13/// What: The key line that opens the package list.
14/// Why: The first line that trims to exactly this starts the list.
15///
16/// In TS you'd write (pseudocode):
17/// ```ts
18/// const PACKAGES_KEY = 'packages:';
19/// ```
20const PACKAGES_KEY: &str = "packages:";
21
22/// What: The start of one single-quoted list item.
23/// Why: An item is `- '<name>'`; the name is what lies between the quotes.
24///
25/// In TS you'd write (pseudocode):
26/// ```ts
27/// const ITEM_PREFIX = "- '";
28/// ```
29const ITEM_PREFIX: &str = "- '";
30
31/// What: Whether a character is one `String.prototype.trim` removes: ECMAScript
32/// `WhiteSpace` and `LineTerminator`. `matches!` tests a value against patterns;
33/// `..=` is an inclusive range.
34/// Why: Rust's `str::trim` differs: it keeps U+FEFF and removes U+0085.
35///
36/// In TS you'd write (pseudocode):
37/// ```ts
38/// const isTrimmed = (c: string) => c.trim() === '';
39/// ```
40fn is_js_whitespace(character: char) -> bool {
41 return matches!(
42 character,
43 '\u{9}'..='\u{d}'
44 | ' '
45 | '\u{a0}'
46 | '\u{1680}'
47 | '\u{2000}'..='\u{200a}'
48 | '\u{2028}'
49 | '\u{2029}'
50 | '\u{202f}'
51 | '\u{205f}'
52 | '\u{3000}'
53 | '\u{feff}'
54 );
55}
56
57/// What: The list item's name, or nothing when the line is not an item.
58/// `.strip_prefix`/`.strip_suffix` remove a known start or end.
59/// Why: The first line that is not a non-empty quoted item ends the list.
60///
61/// In TS you'd write (pseudocode):
62/// ```ts
63/// const isItem = trimmed.startsWith("- '") && trimmed.endsWith("'") && trimmed.length > 4;
64/// ```
65fn item_name(trimmed: &str) -> Option<&str> {
66 return trimmed
67 .strip_prefix(ITEM_PREFIX)
68 .and_then(|rest: &str| return rest.strip_suffix('\''))
69 .filter(|name: &&str| return !name.is_empty());
70}
71
72/// What: The names in the `packages:` list, in configuration order; nothing when the list
73/// is absent. `.split('\n')` keeps a carriage return on each line, which the trim
74/// removes.
75/// Why: The generator's output is the publish set.
76///
77/// In TS you'd write (pseudocode):
78/// ```ts
79/// function readPublishableNames(configText: string): string[];
80/// ```
81pub fn read_publishable_names(config_text: &str) -> Vec<String> {
82 let mut lines = config_text
83 .split('\n')
84 .map(|line: &str| return line.trim_matches(is_js_whitespace));
85 // `.any(...)` consumes lines up to and including the key line.
86 if !lines.any(|line: &str| return line == PACKAGES_KEY) {
87 return Vec::new();
88 }
89 return lines.map_while(item_name).map(String::from).collect();
90}
91
92/// Publishable-name reading, including every case of the incumbent's unit tests.
93#[cfg(test)]
94#[path = "dependent_version_publishable_tests.rs"]
95mod tests;