use std::collections::HashMap;
use std::fmt;
use serde::Serialize;
#[derive(Clone, Serialize)]
pub struct FixtureType {
name: String,
channels: HashMap<String, u16>,
pub max_strobe_frequency: Option<f64>,
pub min_strobe_frequency: Option<f64>,
pub strobe_dmx_offset: Option<u8>,
}
impl FixtureType {
pub fn new(name: String, channels: HashMap<String, u16>) -> FixtureType {
FixtureType {
name,
channels,
max_strobe_frequency: None,
min_strobe_frequency: None,
strobe_dmx_offset: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn channels(&self) -> &HashMap<String, u16> {
&self.channels
}
pub fn max_strobe_frequency(&self) -> Option<f64> {
self.max_strobe_frequency
}
pub fn min_strobe_frequency(&self) -> Option<f64> {
self.min_strobe_frequency
}
pub fn strobe_dmx_offset(&self) -> Option<u8> {
self.strobe_dmx_offset
}
}
impl fmt::Display for FixtureType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "fixture_type \"{}\" {{", self.name)?;
writeln!(f, " channels: {}", self.channels.len())?;
writeln!(f, " channel_map: {{")?;
let mut entries: Vec<_> = self.channels.iter().collect();
entries.sort_by_key(|(_, v)| *v);
for (i, (name, offset)) in entries.iter().enumerate() {
let comma = if i + 1 < entries.len() { "," } else { "" };
writeln!(f, " \"{}\": {}{}", name, offset, comma)?;
}
writeln!(f, " }}")?;
if let Some(v) = self.max_strobe_frequency {
writeln!(f, " max_strobe_frequency: {v}")?;
}
if let Some(v) = self.min_strobe_frequency {
writeln!(f, " min_strobe_frequency: {v}")?;
}
if let Some(v) = self.strobe_dmx_offset {
writeln!(f, " strobe_dmx_offset: {v}")?;
}
write!(f, "}}")
}
}
#[derive(Clone, Serialize)]
pub struct Fixture {
name: String,
fixture_type: String,
universe: u16,
start_channel: u16,
tags: Vec<String>,
}
impl Fixture {
pub fn new(
name: String,
fixture_type: String,
universe: u16,
start_channel: u16,
tags: Vec<String>,
) -> Fixture {
Fixture {
name,
fixture_type,
universe,
start_channel,
tags,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn fixture_type(&self) -> &str {
&self.fixture_type
}
pub fn universe(&self) -> u16 {
self.universe
}
pub fn start_channel(&self) -> u16 {
self.start_channel
}
pub fn tags(&self) -> &[String] {
&self.tags
}
}
#[derive(Clone, Serialize)]
pub struct Group {
name: String,
fixtures: Vec<String>,
}
impl Group {
pub fn new(name: String, fixtures: Vec<String>) -> Group {
Group { name, fixtures }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn fixtures(&self) -> &[String] {
&self.fixtures
}
}
#[derive(Clone, Serialize)]
pub struct Venue {
name: String,
fixtures: HashMap<String, Fixture>,
groups: HashMap<String, Group>,
}
impl Venue {
pub fn new(
name: String,
fixtures: HashMap<String, Fixture>,
groups: HashMap<String, Group>,
) -> Venue {
Venue {
name,
fixtures,
groups,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn fixtures(&self) -> &HashMap<String, Fixture> {
&self.fixtures
}
pub fn groups(&self) -> &HashMap<String, Group> {
&self.groups
}
}
impl fmt::Display for Venue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "venue \"{}\" {{", self.name)?;
let mut fixtures: Vec<_> = self.fixtures.values().collect();
fixtures.sort_by_key(|fix| (fix.universe, fix.start_channel));
for fix in &fixtures {
write!(
f,
" fixture \"{}\" {} @ {}:{}",
fix.name, fix.fixture_type, fix.universe, fix.start_channel
)?;
if !fix.tags.is_empty() {
let tags: Vec<String> = fix.tags.iter().map(|t| format!("\"{t}\"")).collect();
write!(f, " tags [{}]", tags.join(", "))?;
}
writeln!(f)?;
}
let mut groups: Vec<_> = self.groups.values().collect();
groups.sort_by_key(|g| g.name());
for group in &groups {
writeln!(
f,
" group \"{}\" = {}",
group.name,
group.fixtures.join(", ")
)?;
}
write!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixture_type_new() {
let mut channels = HashMap::new();
channels.insert("red".to_string(), 1);
channels.insert("green".to_string(), 2);
channels.insert("blue".to_string(), 3);
let ft = FixtureType::new("RGB Par".to_string(), channels);
assert_eq!(ft.name(), "RGB Par");
assert_eq!(ft.channels().len(), 3);
assert_eq!(ft.max_strobe_frequency(), None);
assert_eq!(ft.min_strobe_frequency(), None);
assert_eq!(ft.strobe_dmx_offset(), None);
}
#[test]
fn fixture_type_strobe_fields() {
let mut ft = FixtureType::new("Strobe".to_string(), HashMap::new());
ft.max_strobe_frequency = Some(25.0);
ft.min_strobe_frequency = Some(1.0);
ft.strobe_dmx_offset = Some(128);
assert_eq!(ft.max_strobe_frequency(), Some(25.0));
assert_eq!(ft.min_strobe_frequency(), Some(1.0));
assert_eq!(ft.strobe_dmx_offset(), Some(128));
}
#[test]
fn fixture_new() {
let f = Fixture::new(
"par1".to_string(),
"RGB Par".to_string(),
1,
10,
vec!["front".to_string(), "wash".to_string()],
);
assert_eq!(f.name(), "par1");
assert_eq!(f.fixture_type(), "RGB Par");
assert_eq!(f.universe(), 1);
assert_eq!(f.start_channel(), 10);
assert_eq!(f.tags(), &["front", "wash"]);
}
#[test]
fn fixture_no_tags() {
let f = Fixture::new("spot1".to_string(), "Spot".to_string(), 2, 1, vec![]);
assert!(f.tags().is_empty());
}
#[test]
fn group_new() {
let g = Group::new(
"front_wash".to_string(),
vec!["par1".to_string(), "par2".to_string()],
);
assert_eq!(g.name(), "front_wash");
assert_eq!(g.fixtures().len(), 2);
assert_eq!(g.fixtures()[0], "par1");
}
#[test]
fn group_empty_fixtures() {
let g = Group::new("empty".to_string(), vec![]);
assert!(g.fixtures().is_empty());
}
#[test]
fn venue_new() {
let mut fixtures = HashMap::new();
fixtures.insert(
"par1".to_string(),
Fixture::new("par1".to_string(), "RGB".to_string(), 1, 1, vec![]),
);
let mut groups = HashMap::new();
groups.insert(
"all".to_string(),
Group::new("all".to_string(), vec!["par1".to_string()]),
);
let v = Venue::new("Club".to_string(), fixtures, groups);
assert_eq!(v.name(), "Club");
assert_eq!(v.fixtures().len(), 1);
assert!(v.fixtures().contains_key("par1"));
assert_eq!(v.groups().len(), 1);
assert!(v.groups().contains_key("all"));
}
}