h4md 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.
- h4md-0.1.0/LICENSE +28 -0
- h4md-0.1.0/MANIFEST.in +7 -0
- h4md-0.1.0/PKG-INFO +62 -0
- h4md-0.1.0/README.md +34 -0
- h4md-0.1.0/h4md/__init__.py +3 -0
- h4md-0.1.0/h4md/h4md.py +239 -0
- h4md-0.1.0/h4md.egg-info/PKG-INFO +62 -0
- h4md-0.1.0/h4md.egg-info/SOURCES.txt +14 -0
- h4md-0.1.0/h4md.egg-info/dependency_links.txt +1 -0
- h4md-0.1.0/h4md.egg-info/entry_points.txt +2 -0
- h4md-0.1.0/h4md.egg-info/requires.txt +2 -0
- h4md-0.1.0/h4md.egg-info/top_level.txt +1 -0
- h4md-0.1.0/requirements.txt +5 -0
- h4md-0.1.0/setup.cfg +4 -0
- h4md-0.1.0/setup.py +41 -0
- h4md-0.1.0/tests/test_h4md.py +112 -0
h4md-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, H. Joe Lee
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
h4md-0.1.0/MANIFEST.in
ADDED
h4md-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: h4md
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A command-line tool to convert HDF4 datasets to markdown
|
|
5
|
+
Home-page: https://github.com/iowarp/h4md
|
|
6
|
+
Author: IOWarp User
|
|
7
|
+
Author-email: user@iowarp.org
|
|
8
|
+
Keywords: hdf4,markdown,conversion,hdf,data
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.6
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: pyhdf>=0.10.5
|
|
27
|
+
Requires-Dist: click>=8.1.0
|
|
28
|
+
|
|
29
|
+
# h4md (HDF4 to Markdown)
|
|
30
|
+
|
|
31
|
+
A command-line tool to convert HDF4 datasets and attributes to markdown format.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
You can install h4md directly from the repository:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install .
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
After installation, you can use the `h4md` command directly:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
h4md input.hdf output.md
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you don't specify an output file, it will use the input filename with a `.md` extension:
|
|
49
|
+
```bash
|
|
50
|
+
h4md input.hdf
|
|
51
|
+
# Creates input.md
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Output Format
|
|
55
|
+
|
|
56
|
+
The tool generates markdown with the following structure:
|
|
57
|
+
- File name as main heading
|
|
58
|
+
- Global attributes section
|
|
59
|
+
- Datasets section with each dataset containing:
|
|
60
|
+
- Shape information
|
|
61
|
+
- Data type
|
|
62
|
+
- Dataset-specific attributes
|
h4md-0.1.0/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# h4md (HDF4 to Markdown)
|
|
2
|
+
|
|
3
|
+
A command-line tool to convert HDF4 datasets and attributes to markdown format.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can install h4md directly from the repository:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install .
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
After installation, you can use the `h4md` command directly:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
h4md input.hdf output.md
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If you don't specify an output file, it will use the input filename with a `.md` extension:
|
|
21
|
+
```bash
|
|
22
|
+
h4md input.hdf
|
|
23
|
+
# Creates input.md
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Output Format
|
|
27
|
+
|
|
28
|
+
The tool generates markdown with the following structure:
|
|
29
|
+
- File name as main heading
|
|
30
|
+
- Global attributes section
|
|
31
|
+
- Datasets section with each dataset containing:
|
|
32
|
+
- Shape information
|
|
33
|
+
- Data type
|
|
34
|
+
- Dataset-specific attributes
|
h4md-0.1.0/h4md/h4md.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
h4md - Convert HDF4 datasets and attributes to markdown
|
|
4
|
+
"""
|
|
5
|
+
import click
|
|
6
|
+
from pyhdf.SD import SD, SDC
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
def format_dataset(dataset, dataset_name=None):
|
|
11
|
+
"""Format a dataset's information as markdown."""
|
|
12
|
+
try:
|
|
13
|
+
# Get basic dataset information
|
|
14
|
+
info = dataset.info()
|
|
15
|
+
|
|
16
|
+
# Use provided dataset_name or extract from info tuple when name() method fails
|
|
17
|
+
if dataset_name is not None:
|
|
18
|
+
name = dataset_name
|
|
19
|
+
else:
|
|
20
|
+
try:
|
|
21
|
+
name = dataset.name()
|
|
22
|
+
except (AttributeError, Exception):
|
|
23
|
+
# Extract name from info tuple: (name, rank, shape, data_type, n_attrs)
|
|
24
|
+
name = info[0] if info and len(info) > 0 else "Unknown"
|
|
25
|
+
|
|
26
|
+
shape = info[2]
|
|
27
|
+
data_type = info[3]
|
|
28
|
+
|
|
29
|
+
# Build markdown content
|
|
30
|
+
md = f"### Dataset: {name}\n\n"
|
|
31
|
+
md += f"- **Shape**: {shape}\n"
|
|
32
|
+
md += f"- **Type**: {data_type}\n"
|
|
33
|
+
|
|
34
|
+
# Get attributes if any
|
|
35
|
+
try:
|
|
36
|
+
# This returns a dictionary of attributes
|
|
37
|
+
attrs_dict = dataset.attributes()
|
|
38
|
+
|
|
39
|
+
if attrs_dict:
|
|
40
|
+
md += "\n#### Attributes:\n\n"
|
|
41
|
+
# The attributes dict contains values directly
|
|
42
|
+
for attr_name, attr_value in attrs_dict.items():
|
|
43
|
+
# Handle different attribute value types
|
|
44
|
+
if isinstance(attr_value, bytes):
|
|
45
|
+
# Convert bytes to string safely, replacing null bytes
|
|
46
|
+
try:
|
|
47
|
+
attr_str = attr_value.decode('utf-8', errors='replace').replace('\x00', '')
|
|
48
|
+
except:
|
|
49
|
+
attr_str = f"<binary data length={len(attr_value)}>"
|
|
50
|
+
elif isinstance(attr_value, (list, tuple)):
|
|
51
|
+
# Handle lists/tuples
|
|
52
|
+
attr_str = str(attr_value)
|
|
53
|
+
else:
|
|
54
|
+
# Handle other types (strings, numbers)
|
|
55
|
+
attr_str = str(attr_value)
|
|
56
|
+
|
|
57
|
+
# Truncate very long attributes to avoid huge markdown files
|
|
58
|
+
if len(attr_str) > 50:
|
|
59
|
+
attr_str = attr_str[:50] + "... (truncated, length=" + str(len(attr_str)) + ")"
|
|
60
|
+
|
|
61
|
+
# Clean up any remaining control characters
|
|
62
|
+
attr_str = ''.join(char for char in attr_str if ord(char) >= 32 or char in '\n\r\t')
|
|
63
|
+
|
|
64
|
+
md += f"- **{attr_name}**: {attr_str}\n"
|
|
65
|
+
except Exception as attrs_err:
|
|
66
|
+
# In case of error, continue with other parts of the dataset
|
|
67
|
+
md += "\n*Error reading attributes*\n"
|
|
68
|
+
|
|
69
|
+
return md
|
|
70
|
+
except Exception as e:
|
|
71
|
+
# If there's an error with the dataset, return a placeholder
|
|
72
|
+
return f"### Dataset: {dataset.name() if hasattr(dataset, 'name') else 'Unknown'}\n\n*Error formatting dataset*\n"
|
|
73
|
+
|
|
74
|
+
def hdf4_to_markdown(file_path):
|
|
75
|
+
"""Convert HDF4 file content to markdown format."""
|
|
76
|
+
hdf = None
|
|
77
|
+
try:
|
|
78
|
+
# Check if file exists and is readable
|
|
79
|
+
if not os.path.exists(file_path):
|
|
80
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
81
|
+
|
|
82
|
+
# Open the HDF4 file
|
|
83
|
+
hdf = SD(file_path, SDC.READ)
|
|
84
|
+
|
|
85
|
+
# Get filename for the header
|
|
86
|
+
filename = os.path.basename(file_path)
|
|
87
|
+
md = f"# HDF4 File: {filename}\n\n"
|
|
88
|
+
|
|
89
|
+
# Process global attributes
|
|
90
|
+
try:
|
|
91
|
+
# This returns a dictionary of attributes
|
|
92
|
+
attrs_dict = hdf.attributes()
|
|
93
|
+
|
|
94
|
+
if attrs_dict:
|
|
95
|
+
md += "## Global Attributes\n\n"
|
|
96
|
+
# Use the attributes dictionary directly
|
|
97
|
+
for attr_name, attr_value in attrs_dict.items():
|
|
98
|
+
# Handle different attribute value types
|
|
99
|
+
if isinstance(attr_value, bytes):
|
|
100
|
+
# Convert bytes to string safely, replacing null bytes
|
|
101
|
+
try:
|
|
102
|
+
attr_str = attr_value.decode('utf-8', errors='replace').replace('\x00', '')
|
|
103
|
+
except:
|
|
104
|
+
attr_str = f"<binary data length={len(attr_value)}>"
|
|
105
|
+
elif isinstance(attr_value, (list, tuple)):
|
|
106
|
+
# Handle lists/tuples
|
|
107
|
+
attr_str = str(attr_value)
|
|
108
|
+
else:
|
|
109
|
+
# Handle other types (strings, numbers)
|
|
110
|
+
attr_str = str(attr_value)
|
|
111
|
+
|
|
112
|
+
# Truncate very long attributes to avoid huge markdown files
|
|
113
|
+
if len(attr_str) > 50:
|
|
114
|
+
attr_str = attr_str[:50] + "... (truncated, length=" + str(len(attr_str)) + ")"
|
|
115
|
+
|
|
116
|
+
# Clean up any remaining control characters
|
|
117
|
+
attr_str = ''.join(char for char in attr_str if ord(char) >= 32 or char in '\n\r\t')
|
|
118
|
+
|
|
119
|
+
md += f"- **{attr_name}**: {attr_str}\n"
|
|
120
|
+
md += "\n"
|
|
121
|
+
except Exception:
|
|
122
|
+
# In case of error, continue with datasets
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
# Process datasets
|
|
126
|
+
try:
|
|
127
|
+
# Get all dataset names
|
|
128
|
+
datasets_dict = hdf.datasets()
|
|
129
|
+
dataset_names = list(datasets_dict.keys())
|
|
130
|
+
|
|
131
|
+
if dataset_names:
|
|
132
|
+
md += "## Datasets\n\n"
|
|
133
|
+
for ds_name in dataset_names:
|
|
134
|
+
try:
|
|
135
|
+
# Select and process each dataset
|
|
136
|
+
dataset = hdf.select(ds_name)
|
|
137
|
+
md += format_dataset(dataset, ds_name)
|
|
138
|
+
md += "\n"
|
|
139
|
+
except Exception:
|
|
140
|
+
# If there's an error with a dataset, continue with others
|
|
141
|
+
md += f"### Dataset: {ds_name}\n\n*Error processing dataset*\n\n"
|
|
142
|
+
except Exception:
|
|
143
|
+
# If there's an error getting datasets, note it
|
|
144
|
+
md += "*Error reading datasets*\n"
|
|
145
|
+
|
|
146
|
+
return md
|
|
147
|
+
except Exception as e:
|
|
148
|
+
# Handle any other errors
|
|
149
|
+
error_msg = str(e)
|
|
150
|
+
if "File is supported, must be either hdf, cdf, netcdf" in error_msg:
|
|
151
|
+
raise click.ClickException(f"File '{os.path.basename(file_path)}' is not a valid HDF4 file. It may be a different format (NetCDF, HDF5, etc.).")
|
|
152
|
+
elif "No such file or directory" in error_msg:
|
|
153
|
+
raise click.ClickException(f"File not found: {file_path}")
|
|
154
|
+
else:
|
|
155
|
+
raise click.ClickException(f"Error processing HDF4 file: {e}")
|
|
156
|
+
finally:
|
|
157
|
+
# Always close the file
|
|
158
|
+
if hdf is not None:
|
|
159
|
+
try:
|
|
160
|
+
hdf.end()
|
|
161
|
+
except Exception:
|
|
162
|
+
# If we can't close it cleanly, that's ok
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
@click.command()
|
|
166
|
+
@click.argument('input_file', type=click.Path(exists=True))
|
|
167
|
+
@click.argument('output_file', type=click.Path(), required=False)
|
|
168
|
+
def main(input_file, output_file=None):
|
|
169
|
+
"""
|
|
170
|
+
Convert HDF4 file to markdown format.
|
|
171
|
+
|
|
172
|
+
INPUT_FILE: Path to the input HDF4 file
|
|
173
|
+
OUTPUT_FILE: Optional path for the output markdown file (defaults to input_file with .md extension)
|
|
174
|
+
"""
|
|
175
|
+
if output_file is None:
|
|
176
|
+
output_file = os.path.splitext(input_file)[0] + '.md'
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
markdown_content = hdf4_to_markdown(input_file)
|
|
180
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
181
|
+
f.write(markdown_content)
|
|
182
|
+
click.echo(f"Successfully converted {input_file} to {output_file}")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
raise click.ClickException(str(e))
|
|
185
|
+
|
|
186
|
+
def test_function():
|
|
187
|
+
"""Test function to debug the hdf4_to_markdown function"""
|
|
188
|
+
import tempfile
|
|
189
|
+
from pyhdf.SD import SD, SDC
|
|
190
|
+
import numpy as np
|
|
191
|
+
|
|
192
|
+
# Create a temporary HDF4 file
|
|
193
|
+
with tempfile.NamedTemporaryFile(suffix='.hdf', delete=False) as tmp:
|
|
194
|
+
file_path = tmp.name
|
|
195
|
+
|
|
196
|
+
print(f"Creating test HDF4 file at: {file_path}")
|
|
197
|
+
try:
|
|
198
|
+
hdf = SD(file_path, SDC.WRITE | SDC.CREATE)
|
|
199
|
+
|
|
200
|
+
# Add global attributes
|
|
201
|
+
hdf.setattr('title', 'Test Dataset')
|
|
202
|
+
hdf.setattr('description', 'Sample HDF4 file for testing')
|
|
203
|
+
|
|
204
|
+
# Create a sample dataset
|
|
205
|
+
data = np.arange(6).reshape(2, 3)
|
|
206
|
+
sds = hdf.create('sample_data', SDC.FLOAT32, (2, 3))
|
|
207
|
+
sds.data[:] = data
|
|
208
|
+
sds.setattr('units', 'meters')
|
|
209
|
+
|
|
210
|
+
hdf.end()
|
|
211
|
+
|
|
212
|
+
# Now convert to markdown
|
|
213
|
+
print("\nTesting hdf4_to_markdown function:")
|
|
214
|
+
markdown = hdf4_to_markdown(file_path)
|
|
215
|
+
print("\nGenerated Markdown:")
|
|
216
|
+
print(markdown)
|
|
217
|
+
|
|
218
|
+
return True
|
|
219
|
+
except Exception as e:
|
|
220
|
+
print(f"\nTest FAILED with error: {type(e).__name__}: {e}")
|
|
221
|
+
import traceback
|
|
222
|
+
traceback.print_exc()
|
|
223
|
+
return False
|
|
224
|
+
finally:
|
|
225
|
+
import os
|
|
226
|
+
if os.path.exists(file_path):
|
|
227
|
+
try:
|
|
228
|
+
os.remove(file_path)
|
|
229
|
+
print(f"Removed temporary file: {file_path}")
|
|
230
|
+
except:
|
|
231
|
+
print(f"Failed to remove temporary file: {file_path}")
|
|
232
|
+
|
|
233
|
+
if __name__ == '__main__':
|
|
234
|
+
if len(sys.argv) > 1:
|
|
235
|
+
main()
|
|
236
|
+
else:
|
|
237
|
+
print("Running test function...")
|
|
238
|
+
success = test_function()
|
|
239
|
+
print(f"Test {'succeeded' if success else 'failed'}")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: h4md
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A command-line tool to convert HDF4 datasets to markdown
|
|
5
|
+
Home-page: https://github.com/iowarp/h4md
|
|
6
|
+
Author: IOWarp User
|
|
7
|
+
Author-email: user@iowarp.org
|
|
8
|
+
Keywords: hdf4,markdown,conversion,hdf,data
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.6
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: pyhdf>=0.10.5
|
|
27
|
+
Requires-Dist: click>=8.1.0
|
|
28
|
+
|
|
29
|
+
# h4md (HDF4 to Markdown)
|
|
30
|
+
|
|
31
|
+
A command-line tool to convert HDF4 datasets and attributes to markdown format.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
You can install h4md directly from the repository:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install .
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
After installation, you can use the `h4md` command directly:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
h4md input.hdf output.md
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you don't specify an output file, it will use the input filename with a `.md` extension:
|
|
49
|
+
```bash
|
|
50
|
+
h4md input.hdf
|
|
51
|
+
# Creates input.md
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Output Format
|
|
55
|
+
|
|
56
|
+
The tool generates markdown with the following structure:
|
|
57
|
+
- File name as main heading
|
|
58
|
+
- Global attributes section
|
|
59
|
+
- Datasets section with each dataset containing:
|
|
60
|
+
- Shape information
|
|
61
|
+
- Data type
|
|
62
|
+
- Dataset-specific attributes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
requirements.txt
|
|
5
|
+
setup.py
|
|
6
|
+
h4md/__init__.py
|
|
7
|
+
h4md/h4md.py
|
|
8
|
+
h4md.egg-info/PKG-INFO
|
|
9
|
+
h4md.egg-info/SOURCES.txt
|
|
10
|
+
h4md.egg-info/dependency_links.txt
|
|
11
|
+
h4md.egg-info/entry_points.txt
|
|
12
|
+
h4md.egg-info/requires.txt
|
|
13
|
+
h4md.egg-info/top_level.txt
|
|
14
|
+
tests/test_h4md.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
h4md
|
h4md-0.1.0/setup.cfg
ADDED
h4md-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="h4md",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
py_modules=["h4md"],
|
|
8
|
+
install_requires=[
|
|
9
|
+
"pyhdf>=0.10.5",
|
|
10
|
+
"click>=8.1.0",
|
|
11
|
+
],
|
|
12
|
+
entry_points={
|
|
13
|
+
"console_scripts": [
|
|
14
|
+
"h4md=h4md:main",
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
author="IOWarp User",
|
|
18
|
+
author_email="user@iowarp.org",
|
|
19
|
+
url="https://github.com/iowarp/h4md",
|
|
20
|
+
description="A command-line tool to convert HDF4 datasets to markdown",
|
|
21
|
+
long_description=open("README.md").read(),
|
|
22
|
+
long_description_content_type="text/markdown",
|
|
23
|
+
keywords="hdf4, markdown, conversion, hdf, data",
|
|
24
|
+
python_requires=">=3.6",
|
|
25
|
+
classifiers=[
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Intended Audience :: Science/Research",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3.6",
|
|
32
|
+
"Programming Language :: Python :: 3.7",
|
|
33
|
+
"Programming Language :: Python :: 3.8",
|
|
34
|
+
"Programming Language :: Python :: 3.9",
|
|
35
|
+
"Programming Language :: Python :: 3.10",
|
|
36
|
+
"Programming Language :: Python :: 3.11",
|
|
37
|
+
"Programming Language :: Python :: 3.12",
|
|
38
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
39
|
+
"Topic :: Utilities",
|
|
40
|
+
],
|
|
41
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from pyhdf.SD import SD, SDC
|
|
4
|
+
import numpy as np
|
|
5
|
+
from h4md.h4md import hdf4_to_markdown
|
|
6
|
+
|
|
7
|
+
@pytest.fixture
|
|
8
|
+
def sample_hdf_file(tmp_path):
|
|
9
|
+
"""Create a sample HDF4 file for testing."""
|
|
10
|
+
file_path = tmp_path / "test.hdf"
|
|
11
|
+
file_path_str = str(file_path)
|
|
12
|
+
|
|
13
|
+
# Create the HDF4 file
|
|
14
|
+
hdf = SD(file_path_str, SDC.WRITE | SDC.CREATE)
|
|
15
|
+
|
|
16
|
+
# Add global attributes
|
|
17
|
+
hdf.setattr('title', 'Test Dataset')
|
|
18
|
+
hdf.setattr('description', 'Sample HDF4 file for testing')
|
|
19
|
+
|
|
20
|
+
# Create a sample dataset
|
|
21
|
+
data = np.arange(6).reshape(2, 3)
|
|
22
|
+
sds = hdf.create('sample_data', SDC.FLOAT32, (2, 3))
|
|
23
|
+
sds.data[:] = data
|
|
24
|
+
sds.setattr('units', 'meters')
|
|
25
|
+
|
|
26
|
+
# Close file
|
|
27
|
+
hdf.end()
|
|
28
|
+
|
|
29
|
+
# Verify the file exists before returning
|
|
30
|
+
assert file_path.exists(), f"HDF4 file was not created: {file_path_str}"
|
|
31
|
+
return file_path
|
|
32
|
+
|
|
33
|
+
def test_hdf4_to_markdown(sample_hdf_file, tmp_path):
|
|
34
|
+
"""Test conversion of HDF4 to markdown."""
|
|
35
|
+
import traceback
|
|
36
|
+
print("\n-------------- Starting test_hdf4_to_markdown ---------------")
|
|
37
|
+
output_file = tmp_path / "output.md"
|
|
38
|
+
print(f"Sample HDF file path: {sample_hdf_file}")
|
|
39
|
+
print(f"Output file path: {output_file}")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
# Verify file exists
|
|
43
|
+
import os
|
|
44
|
+
file_path_str = str(sample_hdf_file)
|
|
45
|
+
print(f"File exists check: {os.path.exists(file_path_str)}")
|
|
46
|
+
|
|
47
|
+
# Try to manually open the HDF file to check if it's valid
|
|
48
|
+
print("Checking if HDF file is readable:")
|
|
49
|
+
try:
|
|
50
|
+
from pyhdf.SD import SD, SDC
|
|
51
|
+
hdf = SD(file_path_str, SDC.READ)
|
|
52
|
+
print(f" - HDF file opened successfully")
|
|
53
|
+
print(f" - Datasets: {list(hdf.datasets().keys())}")
|
|
54
|
+
attr_names = list(hdf.attributes().keys())
|
|
55
|
+
print(f" - Attributes: {attr_names}")
|
|
56
|
+
|
|
57
|
+
# Try reading an attribute
|
|
58
|
+
if attr_names:
|
|
59
|
+
for name in attr_names:
|
|
60
|
+
print(f" - Reading attribute '{name}':")
|
|
61
|
+
value = hdf.attr(name).get()
|
|
62
|
+
print(f" Value: {value}")
|
|
63
|
+
|
|
64
|
+
hdf.end()
|
|
65
|
+
print("Successfully closed test HDF file")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"Error reading test HDF file: {type(e).__name__}: {e}")
|
|
68
|
+
traceback.print_exc()
|
|
69
|
+
|
|
70
|
+
# Convert HDF4 to markdown
|
|
71
|
+
print("\nCalling hdf4_to_markdown now...")
|
|
72
|
+
markdown_content = hdf4_to_markdown(file_path_str)
|
|
73
|
+
print("hdf4_to_markdown returned successfully")
|
|
74
|
+
|
|
75
|
+
# Write markdown to file
|
|
76
|
+
with open(output_file, 'w') as f:
|
|
77
|
+
f.write(markdown_content)
|
|
78
|
+
print(f"Wrote markdown to {output_file}")
|
|
79
|
+
|
|
80
|
+
# Read and verify the markdown content
|
|
81
|
+
with open(output_file) as f:
|
|
82
|
+
content = f.read()
|
|
83
|
+
print(f"\nGenerated markdown content:\n{content[:200]}...")
|
|
84
|
+
|
|
85
|
+
# Check for expected content
|
|
86
|
+
print("\nVerifying markdown content...")
|
|
87
|
+
assert "# HDF4 File: test.hdf" in content, "Missing file name in header"
|
|
88
|
+
assert "Test Dataset" in content, "Missing 'Test Dataset' in content"
|
|
89
|
+
assert "Sample HDF4 file for testing" in content, "Missing description"
|
|
90
|
+
assert "sample_data" in content, "Missing dataset name"
|
|
91
|
+
assert "meters" in content, "Missing units attribute"
|
|
92
|
+
assert "(2, 3)" in content, "Missing shape information"
|
|
93
|
+
print("All assertions passed successfully!")
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f"\nEXCEPTION IN TEST: {type(e).__name__}: {e}")
|
|
97
|
+
traceback.print_exc()
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
def test_nonexistent_file():
|
|
101
|
+
"""Test handling of non-existent file."""
|
|
102
|
+
with pytest.raises(Exception):
|
|
103
|
+
hdf4_to_markdown("nonexistent.hdf")
|
|
104
|
+
|
|
105
|
+
def test_invalid_hdf4_file(tmp_path):
|
|
106
|
+
"""Test handling of invalid HDF4 file."""
|
|
107
|
+
invalid_file = tmp_path / "invalid.hdf"
|
|
108
|
+
with open(invalid_file, 'w') as f:
|
|
109
|
+
f.write("Not an HDF4 file")
|
|
110
|
+
|
|
111
|
+
with pytest.raises(Exception):
|
|
112
|
+
hdf4_to_markdown(str(invalid_file))
|