use std::str;
use std::process::Command;
use regex::Regex;
use super::Error;
#[derive(Clone, Copy)]
pub enum Query { Fqbn, Port }
pub fn query(query: Query) -> Result<String, Error> {
let output = Command::new("arduino-cli")
.args(&["board", "list"])
.output();
match output {
Ok(output) => {
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)
}
}
fn query_from_board_list(query: Query, board_list: &str) -> Result<String, Error> {
let emtpy_line = Regex::new(r"^\s*$").unwrap();
let mut board_entry: Option<&str> = None;
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 let Some(board_entry) = board_entry {
query_from_board_entry(query, board_entry)
} else {
Err(Error::NoDevice)
}
}
fn query_from_board_entry(query: Query, board_entry: &str) -> Result<String, Error> {
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;
for field in board_entry.split('\t') {
field_count += 1;
if field_count == query_column { query_item = Some(field); }
}
if field_count == REQUIRED_FIELD_COUNT {
Ok(String::from(query_item.unwrap()))
} else {
Err(Error::UnexpectedSyntax)
}
}
#[cfg(test)]
mod tests {
use super::*;
const DEVICE_LIST_HEADER: &'static str = "FQBN\tPort\tID\tBoard Name";
const SOME_QUERY_ITEM: &'static str = "Query::Item/1'\\\"_";
const SOME_QUERY: Query = Query::Fqbn;
#[test]
fn no_devices() {
let no_device_list = format!("{}\n", DEVICE_LIST_HEADER);
let result = query_from_board_list(SOME_QUERY, &no_device_list);
let err = result.unwrap_err();
assert_eq!(err, Error::NoDevice);
}
#[test]
fn multiple_devices() {
let multi_device_list = format!("{}\n1\t2\t3\t4\nA\tB\tC\tD\n", DEVICE_LIST_HEADER);
let result = query_from_board_list(SOME_QUERY, &multi_device_list);
let err = result.unwrap_err();
assert_eq!(err, Error::MultipleDevices);
}
#[test]
fn unexpected_syntax() {
const INVALID_ENTRY: &'static str = "1\t2\t3\t4\t5\n";
let result = query_from_board_entry(SOME_QUERY, INVALID_ENTRY);
let err = result.unwrap_err();
assert_eq!(err, Error::UnexpectedSyntax);
}
#[test]
fn valid_list() {
let valid_list = format!("{}\n\t\n1\t2\t3\t4\n\t\n", DEVICE_LIST_HEADER);
let result = query_from_board_list(SOME_QUERY, &valid_list);
assert!(result.is_ok());
}
#[test]
fn valid_fqbn_query() {
let board_list = format!("{}\n{}\t2\t3\t4\n\t\n", DEVICE_LIST_HEADER, SOME_QUERY_ITEM);
let query = Query::Fqbn;
let result = query_from_board_list(query, &board_list);
let query_item = result.unwrap();
assert_eq!(query_item, SOME_QUERY_ITEM);
}
#[test]
fn valid_port_query() {
let board_list = format!("{}\n1\t{}\t3\t4\n\t\n", DEVICE_LIST_HEADER, SOME_QUERY_ITEM);
let query = Query::Port;
let result = query_from_board_list(query, &board_list);
let query_item = result.unwrap();
assert_eq!(query_item, SOME_QUERY_ITEM);
}
}