Skip to main content

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;