use std::collections::BTreeMap;
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Default,
clap::ValueEnum,
serde::Serialize,
serde::Deserialize,
)]
pub enum Profile {
#[default]
Engineer,
Business,
Station,
}
impl Profile {
#[must_use]
pub fn from_str_case_insensitive(s: &str) -> Self {
match s.to_lowercase().as_str() {
"business" => Self::Business,
"station" => Self::Station,
_ => Self::Engineer,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VariableScope {
Locals,
Parameters,
FileGlobals,
StationGlobals,
}
impl VariableScope {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Locals => "Locals",
Self::Parameters => "Parameters",
Self::FileGlobals => "FileGlobals",
Self::StationGlobals => "StationGlobals",
}
}
}
#[derive(Debug, Clone)]
#[allow(
clippy::struct_excessive_bools,
reason = "CLI generator configuration flags"
)]
pub struct ExtractorConfig {
pub profile: Profile,
pub rules: crate::rules::DocumentRules,
pub only_sequences: Vec<String>,
pub include_process_models: bool,
pub include_scopes: Vec<VariableScope>,
pub ignore_skipped: bool,
pub include_flowcharts: bool,
pub include_station_options: bool,
pub include_types: bool,
pub include_file_custom_data_types: bool,
pub types_attached_only: bool,
pub estimate_software_delays: bool,
pub detailed_popup_messages: bool,
pub author: String,
pub company: String,
pub email: String,
pub version: String,
pub show_paths: bool,
pub recurse_subsequences: bool,
pub max_depth: usize,
pub company_logo: Option<String>,
}
impl Default for ExtractorConfig {
fn default() -> Self {
Self {
profile: Profile::Engineer,
rules: crate::rules::DocumentRules::for_profile(Profile::Engineer),
only_sequences: Vec::new(),
include_process_models: false,
include_scopes: vec![
VariableScope::Locals,
VariableScope::Parameters,
VariableScope::FileGlobals,
VariableScope::StationGlobals,
],
ignore_skipped: false,
include_flowcharts: true,
include_station_options: false,
include_types: false,
include_file_custom_data_types: false,
types_attached_only: true,
estimate_software_delays: false,
detailed_popup_messages: true,
author: String::new(),
company: String::new(),
email: String::new(),
version: "1.0.0".to_owned(),
show_paths: false,
recurse_subsequences: true,
max_depth: 3,
company_logo: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Variable {
pub name: String,
pub type_name: String,
pub default_value: Option<String>,
pub comment: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct FieldData {
pub name: String,
pub type_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct EnumeratorData {
pub name: String,
pub value: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CustomDataType {
pub name: String,
pub type_display: String,
pub fields: Vec<FieldData>,
pub enumerators: Vec<EnumeratorData>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Limits {
pub low: String,
pub high: String,
pub target: String,
pub comp: String,
pub unit: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MeasurementData {
pub name: String,
pub limits: Limits,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ModuleInfo {
pub path: String,
pub adapter_type: String,
pub occurrences: usize,
pub entry_point: Option<String>,
pub extra: BTreeMap<String, String>,
}
impl ExtractorConfig {
#[must_use]
pub fn for_profile(profile: Profile) -> Self {
Self {
profile,
rules: crate::rules::DocumentRules::for_profile(profile),
..Self::default()
}
}
}
#[derive(
Debug,
Clone,
Copy,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Serialize,
serde::Deserialize,
)]
pub enum CallKind {
#[default]
Sequential,
NewThread,
NewExecution,
Remote,
}
impl CallKind {
#[must_use]
pub const fn from_option(option: i64) -> Self {
if option & 0x100 != 0 {
return Self::Remote;
}
match option & 0xff {
1 => Self::NewThread,
2 => Self::NewExecution,
_ => Self::Sequential,
}
}
#[must_use]
pub const fn describe(self) -> Option<&'static str> {
match self {
Self::Sequential => None,
Self::NewThread => Some("in a new thread"),
Self::NewExecution => Some("as a new execution"),
Self::Remote => Some("on another machine"),
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct StepData {
pub id: String,
pub name: String,
pub step_type: String,
pub adapter: String,
pub description: String,
pub comment: String,
pub precondition: String,
pub module_path: String,
pub module_info: Option<ModuleInfo>,
pub limits: Limits,
pub measurements: Vec<MeasurementData>,
pub target_sequence: String,
pub call_kind: CallKind,
pub target_by_expression: bool,
pub expressions: BTreeMap<String, String>,
pub step_settings: BTreeMap<String, String>,
pub requirements: Vec<String>,
pub run_mode: String,
pub skipped: bool,
pub estimated_software_delay: Option<f64>,
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum SequenceCategory {
EntryPoint = 0,
ModelCallback = 1,
EngineCallback = 2,
FrontEndCallback = 3,
Callback = 4,
Subsequence = 5,
}
impl SequenceCategory {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::EntryPoint => "Entry Point",
Self::ModelCallback => "Model Callback",
Self::EngineCallback => "Engine Callback",
Self::FrontEndCallback => "Front-End Callback",
Self::Callback => "Callback",
Self::Subsequence => "Subsequence",
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct SequenceData {
pub name: String,
pub category: Option<SequenceCategory>,
pub comment: String,
pub record_results: Option<bool>,
pub failure_action: Option<String>,
pub requirements: Vec<String>,
pub variables: BTreeMap<String, Vec<Variable>>,
pub step_groups: BTreeMap<String, Vec<StepData>>,
pub estimated_software_delay: Option<f64>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct FileData {
pub name: String,
pub path: String,
pub sequences: Vec<SequenceData>,
pub depth: usize,
pub file_globals: Vec<Variable>,
pub station_globals: Vec<Variable>,
pub file_version: String,
pub model_file: String,
pub load_opt: String,
pub unload_opt: String,
pub requirements: Vec<String>,
pub custom_data_types: Vec<CustomDataType>,
pub estimated_software_delay: Option<f64>,
}
pub const STEP_GROUP_EXECUTION_ORDER: [&str; 3] = ["Setup", "Main", "Cleanup"];
impl SequenceData {
#[must_use]
pub fn step_groups_in_execution_order(&self) -> Vec<(&String, &Vec<StepData>)> {
let mut ordered: Vec<(&String, &Vec<StepData>)> = Vec::new();
for wanted in STEP_GROUP_EXECUTION_ORDER {
if let Some((name, steps)) = self.step_groups.get_key_value(wanted) {
ordered.push((name, steps));
}
}
for (name, steps) in &self.step_groups {
if !STEP_GROUP_EXECUTION_ORDER.contains(&name.as_str()) {
ordered.push((name, steps));
}
}
ordered
}
}
#[cfg(test)]
mod execution_order_tests {
use super::{SequenceData, StepData};
use std::collections::BTreeMap;
fn sequence_with(groups: &[&str]) -> SequenceData {
let mut step_groups = BTreeMap::new();
for name in groups {
step_groups.insert((*name).to_owned(), vec![StepData::default()]);
}
SequenceData {
step_groups,
..SequenceData::default()
}
}
#[test]
fn groups_come_back_in_the_order_the_engine_runs_them() {
let sequence = sequence_with(&["Cleanup", "Main", "Setup"]);
let order: Vec<&str> = sequence
.step_groups_in_execution_order()
.iter()
.map(|(name, _)| name.as_str())
.collect();
assert_eq!(order, ["Setup", "Main", "Cleanup"]);
}
#[test]
fn a_missing_group_is_skipped_rather_than_invented() {
let sequence = sequence_with(&["Cleanup", "Main"]);
let order: Vec<&str> = sequence
.step_groups_in_execution_order()
.iter()
.map(|(name, _)| name.as_str())
.collect();
assert_eq!(order, ["Main", "Cleanup"]);
}
#[test]
fn a_custom_group_is_kept_after_the_standard_three() {
let sequence = sequence_with(&["Main", "Setup", "Diagnostics"]);
let order: Vec<&str> = sequence
.step_groups_in_execution_order()
.iter()
.map(|(name, _)| name.as_str())
.collect();
assert_eq!(order, ["Setup", "Main", "Diagnostics"]);
}
}