task2md 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
task2md/__init__.py ADDED
File without changes
File without changes
task2md/task/file.py ADDED
@@ -0,0 +1,249 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List
6
+
7
+ import yaml
8
+ from pydantic import BaseModel
9
+
10
+ from task2md.task.header import Header
11
+ from task2md.task.task import Task
12
+ from task2md.task.variable import Variable
13
+ from task2md.util.dir import Dir
14
+
15
+
16
+ class File(BaseModel):
17
+ """File Task content class"""
18
+
19
+ path: str = ""
20
+ content: str = ""
21
+ yaml: Dict[str, Any] = {}
22
+ header: Header = Header()
23
+ global_variables: List[Variable] = []
24
+ tasks: List[Task] = []
25
+ parsed: bool = False
26
+
27
+ def get_filename(self) -> str:
28
+ """Get filename without extension
29
+
30
+ Returns:
31
+ str: The filename
32
+ """
33
+ return Path(self.path).stem
34
+
35
+ def generate(self, dir: Dir) -> None:
36
+ """Generate markdown file in output directory
37
+
38
+ Args:
39
+ dir (Dir): The dir object
40
+ """
41
+ self.load()
42
+ self.parse()
43
+ output = self.to_md()
44
+
45
+ output_filename = dir.path + "/" + self.get_filename() + ".md"
46
+
47
+ with open(output_filename, "w") as f:
48
+ f.write(output)
49
+
50
+ def load(self) -> None:
51
+ """Load file content from path"""
52
+ with open(self.path) as f:
53
+ self.content = f.read()
54
+
55
+ def parse(self) -> None:
56
+ """Parse yaml content"""
57
+
58
+ yaml_content = yaml.safe_load(self.content)
59
+
60
+ if (yaml_content is not None) and isinstance(yaml_content, dict):
61
+ self.yaml = yaml_content
62
+
63
+ # 1 - Parse header:
64
+ header_lines = re.findall(r"# @.*", self.content, flags=re.MULTILINE)
65
+ self.header.parse(header_lines)
66
+
67
+ # 2 - Parse vars:
68
+ if ("vars" in self.yaml) and (self.yaml["vars"] is not None):
69
+ sorted_keys = sorted(self.yaml["vars"].keys())
70
+ for name in sorted_keys:
71
+ v = Variable(name=name)
72
+ if (self.yaml["vars"][name] is not None) and isinstance(
73
+ self.yaml["vars"][name], str
74
+ ):
75
+ v.value = self.yaml["vars"][name]
76
+
77
+ # Get comment if any
78
+ line_var = re.findall(
79
+ r"^ " + v.name + ": .*$", self.content, flags=re.MULTILINE
80
+ )
81
+ if len(line_var) > 0:
82
+ # Remove name and value of line
83
+ desc = line_var[0].replace(" " + v.name + ": " + v.value, "")
84
+ # if # remove -> description
85
+ if "#" in desc:
86
+ value_desc = desc.split("#", 1)
87
+ v.description = value_desc[1].strip()
88
+
89
+ self.global_variables.append(v)
90
+
91
+ # 3 - Parse tasks:
92
+ if ("tasks" in self.yaml) and (self.yaml["tasks"] is not None):
93
+ sorted_keys = sorted(self.yaml["tasks"].keys())
94
+ for name in sorted_keys:
95
+ # Get task only if desc is defined
96
+ if (
97
+ ("desc" in self.yaml["tasks"][name])
98
+ and (self.yaml["tasks"][name]["desc"] is not None)
99
+ and isinstance(self.yaml["tasks"][name]["desc"], str)
100
+ and (len(self.yaml["tasks"][name]["desc"]) > 0)
101
+ ):
102
+ t = Task(name=name, desc=self.yaml["tasks"][name]["desc"])
103
+ if (
104
+ ("summary" in self.yaml["tasks"][name])
105
+ and (self.yaml["tasks"][name]["summary"] is not None)
106
+ and isinstance(self.yaml["tasks"][name]["summary"], str)
107
+ and (len(self.yaml["tasks"][name]["summary"]) > 0)
108
+ ):
109
+ t.summary = self.yaml["tasks"][name]["summary"]
110
+
111
+ self.tasks.append(t)
112
+
113
+ self.parsed = True
114
+
115
+ def to_md(self) -> str:
116
+ """Return the content of the file in markdown
117
+
118
+ Returns:
119
+ str: Markdown content
120
+ """
121
+ output = ""
122
+ # Tag
123
+ output += self.tags_to_md()
124
+
125
+ # Top
126
+ output += "---\n\n# " + self.get_filename() + "\n\n"
127
+ output += self.header.description + "\n\n"
128
+ output += '!!! info "' + self.get_filename() + ' template details"\n\n'
129
+ output += self.header_to_md()
130
+
131
+ # Tasks list
132
+ output += "## :material-list-box: List of tasks\n"
133
+ output += self.tasks_list_to_md() + "\n"
134
+
135
+ # Global variables
136
+ output += "## :material-variable: global variables\n"
137
+ output += self.global_variables_to_md() + "\n"
138
+
139
+ # Tasks details
140
+ output += self.tasks_details_to_md()
141
+
142
+ return output
143
+
144
+ def tags_to_md(self) -> str:
145
+ """Return the tags list
146
+
147
+ Returns:
148
+ str: markdown content
149
+ """
150
+ output = ""
151
+ if len(self.header.tags) > 0:
152
+ output += "---\ntags:\n"
153
+ for tag in self.header.tags:
154
+ output += f" - {tag}\n"
155
+
156
+ return output
157
+
158
+ def header_to_md(self) -> str:
159
+ """Return the header data
160
+
161
+ Returns:
162
+ str: markdown content
163
+ """
164
+ match self.header.status:
165
+ case "stable":
166
+ status_icon = "material-check-circle"
167
+ status_label = "stable"
168
+ case "deprecated":
169
+ status_icon = "material-delete"
170
+ status_label = "deprecated"
171
+ case "beta":
172
+ status_icon = "material-beta"
173
+ status_label = "beta"
174
+ case _:
175
+ status_icon = "material-draw"
176
+ status_label = "draft"
177
+
178
+ output = ""
179
+ output += f" * :{status_icon}: Status: {status_label}\n"
180
+ output += (
181
+ " * :material-bookmark-check: File: ["
182
+ + self.header.file_raw
183
+ + "]("
184
+ + self.header.file_ui
185
+ + ")\n"
186
+ )
187
+ output += (
188
+ " * :material-home: Home: ["
189
+ + self.header.home
190
+ + "]("
191
+ + self.header.home
192
+ + ")\n"
193
+ )
194
+ output += f" * :material-license: License: {self.header.license}\n\n"
195
+
196
+ return output
197
+
198
+ def tasks_list_to_md(self) -> str:
199
+ """Return the Tasks list to a markdown table
200
+
201
+ Returns:
202
+ str: markdown table
203
+ """
204
+ output = """
205
+ | Tasks | Description |
206
+ | ----- | ----------- |
207
+ """
208
+ for task in self.tasks:
209
+ file_name = self.get_filename()
210
+ output += (
211
+ f"| [`{file_name}:{task.name}`](#:simple-task:-{file_name}:{task.name})"
212
+ f" | {task.desc} |\n"
213
+ )
214
+
215
+ return output
216
+
217
+ def global_variables_to_md(self) -> str:
218
+ """Return the global variables to a markdown table
219
+
220
+ Returns:
221
+ str: markdown table
222
+ """
223
+ output = """
224
+ | Variables | Description | Default value |
225
+ | --------- | ----------- | ------------- |
226
+ """
227
+
228
+ if len(self.global_variables) == 0:
229
+ # Empty variable to generate a line with no value
230
+ v = Variable()
231
+ output += v.to_md() + "\n"
232
+ else:
233
+ for var in self.global_variables:
234
+ output += var.to_md() + "\n"
235
+
236
+ return output
237
+
238
+ def tasks_details_to_md(self) -> str:
239
+ """Return the Tasks details
240
+
241
+ Returns:
242
+ str: List of tasks details
243
+ """
244
+ output = ""
245
+
246
+ for task in self.tasks:
247
+ output += task.to_md(self.get_filename())
248
+
249
+ return output
task2md/task/header.py ADDED
@@ -0,0 +1,71 @@
1
+ # -*- coding: utf-8 -*-
2
+ import re
3
+ from typing import ClassVar, List
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class Header(BaseModel):
9
+ """Header Task file class"""
10
+
11
+ PATTERN: ClassVar[str] = r"@([\w-]+): (.*)"
12
+
13
+ description: str = "-"
14
+ tags: List[str] = []
15
+ authors: List[str] = []
16
+ file_raw: str = ""
17
+ file_ui: str = ""
18
+ home: str = ""
19
+ links: List[str] = []
20
+ license: str = ""
21
+ status: str = ""
22
+ deprecated_tasks: List[str] = []
23
+
24
+ def parse(self, header_lines: List[str]) -> None:
25
+ """Parse the header comment of a Task file
26
+
27
+ Args:
28
+ header_lines (List[str]): List of header lines
29
+ """
30
+ for line in header_lines:
31
+ match = re.search(Header.PATTERN, line)
32
+ if match:
33
+ label, value = match.groups()
34
+ match label.strip():
35
+ case "description":
36
+ self.description = value.strip()
37
+ case "tags":
38
+ self.tags = Header.string2list(value)
39
+ case "authors":
40
+ self.authors = Header.string2list(value)
41
+ case "file-raw":
42
+ self.file_raw = value.strip()
43
+ case "file-ui":
44
+ self.file_ui = value.strip()
45
+ case "home":
46
+ self.home = value.strip()
47
+ case "links":
48
+ self.links = Header.string2list(value)
49
+ case "license":
50
+ self.license = value.strip()
51
+ case "status":
52
+ self.status = value.strip()
53
+ case "deprecated-tasks":
54
+ self.deprecated_tasks = Header.string2list(value)
55
+
56
+ @classmethod
57
+ def string2list(cls, input: str) -> List[str]:
58
+ """Split a string with comma separator
59
+
60
+ Args:
61
+ input (str): string to split
62
+
63
+ Returns:
64
+ List[str]: A list of string
65
+ """
66
+ list = input.split(",")
67
+ output = []
68
+ for element in list:
69
+ output.append(element.strip())
70
+
71
+ return output
task2md/task/task.py ADDED
@@ -0,0 +1,137 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import re
4
+ from typing import ClassVar, List
5
+
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class Argument(BaseModel):
10
+ label: str = ""
11
+ value: str = ""
12
+
13
+
14
+ class Task(BaseModel):
15
+ """Task class"""
16
+
17
+ LINE_ARG: ClassVar[str] = "Arguments:"
18
+ PATTERN_ARG: ClassVar[str] = r"([\w-]+\s*[|]\s*[\w-]*): (.*)"
19
+ LINE_REQ: ClassVar[str] = "Requirements:"
20
+ PATTERN_REQ: ClassVar[str] = r"(-\s.*)"
21
+
22
+ name: str = ""
23
+ desc: str = ""
24
+ summary: str = ""
25
+ summary_head: str = ""
26
+ summary_args: List[Argument] = []
27
+ summary_req: List[str] = []
28
+ summary_comments: str = ""
29
+ parsed: bool = False
30
+
31
+ def parse(self) -> None:
32
+ """Parse the Task summary"""
33
+ # Head - get beginning until blank line
34
+ lines = self.summary.splitlines()
35
+ head: List[str] = []
36
+ for line in lines:
37
+ if line.strip():
38
+ head.append(line)
39
+ else:
40
+ break
41
+
42
+ self.summary_head = "\n".join(head) + "\n"
43
+ # Get partial summary without head lines
44
+ count_line_head = len(head)
45
+ index_partial_summary = max(0, count_line_head)
46
+ partial_summary_lines = lines[index_partial_summary:]
47
+ partial_summary_lines_count = len(partial_summary_lines)
48
+
49
+ # Arguments
50
+ found_arguments = False
51
+ for i, line in enumerate(partial_summary_lines):
52
+ if (line.strip() == Task.LINE_ARG) and (
53
+ (i + 1) < partial_summary_lines_count
54
+ ):
55
+ found_arguments = True
56
+ # Parse arguments
57
+ args_summary_lines = partial_summary_lines[(i + 1) :]
58
+ for j, line_arg in enumerate(args_summary_lines):
59
+ match = re.search(Task.PATTERN_ARG, line_arg.strip())
60
+ if match:
61
+ label, value = match.groups()
62
+ arg = Argument(label=label.strip(), value=value.strip())
63
+ self.summary_args.append(arg)
64
+ else:
65
+ break
66
+ if found_arguments:
67
+ del partial_summary_lines[i : (i + j + 2)]
68
+ break
69
+
70
+ # Requirements
71
+ partial_summary_lines_count = len(partial_summary_lines)
72
+ found_req = False
73
+ for i, line in enumerate(partial_summary_lines):
74
+ if (line.strip() == Task.LINE_REQ) and (
75
+ (i + 1) < partial_summary_lines_count
76
+ ):
77
+ found_req = True
78
+ # Parse req
79
+ req_summary_lines = partial_summary_lines[(i + 1) :]
80
+ for j, line_req in enumerate(req_summary_lines):
81
+ match = re.search(Task.PATTERN_REQ, line_req.strip())
82
+ if match:
83
+ req = match.groups()
84
+ self.summary_req.append(req[0])
85
+ else:
86
+ break
87
+ if found_req:
88
+ del partial_summary_lines[i : (i + j + 2)]
89
+ break
90
+
91
+ # Comments
92
+ self.summary_comments = "\n".join(partial_summary_lines).strip("\n")
93
+
94
+ self.parsed = True
95
+
96
+ def to_md(self, file_name: str) -> str:
97
+ """Return the details of the task
98
+
99
+ Args:
100
+ file_name (str): File_name of the task file.
101
+
102
+ Returns:
103
+ str: Markdown of the task details
104
+ """
105
+ self.parse()
106
+ output = f"\n## :simple-task: {file_name}:{self.name}\n\n"
107
+
108
+ output += f"{self.desc} \n\n"
109
+ output += "```shell\n"
110
+ output += self.summary_head
111
+ output += "```\n"
112
+
113
+ # Arguments
114
+ output += """
115
+ | Arguments | Description |
116
+ | --------- | ----------- |
117
+ """
118
+ if len(self.summary_args) == 0:
119
+ output += "| - | - |\n"
120
+ else:
121
+ for arg in self.summary_args:
122
+ label = arg.label.replace("|", "\\|")
123
+ output += f"| `{label}` | {arg.value} |\n"
124
+ output += "\n"
125
+
126
+ # Comments
127
+ output += f"{self.summary_comments}\n\n"
128
+
129
+ # Requirements
130
+ output += '!!! info "Requirements:"\n\n'
131
+ if len(self.summary_req) == 0:
132
+ output += " - None\n"
133
+ else:
134
+ for req in self.summary_req:
135
+ output += f" {req}\n"
136
+
137
+ return output
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class Variable(BaseModel):
7
+ name: str = ""
8
+ value: str = ""
9
+ description: str = ""
10
+
11
+ def to_md(self) -> str:
12
+ """Return the details of the variable
13
+
14
+ Returns:
15
+ str: Markdown columns of the variable details
16
+ """
17
+ if self.name == "":
18
+ name = " - "
19
+ else:
20
+ name = "`" + self.name + "`"
21
+ if self.description == "":
22
+ description = " - "
23
+ else:
24
+ description = self.description
25
+ if self.value == "":
26
+ value = " - "
27
+ else:
28
+ value = "`" + self.value + "`"
29
+
30
+ return f"| {name} | {description} | {value} |"
task2md/task2md.py ADDED
@@ -0,0 +1,126 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import os
4
+ from typing import List
5
+
6
+ import click
7
+
8
+ from task2md.task.file import File
9
+ from task2md.util.dir import Dir
10
+
11
+
12
+ @click.group()
13
+ @click.version_option("1.0.0", prog_name="task2md")
14
+ def cli() -> None:
15
+ """A CLI tool to generate markdown documentation files from Task files."""
16
+ pass
17
+
18
+
19
+ @cli.command()
20
+ @click.option(
21
+ "-i",
22
+ "--input",
23
+ "input_dir",
24
+ type=click.Path(exists=True, file_okay=False, dir_okay=True),
25
+ required=True,
26
+ help="Input directory",
27
+ )
28
+ @click.option(
29
+ "-d",
30
+ "--dir",
31
+ "output_dir",
32
+ type=click.Path(exists=False, file_okay=False, dir_okay=True, writable=True),
33
+ required=False,
34
+ help="Output markdown documentation files directory. Default current directory.",
35
+ )
36
+ def dir(
37
+ input_dir: click.Path,
38
+ output_dir: click.Path,
39
+ ) -> None:
40
+ """Command to generate a markdown documentation file from a directory.
41
+
42
+ Raises:
43
+ click.ClickException: Error when reading input file or writing output file
44
+ """
45
+ task_files: List[str] = []
46
+ in_dir = click.format_filename(str(input_dir))
47
+ for filename in os.listdir(in_dir):
48
+ if filename.endswith(".yml") or filename.endswith(".yaml"):
49
+ task_files.append(filename)
50
+
51
+ if len(task_files) == 0:
52
+ click.echo(f"No yaml file found in: {in_dir}")
53
+ else:
54
+ try:
55
+ out_dir = Dir(output_dir, True)
56
+
57
+ except OSError as error:
58
+ raise click.ClickException(
59
+ "Output directory can not be created!\n" + str(error)
60
+ )
61
+
62
+ try:
63
+ for filename in task_files:
64
+ task_file = File(path=f"{in_dir}/{filename}")
65
+ task_file.generate(out_dir)
66
+
67
+ click.echo(
68
+ f"Task documentation generated: {task_file.get_filename()}.md"
69
+ )
70
+
71
+ except ValueError as ve:
72
+ raise click.ClickException(
73
+ "Error on reading file {} :\n {}".format(filename, str(ve))
74
+ )
75
+
76
+
77
+ @cli.command()
78
+ @click.option(
79
+ "-i",
80
+ "--input",
81
+ "input_file",
82
+ type=click.Path(exists=True, file_okay=True, dir_okay=False),
83
+ required=True,
84
+ help="Input Task yaml file.",
85
+ )
86
+ @click.option(
87
+ "-d",
88
+ "--dir",
89
+ "output_dir",
90
+ type=click.Path(exists=False, file_okay=False, dir_okay=True, writable=True),
91
+ required=False,
92
+ help="Output markdown documentation files directory. Default current directory.",
93
+ )
94
+ def file(
95
+ input_file: click.Path,
96
+ output_dir: click.Path,
97
+ ) -> None:
98
+ """Command to generate a markdown documentation file from a Task file.
99
+
100
+ Raises:
101
+ click.ClickException: Error when reading input file or writing output file
102
+ """
103
+ input_filename = click.format_filename(str(input_file))
104
+ task_file = File(path=input_filename)
105
+
106
+ try:
107
+ dir = Dir(output_dir, True)
108
+
109
+ except OSError as error:
110
+ raise click.ClickException(
111
+ "Output directory can not be created!\n" + str(error)
112
+ )
113
+
114
+ try:
115
+ task_file.generate(dir)
116
+
117
+ click.echo(f"Task documentation generated: {task_file.get_filename()}.md")
118
+
119
+ except ValueError as ve:
120
+ raise click.ClickException(
121
+ "Error on reading file {} :\n {}".format(input_filename, str(ve))
122
+ )
123
+
124
+
125
+ if __name__ == "__main__":
126
+ cli() # pragma: no cover
File without changes
task2md/util/dir.py ADDED
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import os
4
+
5
+ import click
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class Dir(BaseModel):
10
+ """Utility class for a directory."""
11
+
12
+ path: str
13
+
14
+ def __init__(self, dir_path: click.Path | None = None, create: bool = False):
15
+ """Constructor.
16
+
17
+ Args:
18
+ dir_path (click.Path | None): Directory file path. \
19
+ Current working directory if None.
20
+ create (bool, optional): Create directory if doesn't exist. \
21
+ Defaults to False.
22
+
23
+ Raises:
24
+ FileNotFoundError: When create is False and directory not found.
25
+ """
26
+ if dir_path is None:
27
+ path = os.getcwd()
28
+ else:
29
+ path = click.format_filename(str(dir_path))
30
+
31
+ if not os.path.exists(path):
32
+ if create:
33
+ os.makedirs(path)
34
+ else:
35
+ raise FileNotFoundError(f"Directory {path} not found!")
36
+
37
+ super().__init__(path=path)
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.3
2
+ Name: task2md
3
+ Version: 1.0.0
4
+ Summary: A program to generate markdown documentation files from Task files
5
+ Project-URL: Homepage, https://gitlab.com/op_so/task/task2md
6
+ Project-URL: Documentation, https://op_so.gitlab.io/task/task2md/
7
+ Author-email: FX Soubirou <soubirou@yahoo.fr>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: click>=8.1.7
15
+ Requires-Dist: pydantic>=2.6.3
16
+ Requires-Dist: pyyaml>=6.0.1
17
+ Requires-Dist: types-pyyaml>=6.0.12.12
18
+ Description-Content-Type: text/markdown
19
+
20
+ # `task2md`
21
+
22
+ [![Software License](https://img.shields.io/badge/license-MIT-informational.svg?style=for-the-badge)](LICENSE)
23
+ [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release&style=for-the-badge)](https://github.com/semantic-release/semantic-release)
24
+ [![Pipeline Status](https://gitlab.com/op_so/task/task2md/badges/main/pipeline.svg)](https://gitlab.com/op_so/task/task2md/pipelines)
25
+
26
+ [![Built with Material for MkDocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?style=for-the-badge&logo=MaterialForMkDocs&logoColor=white)](https://op_so.gitlab.io/task/task2md/) Source code documentation
27
+
28
+ A CLI tool to generate from [Task](https://taskfile.dev/) files, some markdown
29
+ documentation files for [`mkdocs`](https://squidfunk.github.io/mkdocs-material/) static site.
30
+
31
+ ```bash
32
+ Usage: task2md [OPTIONS] COMMAND [ARGS]...
33
+
34
+ A CLI tool to generate markdown documentation files from Task files.
35
+
36
+ Options:
37
+ --version Show the version and exit.
38
+ --help Show this message and exit.
39
+
40
+ Commands:
41
+ dir Command to generate a markdown documentation file from a directory.
42
+ file Command to generate a markdown documentation file from a Task file.
43
+ ```
44
+
45
+ ## `dir`
46
+
47
+ Get all files with `yaml/yml` extension from the input directory and generate the
48
+ markdown files in the output directory.
49
+
50
+ ```bash
51
+ Usage: task2md dir [OPTIONS]
52
+
53
+ Command to generate a markdown documentation file from a directory.
54
+
55
+ Raises: click.ClickException: Error when reading input file or writing
56
+ output file
57
+
58
+ Options:
59
+ -i, --input DIRECTORY Input directory [required]
60
+ -d, --dir DIRECTORY Output markdown documentation files directory.
61
+ Default current directory.
62
+ --help Show this message and exit.
63
+ ```
64
+
65
+ Example:
66
+
67
+ ```bash
68
+ task2md dir --input Taskfile.d/ -d doc_dir/
69
+ ```
70
+
71
+ ## `file`
72
+
73
+ Generate from the input file a markdown file in the output directory.
74
+
75
+ ```bash
76
+ Usage: task2md file [OPTIONS]
77
+
78
+ Command to generate a markdown documentation file from a Task file.
79
+
80
+ Raises: click.ClickException: Error when reading input file or writing
81
+ output file
82
+
83
+ Options:
84
+ -i, --input FILE Input Task yaml file. [required]
85
+ -d, --dir DIRECTORY Output markdown documentation files directory. Default
86
+ current directory.
87
+ --help Show this message and exit.
88
+ ```
89
+
90
+ Example:
91
+
92
+ ```bash
93
+ task2md file --input Taskfile.d/lint.yml -d doc_dir/
94
+ ```
95
+
96
+ ## Installation
97
+
98
+ ### With `Python` environment
99
+
100
+ To use:
101
+
102
+ - Minimal Python version: 3.10
103
+
104
+ Installation with Python `pip`:
105
+
106
+ ```bash
107
+ python3 -m pip install task2md
108
+ task2md --help
109
+ ```
110
+
111
+ ## Authors
112
+
113
+ <!-- vale off -->
114
+ - **FX Soubirou** - *Initial work* - [GitLab repositories](https://gitlab.com/op_so)
115
+ <!-- vale on -->
116
+
117
+ ## License
118
+
119
+ <!-- vale off -->
120
+ This program is free software: you can redistribute it and/or modify it under the terms of the MIT License (MIT).
121
+ See the [LICENSE](https://opensource.org/licenses/MIT) for details.
122
+ <!-- vale on -->
@@ -0,0 +1,14 @@
1
+ task2md/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ task2md/task2md.py,sha256=AoCIV0xE3-a5WibvXNGqLpaWDxZXQsVIIOwtmj3lq7U,3328
3
+ task2md/task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ task2md/task/file.py,sha256=09YxIXDgQShrnLdvMPh-yoB4UCjbOlbFxPYm-FgsgpQ,7466
5
+ task2md/task/header.py,sha256=MY_uqIGgFPY6cGVs52ZegPtD0Szq0-Tq6PxTshl7Pgc,2180
6
+ task2md/task/task.py,sha256=wrYNAql_LZhS9YVEj85OIjVcjF8ZdChenO-zb1dqUYc,4314
7
+ task2md/task/variable.py,sha256=jSmG9jHeyHz7_gEu2bY2LYvJGBG_NRo1HRjfypM44FQ,713
8
+ task2md/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ task2md/util/dir.py,sha256=2MeWmhMGkxFiJuZI6xcf71YRhhqBq1HqbDI4tKc2FOc,965
10
+ task2md-1.0.0.dist-info/METADATA,sha256=TtuyDxej0KNYgjEncpnmVje-nWsFDlaPY8ewRxvltAI,3686
11
+ task2md-1.0.0.dist-info/WHEEL,sha256=uNdcs2TADwSd5pVaP0Z_kcjcvvTUklh2S7bxZMF8Uj0,87
12
+ task2md-1.0.0.dist-info/entry_points.txt,sha256=zXCjf5tbisw5ZzMarzBDlSkNgzV1YDsMx3s8h9azjkc,48
13
+ task2md-1.0.0.dist-info/licenses/LICENSE,sha256=6j37YDpY2ikwX8yV80eUpAGPDShonchJW9nyFpqAGKM,1095
14
+ task2md-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.22.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ task2md = task2md.task2md:cli
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2024 FX Soubirou soubirou@yahoo.fr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.