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.
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/PKG-INFO +18 -22
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/README.md +14 -15
- numbers_parser-4.14.2/pyproject.toml +193 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/__init__.py +5 -4
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/_cat_numbers.py +24 -16
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/_csv2numbers.py +13 -14
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/_unpack_numbers.py +6 -7
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/bullets.py +7 -8
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/cell.py +280 -255
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/constants.py +22 -8
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/containers.py +11 -10
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/document.py +196 -150
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/exceptions.py +1 -8
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/formula.py +29 -32
- 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.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/fontmap.py +16 -10
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/mapping.py +0 -1
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/iwafile.py +16 -16
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/iwork.py +32 -17
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/model.py +222 -210
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/numbers_cache.py +6 -7
- {numbers_parser-4.13.3 → 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.13.3/pyproject.toml +0 -149
- numbers_parser-4.13.3/src/numbers_parser/generated/TSKArchives_pb2.py +0 -146
- numbers_parser-4.13.3/src/numbers_parser/generated/TSSArchives_pb2.py +0 -64
- numbers_parser-4.13.3/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -133
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/LICENSE.rst +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/currencies.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/data/empty.numbers +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/experimental.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
- {numbers_parser-4.13.3 → numbers_parser-4.14.2}/src/numbers_parser/generated/__init__.py +0 -0
- {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.
|
|
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:
|
|
26
|
-
Requires-Dist:
|
|
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.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
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.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
88
|
+
if args.formulas and cell.formula is not None:
|
|
80
89
|
return cell.formula
|
|
81
|
-
|
|
90
|
+
if args.formatting and cell.formatted_value is not None:
|
|
82
91
|
return cell.formatted_value
|
|
83
|
-
|
|
84
|
-
return sigfig
|
|
85
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
@@ -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 =
|
|
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] =
|
|
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:
|
|
28
|
-
ListStyleArchive.kRomanUpperDoubleParen: lambda x:
|
|
29
|
-
ListStyleArchive.kRomanUpperRightParen: lambda x:
|
|
30
|
-
ListStyleArchive.kRomanLowerDecimal: lambda x:
|
|
31
|
-
ListStyleArchive.kRomanLowerDoubleParen: lambda x:
|
|
32
|
-
ListStyleArchive.kRomanLowerRightParen: lambda x:
|
|
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),
|