- commit
- f4a35784689dd2e81e71773df59e47b7d73fdbb3
- parent
- 01699e8d6b70938aae9b74f878605036e704d44b
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2024-07-05 17:21
split into separate files
Diffstat
| D | foo.py | 358 | ------------------------------------------------------------ |
| A | sheet/__init__.py | 0 | |
| A | sheet/__main__.py | 10 | ++++++++++ |
| A | sheet/csv.py | 38 | ++++++++++++++++++++++++++++++++++++++ |
| A | sheet/expression.py | 117 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | sheet/sheet.py | 150 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | sheet/term.py | 63 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 378 insertions, 358 deletions
diff --git a/foo.py b/foo.py
@@ -1,358 +0,0 @@1 -1 import csv2 -1 import re3 -1 import string4 -1 import sys5 -16 -17 -1 class ParseError(ValueError):8 -1 pass9 -110 -111 -1 class ReferenceError(ValueError):12 -1 pass13 -114 -115 -1 class ExpressionParser:16 -1 def parse_any(self, text, parsers):17 -1 for parser in parsers:18 -1 try:19 -1 return parser(text)20 -1 except ParseError:21 -1 pass22 -1 raise ParseError(f'None of the subparsers matched for {text}')23 -124 -1 def parse_re(self, text, pattern):25 -1 m = re.match(pattern, text)26 -1 if not m:27 -1 raise ParseError28 -1 return m, text[m.end():]29 -130 -1 def parse_string(self, text):31 -1 m, tail = self.parse_re(text, r'"[^"]*"')32 -1 return ('str', m[0][1:-1]), tail33 -134 -1 def parse_float(self, text):35 -1 m, tail = self.parse_re(text, r'[0-9]+\.[0-9]+')36 -1 return ('float', float(m[0])), tail37 -138 -1 def parse_int(self, text):39 -1 m, tail = self.parse_re(text, r'[0-9]+')40 -1 return ('int', int(m[0], 10)), tail41 -142 -1 def parse_ref(self, text):43 -1 m, tail = self.parse_re(text, r'\$?([A-Z]+)\$?([1-9][0-9]*)')44 -1 return ('ref', m[1] + m[2], m[0]), tail45 -146 -1 def parse_range(self, text):47 -1 ref1, tail = self.parse_ref(text)48 -1 _, tail = self.parse_re(tail, r':')49 -1 ref2, tail = self.parse_ref(tail)50 -1 return ('range', ref1, ref2), tail51 -152 -1 def parse_brace(self, text):53 -1 _, tail = self.parse_re(text, r'\(')54 -1 exp, tail = self.parse_expression(tail)55 -1 _, tail = self.parse_re(tail, r'\)')56 -1 return exp, tail57 -158 -1 def parse_add(self, text):59 -1 lhs, tail = self.parse_expression2(text)60 -1 m, tail = self.parse_re(tail, r'\s*[-+]\s*')61 -1 rhs, tail = self.parse_expression(tail)62 -1 return (m[0].strip(), lhs, rhs), tail63 -164 -1 def parse_mul(self, text):65 -1 lhs, tail = self.parse_expression3(text)66 -1 m, tail = self.parse_re(tail, r'\s*[*/]\s*')67 -1 rhs, tail = self.parse_expression2(tail)68 -1 return (m[0].strip(), lhs, rhs), tail69 -170 -1 def parse_call(self, text):71 -1 m, tail = self.parse_re(text, r'[a-zA-Z][a-zA-Z0-9]*')72 -1 _, tail = self.parse_re(tail, r'\(')73 -1 args = []74 -1 if tail.startswith(')'):75 -1 return (m[0], args), tail[1:]76 -1 while True:77 -1 arg, tail = self.parse_expression(tail)78 -1 args.append(arg)79 -1 if tail.startswith(')'):80 -1 return (m[0], args), tail[1:]81 -1 _, tail = self.parse_re(tail, r',\s*')82 -1 raise ParseError83 -184 -1 def parse_expression3(self, text):85 -1 return self.parse_any(text, [86 -1 self.parse_string,87 -1 self.parse_float,88 -1 self.parse_int,89 -1 self.parse_range,90 -1 self.parse_ref,91 -1 self.parse_call,92 -1 self.parse_brace,93 -1 ])94 -195 -1 def parse_expression2(self, text):96 -1 return self.parse_any(text, [97 -1 self.parse_mul,98 -1 self.parse_expression3,99 -1 ])100 -1101 -1 def parse_expression(self, text):102 -1 return self.parse_any(text, [103 -1 self.parse_add,104 -1 self.parse_expression2,105 -1 ])106 -1107 -1 def parse(self, text):108 -1 expr, tail = self.parse_expression(text)109 -1 if tail:110 -1 raise ParseError(f'unexpected tail: {tail}')111 -1 return expr112 -1113 -1114 -1 def x2col(x):115 -1 a, b = divmod(x, len(string.ascii_uppercase))116 -1 s = string.ascii_uppercase[b]117 -1 while a:118 -1 a, b = divmod(a - 1, len(string.ascii_uppercase))119 -1 s = string.ascii_uppercase[b] + s120 -1 return s121 -1122 -1123 -1 def xy2ref(x, y):124 -1 return x2col(x) + str(y + 1)125 -1126 -1127 -1 def col2x(col):128 -1 alph = string.ascii_uppercase129 -1 x = -1130 -1 for c in col:131 -1 x = (x + 1) * len(alph) + alph.index(c)132 -1 return x133 -1134 -1135 -1 def ref2xy(ref):136 -1 m = re.match('([A-Z]*)([0-9]*)', ref)137 -1 return col2x(m[1]), int(m[2], 10) - 1138 -1139 -1140 -1 def iter_range(cell1, cell2):141 -1 x1, y1 = ref2xy(cell1)142 -1 x2, y2 = ref2xy(cell2)143 -1 if x1 > x2:144 -1 x1, x2 = x2, x1145 -1 if y1 > y2:146 -1 y1, y2 = y2, y1147 -1 for y in range(y1, y2 + 1):148 -1 for x in range(x1, x2 + 1):149 -1 yield xy2ref(x, y)150 -1151 -1152 -1 class Sheet:153 -1 def __init__(self):154 -1 self.expression_parser = ExpressionParser()155 -1 self.reset()156 -1157 -1 def reset(self):158 -1 self.raw = {}159 -1 self.parsed = {}160 -1 self.cache = {}161 -1 self.width = 0162 -1 self.height = 0163 -1164 -1 def parse(self, raw: str) -> tuple|float|int|str:165 -1 if raw.startswith('='):166 -1 try:167 -1 return self.expression_parser.parse(raw[1:])168 -1 except ParseError as err:169 -1 return ('err', err)170 -1 try:171 -1 return int(raw, 10)172 -1 except ValueError:173 -1 pass174 -1 try:175 -1 return float(raw)176 -1 except ValueError:177 -1 pass178 -1 return raw179 -1180 -1 def call_function(self, name: str, args: list[tuple]) -> float|int|str:181 -1 if name == 'sum':182 -1 if len(args) != 1 or args[0][0] != 'range':183 -1 raise ValueError(args)184 -1 _, ref1, ref2 = args[0]185 -1 return sum(186 -1 self.to_number(self.get_value(ref))187 -1 for ref in iter_range(ref1[1], ref2[1])188 -1 )189 -1 elif name == 'power':190 -1 if len(args) != 2:191 -1 raise ValueError(args)192 -1 base = self.to_number(self.evaluate(args[0]))193 -1 exp = self.to_number(self.evaluate(args[1]))194 -1 return base ** exp195 -1 else:196 -1 raise NameError(name)197 -1198 -1 def evaluate(self, expr: tuple) -> float|int|str:199 -1 if expr[0] in ['int', 'float', 'str']:200 -1 return expr[1]201 -1 elif expr[0] == 'ref':202 -1 return self.get_value(expr[1])203 -1 elif expr[0] == 'err':204 -1 raise expr[1]205 -1 elif expr[0] == '+':206 -1 return self.evaluate(expr[1]) + self.evaluate(expr[2])207 -1 elif expr[0] == '-':208 -1 return self.evaluate(expr[1]) - self.evaluate(expr[2])209 -1 elif expr[0] == '*':210 -1 return self.evaluate(expr[1]) * self.evaluate(expr[2])211 -1 elif expr[0] == '/':212 -1 return self.evaluate(expr[1]) / self.evaluate(expr[2])213 -1 else:214 -1 return self.call_function(*expr)215 -1216 -1 def set(self, cell: str, raw: str):217 -1 if raw:218 -1 self.raw[cell] = raw219 -1 self.parsed[cell] = self.parse(raw)220 -1 x, y = ref2xy(cell)221 -1 self.width = max(self.width, x + 1)222 -1 self.height = max(self.height, y + 1)223 -1 elif cell in self.raw:224 -1 del self.raw[cell]225 -1 del self.parsed[cell]226 -1 self.width = max(ref2xy(cell)[0] for cell in self.raw) + 1227 -1 self.height = max(ref2xy(cell)[1] for cell in self.raw) + 1228 -1 self.cache = {}229 -1230 -1 def get_raw(self, cell: str) -> str:231 -1 return self.raw.get(cell, '')232 -1233 -1 def get_parsed(self, cell: str) -> tuple|float|int|str|None:234 -1 return self.parsed.get(cell)235 -1236 -1 def get_value(self, cell: str) -> float|int|str|None|Exception:237 -1 parsed = self.get_parsed(cell)238 -1 if isinstance(parsed, tuple):239 -1 if cell not in self.cache:240 -1 self.cache[cell] = ReferenceError(cell)241 -1 try:242 -1 self.cache[cell] = self.evaluate(parsed)243 -1 except Exception as err:244 -1 self.cache[cell] = err245 -1 return self.cache[cell]246 -1 else:247 -1 return parsed248 -1249 -1 def to_number(self, value: float|int|str|None|Exception) -> float|int:250 -1 if isinstance(value, float):251 -1 return value252 -1 elif isinstance(value, int):253 -1 return value254 -1 elif isinstance(value, str):255 -1 raise TypeError(value)256 -1 elif value is None:257 -1 return 0258 -1 elif isinstance(value, Exception):259 -1 raise value260 -1261 -1 def to_display(self, value: float|int|str|None|Exception) -> str:262 -1 if isinstance(value, float):263 -1 return str(value)264 -1 elif isinstance(value, int):265 -1 return str(value)266 -1 elif isinstance(value, str):267 -1 return value268 -1 elif value is None:269 -1 return ''270 -1 elif isinstance(value, Exception):271 -1 return repr(value)272 -1273 -1 def render(self, value: float|int|str|None|Exception, width: int) -> str:274 -1 if isinstance(value, float):275 -1 return align_right(str(value), width)276 -1 elif isinstance(value, int):277 -1 return align_right(str(value), width)278 -1 elif isinstance(value, str):279 -1 return align_left(value, width)280 -1 elif value is None:281 -1 return ' ' * width282 -1 elif isinstance(value, Exception):283 -1 return red(align_left(repr(value), width))284 -1285 -1 def load_csv(self, fh, **kwargs):286 -1 self.raw = {}287 -1 self.parsed = {}288 -1 self.cache = {}289 -1 for y, row in enumerate(csv.reader(fh, **kwargs)):290 -1 for x, raw in enumerate(row):291 -1 ref = xy2ref(x, y)292 -1 self.set(ref, raw)293 -1294 -1 def write_csv(self, fh, *, display=False, **kwargs):295 -1 if display:296 -1 def get(cell):297 -1 return self.to_display(self.get_value(cell))298 -1 else:299 -1 get = self.get_raw300 -1301 -1 w = csv.writer(fh, **kwargs)302 -1 for y in range(self.height):303 -1 w.writerow([get(xy2ref(x, y)) for x in range(self.width)])304 -1305 -1306 -1 def align_right(s, width):307 -1 if len(s) > width:308 -1 s = '###'309 -1 return ' ' + ' ' * (width - len(s) - 1) + s310 -1311 -1312 -1 def align_left(s, width):313 -1 if len(s) > width:314 -1 s = '###'315 -1 return s + ' ' * (width - len(s))316 -1317 -1318 -1 def align_center(s, width):319 -1 if len(s) > width:320 -1 s = '###'321 -1 t = width - len(s)322 -1 return ' ' * (t // 2) + s + ' ' * (t - t // 2)323 -1324 -1325 -1 def red(s):326 -1 return f'\033[31m{s}\033[0m'327 -1328 -1329 -1 def invert(s):330 -1 return f'\033[7m{s}\033[0m'331 -1332 -1333 -1 def render(sheet, width, height, cell_offset, cell_width):334 -1 x0, y0 = ref2xy(cell_offset)335 -1 rows = []336 -1 w = width // cell_width337 -1 rows.append([338 -1 ' ' * cell_width,339 -1 ] + [340 -1 align_center(x2col(x0 + dx), cell_width)341 -1 for dx in range(w - 1)342 -1 ])343 -1 for dy in range(height - 1):344 -1 rows.append([345 -1 align_right(str(y0 + dy + 1), cell_width),346 -1 ] + [347 -1 sheet.render(sheet.get_value(xy2ref(x0 + dx, y0 + dy)), cell_width)348 -1 for dx in range(w - 1)349 -1 ])350 -1 return '\n'.join([''.join(row) for row in rows])351 -1352 -1353 -1 if __name__ == '__main__':354 -1 s = Sheet()355 -1 with open(sys.argv[1]) as fh:356 -1 s.load_csv(fh)357 -1358 -1 print(render(s, 80, 40, 'A1', 10))
diff --git a/sheet/__init__.py b/sheet/__init__.py
diff --git a/sheet/__main__.py b/sheet/__main__.py
@@ -0,0 +1,10 @@ -1 1 import sys -1 2 -1 3 from .csv import load_csv -1 4 from .term import render -1 5 -1 6 -1 7 with open(sys.argv[1]) as fh: -1 8 sheet = load_csv(fh) -1 9 -1 10 print(render(sheet, 80, 40, 'A1', 10))
diff --git a/sheet/csv.py b/sheet/csv.py
@@ -0,0 +1,38 @@ -1 1 import csv -1 2 -1 3 from .sheet import Sheet -1 4 from .sheet import xy2ref -1 5 -1 6 -1 7 def to_display(value: float|int|str|None|Exception) -> str: -1 8 if isinstance(value, float): -1 9 return str(value) -1 10 elif isinstance(value, int): -1 11 return str(value) -1 12 elif isinstance(value, str): -1 13 return value -1 14 elif value is None: -1 15 return '' -1 16 elif isinstance(value, Exception): -1 17 return repr(value) -1 18 -1 19 -1 20 def load_csv(fh, **kwargs): -1 21 sheet = Sheet() -1 22 for y, row in enumerate(csv.reader(fh, **kwargs)): -1 23 for x, raw in enumerate(row): -1 24 ref = xy2ref(x, y) -1 25 sheet.set(ref, raw) -1 26 return sheet -1 27 -1 28 -1 29 def dump_csv(sheet, fh, *, display=False, **kwargs): -1 30 if display: -1 31 def get(cell): -1 32 return to_display(sheet.get_value(cell)) -1 33 else: -1 34 get = sheet.get_raw -1 35 -1 36 w = csv.writer(fh, **kwargs) -1 37 for y in range(sheet.height): -1 38 w.writerow([get(xy2ref(x, y)) for x in range(sheet.width)])
diff --git a/sheet/expression.py b/sheet/expression.py
@@ -0,0 +1,117 @@
-1 1 import re
-1 2
-1 3
-1 4 class ParseError(ValueError):
-1 5 pass
-1 6
-1 7
-1 8 def parse_any(text, parsers):
-1 9 for parser in parsers:
-1 10 try:
-1 11 return parser(text)
-1 12 except ParseError:
-1 13 pass
-1 14 raise ParseError(f'None of the subparsers matched for {text}')
-1 15
-1 16
-1 17 def parse_re(text, pattern):
-1 18 m = re.match(pattern, text)
-1 19 if not m:
-1 20 raise ParseError
-1 21 return m, text[m.end():]
-1 22
-1 23
-1 24 def parse_string(text):
-1 25 m, tail = parse_re(text, r'"[^"]*"')
-1 26 return ('str', m[0][1:-1]), tail
-1 27
-1 28
-1 29 def parse_float(text):
-1 30 m, tail = parse_re(text, r'[0-9]+\.[0-9]+')
-1 31 return ('float', float(m[0])), tail
-1 32
-1 33
-1 34 def parse_int(text):
-1 35 m, tail = parse_re(text, r'[0-9]+')
-1 36 return ('int', int(m[0], 10)), tail
-1 37
-1 38
-1 39 def parse_ref(text):
-1 40 m, tail = parse_re(text, r'\$?([A-Z]+)\$?([1-9][0-9]*)')
-1 41 return ('ref', m[1] + m[2], m[0]), tail
-1 42
-1 43
-1 44 def parse_range(text):
-1 45 ref1, tail = parse_ref(text)
-1 46 _, tail = parse_re(tail, r':')
-1 47 ref2, tail = parse_ref(tail)
-1 48 return ('range', ref1, ref2), tail
-1 49
-1 50
-1 51 def parse_brace(text):
-1 52 _, tail = parse_re(text, r'\(')
-1 53 exp, tail = parse_expression(tail)
-1 54 _, tail = parse_re(tail, r'\)')
-1 55 return exp, tail
-1 56
-1 57
-1 58 def parse_add(text):
-1 59 lhs, tail = parse_expression2(text)
-1 60 m, tail = parse_re(tail, r'\s*[-+]\s*')
-1 61 rhs, tail = parse_expression(tail)
-1 62 return (m[0].strip(), lhs, rhs), tail
-1 63
-1 64
-1 65 def parse_mul(text):
-1 66 lhs, tail = parse_expression3(text)
-1 67 m, tail = parse_re(tail, r'\s*[*/]\s*')
-1 68 rhs, tail = parse_expression2(tail)
-1 69 return (m[0].strip(), lhs, rhs), tail
-1 70
-1 71
-1 72 def parse_call(text):
-1 73 m, tail = parse_re(text, r'[a-zA-Z][a-zA-Z0-9]*')
-1 74 _, tail = parse_re(tail, r'\(')
-1 75 args = []
-1 76 if tail.startswith(')'):
-1 77 return (m[0], args), tail[1:]
-1 78 while True:
-1 79 arg, tail = parse_expression(tail)
-1 80 args.append(arg)
-1 81 if tail.startswith(')'):
-1 82 return (m[0], args), tail[1:]
-1 83 _, tail = parse_re(tail, r',\s*')
-1 84 raise ParseError('no closing brace on function call')
-1 85
-1 86
-1 87 def parse_expression3(text):
-1 88 return parse_any(text, [
-1 89 parse_string,
-1 90 parse_float,
-1 91 parse_int,
-1 92 parse_range,
-1 93 parse_ref,
-1 94 parse_call,
-1 95 parse_brace,
-1 96 ])
-1 97
-1 98
-1 99 def parse_expression2(text):
-1 100 return parse_any(text, [
-1 101 parse_mul,
-1 102 parse_expression3,
-1 103 ])
-1 104
-1 105
-1 106 def parse_expression(text):
-1 107 return parse_any(text, [
-1 108 parse_add,
-1 109 parse_expression2,
-1 110 ])
-1 111
-1 112
-1 113 def parse(text):
-1 114 expr, tail = parse_expression(text)
-1 115 if tail:
-1 116 raise ParseError(f'unexpected tail: {tail}')
-1 117 return expr
diff --git a/sheet/sheet.py b/sheet/sheet.py
@@ -0,0 +1,150 @@
-1 1 import re
-1 2 import string
-1 3
-1 4 from .expression import ParseError
-1 5 from .expression import parse
-1 6
-1 7
-1 8 def x2col(x):
-1 9 a, b = divmod(x, len(string.ascii_uppercase))
-1 10 s = string.ascii_uppercase[b]
-1 11 while a:
-1 12 a, b = divmod(a - 1, len(string.ascii_uppercase))
-1 13 s = string.ascii_uppercase[b] + s
-1 14 return s
-1 15
-1 16
-1 17 def xy2ref(x, y):
-1 18 return x2col(x) + str(y + 1)
-1 19
-1 20
-1 21 def col2x(col):
-1 22 alph = string.ascii_uppercase
-1 23 x = -1
-1 24 for c in col:
-1 25 x = (x + 1) * len(alph) + alph.index(c)
-1 26 return x
-1 27
-1 28
-1 29 def ref2xy(ref):
-1 30 m = re.match('([A-Z]*)([0-9]*)', ref)
-1 31 return col2x(m[1]), int(m[2], 10) - 1
-1 32
-1 33
-1 34 def iter_range(cell1, cell2):
-1 35 x1, y1 = ref2xy(cell1)
-1 36 x2, y2 = ref2xy(cell2)
-1 37 if x1 > x2:
-1 38 x1, x2 = x2, x1
-1 39 if y1 > y2:
-1 40 y1, y2 = y2, y1
-1 41 for y in range(y1, y2 + 1):
-1 42 for x in range(x1, x2 + 1):
-1 43 yield xy2ref(x, y)
-1 44
-1 45
-1 46 def to_number(value: float|int|str|None|Exception) -> float|int:
-1 47 if isinstance(value, float):
-1 48 return value
-1 49 elif isinstance(value, int):
-1 50 return value
-1 51 elif isinstance(value, str):
-1 52 raise TypeError(value)
-1 53 elif value is None:
-1 54 return 0
-1 55 elif isinstance(value, Exception):
-1 56 raise value
-1 57
-1 58
-1 59 class Sheet:
-1 60 def __init__(self):
-1 61 self.raw = {}
-1 62 self.parsed = {}
-1 63 self.cache = {}
-1 64 self.width = 0
-1 65 self.height = 0
-1 66
-1 67 def parse(self, raw: str) -> tuple|float|int|str:
-1 68 if raw.startswith('='):
-1 69 try:
-1 70 return parse(raw[1:])
-1 71 except ParseError as err:
-1 72 return ('err', err)
-1 73 try:
-1 74 return int(raw, 10)
-1 75 except ValueError:
-1 76 pass
-1 77 try:
-1 78 return float(raw)
-1 79 except ValueError:
-1 80 pass
-1 81 return raw
-1 82
-1 83 def call_function(self, name: str, args: list[tuple]) -> float|int|str:
-1 84 if name == 'sum':
-1 85 if len(args) != 1 or args[0][0] != 'range':
-1 86 raise ValueError(args)
-1 87 _, ref1, ref2 = args[0]
-1 88 return sum(
-1 89 to_number(self.get_value(ref))
-1 90 for ref in iter_range(ref1[1], ref2[1])
-1 91 )
-1 92 elif name == 'power':
-1 93 if len(args) != 2:
-1 94 raise ValueError(args)
-1 95 base = to_number(self.evaluate(args[0]))
-1 96 exp = to_number(self.evaluate(args[1]))
-1 97 return base ** exp
-1 98 else:
-1 99 raise NameError(name)
-1 100
-1 101 def evaluate(self, expr: tuple) -> float|int|str:
-1 102 if expr[0] in ['int', 'float', 'str']:
-1 103 return expr[1]
-1 104 elif expr[0] == 'ref':
-1 105 return self.get_value(expr[1])
-1 106 elif expr[0] == 'err':
-1 107 raise expr[1]
-1 108 elif expr[0] == '+':
-1 109 return self.evaluate(expr[1]) + self.evaluate(expr[2])
-1 110 elif expr[0] == '-':
-1 111 return self.evaluate(expr[1]) - self.evaluate(expr[2])
-1 112 elif expr[0] == '*':
-1 113 return self.evaluate(expr[1]) * self.evaluate(expr[2])
-1 114 elif expr[0] == '/':
-1 115 return self.evaluate(expr[1]) / self.evaluate(expr[2])
-1 116 else:
-1 117 return self.call_function(*expr)
-1 118
-1 119 def set(self, cell: str, raw: str):
-1 120 if raw:
-1 121 self.raw[cell] = raw
-1 122 self.parsed[cell] = self.parse(raw)
-1 123 x, y = ref2xy(cell)
-1 124 self.width = max(self.width, x + 1)
-1 125 self.height = max(self.height, y + 1)
-1 126 elif cell in self.raw:
-1 127 del self.raw[cell]
-1 128 del self.parsed[cell]
-1 129 self.width = max(ref2xy(cell)[0] for cell in self.raw) + 1
-1 130 self.height = max(ref2xy(cell)[1] for cell in self.raw) + 1
-1 131 self.cache = {}
-1 132
-1 133 def get_raw(self, cell: str) -> str:
-1 134 return self.raw.get(cell, '')
-1 135
-1 136 def get_parsed(self, cell: str) -> tuple|float|int|str|None:
-1 137 return self.parsed.get(cell)
-1 138
-1 139 def get_value(self, cell: str) -> float|int|str|None|Exception:
-1 140 parsed = self.get_parsed(cell)
-1 141 if isinstance(parsed, tuple):
-1 142 if cell not in self.cache:
-1 143 self.cache[cell] = ReferenceError(cell)
-1 144 try:
-1 145 self.cache[cell] = self.evaluate(parsed)
-1 146 except Exception as err:
-1 147 self.cache[cell] = err
-1 148 return self.cache[cell]
-1 149 else:
-1 150 return parsed
diff --git a/sheet/term.py b/sheet/term.py
@@ -0,0 +1,63 @@
-1 1 from .sheet import ref2xy
-1 2 from .sheet import x2col
-1 3 from .sheet import xy2ref
-1 4
-1 5
-1 6 def align_right(s, width):
-1 7 if len(s) > width:
-1 8 s = '###'
-1 9 return ' ' + ' ' * (width - len(s) - 1) + s
-1 10
-1 11
-1 12 def align_left(s, width):
-1 13 if len(s) > width:
-1 14 s = '###'
-1 15 return s + ' ' * (width - len(s))
-1 16
-1 17
-1 18 def align_center(s, width):
-1 19 if len(s) > width:
-1 20 s = '###'
-1 21 t = width - len(s)
-1 22 return ' ' * (t // 2) + s + ' ' * (t - t // 2)
-1 23
-1 24
-1 25 def red(s):
-1 26 return f'\033[31m{s}\033[0m'
-1 27
-1 28
-1 29 def invert(s):
-1 30 return f'\033[7m{s}\033[0m'
-1 31
-1 32
-1 33 def to_cell(value: float|int|str|None|Exception, width: int) -> str:
-1 34 if isinstance(value, float):
-1 35 return align_right(str(value), width)
-1 36 elif isinstance(value, int):
-1 37 return align_right(str(value), width)
-1 38 elif isinstance(value, str):
-1 39 return align_left(value, width)
-1 40 elif value is None:
-1 41 return ' ' * width
-1 42 elif isinstance(value, Exception):
-1 43 return red(align_left(repr(value), width))
-1 44
-1 45
-1 46 def render(sheet, width, height, cell_offset, cell_width):
-1 47 x0, y0 = ref2xy(cell_offset)
-1 48 rows = []
-1 49 w = width // cell_width
-1 50 rows.append([
-1 51 ' ' * cell_width,
-1 52 ] + [
-1 53 align_center(x2col(x0 + dx), cell_width)
-1 54 for dx in range(w - 1)
-1 55 ])
-1 56 for dy in range(height - 1):
-1 57 rows.append([
-1 58 align_right(str(y0 + dy + 1), cell_width),
-1 59 ] + [
-1 60 to_cell(sheet.get_value(xy2ref(x0 + dx, y0 + dy)), cell_width)
-1 61 for dx in range(w - 1)
-1 62 ])
-1 63 return '\n'.join([''.join(row) for row in rows])