mirror of
https://gitlab.cern.ch/wotsubo/psboard-qaqc-postprocess.git
synced 2025-07-02 01:29:28 +09:00
New: Parsing till position/id assignment
This commit is contained in:
commit
474f2397c6
4 changed files with 899 additions and 0 deletions
341
src/main.rs
Normal file
341
src/main.rs
Normal file
|
@ -0,0 +1,341 @@
|
|||
use core::str;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use clap::Parser;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use regex::Regex;
|
||||
use semver::Version;
|
||||
|
||||
/// Parse master jathub logfile for PS Board QAQC and write out to CSV.
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Master log file.
|
||||
master_log: path::PathBuf,
|
||||
#[command(flatten)]
|
||||
verbose: clap_verbosity_flag::Verbosity,
|
||||
}
|
||||
|
||||
/// Layer
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
enum PositionLayer {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
impl Display for PositionLayer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let c = match self {
|
||||
PositionLayer::A => "A",
|
||||
PositionLayer::B => "B",
|
||||
};
|
||||
write!(f, "{}", c)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PositionLayer {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s {
|
||||
"A" => Ok(Self::A),
|
||||
"B" => Ok(Self::B),
|
||||
_ => Err(anyhow!("Invalid value")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Where PS Board is placed while QAQC.
|
||||
/// TODO: name
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
struct Position {
|
||||
major: PositionLayer,
|
||||
minor: u8,
|
||||
patch: u8,
|
||||
}
|
||||
|
||||
impl Display for Position {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}-{}-{}", self.major, self.minor, self.patch)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Position {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut count = 0;
|
||||
let mut major_pre = None;
|
||||
let mut minor_pre = None;
|
||||
let mut patch_pre = None;
|
||||
for part in s.split('-') {
|
||||
count += 1;
|
||||
match count {
|
||||
1 => major_pre = Some(part.parse()?),
|
||||
2 => minor_pre = Some(part.parse()?),
|
||||
3 => patch_pre = Some(part.parse()?),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if count > 3 {
|
||||
return Err(anyhow!("Invalid Position format"));
|
||||
}
|
||||
|
||||
Ok(Position {
|
||||
major: major_pre.context("No major")?,
|
||||
minor: minor_pre.context("No minor")?,
|
||||
patch: patch_pre.context("No patch")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// PSB ID printed on the tape on the board.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct PsbId {
|
||||
id: u8,
|
||||
}
|
||||
|
||||
impl PsbId {
|
||||
/// Without validation.
|
||||
pub fn new(id: u8) -> Self {
|
||||
PsbId { id }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PsbId {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let num: u8 = s.parse()?;
|
||||
// TODO: add validation
|
||||
warn!("No validation implemented");
|
||||
Ok(PsbId::new(num))
|
||||
}
|
||||
}
|
||||
|
||||
/// QAQC results for each boards extracted from master log.
|
||||
/// TODO: specify each filed types.(maybe multi values)
|
||||
#[derive(Debug)]
|
||||
struct BoardResult {
|
||||
id: PsbId,
|
||||
qspif: bool,
|
||||
qspip: bool,
|
||||
recov: bool,
|
||||
clock: bool,
|
||||
regac: bool,
|
||||
asdtp: bool,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
/// Full result for a single QAQC run from a log file on JATHub master.
|
||||
#[derive(Debug)]
|
||||
struct MasterLogResult {
|
||||
version: Version,
|
||||
datetime: DateTime<Local>,
|
||||
shifter: String,
|
||||
board_results: HashMap<Position, BoardResult>,
|
||||
}
|
||||
|
||||
/// Get version of shift script.
|
||||
fn extract_version(line: &str) -> Result<Version> {
|
||||
Ok(line
|
||||
.split_whitespace()
|
||||
.nth(2)
|
||||
.context("Invalid log format(version)")?
|
||||
.parse()?)
|
||||
}
|
||||
|
||||
/// Get shifters from shifter line
|
||||
fn extract_shifter_line(line: &str) -> Result<String> {
|
||||
let re = Regex::new(r"^Shifters: (.+)$").unwrap();
|
||||
let caps = re.captures(line).unwrap();
|
||||
trace!("Regex {:?}", caps);
|
||||
Ok(caps.get(1).unwrap().as_str().to_owned())
|
||||
}
|
||||
|
||||
/// Get position and PSB ID pair from a master log line.
|
||||
fn extract_position_id(line: &str) -> Result<(Position, PsbId)> {
|
||||
let re = Regex::new(r"Position / assigned-ID : (.+) / (.+)").unwrap();
|
||||
let caps = re.captures(line).context("No capture")?;
|
||||
let pos = Position::from_str(caps.get(1).unwrap().into())?;
|
||||
let id = PsbId::from_str(caps.get(2).unwrap().into())?;
|
||||
Ok((pos, id))
|
||||
}
|
||||
|
||||
impl MasterLogResult {
|
||||
/// Parse log file on master jathub.
|
||||
fn parse_file(file: impl BufRead) -> Result<MasterLogResult> {
|
||||
let mut lines = file.lines();
|
||||
|
||||
let version = {
|
||||
let line = lines.next().context("Invalid log format(no versions)")??;
|
||||
extract_version(&line)?
|
||||
};
|
||||
|
||||
let _sep = lines.next().context("Invalid log format(separator)")??;
|
||||
|
||||
let datetime: DateTime<Utc> = {
|
||||
let line = lines
|
||||
.next()
|
||||
.context("Invalid log format(no datetime line)")??;
|
||||
DateTime::parse_from_str(&line, "Date: %+")
|
||||
.context("Invalid datetime format (must be ISO 8601)")?
|
||||
.to_utc()
|
||||
};
|
||||
|
||||
let shifter = {
|
||||
let line = lines
|
||||
.next()
|
||||
.context("Invalid log format(no shifter line)")??;
|
||||
extract_shifter_line(&line)?
|
||||
};
|
||||
|
||||
let _sep = lines.next().context("Invalid log format")?;
|
||||
|
||||
if !lines
|
||||
.next()
|
||||
.context("Invalid log format")??
|
||||
.starts_with("PBS Assignment:")
|
||||
{
|
||||
return Err(anyhow!("Invalid log format"));
|
||||
}
|
||||
|
||||
let mut assignments = HashMap::new();
|
||||
for i in 0..18 {
|
||||
let line = lines.next().context("Unexpected EOF")??;
|
||||
if line.starts_with("=========") {
|
||||
debug!("End of assignments");
|
||||
break;
|
||||
}
|
||||
let (pos, id) = extract_position_id(&line)?;
|
||||
match assignments.insert(pos.clone(), id) {
|
||||
None => (),
|
||||
Some(old_id) => {
|
||||
return Err(anyhow!(
|
||||
"Value already exists on row {}: {:?} => {:?}",
|
||||
i,
|
||||
pos,
|
||||
old_id
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
trace!("Read all PBS assignments");
|
||||
info!("{:?}", assignments);
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
env_logger::Builder::new()
|
||||
.filter_level(args.verbose.log_level_filter())
|
||||
.init();
|
||||
debug!("Args: {:?}", args);
|
||||
println!("Hello, world!");
|
||||
|
||||
let file = File::open(args.master_log)?;
|
||||
let mut reader = BufReader::new(file);
|
||||
let result = MasterLogResult::parse_file(reader)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::{extract_position_id, extract_shifter_line, Position, PositionLayer, PsbId};
|
||||
|
||||
#[test]
|
||||
fn parse_position() {
|
||||
assert_eq!(
|
||||
Position::from_str("A-1-4").unwrap(),
|
||||
Position {
|
||||
major: PositionLayer::A,
|
||||
minor: 1,
|
||||
patch: 4
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
Position::from_str("A-0-9").unwrap(),
|
||||
Position {
|
||||
major: PositionLayer::A,
|
||||
minor: 0,
|
||||
patch: 9
|
||||
}
|
||||
);
|
||||
assert_ne!(
|
||||
Position::from_str("A-1-4").unwrap(),
|
||||
Position {
|
||||
major: PositionLayer::A,
|
||||
minor: 0,
|
||||
patch: 2
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_datetime() {
|
||||
assert!(DateTime::parse_from_str("2024-06-20T08:42:01+0000", "%+").is_ok());
|
||||
assert!(DateTime::parse_from_str("Date: 2024-06-20T08:42:01+0000", "Date: %+").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_shifter_line() {
|
||||
assert_eq!(extract_shifter_line("Shifters: foo").unwrap(), "foo");
|
||||
assert_eq!(
|
||||
extract_shifter_line("Shifters: foo bar").unwrap(),
|
||||
"foo bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pos_id_line() {
|
||||
assert_eq!(
|
||||
extract_position_id("Position / assigned-ID : A-0-0 / 000004").unwrap(),
|
||||
(
|
||||
Position {
|
||||
major: PositionLayer::A,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
},
|
||||
PsbId::new(4)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
extract_position_id("Position / assigned-ID : A-1-7 / 000108").unwrap(),
|
||||
(
|
||||
Position {
|
||||
major: PositionLayer::A,
|
||||
minor: 1,
|
||||
patch: 7,
|
||||
},
|
||||
PsbId::new(108)
|
||||
)
|
||||
);
|
||||
assert_ne!(
|
||||
extract_position_id("Position / assigned-ID : A-1-7 / 000108").unwrap(),
|
||||
(
|
||||
Position {
|
||||
major: PositionLayer::A,
|
||||
minor: 0,
|
||||
patch: 7,
|
||||
},
|
||||
PsbId::new(106)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue