import re
from enum import Enum, auto
from pathlib import Path
from textwrap import wrap
from typing import List, Dict, Any
from iode.iode_cython import cython_is_NA, cython_suppress_msgs, cython_enable_msgs, IodeFileType, IodeType
from .common import IODE_FILE_TYPES
# import constants, functions and classes hidden from users but maybe useful for developers
# (for the GUI for example)
from iode.iode_cython import (IODE_DEFAULT_DATABASE_FILENAME, register_super_function, c_api_error_as_exception,
set_printer_preferences, set_A2M_preferences, set_MIF_preferences, set_RTF_preferences,
set_HTML_preferences)
from .common import FileType
_list_separator = r",;\s"
[docs]
def is_NA(value: float) -> bool:
"""
Check whether a float value represents a valid IODE number or an IODE *Not Available*
:math:`NA` value.
Parameters
----------
value: float
Returns
-------
bool
True if the float value represents an IODE *Not Available* :math:`NA` value.
Examples
--------
>>> from iode import NA, is_NA
>>> is_NA(1.0)
False
>>> is_NA(NA)
True
"""
if isinstance(value, int):
value = float(value)
if not isinstance(value, float):
raise TypeError("Expected value of type float. "
f"Got value of type {type(value).__name__} instead.")
return cython_is_NA(value)
def iode_number_to_str(value: float) -> str:
return "na" if is_NA(value) else f"{value:g}"
[docs]
def suppress_msgs():
"""
Suppress the output during an IODE session
"""
cython_suppress_msgs()
[docs]
def enable_msgs():
"""
Reset the normal output mechanism during an IODE session
"""
cython_enable_msgs()
[docs]
def split_list(list_txt: str):
"""
Split an IODE list written as a string and return a Python list.
By default, the delimiters are ``, ;`` and any ``whitespace``.
Parameters
----------
list_txt: str
IODE list written as a string.
Returns
-------
list(str)
Examples
--------
>>> from iode import split_list
>>> well_written_list = "A;B;C;D;E;F;G;H"
>>> split_list(well_written_list)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
>>> badly_written_list = " ,A,B C;D E,,,F;, G;H, "
>>> split_list(badly_written_list)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
"""
if not isinstance(list_txt, str):
raise TypeError(f"Expected value of type str. Got value of type {type(list_txt).__name__} instead.")
splitted_list = re.split(f"[{_list_separator}]+", list_txt.strip())
if splitted_list[0] == '':
splitted_list = splitted_list[1:]
if len(splitted_list) and splitted_list[-1] == '':
splitted_list = splitted_list[:-1]
return splitted_list
def join_lines(value: str) -> str:
return ' '.join(line.strip() for line in value.splitlines())
class JUSTIFY(Enum):
LEFT = auto()
CENTER = auto()
RIGHT = auto()
def table2str(columns: Dict[str, List[str]], sep: str = '\t', justify_funcs: Dict[str, JUSTIFY] = None,
max_lines: int = -1, edgeitems: int = 5, max_width_col: int = 100, max_width: int = 200,
precision: int = 4) -> str:
r"""
Return table (dict of columns) as string
Parameters
----------
columns: dict(str, list(str))
Pairs (column name, column values) of the table to return as string.
sep: str, optional
Separator between columns.
Defaults to '\t'.
justify_funcs: dict[str, JUSTIFY], optional
Justify function to apply on string representation of column values.
Defaults to RIGHT for all columns.
max_lines: int, optional
Maximum number of lines to show.
Defaults to -1 (show all lines).
edgeitems : int, optional
If number of lines to display is greater than max_lines, only the first and
last edgeitems lines are displayed. Only active if max_lines is not -1.
Defaults to 5.
max_width_col: int, optional
Maximum width (= nb characters) for any column.
Defaults to 100.
max_width: int, optional
Maximum width (= nb characters) for lines to show.
A value of -1 means unlimited width.
Defaults to 200.
precision: int, optional
Precision of the string representation of the float values to show.
Defaults to 4.
Returns
-------
str
Examples
--------
>>> from iode.util import table2str, JUSTIFY
>>> columns = {'category_1': [23.9010175, 18.07965729, 95.38763094, 7.605285, 45.77738378, 31.20329029, 26.24638991, 94.85772881, 17.19156602, 86.97879573],
... 'category_2': [39.52903949, 91.1525069, 83.76037566, 43.83771813, 58.17427187, 80.56636471, 76.80209378, 53.63765832, 15.94267299, 88.42504629],
... 'category_3': [64.36370439, 53.8316863, 49.02940035, 61.91318117, 25.01676303, 7.52817659, 9.18225827, 16.68777191, 46.39337746, 27.30904207],
... 'category_4': [92.90886005, 98.98271845, 16.87014647, 52.23406945, 75.85269188, 68.53109561, 87.69120031, 6.19099948, 57.21632568, 13.4801123],
... 'category_5': [55.99231002, 29.0795402, 6.06556571, 15.85292046, 66.87820451, 61.1155681, 16.17608971, 49.67034014, 29.84330636, 73.399402],
... 'category_6': [84.74263881, 86.74429788, 14.59513443, 21.0325867, 81.49218407, 0.09002046, 6.96791193, 84.88923794, 82.1819903, 1.07949955],
... 'category_7': [80.65553154, 97.73020615, 27.59011683, 47.88146724, 86.22525403, 71.41743477, 67.53744625, 75.27254816, 92.51563273, 87.1841808],
... 'category_8': [81.92785908, 49.47791145, 95.31219267, 78.94364085, 62.62581941, 29.65099761, 23.61973127, 81.37426694, 62.02338724, 73.65105918],
... 'category_9': [18.58065375, 58.86699547, 28.93429997, 40.62139687, 47.13588669, 3.0366871, 70.22586303, 16.8850943, 3.34322589, 8.02234255],
... 'category_10': [31.39205972, 63.99854372, 72.89885168, 76.08958234, 40.29562424, 19.04340988, 24.46528084, 62.13187458, 71.05022862, 58.76206382]}
>>> s = table2str(columns)
>>> print(s) # doctest: +NORMALIZE_WHITESPACE
category_1 category_2 category_3 category_4 category_5 category_6 category_7 category_8 category_9 category_10
23.9010 39.5290 64.3637 92.9089 55.9923 84.7426 80.6555 81.9279 18.5807 31.3921
18.0797 91.1525 53.8317 98.9827 29.0795 86.7443 97.7302 49.4779 58.8670 63.9985
95.3876 83.7604 49.0294 16.8701 6.0656 14.5951 27.5901 95.3122 28.9343 72.8989
7.6053 43.8377 61.9132 52.2341 15.8529 21.0326 47.8815 78.9436 40.6214 76.0896
45.7774 58.1743 25.0168 75.8527 66.8782 81.4922 86.2253 62.6258 47.1359 40.2956
31.2033 80.5664 7.5282 68.5311 61.1156 0.0900 71.4174 29.6510 3.0367 19.0434
26.2464 76.8021 9.1823 87.6912 16.1761 6.9679 67.5374 23.6197 70.2259 24.4653
94.8577 53.6377 16.6878 6.1910 49.6703 84.8892 75.2725 81.3743 16.8851 62.1319
17.1916 15.9427 46.3934 57.2163 29.8433 82.1820 92.5156 62.0234 3.3432 71.0502
86.9788 88.4250 27.3090 13.4801 73.3994 1.0795 87.1842 73.6511 8.0223 58.7621
<BLANKLINE>
>>> s = table2str(columns, max_lines=6, edgeitems=3)
>>> print(s) # doctest: +NORMALIZE_WHITESPACE
category_1 category_2 category_3 category_4 category_5 category_6 category_7 category_8 category_9 category_10
23.9010 39.5290 64.3637 92.9089 55.9923 84.7426 80.6555 81.9279 18.5807 31.3921
18.0797 91.1525 53.8317 98.9827 29.0795 86.7443 97.7302 49.4779 58.8670 63.9985
95.3876 83.7604 49.0294 16.8701 6.0656 14.5951 27.5901 95.3122 28.9343 72.8989
... ... ... ... ... ... ... ... ... ...
94.8577 53.6377 16.6878 6.1910 49.6703 84.8892 75.2725 81.3743 16.8851 62.1319
17.1916 15.9427 46.3934 57.2163 29.8433 82.1820 92.5156 62.0234 3.3432 71.0502
86.9788 88.4250 27.3090 13.4801 73.3994 1.0795 87.1842 73.6511 8.0223 58.7621
<BLANKLINE>
>>> s = table2str(columns, sep=' | ')
>>> print(s) # doctest: +NORMALIZE_WHITESPACE
category_1 | category_2 | category_3 | category_4 | category_5 | category_6 | category_7 | category_8 | category_9 | category_10
23.9010 | 39.5290 | 64.3637 | 92.9089 | 55.9923 | 84.7426 | 80.6555 | 81.9279 | 18.5807 | 31.3921
18.0797 | 91.1525 | 53.8317 | 98.9827 | 29.0795 | 86.7443 | 97.7302 | 49.4779 | 58.8670 | 63.9985
95.3876 | 83.7604 | 49.0294 | 16.8701 | 6.0656 | 14.5951 | 27.5901 | 95.3122 | 28.9343 | 72.8989
7.6053 | 43.8377 | 61.9132 | 52.2341 | 15.8529 | 21.0326 | 47.8815 | 78.9436 | 40.6214 | 76.0896
45.7774 | 58.1743 | 25.0168 | 75.8527 | 66.8782 | 81.4922 | 86.2253 | 62.6258 | 47.1359 | 40.2956
31.2033 | 80.5664 | 7.5282 | 68.5311 | 61.1156 | 0.0900 | 71.4174 | 29.6510 | 3.0367 | 19.0434
26.2464 | 76.8021 | 9.1823 | 87.6912 | 16.1761 | 6.9679 | 67.5374 | 23.6197 | 70.2259 | 24.4653
94.8577 | 53.6377 | 16.6878 | 6.1910 | 49.6703 | 84.8892 | 75.2725 | 81.3743 | 16.8851 | 62.1319
17.1916 | 15.9427 | 46.3934 | 57.2163 | 29.8433 | 82.1820 | 92.5156 | 62.0234 | 3.3432 | 71.0502
86.9788 | 88.4250 | 27.3090 | 13.4801 | 73.3994 | 1.0795 | 87.1842 | 73.6511 | 8.0223 | 58.7621
<BLANKLINE>
>>> s = table2str(columns, max_width=100)
>>> print(s) # doctest: +NORMALIZE_WHITESPACE
category_1 category_2 category_3 category_4 ... category_7 category_8 category_9 category_10
23.9010 39.5290 64.3637 92.9089 ... 80.6555 81.9279 18.5807 31.3921
18.0797 91.1525 53.8317 98.9827 ... 97.7302 49.4779 58.8670 63.9985
95.3876 83.7604 49.0294 16.8701 ... 27.5901 95.3122 28.9343 72.8989
7.6053 43.8377 61.9132 52.2341 ... 47.8815 78.9436 40.6214 76.0896
45.7774 58.1743 25.0168 75.8527 ... 86.2253 62.6258 47.1359 40.2956
31.2033 80.5664 7.5282 68.5311 ... 71.4174 29.6510 3.0367 19.0434
26.2464 76.8021 9.1823 87.6912 ... 67.5374 23.6197 70.2259 24.4653
94.8577 53.6377 16.6878 6.1910 ... 75.2725 81.3743 16.8851 62.1319
17.1916 15.9427 46.3934 57.2163 ... 92.5156 62.0234 3.3432 71.0502
86.9788 88.4250 27.3090 13.4801 ... 87.1842 73.6511 8.0223 58.7621
<BLANKLINE>
>>> s = table2str(columns, precision=2)
>>> print(s) # doctest: +NORMALIZE_WHITESPACE
category_1 category_2 category_3 category_4 category_5 category_6 category_7 category_8 category_9 category_10
23.90 39.53 64.36 92.91 55.99 84.74 80.66 81.93 18.58 31.39
18.08 91.15 53.83 98.98 29.08 86.74 97.73 49.48 58.87 64.00
95.39 83.76 49.03 16.87 6.07 14.60 27.59 95.31 28.93 72.90
7.61 43.84 61.91 52.23 15.85 21.03 47.88 78.94 40.62 76.09
45.78 58.17 25.02 75.85 66.88 81.49 86.23 62.63 47.14 40.30
31.20 80.57 7.53 68.53 61.12 0.09 71.42 29.65 3.04 19.04
26.25 76.80 9.18 87.69 16.18 6.97 67.54 23.62 70.23 24.47
94.86 53.64 16.69 6.19 49.67 84.89 75.27 81.37 16.89 62.13
17.19 15.94 46.39 57.22 29.84 82.18 92.52 62.02 3.34 71.05
86.98 88.43 27.31 13.48 73.40 1.08 87.18 73.65 8.02 58.76
<BLANKLINE>
>>> columns = {'names': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank', 'Grace', 'Hannah', 'Ivy', 'Jack'],
... 'values': ['Tempora amet voluptatem sed modi. Tempora est non est. Voluptatem velit eius modi quaerat. Quaerat quaerat dolorem sed adipisci. Numquam neque sit quiquia. Sit tempora quiquia',
... 'Numquam porro quiquia est sed labore. Sit neque modi dolor voluptatem. Quisquam amet aliquam quiquia quiquia. Voluptatem dolore dolor',
... 'Ipsum dolorem quaerat tempora ipsum adipisci quiquia. Magnam sit velit ut ut sit dolorem. Etincidunt sit sed velit tempora. Sit velit ipsum sit modi.',
... 'Numquam adipisci est tempora tempora porro. Sit numquam porro dolore sit adipisci velit est. Sed magnam adipisci voluptatem',
... 'Numquam dolore aliquam quaerat non dolore magnam non. Eius est sed aliquam dolore voluptatem consectetur. Modi sed labore amet sit sit',
... 'Quiquia ut consectetur voluptatem est. Quaerat porro consectetur adipisci est modi dolore. Ipsum modi ipsum',
... 'Etincidunt numquam non sit etincidunt. Eius labore dolore neque quiquia consectetur dolorem sit. Porro labore adipisci labore ut. Dolor magnam aliquam ipsum ipsum ut eius non.',
... 'Porro quisquam sit quiquia. Dolor ut sed modi magnam voluptatem quisquam. Consectetur',
... 'Modi sit amet est etincidunt quisquam. Amet amet quaerat eius adipisci quiquia numquam modi. Labore non labore amet ut. Adipisci voluptate',
... 'Aliquam magnam quiquia amet amet non est. Velit quaerat tempora etincidunt sit magnam dolore']}
>>> s = table2str(columns, justify_funcs={"names": JUSTIFY.LEFT, "values": JUSTIFY.LEFT})
>>> print(s) # doctest: +NORMALIZE_WHITESPACE
names values
Alice Tempora amet voluptatem sed modi. Tempora est non est. Voluptatem velit eius modi quaerat. Quaerat
quaerat dolorem sed adipisci. Numquam neque sit quiquia. Sit tempora quiquia
Bob Numquam porro quiquia est sed labore. Sit neque modi dolor voluptatem. Quisquam amet aliquam quiquia
quiquia. Voluptatem dolore dolor
Charlie Ipsum dolorem quaerat tempora ipsum adipisci quiquia. Magnam sit velit ut ut sit dolorem. Etincidunt
sit sed velit tempora. Sit velit ipsum sit modi.
David Numquam adipisci est tempora tempora porro. Sit numquam porro dolore sit adipisci velit est. Sed
magnam adipisci voluptatem
Eve Numquam dolore aliquam quaerat non dolore magnam non. Eius est sed aliquam dolore voluptatem
consectetur. Modi sed labore amet sit sit
Frank Quiquia ut consectetur voluptatem est. Quaerat porro consectetur adipisci est modi dolore. Ipsum
modi ipsum
Grace Etincidunt numquam non sit etincidunt. Eius labore dolore neque quiquia consectetur dolorem sit.
Porro labore adipisci labore ut. Dolor magnam aliquam ipsum ipsum ut eius non.
Hannah Porro quisquam sit quiquia. Dolor ut sed modi magnam voluptatem quisquam. Consectetur
Ivy Modi sit amet est etincidunt quisquam. Amet amet quaerat eius adipisci quiquia numquam modi. Labore
non labore amet ut. Adipisci voluptate
Jack Aliquam magnam quiquia amet amet non est. Velit quaerat tempora etincidunt sit magnam dolore
<BLANKLINE>
"""
def format_value(value: Any) -> str:
if isinstance(value, float):
return f"{value:.{precision}f}" if not is_NA(value) else "na"
else:
value = str(value).strip()
return join_lines(value)
justify_funcs_ = dict()
if justify_funcs is not None:
for key, value in justify_funcs.items():
if value == JUSTIFY.LEFT:
justify_funcs_[key] = str.ljust
elif value == JUSTIFY.CENTER:
justify_funcs_[key] = str.center
else:
justify_funcs_[key] = str.rjust
cont = '...'
nb_columns = len(columns)
column_names = list(columns.keys())
# check that all columns have the same length
column_length = len(columns[column_names[0]])
if not all(len(column) == column_length for column in columns.values()):
raise ValueError("All columns must have the same length. Got columns of lengths (" +
', '.join(f"{name}: {len(column)}" for name, column in columns.items()) + ")")
# skip lines ?
if max_lines != -1 and column_length > max_lines:
columns_ = dict()
# replace middle lines of the table by '...'.
# We show only the first and last edgeitems lines.
for name, column in columns.items():
columns_[name] = column[:edgeitems] + [cont] + column[-edgeitems:]
else:
columns_ = columns
# prepare table (rows = columns) and format values
table = []
for name, column in columns_.items():
column = [format_value(value) for value in column]
table += [[name] + column]
# transpose table
table = [list(row) for row in zip(*table)]
# need to wrap column ?
# note: see table2str() from larray project (original algorithm from @gdementen)
rows = []
for row in table:
wrapped_row = [wrap(value, max_width_col) for value in row]
maxlines = max(len(value) for value in wrapped_row)
newlines = [[] for _ in range(maxlines)]
for value in wrapped_row:
for i in range(maxlines):
chunk = value[i] if i < len(value) else ''
newlines[i].append(chunk)
rows.extend(newlines)
table = rows
nb_rows = len(table)
# center column names and align values
col_widths = []
for j in range(nb_columns):
column_name = table[0][j]
justify_func = justify_funcs_.get(column_name, str.rjust)
max_col_length = max(len(table[i][j]) for i in range(nb_rows))
table[0][j] = column_name.center(max_col_length)
for i in range(nb_rows):
table[i][j] = justify_func(table[i][j], max_col_length)
col_widths += [max_col_length]
# skip columns ?
# note: see table2str() from larray project (original algorithm from @gdementen)
sep_width = len(sep) * (len(columns) - 1)
if max_width != -1 and sum(col_widths) + sep_width > max_width:
nb_edges = 0
max_edges = nb_columns // 2
if max_edges:
# width of the "continuation" column (i.e. ellipsis column)
col_w = len(cont)
nb_cols = 1
# iterate like this: first_column --> <-- last_column
for i in range(1, max_edges + 1):
col_w += col_widths[i-1] + col_widths[-i]
# + 1 for the "continuation" column (i.e. ellipsis column)
nb_cols += 2
sep_w = len(sep) * (nb_cols - 1)
if col_w + sep_w > max_width:
break
nb_edges = i
if nb_edges:
table = [row[:nb_edges] + [cont] + row[-nb_edges:] for row in table]
# return table as string
s = ''
for row in table:
s += sep.join(row) + '\n'
return s
def check_file(filepath: str, file_must_exist: bool = False) -> Path:
"""
This function checks if the parent directory of the 'filepath' exists and
returns its absolute path.
If 'file_must_exist' is True, then the function checks if the 'filepath' exists.
"""
# check if empty
if not filepath:
raise ValueError("Empty filepath.")
p_filepath = Path(filepath)
# convert to absolute path
p_filepath = p_filepath.resolve()
# check if parent directory exist
p_directory = p_filepath.parent
if not p_directory.exists():
raise ValueError(f"Directory '{p_directory}' "
f"in filepath '{p_filepath}' does not exist")
if file_must_exist and not p_filepath.exists():
raise FileNotFoundError(f"The file '{p_filepath}' does not exist")
return p_filepath
def check_file_exists(filepath: str) -> Path:
"""
This function checks if the 'filepath' exists and returns its absolute path.
"""
if not filepath:
raise ValueError("Empty filepath.")
p_filepath = Path(filepath).resolve()
if not p_filepath.exists():
raise FileNotFoundError(f"The file '{p_filepath}' does not exist")
return p_filepath
def check_filepath(filepath: str, expected_file_type: IodeFileType, file_must_exist: bool) -> str:
r"""
Check the validity of a filepath and its extension.
If the filename does not contain an extension, it is added automatically according to 'expected_file_type'.
If the (modified) filepath is valid, it is returned.
Otherwise, an error is raised.
filepath: str
The filepath to check. It can be a relative or an absolute path.
If it is a relative path, it is considered to be relative to the current working directory.
expected_file_type: IodeFileType
The expected file type. If the filepath does not contain an extension, it is added
automatically according to this parameter.
If the filepath contains an extension, it is checked against this parameter.
If the extension is not valid, an error is raised.
file_must_exist: bool
If True, the file must exist.
Returns
-------
str:
The checked filepath. If the filepath did not contain an extension, it is added automatically.
Examples
--------
>>> from pathlib import Path
>>> from iode import IodeFileType, SAMPLE_DATA_DIR
>>> from iode.util import check_filepath
>>> # No extension but an IODE objects file -> extension added automatically
>>> filepath = str(Path(SAMPLE_DATA_DIR) / "fun")
>>> checked_filepath = check_filepath(filepath, IodeFileType.FILE_COMMENTS, True)
>>> Path(checked_filepath).name
'fun.cmt'
>>> Path(checked_filepath).parent.name
'data'
>>> # any file
>>> filepath = str(Path(SAMPLE_DATA_DIR) / "fun_xode.ac.ref")
>>> checked_filepath = check_filepath(filepath, IodeFileType.FILE_ANY, True)
>>> Path(checked_filepath).name
'fun_xode.ac.ref'
>>> # wrong directory
>>> cwd = Path.cwd()
>>> fake_dir = cwd.parent / "fake_dir"
>>> filepath = str(fake_dir / "fun.cmt")
>>> check_filepath(filepath, IodeFileType.FILE_COMMENTS, False) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ValueError: Directory '...fake_dir' in filepath '...fun.cmt' does not exist
>>> # wrong extension -> expect a COMMENTS file
>>> filepath = str(Path(SAMPLE_DATA_DIR) / "fun.eqs")
>>> check_filepath(filepath, IodeFileType.FILE_COMMENTS, False)
Traceback (most recent call last):
...
ValueError: The file 'fun.eqs' has a wrong extension '.eqs'
Expected extensions are: ['.cmt', '.ac']
>>> # wrong extension
>>> filepath = str(Path(SAMPLE_DATA_DIR) / "fun.docx")
>>> check_filepath(filepath, IodeFileType.FILE_TXT, False)
Traceback (most recent call last):
...
ValueError: The file 'fun.docx' has a wrong extension '.docx'
Expected extensions are: ['.txt']
>>> # file does not exist
>>> filepath = str(Path(SAMPLE_DATA_DIR) / "funxxx.cmt")
>>> check_filepath(filepath, IodeFileType.FILE_COMMENTS, True) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
FileNotFoundError: The file '...funxxx.cmt' does not exist
>>> # file does not exist (no extension given)
>>> filepath = str(Path(SAMPLE_DATA_DIR) / "funxxx")
>>> check_filepath(filepath, IodeFileType.FILE_COMMENTS, True) # doctest: +SKIP
Traceback (most recent call last):
...
FileNotFoundError: Neither 'funxxx.cmt' nor 'funxxx.ac' could be found in directory '...'
>>> # No extension but not an IODE objects file
>>> filepath = str(Path(SAMPLE_DATA_DIR) / "fun")
>>> check_filepath(filepath, IodeFileType.FILE_TXT, True) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ValueError: You must provide an extension to the file '...fun'
"""
p_filepath: Path = check_file(filepath, False)
if p_filepath.suffix:
# check extension
ext = p_filepath.suffix
expected_extensions = IODE_FILE_TYPES[int(expected_file_type)].extensions
if not (expected_file_type == IodeFileType.FILE_ANY or ext in expected_extensions):
raise ValueError(f"The file '{p_filepath.name}' has a wrong extension '{ext}'\n"
f"Expected extensions are: {expected_extensions}")
# check if file exist
if file_must_exist and not p_filepath.exists():
raise FileNotFoundError(f"The file '{p_filepath}' does not exist")
else:
if expected_file_type > IodeFileType.FILE_VARIABLES:
raise ValueError(f"You must provide an extension to the file '{p_filepath}'")
database_type: IodeType = IodeType(expected_file_type)
# set binary format extension
binary_ext = IODE_FILE_TYPES[int(database_type)].extensions[0]
p_filepath = p_filepath.with_suffix(binary_ext)
# check if file exist
if file_must_exist:
# check first with binary format extension
if not p_filepath.exists():
# switch to ascii format extension and check if file exist
ascii_ext = IODE_FILE_TYPES[int(database_type)].extensions[1]
p_filepath = p_filepath.with_suffix(ascii_ext)
if not p_filepath.exists():
p_directory = p_filepath.parent
stem = p_filepath.stem
raise FileNotFoundError(f"Neither '{stem}{binary_ext}' nor '{stem}{ascii_ext}' "
f"could be found in directory '{p_directory}'")
return str(p_filepath)
def get_iode_file_type(filepath: str) -> IodeFileType:
r"""
Return the IODE file type of a filepath. The filepath can be a relative or an absolute path.
Parameters
----------
filepath: str
The filepath to check. It can be a relative or an absolute path.
Returns
-------
IodeFileType
The IODE file type of the filepath.
Examples
--------
>>> from pathlib import Path
>>> from iode import IodeFileType, SAMPLE_DATA_DIR
>>> from iode.util import get_iode_file_type
>>> filename = ""
>>> get_iode_file_type(filename)
<IodeFileType.FILE_ANY: 32>
>>> filename = "ws"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_ANY: 32>
>>> get_iode_file_type(SAMPLE_DATA_DIR)
<IodeFileType.DIRECTORY: 33>
>>> filename = "fun.cmt"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_COMMENTS: 0>
>>> filename = "fun.ac"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_COMMENTS: 0>
>>> filename = "fun.eqs"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_EQUATIONS: 1>
>>> filename = "fun.ae"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_EQUATIONS: 1>
>>> filename = "fun.idt"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_IDENTITIES: 2>
>>> filename = "fun.ai"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_IDENTITIES: 2>
>>> filename = "fun.lst"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_LISTS: 3>
>>> filename = "fun.al"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_LISTS: 3>
>>> filename = "fun.scl"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_SCALARS: 4>
>>> filename = "fun.as"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_SCALARS: 4>
>>> filename = "fun.tbl"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_TABLES: 5>
>>> filename = "fun.at"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_TABLES: 5>
>>> filename = "fun.var"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_VARIABLES: 6>
>>> filename = "fun.av"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_VARIABLES: 6>
>>> filename = "fun.rep"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_REP: 16>
>>> filename = "fun.log"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_LOG: 30>
>>> filename = "fun.ini"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_SETTINGS: 31>
>>> filename = "fun.txt"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_TXT: 25>
>>> filename = "fun.a2m"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_A2M: 17>
>>> filename = "fun.agl"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_AGL: 18>
>>> filename = "fun.prf"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_PRF: 19>
>>> filename = "fun.dif"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_DIF: 20>
>>> filename = "fun.mif"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_MIF: 21>
>>> filename = "fun.rtf"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_RTF: 22>
>>> filename = "fun.asc"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_AAS: 24>
>>> filename = "fun.ref"
>>> get_iode_file_type(filename)
<IodeFileType.FILE_REF: 29>
"""
if not filepath:
return IodeFileType.FILE_ANY
p_filepath = Path(filepath)
if p_filepath.is_dir():
return IodeFileType.DIRECTORY
if not p_filepath.suffix:
return IodeFileType.FILE_ANY
ext = p_filepath.suffix
for i, file_type in enumerate(IODE_FILE_TYPES):
if ext in file_type.extensions:
return IodeFileType(i)
return IodeFileType.FILE_ANY