Source code for pyNastran.bdf.bdf_interface.include_file

"""
Defines utilities for parsing include files:
 - get_include_filename(card_lines, include_dir='', is_windows=None)

"""
from __future__ import annotations
import os
import ntpath
import posixpath
from typing import Optional, TYPE_CHECKING
from pathlib import Path, PurePosixPath, PureWindowsPath
from pyNastran.utils import print_bad_path, PathLike
from pyNastran.bdf.errors import EnvironmentVariableError
if TYPE_CHECKING:  # pragma: no cover
    from cpylog import SimpleLogger

IS_WINDOWS = 'nt' in os.name
#is_linux = 'posix' in os.name
#is_mac = 'darwin' in os.name


[docs] def get_include_filename(log: SimpleLogger, include_lines: list[str], include_dirs: list[str], replace_includes: dict[str, str], source_filename: str='', is_windows: Optional[bool]=None, debug: bool=False, write_env_on_error: bool=False) -> str: """ Parses an INCLUDE file split into multiple lines (as a list). Parameters ---------- include_lines : list[str] the list of lines in the include card (all the lines!) include_dirs : list[str] the include directory is_windows: Optional[bool] None: set it dynamically bool: Windows/Linux/Mac have different enviornment variable forms (%PATH% for Windows and $PATH for Linux/Mac). There are also different characters that are allowed in paths. Returns ------- filename : str the INCLUDE filename """ for line in include_lines: if len(line) > 72: msg = '\n - '.join(include_lines) msg += f'\nsource_filename: {source_filename!r}' log.warning(f'INCLUDE line={line!r} is too long (n={len(line)} for:\n - {msg}') #print(f'card_lines={card_lines};\nsource_filename={source_filename!r}') if not isinstance(include_dirs, list): assert isinstance(include_dirs, PathLike), include_dirs #if str(include_dirs) == '': #include_dirs = os.getcwd() include_dirs = [include_dirs] if is_windows is None: is_windows = IS_WINDOWS filename_raw = parse_include_lines(include_lines) if filename_raw in replace_includes: filename_raw = replace_includes[filename_raw] if len(filename_raw) == 0: return filename_raw ninclude_dirs = len(include_dirs) for include_dir in include_dirs: #print(f'filename_raw = {filename_raw}') #print(f'dir = {include_dir}') tokens = split_filename_into_tokens( include_dir, filename_raw, is_windows, debug=debug) filename = str(tokens) if os.path.exists(filename): if debug: print(f'found {filename}') break elif debug: print(f'could not find {filename}') print(print_bad_path(filename)) if ninclude_dirs == 1: break else: msg = f'Could not find INCLUDE file:\n{include_lines}\n' msg += f' filename: {os.path.abspath(filename_raw)}\n' if source_filename: msg += (f' source file: {os.path.abspath(source_filename)}\n'\ f'\n{print_bad_path(filename_raw)}\n\n') msg += f' include_dirs:\n - ' + '\n - '.join(repr(val) for val in include_dirs) + '\n' if write_env_on_error: msg += ' environment:' skip_keys = [ 'LESSOPEN', 'LOGNAME', 'LS_COLORS', 'MAIL', 'NAME', 'XDG_SESSION_CLASS', 'XDG_DATA_DIRS', 'XDG_RUNTIME_DIR', 'XDG_SESSION_ID', 'XDG_SESSION_TYPE', 'DISPLAY', 'LANG', 'HOSTTYPE', 'MOTD_SHOWN', 'DEBUGINFOD_URLS', 'DISPLAY', 'USER', 'TERM', 'WSL_DISTRO_NAME', 'WSL_INTEROP', 'WT_PROFILE_ID', 'WT_SESSION', 'WSL2_GUI_APPS_ENABLED', 'WAYLAND_DISPLAY', 'PULSE_SERVER', ] for key, value in sorted(os.environ.items()): if key in skip_keys: continue msg += f' {key}: {value!r}\n' #else: #msg += f' environment_keys: {list(key for key in os.environ)}' raise IOError(msg) return filename
[docs] def parse_include_lines(card_lines: list[str]) -> str: """handles splitting out the INCLUDE lines""" card_lines2 = [] #print(f'card_lines = {card_lines}') for line in card_lines: line = line.strip('\t\r\n ') card_lines2.append(line) # combine the lines # ----------------- # initial: # INCLUDE '/dir123 # /dir456 # /dir789/filename.dat' # # final (not the extra single quotes): # "'/dir123/dir456/dir789/filename.dat'" card_lines2[0] = card_lines2[0][7:].strip() # remove the INCLUDE filename = ''.join(card_lines2) #print(f'card_lines2 = {card_lines2}') # drop the single quotes: # "'path1/path2/model.inc'" to "path1/path2/model.inc" filename = filename.strip('"').strip("'") if len(filename.strip()) == 0: raise SyntaxError(f'INCLUDE file is empty...card_lines={card_lines}\n' 'there is a $ sign in the INCLUDE card') # not handled... # include '/mydir' /level1 /level2/ 'myfile.x' # # -> /proj/dept123/sect 456/joe/flange.bdf # probably not handled... # include c:\project, # $ A comment line # '\Data Files' \subdir\thisfile # # -> C:\PROJECT\Data Files\SUBDIR\THISFILE return filename
[docs] def split_filename_into_tokens(include_dir: str, filename: str, is_windows: bool, debug: bool=False) -> Path: r""" Tokens are the individual components of paths Invalid Linux Tokens '\0' (NUL) Invalid Windows Tokens < (less than) > (greater than) : (colon - sometimes works, but is actually NTFS Alternate Data Streams) " (double quote) / (forward slash) \ (backslash) | (vertical bar or pipe) ? (question mark) * (asterisk) All control codes (<= 31) """ if is_windows: inc = PureWindowsPath(include_dir) pth = PureWindowsPath(filename).parts # fails if the comment has stripped out the file # (e.g., "INCLUDE '$ENV/model.bdf'") pth0 = pth[0] # Linux style paths # /work/model.bdf if len(pth0) == 1 and pth0[0] == '\\': # utterly breaks os.path.join raise SyntaxError(f'filename={filename!r} cannot start with / on Windows') else: inc = PurePosixPath(include_dir) pth = PurePosixPath(filename).parts # fails if the comment has stripped out the file (e.g., "INCLUDE '$ENV/model.bdf'") pth0 = pth[0] if len(pth0) >= 2 and pth0[:2] == r'\\': # Windows network paths # \\nas3\work\model.bdf raise SyntaxError(f'filename={filename!r} cannot start with \\\\ on Linux') pth2 = split_tokens(pth, is_windows, debug=debug) if is_windows: pth3 = ntpath.join(*pth2) else: pth3 = posixpath.join(*pth2) pth_out = inc / pth3 return pth_out
[docs] def split_tokens(tokens: tuple[str], is_windows: bool, debug: bool=False) -> list[str]: """converts a series of path tokens into a joinable path""" tokens2: list[str] = [] is_mac_linux = not is_windows for itoken, token in enumerate(tokens): # this is technically legal... # INCLUDE '/testdir/dir1/dir2/*/myfile.dat' assert '*' not in token, f'* in path not supported; tokens={tokens}' if is_windows: assert '$' not in token, f'$ in path not supported; tokens={tokens}' else: assert '%' not in token, f'%% in path not supported; tokens={tokens}' if itoken == 0 and is_mac_linux and ':' in token: tokensi, stokens = split_drive_token( itoken, tokens, is_windows, debug=debug) tokens2.extend(tokensi) tokens2.append(stokens[1]) elif ':' in token: # Logical symbols provide you with a way of specifying file # locations with a convenient shorthand. This feature also allows # input files containing filename specifications to be moved # between computers without requiring modifications to the input # files. Only the logical symbol definitions that specify actual # file locations need to be modified. # Windows # this has an environment variable or a drive letter stokens = token.split(':') if len(stokens[0]) == 1: # this is a drive letter if len(stokens[1]) not in [0, 1]: raise SyntaxError( f'tokens={tokens!r} token={token!r} stokens={stokens} ' f'stokens[1]={stokens[1]!r}; len={len(stokens[1]):d}') # drive letter if itoken != 0: raise SyntaxError('the drive letter is in the wrong place; ' f'itoken={itoken:d}; token={token!r}; ' f'stokend={stokens}; tokens={tokens}') tokens2.append(token) else: # variables in Windows are not case sensitive; not handled? environment_vars_to_expand = stokens[:-1] if len(environment_vars_to_expand) != 1: raise SyntaxError( 'Only 1 environment variable can be expanded; ' f'environment_vars_to_expand = {environment_vars_to_expand!r}') for env_var in environment_vars_to_expand: if env_var.strip('$ %') not in os.environ: environment_variables = list(os.environ.keys()) environment_variables.sort() raise EnvironmentVariableError( f"Can't find environment variable={repr(env_var)}\n" f'environ={environment_variables}\n' f'which is required for {repr(tokens)}') env_vari = os.path.expandvars('$' + env_var.strip('%')) if debug: print(f'expanded env {env_var!r} -> {env_vari!r}') if '$' in env_vari: raise SyntaxError(f'env_vari={env_vari!r} has a $ in it after expanding (token0={env_var!r})...\n' f'tokens={tokens} stokens={stokens}') if is_windows: tokensi = PureWindowsPath(env_vari).parts else: tokensi = PurePosixPath(env_vari).parts tokens2.extend(tokensi) tokens2.append(stokens[-1]) else: # standard tokens2.append(token) if debug: print(f' split_tokens(is_windows={is_windows}):') print(f' tokens: {tokens}') print(f' tokens2: {tokens2}') return tokens2
[docs] def split_drive_token(itoken: int, tokens: tuple[str, ...], is_windows: bool, debug: bool=False) -> tuple[tuple[str, ...], list[str]]: """ splits the drive off: C:/work/file.bdf (Windows) ENVVAR:file.bdf (Windows/Linux) """ ## no C:/dir/model.bdf on linux/mac # raise SyntaxError('token cannot include colons (:); token=%r; tokens=%s' % ( # token, str(tokens))) # this has an environment variable or a drive letter # print(token) assert isinstance(tokens, tuple), tokens token = tokens[itoken] stokens = token.split(':') nstokens = len(stokens) token0 = stokens[0] if nstokens != 2: msg = f'len(stokens)={nstokens:d} must be 2; stokens={stokens}' raise SyntaxError(msg) if len(token0) == 1: if len(stokens[1]) not in [0, 1]: msg = (f'tokens={tokens} tokens[{itoken:d}={token!r} stokens={stokens} ' f'stoken[1]={stokens[1]!r}; len={len(stokens[1]):d}') raise SyntaxError(msg) if len(token0) < 2: raise SyntaxError('token cannot include colons (:); ' f'token={token!r}; tokens={tokens}') # variables in Windows are not case sensitive; not handled? if is_windows: assert '$' not in token0, token0 assert '%' not in token0, token0 if '%' in token0: assert token0[0] == '%', token0 assert token0[-1] == '%', token0 token0 = '%' + token0 + '%' else: token0 = '$' + token0 # tokeni = os.path.expandvars('$' + token0) tokeni = os.path.expandvars(token0) if debug: print(f'expanded env {token0!r} -> {tokeni!r}') if '$' in tokeni: raise SyntaxError(f'tokeni={tokeni!r} has a $ in it after ' f'expanding (token0={token0!r})...\n' f'tokens={tokens} stokens={stokens}') tokensi = PureWindowsPath(tokeni).parts else: if '$' in token0: assert token0[0] == '$', token0 else: token0 = '$' + token0 assert '%' not in stokens[0], token0 tokeni = os.path.expandvars(token0) if debug: print(f'expanded env {token0!r} -> {tokeni!r}') tokensi = PurePosixPath(tokeni).parts assert isinstance(tokensi, tuple), type(tokensi) assert isinstance(stokens, list), type(stokens) return tokensi, stokens