mirror of
https://github.com/GAM-team/GAM.git
synced 2025-07-08 13:43:35 +00:00
184 lines
5.7 KiB
Python
184 lines
5.7 KiB
Python
"""Common file operations."""
|
|
|
|
import io
|
|
import os
|
|
import sys
|
|
|
|
from gam import controlflow
|
|
from gam import display
|
|
from gam.var import GM_Globals
|
|
from gam.var import GM_SYS_ENCODING
|
|
from gam.var import UTF8_SIG
|
|
|
|
|
|
def _open_file(filename, mode, encoding=None, newline=None):
|
|
"""Opens a file with no error handling."""
|
|
# Determine which encoding to use
|
|
if 'b' in mode:
|
|
encoding = None
|
|
elif not encoding:
|
|
encoding = GM_Globals[GM_SYS_ENCODING]
|
|
elif 'r' in mode and encoding.lower().replace('-', '') == 'utf8':
|
|
encoding = UTF8_SIG
|
|
|
|
return open(os.path.expanduser(filename),
|
|
mode,
|
|
newline=newline,
|
|
encoding=encoding)
|
|
|
|
|
|
def open_file(filename,
|
|
mode='r',
|
|
encoding=None,
|
|
newline=None,
|
|
strip_utf_bom=False):
|
|
"""Opens a file.
|
|
|
|
Args:
|
|
filename: String, the name of the file to open, or '-' to use stdin/stdout,
|
|
to read/write, depending on the mode param, respectively.
|
|
mode: String, the common file mode to open the file with. Default is read.
|
|
encoding: String, the name of the encoding used to decode or encode the
|
|
file. This should only be used in text mode.
|
|
newline: See param description in
|
|
https://docs.python.org/3.7/library/functions.html#open
|
|
strip_utf_bom: Boolean, True if the file being opened should seek past the
|
|
UTF Byte Order Mark before being returned.
|
|
See more: https://en.wikipedia.org/wiki/UTF-8#Byte_order_mark
|
|
|
|
Returns:
|
|
The opened file.
|
|
"""
|
|
try:
|
|
if filename == '-':
|
|
# Read from stdin, rather than a file
|
|
if 'r' in mode:
|
|
return io.StringIO(str(sys.stdin.read()))
|
|
return sys.stdout
|
|
|
|
# Open a file on disk
|
|
f = _open_file(filename, mode, newline=newline, encoding=encoding)
|
|
if strip_utf_bom:
|
|
utf_bom = '\ufeff'
|
|
has_bom = False
|
|
|
|
if 'b' in mode:
|
|
has_bom = f.read(3).decode('UTF-8') == utf_bom
|
|
elif f.encoding and not f.encoding.lower().startswith('utf'):
|
|
# Convert UTF BOM into ISO-8859-1 via Bytes
|
|
utf8_bom_bytes = utf_bom.encode('UTF-8')
|
|
iso_8859_1_bom = utf8_bom_bytes.decode('iso-8859-1').encode(
|
|
'iso-8859-1')
|
|
has_bom = f.read(3).encode('iso-8859-1',
|
|
'replace') == iso_8859_1_bom
|
|
else:
|
|
has_bom = f.read(1) == utf_bom
|
|
|
|
if not has_bom:
|
|
f.seek(0)
|
|
|
|
return f
|
|
|
|
except OSError as e:
|
|
controlflow.system_error_exit(6, e)
|
|
|
|
|
|
def close_file(f, force_flush=False):
|
|
"""Closes a file.
|
|
|
|
Args:
|
|
f: The file to close
|
|
force_flush: Flush file to disk emptying Python and OS caches. See:
|
|
https://stackoverflow.com/a/13762137/1503886
|
|
|
|
Returns:
|
|
Boolean, True if the file was successfully closed. False if an error
|
|
was encountered while closing.
|
|
"""
|
|
if force_flush:
|
|
f.flush()
|
|
os.fsync(f.fileno())
|
|
try:
|
|
f.close()
|
|
return True
|
|
except OSError as e:
|
|
display.print_error(e)
|
|
return False
|
|
|
|
|
|
def read_file(filename,
|
|
mode='r',
|
|
encoding=None,
|
|
newline=None,
|
|
continue_on_error=False,
|
|
display_errors=True):
|
|
"""Reads a file from disk.
|
|
|
|
Args:
|
|
filename: String, the path of the file to open from disk, or "-" to read
|
|
from stdin.
|
|
mode: String, the mode in which to open the file.
|
|
encoding: String, the name of the encoding used to decode or encode the
|
|
file. This should only be used in text mode.
|
|
newline: See param description in
|
|
https://docs.python.org/3.7/library/functions.html#open
|
|
continue_on_error: Boolean, If True, suppresses any IO errors and returns to
|
|
the caller without any externalities.
|
|
display_errors: Boolean, If True, prints error messages when errors are
|
|
encountered and continue_on_error is True.
|
|
|
|
Returns:
|
|
The contents of the file, or stdin if filename == "-". Returns None if
|
|
an error is encountered and continue_on_errors is True.
|
|
"""
|
|
try:
|
|
if filename == '-':
|
|
# Read from stdin, rather than a file.
|
|
return str(sys.stdin.read())
|
|
|
|
with _open_file(filename, mode, newline=newline,
|
|
encoding=encoding) as f:
|
|
return f.read()
|
|
|
|
except OSError as e:
|
|
if continue_on_error:
|
|
if display_errors:
|
|
display.print_warning(e)
|
|
return None
|
|
controlflow.system_error_exit(6, e)
|
|
except (LookupError, UnicodeDecodeError, UnicodeError) as e:
|
|
controlflow.system_error_exit(2, str(e))
|
|
|
|
|
|
def write_file(filename,
|
|
data,
|
|
mode='w',
|
|
continue_on_error=False,
|
|
display_errors=True):
|
|
"""Writes data to a file.
|
|
|
|
Args:
|
|
filename: String, the path of the file to write to disk.
|
|
data: Serializable data to write to the file.
|
|
mode: String, the mode in which to open the file and write to it.
|
|
continue_on_error: Boolean, If True, suppresses any IO errors and returns to
|
|
the caller without any externalities.
|
|
display_errors: Boolean, If True, prints error messages when errors are
|
|
encountered and continue_on_error is True.
|
|
|
|
Returns:
|
|
Boolean, True if the write operation succeeded, or False if not.
|
|
"""
|
|
try:
|
|
with _open_file(filename, mode) as f:
|
|
f.write(data)
|
|
return True
|
|
|
|
except OSError as e:
|
|
if continue_on_error:
|
|
if display_errors:
|
|
display.print_error(e)
|
|
return False
|
|
else:
|
|
controlflow.system_error_exit(6, e)
|