Skip to content

Task

Argument

Bases: BaseModel

Task argument class

Source code in src/task2md/template/task.py
 7
 8
 9
10
11
class Argument(BaseModel):
    """Task argument class"""

    label: str = ""
    value: str = ""

Task

Bases: BaseModel

Task class

Source code in src/task2md/template/task.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
class Task(BaseModel):
    """Task class"""

    LINE_ARG: ClassVar[str] = "Arguments:"
    PATTERN_ARG: ClassVar[str] = r"([\w-]+\s*[|]\s*[\w-]*): (.*)"
    LINE_REQ: ClassVar[str] = "Requirements:"
    PATTERN_REQ: ClassVar[str] = r"(-\s.*)"

    name: str = ""
    desc: str = ""
    summary: str = ""
    summary_head: str = ""
    summary_args: list[Argument] = []
    summary_req: list[str] = []
    summary_comments: str = ""
    parsed: bool = False

    @staticmethod
    def _consume_block(
        lines: list[str], marker: str, pattern: str
    ) -> list[tuple[str, ...]]:
        """Consume a marker-delimited block from `lines`, in place.

        Locates the first line equal to `marker` that is followed by at least
        one line, then collects the `pattern` match groups of the lines below
        it, stopping at the first line that does not match. The marker, the
        matching lines and that first non-matching line (the blank separator)
        are all removed from `lines`.

        Args:
            lines (list[str]): Summary lines, mutated in place.
            marker (str): Line introducing the block, for example "Arguments:".
            pattern (str): Regular expression applied to each stripped line.

        Returns:
            list[tuple[str, ...]]: Match groups, empty if the marker is absent.
        """
        for index, line in enumerate(lines):
            if (line.strip() != marker) or ((index + 1) >= len(lines)):
                continue

            groups: list[tuple[str, ...]] = []
            consumed = 0
            for candidate in lines[(index + 1) :]:
                consumed += 1
                match = re.search(pattern, candidate.strip())
                if not match:
                    break
                groups.append(match.groups())

            del lines[index : (index + consumed + 1)]
            return groups

        return []

    def parse(self) -> None:
        """Parse the Task summary. Parsing twice is a no-op."""
        if self.parsed:
            return

        # Head - get beginning until blank line
        lines = self.summary.splitlines()
        head: list[str] = []
        for line in lines:
            if line.strip():
                head.append(line)
            else:
                break

        self.summary_head = "\n".join(head) + "\n"
        # Get partial summary without head lines
        count_line_head = len(head)
        index_partial_summary = max(0, count_line_head)
        partial_summary_lines = lines[index_partial_summary:]

        # Arguments
        for groups in Task._consume_block(
            partial_summary_lines, Task.LINE_ARG, Task.PATTERN_ARG
        ):
            self.summary_args.append(
                Argument(label=groups[0].strip(), value=groups[1].strip())
            )

        # Requirements
        for groups in Task._consume_block(
            partial_summary_lines, Task.LINE_REQ, Task.PATTERN_REQ
        ):
            self.summary_req.append(groups[0])

        # Comments
        self.summary_comments = (
            "\n".join(partial_summary_lines).strip().replace("\n", "  \n")
        )

        self.parsed = True

    def to_md(self, file_name: str) -> str:
        """Return the details of the task

        Args:
            file_name (str): File_name of the task file.

        Returns:
            str: Markdown of the task details
        """
        self.parse()
        output = f"\n## :simple-task: {file_name}:{self.name}\n\n"

        output += f"{self.desc} \n\n"
        output += "```shell\n"
        output += self.summary_head
        output += "```\n"

        # Arguments
        output += """
| Arguments | Description |
| --------- | ----------- |
"""
        if len(self.summary_args) == 0:
            output += "| - | - |\n"
        else:
            for arg in self.summary_args:
                output += f"| `{arg.label}` | {arg.value} |\n"
        output += "\n"

        # Comments
        output += f"{self.summary_comments}\n\n"

        # Requirements
        output += '!!! info "Requirements:"\n\n'
        if len(self.summary_req) == 0:
            output += "    - None\n"
        else:
            for req in self.summary_req:
                output += f"    {req}\n"

        return output

parse()

Parse the Task summary. Parsing twice is a no-op.

Source code in src/task2md/template/task.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def parse(self) -> None:
    """Parse the Task summary. Parsing twice is a no-op."""
    if self.parsed:
        return

    # Head - get beginning until blank line
    lines = self.summary.splitlines()
    head: list[str] = []
    for line in lines:
        if line.strip():
            head.append(line)
        else:
            break

    self.summary_head = "\n".join(head) + "\n"
    # Get partial summary without head lines
    count_line_head = len(head)
    index_partial_summary = max(0, count_line_head)
    partial_summary_lines = lines[index_partial_summary:]

    # Arguments
    for groups in Task._consume_block(
        partial_summary_lines, Task.LINE_ARG, Task.PATTERN_ARG
    ):
        self.summary_args.append(
            Argument(label=groups[0].strip(), value=groups[1].strip())
        )

    # Requirements
    for groups in Task._consume_block(
        partial_summary_lines, Task.LINE_REQ, Task.PATTERN_REQ
    ):
        self.summary_req.append(groups[0])

    # Comments
    self.summary_comments = (
        "\n".join(partial_summary_lines).strip().replace("\n", "  \n")
    )

    self.parsed = True

to_md(file_name)

Return the details of the task

Parameters:

Name Type Description Default
file_name str

File_name of the task file.

required

Returns:

Name Type Description
str str

Markdown of the task details

Source code in src/task2md/template/task.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
    def to_md(self, file_name: str) -> str:
        """Return the details of the task

        Args:
            file_name (str): File_name of the task file.

        Returns:
            str: Markdown of the task details
        """
        self.parse()
        output = f"\n## :simple-task: {file_name}:{self.name}\n\n"

        output += f"{self.desc} \n\n"
        output += "```shell\n"
        output += self.summary_head
        output += "```\n"

        # Arguments
        output += """
| Arguments | Description |
| --------- | ----------- |
"""
        if len(self.summary_args) == 0:
            output += "| - | - |\n"
        else:
            for arg in self.summary_args:
                output += f"| `{arg.label}` | {arg.value} |\n"
        output += "\n"

        # Comments
        output += f"{self.summary_comments}\n\n"

        # Requirements
        output += '!!! info "Requirements:"\n\n'
        if len(self.summary_req) == 0:
            output += "    - None\n"
        else:
            for req in self.summary_req:
                output += f"    {req}\n"

        return output