\chapter{The \texttt{cli.utils.rooms} module and \texttt{utils rooms} % ===> this file was generated automatically by noweave --- better not edit it
subcommands}%
\label{cli.utils.rooms}

In this chapter we introduce the subommands found under {\Tt{}nytid\ utils\ \nwnewline
rooms\nwendquote},
it's the {\Tt{}cli.utils.rooms\nwendquote} module.
We want to add tools to easily filter and find free rooms that we're interested 
in for our courses.

The overall structure is like this.
\nwfilename{rooms.nw}\nwbegincode{1}\sublabel{NWAFDpM-1T2u1E-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-1T2u1E-1}}}\moddef{rooms.py~{\nwtagstyle{}\subpageref{NWAFDpM-1T2u1E-1}}}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
import logging
import typer
import typerconf
from typing_extensions import Annotated

\LA{}imports~{\nwtagstyle{}\subpageref{NWAFDpM-1n4S18-1}}\RA{}

cli = typer.Typer(name="rooms",
                  help="Finding free rooms for courses")

\LA{}constants~{\nwtagstyle{}\subpageref{NWAFDpM-3UrjvT-1}}\RA{}
\LA{}helper functions~{\nwtagstyle{}\subpageref{NWAFDpM-47YflN-1}}\RA{}
\LA{}argument and option definitions~{\nwtagstyle{}\subpageref{NWAFDpM-2Ns6UP-1}}\RA{}
\LA{}subcommands~{\nwtagstyle{}\subpageref{NWAFDpM-CyCSh-1}}\RA{}

if __name__ == "__main__":
  cli()
\nwnotused{rooms.py}\nwendcode{}\nwbegindocs{2}\nwdocspar


\section{Setting the URL to free rooms CSV}

We want a command that sets the URL to the free rooms CSV file in the config.
Something like this:
\begin{center}
{\Tt{}nytid\ utils\ rooms\ set-url\ https://cloud.timeedit.net/.../xyz.csv\nwendquote}
\end{center}
So that we don't have to set it manually.

We want to add a helpful description of how to find the right URL to add.
This is another reason to make this a command.
\nwenddocs{}\nwbegincode{3}\sublabel{NWAFDpM-2wJcSK-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-2wJcSK-1}}}\moddef{\code{}set-url\edoc{} doc~{\nwtagstyle{}\subpageref{NWAFDpM-2wJcSK-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-CyCSh-1}}\nwenddeflinemarkup
Search for all the rooms that you're interested in in TimeEdit.
Set the relevant time intervals to something future proof: you
don't want it to be too short, then you have to update the URL
too often. Get the URL for downloading the schedule in CSV
format.
\nwused{\\{NWAFDpM-CyCSh-1}}\nwendcode{}\nwbegindocs{4}\nwdocspar

Then the command itself simply sets the given URL in the config.
\nwenddocs{}\nwbegincode{5}\sublabel{NWAFDpM-CyCSh-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-CyCSh-1}}}\moddef{subcommands~{\nwtagstyle{}\subpageref{NWAFDpM-CyCSh-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{\relax}{NWAFDpM-CyCSh-2}\nwenddeflinemarkup
@cli.command(name="set-url")
def set_url_cmd(\LA{}arg \code{}url\edoc{} for URL to CSV file~{\nwtagstyle{}\subpageref{NWAFDpM-1TSgEC-1}}\RA{}):
  """
  \LA{}\code{}set-url\edoc{} doc~{\nwtagstyle{}\subpageref{NWAFDpM-2wJcSK-1}}\RA{}
  """
  try:
    typerconf.set(BOOKED_ROOMS_URL, url)
  except Exception as err:
    logging.error(f"Can't set URL: \{err\}")
    raise typer.Exit(1)
\nwalsodefined{\\{NWAFDpM-CyCSh-2}\\{NWAFDpM-CyCSh-3}}\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegincode{6}\sublabel{NWAFDpM-3UrjvT-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-3UrjvT-1}}}\moddef{constants~{\nwtagstyle{}\subpageref{NWAFDpM-3UrjvT-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwenddeflinemarkup
BOOKED_ROOMS_URL = "utils.rooms.url"
\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegincode{7}\sublabel{NWAFDpM-1TSgEC-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-1TSgEC-1}}}\moddef{arg \code{}url\edoc{} for URL to CSV file~{\nwtagstyle{}\subpageref{NWAFDpM-1TSgEC-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-CyCSh-1}}\nwenddeflinemarkup
url: Annotated[str, typer.Argument(help="URL to CSV file with "
                                        "TimeEdit export")]
\nwused{\\{NWAFDpM-CyCSh-1}}\nwendcode{}\nwbegindocs{8}\nwdocspar


\section{The format of the CSV file we get}

The CSV file that we get from TimeEdit looks like in 
\cref{fig:TimeEditCSVexport}.
We can see that the rooms sought for are listed as a comma separated list in 
cell A3 (the highlighted cell in \cref{fig:TimeEditCSVexport}).
However, we see that those names differ slightly from the room names in the 
last column (the rightmost column in \cref{fig:TimeEditCSVexport}).
There are some extra characters that we must filter out.
Otherwise, the dates and times are in ISO format.

\begin{figure}
  \centering
  \begin{fullwidth}
  \includegraphics[width=\columnwidth]{figs/timeedit-csv-export.png}
  \caption{The CSV file we get from TimeEdit.}
  \label{fig:TimeEditCSVexport}
  \end{fullwidth}
\end{figure}

We only need columns A, B, C, D and F (the rightmost) and the cell A3 for our 
purposes.
We get the date and time from columns A--D, we get all rooms from cell A3 and 
the booked ones from column F.
This way we can compute which of the rooms we're interested in are free.

Let's write a function that given such a file gives us a CSV (list of tuples) 
with the free rooms at the different dates and times.
\nwenddocs{}\nwbegincode{9}\sublabel{NWAFDpM-47YflN-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-47YflN-1}}}\moddef{helper functions~{\nwtagstyle{}\subpageref{NWAFDpM-47YflN-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{\relax}{NWAFDpM-47YflN-2}\nwenddeflinemarkup
def free_rooms(csv_url: str) -> list[tuple]:
  """
  Given a URL or path to a TimeEdit CSV file ([[csv_url]]),
  return a CSV (list of tuples) of the free rooms:

  [(start, end, unbooked_rooms), ...]

  where start and end are datetime objects and unbooked_rooms is a set of 
  strings.
  """
  results = []

  \LA{}let \code{}csv{\_}file\edoc{} be the opened CSV file we get from \code{}csv{\_}url\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-3XqkDW-1}}\RA{}
  with csv_file:
    reader = csv.reader(csv_file)

    # Skip the first two rows
    next(reader)
    next(reader)
    # Get the row containing the sought for rooms
    rooms_row = next(reader)
    \LA{}let \code{}all{\_}rooms\edoc{} be the set of all rooms based on \code{}rooms{\_}row\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-2jswbw-1}}\RA{}

    # skip the header row
    next(reader)

    # read the data
    for row in reader:
      # Get the date and time from the first four columns
      start_date = datetime.date.fromisoformat(row[0])
      start_time = datetime.time.fromisoformat(row[1])
      start = datetime.datetime.combine(start_date, start_time)

      end_date = datetime.date.fromisoformat(row[2])
      end_time = datetime.time.fromisoformat(row[3])
      end = datetime.datetime.combine(end_date, end_time)

      # Get the booked rooms from the last column,
      # these are already clean.
      booked_rooms = row[7].split(",")
      \LA{}turn \code{}booked{\_}rooms\edoc{} into a set, properly~{\nwtagstyle{}\subpageref{NWAFDpM-3HMlH1-1}}\RA{}
      unbooked_rooms = all_rooms - booked_rooms

      results.append((start, end, unbooked_rooms))

  \LA{}remove overlap in \code{}results\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-3mbD0A-1}}\RA{}

  return results
\nwalsodefined{\\{NWAFDpM-47YflN-2}}\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegincode{10}\sublabel{NWAFDpM-1n4S18-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-1n4S18-1}}}\moddef{imports~{\nwtagstyle{}\subpageref{NWAFDpM-1n4S18-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{\relax}{NWAFDpM-1n4S18-2}\nwenddeflinemarkup
import csv
import datetime
\nwalsodefined{\\{NWAFDpM-1n4S18-2}\\{NWAFDpM-1n4S18-3}}\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegindocs{11}\nwdocspar

If there are no booked rooms for a time, we get an empty string in a 
list---which will not be an empty set.
\nwenddocs{}\nwbegincode{12}\sublabel{NWAFDpM-3HMlH1-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-3HMlH1-1}}}\moddef{turn \code{}booked{\_}rooms\edoc{} into a set, properly~{\nwtagstyle{}\subpageref{NWAFDpM-3HMlH1-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-47YflN-1}\\{NWAFDpM-47YflN-2}}\nwenddeflinemarkup
if len(booked_rooms) == 1 and booked_rooms[0] == "":
  booked_rooms = set()
else:
  booked_rooms = set(booked_rooms)
\nwused{\\{NWAFDpM-47YflN-1}\\{NWAFDpM-47YflN-2}}\nwendcode{}\nwbegindocs{13}\nwdocspar

We want to extract the rooms from the first column.
This is a comma separated list of the rooms sought for, but unfortunately with 
extra characters.
We can use the {\Tt{}str.split()\nwendquote} method to split the string into a list of 
strings.
Then we can use the {\Tt{}str.strip()\nwendquote} method to remove the extra characters---as 
they're only at the start.
\nwenddocs{}\nwbegincode{14}\sublabel{NWAFDpM-2jswbw-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-2jswbw-1}}}\moddef{let \code{}all{\_}rooms\edoc{} be the set of all rooms based on \code{}rooms{\_}row\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-2jswbw-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-47YflN-1}}\nwenddeflinemarkup
all_rooms = rooms_row[0].split(",")
extra_chars = " $-§"
all_rooms = set([room.strip(extra_chars) for room in all_rooms])
\nwused{\\{NWAFDpM-47YflN-1}}\nwendcode{}\nwbegindocs{15}\nwdocspar

Finally, let's turn our attention to how to open the file.
We want to be able to open the file from a URL or a local path.
So we must test for this.
We can use the {\Tt{}urlparse\nwendquote} function from the {\Tt{}urllib.parse\nwendquote} module to 
determine if the given URL is a local path or a URL.
If the scheme is empty, then it's a local file.
Otherwise, it's a URL.
\nwenddocs{}\nwbegincode{16}\sublabel{NWAFDpM-3XqkDW-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-3XqkDW-1}}}\moddef{let \code{}csv{\_}file\edoc{} be the opened CSV file we get from \code{}csv{\_}url\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-3XqkDW-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-47YflN-1}\\{NWAFDpM-47YflN-2}}\nwenddeflinemarkup
parsed_url = urllib.parse.urlparse(csv_url)
if parsed_url.scheme == "":
  csv_file = open(csv_url, "r")
else:
  csv_file = urllib.request.urlopen(csv_url)
\nwused{\\{NWAFDpM-47YflN-1}\\{NWAFDpM-47YflN-2}}\nwendcode{}\nwbegincode{17}\sublabel{NWAFDpM-1n4S18-2}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-1n4S18-2}}}\moddef{imports~{\nwtagstyle{}\subpageref{NWAFDpM-1n4S18-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{NWAFDpM-1n4S18-1}{NWAFDpM-1n4S18-3}\nwenddeflinemarkup
import urllib.parse
import urllib.request
\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegindocs{18}\nwdocspar

\subsection{Removing overlapping time intervals}

Now that we have the CSV in free-room form, instead of booked form, we just 
have to find all the overlap.
We'll make it a simple algorithm:
If the start and end are the same, merge them by taking the intersection.

We'll use a dictionary to keep track of start and end times that are the same.
We use the start and end as the key, the free rooms as the value.
Then we can merge whenever the key already exists.
\nwenddocs{}\nwbegincode{19}\sublabel{NWAFDpM-3mbD0A-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-3mbD0A-1}}}\moddef{remove overlap in \code{}results\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-3mbD0A-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-47YflN-1}}\nwenddeflinemarkup
time_dict = \{\}
for start, end, unbooked_rooms in results:
  if (start, end) in time_dict:
    time_dict[(start, end)] &= unbooked_rooms
  else:
    time_dict[(start, end)] = unbooked_rooms

results = [(start, end, unbooked_rooms) \\
           for (start, end), unbooked_rooms in time_dict.items()]
\nwused{\\{NWAFDpM-47YflN-1}}\nwendcode{}\nwbegindocs{20}\nwdocspar



\section{The \texttt{unbooked} command}

We want a command that shows the free rooms in a human readable format.
\begin{center}
{\Tt{}nytid\ utils\ rooms\ unbooked\nwendquote}
\end{center}
This function will simply call the {\Tt{}free{\_}rooms\nwendquote} function and print the CSV 
data that it returns.
\nwenddocs{}\nwbegincode{21}\sublabel{NWAFDpM-2ZPBlX-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-2ZPBlX-1}}}\moddef{\code{}unbooked\edoc{} doc~{\nwtagstyle{}\subpageref{NWAFDpM-2ZPBlX-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-CyCSh-2}}\nwenddeflinemarkup
Shows date and time and which rooms are free.
\nwused{\\{NWAFDpM-CyCSh-2}}\nwendcode{}\nwbegincode{22}\sublabel{NWAFDpM-CyCSh-2}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-CyCSh-2}}}\moddef{subcommands~{\nwtagstyle{}\subpageref{NWAFDpM-CyCSh-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{NWAFDpM-CyCSh-1}{NWAFDpM-CyCSh-3}\nwenddeflinemarkup
@cli.command(name="unbooked")
def unbooked_cmd(\LA{}arg \code{}delimiter\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-1DTQKU-1}}\RA{}):
  """
  \LA{}\code{}unbooked\edoc{} doc~{\nwtagstyle{}\subpageref{NWAFDpM-2ZPBlX-1}}\RA{}
  """
  try:
    rooms_url = typerconf.get(BOOKED_ROOMS_URL)
  except Exception as err:
    logging.error(f"Can't get URL from config: \{err\}")
    logging.info("Please set it with "
                 "'nytid utils rooms set-url <url>'")
    raise typer.Exit(1)

  try:
    csv_out = csv.writer(sys.stdout, delimiter=delimiter)

    for start, end, rooms in free_rooms(rooms_url):
      csv_out.writerow([start, end, ", ".join(sorted(rooms))])
  except Exception as err:
    logging.error(f"Can't get free rooms: \{err\}")
    raise typer.Exit(1)
\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegincode{23}\sublabel{NWAFDpM-1n4S18-3}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-1n4S18-3}}}\moddef{imports~{\nwtagstyle{}\subpageref{NWAFDpM-1n4S18-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{NWAFDpM-1n4S18-2}{\relax}\nwenddeflinemarkup
import sys
\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegincode{24}\sublabel{NWAFDpM-1DTQKU-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-1DTQKU-1}}}\moddef{arg \code{}delimiter\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-1DTQKU-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-CyCSh-2}\\{NWAFDpM-CyCSh-3}}\nwenddeflinemarkup
delimiter: delimiter_opt = "\\t",
\nwused{\\{NWAFDpM-CyCSh-2}\\{NWAFDpM-CyCSh-3}}\nwendcode{}\nwbegincode{25}\sublabel{NWAFDpM-2Ns6UP-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-2Ns6UP-1}}}\moddef{argument and option definitions~{\nwtagstyle{}\subpageref{NWAFDpM-2Ns6UP-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwenddeflinemarkup
delimiter_opt = Annotated[str,
                          typer.Option("-d", "--delimiter",
                          help="CSV delimiter, default tab.",
                          show_default=False)]
\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegindocs{26}\nwdocspar


\section{The \texttt{booked} command}

We also add a command that simply prints the bookings as they are.
\begin{center}
{\Tt{}nytid\ utils\ rooms\ booked\nwendquote}
\end{center}
This is useful to see what rooms are booked and when, because we still get 
overlaps when some rooms are booked 15--17 and some 15--16.
\nwenddocs{}\nwbegincode{27}\sublabel{NWAFDpM-3BCl61-1}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-3BCl61-1}}}\moddef{\code{}booked\edoc{} doc~{\nwtagstyle{}\subpageref{NWAFDpM-3BCl61-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-CyCSh-3}}\nwenddeflinemarkup
Shows date and time and which rooms are booked.
\nwused{\\{NWAFDpM-CyCSh-3}}\nwendcode{}\nwbegincode{28}\sublabel{NWAFDpM-CyCSh-3}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-CyCSh-3}}}\moddef{subcommands~{\nwtagstyle{}\subpageref{NWAFDpM-CyCSh-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{NWAFDpM-CyCSh-2}{\relax}\nwenddeflinemarkup
@cli.command(name="booked")
def booked_cmd(\LA{}arg \code{}delimiter\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-1DTQKU-1}}\RA{}):
  """
  \LA{}\code{}booked\edoc{} doc~{\nwtagstyle{}\subpageref{NWAFDpM-3BCl61-1}}\RA{}
  """
  try:
    rooms_url = typerconf.get(BOOKED_ROOMS_URL)
  except Exception as err:
    logging.error(f"Can't get URL from config: \{err\}")
    logging.info("Please set it with "
                 "'nytid utils rooms set-url <url>'")
    raise typer.Exit(1)

  try:
    csv_out = csv.writer(sys.stdout, delimiter=delimiter)

    for start, end, rooms in booked_rooms(rooms_url):
      csv_out.writerow([start, end, ", ".join(sorted(rooms))])
  except Exception as err:
    logging.error(f"Can't get booked rooms: \{err\}")
    raise typer.Exit(1)
\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}\nwbegindocs{29}\nwdocspar

Now we implement the {\Tt{}booked{\_}rooms\nwendquote} function similarly to the {\Tt{}free{\_}rooms\nwendquote} 
function.
\nwenddocs{}

\nwixlogsorted{c}{{\code{}booked\edoc{} doc}{NWAFDpM-3BCl61-1}{\nwixd{NWAFDpM-3BCl61-1}\nwixu{NWAFDpM-CyCSh-3}}}%
\nwixlogsorted{c}{{\code{}set-url\edoc{} doc}{NWAFDpM-2wJcSK-1}{\nwixd{NWAFDpM-2wJcSK-1}\nwixu{NWAFDpM-CyCSh-1}}}%
\nwixlogsorted{c}{{\code{}unbooked\edoc{} doc}{NWAFDpM-2ZPBlX-1}{\nwixd{NWAFDpM-2ZPBlX-1}\nwixu{NWAFDpM-CyCSh-2}}}%
\nwixlogsorted{c}{{arg \code{}delimiter\edoc{}}{NWAFDpM-1DTQKU-1}{\nwixu{NWAFDpM-CyCSh-2}\nwixd{NWAFDpM-1DTQKU-1}\nwixu{NWAFDpM-CyCSh-3}}}%
\nwixlogsorted{c}{{arg \code{}url\edoc{} for URL to CSV file}{NWAFDpM-1TSgEC-1}{\nwixu{NWAFDpM-CyCSh-1}\nwixd{NWAFDpM-1TSgEC-1}}}%
\nwixlogsorted{c}{{argument and option definitions}{NWAFDpM-2Ns6UP-1}{\nwixu{NWAFDpM-1T2u1E-1}\nwixd{NWAFDpM-2Ns6UP-1}}}%
\nwixlogsorted{c}{{constants}{NWAFDpM-3UrjvT-1}{\nwixu{NWAFDpM-1T2u1E-1}\nwixd{NWAFDpM-3UrjvT-1}}}%
\nwixlogsorted{c}{{helper functions}{NWAFDpM-47YflN-1}{\nwixu{NWAFDpM-1T2u1E-1}\nwixd{NWAFDpM-47YflN-1}\nwixd{NWAFDpM-47YflN-2}}}%
\nwixlogsorted{c}{{imports}{NWAFDpM-1n4S18-1}{\nwixu{NWAFDpM-1T2u1E-1}\nwixd{NWAFDpM-1n4S18-1}\nwixd{NWAFDpM-1n4S18-2}\nwixd{NWAFDpM-1n4S18-3}}}%
\nwixlogsorted{c}{{let \code{}all{\_}rooms\edoc{} be the set of all rooms based on \code{}rooms{\_}row\edoc{}}{NWAFDpM-2jswbw-1}{\nwixu{NWAFDpM-47YflN-1}\nwixd{NWAFDpM-2jswbw-1}}}%
\nwixlogsorted{c}{{let \code{}csv{\_}file\edoc{} be the opened CSV file we get from \code{}csv{\_}url\edoc{}}{NWAFDpM-3XqkDW-1}{\nwixu{NWAFDpM-47YflN-1}\nwixd{NWAFDpM-3XqkDW-1}\nwixu{NWAFDpM-47YflN-2}}}%
\nwixlogsorted{c}{{remove overlap in \code{}results\edoc{}}{NWAFDpM-3mbD0A-1}{\nwixu{NWAFDpM-47YflN-1}\nwixd{NWAFDpM-3mbD0A-1}}}%
\nwixlogsorted{c}{{rooms.py}{NWAFDpM-1T2u1E-1}{\nwixd{NWAFDpM-1T2u1E-1}}}%
\nwixlogsorted{c}{{subcommands}{NWAFDpM-CyCSh-1}{\nwixu{NWAFDpM-1T2u1E-1}\nwixd{NWAFDpM-CyCSh-1}\nwixd{NWAFDpM-CyCSh-2}\nwixd{NWAFDpM-CyCSh-3}}}%
\nwixlogsorted{c}{{turn \code{}booked{\_}rooms\edoc{} into a set, properly}{NWAFDpM-3HMlH1-1}{\nwixu{NWAFDpM-47YflN-1}\nwixd{NWAFDpM-3HMlH1-1}\nwixu{NWAFDpM-47YflN-2}}}%
\nwbegincode{30}\sublabel{NWAFDpM-47YflN-2}\nwmargintag{{\nwtagstyle{}\subpageref{NWAFDpM-47YflN-2}}}\moddef{helper functions~{\nwtagstyle{}\subpageref{NWAFDpM-47YflN-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NWAFDpM-1T2u1E-1}}\nwprevnextdefs{NWAFDpM-47YflN-1}{\relax}\nwenddeflinemarkup
def booked_rooms(csv_url: str) -> list[tuple]:
  """
  Given a URL or path to a TimeEdit CSV file ([[csv_url]]),
  return a CSV (list of tuples) of the booked rooms:

  [(start, end, booked_rooms), ...]

  where start and end are datetime objects and booked_rooms is a set of 
  strings.
  """
  results = []

  \LA{}let \code{}csv{\_}file\edoc{} be the opened CSV file we get from \code{}csv{\_}url\edoc{}~{\nwtagstyle{}\subpageref{NWAFDpM-3XqkDW-1}}\RA{}
  with csv_file:
    reader = csv.reader(csv_file)

    # Skip the first two rows.
    next(reader)
    next(reader)

    # Ignore the row containing the sought for rooms.
    next(reader)

    # Skip the header row.
    next(reader)

    # read the data
    for row in reader:
      # Get the date and time from the first four columns
      start_date = datetime.date.fromisoformat(row[0])
      start_time = datetime.time.fromisoformat(row[1])
      start = datetime.datetime.combine(start_date, start_time)

      end_date = datetime.date.fromisoformat(row[2])
      end_time = datetime.time.fromisoformat(row[3])
      end = datetime.datetime.combine(end_date, end_time)

      # Get the booked rooms from the last column,
      # these are already clean.
      booked_rooms = row[7].split(",")
      \LA{}turn \code{}booked{\_}rooms\edoc{} into a set, properly~{\nwtagstyle{}\subpageref{NWAFDpM-3HMlH1-1}}\RA{}
      if booked_rooms:
        results.append((start, end, booked_rooms))

  return results
\nwused{\\{NWAFDpM-1T2u1E-1}}\nwendcode{}
