md-table-writer 0.1.0__tar.gz
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.
- md_table_writer-0.1.0/LICENSE +21 -0
- md_table_writer-0.1.0/PKG-INFO +65 -0
- md_table_writer-0.1.0/README.md +47 -0
- md_table_writer-0.1.0/pyproject.toml +34 -0
- md_table_writer-0.1.0/setup.cfg +4 -0
- md_table_writer-0.1.0/src/main.py +36 -0
- md_table_writer-0.1.0/src/md_table_writer.egg-info/PKG-INFO +65 -0
- md_table_writer-0.1.0/src/md_table_writer.egg-info/SOURCES.txt +12 -0
- md_table_writer-0.1.0/src/md_table_writer.egg-info/dependency_links.txt +1 -0
- md_table_writer-0.1.0/src/md_table_writer.egg-info/entry_points.txt +2 -0
- md_table_writer-0.1.0/src/md_table_writer.egg-info/requires.txt +3 -0
- md_table_writer-0.1.0/src/md_table_writer.egg-info/top_level.txt +2 -0
- md_table_writer-0.1.0/src/table_generator.py +200 -0
- md_table_writer-0.1.0/tests/test_table_generator.py +122 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Screven
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: md-table-writer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate Markdown formatted tables from data files.
|
|
5
|
+
Author-email: Thomas Screven <tscreven@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tscreven/MarkdownTableGenerator
|
|
8
|
+
Project-URL: Issues, https://github.com/tscreven/MarkdownTableGenerator/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: openpyxl
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# markdown-table
|
|
20
|
+
|
|
21
|
+
`markdown-table` programatically generates Markdown formatted table(s) from a given data file and writes it to a Markdown file. Supported data file formats: CSV (.csv), NumPy (.npy), and Excel (.xlsx) files.
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
Program processes text from data files and processes them into row-ordered table(s) with column headers. The table is written to a Markdown file either at a specific line number or appendeded to the end of the file.
|
|
26
|
+
|
|
27
|
+
**CSV and Excel:** Users have the option to specify which column headers from the file to process. If left unspecified, the package will process all columns. The package assumes the first row in the file is column headers.
|
|
28
|
+
|
|
29
|
+
**Excel**: For Excel files, a new table is generated for every sheet. There is an additional option to specify what sheet names to generate tables for. If left unspecified, every sheet will be processed.
|
|
30
|
+
|
|
31
|
+
**NumPy**: Unlike the other two file types, it is required to specify column headers. The first row in the matrix should not be the intended column headers. The number of column headers must match the number of columns in the matrix.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
```bash
|
|
36
|
+
markdown-table f md -cols -sheets -align -line -append
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| Argument | Description | Required? | Default | Example |
|
|
40
|
+
| :------: | :----------------------------------------------------: | :----------------------------------: | :---------: | :-------------------: |
|
|
41
|
+
| f | Data file | Yes | | data.xlsx |
|
|
42
|
+
| md | Markdown file | Yes | | report.md |
|
|
43
|
+
| -cols | List of column headers to include from data file | No, unless data file is a NumPy file | | -cols Time Population |
|
|
44
|
+
| -sheets | List of sheets from Excel file to write tables for | No | | -sheets Sheet1 Sheet2 |
|
|
45
|
+
| -align | Table column alignment options: left, center, or right | No | center | -align right |
|
|
46
|
+
| -line | Line number in Markdown file to write table to | No | end of file | -line 50 |
|
|
47
|
+
| -append | Flag to write table at end of Markdown file | No | False | -append |
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Examples
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
markdown-table data.xlsx report.md -cols Time Population -sheets Sheet1 Sheet2 -align right -line 50
|
|
54
|
+
```
|
|
55
|
+
Generates two tables for Sheet1 and Sheet2 with columns Time and Population from data.xlsx. The table's columns are right aligned. The table is written on line 50 in report.md.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
markdown-table data.csv report.md -align left -append
|
|
59
|
+
```
|
|
60
|
+
Generates one table with columns Time and Population from data.csv. The table's columns are left aligned. The table is written to the end of report.md.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
markdown-table data.npy report.md -cols Time Population -line 50
|
|
64
|
+
```
|
|
65
|
+
Generates one table from the 2D matrix in data.npy with column headers Time and Population. The table's columns are center aligned. The table is written onen line 50 in report.md.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# markdown-table
|
|
2
|
+
|
|
3
|
+
`markdown-table` programatically generates Markdown formatted table(s) from a given data file and writes it to a Markdown file. Supported data file formats: CSV (.csv), NumPy (.npy), and Excel (.xlsx) files.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Program processes text from data files and processes them into row-ordered table(s) with column headers. The table is written to a Markdown file either at a specific line number or appendeded to the end of the file.
|
|
8
|
+
|
|
9
|
+
**CSV and Excel:** Users have the option to specify which column headers from the file to process. If left unspecified, the package will process all columns. The package assumes the first row in the file is column headers.
|
|
10
|
+
|
|
11
|
+
**Excel**: For Excel files, a new table is generated for every sheet. There is an additional option to specify what sheet names to generate tables for. If left unspecified, every sheet will be processed.
|
|
12
|
+
|
|
13
|
+
**NumPy**: Unlike the other two file types, it is required to specify column headers. The first row in the matrix should not be the intended column headers. The number of column headers must match the number of columns in the matrix.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
```bash
|
|
18
|
+
markdown-table f md -cols -sheets -align -line -append
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Argument | Description | Required? | Default | Example |
|
|
22
|
+
| :------: | :----------------------------------------------------: | :----------------------------------: | :---------: | :-------------------: |
|
|
23
|
+
| f | Data file | Yes | | data.xlsx |
|
|
24
|
+
| md | Markdown file | Yes | | report.md |
|
|
25
|
+
| -cols | List of column headers to include from data file | No, unless data file is a NumPy file | | -cols Time Population |
|
|
26
|
+
| -sheets | List of sheets from Excel file to write tables for | No | | -sheets Sheet1 Sheet2 |
|
|
27
|
+
| -align | Table column alignment options: left, center, or right | No | center | -align right |
|
|
28
|
+
| -line | Line number in Markdown file to write table to | No | end of file | -line 50 |
|
|
29
|
+
| -append | Flag to write table at end of Markdown file | No | False | -append |
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Examples
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
markdown-table data.xlsx report.md -cols Time Population -sheets Sheet1 Sheet2 -align right -line 50
|
|
36
|
+
```
|
|
37
|
+
Generates two tables for Sheet1 and Sheet2 with columns Time and Population from data.xlsx. The table's columns are right aligned. The table is written on line 50 in report.md.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
markdown-table data.csv report.md -align left -append
|
|
41
|
+
```
|
|
42
|
+
Generates one table with columns Time and Population from data.csv. The table's columns are left aligned. The table is written to the end of report.md.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
markdown-table data.npy report.md -cols Time Population -line 50
|
|
46
|
+
```
|
|
47
|
+
Generates one table from the 2D matrix in data.npy with column headers Time and Population. The table's columns are center aligned. The table is written onen line 50 in report.md.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "md-table-writer"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
|
|
5
|
+
authors = [
|
|
6
|
+
{ name="Thomas Screven", email="tscreven@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
description = "Generate Markdown formatted tables from data files."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pandas",
|
|
13
|
+
"numpy",
|
|
14
|
+
"openpyxl"
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.12"
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
license = "MIT"
|
|
23
|
+
license-files = ["LICENSE"]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/tscreven/MarkdownTableGenerator"
|
|
27
|
+
Issues = "https://github.com/tscreven/MarkdownTableGenerator/issues"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
markdown-table = "main:main"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools]
|
|
33
|
+
package-dir = {"" = "src"}
|
|
34
|
+
py-modules = ["main", "table_generator"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from table_generator import MarkdownTable
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
parser = argparse.ArgumentParser(
|
|
6
|
+
description="Generate Markdown tables from CSV, Excel, or NumPy files."
|
|
7
|
+
)
|
|
8
|
+
parser.add_argument("f", help="Path to data file.")
|
|
9
|
+
parser.add_argument("md", help="Path to target Markdown file.")
|
|
10
|
+
parser.add_argument("-cols", nargs='+',
|
|
11
|
+
help="Column headers to include from the data file.")
|
|
12
|
+
parser.add_argument("-sheets", nargs='+',
|
|
13
|
+
help="Excel sheet names to generate tables from.")
|
|
14
|
+
parser.add_argument("-align", choices=["left", "center", "right"],
|
|
15
|
+
default="center", help="Table column alignment.")
|
|
16
|
+
parser.add_argument("-line", type=int, default=0,
|
|
17
|
+
help="Line number where table(s) are inserted in the Markdown file.")
|
|
18
|
+
parser.add_argument("-append", action='store_true',
|
|
19
|
+
help="Append table(s) to the end of the Markdown file.")
|
|
20
|
+
|
|
21
|
+
args = parser.parse_args()
|
|
22
|
+
|
|
23
|
+
col_headers = [] if args.cols is None else args.cols
|
|
24
|
+
excel_sheets = [] if args.sheets is None else args.sheets
|
|
25
|
+
|
|
26
|
+
MarkdownTable(args.f,
|
|
27
|
+
args.md,
|
|
28
|
+
args.align,
|
|
29
|
+
args.line,
|
|
30
|
+
args.append,
|
|
31
|
+
col_headers,
|
|
32
|
+
excel_sheets
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: md-table-writer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate Markdown formatted tables from data files.
|
|
5
|
+
Author-email: Thomas Screven <tscreven@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tscreven/MarkdownTableGenerator
|
|
8
|
+
Project-URL: Issues, https://github.com/tscreven/MarkdownTableGenerator/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: openpyxl
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# markdown-table
|
|
20
|
+
|
|
21
|
+
`markdown-table` programatically generates Markdown formatted table(s) from a given data file and writes it to a Markdown file. Supported data file formats: CSV (.csv), NumPy (.npy), and Excel (.xlsx) files.
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
Program processes text from data files and processes them into row-ordered table(s) with column headers. The table is written to a Markdown file either at a specific line number or appendeded to the end of the file.
|
|
26
|
+
|
|
27
|
+
**CSV and Excel:** Users have the option to specify which column headers from the file to process. If left unspecified, the package will process all columns. The package assumes the first row in the file is column headers.
|
|
28
|
+
|
|
29
|
+
**Excel**: For Excel files, a new table is generated for every sheet. There is an additional option to specify what sheet names to generate tables for. If left unspecified, every sheet will be processed.
|
|
30
|
+
|
|
31
|
+
**NumPy**: Unlike the other two file types, it is required to specify column headers. The first row in the matrix should not be the intended column headers. The number of column headers must match the number of columns in the matrix.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
```bash
|
|
36
|
+
markdown-table f md -cols -sheets -align -line -append
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| Argument | Description | Required? | Default | Example |
|
|
40
|
+
| :------: | :----------------------------------------------------: | :----------------------------------: | :---------: | :-------------------: |
|
|
41
|
+
| f | Data file | Yes | | data.xlsx |
|
|
42
|
+
| md | Markdown file | Yes | | report.md |
|
|
43
|
+
| -cols | List of column headers to include from data file | No, unless data file is a NumPy file | | -cols Time Population |
|
|
44
|
+
| -sheets | List of sheets from Excel file to write tables for | No | | -sheets Sheet1 Sheet2 |
|
|
45
|
+
| -align | Table column alignment options: left, center, or right | No | center | -align right |
|
|
46
|
+
| -line | Line number in Markdown file to write table to | No | end of file | -line 50 |
|
|
47
|
+
| -append | Flag to write table at end of Markdown file | No | False | -append |
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Examples
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
markdown-table data.xlsx report.md -cols Time Population -sheets Sheet1 Sheet2 -align right -line 50
|
|
54
|
+
```
|
|
55
|
+
Generates two tables for Sheet1 and Sheet2 with columns Time and Population from data.xlsx. The table's columns are right aligned. The table is written on line 50 in report.md.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
markdown-table data.csv report.md -align left -append
|
|
59
|
+
```
|
|
60
|
+
Generates one table with columns Time and Population from data.csv. The table's columns are left aligned. The table is written to the end of report.md.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
markdown-table data.npy report.md -cols Time Population -line 50
|
|
64
|
+
```
|
|
65
|
+
Generates one table from the 2D matrix in data.npy with column headers Time and Population. The table's columns are center aligned. The table is written onen line 50 in report.md.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/main.py
|
|
5
|
+
src/table_generator.py
|
|
6
|
+
src/md_table_writer.egg-info/PKG-INFO
|
|
7
|
+
src/md_table_writer.egg-info/SOURCES.txt
|
|
8
|
+
src/md_table_writer.egg-info/dependency_links.txt
|
|
9
|
+
src/md_table_writer.egg-info/entry_points.txt
|
|
10
|
+
src/md_table_writer.egg-info/requires.txt
|
|
11
|
+
src/md_table_writer.egg-info/top_level.txt
|
|
12
|
+
tests/test_table_generator.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
from typing import Literal
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
def error(message:str):
|
|
8
|
+
print(message)
|
|
9
|
+
sys.exit(1)
|
|
10
|
+
|
|
11
|
+
class MarkdownTable:
|
|
12
|
+
|
|
13
|
+
def __init__(self,
|
|
14
|
+
data_file, md_file,
|
|
15
|
+
table_alignment: Literal["left", "center", "right"],
|
|
16
|
+
line_num:int, append:bool, col_headers:list, excel_sheets:list
|
|
17
|
+
) -> None:
|
|
18
|
+
|
|
19
|
+
if not os.path.exists(data_file):
|
|
20
|
+
error(f"{data_file} does not exist.")
|
|
21
|
+
|
|
22
|
+
if md_file[-3:] != ".md":
|
|
23
|
+
error(f"Target {md_file} is not a Markdown file.")
|
|
24
|
+
|
|
25
|
+
if not os.path.exists(md_file):
|
|
26
|
+
error(f"{md_file} does not exist.")
|
|
27
|
+
|
|
28
|
+
if not append and line_num <= 0:
|
|
29
|
+
print(f"Appending table to end of {md_file} because either no line number was given or the line number is not positive.")
|
|
30
|
+
append = True
|
|
31
|
+
|
|
32
|
+
self.data_file = data_file
|
|
33
|
+
self.md_file = md_file
|
|
34
|
+
self.align = table_alignment
|
|
35
|
+
self.line_num = line_num
|
|
36
|
+
self.append = append
|
|
37
|
+
self.col_headers = col_headers
|
|
38
|
+
self.num_cols = len(col_headers)
|
|
39
|
+
|
|
40
|
+
self.delegate(excel_sheets)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def delegate(self, excel_sheets):
|
|
44
|
+
'''Direct table value processing based on file type.'''
|
|
45
|
+
if self.data_file[-4:] == ".csv":
|
|
46
|
+
self.process_csv()
|
|
47
|
+
elif self.data_file[-4:] == ".npy":
|
|
48
|
+
if self.num_cols == 0:
|
|
49
|
+
error("Column headers must be given for NumPy file.")
|
|
50
|
+
self.process_npy()
|
|
51
|
+
elif self.data_file[-5:] == ".xlsx":
|
|
52
|
+
self.process_excel(excel_sheets)
|
|
53
|
+
else:
|
|
54
|
+
error("Invalid file type. Data file must be either a CSV (.csv), NumPy (.npy), or Excel (.xlsx) file.")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def col_header_update(self, headers):
|
|
58
|
+
'''Update instance for tracking new column headers.'''
|
|
59
|
+
if len(headers) == 0:
|
|
60
|
+
error(f"No column headers given and column headers could not be located in {self.data_file}.")
|
|
61
|
+
self.col_headers = headers
|
|
62
|
+
self.num_cols = len(headers)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def process_dataframe(self, df:pd.DataFrame):
|
|
66
|
+
'''Helper function for file processing functions using Dataframes.'''
|
|
67
|
+
|
|
68
|
+
def check_header(header):
|
|
69
|
+
if header not in df:
|
|
70
|
+
error(f'Category "{header}" is not a column header in {self.data_file}. Note that column headers are case sensitive.')
|
|
71
|
+
|
|
72
|
+
check_header(self.col_headers[0])
|
|
73
|
+
|
|
74
|
+
num_rows = len(df[self.col_headers[0]])
|
|
75
|
+
rows = [[] for _ in range(num_rows)]
|
|
76
|
+
|
|
77
|
+
for header in self.col_headers:
|
|
78
|
+
|
|
79
|
+
check_header(header)
|
|
80
|
+
|
|
81
|
+
if len(df[header]) != num_rows:
|
|
82
|
+
error(f"Unequal column length between columns {self.col_headers[0]} and {header}.")
|
|
83
|
+
|
|
84
|
+
col_values = df[header]
|
|
85
|
+
for i in range(num_rows):
|
|
86
|
+
rows[i].append(col_values[i])
|
|
87
|
+
|
|
88
|
+
self.gen_table(rows)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def process_csv(self):
|
|
92
|
+
'''Process given CSV file: find column headers if none given and store
|
|
93
|
+
values for all tracked column headers in row order.'''
|
|
94
|
+
# If no columns given, assume the entire first row is column headers.
|
|
95
|
+
if self.num_cols == 0:
|
|
96
|
+
with open(self.data_file, 'r') as f:
|
|
97
|
+
line = next(f)
|
|
98
|
+
headers = line.strip().split(',')
|
|
99
|
+
self.col_header_update(headers)
|
|
100
|
+
|
|
101
|
+
df = pd.read_csv(self.data_file)
|
|
102
|
+
self.process_dataframe(df)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def process_excel(self, sheet_names:list):
|
|
106
|
+
'''Process given Excel file: find column headers in sheet_names if none
|
|
107
|
+
are given and store values for all tracked column headers in row order.
|
|
108
|
+
If there are multiple sheets, generate a table for each sheet.'''
|
|
109
|
+
|
|
110
|
+
dfs = pd.read_excel(self.data_file, sheet_name=None)
|
|
111
|
+
all_file_sheets = dfs.keys()
|
|
112
|
+
if sheet_names == []:
|
|
113
|
+
# Load all sheets in Excel file if sheet_names are not specified.
|
|
114
|
+
sheet_names = list(all_file_sheets)
|
|
115
|
+
else:
|
|
116
|
+
for sheet in sheet_names:
|
|
117
|
+
if sheet not in all_file_sheets:
|
|
118
|
+
error(f"Sheet name {sheet} is not in {self.data_file}.")
|
|
119
|
+
|
|
120
|
+
# If no columns given, assume the entire first row is column headers.
|
|
121
|
+
no_headers = self.num_cols == 0
|
|
122
|
+
|
|
123
|
+
for i, sheet in enumerate(sheet_names):
|
|
124
|
+
if no_headers:
|
|
125
|
+
headers = dfs[sheet_names[i]].columns
|
|
126
|
+
self.col_header_update(headers)
|
|
127
|
+
|
|
128
|
+
df = dfs[sheet]
|
|
129
|
+
self.process_dataframe(df)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def process_npy(self):
|
|
133
|
+
'''Process given NumPy file: Column headers must be given. Table values
|
|
134
|
+
are stored in 2D NumPy matrix in row order; transpose the matrix if it
|
|
135
|
+
is required to match the number of expected columns.'''
|
|
136
|
+
|
|
137
|
+
data = np.load(self.data_file)
|
|
138
|
+
assert type(data) == np.ndarray
|
|
139
|
+
|
|
140
|
+
if data.ndim != 2:
|
|
141
|
+
error(f"Invalid number of dimensions in {self.data_file} with {data.ndim} dimension(s). NumPy files must be a 2D matrix.")
|
|
142
|
+
|
|
143
|
+
data_cols = data.shape[1]
|
|
144
|
+
if data_cols != self.num_cols:
|
|
145
|
+
print(f"Mismatch in number of columns in NumPy matrix and given column headers.")
|
|
146
|
+
|
|
147
|
+
if data.shape[0] == self.num_cols:
|
|
148
|
+
print("The number of rows match the number of given column headers. Generated table from transposed matrix.")
|
|
149
|
+
data = data.T
|
|
150
|
+
else:
|
|
151
|
+
error(f"{self.data_file} contains {data_cols} columns, given {self.num_cols} column headers.")
|
|
152
|
+
|
|
153
|
+
self.gen_table(data.tolist())
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def gen_table(self, rows:list):
|
|
157
|
+
'''Write Markdown formatted table in md_file at line_num or at end of file.'''
|
|
158
|
+
|
|
159
|
+
next_line = "\n|"
|
|
160
|
+
table_str = next_line
|
|
161
|
+
|
|
162
|
+
# Column headers
|
|
163
|
+
for h in self.col_headers:
|
|
164
|
+
table_str += f" {h} |"
|
|
165
|
+
table_str += next_line
|
|
166
|
+
|
|
167
|
+
if self.align == "left":
|
|
168
|
+
sep = ":-"
|
|
169
|
+
elif self.align == "center":
|
|
170
|
+
sep = ":-:"
|
|
171
|
+
else: # right alignment
|
|
172
|
+
sep = "-:"
|
|
173
|
+
|
|
174
|
+
# Horizontal line separator
|
|
175
|
+
for _ in range(self.num_cols):
|
|
176
|
+
table_str += f" {sep} |"
|
|
177
|
+
|
|
178
|
+
# Add each row
|
|
179
|
+
for row in rows:
|
|
180
|
+
table_str += next_line
|
|
181
|
+
for item in row:
|
|
182
|
+
table_str += f" {item} |"
|
|
183
|
+
|
|
184
|
+
new_lines = len(rows) + 4
|
|
185
|
+
|
|
186
|
+
# Need extra new line for formatting.
|
|
187
|
+
table_str += "\n"*2
|
|
188
|
+
|
|
189
|
+
with open(self.md_file, "r", encoding="utf-8") as f:
|
|
190
|
+
lines = f.readlines()
|
|
191
|
+
|
|
192
|
+
if self.append or self.line_num + 1 > len(lines):
|
|
193
|
+
with open(self.md_file, "a", encoding="utf-8") as f:
|
|
194
|
+
f.write('\n')
|
|
195
|
+
f.write(table_str[:-1])
|
|
196
|
+
new_lines += 1
|
|
197
|
+
else:
|
|
198
|
+
lines.insert(self.line_num - 1, table_str)
|
|
199
|
+
with open(self.md_file, "w", encoding="utf-8") as f:
|
|
200
|
+
f.writelines(lines)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import sys
|
|
3
|
+
import unittest
|
|
4
|
+
from contextlib import redirect_stdout
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from tempfile import TemporaryDirectory
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
12
|
+
|
|
13
|
+
from table_generator import MarkdownTable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MarkdownTableTests(unittest.TestCase):
|
|
17
|
+
def setUp(self):
|
|
18
|
+
self.temp_dir = TemporaryDirectory()
|
|
19
|
+
self.base_path = Path(self.temp_dir.name)
|
|
20
|
+
self.md_file = self.base_path / "report.md"
|
|
21
|
+
self.md_file.write_text("# Report\n", encoding="utf-8")
|
|
22
|
+
|
|
23
|
+
def tearDown(self):
|
|
24
|
+
self.temp_dir.cleanup()
|
|
25
|
+
|
|
26
|
+
def generate_table(self, data_file:str, align:Literal["left", "center", "right"]="center",
|
|
27
|
+
line_num=0, append=True, col_headers=[], excel_sheets=[]):
|
|
28
|
+
|
|
29
|
+
MarkdownTable(str(data_file), str(self.md_file), align, line_num,
|
|
30
|
+
append, col_headers, excel_sheets,)
|
|
31
|
+
return self.md_file.read_text(encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_csv_no_given_headers(self):
|
|
35
|
+
data_file = self.base_path / "data.csv"
|
|
36
|
+
data_file.write_text("Name,Score\nAda,10\nGrace,12\n", encoding="utf-8")
|
|
37
|
+
|
|
38
|
+
content = self.generate_table(str(data_file))
|
|
39
|
+
|
|
40
|
+
self.assertIn(
|
|
41
|
+
"| Name | Score |\n| :-: | :-: |\n| Ada | 10 |\n| Grace | 12 |",
|
|
42
|
+
content,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_csv_with_given_headers(self):
|
|
47
|
+
data_file = self.base_path / "data.csv"
|
|
48
|
+
data_file.write_text("Name,Score,Rank\nAda,10,2\nGrace,12,1\n", encoding="utf-8")
|
|
49
|
+
|
|
50
|
+
content = self.generate_table(str(data_file), align="right", col_headers=["Rank", "Name"])
|
|
51
|
+
|
|
52
|
+
self.assertIn( "| Rank | Name |\n| -: | -: |\n| 2 | Ada |\n| 1 | Grace |", content)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_line_insertion(self):
|
|
56
|
+
self.md_file.write_text("First\nSecond\nThird\n", encoding="utf-8")
|
|
57
|
+
data_file = self.base_path / "data.csv"
|
|
58
|
+
data_file.write_text("Name,Score\nAda,10\n", encoding="utf-8")
|
|
59
|
+
|
|
60
|
+
content = self.generate_table( str(data_file), align="left", line_num=2,
|
|
61
|
+
append=False, col_headers=["Name", "Score"])
|
|
62
|
+
|
|
63
|
+
self.assertTrue(content.startswith("First\n\n| Name | Score |"))
|
|
64
|
+
self.assertIn("| :- | :- |\n| Ada | 10 |", content)
|
|
65
|
+
self.assertTrue(content.endswith("\nSecond\nThird\n"))
|
|
66
|
+
|
|
67
|
+
def test_np_array(self):
|
|
68
|
+
data_file = self.base_path / "data.npy"
|
|
69
|
+
np.save(data_file, np.array([[1, 2], [3, 4]]))
|
|
70
|
+
|
|
71
|
+
content = self.generate_table(str(data_file), col_headers=["A", "B"])
|
|
72
|
+
|
|
73
|
+
self.assertIn("| A | B |\n| :-: | :-: |\n| 1 | 2 |\n| 3 | 4 |", content)
|
|
74
|
+
|
|
75
|
+
def test_np_array_transposed(self):
|
|
76
|
+
data_file = self.base_path / "data.npy"
|
|
77
|
+
np.save(data_file, np.array([[1, 2, 3], [4, 5, 6]]))
|
|
78
|
+
|
|
79
|
+
with redirect_stdout(io.StringIO()):
|
|
80
|
+
content = self.generate_table(str(data_file), col_headers=["A", "B"])
|
|
81
|
+
|
|
82
|
+
self.assertIn("| A | B |\n| :-: | :-: |\n| 1 | 4 |\n| 2 | 5 |\n| 3 | 6 |", content)
|
|
83
|
+
|
|
84
|
+
def test_excel_given_sheets_headers(self):
|
|
85
|
+
data_file = self.base_path / "data.xlsx"
|
|
86
|
+
with pd.ExcelWriter(data_file) as writer:
|
|
87
|
+
pd.DataFrame({"Name": ["Ada"], "Score": [10]}).to_excel(
|
|
88
|
+
writer, sheet_name="Scores", index=False
|
|
89
|
+
)
|
|
90
|
+
pd.DataFrame({"Name": ["Ignored"], "Score": [0]}).to_excel(
|
|
91
|
+
writer, sheet_name="Other", index=False
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
content = self.generate_table(str(data_file), col_headers=["Name", "Score"], excel_sheets=["Scores"])
|
|
95
|
+
|
|
96
|
+
self.assertIn("| Name | Score |\n| :-: | :-: |\n| Ada | 10 |", content)
|
|
97
|
+
self.assertNotIn("Ignored", content)
|
|
98
|
+
|
|
99
|
+
def test_np_array_no_headers_assert_error(self):
|
|
100
|
+
data_file = self.base_path / "data.npy"
|
|
101
|
+
np.save(data_file, np.array([[1]]))
|
|
102
|
+
|
|
103
|
+
with self.assertRaises(SystemExit) as raised, redirect_stdout(io.StringIO()) as output:
|
|
104
|
+
MarkdownTable(str(data_file), str(self.md_file), "center", 0, True, [], [])
|
|
105
|
+
|
|
106
|
+
self.assertEqual(raised.exception.code, 1)
|
|
107
|
+
self.assertIn("Column headers must be given for NumPy file.", output.getvalue())
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_given_wrong_header_assert_error(self):
|
|
111
|
+
data_file = self.base_path / "data.csv"
|
|
112
|
+
data_file.write_text("Name,Score\nAda,10\n", encoding="utf-8")
|
|
113
|
+
|
|
114
|
+
with self.assertRaises(SystemExit) as raised, redirect_stdout(io.StringIO()) as output:
|
|
115
|
+
MarkdownTable(str(data_file), str(self.md_file), "center", 0, True, ["Missing"], [])
|
|
116
|
+
|
|
117
|
+
self.assertEqual(raised.exception.code, 1)
|
|
118
|
+
self.assertIn( 'Category "Missing" is not a column header in', output.getvalue())
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
unittest.main()
|