{"assignment":{"_schema_version":2,"course_id":37,"date_created":"2025-10-09T18:00:00+00:00","date_modified":"2025-10-23T14:01:43.568112+00:00","extra_instructor_files":"{}","extra_starting_files":"","forked_id":null,"forked_version":null,"hidden":false,"id":2544,"instructions":"# Commenter\n\n## Another Example\n\n-   Commenter: Add comments to a file.\n-   [commenter.py](https://gist.githubusercontent.com/acbart/8bfb48903b6e5a5073a32d602afdff4a/raw/be48bf598b7a6312a5b261e4578ed11a4e035565/commenter.py)\n\nIn this example program, we created a tool for annotating documents, especially source code. You can upload a file or type in some code, and then you can add comments to individual lines of the code. This would be a useful tool for code review, where one programmer reviews another programmer's code and adds comments about how to improve it.\n\nLike the other two programs, we strongly encourage you to download this program and run it in Thonny. It is a longer program, and it will be easier to read the code in Thonny. It will also be easier to interact with the program that way too.\n\n## The Code\n\n```python commenter\nfrom drafter import *\nfrom dataclasses import dataclass\n\nadd_website_css(\n    \".comment-line\",\n    \"display: flex; justify-content: space-between; align-items: center; border-bottom: 1px dashed #ccc;\",\n)\n\n\n@dataclass\nclass Line:\n    \"\"\"\n    A line of code with an optional comment.\n    Attributes:\n        original (str): The original line of code.\n        comment (str): The comment associated with the line of code.\n        index (int): The line number in the original text.\n    \"\"\"\n\n    original: str\n    comment: str\n    index: int\n\n\n@dataclass\nclass State:\n    \"\"\"\n    The state of the code commenter application.\n\n    Attributes:\n        comments (list[Line]): A list of Line objects representing the code lines and their comments.\n        original_text (str): The original text of the code being commented on.\n    \"\"\"\n\n    comments: list[Line]\n    original_text: str\n\n\ndef parse_lines(text: str) -> list[Line]:\n    \"\"\"\n    Parses the input text into a list of Line objects.\n\n    Args:\n        text (str): The input text to parse.\n    Returns:\n        list[Line]: A list of Line objects.\n    \"\"\"\n    lines = []\n    for i, line in enumerate(text.split(\"\\n\")):\n        lines.append(Line(line.rstrip(), \"\", i))\n    return lines\n\n\n@route\ndef index(state: State) -> Page:\n    \"\"\"\n    Display the main page of the code commenter application.\n\n    Args:\n        state (State): The current state of the application.\n    Returns:\n        Page: The rendered main page.\n    \"\"\"\n    return Page(\n        state,\n        [\n            Header(\"Code Commenter\"),\n            \"Upload a file or type some code to get started.\",\n            FileUpload(\"code_file\"),\n            TextArea(\"text_box\", state.original_text),\n            Button(\"Start\", \"start_commenting\"),\n        ],\n    )\n\n\n@route\ndef error_page(message: str) -> Page:\n    \"\"\"\n    Display an error page with the given message.\n\n    Args:\n        message (str): The error message to display.\n    Returns:\n        Page: The rendered error page.\n    \"\"\"\n    return Page(\n        None,\n        [\n            Header(\"Error\"),\n            f\"Error: {message}\",\n            Button(\"Back\", \"index\"),\n        ],\n    )\n\n\n@route\ndef start_commenting(state: State, code_file: str = \"\", text_box: str = \"\") -> Page:\n    \"\"\"\n    Starts the commenting process by parsing the input text or file.\n\n    Args:\n        state (State): The current state of the application.\n        code_file (str): The content of the uploaded file (if any).\n        text_box (str): The content of the text area (if any).\n    Returns:\n        Page: The page displaying the code lines for commenting.\n    \"\"\"\n    if code_file:\n        text = code_file\n    elif text_box:\n        text = text_box\n    else:\n        return error_page(\"Please upload a file or enter some text.\")\n\n    lines = parse_lines(text)\n    state.comments = lines\n    state.original_text = text\n\n    return page_commenter(state)\n\n\n@route\ndef page_commenter(state: State) -> Page:\n    \"\"\"\n    Displays the page for commenting on code lines.\n\n    Args:\n        state (State): The current state of the application.\n    Returns:\n        Page: The page displaying the code lines for commenting.\n    \"\"\"\n    content = [Header(\"Code Commenter\")]\n    for line in state.comments:\n        if line.original:\n            content.append(\n                Div(\n                    line.original,\n                    Button(\"\ud83d\udcac\", \"comment\", Argument(\"index\", line.index)),\n                    classes=\"comment-line\",\n                )\n            )\n        else:\n            content.append(Div(classes=\"comment-line\", style_height=\"1.5em\"))\n        if line.comment:\n            content.append(Div(small_font(line.comment), classes=\"comment-line\"))\n\n    content.append(Button(\"Reset\", \"index\"))\n\n    return Page(state, content)\n\n\n@route\ndef comment(state: State, index: int) -> Page:\n    \"\"\"\n    Displays the comment box for a specific line of code.\n\n    Args:\n        state (State): The current state of the application.\n        index (int): The index of the line to comment on.\n    Returns:\n        Page: The page displaying the comment box for the specified line.\n    \"\"\"\n    comment = state.comments[index]\n    return Page(\n        state,\n        [\n            Header(\"Add Comment\"),\n            PreformattedText(comment.original),\n            TextArea(\"comment_box\", comment.comment),\n            Button(\n                \"Save Comment\",\n                \"save_comment\",\n                arguments=Argument(\"index\", index),\n            ),\n            Button(\"Back\", \"page_commenter\"),\n        ],\n    )\n\n\n@route\ndef save_comment(state: State, index: int, comment_box: str) -> Page:\n    \"\"\"\n    Saves the comment for a specific line of code.\n\n    Args:\n        state (State): The current state of the application.\n        index (int): The index of the line to save the comment for.\n        comment_box (str): The content of the comment box.\n    Returns:\n        Page: The updated page displaying the code lines for commenting.\n    \"\"\"\n    state.comments[index].comment = comment_box\n    return page_commenter(state)\n\n\nstart_server(State([], \"\"))\n```\n\n## File Upload\n\nUnlike the `food_lookup.py` program from the previous example, this program is just a single file. However, it does allow you to upload a file using the `FileUpload` component. You can upload any text file, but the program is really designed for source code files. You can also just type or paste code into the text area. The `start_commenting` function checks to see if you uploaded a file or typed something in the text area. If you did neither, it shows an error message.\n\n## Index Parameter\n\nIn order to know which line you are commenting on, the `comment` and `save_comment` functions take an `index` parameter. This is passed as an argument to the button that opens the comment box and the button that saves the comment. The `Argument` component is used to specify that the value of the `index` parameter should be taken from the `index` attribute of the `Line` object.\n\nBecause we're dealing with a fixed set of lines, we can be sure that the index will always be valid. In a more complex application where you can add and delete lines, you might want to add error handling to ensure that the index is within the bounds of the list of comments.\n\nThe `index` parameter is originally calculated in the `parse_lines` function, which assigns each line a unique index based on its position in the original text. The `enumerate` function is used to get both the line and its index as we iterate through the lines of text.\n\n## Styling\n\n-   `add_website_css(css: str)`: Adds custom CSS to the entire website.\n-   `add_website_css(selector: str, css: str)`: Adds custom CSS to a specific css selector.\n-   `classes`: A parameter for any component (e.g., `Div`) that allows you to add one or more CSS classes to the div.\n-   `style_height`: A parameter for any component (e.g., `Div`) that allows you to set the CSS `height` property of the div.\n-   `small_font(text: str)`: A function that applies a smaller font size to the given text.\n\nThe program uses some custom CSS to style the lines of code and their associated comment buttons. The [`add_website_css`](https://drafter-edu.github.io/drafter/students/styling.html#adding-website-css) function is used to add CSS rules that make the code lines and buttons align nicely. The `.comment-line` class is applied to each line of code and its button, ensuring they are displayed in a flex container with space between them. Notice that we use the `classes` parameter of the `Div` component to apply the CSS class, without the dot prefix (that's only used in the CSS definition to [target the class](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors/Selectors_and_combinators)).\n\nThe `style_height` parameter is used to set a minimum height for empty lines, so that they still take up some vertical space. This makes it easier to see where blank lines are in the text. We will learn more about these \"named parameters\" in a future chapter of the textbook.\n\nThe `small_font` function is used to make the comments appear in a smaller font size, which helps differentiate them from the code lines. This function wraps the text in a `<span>` element with inline CSS to set the CSS `font-size` property to a smaller value.\n\nThere are many other ways to style your Drafter applications. See the [Styling](https://drafter-edu.github.io/drafter/students/styling.html) section of the Drafter documentation for more information.","ip_ranges":"","name":"9B1) Commenter","on_change":"","on_eval":"","on_run":"","owner_id":1,"owner_id__email":"acbart@udel.edu","points":0,"public":true,"reviewed":false,"sample_submissions":[],"settings":"{\"summary\": \"This is a third example of a longer Drafter program.\", \"small_layout\": true, \"disable_timeout\": true, \"hide_files\": false}","starting_code":"","subordinate":true,"tags":[],"type":"reading","url":"bakery_project2_example3_read","version":1},"ip":"216.73.216.157","submission":{"_schema_version":3,"assignment_id":2544,"assignment_version":1,"attempts":0,"code":"","correct":false,"course_id":37,"date_created":"2026-05-20T12:38:53.053575+00:00","date_due":"","date_graded":"","date_locked":"","date_modified":"2026-05-20T12:38:53.053575+00:00","date_started":"","date_submitted":"","endpoint":"","extra_files":"","feedback":"","grading_status":"NotReady","id":2036728,"score":0.0,"submission_status":"Started","time_limit":"","url":"submission_url-722ae625-a58e-410d-8537-0e43610d2e95","user_id":2044658,"user_id__email":"","version":0},"success":true}
