numbers-parser 4.13.3__tar.gz → 4.14.2__tar.gz

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.
Files changed (65) hide show
  1. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/PKG-INFO +18 -22
  2. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/README.md +14 -15
  3. numbers_parser-4.14.2/pyproject.toml +193 -0
  4. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/__init__.py +5 -4
  5. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/_cat_numbers.py +24 -16
  6. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/_csv2numbers.py +13 -14
  7. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/_unpack_numbers.py +6 -7
  8. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/bullets.py +7 -8
  9. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/cell.py +280 -255
  10. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/constants.py +22 -8
  11. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/containers.py +11 -10
  12. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/document.py +196 -150
  13. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/exceptions.py +1 -8
  14. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/formula.py +29 -32
  15. numbers_parser-4.14.2/src/numbers_parser/generated/TSKArchives_pb2.py +146 -0
  16. numbers_parser-4.14.2/src/numbers_parser/generated/TSSArchives_pb2.py +64 -0
  17. numbers_parser-4.14.2/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +133 -0
  18. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/fontmap.py +16 -10
  19. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/mapping.py +0 -1
  20. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/iwafile.py +16 -16
  21. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/iwork.py +32 -17
  22. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/model.py +222 -210
  23. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/numbers_cache.py +6 -7
  24. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/numbers_uuid.py +4 -1
  25. numbers_parser-4.14.2/src/numbers_parser/roman.py +32 -0
  26. numbers_parser-4.13.3/pyproject.toml +0 -149
  27. numbers_parser-4.13.3/src/numbers_parser/generated/TSKArchives_pb2.py +0 -146
  28. numbers_parser-4.13.3/src/numbers_parser/generated/TSSArchives_pb2.py +0 -64
  29. numbers_parser-4.13.3/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -133
  30. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/LICENSE.rst +0 -0
  31. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/currencies.py +0 -0
  32. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/data/empty.numbers +0 -0
  33. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/experimental.py +0 -0
  34. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
  35. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
  36. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
  37. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
  38. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
  39. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
  40. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
  41. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
  42. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
  43. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
  44. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
  45. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
  46. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
  47. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
  48. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
  49. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
  50. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
  51. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
  52. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
  53. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
  54. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
  55. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
  56. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
  57. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
  58. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
  59. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
  60. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
  61. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
  62. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
  63. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
  64. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/__init__.py +0 -0
  65. {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/functionmap.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: numbers-parser
3
- Version: 4.13.3
3
+ Version: 4.14.2
4
4
  Summary: Read and write Apple Numbers spreadsheets
5
5
  Home-page: https://github.com/masaccio/numbers-parser
6
6
  License: MIT
@@ -15,17 +15,14 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
18
- Requires-Dist: colorama (>=0.4.6,<0.5.0)
19
18
  Requires-Dist: compact-json (>=1.1.3,<2.0.0)
20
19
  Requires-Dist: enum-tools (>=0.11)
21
20
  Requires-Dist: importlib-resources (>=6.1)
22
- Requires-Dist: pendulum (>=3.0,<4.0)
23
21
  Requires-Dist: protobuf
22
+ Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
24
23
  Requires-Dist: python-snappy (>=0.7,<0.8)
25
- Requires-Dist: regex (>2024.0)
26
- Requires-Dist: roman (>=4.0)
27
- Requires-Dist: setuptools (>=69.0.3)
28
- Requires-Dist: sigfig (>=1.3.2,<2.0.0)
24
+ Requires-Dist: setuptools (>=70.0.0)
25
+ Requires-Dist: sigfig (>=1.3.3,<2.0.0)
29
26
  Project-URL: Documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
30
27
  Project-URL: Repository, https://github.com/masaccio/numbers-parser
31
28
  Description-Content-Type: text/markdown
@@ -129,21 +126,19 @@ the column values.
129
126
 
130
127
  Cells are objects with a common base class of `Cell`. All cell types
131
128
  have a property `value` which returns the contents of the cell as a
132
- python datatype. `numbers-parser` uses
133
- [pendulum](https://pendulum.eustace.io) instead of python’s builtin
134
- types. Available cell types are:
135
-
136
- | Cell type | value type | Additional properties |
137
- |--------------|---------------------|--------------------------------------------------------------------------------------------------------|
138
- | NumberCell | `float` | |
139
- | TextCell | `str` | |
140
- | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
141
- | EmptyCell | `None` | |
142
- | BoolCell | `bool` | |
143
- | DateCell | `pendulum.datetime` | |
144
- | DurationCell | `pendulum.duration` | |
145
- | ErrorCell | `None` | |
146
- | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
129
+ python datatype. Available cell types are:
130
+
131
+ | Cell type | value type | Additional properties |
132
+ |--------------|----------------------|--------------------------------------------------------------------------------------------------------|
133
+ | NumberCell | `float` | |
134
+ | TextCell | `str` | |
135
+ | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
136
+ | EmptyCell | `None` | |
137
+ | BoolCell | `bool` | |
138
+ | DateCell | `datetime.datetime` | |
139
+ | DurationCell | `datetime.timedelta` | |
140
+ | ErrorCell | `None` | |
141
+ | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
147
142
 
148
143
  Cell references can be either zero-offset row/column integers or an
149
144
  Excel/Numbers A1 notation. Where cell values are not `None` the
@@ -506,6 +501,7 @@ The following limitations are expected to always remain:
506
501
  (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69) for details).
507
502
  - Password-encrypted documents cannot be opened. You must first re-save without
508
503
  a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
504
+ A UnsupportedError exception is raised when such documents are opened.
509
505
 
510
506
  ## License
511
507
 
@@ -97,21 +97,19 @@ the column values.
97
97
 
98
98
  Cells are objects with a common base class of `Cell`. All cell types
99
99
  have a property `value` which returns the contents of the cell as a
100
- python datatype. `numbers-parser` uses
101
- [pendulum](https://pendulum.eustace.io) instead of python’s builtin
102
- types. Available cell types are:
103
-
104
- | Cell type | value type | Additional properties |
105
- |--------------|---------------------|--------------------------------------------------------------------------------------------------------|
106
- | NumberCell | `float` | |
107
- | TextCell | `str` | |
108
- | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
109
- | EmptyCell | `None` | |
110
- | BoolCell | `bool` | |
111
- | DateCell | `pendulum.datetime` | |
112
- | DurationCell | `pendulum.duration` | |
113
- | ErrorCell | `None` | |
114
- | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
100
+ python datatype. Available cell types are:
101
+
102
+ | Cell type | value type | Additional properties |
103
+ |--------------|----------------------|--------------------------------------------------------------------------------------------------------|
104
+ | NumberCell | `float` | |
105
+ | TextCell | `str` | |
106
+ | RichTextCell | `str` | See [Rich text](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.RichTextCell) |
107
+ | EmptyCell | `None` | |
108
+ | BoolCell | `bool` | |
109
+ | DateCell | `datetime.datetime` | |
110
+ | DurationCell | `datetime.timedelta` | |
111
+ | ErrorCell | `None` | |
112
+ | MergedCell | `None` | See [Merged cells](https://masaccio.github.io/numbers-parser/api/cells.html#numbers_parser.MergedCell) |
115
113
 
116
114
  Cell references can be either zero-offset row/column integers or an
117
115
  Excel/Numbers A1 notation. Where cell values are not `None` the
@@ -474,6 +472,7 @@ The following limitations are expected to always remain:
474
472
  (see [issue 69](https://github.com/masaccio/numbers-parser/issues/69) for details).
475
473
  - Password-encrypted documents cannot be opened. You must first re-save without
476
474
  a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
475
+ A UnsupportedError exception is raised when such documents are opened.
477
476
 
478
477
  ## License
479
478
 
@@ -0,0 +1,193 @@
1
+ [tool.poetry]
2
+ authors = ["Jon Connell <python@figsandfudge.com>"]
3
+ classifiers = [
4
+ "Topic :: Office/Business :: Financial :: Spreadsheet",
5
+ "Programming Language :: Python :: 3",
6
+ "Operating System :: OS Independent",
7
+ ]
8
+ description = "Read and write Apple Numbers spreadsheets"
9
+ documentation = "https://github.com/masaccio/numbers-parser/blob/main/README.md"
10
+ license = "MIT"
11
+ name = "numbers-parser"
12
+ packages = [{include = "numbers_parser", from = "src"}]
13
+ readme = "README.md"
14
+ repository = "https://github.com/masaccio/numbers-parser"
15
+ version = "4.14.2"
16
+
17
+ [tool.poetry.scripts]
18
+ cat-numbers = "numbers_parser._cat_numbers:main"
19
+ unpack-numbers = "numbers_parser._unpack_numbers:main"
20
+ csv2numbers = "numbers_parser._csv2numbers:main"
21
+
22
+ [tool.poetry.dependencies]
23
+ compact-json = "^1.1.3"
24
+ protobuf = "*"
25
+ python = ">=3.9,<4.0"
26
+ python-snappy = "^0.7"
27
+ sigfig = "^1.3.3"
28
+ setuptools = ">=70.0.0"
29
+ importlib-resources = ">=6.1"
30
+ enum-tools = ">=0.11"
31
+ python-dateutil = "^2.9.0.post0"
32
+
33
+ [tool.poetry.group.dev.dependencies]
34
+ gprof2dot = "^2022.7.29"
35
+ line-profiler = "^4.0.3"
36
+ mock = ">=5.1.0"
37
+ psutil = ">=5.9"
38
+ pytest = ">=7.2.0"
39
+ pytest-check = ">=1.0"
40
+ pytest-console-scripts = "^1.3.1"
41
+ pytest-cov = ">=4.0,>=5.0"
42
+ pytest-xdist = "^3.3.1"
43
+ ruff = "*"
44
+ tox = "^4.11.4"
45
+ python-magic = ">=0.4"
46
+ tqdm = ">=4.66"
47
+ colorama = "^0.4.6"
48
+ roman = "^4.2"
49
+
50
+ [tool.poetry.group.docs]
51
+ optional = true
52
+
53
+ [tool.poetry.group.docs.dependencies]
54
+ sphinx = ">= 7.3"
55
+ enum-tools = ">=0.11"
56
+ sphinx-toolbox = ">=3.5"
57
+ sphinx-nefertiti = ">=0.3.3"
58
+ sphinx-markdown-builder = ">=0.6"
59
+ sphinx-copybutton = ">=0.5"
60
+
61
+ [tool.poetry.group.bootstrap]
62
+ optional = true
63
+
64
+ [tool.poetry.group.bootstrap.dependencies]
65
+ pyobjc-core = ">=10.2"
66
+ pyobjc-framework-Cocoa = ">=10.2"
67
+ py2app = ">=0.28"
68
+
69
+ [build-system]
70
+ build-backend = "poetry.core.masonry.api"
71
+ requires = ["poetry-core>=1.0.0"]
72
+
73
+ [tool.coverage.run]
74
+ branch = true
75
+ omit = ["src/numbers_parser/generated/*.py"]
76
+
77
+ [tool.coverage.html]
78
+ directory = "coverage_html_report"
79
+ show_contexts = true
80
+
81
+ [tool.pytest.ini_options]
82
+ addopts = "--cov=src/numbers_parser --cov-report=html --cov-report=term-missing:skip-covered --cov-context=test"
83
+
84
+ [tool.tox]
85
+ legacy_tox_ini = """
86
+ [tox]
87
+ isolated_build = true
88
+ envlist = py39, py310, py311, py312, py313
89
+ [testenv]
90
+ deps =
91
+ pytest
92
+ pytest-check
93
+ pytest-console-scripts
94
+ pytest-cov
95
+ pytest-xdist
96
+ python-magic
97
+ psutil
98
+ roman
99
+ commands =
100
+ pytest tests/ --import-mode importlib -n logical --no-cov
101
+ """
102
+
103
+ [tool.isort]
104
+ profile = "black"
105
+
106
+ [tool.ruff]
107
+ exclude = [
108
+ # Machine-generated files
109
+ "**/.bootstrap/*",
110
+ "**/.tox/*",
111
+ "**/.vscode/*",
112
+ "**/src/numbers_parser/generated/*",
113
+ # Third-party files not to lint
114
+ "**/src/debug/lldbutil.py",
115
+ ]
116
+ fix = true
117
+ lint.ignore = [
118
+ # "PLR2004", # Allow constant values
119
+ "T201", # Allow print()
120
+ # To fix:
121
+ "ANN001", #Missing type annotation
122
+ "ANN002", #Missing type annotation
123
+ "ANN003", #Missing type annotation
124
+ "ANN201", #Missing return type annotation
125
+ "ANN202", #Missing return type annotation
126
+ "ANN204", #Missing return type annotation
127
+ "ANN205", #Missing return type annotation
128
+ "ANN206", #Missing return type annotation
129
+ "ANN401", #Dynamically typed expressions
130
+ "ARG001", #Unused function argument
131
+ "ARG002", #Unused method argument
132
+ "ARG003", #Unused class method argument
133
+ "BLE001", #Do not catch blind exception
134
+ "C901", #code too complex
135
+ "D100", #Missing docstring
136
+ "D101", #Missing docstring
137
+ "D102", #Missing docstring
138
+ "D103", #Missing docstring
139
+ "D105", #Missing docstring
140
+ "D107", #Missing docstring
141
+ "D205", # Blank line - conflicts with black
142
+ "D210", #No whitespaces allowed surrounding docstring text
143
+ "D400", #First line should end with a period
144
+ "D401", #First line of docstring should be in imperative mood
145
+ "D415", #First line should end with a period, question mark, or exclamation point
146
+ "E501", #Line too long
147
+ "ERA001", #Found commented-out code
148
+ "FBT001", #Boolean-typed positional argument in function definition
149
+ "FBT002", #Boolean default positional argument in function definition
150
+ "FBT003", #Boolean positional value in function call
151
+ "FIX002", #Line contains TODO, consider resolving the issue
152
+ "ISC003", #Explicitly concatenated string should be implicitly concatenated
153
+ "PERF401", #Use a list comprehension to create a transformed list
154
+ "PLR0913", #Too many arguments
155
+ "PLR2004", #Magic value used
156
+ "PLW0603", #Using the global statement
157
+ "PTH103", # Use of os.makedirs()
158
+ "PTH118", # Use of os.path.join()
159
+ "PTH119", # Use of os.path.basename()
160
+ "PTH123", # Use of open()
161
+ "RUF001", #String contains ambiguous
162
+ "RUF002", #Docstring contains ambiguous
163
+ "RUF003", #Comment contains ambiguous
164
+ "SIM115", #Use a context manager for opening files
165
+ "SLF001", #Private member accessed
166
+ "TD002", # TODO author
167
+ "TD003", # TODO author
168
+ # Conflicts with other rules
169
+ "D212", # Multiline summary first line
170
+ "D203", # Blank lines before class docstrings
171
+ "D416", # Colon after after section titles
172
+ ]
173
+ line-length = 100
174
+ lint.select = [ "ALL" ]
175
+ src = ["src", "tests"]
176
+ target-version = "py39"
177
+ lint.unfixable = [
178
+ "ERA", # do not autoremove commented out code
179
+ ]
180
+
181
+ [tool.ruff.lint.pylint]
182
+ max-statements = 100
183
+ max-branches = 20
184
+
185
+ [tool.ruff.lint.flake8-tidy-imports]
186
+ ban-relative-imports = "all"
187
+
188
+ [tool.ruff.lint.per-file-ignores]
189
+ "docs/conf.py" = ["INP001"]
190
+ "src/build/**" = ["PLR2004", "INP001", "PTH"]
191
+ "src/build/protodump.py" = ["PLR2004", "INP001", "PTH", "S110", "N806"]
192
+ "src/debug/**" = ["INP001"]
193
+ "tests/**" = ["PLR2004", "S101", "D103", "ANN201", "ANN001"]
@@ -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")
@@ -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),