// Copyright 2020 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::fs::File; use std::io::{BufRead, BufReader, Read}; pub(crate) fn read_file_to_array(path: &str) -> Result, String> { let file = File::open(path).map_err(|e| e.to_string()).map_err(|err| { format!( "{} reading path: {:?}, current directory: {:?}", err, path, std::env::current_dir() ) })?; read_to_array(file) } pub(crate) fn read_stamp_status_to_array(path: String) -> Result, String> { let file = File::open(path).map_err(|e| e.to_string())?; stamp_status_to_array(file) } fn read_to_array(reader: impl Read) -> Result, String> { let reader = BufReader::new(reader); let mut ret = vec![]; let mut escaped_line = String::new(); for l in reader.lines() { let line = l.map_err(|e| e.to_string())?; if line.is_empty() { continue; } // a \ at the end of a line allows us to escape the new line break, // \\ yields a single \, so \\\ translates to a single \ and a new line // escape let end_backslash_count = line.chars().rev().take_while(|&c| c == '\\').count(); // a 0 or even number of backslashes do not lead to a new line escape let escape = end_backslash_count % 2 == 1; // remove backslashes and add back two for every one let l = line.trim_end_matches('\\'); escaped_line.push_str(l); for _ in 0..end_backslash_count / 2 { escaped_line.push('\\'); } if escape { // we add a newline as we expect a line after this escaped_line.push('\n'); } else { ret.push(escaped_line); escaped_line = String::new(); } } Ok(ret) } fn stamp_status_to_array(reader: impl Read) -> Result, String> { let escaped_lines = read_to_array(reader)?; escaped_lines .into_iter() .map(|l| { let (s1, s2) = l .split_once(' ') .ok_or_else(|| format!("wrong workspace status file format for \"{l}\""))?; Ok((s1.to_owned(), s2.to_owned())) }) .collect() } #[cfg(test)] mod test { use super::*; #[test] fn test_read_to_array() { let input = r"some escaped \\\ string with other lines" .to_owned(); let expected = vec![ r"some escaped \ string", "with other lines", ]; let got = read_to_array(input.as_bytes()).unwrap(); assert_eq!(expected, got); } #[test] fn test_stamp_status_to_array() { let lines = "aaa bbb\\\nvvv\nccc ddd\neee fff"; let got = stamp_status_to_array(lines.as_bytes()).unwrap(); let expected = vec![ ("aaa".to_owned(), "bbb\nvvv".to_owned()), ("ccc".to_owned(), "ddd".to_owned()), ("eee".to_owned(), "fff".to_owned()), ]; assert_eq!(expected, got); } }