172 lines
4.2 KiB
Python
172 lines
4.2 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
"""
|
||
|
jishaku.repl.scope
|
||
|
~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
The Scope class and functions relating to it.
|
||
|
|
||
|
:copyright: (c) 2019 Devon (Gorialis) R
|
||
|
:license: MIT, see LICENSE for more details.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import inspect
|
||
|
import typing
|
||
|
|
||
|
|
||
|
class Scope:
|
||
|
"""
|
||
|
Class that represents a global and local scope for both scope inspection and creation.
|
||
|
|
||
|
Many REPL functions expect or return this class.
|
||
|
|
||
|
.. code:: python3
|
||
|
|
||
|
scope = Scope() # an empty Scope
|
||
|
|
||
|
scope = Scope(globals(), locals()) # a Scope imitating the current, real scope.
|
||
|
|
||
|
scope = Scope({'a': 3}) # a Scope with a pre-existing global scope key, and an empty local scope.
|
||
|
"""
|
||
|
|
||
|
__slots__ = ('globals', 'locals')
|
||
|
|
||
|
def __init__(self, globals_: dict = None, locals_: dict = None):
|
||
|
self.globals: dict = globals_ or {}
|
||
|
self.locals: dict = locals_ or {}
|
||
|
|
||
|
def clear_intersection(self, other_dict):
|
||
|
"""
|
||
|
Clears out locals and globals from this scope where the key-value pair matches
|
||
|
with other_dict.
|
||
|
|
||
|
This allows cleanup of temporary variables that may have washed up into this
|
||
|
Scope.
|
||
|
|
||
|
Parameters
|
||
|
-----------
|
||
|
other_dict: :class:`dict`
|
||
|
The dictionary to be used to determine scope clearance.
|
||
|
|
||
|
If a key from this dict matches an entry in the globals or locals of this scope,
|
||
|
and the value is identical, it is removed from the scope.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
Scope
|
||
|
The updated scope (self).
|
||
|
"""
|
||
|
|
||
|
for key, value in other_dict.items():
|
||
|
if key in self.globals and self.globals[key] is value:
|
||
|
del self.globals[key]
|
||
|
if key in self.locals and self.locals[key] is value:
|
||
|
del self.locals[key]
|
||
|
|
||
|
return self
|
||
|
|
||
|
def update(self, other):
|
||
|
"""
|
||
|
Updates this scope with the content of another scope.
|
||
|
|
||
|
Parameters
|
||
|
---------
|
||
|
other: :class:`Scope`
|
||
|
The scope to overlay onto this one.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
Scope
|
||
|
The updated scope (self).
|
||
|
"""
|
||
|
|
||
|
self.globals.update(other.globals)
|
||
|
self.locals.update(other.locals)
|
||
|
return self
|
||
|
|
||
|
def update_globals(self, other: dict):
|
||
|
"""
|
||
|
Updates this scope's globals with a dict.
|
||
|
|
||
|
Parameters
|
||
|
-----------
|
||
|
other: :class:`dict`
|
||
|
The dictionary to be merged into this scope.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
Scope
|
||
|
The updated scope (self).
|
||
|
"""
|
||
|
|
||
|
self.globals.update(other)
|
||
|
return self
|
||
|
|
||
|
def update_locals(self, other: dict):
|
||
|
"""
|
||
|
Updates this scope's locals with a dict.
|
||
|
|
||
|
Parameters
|
||
|
-----------
|
||
|
other: :class:`dict`
|
||
|
The dictionary to be merged into this scope.
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
Scope
|
||
|
The updated scope (self).
|
||
|
"""
|
||
|
|
||
|
self.locals.update(other)
|
||
|
return self
|
||
|
|
||
|
|
||
|
def get_parent_scope_from_var(name, global_ok=False, skip_frames=0) -> typing.Optional[Scope]:
|
||
|
"""
|
||
|
Iterates up the frame stack looking for a frame-scope containing the given variable name.
|
||
|
|
||
|
Returns
|
||
|
--------
|
||
|
Optional[Scope]
|
||
|
The relevant :class:`Scope` or None
|
||
|
"""
|
||
|
|
||
|
stack = inspect.stack()
|
||
|
try:
|
||
|
for frame_info in stack[skip_frames + 1:]:
|
||
|
frame = None
|
||
|
|
||
|
try:
|
||
|
frame = frame_info.frame
|
||
|
|
||
|
if name in frame.f_locals or (global_ok and name in frame.f_globals):
|
||
|
return Scope(globals_=frame.f_globals, locals_=frame.f_locals)
|
||
|
finally:
|
||
|
del frame
|
||
|
finally:
|
||
|
del stack
|
||
|
|
||
|
return None
|
||
|
|
||
|
|
||
|
def get_parent_var(name, global_ok=False, default=None, skip_frames=0):
|
||
|
"""
|
||
|
Directly gets a variable from a parent frame-scope.
|
||
|
|
||
|
Returns
|
||
|
--------
|
||
|
Any
|
||
|
The content of the variable found by the given name, or None.
|
||
|
"""
|
||
|
|
||
|
scope = get_parent_scope_from_var(name, global_ok=global_ok, skip_frames=skip_frames + 1)
|
||
|
|
||
|
if not scope:
|
||
|
return default
|
||
|
|
||
|
if name in scope.locals:
|
||
|
return scope.locals.get(name, default)
|
||
|
|
||
|
return scope.globals.get(name, default)
|