"""
editors_helpers.py
------------------
The functions in this module have been created so that user editors/IDEs
could use Friendly without having to change the content of
their own programs.
None of these are part of the public API.
If you make use of any other function here, please file an issue so
it can be determined if it should be added to the public API.
"""
import sys
from typing import Any, Dict, Tuple, Union
from .config import session
from .ft_gettext import current_lang
from .source_cache import cache
[docs]def check_syntax(
*, source=None, filename="Fake_filename", path=None, include=None, lang=None
) -> Union[bool, Tuple[Any, str]]:
"""This uses Python's ``compile()`` builtin which does some analysis of
its code argument and will raise an exception if it identifies
some syntax errors, but also some less common "overflow" and "value"
errors.
Note that there are a few syntax errors that are not caught by this,
as they are identified by Python very late in its execution
process. See for example
`this blog post <https://aroberge.blogspot.com/2019/12/a-tiny-python-exception-oddity.html>`_
This function can either be used on a file, using the ``path`` argument, or
on some code passed as a string, using the ``source`` argument.
For the latter case, one can also specify a corresponding ``filename``:
this could be useful if this function is invoked from a GUI-based
editor.
Note that the ``path`` argument, if provided, takes precedence
over the ``source`` argument.
Two additional named arguments, ``include`` and ``lang``, can be
provided to temporarily set the values to be used during this function
call. The original values are restored at the end.
If friendly exception hook has not been set up prior
to calling check_syntax, it will only be used for the duration
of this function call.
Returns a tuple containing a code object and a filename if no exception
has been raised, False otherwise.
"""
_ = current_lang.translate
saved_except_hook, saved_include = _save_settings()
saved_lang = _temp_set_lang(lang)
if path is not None:
try:
with open(path, encoding="utf8") as f:
source = f.read()
filename = path
except Exception: # noqa
# Do not show the Python traceback which would include
# the call to open() in the traceback
if include is None:
session.set_include("no_tb")
else:
session.set_include(include)
session.explain_traceback()
_reset(saved_except_hook, saved_lang, saved_include)
return False
cache.add(filename, source)
try:
code = compile(source, filename, "exec")
except Exception: # noqa
if include is None:
session.set_include("explain") # our default
else:
session.set_include(include)
session.explain_traceback()
_reset(saved_except_hook, saved_lang, saved_include)
return ""
_reset(saved_except_hook, saved_lang, saved_include)
return code
[docs]def exec_code(*, source=None, path=None, include=None, lang=None) -> Dict:
"""This uses check_syntax to see if the code is valid and, if so,
executes it into a globals dict containing only
``{"__name__": "__main__"}``.
If no ``SyntaxError`` exception is raised, this dict is returned;
otherwise, an empty dict is returned.
It can either be used on a file, using the ``path`` argument, or
on some code passed as a string, using the ``source`` argument.
Note that the ``path`` argument, if provided, takes precedence
over the ``source`` argument.
Two additional named arguments, ``include`` and ``lang``, can be
provided to temporarily set the values to be used during this function
call. The original values are restored at the end.
If friendly exception hook has not been set up prior
to calling check_syntax, it will only be used for the duration
of this function call.
"""
code = check_syntax(source=source, path=path, include=include, lang=lang)
if not code:
return {}
saved_except_hook, saved_include = _save_settings()
saved_lang = _temp_set_lang(lang)
module_globals = {"__name__": "__main__"}
try:
exec(code, module_globals)
except Exception: # noqa
if include is None:
session.set_include("explain") # our default
else:
session.set_include(include)
session.explain_traceback()
_reset(saved_except_hook, saved_lang, saved_include)
return module_globals
_reset(saved_except_hook, saved_lang, saved_include)
return module_globals
def _temp_set_lang(lang: str) -> str:
"""If lang is not none, temporarily set session.lang to the provided
value. Keep track of the original lang setting and return it.
A value of None for saved_lang indicates that no resetting will
be required.
"""
saved_lang = None
if lang is not None:
saved_lang = session.lang
if saved_lang != lang:
session.install_gettext(lang)
else:
saved_lang = None
return saved_lang
def _save_settings():
current_except_hook = sys.excepthook
current_include = session.include
return current_except_hook, current_include
def _reset(saved_except_hook, saved_lang, saved_include):
"""Resets both include and lang to their original values"""
if saved_lang is not None:
session.install_gettext(saved_lang)
session.set_include(saved_include)
# set_include(0) restores sys.excepthook to sys.__excepthook__
# which might not be what is wanted. So, we reset sys.excepthook last
sys.excepthook = saved_except_hook