spreadsheet

terminal spreadsheet application
git clone https://git.ce9e.org/spreadsheet.git

commit
746d6a6ef5c61d0ac1672fe724cecdf6ecf27812
parent
f4a35784689dd2e81e71773df59e47b7d73fdbb3
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-07-05 17:31
use (x, y) instead of A1 internally

Diffstat

M sheet/__main__.py 2 +-
M sheet/csv.py 6 ++----
M sheet/expression.py 26 ++++++++++++++++++++++++--
M sheet/sheet.py 46 ++++++++++------------------------------------
M sheet/term.py 8 +++-----

5 files changed, 40 insertions, 48 deletions


diff --git a/sheet/__main__.py b/sheet/__main__.py

@@ -7,4 +7,4 @@ from .term import render
    7     7 with open(sys.argv[1]) as fh:
    8     8     sheet = load_csv(fh)
    9     9 
   10    -1 print(render(sheet, 80, 40, 'A1', 10))
   -1    10 print(render(sheet, 80, 40, (0, 0), 10))

diff --git a/sheet/csv.py b/sheet/csv.py

@@ -1,7 +1,6 @@
    1     1 import csv
    2     2 
    3     3 from .sheet import Sheet
    4    -1 from .sheet import xy2ref
    5     4 
    6     5 
    7     6 def to_display(value: float|int|str|None|Exception) -> str:
@@ -21,8 +20,7 @@ def load_csv(fh, **kwargs):
   21    20     sheet = Sheet()
   22    21     for y, row in enumerate(csv.reader(fh, **kwargs)):
   23    22         for x, raw in enumerate(row):
   24    -1             ref = xy2ref(x, y)
   25    -1             sheet.set(ref, raw)
   -1    23             sheet.set((x, y), raw)
   26    24     return sheet
   27    25 
   28    26 
@@ -35,4 +33,4 @@ def dump_csv(sheet, fh, *, display=False, **kwargs):
   35    33 
   36    34     w = csv.writer(fh, **kwargs)
   37    35     for y in range(sheet.height):
   38    -1         w.writerow([get(xy2ref(x, y)) for x in range(sheet.width)])
   -1    36         w.writerow([get((x, y)) for x in range(sheet.width)])

diff --git a/sheet/expression.py b/sheet/expression.py

@@ -1,4 +1,5 @@
    1     1 import re
   -1     2 import string
    2     3 
    3     4 
    4     5 class ParseError(ValueError):
@@ -36,9 +37,30 @@ def parse_int(text):
   36    37     return ('int', int(m[0], 10)), tail
   37    38 
   38    39 
   -1    40 def col2x(col):
   -1    41     alph = string.ascii_uppercase
   -1    42     x = -1
   -1    43     for c in col:
   -1    44         x = (x + 1) * len(alph) + alph.index(c)
   -1    45     return x
   -1    46 
   -1    47 
   -1    48 def x2col(x):
   -1    49     a, b = divmod(x, len(string.ascii_uppercase))
   -1    50     s = string.ascii_uppercase[b]
   -1    51     while a:
   -1    52         a, b = divmod(a - 1, len(string.ascii_uppercase))
   -1    53         s = string.ascii_uppercase[b] + s
   -1    54     return s
   -1    55 
   -1    56 
   39    57 def parse_ref(text):
   40    -1     m, tail = parse_re(text, r'\$?([A-Z]+)\$?([1-9][0-9]*)')
   41    -1     return ('ref', m[1] + m[2], m[0]), tail
   -1    58     m, tail = parse_re(text, r'(\$)?([A-Z]+)(\$)?([1-9][0-9]*)')
   -1    59     x_fixed = bool(m[1])
   -1    60     x = col2x(m[2])
   -1    61     y_fixed = bool(m[3])
   -1    62     y = int(m[4], 10) - 1
   -1    63     return ('ref', (x, y), (x_fixed, y_fixed)), tail
   42    64 
   43    65 
   44    66 def parse_range(text):

diff --git a/sheet/sheet.py b/sheet/sheet.py

@@ -5,42 +5,16 @@ from .expression import ParseError
    5     5 from .expression import parse
    6     6 
    7     7 
    8    -1 def x2col(x):
    9    -1     a, b = divmod(x, len(string.ascii_uppercase))
   10    -1     s = string.ascii_uppercase[b]
   11    -1     while a:
   12    -1         a, b = divmod(a - 1, len(string.ascii_uppercase))
   13    -1         s = string.ascii_uppercase[b] + s
   14    -1     return s
   15    -1 
   16    -1 
   17    -1 def xy2ref(x, y):
   18    -1     return x2col(x) + str(y + 1)
   19    -1 
   20    -1 
   21    -1 def col2x(col):
   22    -1     alph = string.ascii_uppercase
   23    -1     x = -1
   24    -1     for c in col:
   25    -1         x = (x + 1) * len(alph) + alph.index(c)
   26    -1     return x
   27    -1 
   28    -1 
   29    -1 def ref2xy(ref):
   30    -1     m = re.match('([A-Z]*)([0-9]*)', ref)
   31    -1     return col2x(m[1]), int(m[2], 10) - 1
   32    -1 
   33    -1 
   34     8 def iter_range(cell1, cell2):
   35    -1     x1, y1 = ref2xy(cell1)
   36    -1     x2, y2 = ref2xy(cell2)
   -1     9     x1, y1 = cell1
   -1    10     x2, y2 = cell2
   37    11     if x1 > x2:
   38    12         x1, x2 = x2, x1
   39    13     if y1 > y2:
   40    14         y1, y2 = y2, y1
   41    15     for y in range(y1, y2 + 1):
   42    16         for x in range(x1, x2 + 1):
   43    -1             yield xy2ref(x, y)
   -1    17             yield x, y
   44    18 
   45    19 
   46    20 def to_number(value: float|int|str|None|Exception) -> float|int:
@@ -116,27 +90,27 @@ class Sheet:
  116    90         else:
  117    91             return self.call_function(*expr)
  118    92 
  119    -1     def set(self, cell: str, raw: str):
   -1    93     def set(self, cell, raw: str):
  120    94         if raw:
  121    95             self.raw[cell] = raw
  122    96             self.parsed[cell] = self.parse(raw)
  123    -1             x, y = ref2xy(cell)
   -1    97             x, y = cell
  124    98             self.width = max(self.width, x + 1)
  125    99             self.height = max(self.height, y + 1)
  126   100         elif cell in self.raw:
  127   101             del self.raw[cell]
  128   102             del self.parsed[cell]
  129    -1             self.width = max(ref2xy(cell)[0] for cell in self.raw) + 1
  130    -1             self.height = max(ref2xy(cell)[1] for cell in self.raw) + 1
   -1   103             self.width = max(cell[0] for cell in self.raw) + 1
   -1   104             self.height = max(cell[1] for cell in self.raw) + 1
  131   105         self.cache = {}
  132   106 
  133    -1     def get_raw(self, cell: str) -> str:
   -1   107     def get_raw(self, cell) -> str:
  134   108         return self.raw.get(cell, '')
  135   109 
  136    -1     def get_parsed(self, cell: str) -> tuple|float|int|str|None:
   -1   110     def get_parsed(self, cell) -> tuple|float|int|str|None:
  137   111         return self.parsed.get(cell)
  138   112 
  139    -1     def get_value(self, cell: str) -> float|int|str|None|Exception:
   -1   113     def get_value(self, cell) -> float|int|str|None|Exception:
  140   114         parsed = self.get_parsed(cell)
  141   115         if isinstance(parsed, tuple):
  142   116             if cell not in self.cache:

diff --git a/sheet/term.py b/sheet/term.py

@@ -1,6 +1,4 @@
    1    -1 from .sheet import ref2xy
    2    -1 from .sheet import x2col
    3    -1 from .sheet import xy2ref
   -1     1 from .expression import x2col
    4     2 
    5     3 
    6     4 def align_right(s, width):
@@ -44,7 +42,7 @@ def to_cell(value: float|int|str|None|Exception, width: int) -> str:
   44    42 
   45    43 
   46    44 def render(sheet, width, height, cell_offset, cell_width):
   47    -1     x0, y0 = ref2xy(cell_offset)
   -1    45     x0, y0 = cell_offset
   48    46     rows = []
   49    47     w = width // cell_width
   50    48     rows.append([
@@ -57,7 +55,7 @@ def render(sheet, width, height, cell_offset, cell_width):
   57    55         rows.append([
   58    56             align_right(str(y0 + dy + 1), cell_width),
   59    57         ] + [
   60    -1             to_cell(sheet.get_value(xy2ref(x0 + dx, y0 + dy)), cell_width)
   -1    58             to_cell(sheet.get_value((x0 + dx, y0 + dy)), cell_width)
   61    59             for dx in range(w - 1)
   62    60         ])
   63    61     return '\n'.join([''.join(row) for row in rows])