1
1
__all__ = ["handcalc" ]
2
2
3
- from typing import Optional
4
- from functools import wraps
3
+ from typing import Optional , Callable
4
+ from functools import wraps , update_wrapper
5
5
import inspect
6
6
import innerscope
7
7
from .handcalcs import LatexRenderer
@@ -13,43 +13,112 @@ def handcalc(
13
13
left : str = "" ,
14
14
right : str = "" ,
15
15
scientific_notation : Optional [bool ] = None ,
16
- decimal_separator : str = "." ,
17
16
jupyter_display : bool = False ,
17
+ record : bool = False ,
18
18
):
19
19
def handcalc_decorator (func ):
20
- @wraps (func )
21
- def wrapper (* args , ** kwargs ):
22
- line_args = {
23
- "override" : override ,
24
- "precision" : precision ,
25
- "sci_not" : scientific_notation ,
26
- }
27
- func_source = inspect .getsource (func )
28
- cell_source = _func_source_to_cell (func_source )
29
- # use innerscope to get the values of locals, closures, and globals when calling func
30
- scope = innerscope .call (func , * args , ** kwargs )
31
- LatexRenderer .dec_sep = decimal_separator
32
- renderer = LatexRenderer (cell_source , scope , line_args )
33
- latex_code = renderer .render ()
34
- if jupyter_display :
35
- try :
36
- from IPython .display import Latex , display
37
- except ModuleNotFoundError :
38
- ModuleNotFoundError (
39
- "jupyter_display option requires IPython.display to be installed."
40
- )
41
- display (Latex (latex_code ))
42
- return scope .return_value
20
+ if record :
21
+ decorated = HandcalcsCallRecorder (
22
+ func ,
23
+ override ,
24
+ precision ,
25
+ left ,
26
+ right ,
27
+ scientific_notation ,
28
+ jupyter_display ,
29
+ )
30
+ else :
43
31
44
- # https://stackoverflow.com/questions/9943504/right-to-left-string-replace-in-python
45
- latex_code = "" .join (latex_code .replace ("\\ [" , "" , 1 ).rsplit ("\\ ]" , 1 ))
46
- return (left + latex_code + right , scope .return_value )
32
+ @wraps (func )
33
+ def decorated (* args , ** kwargs ):
34
+ line_args = {
35
+ "override" : override ,
36
+ "precision" : precision ,
37
+ "sci_not" : scientific_notation ,
38
+ }
39
+ func_source = inspect .getsource (func )
40
+ cell_source = _func_source_to_cell (func_source )
41
+ # innerscope retrieves values of locals, closures, and globals
42
+ scope = innerscope .call (func , * args , ** kwargs )
43
+ renderer = LatexRenderer (cell_source , scope , line_args )
44
+ latex_code = renderer .render ()
45
+ raw_latex_code = "" .join (
46
+ latex_code .replace ("\\ [" , "" , 1 ).rsplit ("\\ ]" , 1 )
47
+ )
48
+ if jupyter_display :
49
+ try :
50
+ from IPython .display import Latex , display
51
+ except ModuleNotFoundError :
52
+ ModuleNotFoundError (
53
+ "jupyter_display option requires IPython.display to be installed."
54
+ )
55
+ display (Latex (latex_code ))
56
+ return scope .return_value
57
+ return (left + raw_latex_code + right , scope .return_value )
47
58
48
- return wrapper
59
+ return decorated
49
60
50
61
return handcalc_decorator
51
62
52
63
64
+ class HandcalcsCallRecorder :
65
+ """
66
+ Records function calls for the func stored in .callable
67
+ """
68
+
69
+ def __init__ (
70
+ self ,
71
+ func : Callable ,
72
+ _override : str = "" ,
73
+ _precision : int = 3 ,
74
+ _left : str = "" ,
75
+ _right : str = "" ,
76
+ _scientific_notation : Optional [bool ] = None ,
77
+ _jupyter_display : bool = False ,
78
+ ):
79
+ self .callable = func
80
+ self .history = list ()
81
+ self ._override = _override
82
+ self ._precision = _precision
83
+ self ._left = _left
84
+ self ._right = _right
85
+ self ._scientific_notation = _scientific_notation
86
+ self ._jupyter_display = _jupyter_display
87
+ update_wrapper (self , func )
88
+
89
+ def __repr__ (self ):
90
+ return f"{ self .__class__ .__name__ } ({ self .callable .__name__ } , num_of_calls: { len (self .history )} )"
91
+
92
+ @property
93
+ def calls (self ):
94
+ return len (self .history )
95
+
96
+ def __call__ (self , * args , ** kwargs ):
97
+ line_args = {
98
+ "override" : self ._override ,
99
+ "precision" : self ._precision ,
100
+ "sci_not" : self ._scientific_notation ,
101
+ }
102
+ func_source = inspect .getsource (self .callable )
103
+ cell_source = _func_source_to_cell (func_source )
104
+ # innerscope retrieves values of locals, closures, and globals
105
+ scope = innerscope .call (self .callable , * args , ** kwargs )
106
+ renderer = LatexRenderer (cell_source , scope , line_args )
107
+ latex_code = renderer .render ()
108
+ raw_latex_code = "" .join (latex_code .replace ("\\ [" , "" , 1 ).rsplit ("\\ ]" , 1 ))
109
+ self .history .append ({"return" : scope .return_value , "latex" : raw_latex_code })
110
+ if self ._jupyter_display :
111
+ try :
112
+ from IPython .display import Latex , display
113
+ except ModuleNotFoundError :
114
+ ModuleNotFoundError (
115
+ "jupyter_display option requires IPython.display to be installed."
116
+ )
117
+ display (Latex (latex_code ))
118
+ return scope .return_value
119
+ return (self ._left + raw_latex_code + self ._right , scope .return_value )
120
+
121
+
53
122
def _func_source_to_cell (source : str ):
54
123
"""
55
124
Returns a string that represents `source` but with no signature, doc string,
0 commit comments