1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
use std::str; use std::process::Command; use regex::Regex; use super::Error; /// Items that the Arduino CLI can be queried for. pub enum Query { Fqbn, Port } /// Extracts the item associated with a given query by calling the Arduino CLI - or more /// specifically `arduino-cli board list`. /// /// # Errors /// * `CommandFailure`, if the `arduino-cli` command fails or produces non-UTF-8 output. /// * `NoDevice`, if no Arduino is connected to the computer during the call. /// * `MultipleDevices`, if more than one Arduino is connected to the computer during the call. /// * `UnexpectedSyntax`, if the call to the Arduino CLI produced an output in a different format /// than expected. pub fn query(query: Query) -> Result<String, Error> { // Asks the Arduino CLI for connected Arduinos. let output = Command::new("arduino-cli") .args(&["board", "list"]) .output(); // Makes sure the call to the Arduino CLI even worked. match output { Ok(output) => { // Turns the result of the previous call into a string. let board_list = match str::from_utf8(&output.stdout) { Ok(board_list) => board_list, Err(_) => return Err(Error::CommandFailure) }; query_from_board_list(query, board_list) } Err(_) => Err(Error::CommandFailure) } } // Extracts a given query item from a given output of "arduino-cli board list". If that is not // possible an error is returned. fn query_from_board_list(query: Query, board_list: &str) -> Result<String, Error> { let emtpy_line = Regex::new(r"^\s*$").unwrap(); // A container in which the single board entry will be placed. let mut board_entry: Option<&str> = None; // Fills the board entry container with the single board's entry, and returns an error if // multiple entries were found. // The first line in the board list is the header and is therefore skipped. for line in board_list.lines().skip(1) { if !emtpy_line.is_match(line) { if board_entry.is_none() { board_entry = Some(line); } else { return Err(Error::MultipleDevices); } } } // If the board entry container is still empty at this point, no Arduino was found. if let Some(board_entry) = board_entry { query_from_board_entry(query, board_entry) } else { Err(Error::NoDevice) } } // Extracts a given query item from a given board entry of the output of "arduino-cli board // list". // The entry is expected to have the format: // <fqbn> <port> <id> <board name> // If it does not, an error is returned. fn query_from_board_entry(query: Query, board_entry: &str) -> Result<String, Error> { // The required field count is 4, as the expected format of a board list enty above shows. // The field count might be higher though, as a field may contain white space. This will // not affect the current query items though, as they will not contain whitespace (?). const REQUIRED_FIELD_COUNT: u8 = 4; let mut field_count = 0; let query_column = match query { Query::Fqbn => 1, Query::Port => 2, }; let mut query_item: Option<&str> = None; // Iterates over the fields in the entry, on the one hand to extract the query item, and on // the other hand to count the number of fields. for field in board_entry.split_whitespace() { field_count += 1; if field_count == query_column { query_item = Some(field); } } // The query item container will definitely contain a value, if the field count has reached // the required field count. if field_count >= REQUIRED_FIELD_COUNT { Ok(String::from(query_item.unwrap())) } else { Err(Error::UnexpectedSyntax) } }