UebelAndre | fa943c7 | 2024-10-01 11:42:22 -0700 | [diff] [blame] | 1 | //! Utilities for parsing [Args](https://bazel.build/rules/lib/builtins/Args.html) param files. |
| 2 | |
| 3 | use std::path::Path; |
| 4 | |
| 5 | /// The format for an [Args param file[(https://bazel.build/rules/lib/builtins/Args.html#set_param_file_format). |
| 6 | #[derive(Debug)] |
| 7 | pub enum ActionArgsFormat { |
| 8 | /// Each item (argument name or value) is written verbatim to the param |
| 9 | /// file with a newline character following it. |
| 10 | Multiline, |
| 11 | |
| 12 | /// Same as [Self::Multiline], but the items are shell-quoted. |
| 13 | Shell, |
| 14 | |
| 15 | /// Same as [Self::Multiline], but (1) only flags (beginning with '--') |
| 16 | /// are written to the param file, and (2) the values of the flags, if |
| 17 | /// any, are written on the same line with a '=' separator. This is the |
| 18 | /// format expected by the Abseil flags library. |
| 19 | FlagPerLine, |
| 20 | } |
| 21 | |
| 22 | impl Default for ActionArgsFormat { |
| 23 | fn default() -> Self { |
| 24 | Self::Shell |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | /// Parsed [`ctx.action.args`](https://bazel.build/rules/lib/builtins/Args.html) params. |
| 29 | type ActionArgv = Vec<String>; |
| 30 | |
| 31 | /// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file string into an argv list. |
| 32 | pub fn parse_args_with_fmt(text: String, fmt: ActionArgsFormat) -> ActionArgv { |
| 33 | text.lines() |
| 34 | .map(|s| match fmt { |
| 35 | ActionArgsFormat::Shell => { |
| 36 | if s.starts_with('\'') && s.ends_with('\'') { |
| 37 | s[1..s.len() - 1].to_owned() |
| 38 | } else { |
| 39 | s.to_owned() |
| 40 | } |
| 41 | } |
| 42 | _ => s.to_owned(), |
| 43 | }) |
| 44 | .collect() |
| 45 | } |
| 46 | |
| 47 | /// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file string into an argv list. |
| 48 | pub fn parse_args(text: String) -> ActionArgv { |
| 49 | parse_args_with_fmt(text, ActionArgsFormat::default()) |
| 50 | } |
| 51 | |
| 52 | /// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file into an argv list. |
| 53 | pub fn try_parse_args_with_fmt( |
| 54 | path: &Path, |
| 55 | fmt: ActionArgsFormat, |
| 56 | ) -> Result<ActionArgv, std::io::Error> { |
| 57 | let text = std::fs::read_to_string(path)?; |
| 58 | Ok(parse_args_with_fmt(text, fmt)) |
| 59 | } |
| 60 | |
| 61 | /// Parse an [Args](https://bazel.build/rules/lib/builtins/Args.html) param file into an argv list. |
| 62 | pub fn try_parse_args(path: &Path) -> Result<ActionArgv, std::io::Error> { |
| 63 | let text = std::fs::read_to_string(path)?; |
| 64 | Ok(parse_args(text)) |
| 65 | } |
| 66 | |
| 67 | #[cfg(test)] |
| 68 | mod test { |
| 69 | use std::path::PathBuf; |
| 70 | |
| 71 | use super::*; |
| 72 | |
| 73 | const TEST_ARGS: [&str; 5] = ["foo", "-bar", "'baz'", "'--qux=quux'", "--quuz='corge'"]; |
| 74 | |
| 75 | #[test] |
| 76 | fn test_multiline_string() { |
| 77 | let text = TEST_ARGS.join("\n"); |
| 78 | |
| 79 | let args = parse_args_with_fmt(text, ActionArgsFormat::Multiline); |
| 80 | assert_eq!( |
| 81 | vec!["foo", "-bar", "'baz'", "'--qux=quux'", "--quuz='corge'"], |
| 82 | args |
| 83 | ) |
| 84 | } |
| 85 | |
| 86 | #[test] |
| 87 | fn test_shell_string() { |
| 88 | let text = TEST_ARGS.join("\n"); |
| 89 | |
| 90 | let args = parse_args_with_fmt(text, ActionArgsFormat::Shell); |
| 91 | assert_eq!( |
| 92 | vec!["foo", "-bar", "baz", "--qux=quux", "--quuz='corge'"], |
| 93 | args |
| 94 | ) |
| 95 | } |
| 96 | |
| 97 | #[test] |
| 98 | fn test_flag_per_line_string() { |
| 99 | let text = TEST_ARGS.join("\n"); |
| 100 | |
| 101 | let args = parse_args_with_fmt(text, ActionArgsFormat::FlagPerLine); |
| 102 | assert_eq!( |
| 103 | vec!["foo", "-bar", "'baz'", "'--qux=quux'", "--quuz='corge'"], |
| 104 | args |
| 105 | ) |
| 106 | } |
| 107 | |
| 108 | #[test] |
| 109 | fn test_from_file() { |
| 110 | let text = TEST_ARGS.join("\n"); |
| 111 | |
| 112 | let test_tempdir = PathBuf::from(std::env::var("TEST_TMPDIR").unwrap()); |
| 113 | let test_file = test_tempdir.join("test_from_file.txt"); |
| 114 | |
| 115 | assert!(try_parse_args(&test_file).is_err()); |
| 116 | |
| 117 | std::fs::write(&test_file, text).unwrap(); |
| 118 | |
| 119 | let args = try_parse_args(&test_file).unwrap(); |
| 120 | assert_eq!( |
| 121 | vec!["foo", "-bar", "baz", "--qux=quux", "--quuz='corge'"], |
| 122 | args |
| 123 | ) |
| 124 | } |
| 125 | } |