numbers-parser 4.13.3__py3-none-any.whl → 4.14.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- __doc__ = """Parse and extract data from Apple Numbers spreadsheets"""
1
+ """Parse and extract data from Apple Numbers spreadsheets."""
2
2
 
3
3
  import importlib.metadata
4
4
  import os
@@ -20,11 +20,11 @@ _DEFAULT_NUMBERS_INSTALL_PATH = "/Applications/Numbers.app"
20
20
  _VERSION_PLIST_PATH = "Contents/version.plist"
21
21
 
22
22
 
23
- def _get_version():
23
+ def _get_version() -> str:
24
24
  return __version__
25
25
 
26
26
 
27
- def _check_installed_numbers_version():
27
+ def _check_installed_numbers_version() -> str:
28
28
  try:
29
29
  fp = open(os.path.join(_DEFAULT_NUMBERS_INSTALL_PATH, _VERSION_PLIST_PATH), "rb")
30
30
  except OSError:
@@ -37,7 +37,8 @@ def _check_installed_numbers_version():
37
37
  installed_version = re.sub(r"(\d+)\.(\d+)\.\d+", r"\1.\2", installed_version)
38
38
  if installed_version not in SUPPORTED_NUMBERS_VERSIONS:
39
39
  warnings.warn(
40
- f"Numbers version {installed_version} not tested with this version", stacklevel=2,
40
+ f"Numbers version {installed_version} not tested with this version",
41
+ stacklevel=2,
41
42
  )
42
43
  fp.close()
43
44
  return installed_version
@@ -3,7 +3,7 @@ import csv
3
3
  import logging
4
4
  import sys
5
5
 
6
- import sigfig
6
+ from sigfig import round as sigfig
7
7
 
8
8
  from numbers_parser import Document, ErrorCell, FileError, FileFormatError, NumberCell, _get_version
9
9
  from numbers_parser import __name__ as numbers_parser_name
@@ -49,25 +49,34 @@ def command_line_parser():
49
49
  help="Dump formatted cells (durations) as they appear in Numbers",
50
50
  )
51
51
  parser.add_argument(
52
- "-s", "--sheet", action="append", help="Names of sheet(s) to include in export",
52
+ "-s",
53
+ "--sheet",
54
+ action="append",
55
+ help="Names of sheet(s) to include in export",
53
56
  )
54
57
  parser.add_argument(
55
- "-t", "--table", action="append", help="Names of table(s) to include in export",
58
+ "-t",
59
+ "--table",
60
+ action="append",
61
+ help="Names of table(s) to include in export",
56
62
  )
57
63
  parser.add_argument("document", nargs="*", help="Document(s) to export")
58
64
  parser.add_argument("--debug", default=False, action="store_true", help="Enable debug logging")
59
65
  parser.add_argument(
60
- "--experimental", default=False, action="store_true", help=argparse.SUPPRESS,
66
+ "--experimental",
67
+ default=False,
68
+ action="store_true",
69
+ help=argparse.SUPPRESS,
61
70
  )
62
71
  return parser
63
72
 
64
73
 
65
- def print_sheet_names(filename):
74
+ def print_sheet_names(filename) -> None:
66
75
  for sheet in Document(filename).sheets:
67
76
  print(f"{filename}: {sheet.name}")
68
77
 
69
78
 
70
- def print_table_names(filename):
79
+ def print_table_names(filename) -> None:
71
80
  for sheet in Document(filename).sheets:
72
81
  for table in sheet.tables:
73
82
  print(f"{filename}: {sheet.name}: {table.name}")
@@ -76,19 +85,18 @@ def print_table_names(filename):
76
85
  def cell_as_string(args, cell):
77
86
  if isinstance(cell, ErrorCell) and not (args.formulas):
78
87
  return "#REF!"
79
- elif args.formulas and cell.formula is not None:
88
+ if args.formulas and cell.formula is not None:
80
89
  return cell.formula
81
- elif args.formatting and cell.formatted_value is not None:
90
+ if args.formatting and cell.formatted_value is not None:
82
91
  return cell.formatted_value
83
- elif isinstance(cell, NumberCell):
84
- return sigfig.round(cell.value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
85
- elif cell.value is None:
92
+ if isinstance(cell, NumberCell):
93
+ return sigfig(cell.value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
94
+ if cell.value is None:
86
95
  return ""
87
- else:
88
- return str(cell.value)
96
+ return str(cell.value)
89
97
 
90
98
 
91
- def print_table(args, filename):
99
+ def print_table(args, filename) -> None:
92
100
  writer = csv.writer(sys.stdout, dialect="excel")
93
101
  for sheet in Document(filename).sheets:
94
102
  if args.sheet is not None and sheet.name not in args.sheet:
@@ -103,7 +111,7 @@ def print_table(args, filename):
103
111
  writer.writerow(cells)
104
112
 
105
113
 
106
- def main():
114
+ def main() -> None:
107
115
  parser = command_line_parser()
108
116
  args = parser.parse_args()
109
117
 
@@ -129,7 +137,7 @@ def main():
129
137
  print_table_names(filename)
130
138
  else:
131
139
  print_table(args, filename)
132
- except FileFormatError as e:
140
+ except FileFormatError as e: # noqa: PERF203
133
141
  print(f"{filename}:", str(e), file=sys.stderr)
134
142
  sys.exit(1)
135
143
  except FileError as e:
@@ -3,13 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import contextlib
6
7
  import csv
7
8
  import re
8
9
  from dataclasses import dataclass
9
10
  from datetime import datetime, timezone
10
11
  from pathlib import Path
11
12
  from sys import exit, stderr
12
- from typing import NamedTuple, Tuple # noqa: F401
13
+ from typing import NamedTuple
13
14
 
14
15
  from dateutil.parser import parse
15
16
 
@@ -68,18 +69,18 @@ class Converter:
68
69
  """Parse a date string and return a datetime."""
69
70
  return parse(x, dayfirst=self.day_first).replace(tzinfo=timezone.utc)
70
71
 
71
- def _transform_data(self):
72
+ def _transform_data(self) -> None:
72
73
  """Apply type transformations to the data based in current configuration."""
73
74
  # Convert data rows to dicts. csv.DictReader is not enough as we support CSV
74
75
  # files with no header.
75
76
  if self.no_header:
76
- self.header = [x for x in range(len(self.data[0]))]
77
- self.data = [{k: v for k, v in dict(zip(self.header, row)).items()} for row in self.data]
77
+ self.header = list(range(len(self.data[0])))
78
+ self.data = [dict(dict(zip(self.header, row)).items()) for row in self.data]
78
79
 
79
80
  if self.reverse:
80
81
  self.data = list(reversed(self.data))
81
82
  if self.date_columns is not None:
82
- is_date_column = {x: True if x in self.date_columns else False for x in self.header}
83
+ is_date_column = {x: x in self.date_columns for x in self.header}
83
84
  for row in self.data:
84
85
  for k, v in row.items():
85
86
  if self.whitespace:
@@ -88,17 +89,15 @@ class Converter:
88
89
  row[k] = self._parse_date(v)
89
90
  else:
90
91
  # Attempt to coerce value into float
91
- try:
92
+ with contextlib.suppress(ValueError):
92
93
  row[k] = float(v.replace(",", ""))
93
- except ValueError:
94
- pass
95
94
 
96
95
  def rename_columns(self: Converter, mapper: dict) -> None:
97
96
  """Rename columns using column map."""
98
97
  if mapper is None:
99
98
  return
100
99
  self.no_header = False
101
- self.header = [mapper[x] if x in mapper else x for x in self.header]
100
+ self.header = [mapper.get(x, x) for x in self.header]
102
101
 
103
102
  def delete_columns(self: Converter, columns: list) -> None:
104
103
  """Delete columns from the data."""
@@ -131,10 +130,7 @@ class Converter:
131
130
  doc = Document(num_rows=2, num_cols=2)
132
131
  table = doc.sheets[0].tables[0]
133
132
 
134
- if self.no_header:
135
- data = []
136
- else:
137
- data = [self.header]
133
+ data = [] if self.no_header else [self.header]
138
134
  data += [row.values() for row in self.data]
139
135
 
140
136
  for row_num, row in enumerate(data):
@@ -142,7 +138,10 @@ class Converter:
142
138
  table.write(row_num, col_num, value)
143
139
  if isinstance(value, datetime):
144
140
  table.set_cell_formatting(
145
- row_num, col_num, "datetime", date_time_format="d MMM yyyy"
141
+ row_num,
142
+ col_num,
143
+ "datetime",
144
+ date_time_format="d MMM yyyy",
146
145
  )
147
146
 
148
147
  doc.save(self.output_filename)
@@ -11,7 +11,6 @@ from binascii import hexlify
11
11
  from dataclasses import dataclass
12
12
  from pathlib import Path
13
13
 
14
- import regex
15
14
  from compact_json import Formatter
16
15
 
17
16
  from numbers_parser import __name__ as numbers_parser_name
@@ -35,7 +34,7 @@ class NumbersUnpacker(IWorkHandler):
35
34
 
36
35
  def store_file(self, filename: str, blob: bytes) -> None:
37
36
  """Store a profobuf archive."""
38
- filename = regex.sub(r".*\.numbers/", "", str(filename))
37
+ filename = re.sub(r".*\.numbers/", "", str(filename))
39
38
  self.ensure_directory_exists(filename)
40
39
  target_path = os.path.join(self.output_dir, filename)
41
40
  if isinstance(blob, IWAFile):
@@ -65,13 +64,13 @@ class NumbersUnpacker(IWorkHandler):
65
64
  with open(target_path, "wb") as out:
66
65
  out.write(blob)
67
66
 
68
- def ensure_directory_exists(self, path: str):
67
+ def ensure_directory_exists(self, path: str) -> None:
69
68
  """Ensure that a path's directory exists."""
70
69
  parts = os.path.split(path)
71
70
  with contextlib.suppress(OSError):
72
71
  os.makedirs(os.path.join(*([self.output_dir, *list(parts[:-1])])))
73
72
 
74
- def prettify_uuids(self, obj: object):
73
+ def prettify_uuids(self, obj: object) -> None:
75
74
  if isinstance(obj, dict):
76
75
  for k, v in obj.items():
77
76
  if isinstance(v, dict):
@@ -91,7 +90,7 @@ class NumbersUnpacker(IWorkHandler):
91
90
  elif isinstance(v, list):
92
91
  self.prettify_uuids(v)
93
92
 
94
- def prettify_cell_storage(self, obj: object):
93
+ def prettify_cell_storage(self, obj: object) -> None:
95
94
  if isinstance(obj, dict):
96
95
  for k, v in obj.items():
97
96
  if isinstance(v, (dict, list)):
@@ -102,7 +101,7 @@ class NumbersUnpacker(IWorkHandler):
102
101
  elif k in ["cell_offsets", k == "cell_offsets_pre_bnc"]:
103
102
  offsets = array("h", b64decode(obj[k])).tolist()
104
103
  obj[k] = ",".join([str(x) for x in offsets])
105
- obj[k] = regex.sub(r"(?:,-1)+$", ",[...]", obj[k])
104
+ obj[k] = re.sub(r"(?:,-1)+$", ",[...]", obj[k])
106
105
  else: # list
107
106
  for v in obj:
108
107
  if isinstance(v, (dict, list)):
@@ -118,7 +117,7 @@ class NumbersUnpacker(IWorkHandler):
118
117
  return version in SUPPORTED_NUMBERS_VERSIONS
119
118
 
120
119
 
121
- def main():
120
+ def main() -> None:
122
121
  parser = ArgumentParser()
123
122
  parser.add_argument("document", help="Apple Numbers file(s)", nargs="*")
124
123
  parser.add_argument("-V", "--version", action="store_true")
numbers_parser/bullets.py CHANGED
@@ -1,6 +1,5 @@
1
- from roman import toRoman
2
-
3
1
  from numbers_parser.generated.TSWPArchives_pb2 import ListStyleArchive
2
+ from numbers_parser.roman import to_roman
4
3
 
5
4
  BULLET_PREFIXES = {
6
5
  ListStyleArchive.kNumericDecimal: "",
@@ -24,12 +23,12 @@ BULLET_CONVERSION = {
24
23
  ListStyleArchive.kNumericDecimal: lambda x: str(x + 1),
25
24
  ListStyleArchive.kNumericDoubleParen: lambda x: str(x + 1),
26
25
  ListStyleArchive.kNumericRightParen: lambda x: str(x + 1),
27
- ListStyleArchive.kRomanUpperDecimal: lambda x: toRoman(x + 1),
28
- ListStyleArchive.kRomanUpperDoubleParen: lambda x: toRoman(x + 1),
29
- ListStyleArchive.kRomanUpperRightParen: lambda x: toRoman(x + 1),
30
- ListStyleArchive.kRomanLowerDecimal: lambda x: toRoman(x + 1).lower(),
31
- ListStyleArchive.kRomanLowerDoubleParen: lambda x: toRoman(x + 1).lower(),
32
- ListStyleArchive.kRomanLowerRightParen: lambda x: toRoman(x + 1).lower(),
26
+ ListStyleArchive.kRomanUpperDecimal: lambda x: to_roman(x + 1),
27
+ ListStyleArchive.kRomanUpperDoubleParen: lambda x: to_roman(x + 1),
28
+ ListStyleArchive.kRomanUpperRightParen: lambda x: to_roman(x + 1),
29
+ ListStyleArchive.kRomanLowerDecimal: lambda x: to_roman(x + 1).lower(),
30
+ ListStyleArchive.kRomanLowerDoubleParen: lambda x: to_roman(x + 1).lower(),
31
+ ListStyleArchive.kRomanLowerRightParen: lambda x: to_roman(x + 1).lower(),
33
32
  ListStyleArchive.kAlphaUpperDecimal: lambda x: chr(x + 65),
34
33
  ListStyleArchive.kAlphaUpperDoubleParen: lambda x: chr(x + 65),
35
34
  ListStyleArchive.kAlphaUpperRightParen: lambda x: chr(x + 65),