summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcos <cos>2024-05-30 12:18:38 +0200
committercos <cos>2024-05-30 12:18:38 +0200
commit7251320e9928f898d478aa4e5f0551450264cb6a (patch)
tree297dedf73193eacbb2eae8ac2ab3617218cfd55e
downloadcphrs-attendance-7251320e9928f898d478aa4e5f0551450264cb6a.zip
Initial commit, used for meetup#46
-rw-r--r--.gitignore1
-rw-r--r--Cargo.toml11
-rw-r--r--src/main.rs103
3 files changed, 115 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..898f90c
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "cphrs-attendance"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.82"
+html-escape = "0.2.13"
+svg = "0.15.0"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..b92a0eb
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,103 @@
+use {
+ anyhow::{
+ anyhow,
+ Result,
+ },
+ std::{
+ env::args,
+ fs::File,
+ io::prelude::*,
+ },
+ svg::{
+ Document,
+ node::{
+ Text as TextNode,
+ element::{
+ Rectangle,
+ Text,
+ }
+ }
+ }
+};
+
+const COL_WIDTH: f64 = 350.0;
+const ROWS: usize = 35;
+
+fn main() -> Result<()> {
+ let first_arg = args().nth(1).ok_or_else(|| anyhow!("Missing meetup csv file argument."))?;
+ let fullpath = first_arg.clone();
+ let filename = fullpath.split('/').last().unwrap_or(&fullpath);
+ let (basename, _) = filename.split_once('.')
+ .ok_or_else(|| anyhow!("Unexpected filename. Was it downloaded from meetup.com?"))?;
+ let (event_name, _) = filename.split_once("_sponsored")
+ .ok_or_else(|| anyhow!("Unexpected filename. Was it downloaded from meetup.com?"))?;
+
+ let mut csv_file = File::open(first_arg)?;
+ let mut contents = String::new();
+ csv_file.read_to_string(&mut contents)?;
+
+ let mut attendees: Vec<String> = vec![];
+ let mut waitlisted: Vec<String> = vec![];
+
+ for (n, row) in contents.split('\n').enumerate() {
+ let fields: Vec<&str> = row.split('\t').collect();
+ match (fields.get(1), fields.get(4)) {
+ (Some(name), Some(rsvp)) => { },
+ _ => eprintln!("Unparsable row {n}: {}", row),
+ // fields.get(5) "Waiting List"
+ }
+ }
+
+ let page_height = "297mm";
+ let page_width = "210mm";
+ let x_crop = 60.047f64;
+ let y_crop = 110.717f64;
+ // let fontfamily = "Liberation Serif";
+ let fontfamily = "Iosevka";
+ let fontsize = 22.2;
+
+ let title_text = Text::new()
+ .set("x", 1.2 * x_crop)
+ .set("y", 1.2 * y_crop / 2.0)
+ .add(TextNode::new(event_name.chars().map(|c| if c == '_' { ' '} else { c }).collect::<String>()))
+ .set("style", format!("font-size:{fontsize}px;line-height:1.25;font-family:'{fontfamily}'"));
+
+ let mut document = Document::new()
+ .set("viewBox", (0, 0, page_width, page_height))
+ .set("width", page_width)
+ .set("height", page_height)
+ .set("xmlns:inkscape", "http://www.inkscape.org/namespaces/inkscape")
+ .add(title_text);
+
+ for (n, attendee) in contents.split('\n').enumerate() {
+ if let Some((name, _)) = attendee.split_once('\t') {
+ let row = n % ROWS;
+ let col = n / ROWS;
+
+ let attendee_box = Rectangle::new()
+ .set("x", 1.2 * x_crop + col as f64 * COL_WIDTH)
+ .set("y", 1.2 * y_crop + row as f64 * fontsize * 1.2)
+ .set("style", "fill:#ffffff;stroke:#000000;fill-opacity=0.4;opacity=0.4")
+ .set("height", fontsize * 1.2)
+ .set("width", COL_WIDTH);
+
+ let attendee_text = Text::new()
+ .set("x", 1.2 * x_crop + col as f64 * COL_WIDTH + 40.0)
+ .set("y", 1.2 * y_crop + fontsize + row as f64 * fontsize * 1.2)
+ .add(TextNode::new(name))
+ .set("style", format!("font-size:{fontsize}px;line-height:1.25;font-family:'{fontfamily}'"));
+
+ document = document
+ .add(attendee_box)
+ .add(attendee_text);
+ } else {
+ if attendee.is_empty() {
+ continue;
+ }
+ return Err(anyhow!("Could not parse: '{attendee}'"));
+ }
+ }
+
+ svg::save(format!("{basename}.svg"), &document).unwrap();
+ Ok(())
+}