From 5bd1ffb4581d0bb717b2c11bb5fdf34267fa69d6 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 01:58:05 -0400 Subject: [PATCH 01/22] creates full calendar w/ weeks numbered; shades days with the word exam --- examples/calendar_example.typ | 8 ++ src/calendars.typ | 241 ++++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 examples/calendar_example.typ create mode 100644 src/calendars.typ diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ new file mode 100644 index 0000000..d66f93e --- /dev/null +++ b/examples/calendar_example.typ @@ -0,0 +1,8 @@ +#import "@preview/codepoint:0.2.1":labs +#import "../src/calendars.typ" + +#show: calendars.init + +#calendars.header((8, 12), (23, 12), title: "CS-1181 FA26") + +nsddsssssddddddxdddjdsddd \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ new file mode 100644 index 0000000..448cef8 --- /dev/null +++ b/src/calendars.typ @@ -0,0 +1,241 @@ +#let months = ( + ("January", 31), + ("February", 28), + ("March", 31), + ("April", 30), + ("May", 31), + ("June", 30), + ("July", 31), + ("August", 31), + ("September", 30), + ("October", 31), + ("November", 30), + ("December", 31) +) + +#let day-arr = ( + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +) + +/// Initialize a lab with a show rule +/// +/// +/// Example: +/// ```typst +/// #show: labs.init +/// ``` +/// - body (content): body fo lab problem +#let init(body) = { + + assert( + type(body) == content or type(body) == str, + message: "Expected body to be content or str, but received" + str(type(body)) + ) + + + set page(margin: 20pt, width: 8.5in, height: auto) + set text( + font: ("Roboto"), + size: 11pt, + fill: black, + weight: "regular" + ) + set raw(theme: "../themes/codepoint.tmTheme") + show raw: set text(font: ("Courier", "Courier Prime"), weight: "bold", size: 10pt) + + // defaults to 1.2, but on labs specifically, this is not enough spacing + set par(spacing: 1.6em) + + body +} + +#let days-of-week(is-mon-start: false) = { + let days = day-arr + if is-mon-start { + let old = days.remove(0) + days.push("Sunday") + } + + table( + columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr), + align: center, + stroke: none, + ..days.flatten() + ) +} + +#let month-header(month-id, is-mon-start: false) = { + assert( + type(month-id) == int, + message: "Expected month-id to be int, but received" + str(type(month-id)) + ) + + v(-10pt) + text[== #months.at(month-id).at(0)] + line(length: 100%, stroke: 1pt) + v(-15pt) + days-of-week(is-mon-start: is-mon-start) + v(-15pt) + line(length: 100%, stroke: 1pt) +} + +#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false) = { + let max-days = months.at(month-id).at(1) + + if last-month-max != none { + max-days = last-month-max + } + + if is-leap-year and month-id == 1 { + max-days = max-days + 1; + } + + let day-nums = () + let day = start + let reach-max = false + let week-count = last-week-num + + while calc.rem(day-nums.len(), 8) != 0 or not reach-max { + if calc.rem(day-nums.len(), 8) == 0 { + day-nums.push("week #" + str(week-count)) + week-count = week-count + 1 + } + day-nums.push(str(day)) + + day = day + 1 + if day > max-days { + day = 1 + reach-max = true + } + } + return (day-nums, week-count) +} + +#let construct-month-table(days) = { + v(-18pt) + + show table.cell: it => { + // matchs to the text "week #" followed by a one or two digit number + // rotates and bolds the text + show regex("week #\d{1,2}"): it => text(size:11pt, weight: "bold")[#rotate(-90deg, reflow: true)[#v(-20pt)#it]] + it + } + table( + columns: (0fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr), + align: center, + rows: 60pt, + ..days.map(text-str => { + table.cell(fill: if text-str.contains("exam") { rgb("#a12310").lighten(40%) } else { none })[#text-str] + }) + //..days.flatten() + ) +} + +/// header: Render the document section header block for lab problems +/// - startMonth (content, str): Class name +/// - title (content, str): The title text for the specific lab problem +/// - number (int, string, none): Lab problem number, if applicable +#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false) = { + assert( + type(month-range) == array, + message: "Expected month-range to be array, but received " + str(type(month-range)) + ) + + assert( + type(month-range.at(0)) == int, + message: "Expected month-range to be array of int, but received " + str(type(month-range.at(0))) + ) + + assert( + type(month-range.at(1)) == int, + message: "Expected month-range to be array of int, but received " + str(type(month-range.at(1))) + ) + + assert( + month-range.at(1) > 0 and month-range.at(1) < 13, + message: "Second month-range value must be >0 and <13" + ) + + assert( + month-range.at(0) > 0 and month-range.at(0) < month-range.at(1), + message: "First month-range value must be >0 and <" + str(month-range.at(1)) + ) + + assert( + type(day-range) == array, + message: "Expected day-range to be array, but received " + str(type(day-range)) + ) + + assert( + type(day-range.at(0)) == int, + message: "Expected day-range to be array of int, but received " + str(type(day-range.at(0))) + ) + + assert( + type(day-range.at(1)) == int, + message: "Expected day-range to be array of int, but received " + str(type(day-range.at(1))) + ) + + assert( + day-range.at(0) > 0 and day-range.at(0) < 32 and day-range.at(1) > 0 and day-range.at(1) < 32, + message: "Day-range values must be >0 and <32" + ) + + assert( + type(title) == str or title == none, + message: "Expected title to be string, or none, but received " + str(type(title)) + ) + + assert( + type(is-leap-year) == bool or is-leap-year == none, + message: "Expected is-leap-year to be bool, or none, but received " + str(type(is-leap-year)) + ) + + assert( + type(is-mon-start) == bool or is-mon-start == none, + message: "Expected is-mon-start to be bool, or none, but received " + str(type(is-mon-start)) + ) + + if title != none { + text[= #title] + line(length: 100%, stroke: 2pt) + v(5pt) + } + + let month = month-range.at(0) - 1 + let start-day = day-range.at(0) + let days = (none, 1) + let end-day = none + + while month < month-range.at(1) { + // if we are at the last month, + // set the end-day the user requested + if month == month-range.at(1) - 1 { + end-day = day-range.at(1) + } + + month-header(month, is-mon-start: is-mon-start) + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year) + construct-month-table(days.at(0)) + + start-day = int(days.last()) + 1 + // prevents starting a month on 32 or another not real day + if start-day > 10 { + start-day = 1 + } + month = month + 1 + } + + v(50pt) + // [1#align(left)[#v(-10pt)Exam \#1]] + days = ("week #1", "1-test", "1 exam") + construct-month-table(days) +} + + From 010afb4efbf862236b71f71385ec997a503bae3a Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 02:28:19 -0400 Subject: [PATCH 02/22] custom color coding added --- examples/calendar_example.typ | 13 +++++++++++-- src/calendars.typ | 35 +++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index d66f93e..0acddaf 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -3,6 +3,15 @@ #show: calendars.init -#calendars.header((8, 12), (23, 12), title: "CS-1181 FA26") +#let color-coding = ( + ("zyBooks", rgb("#10a178")), + ("lab", rgb("#104fa1")), + ("project", rgb("#a19e10")), + ("quiz", rgb("#a15d10")), + ("exam", rgb("#a11010")), + ("final", rgb("#a11010")) +) -nsddsssssddddddxdddjdsddd \ No newline at end of file +#calendars.header((8, 12), (23, 12), title: "CS-1181 FA26", color-codes: color-coding) + +nsddsssssddddddxdddjdsd \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 448cef8..0c0b82e 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -117,7 +117,7 @@ return (day-nums, week-count) } -#let construct-month-table(days) = { +#let construct-month-table(days, keywords) = { v(-18pt) show table.cell: it => { @@ -131,7 +131,30 @@ align: center, rows: 60pt, ..days.map(text-str => { - table.cell(fill: if text-str.contains("exam") { rgb("#a12310").lighten(40%) } else { none })[#text-str] + let all-pieces = text-str.split("-") + if all-pieces.len() == 1 { + table.cell()[#text-str] + } else { + let num = all-pieces.at(0) + let temp = [] + let content = text(weight: "bold")[#num] + let i = 1 + while i < all-pieces.len() { + temp = all-pieces.at(i) + let j = 0 + while j < keywords.len() { + if temp.contains(keywords.at(j).at(0)) { + temp = text(fill: keywords.at(j).at(1))[#temp] + break + } + j = j + 1 + } + content = content + align(left)[#v(-10pt)#temp] + i = i + 1 + } + table.cell()[#content] + } + //table.cell(fill: if text-str.contains("exam") { rgb("#a12310").lighten(40%) } else { none })[#text(fill: if text-str.contains("exam") { rgb("#a12310") } else { black })[#text-str]] }) //..days.flatten() ) @@ -141,7 +164,7 @@ /// - startMonth (content, str): Class name /// - title (content, str): The title text for the specific lab problem /// - number (int, string, none): Lab problem number, if applicable -#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false) = { +#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:none) = { assert( type(month-range) == array, message: "Expected month-range to be array, but received " + str(type(month-range)) @@ -222,7 +245,7 @@ month-header(month, is-mon-start: is-mon-start) days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year) - construct-month-table(days.at(0)) + construct-month-table(days.at(0), color-codes) start-day = int(days.last()) + 1 // prevents starting a month on 32 or another not real day @@ -234,8 +257,8 @@ v(50pt) // [1#align(left)[#v(-10pt)Exam \#1]] - days = ("week #1", "1-test", "1 exam") - construct-month-table(days) + days = ("week #1", "1-exam #1", "1-zyBooks #1-lab #1") + construct-month-table(days, color-codes) } From 79f3802aecbbc4e2ceb42e758cec7f42a1b85f9a Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 02:31:39 -0400 Subject: [PATCH 03/22] fixed so works if no color codes provided --- examples/calendar_example.typ | 11 +++++++++-- src/calendars.typ | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 0acddaf..cfc6adf 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -12,6 +12,13 @@ ("final", rgb("#a11010")) ) -#calendars.header((8, 12), (23, 12), title: "CS-1181 FA26", color-codes: color-coding) -nsddsssssddddddxdddjdsd \ No newline at end of file + +#calendars.header( + (8, 12), + (23, 12), + title: "CS-1181 FA26", + color-codes: color-coding +) + +nsddsssssddddddxdddjdss \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 0c0b82e..54371b7 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -231,6 +231,10 @@ v(5pt) } + if color-codes == none { + color-codes = () + } + let month = month-range.at(0) - 1 let start-day = day-range.at(0) let days = (none, 1) From ced71eb9e293e8530734b0d2761fa37b3887f19b Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 02:55:01 -0400 Subject: [PATCH 04/22] added ability to gray out holidays --- examples/calendar_example.typ | 13 ++++++-- src/calendars.typ | 60 +++++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index cfc6adf..ad040ac 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -12,13 +12,20 @@ ("final", rgb("#a11010")) ) - +#let holidays = ( + (9, 7), + (11, 11), + (11, 25), + (11, 26), + (11, 27) +) #calendars.header( (8, 12), (23, 12), title: "CS-1181 FA26", - color-codes: color-coding + color-codes: color-coding, + holidays: holidays ) -nsddsssssddddddxdddjdss \ No newline at end of file +nsddsssssddddddxdddjdsss \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 54371b7..b63639b 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -85,13 +85,16 @@ line(length: 100%, stroke: 1pt) } -#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false) = { +#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, holidays:()) = { + // number of days in the month let max-days = months.at(month-id).at(1) + // override if different last day is provided if last-month-max != none { max-days = last-month-max } + // account for leap year if is-leap-year and month-id == 1 { max-days = max-days + 1; } @@ -101,17 +104,35 @@ let reach-max = false let week-count = last-week-num + // loops until the month max has been reached + // AND the week has been completed while calc.rem(day-nums.len(), 8) != 0 or not reach-max { + // inserts the week # tag at the beginning of each week if calc.rem(day-nums.len(), 8) == 0 { day-nums.push("week #" + str(week-count)) week-count = week-count + 1 } - day-nums.push(str(day)) + + let i = 0 + let day-text = str(day) + while i < holidays.len() { + let date = holidays.at(i) + if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { + day-text = str(day) + "-holiday" + break + } + i = i + 1 + } + + // inserts the day number + day-nums.push(day-text) day = day + 1 + // resets day if the max is reached if day > max-days { day = 1 reach-max = true + month-id = month-id + 1 } } return (day-nums, week-count) @@ -135,24 +156,32 @@ if all-pieces.len() == 1 { table.cell()[#text-str] } else { + let fill-color = none let num = all-pieces.at(0) let temp = [] let content = text(weight: "bold")[#num] let i = 1 while i < all-pieces.len() { temp = all-pieces.at(i) - let j = 0 - while j < keywords.len() { - if temp.contains(keywords.at(j).at(0)) { - temp = text(fill: keywords.at(j).at(1))[#temp] - break + if temp.contains("holiday") { + fill-color = gray + } else { + // check all keywords and perform color coding as needed + let j = 0 + while j < keywords.len() { + if temp.contains(keywords.at(j).at(0)) { + temp = text(fill: keywords.at(j).at(1))[#temp] + break + } + j = j + 1 } - j = j + 1 + // append the text on a new line, left-justified + content = content + align(left)[#v(-10pt)#temp] } - content = content + align(left)[#v(-10pt)#temp] i = i + 1 } - table.cell()[#content] + + table.cell(fill: fill-color)[#content] } //table.cell(fill: if text-str.contains("exam") { rgb("#a12310").lighten(40%) } else { none })[#text(fill: if text-str.contains("exam") { rgb("#a12310") } else { black })[#text-str]] }) @@ -164,7 +193,7 @@ /// - startMonth (content, str): Class name /// - title (content, str): The title text for the specific lab problem /// - number (int, string, none): Lab problem number, if applicable -#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:none) = { +#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), holidays:()) = { assert( type(month-range) == array, message: "Expected month-range to be array, but received " + str(type(month-range)) @@ -225,16 +254,13 @@ message: "Expected is-mon-start to be bool, or none, but received " + str(type(is-mon-start)) ) + // format title if provided if title != none { text[= #title] line(length: 100%, stroke: 2pt) v(5pt) } - if color-codes == none { - color-codes = () - } - let month = month-range.at(0) - 1 let start-day = day-range.at(0) let days = (none, 1) @@ -242,13 +268,13 @@ while month < month-range.at(1) { // if we are at the last month, - // set the end-day the user requested + // set the end-day to what the user requested if month == month-range.at(1) - 1 { end-day = day-range.at(1) } month-header(month, is-mon-start: is-mon-start) - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year) + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, holidays: holidays) construct-month-table(days.at(0), color-codes) start-day = int(days.last()) + 1 From 36894b4f46fb53fcaea584f1ee0de2b42b86c7c6 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 03:01:18 -0400 Subject: [PATCH 05/22] renamed to shading and allowed user to pick color --- examples/calendar_example.typ | 4 +++- src/calendars.typ | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index ad040ac..8c56020 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -20,12 +20,14 @@ (11, 27) ) +#let shading-coding = (holidays, gray) + #calendars.header( (8, 12), (23, 12), title: "CS-1181 FA26", color-codes: color-coding, - holidays: holidays + shading: shading-coding ) nsddsssssddddddxdddjdsss \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index b63639b..308ff2a 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -85,7 +85,7 @@ line(length: 100%, stroke: 1pt) } -#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, holidays:()) = { +#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:()) = { // number of days in the month let max-days = months.at(month-id).at(1) @@ -115,10 +115,10 @@ let i = 0 let day-text = str(day) - while i < holidays.len() { - let date = holidays.at(i) + while i < shading.len() { + let date = shading.at(i) if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { - day-text = str(day) + "-holiday" + day-text = str(day) + "-shade" break } i = i + 1 @@ -138,7 +138,7 @@ return (day-nums, week-count) } -#let construct-month-table(days, keywords) = { +#let construct-month-table(days, keywords, shading) = { v(-18pt) show table.cell: it => { @@ -163,8 +163,8 @@ let i = 1 while i < all-pieces.len() { temp = all-pieces.at(i) - if temp.contains("holiday") { - fill-color = gray + if temp.contains("shade") { + fill-color = shading } else { // check all keywords and perform color coding as needed let j = 0 @@ -193,7 +193,7 @@ /// - startMonth (content, str): Class name /// - title (content, str): The title text for the specific lab problem /// - number (int, string, none): Lab problem number, if applicable -#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), holidays:()) = { +#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), shading:()) = { assert( type(month-range) == array, message: "Expected month-range to be array, but received " + str(type(month-range)) @@ -274,8 +274,8 @@ } month-header(month, is-mon-start: is-mon-start) - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, holidays: holidays) - construct-month-table(days.at(0), color-codes) + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: shading.at(0)) + construct-month-table(days.at(0), color-codes, shading.at(1)) start-day = int(days.last()) + 1 // prevents starting a month on 32 or another not real day @@ -288,7 +288,7 @@ v(50pt) // [1#align(left)[#v(-10pt)Exam \#1]] days = ("week #1", "1-exam #1", "1-zyBooks #1-lab #1") - construct-month-table(days, color-codes) + construct-month-table(days, color-codes, shading.at(1)) } From b7f9daf19bd2020f130ca777947f7382bf112256 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 03:26:25 -0400 Subject: [PATCH 06/22] fixed days of month not flowing correctly; added ability to have multiple different shading encodings --- examples/calendar_example.typ | 20 +++++++++++-- src/calendars.typ | 54 ++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 8c56020..d4071fa 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -20,14 +20,28 @@ (11, 27) ) -#let shading-coding = (holidays, gray) +#let finals-week = ( + (12, 7), + (12, 8), + (12, 9), + (12, 10), + (12, 11) +) + +#let holiday-encodings = (holidays, gray) +#let finals-encodings = (finals-week, rgb("#f59998")) + +#let shading-encodings = ( + holiday-encodings, + finals-encodings +) #calendars.header( (8, 12), (23, 12), title: "CS-1181 FA26", color-codes: color-coding, - shading: shading-coding + shading: shading-encodings ) -nsddsssssddddddxdddjdsss \ No newline at end of file +nsddsssssddddddxdddjs \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 308ff2a..1185ade 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -116,10 +116,15 @@ let i = 0 let day-text = str(day) while i < shading.len() { - let date = shading.at(i) - if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { - day-text = str(day) + "-shade" - break + let j = 0 + let dates = shading.at(i) + while j < dates.len() { + let date = dates.at(j) + if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { + day-text = str(day) + "-" + str(i) + break + } + j = j + 1 } i = i + 1 } @@ -135,10 +140,10 @@ month-id = month-id + 1 } } - return (day-nums, week-count) + return (day-nums, week-count, day) } -#let construct-month-table(days, keywords, shading) = { +#let construct-month-table(days, keywords, shading-colors) = { v(-18pt) show table.cell: it => { @@ -163,9 +168,15 @@ let i = 1 while i < all-pieces.len() { temp = all-pieces.at(i) - if temp.contains("shade") { - fill-color = shading - } else { + let shade-id = 0 + while shade-id < shading-colors.len() { + if temp.contains(str(shade-id)) { + fill-color = shading-colors.at(shade-id) + } + shade-id = shade-id + 1 + } + + if fill-color == none { // check all keywords and perform color coding as needed let j = 0 while j < keywords.len() { @@ -261,6 +272,23 @@ v(5pt) } + // construct arrays for dates to be shaded + // construct arrays for the color to shade + let dates = () + let colors = () + if type(shading.at(1)) == color { + dates.push(shading.at(0)) + colors.push(shading.at(1)) + } else { + let i = 0 + while i < shading.len() { + let pair = shading.at(i) + dates.push(pair.at(0)) + colors.push(pair.at(1)) + i = i + 1 + } + } + let month = month-range.at(0) - 1 let start-day = day-range.at(0) let days = (none, 1) @@ -274,10 +302,10 @@ } month-header(month, is-mon-start: is-mon-start) - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: shading.at(0)) - construct-month-table(days.at(0), color-codes, shading.at(1)) + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates) + construct-month-table(days.at(0), color-codes, colors) - start-day = int(days.last()) + 1 + start-day = days.at(2)//int(days.at(0).last()) + 1 // prevents starting a month on 32 or another not real day if start-day > 10 { start-day = 1 @@ -288,7 +316,7 @@ v(50pt) // [1#align(left)[#v(-10pt)Exam \#1]] days = ("week #1", "1-exam #1", "1-zyBooks #1-lab #1") - construct-month-table(days, color-codes, shading.at(1)) + construct-month-table(days, color-codes, colors) } From 1b3e85bd7efe86584c21075a8d9753c7be7caf4d Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 03:27:37 -0400 Subject: [PATCH 07/22] removed some now un-needed code --- examples/calendar_example.typ | 2 +- src/calendars.typ | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index d4071fa..eed6220 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -44,4 +44,4 @@ shading: shading-encodings ) -nsddsssssddddddxdddjs \ No newline at end of file +nsddsssssddddddxdd \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 1185ade..276493a 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -305,11 +305,7 @@ days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates) construct-month-table(days.at(0), color-codes, colors) - start-day = days.at(2)//int(days.at(0).last()) + 1 - // prevents starting a month on 32 or another not real day - if start-day > 10 { - start-day = 1 - } + start-day = days.at(2) month = month + 1 } From f2545c532dc1640ebb990975e8eb9cb26c0d1d9d Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 03:30:13 -0400 Subject: [PATCH 08/22] fixed issue where shade conditional was checking for contains instead of equal --- examples/calendar_example.typ | 2 +- src/calendars.typ | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index eed6220..65299b6 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -44,4 +44,4 @@ shading: shading-encodings ) -nsddsssssddddddxdd \ No newline at end of file +nsddsssssddddddxddd \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 276493a..8763666 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -158,6 +158,7 @@ rows: 60pt, ..days.map(text-str => { let all-pieces = text-str.split("-") + // if there is only a number if all-pieces.len() == 1 { table.cell()[#text-str] } else { @@ -170,7 +171,7 @@ temp = all-pieces.at(i) let shade-id = 0 while shade-id < shading-colors.len() { - if temp.contains(str(shade-id)) { + if temp == str(shade-id) { fill-color = shading-colors.at(shade-id) } shade-id = shade-id + 1 From 907367e2fcf94b425d710d91001d8e45cb707f4f Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 03:31:25 -0400 Subject: [PATCH 09/22] fixed issue where numbers were only bolded if other text was present --- examples/calendar_example.typ | 2 +- src/calendars.typ | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 65299b6..f4bfd09 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -44,4 +44,4 @@ shading: shading-encodings ) -nsddsssssddddddxddd \ No newline at end of file +nsddsssssddddddxddds \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 8763666..5f98e1b 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -160,7 +160,7 @@ let all-pieces = text-str.split("-") // if there is only a number if all-pieces.len() == 1 { - table.cell()[#text-str] + table.cell()[#text(weight: "bold")[#text-str]] } else { let fill-color = none let num = all-pieces.at(0) From 272bb165c379b742e56618e358e9a25e6a2e7b97 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 04:09:39 -0400 Subject: [PATCH 10/22] added ability to add assignments and other important dates --- examples/calendar_example.typ | 47 ++++++++++++++++++++++++++++++++--- src/calendars.typ | 27 ++++++++++++++------ 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index f4bfd09..121c2a5 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -7,9 +7,10 @@ ("zyBooks", rgb("#10a178")), ("lab", rgb("#104fa1")), ("project", rgb("#a19e10")), - ("quiz", rgb("#a15d10")), + ("QUIZ", rgb("#a15d10")), ("exam", rgb("#a11010")), - ("final", rgb("#a11010")) + ("FINAL", rgb("#a11010")), + ("MIDTERM", rgb("#a11010")), ) #let holidays = ( @@ -36,12 +37,50 @@ finals-encodings ) +#let due-dates = ( + (8, 30, "zyBooks #1"), + (9, 6, "zyBooks #2"), + (9, 13, "zyBooks #3"), + (9, 20, "zyBooks #4"), + (9, 27, "zyBooks #5"), + (10, 4, "zyBooks #6"), + (10, 11, "zyBooks #7"), + (10, 18, "zyBooks #8"), + (10, 25, "zyBooks #9"), + (11, 1, "zyBooks #10"), + (11, 8, "zyBooks #11"), + + (8, 30, "lab #1"), + (9, 6, "lab #2"), + (9, 13, "lab #3"), + (9, 20, "lab #4"), + (9, 27, "lab #5"), + (10, 4, "lab #6"), + (10, 11, "lab #7"), + (10, 18, "lab #8"), + (10, 25, "lab #9"), + (11, 1, "lab #10"), + (11, 8, "lab #11"), + (11, 15, "lab #12"), + + (9, 20, "project #1"), + (10, 11, "project #2"), + (11, 1, "project #3"), + (11, 22, "project #4"), + + (9, 18, "QUIZ #1"), + (10, 16, "MIDTERM"), + (11, 13, "QUIZ #2"), + (12, 11, "FINAL"), +) + #calendars.header( (8, 12), (23, 12), title: "CS-1181 FA26", color-codes: color-coding, - shading: shading-encodings + shading: shading-encodings, + due-dates: due-dates, ) -nsddsssssddddddxddds \ No newline at end of file +nsdd \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 5f98e1b..8ecdb0e 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -85,7 +85,7 @@ line(length: 100%, stroke: 1pt) } -#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:()) = { +#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:()) = { // number of days in the month let max-days = months.at(month-id).at(1) @@ -121,7 +121,7 @@ while j < dates.len() { let date = dates.at(j) if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { - day-text = str(day) + "-" + str(i) + day-text = day-text + "-" + str(i) break } j = j + 1 @@ -129,6 +129,15 @@ i = i + 1 } + i = 0 + while i < assigns.len() { + let item = assigns.at(i) + if (item.at(0) == (month-id + 1)) and (item.at(1) == day) { + day-text = day-text + "-" + item.at(2) + } + i = i + 1 + } + // inserts the day number day-nums.push(day-text) @@ -166,23 +175,28 @@ let num = all-pieces.at(0) let temp = [] let content = text(weight: "bold")[#num] + let skip = false let i = 1 while i < all-pieces.len() { + skip = false temp = all-pieces.at(i) + // checks if the cell should be shaded let shade-id = 0 while shade-id < shading-colors.len() { if temp == str(shade-id) { fill-color = shading-colors.at(shade-id) + skip = true } shade-id = shade-id + 1 } - if fill-color == none { + // only need to check for keywords if we determine it is not a shading key + if skip == false { // check all keywords and perform color coding as needed let j = 0 while j < keywords.len() { if temp.contains(keywords.at(j).at(0)) { - temp = text(fill: keywords.at(j).at(1))[#temp] + temp = text(weight: "bold", fill: keywords.at(j).at(1))[#temp] break } j = j + 1 @@ -205,7 +219,7 @@ /// - startMonth (content, str): Class name /// - title (content, str): The title text for the specific lab problem /// - number (int, string, none): Lab problem number, if applicable -#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), shading:()) = { +#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), shading:(), due-dates:()) = { assert( type(month-range) == array, message: "Expected month-range to be array, but received " + str(type(month-range)) @@ -303,7 +317,7 @@ } month-header(month, is-mon-start: is-mon-start) - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates) + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: due-dates) construct-month-table(days.at(0), color-codes, colors) start-day = days.at(2) @@ -311,7 +325,6 @@ } v(50pt) - // [1#align(left)[#v(-10pt)Exam \#1]] days = ("week #1", "1-exam #1", "1-zyBooks #1-lab #1") construct-month-table(days, color-codes, colors) } From 0ae8bebb1c0cc0135ca8c379766e9cf3af58bcfe Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 04:13:58 -0400 Subject: [PATCH 11/22] clean-up; resize box heights --- examples/calendar_example.typ | 6 +++--- src/calendars.typ | 8 +------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 121c2a5..0bb4012 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -74,6 +74,8 @@ (12, 11, "FINAL"), ) + + #calendars.header( (8, 12), (23, 12), @@ -81,6 +83,4 @@ color-codes: color-coding, shading: shading-encodings, due-dates: due-dates, -) - -nsdd \ No newline at end of file +) \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 8ecdb0e..e64fae9 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -164,7 +164,7 @@ table( columns: (0fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr), align: center, - rows: 60pt, + rows: 70pt, ..days.map(text-str => { let all-pieces = text-str.split("-") // if there is only a number @@ -209,9 +209,7 @@ table.cell(fill: fill-color)[#content] } - //table.cell(fill: if text-str.contains("exam") { rgb("#a12310").lighten(40%) } else { none })[#text(fill: if text-str.contains("exam") { rgb("#a12310") } else { black })[#text-str]] }) - //..days.flatten() ) } @@ -323,10 +321,6 @@ start-day = days.at(2) month = month + 1 } - - v(50pt) - days = ("week #1", "1-exam #1", "1-zyBooks #1-lab #1") - construct-month-table(days, color-codes, colors) } From 73f8377fc9bf6eab9821320d1bc6d1a85074d6f9 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Fri, 24 Jul 2026 11:46:35 -0400 Subject: [PATCH 12/22] fix that shading didnt work without a param --- examples/calendar_example.typ | 17 +++++++++++------ src/calendars.typ | 24 +++++++++++++----------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 0bb4012..90a2a68 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -76,11 +76,16 @@ +//#calendars.header( +// (8, 12), +// (23, 12), +// title: "CS-1181 FA26", +// color-codes: color-coding, +// shading: shading-encodings, +// due-dates: due-dates, +//) + #calendars.header( - (8, 12), - (23, 12), - title: "CS-1181 FA26", - color-codes: color-coding, - shading: shading-encodings, - due-dates: due-dates, + (6, 7), + (21, 18) ) \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index e64fae9..5cad1c8 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -289,17 +289,19 @@ // construct arrays for the color to shade let dates = () let colors = () - if type(shading.at(1)) == color { - dates.push(shading.at(0)) - colors.push(shading.at(1)) - } else { - let i = 0 - while i < shading.len() { - let pair = shading.at(i) - dates.push(pair.at(0)) - colors.push(pair.at(1)) - i = i + 1 - } + if shading != () { + if type(shading.at(1)) == color { + dates.push(shading.at(0)) + colors.push(shading.at(1)) + } else { + let i = 0 + while i < shading.len() { + let pair = shading.at(i) + dates.push(pair.at(0)) + colors.push(pair.at(1)) + i = i + 1 + } + } } let month = month-range.at(0) - 1 From 1ae9455f9187e411cdc218022c5a26722d9a9bc3 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Mon, 27 Jul 2026 14:07:10 -0400 Subject: [PATCH 13/22] rename header to draw-calendar; draw-calendar can now take string months --- examples/calendar_example.typ | 23 +++++-------- src/calendars.typ | 65 +++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 90a2a68..c32f4b8 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -74,18 +74,13 @@ (12, 11, "FINAL"), ) +#calendars.draw-calendar( + ("aug", "dec"), + (23, 12), + title: "CS-1181 FA26", + color-codes: color-coding, + shading: shading-encodings, + due-dates: due-dates, +) - -//#calendars.header( -// (8, 12), -// (23, 12), -// title: "CS-1181 FA26", -// color-codes: color-coding, -// shading: shading-encodings, -// due-dates: due-dates, -//) - -#calendars.header( - (6, 7), - (21, 18) -) \ No newline at end of file +d \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 5cad1c8..f6e2f68 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -1,4 +1,4 @@ -#let months = ( +#let month-days = ( ("January", 31), ("February", 28), ("March", 31), @@ -13,6 +13,21 @@ ("December", 31) ) +#let month-inputs = ( + ("january", "jan"), + ("february", "feb"), + ("march", "mar"), + ("april", "apr"), + ("may",), + ("june", "jun"), + ("july", "jul"), + ("august", "aug"), + ("september", "sep"), + ("october", "oct"), + ("november", "nov"), + ("december", "dec"), +) + #let day-arr = ( "Sunday", "Monday", @@ -77,7 +92,7 @@ ) v(-10pt) - text[== #months.at(month-id).at(0)] + text[== #month-days.at(month-id).at(0)] line(length: 100%, stroke: 1pt) v(-15pt) days-of-week(is-mon-start: is-mon-start) @@ -87,7 +102,7 @@ #let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:()) = { // number of days in the month - let max-days = months.at(month-id).at(1) + let max-days = month-days.at(month-id).at(1) // override if different last day is provided if last-month-max != none { @@ -213,26 +228,56 @@ ) } -/// header: Render the document section header block for lab problems -/// - startMonth (content, str): Class name +#let get-numeric-month(input) = { + let index = 0 + let numeric-month = -1 + while index < month-inputs.len() { + for keyword in month-inputs.at(index) { + if keyword == lower(input) { + numeric-month = index + 1 + } + } + index = index + 1 + } + return numeric-month +} + +/// draw-calendar: Render the calendar as specified +/// - month-range (int, int) OR (str, str): Class name /// - title (content, str): The title text for the specific lab problem /// - number (int, string, none): Lab problem number, if applicable -#let header(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), shading:(), due-dates:()) = { +#let draw-calendar(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), shading:(), due-dates:()) = { assert( type(month-range) == array, message: "Expected month-range to be array, but received " + str(type(month-range)) ) assert( - type(month-range.at(0)) == int, - message: "Expected month-range to be array of int, but received " + str(type(month-range.at(0))) + type(month-range.at(0)) == int or type(month-range.at(0)) == str, + message: "Expected month-range to be array of int or string, but received " + str(type(month-range.at(0))) ) assert( - type(month-range.at(1)) == int, - message: "Expected month-range to be array of int, but received " + str(type(month-range.at(1))) + type(month-range.at(1)) == int or type(month-range.at(1)) == str, + message: "Expected month-range to be array of int or string, but received " + str(type(month-range.at(1))) ) + if type(month-range.at(0)) == str { + month-range.at(0) = get-numeric-month(month-range.at(0)) + assert( + month-range.at(0) != -1, + message: "First month-range value is an invalid string" + ) + } + + if type(month-range.at(1)) == str { + month-range.at(1) = get-numeric-month(month-range.at(1)) + assert( + month-range.at(0) != -1, + message: "Second month-range value is an invalid string" + ) + } + assert( month-range.at(1) > 0 and month-range.at(1) < 13, message: "Second month-range value must be >0 and <13" From 3892cf65f1e516bd2f583e98d735fd2cb88f4a49 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Mon, 27 Jul 2026 16:36:57 -0400 Subject: [PATCH 14/22] fixed shadings to have assertions and take string months --- examples/calendar_example.typ | 54 ++--- src/calendars.typ | 365 +++++++++++++++++++++++----------- 2 files changed, 275 insertions(+), 144 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index c32f4b8..3c3bda1 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -13,30 +13,6 @@ ("MIDTERM", rgb("#a11010")), ) -#let holidays = ( - (9, 7), - (11, 11), - (11, 25), - (11, 26), - (11, 27) -) - -#let finals-week = ( - (12, 7), - (12, 8), - (12, 9), - (12, 10), - (12, 11) -) - -#let holiday-encodings = (holidays, gray) -#let finals-encodings = (finals-week, rgb("#f59998")) - -#let shading-encodings = ( - holiday-encodings, - finals-encodings -) - #let due-dates = ( (8, 30, "zyBooks #1"), (9, 6, "zyBooks #2"), @@ -74,13 +50,37 @@ (12, 11, "FINAL"), ) +#let holidays = ( + (9, 7), + ("nov", 11), + (11, 25), + (11, 26), + (11, 27) +) + +#let finals-week = ( + (12, 7), + (12, 8), + ("dec", 9), + (12, 10), + ("december", 11) +) + +#let test = (("september", 23), blue) + +#let holiday-encodings = (holidays, gray) +#let finals-encodings = (finals-week, rgb("#f59998")) + +#let shading-encodings = ( + holiday-encodings, + finals-encodings, + test +) + #calendars.draw-calendar( ("aug", "dec"), (23, 12), title: "CS-1181 FA26", color-codes: color-coding, - shading: shading-encodings, due-dates: due-dates, ) - -d \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index f6e2f68..d1cd69f 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -48,26 +48,25 @@ /// - body (content): body fo lab problem #let init(body) = { - assert( - type(body) == content or type(body) == str, - message: "Expected body to be content or str, but received" + str(type(body)) - ) - - - set page(margin: 20pt, width: 8.5in, height: auto) - set text( - font: ("Roboto"), - size: 11pt, - fill: black, - weight: "regular" - ) - set raw(theme: "../themes/codepoint.tmTheme") - show raw: set text(font: ("Courier", "Courier Prime"), weight: "bold", size: 10pt) - - // defaults to 1.2, but on labs specifically, this is not enough spacing - set par(spacing: 1.6em) - - body + assert( + type(body) == content or type(body) == str, + message: "Expected body to be content or str, but received" + str(type(body)) + ) + + set page(margin: 20pt, width: 8.5in, height: auto) + set text( + font: ("Roboto"), + size: 11pt, + fill: black, + weight: "regular" + ) + set raw(theme: "../themes/codepoint.tmTheme") + show raw: set text(font: ("Courier"), weight: "bold", size: 10pt) + + // defaults to 1.2, but on labs specifically, this is not enough spacing + set par(spacing: 1.6em) + + body } #let days-of-week(is-mon-start: false) = { @@ -86,18 +85,18 @@ } #let month-header(month-id, is-mon-start: false) = { - assert( - type(month-id) == int, - message: "Expected month-id to be int, but received" + str(type(month-id)) - ) - - v(-10pt) - text[== #month-days.at(month-id).at(0)] - line(length: 100%, stroke: 1pt) - v(-15pt) - days-of-week(is-mon-start: is-mon-start) - v(-15pt) - line(length: 100%, stroke: 1pt) + assert( + type(month-id) == int, + message: "Expected month-id to be int, but received" + str(type(month-id)) + ) + + v(-10pt) + text[== #month-days.at(month-id).at(0)] + line(length: 100%, stroke: 1pt) + v(-15pt) + days-of-week(is-mon-start: is-mon-start) + v(-15pt) + line(length: 100%, stroke: 1pt) } #let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:()) = { @@ -239,43 +238,113 @@ } index = index + 1 } + + assert( + numeric-month != -1, + message: "Expected valid month received " + str(input) + ) + return numeric-month } -/// draw-calendar: Render the calendar as specified -/// - month-range (int, int) OR (str, str): Class name -/// - title (content, str): The title text for the specific lab problem -/// - number (int, string, none): Lab problem number, if applicable -#let draw-calendar(month-range, day-range, title:none, is-leap-year:false, is-mon-start:false, color-codes:(), shading:(), due-dates:()) = { +#let create-color-date-shading-arrays(encoding) = { + let dates = () + let colors = () + + // checks for date/date array and color pair assert( - type(month-range) == array, - message: "Expected month-range to be array, but received " + str(type(month-range)) + type(encoding) == array, + message: "Expected encoding in format: (date/date array, color)" ) + // checks that the first element is an array assert( - type(month-range.at(0)) == int or type(month-range.at(0)) == str, - message: "Expected month-range to be array of int or string, but received " + str(type(month-range.at(0))) + type(encoding.at(0)) == array, + message: "Position 0 must be a date or array of dates" ) + // check that the second element is a color assert( - type(month-range.at(1)) == int or type(month-range.at(1)) == str, - message: "Expected month-range to be array of int or string, but received " + str(type(month-range.at(1))) + type(encoding.at(1)) == color, + message: "Position 1 must be a color" ) + colors.push(encoding.at(1)) - if type(month-range.at(0)) == str { - month-range.at(0) = get-numeric-month(month-range.at(0)) + // if the first element is an array of dates + if type(encoding.at(0).at(0)) == array { + // check if all the dates are in a valid format assert( - month-range.at(0) != -1, - message: "First month-range value is an invalid string" + encoding.at(0).all(s => { + (type(s.at(0)) == str or type(s.at(0)) == int) and type(s.at(1)) == int + }), + message: "Expected all shading dates to be in the format: (int or str, int)" ) - } + let i = 0 + while i < encoding.at(0).len() { + if type(encoding.at(0).at(i).at(0)) == str { + encoding.at(0).at(i).at(0) = get-numeric-month(encoding.at(0).at(i).at(0)) + } + i = i + 1 + } - if type(month-range.at(1)) == str { - month-range.at(1) = get-numeric-month(month-range.at(1)) + dates.push(encoding.at(0)) + // if the first element is a single date + } else { assert( - month-range.at(0) != -1, - message: "Second month-range value is an invalid string" + ((type(encoding.at(0).at(0)) == str or type(encoding.at(0).at(0)) == int) and type(encoding.at(0).at(1)) == int), + message: "Expected date to be in the format: (int or str, int) received (" + str(type(encoding.at(0).at(0))) + ", " + str(type(encoding.at(0).at(1))) + ")" ) + if type(encoding.at(0).at(0)) == str { + encoding.at(0).at(0) = get-numeric-month(encoding.at(0).at(0)) + } + + let all-dates = () + all-dates.push(encoding.at(0)) + dates.push(all-dates) + } + + return (dates, colors) +} + +/// draw-calendar: Render the calendar as specified +/// - month-range (int, int) OR (str, str): the start and ending months of the range desired for the calendar +/// - day-range (int, int): the starting and ending day values for the calendar +/// - title str: adds the provided title to the calendar, defaults to no title +/// - is-leap-year bool: indicates whether the year is a leap year (feb has 29 days), defaults to false +/// - is-mon-start bool: toggles between sunday and monday starts for the week, defaults to sunday start +/// - color-codes (str, color) OR ((str, color), (str, color), ...): indicates if certain words should be colored the specified color, defaults to empty array +/// - shading: +#let draw-calendar(month-range, day-range, title: "", is-leap-year: false, is-mon-start: false, color-codes: (), shading: (), due-dates: ()) = { + ////////////////// + // MONTH CHECKS // + ////////////////// + assert( + type(month-range) == array, + message: "Expected month-range to be array but received " + str(type(month-range)) + ) + + assert( + month-range.len() == 2, + message: "Expected month-range to be an array of length 2 but received array of length " + str(month-range.len()) + ) + + assert( + type(month-range.at(0)) == int or type(month-range.at(0)) == str, + message: "Expected month-range to be array of int or string but received " + str(type(month-range.at(0))) + ) + + assert( + type(month-range.at(1)) == int or type(month-range.at(1)) == str, + message: "Expected month-range to be array of int or string but received " + str(type(month-range.at(1))) + ) + + // if a str month is provided, check that it is valid and convert to numeric + let month-val = 0 + while month-val < month-range.len() { + if type(month-range.at(month-val)) == str { + month-range.at(month-val) = get-numeric-month(month-range.at(month-val)) + } + month-val = month-val + 1 } assert( @@ -288,86 +357,148 @@ message: "First month-range value must be >0 and <" + str(month-range.at(1)) ) - assert( - type(day-range) == array, - message: "Expected day-range to be array, but received " + str(type(day-range)) - ) - assert( - type(day-range.at(0)) == int, - message: "Expected day-range to be array of int, but received " + str(type(day-range.at(0))) - ) + //////////////// + // DAY CHECKS // + //////////////// + assert( + type(day-range) == array, + message: "Expected day-range to be array but received " + str(type(day-range)) + ) - assert( - type(day-range.at(1)) == int, - message: "Expected day-range to be array of int, but received " + str(type(day-range.at(1))) - ) + assert( + month-range.len() == 2, + message: "Expected day-range to be an array of length 2 but received array of length " + str(month-range.len()) + ) - assert( - day-range.at(0) > 0 and day-range.at(0) < 32 and day-range.at(1) > 0 and day-range.at(1) < 32, - message: "Day-range values must be >0 and <32" - ) + assert( + type(day-range.at(0)) == int, + message: "Expected day-range to be array of int but received " + str(type(day-range.at(0))) + ) - assert( - type(title) == str or title == none, - message: "Expected title to be string, or none, but received " + str(type(title)) - ) + assert( + type(day-range.at(1)) == int, + message: "Expected day-range to be array of int but received " + str(type(day-range.at(1))) + ) assert( - type(is-leap-year) == bool or is-leap-year == none, - message: "Expected is-leap-year to be bool, or none, but received " + str(type(is-leap-year)) + day-range.at(0) > 0 and day-range.at(0) < 32 and day-range.at(1) > 0 and day-range.at(1) < 32, + message: "Day-range values must be >0 and <32" ) + + ///////////////// + // TITLE CHECK // + ///////////////// assert( - type(is-mon-start) == bool or is-mon-start == none, - message: "Expected is-mon-start to be bool, or none, but received " + str(type(is-mon-start)) + type(title) == str, + message: "Expected title to be string but received " + str(type(title)) ) - // format title if provided - if title != none { - text[= #title] - line(length: 100%, stroke: 2pt) - v(5pt) - } - - // construct arrays for dates to be shaded - // construct arrays for the color to shade - let dates = () - let colors = () - if shading != () { - if type(shading.at(1)) == color { - dates.push(shading.at(0)) - colors.push(shading.at(1)) - } else { - let i = 0 - while i < shading.len() { - let pair = shading.at(i) - dates.push(pair.at(0)) - colors.push(pair.at(1)) - i = i + 1 + // format title if provided + if title != "" { + text[= #title] + line(length: 100%, stroke: 2pt) + v(5pt) + } + + + ///////////////////// + // LEAP YEAR CHECK // + ///////////////////// + assert( + type(is-leap-year) == bool, + message: "Expected is-leap-year to be bool but received " + str(type(is-leap-year)) + ) + + + //////////////////////// + // MONDAY START CHECK // + //////////////////////// + assert( + type(is-mon-start) == bool, + message: "Expected is-mon-start to be bool but received " + str(type(is-mon-start)) + ) + + + ///////////////////////// + // COLOR CODING CHECKS // + ///////////////////////// + assert( + type(color-codes) == array, + message: "Expected color-codes to be an array but received " + str(type(color-codes)) + ) + + if color-codes != () { + // allows user to pass a singular keyword and color pair: (str, color) + // by converting to an array of pairs + if type(color-codes.at(0)) == str and type(color-codes.at(1)) == color { + let temp = color-codes + color-codes = () + color-codes.push(temp) + } + + // if single value is not in the correct format: (str, color) + assert( + type(color-codes.at(0)) == array, + message: "Expected color-codes in the format: (\"keyword\", color)" + ) + + // if an array of pairs is provided: ((str, color), (str, color), ...) + assert( + color-codes.all(c => { + type(c.at(0)) == str and type(c.at(1)) == color + }), + message: "Expected all color-code pairs to be in the format: (str, color)" + ) + } + + + //////////////////// + // SHADING CHECKS // + //////////////////// + assert( + type(shading) == array, + message: "Expected shading to be an array but received " + str(type(shading)) + ) + + let dates = () + let colors = () + if shading != () { + // handles case of one (or more) dates and one color: ((int or str, int), color) OR ( ((int or str, int), (int or str, int), ...), color ) + if type(shading.at(1)) == color { + let out = create-color-date-shading-arrays(shading) + dates = out.at(0) + colors = out.at(1) + // handles case of multiple colors + } else { + for encoding in shading { + let out = create-color-date-shading-arrays(encoding) + dates.push(out.at(0).at(0)) + colors.push(out.at(1).at(0)) + } } - } - } - - let month = month-range.at(0) - 1 - let start-day = day-range.at(0) - let days = (none, 1) - let end-day = none - - while month < month-range.at(1) { - // if we are at the last month, - // set the end-day to what the user requested - if month == month-range.at(1) - 1 { - end-day = day-range.at(1) } - month-header(month, is-mon-start: is-mon-start) - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: due-dates) - construct-month-table(days.at(0), color-codes, colors) + let month = month-range.at(0) - 1 + let start-day = day-range.at(0) + let days = (none, 1) + let end-day = none + + while month < month-range.at(1) { + // if we are at the last month, + // set the end-day to what the user requested + if month == month-range.at(1) - 1 { + end-day = day-range.at(1) + } - start-day = days.at(2) - month = month + 1 - } + month-header(month, is-mon-start: is-mon-start) + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: due-dates) + construct-month-table(days.at(0), color-codes, colors) + + start-day = days.at(2) + month = month + 1 + } } From 7081548a149ea7170ca3c2df75afbbbaf3403e1b Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Mon, 27 Jul 2026 17:37:24 -0400 Subject: [PATCH 15/22] fully comment, add more assertions --- examples/calendar_example.typ | 12 +-- src/calendars.typ | 139 ++++++++++++++++++++++++++++++---- 2 files changed, 132 insertions(+), 19 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 3c3bda1..621245e 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -19,7 +19,7 @@ (9, 13, "zyBooks #3"), (9, 20, "zyBooks #4"), (9, 27, "zyBooks #5"), - (10, 4, "zyBooks #6"), + ("oct", 4, "zyBooks #6"), (10, 11, "zyBooks #7"), (10, 18, "zyBooks #8"), (10, 25, "zyBooks #9"), @@ -41,7 +41,7 @@ (9, 20, "project #1"), (10, 11, "project #2"), - (11, 1, "project #3"), + ("November", 1, "project #3"), (11, 22, "project #4"), (9, 18, "QUIZ #1"), @@ -73,14 +73,16 @@ #let shading-encodings = ( holiday-encodings, - finals-encodings, - test + finals-encodings ) +#let test2 = ("nov", 5, "BLAH") + #calendars.draw-calendar( ("aug", "dec"), (23, 12), title: "CS-1181 FA26", color-codes: color-coding, - due-dates: due-dates, + shading: shading-encodings, + due-dates: due-dates ) diff --git a/src/calendars.typ b/src/calendars.typ index d1cd69f..cdac784 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -1,3 +1,4 @@ +// months with their corresponding default number of days #let month-days = ( ("January", 31), ("February", 28), @@ -13,6 +14,8 @@ ("December", 31) ) +// acceptable string inputs for each month +// case-insensitive #let month-inputs = ( ("january", "jan"), ("february", "feb"), @@ -28,6 +31,7 @@ ("december", "dec"), ) +// array for days of the week #let day-arr = ( "Sunday", "Monday", @@ -69,8 +73,13 @@ body } + +/// days-of-week: prints the days of the week to the document +/// is-mon-start bool: flag to control whether to start on sunday or monday #let days-of-week(is-mon-start: false) = { let days = day-arr + // if monday start, + // removes sunday from the beginning and appends it to the end if is-mon-start { let old = days.remove(0) days.push("Sunday") @@ -84,21 +93,31 @@ ) } -#let month-header(month-id, is-mon-start: false) = { - assert( - type(month-id) == int, - message: "Expected month-id to be int, but received" + str(type(month-id)) - ) +/// month-header: prints the month header and days of the week to the document +/// - month-id int: id for the month to print +/// - is-mon-start bool: flag to control whether to start on sunday or monday +#let month-header(month-id, is-mon-start: false) = { v(-10pt) + // print month text[== #month-days.at(month-id).at(0)] line(length: 100%, stroke: 1pt) v(-15pt) + // print days of week days-of-week(is-mon-start: is-mon-start) v(-15pt) line(length: 100%, stroke: 1pt) } + +/// construct-day-arr: constructs an array with all the encodings for each day in a month as well as info to assist the creation of encodings for the next month +/// - month-id int: the month to construct days for +/// - start int: the day to start the month on +/// - last-month-max int: allows the month to end sooner than the number of days in the month +/// - last-week-num int: the most recently printed week number +/// - is-leap-year bool: flag to control number of days in february +/// - shading array: any shading info to encode +/// - assigns array: any text to encode on the day #let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:()) = { // number of days in the month let max-days = month-days.at(month-id).at(1) @@ -127,14 +146,21 @@ week-count = week-count + 1 } - let i = 0 + // add day number to day text let day-text = str(day) + // iterate through any shadings provided + let i = 0 while i < shading.len() { let j = 0 + // grab the dates from the shading arrays let dates = shading.at(i) + // iterate through all provided dates while j < dates.len() { + // grab individual date let date = dates.at(j) + // check if the date matches the current date if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { + // if it is a match, encode the shading id # day-text = day-text + "-" + str(i) break } @@ -143,16 +169,20 @@ i = i + 1 } + // iterate through any assigns provided i = 0 while i < assigns.len() { + // grab an assignment let item = assigns.at(i) + // check if the date matches the current date if (item.at(0) == (month-id + 1)) and (item.at(1) == day) { + // if it is a match, encode the assignment text day-text = day-text + "-" + item.at(2) } i = i + 1 } - // inserts the day number + // inserts the day number and associated encodings day-nums.push(day-text) day = day + 1 @@ -160,12 +190,18 @@ if day > max-days { day = 1 reach-max = true + // increment current month month-id = month-id + 1 } } return (day-nums, week-count, day) } + +/// construct-month-table: creates and draws the table of days for a specific month +/// - days ([DAY NUM ENCODING SEPARATED BY DASHES], [DAY NUM ENCODING SEPARATED BY DASHES], ...): contains all the day numbers and appropriate encodings for each day +/// - keywords ((str, color), (str, color), ...): array of keywords and their corresponding colors +/// - shading-colors (color, color, ...): array of colors to shade specified cells #let construct-month-table(days, keywords, shading-colors) = { v(-18pt) @@ -180,25 +216,40 @@ align: center, rows: 70pt, ..days.map(text-str => { + // split day date on dash let all-pieces = text-str.split("-") // if there is only a number if all-pieces.len() == 1 { + // print that number bolded centered in the cell table.cell()[#text(weight: "bold")[#text-str]] } else { - let fill-color = none + // initialize content to include the number let num = all-pieces.at(0) - let temp = [] let content = text(weight: "bold")[#num] + + // init vars + let fill-color = none + let temp = [] let skip = false + + // start grabbing encodings at 1 since we already grabbed the day num let i = 1 + + // iterate through day encodings while i < all-pieces.len() { skip = false + + // grab current encoding piece temp = all-pieces.at(i) + // checks if the cell should be shaded let shade-id = 0 while shade-id < shading-colors.len() { + // if encoding is the id matching a shading id if temp == str(shade-id) { + // update the fill color fill-color = shading-colors.at(shade-id) + // no need to check for keywords w/ this encoding skip = true } shade-id = shade-id + 1 @@ -209,7 +260,9 @@ // check all keywords and perform color coding as needed let j = 0 while j < keywords.len() { + // if the encoding matches a keyword if temp.contains(keywords.at(j).at(0)) { + // format the text appropriately and exit temp = text(weight: "bold", fill: keywords.at(j).at(1))[#temp] break } @@ -220,13 +273,16 @@ } i = i + 1 } - + // print content to the cell table.cell(fill: fill-color)[#content] } }) ) } + +/// get-numeric-month: takes a string and sees if it is a valid month; if it is, converts it to a numeric month +/// - input str: string to check if it is a valid month #let get-numeric-month(input) = { let index = 0 let numeric-month = -1 @@ -247,6 +303,9 @@ return numeric-month } + +/// create-color-date-shading-arrays: Handles all possible types of shading encodings +/// - encoding: the encoding to check if it is in the right format for shading param #let create-color-date-shading-arrays(encoding) = { let dates = () let colors = () @@ -306,14 +365,16 @@ return (dates, colors) } + /// draw-calendar: Render the calendar as specified -/// - month-range (int, int) OR (str, str): the start and ending months of the range desired for the calendar +/// - month-range (int or str, int or str): the start and ending months of the range desired for the calendar /// - day-range (int, int): the starting and ending day values for the calendar /// - title str: adds the provided title to the calendar, defaults to no title /// - is-leap-year bool: indicates whether the year is a leap year (feb has 29 days), defaults to false /// - is-mon-start bool: toggles between sunday and monday starts for the week, defaults to sunday start -/// - color-codes (str, color) OR ((str, color), (str, color), ...): indicates if certain words should be colored the specified color, defaults to empty array -/// - shading: +/// - color-codes (str, color) OR ((str, color), (str, color), ...): indicates if certain words should be colored the specified color, defaults to an empty array +/// - shading ([DATE], color) OR ([DATE ARRAY], color) OR (([DATE], color), ([DATE ARRAY], color), ...) where [DATE] = (int or str, int): takes pairs of date(s) and colors to shade the date boxes accordingly, defaults to an empty array +/// - due-dates: (int or str, int, str) OR ((int or str, int, str), (int or str, int, str), ...): indicates text that the user wants printed on a specific date, defaults to an empty array #let draw-calendar(month-range, day-range, title: "", is-leap-year: false, is-mon-start: false, color-codes: (), shading: (), due-dates: ()) = { ////////////////// // MONTH CHECKS // @@ -462,6 +523,7 @@ message: "Expected shading to be an array but received " + str(type(shading)) ) + // constructs proper dates and colors arrays let dates = () let colors = () if shading != () { @@ -480,6 +542,50 @@ } } + + ///////////////////// + // DUE DATE CHECKS // + ///////////////////// + assert( + type(due-dates) == array, + message: "Expected due-dates to be an array but received " + str(type(due-dates)) + ) + + // constructs proper date text arrays + let date-text = () + if due-dates != () { + // handles a single date text + if (type(due-dates.at(0)) == int or type(due-dates.at(0)) == str) and type(due-dates.at(1)) == int and type(due-dates.at(2)) == str { + if type(due-dates.at(0)) == str { + due-dates.at(0) = get-numeric-month(due-dates.at(0)) + } + date-text.push(due-dates) + // handles multiple dates + } else { + assert( + due-dates.all(d => { + type(d) == array and d.len() == 3 and (type(d.at(0)) == int or type(d.at(0)) == str) and type(d.at(1)) == int and type(d.at(2)) == str + }), + message: "Expected due-dates to be an array of arrays of form (int or str, int, str)" + ) + + let due-date-index = 0 + while due-date-index < due-dates.len() { + if type(due-dates.at(due-date-index).at(0)) == str { + due-dates.at(due-date-index).at(0) = get-numeric-month(due-dates.at(due-date-index).at(0)) + } + date-text.push(due-dates.at(due-date-index)) + due-date-index = due-date-index + 1 + } + } + } + + + ////////////////////////////////// + // ACTUAL CALENDAR CONSTRUCTION // + ////////////////////////////////// + + // adjust start month for zero-indexing let month = month-range.at(0) - 1 let start-day = day-range.at(0) let days = (none, 1) @@ -492,11 +598,16 @@ end-day = day-range.at(1) } + // create header for the month month-header(month, is-mon-start: is-mon-start) - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: due-dates) + // construct an array of the days and all their appropriate encodings for that month + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: date-text) + // print table of days w/ appropriate info construct-month-table(days.at(0), color-codes, colors) + // get new start day from prior day array generation start-day = days.at(2) + month = month + 1 } } From 1acf0395c5bbd98074209cbaef8a0c646f7fb012 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Mon, 27 Jul 2026 17:42:26 -0400 Subject: [PATCH 16/22] add import to lib --- lib.typ | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib.typ b/lib.typ index fc08859..a26fc8a 100755 --- a/lib.typ +++ b/lib.typ @@ -1,2 +1,3 @@ #import "src/exams.typ" -#import "src/labs.typ" \ No newline at end of file +#import "src/labs.typ" +#import "src/calendars.typ" \ No newline at end of file From 5609dffcc0135539339f83f76fbb4090c1d2a47d Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Tue, 28 Jul 2026 13:27:25 -0400 Subject: [PATCH 17/22] now supports week overviews --- examples/calendar_example.typ | 28 ++++++- src/calendars.typ | 135 +++++++++++++++++++++++++++++----- 2 files changed, 139 insertions(+), 24 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 621245e..25e62cf 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -1,4 +1,3 @@ -#import "@preview/codepoint:0.2.1":labs #import "../src/calendars.typ" #show: calendars.init @@ -10,7 +9,7 @@ ("QUIZ", rgb("#a15d10")), ("exam", rgb("#a11010")), ("FINAL", rgb("#a11010")), - ("MIDTERM", rgb("#a11010")), + ("MIDTERM", rgb("#a11010")) ) #let due-dates = ( @@ -48,6 +47,10 @@ (10, 16, "MIDTERM"), (11, 13, "QUIZ #2"), (12, 11, "FINAL"), + + (9,9, "project #1"), + (9,9, "QUIZ #1"), + (9,9, "lab #8"), ) #let holidays = ( @@ -78,11 +81,28 @@ #let test2 = ("nov", 5, "BLAH") +#let overviews = ( + (1, "INTRO AND OOP REVIEW"), + (2, "CONTINUED REVIEW, ABSTRACT CLASSES, INTERFACES"), + (3, "DYNAMIC DISPATCH, INNER CLASSES, LAMBDAS"), + (4, "GENERICS"), + (5, "GENERICS, COLLECTIONS, LIST, ADT INTRO"), + (6, "ADTS, LISTS, STACKS, QUEUES, MAPS, SETS"), + (7, "GUI"), + (9, "GUI"), + (10, "RECURSION"), + (11, "RECURSION"), + (12, "SEARCHING, SORTING, THREADING"), + (13, "THREADING"), + (15, "CATCH-UP, INSTRUCTOR CHOSEN, REVIEW"), +) + #calendars.draw-calendar( ("aug", "dec"), (23, 12), title: "CS-1181 FA26", color-codes: color-coding, shading: shading-encodings, - due-dates: due-dates -) + due-dates: due-dates, + week-overviews: (overviews, rgb("#305e61")), +) \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index cdac784..285cabb 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -118,7 +118,7 @@ /// - is-leap-year bool: flag to control number of days in february /// - shading array: any shading info to encode /// - assigns array: any text to encode on the day -#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:()) = { +#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:(), overviews:()) = { // number of days in the month let max-days = month-days.at(month-id).at(1) @@ -148,6 +148,7 @@ // add day number to day text let day-text = str(day) + // iterate through any shadings provided let i = 0 while i < shading.len() { @@ -161,7 +162,7 @@ // check if the date matches the current date if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { // if it is a match, encode the shading id # - day-text = day-text + "-" + str(i) + day-text = day-text + "𖦹" + str(i) break } j = j + 1 @@ -177,11 +178,24 @@ // check if the date matches the current date if (item.at(0) == (month-id + 1)) and (item.at(1) == day) { // if it is a match, encode the assignment text - day-text = day-text + "-" + item.at(2) + day-text = day-text + "𖦹" + item.at(2) } i = i + 1 } + // if there is overview text, and we are on the middle day of the week, + if overviews != () and calc.rem(day-nums.len(), 8) == 4 { + // iterate through all overviews + let overview-index = 0 + while overview-index < overviews.len() { + // if overview week num matches current week num, add overview text + if (week-count - 1) == overviews.at(overview-index).at(0) { + day-text = day-text + "𖦹OVR𖦹" + overviews.at(overview-index).at(1) + } + overview-index = overview-index + 1 + } + } + // inserts the day number and associated encodings day-nums.push(day-text) @@ -202,7 +216,8 @@ /// - days ([DAY NUM ENCODING SEPARATED BY DASHES], [DAY NUM ENCODING SEPARATED BY DASHES], ...): contains all the day numbers and appropriate encodings for each day /// - keywords ((str, color), (str, color), ...): array of keywords and their corresponding colors /// - shading-colors (color, color, ...): array of colors to shade specified cells -#let construct-month-table(days, keywords, shading-colors) = { +/// - overview-color color: color for overview text +#let construct-month-table(days, keywords, shading-colors, overview-color) = { v(-18pt) show table.cell: it => { @@ -217,7 +232,7 @@ rows: 70pt, ..days.map(text-str => { // split day date on dash - let all-pieces = text-str.split("-") + let all-pieces = text-str.split("𖦹") // if there is only a number if all-pieces.len() == 1 { // print that number bolded centered in the cell @@ -231,6 +246,7 @@ let fill-color = none let temp = [] let skip = false + let amt-down = 0pt // start grabbing encodings at 1 since we already grabbed the day num let i = 1 @@ -242,17 +258,27 @@ // grab current encoding piece temp = all-pieces.at(i) - // checks if the cell should be shaded - let shade-id = 0 - while shade-id < shading-colors.len() { - // if encoding is the id matching a shading id - if temp == str(shade-id) { - // update the fill color - fill-color = shading-colors.at(shade-id) - // no need to check for keywords w/ this encoding - skip = true + // checks for overview flag and adds the overview text + if temp == "OVR" { + i = i + 1 + let disp = 30pt - amt-down + content = content + v(disp) + box(width: 1000pt)[#align(center)[#text(fill: overview-color)[*_#all-pieces.at(i)_*]]] + skip = true + } + + if skip == false { + // checks if the cell should be shaded + let shade-id = 0 + while shade-id < shading-colors.len() { + // if encoding is the id matching a shading id + if temp == str(shade-id) { + // update the fill color + fill-color = shading-colors.at(shade-id) + // no need to check for keywords w/ this encoding + skip = true + } + shade-id = shade-id + 1 } - shade-id = shade-id + 1 } // only need to check for keywords if we determine it is not a shading key @@ -269,7 +295,9 @@ j = j + 1 } // append the text on a new line, left-justified - content = content + align(left)[#v(-10pt)#temp] + content = content + align(left)[#v(-13pt)#temp] + // adjusts the displacement for overview text based on how many lines of text have been added + amt-down = amt-down + 12.25pt } i = i + 1 } @@ -374,8 +402,9 @@ /// - is-mon-start bool: toggles between sunday and monday starts for the week, defaults to sunday start /// - color-codes (str, color) OR ((str, color), (str, color), ...): indicates if certain words should be colored the specified color, defaults to an empty array /// - shading ([DATE], color) OR ([DATE ARRAY], color) OR (([DATE], color), ([DATE ARRAY], color), ...) where [DATE] = (int or str, int): takes pairs of date(s) and colors to shade the date boxes accordingly, defaults to an empty array -/// - due-dates: (int or str, int, str) OR ((int or str, int, str), (int or str, int, str), ...): indicates text that the user wants printed on a specific date, defaults to an empty array -#let draw-calendar(month-range, day-range, title: "", is-leap-year: false, is-mon-start: false, color-codes: (), shading: (), due-dates: ()) = { +/// - due-dates (int or str, int, str) OR ((int or str, int, str), (int or str, int, str), ...): indicates text that the user wants printed on a specific date, defaults to an empty array +/// - week-overviews (int, str, color) OR (((int, str), (int, str), ...), color): indicates a week number and text to add as an overview to that week +#let draw-calendar(month-range, day-range, title: "", is-leap-year: false, is-mon-start: false, color-codes: (), shading: (), due-dates: (), week-overviews: ()) = { ////////////////// // MONTH CHECKS // ////////////////// @@ -581,6 +610,72 @@ } + ////////////////////// + // OVERVIEWS CHECKS // + ////////////////////// + assert( + type(week-overviews) == array, + message: "Expected week-overviews to be an array but received " + str(type(week-overviews)) + ) + + let overview-text = () + let overview-color = black + if week-overviews != () { + // account for single week # and text pair + if type(week-overviews.at(0)) == int and type(week-overviews.at(1)) == str { + // accounts for optional color added + if week-overviews.len() > 2 { + assert( + type(week-overviews.at(2)) == color, + message: "Expected week-overviews to be an array of form (int, str, color) but received (" + str(type(week-overviews.at(0))) + ", " + str(type(week-overviews.at(1))) + ", " + str(type(week-overviews.at(2))) + ")" + ) + overview-color = week-overviews.at(2) + } + assert( + week-overviews.at(0) > 0, + message: "Week-overviews week number must be greater than 0; received " + str(week-overviews.at(0)) + ) + overview-text.push((week-overviews.at(0), week-overviews.at(1))) + // account for array of week # and text pairs + } else { + assert( + type(week-overviews.at(0)) == array, + message: "Expected week-overviews to be an array of arrays but received " + str(type(week-overviews.at(0))) + ) + + let all-overviews = () + // account for color provided + if week-overviews.len() > 1 and type(week-overviews.at(1)) == color { + overview-color = week-overviews.at(1) + + assert( + week-overviews.at(0).all(w => { + type(w.at(0)) == int and w.at(0) > 0 and type(w.at(1)) == str + }), + message: "Expected week-overviews to be an array of arrays of form (((int > 0, str), (int > 0, str), ...), color)" + ) + all-overviews = week-overviews.at(0) + // account for no color provided + } else { + assert( + week-overviews.all(w => { + type(w.at(0)) == int and w.at(0) > 0 and type(w.at(1)) == str + }), + message: "Expected week-overviews to be an array of arrays of form ((int > 0, str), (int > 0, str), ...)" + ) + all-overviews = week-overviews + } + + // add all pairs + let overview-index = 0 + while overview-index < all-overviews.len() { + overview-text.push((all-overviews.at(overview-index).at(0), all-overviews.at(overview-index).at(1))) + overview-index = overview-index + 1 + } + } + } + + ////////////////////////////////// // ACTUAL CALENDAR CONSTRUCTION // ////////////////////////////////// @@ -601,9 +696,9 @@ // create header for the month month-header(month, is-mon-start: is-mon-start) // construct an array of the days and all their appropriate encodings for that month - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: date-text) + days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: date-text, overviews: overview-text) // print table of days w/ appropriate info - construct-month-table(days.at(0), color-codes, colors) + construct-month-table(days.at(0), color-codes, colors, overview-color) // get new start day from prior day array generation start-day = days.at(2) From a7a6cf0f2404950fb166f865142339a95b841bda Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Tue, 28 Jul 2026 13:49:47 -0400 Subject: [PATCH 18/22] fixed week # offset --- examples/calendar_example.typ | 2 +- src/calendars.typ | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 25e62cf..cf9813f 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -105,4 +105,4 @@ shading: shading-encodings, due-dates: due-dates, week-overviews: (overviews, rgb("#305e61")), -) \ No newline at end of file +) diff --git a/src/calendars.typ b/src/calendars.typ index 285cabb..9fc94cc 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -223,7 +223,12 @@ show table.cell: it => { // matchs to the text "week #" followed by a one or two digit number // rotates and bolds the text - show regex("week #\d{1,2}"): it => text(size:11pt, weight: "bold")[#rotate(-90deg, reflow: true)[#v(-20pt)#it]] + + // MAGIC LINE: DO NOT TOUCH + // if you want to change offset left to right, ONLY TOUCH................................................................THIS NUMBER + // vvv + show regex("week #\d{1,2}"): it => text(size:11pt, weight: "bold")[#align(horizon)[#box(width: 1000pt)[#rotate(-90deg)[#it#v(15pt)]]]] + // END MAGIC LINE it } table( From ed05ff1e4dcb16a86c957c89fe9316fcd308a132 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Tue, 28 Jul 2026 13:55:40 -0400 Subject: [PATCH 19/22] upped overview font size 1pt --- examples/calendar_example.typ | 6 +----- src/calendars.typ | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index cf9813f..2d6dfce 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -47,10 +47,6 @@ (10, 16, "MIDTERM"), (11, 13, "QUIZ #2"), (12, 11, "FINAL"), - - (9,9, "project #1"), - (9,9, "QUIZ #1"), - (9,9, "lab #8"), ) #let holidays = ( @@ -105,4 +101,4 @@ shading: shading-encodings, due-dates: due-dates, week-overviews: (overviews, rgb("#305e61")), -) +) \ No newline at end of file diff --git a/src/calendars.typ b/src/calendars.typ index 9fc94cc..eeb211f 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -267,7 +267,7 @@ if temp == "OVR" { i = i + 1 let disp = 30pt - amt-down - content = content + v(disp) + box(width: 1000pt)[#align(center)[#text(fill: overview-color)[*_#all-pieces.at(i)_*]]] + content = content + v(disp) + box(width: 1000pt)[#align(center)[#text(fill: overview-color, size: 12pt)[*_#all-pieces.at(i)_*]]] skip = true } From 42760a5adde1eb5bd7d05bcecd260604082fd3bd Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Tue, 28 Jul 2026 14:42:05 -0400 Subject: [PATCH 20/22] fix leap year handling --- examples/calendar_example.typ | 6 +- src/calendars.typ | 125 +++++++++++++++++++++++++++++----- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 2d6dfce..37b9edc 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -101,4 +101,8 @@ shading: shading-encodings, due-dates: due-dates, week-overviews: (overviews, rgb("#305e61")), -) \ No newline at end of file +) + +#let q = calendars.construct-recurring-dates(("aug", 30), 11, "zyBooks", dates-to-skip: ((10, 18),)) +#q + diff --git a/src/calendars.typ b/src/calendars.typ index eeb211f..e154336 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -119,19 +119,20 @@ /// - shading array: any shading info to encode /// - assigns array: any text to encode on the day #let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:(), overviews:()) = { + // account for leap year + let mon-days = month-days + if is-leap-year { + mon-days.at(1).at(1) = 29 + } + // number of days in the month - let max-days = month-days.at(month-id).at(1) + let max-days = mon-days.at(month-id).at(1) // override if different last day is provided if last-month-max != none { max-days = last-month-max } - // account for leap year - if is-leap-year and month-id == 1 { - max-days = max-days + 1; - } - let day-nums = () let day = start let reach-max = false @@ -410,6 +411,21 @@ /// - due-dates (int or str, int, str) OR ((int or str, int, str), (int or str, int, str), ...): indicates text that the user wants printed on a specific date, defaults to an empty array /// - week-overviews (int, str, color) OR (((int, str), (int, str), ...), color): indicates a week number and text to add as an overview to that week #let draw-calendar(month-range, day-range, title: "", is-leap-year: false, is-mon-start: false, color-codes: (), shading: (), due-dates: (), week-overviews: ()) = { + ///////////////////// + // LEAP YEAR CHECK // + ///////////////////// + assert( + type(is-leap-year) == bool, + message: "Expected is-leap-year to be bool but received " + str(type(is-leap-year)) + ) + + // change feb to 29 days + let mon-days = month-days + if is-leap-year { + mon-days.at(1).at(1) = 29 + } + + ////////////////// // MONTH CHECKS // ////////////////// @@ -477,8 +493,13 @@ ) assert( - day-range.at(0) > 0 and day-range.at(0) < 32 and day-range.at(1) > 0 and day-range.at(1) < 32, - message: "Day-range values must be >0 and <32" + day-range.at(0) > 0 and day-range.at(0) <= mon-days.at(month-range.at(0) - 1).at(1), + message: "First day-range value must be >0 and <=" + str(mon-days.at(month-range.at(0) - 1).at(1)) + ) + + assert( + day-range.at(1) > 0 and day-range.at(1) <= mon-days.at(month-range.at(1) - 1).at(1), + message: "Second day-range values must be >0 and <=" + str(mon-days.at(month-range.at(1) - 1).at(1)) ) @@ -498,15 +519,6 @@ } - ///////////////////// - // LEAP YEAR CHECK // - ///////////////////// - assert( - type(is-leap-year) == bool, - message: "Expected is-leap-year to be bool but received " + str(type(is-leap-year)) - ) - - //////////////////////// // MONDAY START CHECK // //////////////////////// @@ -713,3 +725,82 @@ } +#let construct-recurring-dates(start-date, total, title, dates-to-skip: (), is-leap-year: false) = { + ///////////////////// + // LEAP YEAR CHECK // + ///////////////////// + assert( + type(is-leap-year) == bool, + message: "Expected is-leap-year to be bool but received " + str(type(is-leap-year)) + ) + + // change feb to 29 days + let mon-days = month-days + if is-leap-year { + mon-days.at(1).at(1) = 29 + } + + + /////////////////////// + // START DATE CHECKS // + /////////////////////// + assert( + type(start-date) == array and start-date.len() == 2, + message: "Expected start-date to be an array of length 2 but received " + str(type(start-date)) + ) + + assert( + (type(start-date.at(0)) == int or type(start-date.at(0)) == str) and type(start-date.at(1)) == int, + message: "Expected start-date to be an array of form: (int or str, int) but received: (" + str(type(start-date.at(0))) + ", " + str(type(start-date.at(1))) + ")" + ) + + // if a str month is provided, check that it is valid and convert to numeric + if type(start-date.at(0)) == str { + start-date.at(0) = get-numeric-month(start-date.at(0)) + } + + assert( + start-date.at(0) > 0 and start-date.at(0) < 13, + message: "Month of start-date must be >0 and <13" + ) + + assert( + start-date.at(1) > 0 and start-date.at(1) <= mon-days.at(start-date.at(0) - 1).at(1), + message: "Day of start-date must be >0 and <=" + str(mon-days.at(start-date.at(0) - 1).at(1)) + ) + + + let dates = () + + let curr-mon = start-date.at(0) + let curr-day = start-date.at(1) + let curr-name = title + " #1" + dates.push((curr-mon, curr-day, curr-name)) + + let i = 1 + while i < total { + curr-day = curr-day + 7 + if curr-day > mon-days.at(curr-mon - 1).at(1) { + curr-day = curr-day - mon-days.at(curr-mon - 1).at(1) + curr-mon = curr-mon + 1 + } + + let valid-day = true + let j = 0 + while j < dates-to-skip.len() { + if curr-mon == dates-to-skip.at(j).at(0) and curr-day == dates-to-skip.at(j).at(1){ + valid-day = false + } + j = j + 1 + } + + if valid-day { + i = i + 1 + curr-name = title + " #" + str(i) + dates.push((curr-mon, curr-day, curr-name)) + } + } + + return dates +} + From 645dc1c2b04f106a3d715d8637a0eecd05974e90 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Tue, 28 Jul 2026 15:22:06 -0400 Subject: [PATCH 21/22] added function to help generate assignments that recur --- examples/calendar_example.typ | 60 +++++-------- src/calendars.typ | 153 ++++++++++++++++++++++++++++------ 2 files changed, 150 insertions(+), 63 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 37b9edc..20948c0 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -12,32 +12,7 @@ ("MIDTERM", rgb("#a11010")) ) -#let due-dates = ( - (8, 30, "zyBooks #1"), - (9, 6, "zyBooks #2"), - (9, 13, "zyBooks #3"), - (9, 20, "zyBooks #4"), - (9, 27, "zyBooks #5"), - ("oct", 4, "zyBooks #6"), - (10, 11, "zyBooks #7"), - (10, 18, "zyBooks #8"), - (10, 25, "zyBooks #9"), - (11, 1, "zyBooks #10"), - (11, 8, "zyBooks #11"), - - (8, 30, "lab #1"), - (9, 6, "lab #2"), - (9, 13, "lab #3"), - (9, 20, "lab #4"), - (9, 27, "lab #5"), - (10, 4, "lab #6"), - (10, 11, "lab #7"), - (10, 18, "lab #8"), - (10, 25, "lab #9"), - (11, 1, "lab #10"), - (11, 8, "lab #11"), - (11, 15, "lab #12"), - +#let non-recurring = ( (9, 20, "project #1"), (10, 11, "project #2"), ("November", 1, "project #3"), @@ -49,34 +24,46 @@ (12, 11, "FINAL"), ) -#let holidays = ( +#let zy = calendars.construct-recurring-dates( + ("aug", 30), + 11, + "zyBooks", + dates-to-skip: (10, 18) +) + +#let lab = calendars.construct-recurring-dates( + ("aug", 30), + 12, + "lab", + dates-to-skip: (10, 18) +) +#let due-dates = non-recurring + zy + lab + +#let holiday-encodings = (( (9, 7), ("nov", 11), (11, 25), (11, 26), (11, 27) + ), + gray ) -#let finals-week = ( +#let finals-encodings = (( (12, 7), (12, 8), ("dec", 9), (12, 10), ("december", 11) + ), + rgb("#f59998") ) -#let test = (("september", 23), blue) - -#let holiday-encodings = (holidays, gray) -#let finals-encodings = (finals-week, rgb("#f59998")) - #let shading-encodings = ( holiday-encodings, finals-encodings ) -#let test2 = ("nov", 5, "BLAH") - #let overviews = ( (1, "INTRO AND OOP REVIEW"), (2, "CONTINUED REVIEW, ABSTRACT CLASSES, INTERFACES"), @@ -103,6 +90,5 @@ week-overviews: (overviews, rgb("#305e61")), ) -#let q = calendars.construct-recurring-dates(("aug", 30), 11, "zyBooks", dates-to-skip: ((10, 18),)) -#q + diff --git a/src/calendars.typ b/src/calendars.typ index e154336..4f5682d 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -75,7 +75,7 @@ /// days-of-week: prints the days of the week to the document -/// is-mon-start bool: flag to control whether to start on sunday or monday +/// is-mon-start (bool): flag to control whether to start on sunday or monday #let days-of-week(is-mon-start: false) = { let days = day-arr // if monday start, @@ -95,8 +95,8 @@ /// month-header: prints the month header and days of the week to the document -/// - month-id int: id for the month to print -/// - is-mon-start bool: flag to control whether to start on sunday or monday +/// - month-id (int): id for the month to print +/// - is-mon-start (bool): flag to control whether to start on sunday or monday #let month-header(month-id, is-mon-start: false) = { v(-10pt) // print month @@ -111,13 +111,13 @@ /// construct-day-arr: constructs an array with all the encodings for each day in a month as well as info to assist the creation of encodings for the next month -/// - month-id int: the month to construct days for -/// - start int: the day to start the month on -/// - last-month-max int: allows the month to end sooner than the number of days in the month -/// - last-week-num int: the most recently printed week number -/// - is-leap-year bool: flag to control number of days in february -/// - shading array: any shading info to encode -/// - assigns array: any text to encode on the day +/// - month-id (int): the month to construct days for +/// - start (int): the day to start the month on +/// - last-month-max (int): allows the month to end sooner than the number of days in the month +/// - last-week-num (int): the most recently printed week number +/// - is-leap-year (bool): flag to control number of days in february +/// - shading (array): any shading info to encode +/// - assigns (array): any text to encode on the day #let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:(), overviews:()) = { // account for leap year let mon-days = month-days @@ -214,10 +214,10 @@ /// construct-month-table: creates and draws the table of days for a specific month -/// - days ([DAY NUM ENCODING SEPARATED BY DASHES], [DAY NUM ENCODING SEPARATED BY DASHES], ...): contains all the day numbers and appropriate encodings for each day -/// - keywords ((str, color), (str, color), ...): array of keywords and their corresponding colors -/// - shading-colors (color, color, ...): array of colors to shade specified cells -/// - overview-color color: color for overview text +/// - days (([DAY NUM ENCODING SEPARATED BY DASHES], [DAY NUM ENCODING SEPARATED BY DASHES], ...)): contains all the day numbers and appropriate encodings for each day +/// - keywords (((str, color), (str, color), ...)): array of keywords and their corresponding colors +/// - shading-colors ((color, color, ...)): array of colors to shade specified cells +/// - overview-color (color): color for overview text #let construct-month-table(days, keywords, shading-colors, overview-color) = { v(-18pt) @@ -316,7 +316,7 @@ /// get-numeric-month: takes a string and sees if it is a valid month; if it is, converts it to a numeric month -/// - input str: string to check if it is a valid month +/// - input (str): string to check if it is a valid month #let get-numeric-month(input) = { let index = 0 let numeric-month = -1 @@ -339,7 +339,7 @@ /// create-color-date-shading-arrays: Handles all possible types of shading encodings -/// - encoding: the encoding to check if it is in the right format for shading param +/// - encoding (array): the encoding to check if it is in the right format for shading param #let create-color-date-shading-arrays(encoding) = { let dates = () let colors = () @@ -401,15 +401,15 @@ /// draw-calendar: Render the calendar as specified -/// - month-range (int or str, int or str): the start and ending months of the range desired for the calendar -/// - day-range (int, int): the starting and ending day values for the calendar -/// - title str: adds the provided title to the calendar, defaults to no title -/// - is-leap-year bool: indicates whether the year is a leap year (feb has 29 days), defaults to false -/// - is-mon-start bool: toggles between sunday and monday starts for the week, defaults to sunday start -/// - color-codes (str, color) OR ((str, color), (str, color), ...): indicates if certain words should be colored the specified color, defaults to an empty array -/// - shading ([DATE], color) OR ([DATE ARRAY], color) OR (([DATE], color), ([DATE ARRAY], color), ...) where [DATE] = (int or str, int): takes pairs of date(s) and colors to shade the date boxes accordingly, defaults to an empty array -/// - due-dates (int or str, int, str) OR ((int or str, int, str), (int or str, int, str), ...): indicates text that the user wants printed on a specific date, defaults to an empty array -/// - week-overviews (int, str, color) OR (((int, str), (int, str), ...), color): indicates a week number and text to add as an overview to that week +/// - month-range ((int or str, int or str)): the start and ending months of the range desired for the calendar +/// - day-range ((int, int)): the starting and ending day values for the calendar +/// - title (str): adds the provided title to the calendar, defaults to no title +/// - is-leap-year (bool): indicates whether the year is a leap year (feb has 29 days), defaults to false +/// - is-mon-start (bool): toggles between sunday and monday starts for the week, defaults to sunday start +/// - color-codes ((str, color) OR ((str, color), (str, color), ...)): indicates if certain words should be colored the specified color, defaults to an empty array +/// - shading (([DATE], color) OR ([DATE ARRAY], color) OR (([DATE], color), ([DATE ARRAY], color), ...)) where [DATE] = (int or str, int): takes pairs of date(s) and colors to shade the date boxes accordingly, defaults to an empty array +/// - due-dates ((int or str, int, str) OR ((int or str, int, str), (int or str, int, str), ...)): indicates text that the user wants printed on a specific date, defaults to an empty array +/// - week-overviews ((int, str, color) OR (((int, str), (int, str), ...), color)): indicates a week number and text to add as an overview to that week #let draw-calendar(month-range, day-range, title: "", is-leap-year: false, is-mon-start: false, color-codes: (), shading: (), due-dates: (), week-overviews: ()) = { ///////////////////// // LEAP YEAR CHECK // @@ -725,6 +725,13 @@ } +/// construct-recurring-dates: helps generate an array for assignments that recur weekly +/// - start-date ((int or str, int)): date to have the first assignment +/// - total (int): total number of assignments desired +/// - title (str): name of the assignment +/// - dates-to-skip ((int or str, int) OR ((int or str, int), (int or str, int), ...)): dates to exclude from recurring +/// - is-leap-year (bool): whether or not the current year is a leap year +/// -> array of dates for assignment #let construct-recurring-dates(start-date, total, title, dates-to-skip: (), is-leap-year: false) = { ///////////////////// // LEAP YEAR CHECK // @@ -770,16 +777,107 @@ ) - let dates = () + ///////////////// + // TOTAL CHECK // + ///////////////// + assert( + type(total) == int and total > 0, + message: "Expected total to be an int >0 but received " + str(total) + ) + + + ///////////////// + // TITLE CHECK // + ///////////////// + assert( + type(title) == str, + message: "Expected title to be str but received " + str(type(str)) + ) + + + /////////////////////// + // SKIP DATES CHECK // + ////////////////////// + assert( + type(dates-to-skip) == array, + message: "Expected dates-to-skip to be an array but received " + str(type(dates-to-skip)) + ) + + if dates-to-skip != () { + // allows user to pass a singular date: (int or str, int) + // by converting to an array of dates + if (type(dates-to-skip.at(0)) == int or type(dates-to-skip.at(0)) == str) and type(dates-to-skip.at(1)) == int { + // if a str month is provided, check that it is valid and convert to numeric + if type(dates-to-skip.at(0)) == str { + dates-to-skip.at(0) = get-numeric-month(dates-to-skip.at(0)) + } + + assert( + dates-to-skip.at(0) > 0 and dates-to-skip.at(0) < 13, + message: "Month of dates-to-skip must be >0 and <13" + ) + assert( + dates-to-skip.at(1) > 0 and dates-to-skip.at(1) <= mon-days.at(dates-to-skip.at(0) - 1).at(1), + message: "Day of dates-to-skip must be >0 and <=" + str(mon-days.at(dates-to-skip.at(0) - 1).at(1)) + ) + + let temp = dates-to-skip + dates-to-skip = () + dates-to-skip.push(temp) + } + + // if single value is not in the correct format: (int or str, int) + assert( + type(dates-to-skip.at(0)) == array, + message: "Expected dates-to-skip in the format: ((int or str, int), (int or str, int), ...)" + ) + + // if an array of dates is provided: ((int or str, int), (int or str, int), ...) + assert( + dates-to-skip.all(d => { + (type(d.at(0)) == int or type(d.at(0)) == str) and type(d.at(1)) == int + }), + message: "Expected all dates-to-skip dates to be in the format: (int or str, int)" + ) + + let index = 0 + let temp = dates-to-skip + dates-to-skip = () + while index < temp.len() { + // check each month and day is in range + if temp.at(index).at(0) == str { + temp.at(index).at(0) = get-numeric-month(temp.at(index).at(0)) + } + + assert( + temp.at(index).at(0) > 0 and temp.at(index).at(0) < 13, + message: "Month of dates-to-skip must be >0 and <13" + ) + + assert( + temp.at(index).at(1) > 0 and temp.at(index).at(1) <= mon-days.at(temp.at(index).at(0) - 1).at(1), + message: "Day of dates-to-skip must be >0 and <=" + str(mon-days.at(temp.at(index).at(0) - 1).at(1)) + ) + // add date + dates-to-skip.push(temp.at(index)) + index = index + 1 + } + } + + + // initialize first assignment date + let dates = () let curr-mon = start-date.at(0) let curr-day = start-date.at(1) let curr-name = title + " #1" dates.push((curr-mon, curr-day, curr-name)) + // iterate through remaining assignments required let i = 1 while i < total { curr-day = curr-day + 7 + // handles when the day exceeds the current month if curr-day > mon-days.at(curr-mon - 1).at(1) { curr-day = curr-day - mon-days.at(curr-mon - 1).at(1) curr-mon = curr-mon + 1 @@ -787,13 +885,16 @@ let valid-day = true let j = 0 + // iterates through all skip days while j < dates-to-skip.len() { + // if the current day is a day to skip, don't include it if curr-mon == dates-to-skip.at(j).at(0) and curr-day == dates-to-skip.at(j).at(1){ valid-day = false } j = j + 1 } + // ensure day is not to be skipped if valid-day { i = i + 1 curr-name = title + " #" + str(i) From 5649bd8ca7e66c3277583d80b8a29b15086cb7a9 Mon Sep 17 00:00:00 2001 From: Clarissa Milligan Date: Wed, 29 Jul 2026 12:22:17 -0400 Subject: [PATCH 22/22] "privated" some functions; made the delimeter a global variable --- examples/calendar_example.typ | 36 +++++++++++--------- manual/manual.pdf | Bin 124446 -> 57520 bytes manual/manual.typ | 9 +++++ src/calendars.typ | 62 ++++++++++++++++++---------------- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/examples/calendar_example.typ b/examples/calendar_example.typ index 20948c0..6ae6834 100644 --- a/examples/calendar_example.typ +++ b/examples/calendar_example.typ @@ -5,7 +5,7 @@ #let color-coding = ( ("zyBooks", rgb("#10a178")), ("lab", rgb("#104fa1")), - ("project", rgb("#a19e10")), + ("proj", rgb("#a19e10")), ("QUIZ", rgb("#a15d10")), ("exam", rgb("#a11010")), ("FINAL", rgb("#a11010")), @@ -13,14 +13,18 @@ ) #let non-recurring = ( - (9, 20, "project #1"), - (10, 11, "project #2"), - ("November", 1, "project #3"), - (11, 22, "project #4"), + (9, 2, "proj #1 opens"), + (9, 20, "proj #1 due"), + (9, 23, "proj #2 opens"), + (10, 18, "proj #2 due"), + (10, 21, "proj #3 opens"), + ("November", 8, "proj #3 due"), + (11, 11, "proj #4 opens"), + (11, 29, "proj #4 due"), - (9, 18, "QUIZ #1"), - (10, 16, "MIDTERM"), - (11, 13, "QUIZ #2"), + (9, 16, "QUIZ #1"), + (10, 9, "MIDTERM"), + (11, 4, "QUIZ #2"), (12, 11, "FINAL"), ) @@ -28,16 +32,16 @@ ("aug", 30), 11, "zyBooks", - dates-to-skip: (10, 18) + dates-to-skip: ((9, 6), (11, 15)) ) #let lab = calendars.construct-recurring-dates( - ("aug", 30), + ("sep", 6), 12, "lab", - dates-to-skip: (10, 18) + dates-to-skip: ((10, 18), (11, 29)) ) -#let due-dates = non-recurring + zy + lab +#let due-dates = zy + lab + non-recurring #let holiday-encodings = (( (9, 7), @@ -71,13 +75,16 @@ (4, "GENERICS"), (5, "GENERICS, COLLECTIONS, LIST, ADT INTRO"), (6, "ADTS, LISTS, STACKS, QUEUES, MAPS, SETS"), - (7, "GUI"), + (7, "MIDTERM WEEK"), + (8, "GUI"), (9, "GUI"), (10, "RECURSION"), (11, "RECURSION"), (12, "SEARCHING, SORTING, THREADING"), (13, "THREADING"), + (14, "THANKSGIVING BREAK"), (15, "CATCH-UP, INSTRUCTOR CHOSEN, REVIEW"), + (16, "FINALS WEEK") ) #calendars.draw-calendar( @@ -89,6 +96,3 @@ due-dates: due-dates, week-overviews: (overviews, rgb("#305e61")), ) - - - diff --git a/manual/manual.pdf b/manual/manual.pdf index e28f4ac00696718e9595c13d21a43058146f5121..4db4ce4bf39294f5d5eb1c0c153e928b4bc2a943 100644 GIT binary patch literal 57520 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

ZJEIS2Td?b#IM1vFH)K}uy&8K05W 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 VzW&U+23X)M+{9kISld|Ve*sgPPci@i diff --git a/manual/manual.typ b/manual/manual.typ index dc56b86..b35e3f5 100644 --- a/manual/manual.typ +++ b/manual/manual.typ @@ -11,6 +11,10 @@ read("/src/labs.typ"), label-prefix: "labs-" // avoid name collisions ) +#let calendar-docs = tidy.parse-module( + read("/src/calendars.typ"), + label-prefix: "calendars-" // avoid name collisions +) = Exams Module #tidy.show-module(exam-docs, style: tidy.styles.default) @@ -19,3 +23,8 @@ = Labs Module #tidy.show-module(lab-docs, style: tidy.styles.default) + +#pagebreak() + += Calendars Module +#tidy.show-module(calendar-docs, style: tidy.styles.default) diff --git a/src/calendars.typ b/src/calendars.typ index 4f5682d..e455f53 100644 --- a/src/calendars.typ +++ b/src/calendars.typ @@ -1,3 +1,5 @@ +#let delimeter = "꩜" + // months with their corresponding default number of days #let month-days = ( ("January", 31), @@ -74,9 +76,9 @@ } -/// days-of-week: prints the days of the week to the document +/// _days-of-week: prints the days of the week to the document /// is-mon-start (bool): flag to control whether to start on sunday or monday -#let days-of-week(is-mon-start: false) = { +#let _days-of-week(is-mon-start: false) = { let days = day-arr // if monday start, // removes sunday from the beginning and appends it to the end @@ -94,23 +96,23 @@ } -/// month-header: prints the month header and days of the week to the document +/// _month-header: prints the month header and days of the week to the document /// - month-id (int): id for the month to print /// - is-mon-start (bool): flag to control whether to start on sunday or monday -#let month-header(month-id, is-mon-start: false) = { +#let _month-header(month-id, is-mon-start: false) = { v(-10pt) // print month text[== #month-days.at(month-id).at(0)] line(length: 100%, stroke: 1pt) v(-15pt) // print days of week - days-of-week(is-mon-start: is-mon-start) + _days-of-week(is-mon-start: is-mon-start) v(-15pt) line(length: 100%, stroke: 1pt) } -/// construct-day-arr: constructs an array with all the encodings for each day in a month as well as info to assist the creation of encodings for the next month +/// _construct-day-arr: constructs an array with all the encodings for each day in a month as well as info to assist the creation of encodings for the next month /// - month-id (int): the month to construct days for /// - start (int): the day to start the month on /// - last-month-max (int): allows the month to end sooner than the number of days in the month @@ -118,7 +120,7 @@ /// - is-leap-year (bool): flag to control number of days in february /// - shading (array): any shading info to encode /// - assigns (array): any text to encode on the day -#let construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:(), overviews:()) = { +#let _construct-day-arr(month-id, start, last-month-max, last-week-num, is-leap-year: false, shading:(), assigns:(), overviews:()) = { // account for leap year let mon-days = month-days if is-leap-year { @@ -163,7 +165,7 @@ // check if the date matches the current date if (date.at(0) == (month-id + 1)) and (date.at(1) == day) { // if it is a match, encode the shading id # - day-text = day-text + "𖦹" + str(i) + day-text = day-text + delimeter + str(i) break } j = j + 1 @@ -179,7 +181,7 @@ // check if the date matches the current date if (item.at(0) == (month-id + 1)) and (item.at(1) == day) { // if it is a match, encode the assignment text - day-text = day-text + "𖦹" + item.at(2) + day-text = day-text + delimeter + item.at(2) } i = i + 1 } @@ -191,7 +193,7 @@ while overview-index < overviews.len() { // if overview week num matches current week num, add overview text if (week-count - 1) == overviews.at(overview-index).at(0) { - day-text = day-text + "𖦹OVR𖦹" + overviews.at(overview-index).at(1) + day-text = day-text + delimeter + "OVR" + delimeter + overviews.at(overview-index).at(1) } overview-index = overview-index + 1 } @@ -213,12 +215,12 @@ } -/// construct-month-table: creates and draws the table of days for a specific month +/// _construct-month-table: creates and draws the table of days for a specific month /// - days (([DAY NUM ENCODING SEPARATED BY DASHES], [DAY NUM ENCODING SEPARATED BY DASHES], ...)): contains all the day numbers and appropriate encodings for each day /// - keywords (((str, color), (str, color), ...)): array of keywords and their corresponding colors /// - shading-colors ((color, color, ...)): array of colors to shade specified cells /// - overview-color (color): color for overview text -#let construct-month-table(days, keywords, shading-colors, overview-color) = { +#let _construct-month-table(days, keywords, shading-colors, overview-color) = { v(-18pt) show table.cell: it => { @@ -238,7 +240,7 @@ rows: 70pt, ..days.map(text-str => { // split day date on dash - let all-pieces = text-str.split("𖦹") + let all-pieces = text-str.split(delimeter) // if there is only a number if all-pieces.len() == 1 { // print that number bolded centered in the cell @@ -315,9 +317,9 @@ } -/// get-numeric-month: takes a string and sees if it is a valid month; if it is, converts it to a numeric month +/// _get-numeric-month: takes a string and sees if it is a valid month; if it is, converts it to a numeric month /// - input (str): string to check if it is a valid month -#let get-numeric-month(input) = { +#let _get-numeric-month(input) = { let index = 0 let numeric-month = -1 while index < month-inputs.len() { @@ -338,9 +340,9 @@ } -/// create-color-date-shading-arrays: Handles all possible types of shading encodings +/// _create-color-date-shading-arrays: Handles all possible types of shading encodings /// - encoding (array): the encoding to check if it is in the right format for shading param -#let create-color-date-shading-arrays(encoding) = { +#let _create-color-date-shading-arrays(encoding) = { let dates = () let colors = () @@ -375,7 +377,7 @@ let i = 0 while i < encoding.at(0).len() { if type(encoding.at(0).at(i).at(0)) == str { - encoding.at(0).at(i).at(0) = get-numeric-month(encoding.at(0).at(i).at(0)) + encoding.at(0).at(i).at(0) = _get-numeric-month(encoding.at(0).at(i).at(0)) } i = i + 1 } @@ -388,7 +390,7 @@ message: "Expected date to be in the format: (int or str, int) received (" + str(type(encoding.at(0).at(0))) + ", " + str(type(encoding.at(0).at(1))) + ")" ) if type(encoding.at(0).at(0)) == str { - encoding.at(0).at(0) = get-numeric-month(encoding.at(0).at(0)) + encoding.at(0).at(0) = _get-numeric-month(encoding.at(0).at(0)) } let all-dates = () @@ -453,7 +455,7 @@ let month-val = 0 while month-val < month-range.len() { if type(month-range.at(month-val)) == str { - month-range.at(month-val) = get-numeric-month(month-range.at(month-val)) + month-range.at(month-val) = _get-numeric-month(month-range.at(month-val)) } month-val = month-val + 1 } @@ -575,13 +577,13 @@ if shading != () { // handles case of one (or more) dates and one color: ((int or str, int), color) OR ( ((int or str, int), (int or str, int), ...), color ) if type(shading.at(1)) == color { - let out = create-color-date-shading-arrays(shading) + let out = _create-color-date-shading-arrays(shading) dates = out.at(0) colors = out.at(1) // handles case of multiple colors } else { for encoding in shading { - let out = create-color-date-shading-arrays(encoding) + let out = _create-color-date-shading-arrays(encoding) dates.push(out.at(0).at(0)) colors.push(out.at(1).at(0)) } @@ -603,7 +605,7 @@ // handles a single date text if (type(due-dates.at(0)) == int or type(due-dates.at(0)) == str) and type(due-dates.at(1)) == int and type(due-dates.at(2)) == str { if type(due-dates.at(0)) == str { - due-dates.at(0) = get-numeric-month(due-dates.at(0)) + due-dates.at(0) = _get-numeric-month(due-dates.at(0)) } date-text.push(due-dates) // handles multiple dates @@ -618,7 +620,7 @@ let due-date-index = 0 while due-date-index < due-dates.len() { if type(due-dates.at(due-date-index).at(0)) == str { - due-dates.at(due-date-index).at(0) = get-numeric-month(due-dates.at(due-date-index).at(0)) + due-dates.at(due-date-index).at(0) = _get-numeric-month(due-dates.at(due-date-index).at(0)) } date-text.push(due-dates.at(due-date-index)) due-date-index = due-date-index + 1 @@ -711,11 +713,11 @@ } // create header for the month - month-header(month, is-mon-start: is-mon-start) + _month-header(month, is-mon-start: is-mon-start) // construct an array of the days and all their appropriate encodings for that month - days = construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: date-text, overviews: overview-text) + days = _construct-day-arr(month, start-day, end-day, days.at(1), is-leap-year: is-leap-year, shading: dates, assigns: date-text, overviews: overview-text) // print table of days w/ appropriate info - construct-month-table(days.at(0), color-codes, colors, overview-color) + _construct-month-table(days.at(0), color-codes, colors, overview-color) // get new start day from prior day array generation start-day = days.at(2) @@ -763,7 +765,7 @@ // if a str month is provided, check that it is valid and convert to numeric if type(start-date.at(0)) == str { - start-date.at(0) = get-numeric-month(start-date.at(0)) + start-date.at(0) = _get-numeric-month(start-date.at(0)) } assert( @@ -809,7 +811,7 @@ if (type(dates-to-skip.at(0)) == int or type(dates-to-skip.at(0)) == str) and type(dates-to-skip.at(1)) == int { // if a str month is provided, check that it is valid and convert to numeric if type(dates-to-skip.at(0)) == str { - dates-to-skip.at(0) = get-numeric-month(dates-to-skip.at(0)) + dates-to-skip.at(0) = _get-numeric-month(dates-to-skip.at(0)) } assert( @@ -847,7 +849,7 @@ while index < temp.len() { // check each month and day is in range if temp.at(index).at(0) == str { - temp.at(index).at(0) = get-numeric-month(temp.at(index).at(0)) + temp.at(index).at(0) = _get-numeric-month(temp.at(index).at(0)) } assert(