numbers-parser 4.14.1__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.
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/PKG-INFO +14 -1
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/README.md +13 -0
- numbers_parser-4.14.2/pyproject.toml +193 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/__init__.py +3 -3
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/_cat_numbers.py +10 -11
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/_csv2numbers.py +13 -14
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/_unpack_numbers.py +4 -4
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/cell.py +257 -229
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/constants.py +6 -3
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/containers.py +11 -10
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/document.py +196 -149
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/exceptions.py +1 -8
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/formula.py +28 -30
- numbers_parser-4.14.2/src/numbers_parser/generated/TSKArchives_pb2.py +146 -0
- numbers_parser-4.14.2/src/numbers_parser/generated/TSSArchives_pb2.py +64 -0
- numbers_parser-4.14.2/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +133 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/fontmap.py +16 -10
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/mapping.py +0 -1
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/iwafile.py +16 -16
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/iwork.py +32 -17
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/model.py +222 -210
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/numbers_cache.py +6 -7
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/numbers_uuid.py +4 -1
- numbers_parser-4.14.2/src/numbers_parser/roman.py +32 -0
- numbers_parser-4.14.1/pyproject.toml +0 -147
- numbers_parser-4.14.1/src/numbers_parser/generated/TSKArchives_pb2.py +0 -146
- numbers_parser-4.14.1/src/numbers_parser/generated/TSSArchives_pb2.py +0 -64
- numbers_parser-4.14.1/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -133
- numbers_parser-4.14.1/src/numbers_parser/roman.py +0 -18
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/LICENSE.rst +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/bullets.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/currencies.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/data/empty.numbers +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/experimental.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
- {numbers_parser-4.14.1 → numbers_parser-4.14.2}/src/numbers_parser/generated/__init__.py +0 -0
- {numbers_parser-4.14.1 → 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.14.
|
|
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
|
|
@@ -128,6 +128,18 @@ Cells are objects with a common base class of `Cell`. All cell types
|
|
|
128
128
|
have a property `value` which returns the contents of the cell as a
|
|
129
129
|
python datatype. Available cell types are:
|
|
130
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) |
|
|
142
|
+
|
|
131
143
|
Cell references can be either zero-offset row/column integers or an
|
|
132
144
|
Excel/Numbers A1 notation. Where cell values are not `None` the
|
|
133
145
|
property `formatted_value` returns the cell value as a `str` as
|
|
@@ -489,6 +501,7 @@ The following limitations are expected to always remain:
|
|
|
489
501
|
(see [issue 69](https://github.com/masaccio/numbers-parser/issues/69) for details).
|
|
490
502
|
- Password-encrypted documents cannot be opened. You must first re-save without
|
|
491
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.
|
|
492
505
|
|
|
493
506
|
## License
|
|
494
507
|
|
|
@@ -99,6 +99,18 @@ 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
100
|
python datatype. Available cell types are:
|
|
101
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) |
|
|
113
|
+
|
|
102
114
|
Cell references can be either zero-offset row/column integers or an
|
|
103
115
|
Excel/Numbers A1 notation. Where cell values are not `None` the
|
|
104
116
|
property `formatted_value` returns the cell value as a `str` as
|
|
@@ -460,6 +472,7 @@ The following limitations are expected to always remain:
|
|
|
460
472
|
(see [issue 69](https://github.com/masaccio/numbers-parser/issues/69) for details).
|
|
461
473
|
- Password-encrypted documents cannot be opened. You must first re-save without
|
|
462
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.
|
|
463
476
|
|
|
464
477
|
## License
|
|
465
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
|
-
|
|
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:
|
|
@@ -71,12 +71,12 @@ def command_line_parser():
|
|
|
71
71
|
return parser
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
def print_sheet_names(filename):
|
|
74
|
+
def print_sheet_names(filename) -> None:
|
|
75
75
|
for sheet in Document(filename).sheets:
|
|
76
76
|
print(f"{filename}: {sheet.name}")
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def print_table_names(filename):
|
|
79
|
+
def print_table_names(filename) -> None:
|
|
80
80
|
for sheet in Document(filename).sheets:
|
|
81
81
|
for table in sheet.tables:
|
|
82
82
|
print(f"{filename}: {sheet.name}: {table.name}")
|
|
@@ -85,19 +85,18 @@ def print_table_names(filename):
|
|
|
85
85
|
def cell_as_string(args, cell):
|
|
86
86
|
if isinstance(cell, ErrorCell) and not (args.formulas):
|
|
87
87
|
return "#REF!"
|
|
88
|
-
|
|
88
|
+
if args.formulas and cell.formula is not None:
|
|
89
89
|
return cell.formula
|
|
90
|
-
|
|
90
|
+
if args.formatting and cell.formatted_value is not None:
|
|
91
91
|
return cell.formatted_value
|
|
92
|
-
|
|
92
|
+
if isinstance(cell, NumberCell):
|
|
93
93
|
return sigfig(cell.value, sigfigs=MAX_SIGNIFICANT_DIGITS, warn=False)
|
|
94
|
-
|
|
94
|
+
if cell.value is None:
|
|
95
95
|
return ""
|
|
96
|
-
|
|
97
|
-
return str(cell.value)
|
|
96
|
+
return str(cell.value)
|
|
98
97
|
|
|
99
98
|
|
|
100
|
-
def print_table(args, filename):
|
|
99
|
+
def print_table(args, filename) -> None:
|
|
101
100
|
writer = csv.writer(sys.stdout, dialect="excel")
|
|
102
101
|
for sheet in Document(filename).sheets:
|
|
103
102
|
if args.sheet is not None and sheet.name not in args.sheet:
|
|
@@ -112,7 +111,7 @@ def print_table(args, filename):
|
|
|
112
111
|
writer.writerow(cells)
|
|
113
112
|
|
|
114
113
|
|
|
115
|
-
def main():
|
|
114
|
+
def main() -> None:
|
|
116
115
|
parser = command_line_parser()
|
|
117
116
|
args = parser.parse_args()
|
|
118
117
|
|
|
@@ -138,7 +137,7 @@ def main():
|
|
|
138
137
|
print_table_names(filename)
|
|
139
138
|
else:
|
|
140
139
|
print_table(args, filename)
|
|
141
|
-
except FileFormatError as e:
|
|
140
|
+
except FileFormatError as e: # noqa: PERF203
|
|
142
141
|
print(f"{filename}:", str(e), file=sys.stderr)
|
|
143
142
|
sys.exit(1)
|
|
144
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
|
|
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 =
|
|
77
|
-
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:
|
|
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
|
-
|
|
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
|
|
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,
|
|
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)
|
|
@@ -64,13 +64,13 @@ class NumbersUnpacker(IWorkHandler):
|
|
|
64
64
|
with open(target_path, "wb") as out:
|
|
65
65
|
out.write(blob)
|
|
66
66
|
|
|
67
|
-
def ensure_directory_exists(self, path: str):
|
|
67
|
+
def ensure_directory_exists(self, path: str) -> None:
|
|
68
68
|
"""Ensure that a path's directory exists."""
|
|
69
69
|
parts = os.path.split(path)
|
|
70
70
|
with contextlib.suppress(OSError):
|
|
71
71
|
os.makedirs(os.path.join(*([self.output_dir, *list(parts[:-1])])))
|
|
72
72
|
|
|
73
|
-
def prettify_uuids(self, obj: object):
|
|
73
|
+
def prettify_uuids(self, obj: object) -> None:
|
|
74
74
|
if isinstance(obj, dict):
|
|
75
75
|
for k, v in obj.items():
|
|
76
76
|
if isinstance(v, dict):
|
|
@@ -90,7 +90,7 @@ class NumbersUnpacker(IWorkHandler):
|
|
|
90
90
|
elif isinstance(v, list):
|
|
91
91
|
self.prettify_uuids(v)
|
|
92
92
|
|
|
93
|
-
def prettify_cell_storage(self, obj: object):
|
|
93
|
+
def prettify_cell_storage(self, obj: object) -> None:
|
|
94
94
|
if isinstance(obj, dict):
|
|
95
95
|
for k, v in obj.items():
|
|
96
96
|
if isinstance(v, (dict, list)):
|
|
@@ -117,7 +117,7 @@ class NumbersUnpacker(IWorkHandler):
|
|
|
117
117
|
return version in SUPPORTED_NUMBERS_VERSIONS
|
|
118
118
|
|
|
119
119
|
|
|
120
|
-
def main():
|
|
120
|
+
def main() -> None:
|
|
121
121
|
parser = ArgumentParser()
|
|
122
122
|
parser.add_argument("document", help="Apple Numbers file(s)", nargs="*")
|
|
123
123
|
parser.add_argument("-V", "--version", action="store_true")
|