numbers-parser 4.15.1__tar.gz → 4.16.1__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.15.1 → numbers_parser-4.16.1}/PKG-INFO +23 -25
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/README.md +4 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/pyproject.toml +70 -57
- numbers_parser-4.16.1/setup.cfg +4 -0
- numbers_parser-4.16.1/src/build/extract_functions.py +104 -0
- numbers_parser-4.16.1/src/build/extract_mapping.py +102 -0
- numbers_parser-4.16.1/src/build/generate_fontmap.py +22 -0
- numbers_parser-4.16.1/src/build/generate_mapping.py +70 -0
- numbers_parser-4.16.1/src/build/protodump.py +353 -0
- numbers_parser-4.16.1/src/build/rename_proto_files.py +26 -0
- numbers_parser-4.16.1/src/build/replace_paths.py +15 -0
- numbers_parser-4.16.1/src/debug/dump_formats.py +29 -0
- numbers_parser-4.16.1/src/debug/dump_formula_nodes.py +117 -0
- numbers_parser-4.16.1/src/debug/dump_formulas.py +50 -0
- numbers_parser-4.16.1/src/debug/dump_metadata.py +39 -0
- numbers_parser-4.16.1/src/debug/dump_strokes.py +75 -0
- numbers_parser-4.16.1/src/debug/dump_tree.py +113 -0
- numbers_parser-4.16.1/src/debug/dump_uids.py +31 -0
- numbers_parser-4.16.1/src/debug/lldbutil.py +1451 -0
- numbers_parser-4.16.1/src/debug/profile_large_read.py +23 -0
- numbers_parser-4.16.1/src/debug/profile_large_write.py +20 -0
- numbers_parser-4.16.1/src/debug/sdiff-no-ids.py +69 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/_unpack_numbers.py +1 -7
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/constants.py +11 -2
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/document.py +28 -8
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/model.py +122 -2
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/numbers_cache.py +1 -1
- numbers_parser-4.16.1/src/numbers_parser.egg-info/PKG-INFO +490 -0
- numbers_parser-4.16.1/src/numbers_parser.egg-info/SOURCES.txt +114 -0
- numbers_parser-4.16.1/src/numbers_parser.egg-info/dependency_links.txt +1 -0
- numbers_parser-4.16.1/src/numbers_parser.egg-info/entry_points.txt +4 -0
- numbers_parser-4.16.1/src/numbers_parser.egg-info/requires.txt +8 -0
- numbers_parser-4.16.1/src/numbers_parser.egg-info/top_level.txt +1 -0
- numbers_parser-4.16.1/tests/test_all_formulas.py +88 -0
- numbers_parser-4.16.1/tests/test_api_change.py +28 -0
- numbers_parser-4.16.1/tests/test_borders.py +331 -0
- numbers_parser-4.16.1/tests/test_bullets.py +51 -0
- numbers_parser-4.16.1/tests/test_cat_numbers.py +283 -0
- numbers_parser-4.16.1/tests/test_categories.py +687 -0
- numbers_parser-4.16.1/tests/test_coverage.py +307 -0
- numbers_parser-4.16.1/tests/test_create_cells.py +82 -0
- numbers_parser-4.16.1/tests/test_csv2numbers.py +337 -0
- numbers_parser-4.16.1/tests/test_currency.py +25 -0
- numbers_parser-4.16.1/tests/test_folder.py +16 -0
- numbers_parser-4.16.1/tests/test_formatting.py +955 -0
- numbers_parser-4.16.1/tests/test_formulas.py +404 -0
- numbers_parser-4.16.1/tests/test_issues.py +608 -0
- numbers_parser-4.16.1/tests/test_large.py +39 -0
- numbers_parser-4.16.1/tests/test_memory_leaks.py +28 -0
- numbers_parser-4.16.1/tests/test_merges.py +79 -0
- numbers_parser-4.16.1/tests/test_package.py +58 -0
- numbers_parser-4.16.1/tests/test_properties.py +56 -0
- numbers_parser-4.16.1/tests/test_roman.py +24 -0
- numbers_parser-4.16.1/tests/test_save.py +348 -0
- numbers_parser-4.16.1/tests/test_slices.py +144 -0
- numbers_parser-4.16.1/tests/test_styles.py +444 -0
- numbers_parser-4.16.1/tests/test_table_size.py +115 -0
- numbers_parser-4.16.1/tests/test_tables.py +203 -0
- numbers_parser-4.16.1/tests/test_unpack_numbers.py +206 -0
- numbers_parser-4.16.1/tests/test_unsupported.py +8 -0
- numbers_parser-4.16.1/tests/test_uuids.py +37 -0
- numbers_parser-4.16.1/tests/test_version.py +52 -0
- numbers_parser-4.15.1/src/numbers_parser/data/empty.numbers +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/LICENSE.rst +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/__init__.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/_cat_numbers.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/_csv2numbers.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/bullets.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/cell.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/containers.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/currencies.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/exceptions.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/experimental.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/formula.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TNArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TNArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TNCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TNCommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSAArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSAArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSACommandArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCEArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCH3DArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCHArchives_Common_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCHArchives_GEN_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCHArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCHArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCHCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCHPreUFFArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCKArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSCKArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSDArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSDArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSDCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSKArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSPArchiveMessages_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSPDatabaseMessages_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSPMessages_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSSArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSSArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSTArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSTArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSTCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSTStylePropertyArchiving_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSWPArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSWPArchives_sos_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/TSWPCommandArchives_pb2.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/__init__.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/fontmap.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/functionmap.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/generated/mapping.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/iwafile.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/iwork.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/numbers_uuid.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/roman.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/tokenizer.py +0 -0
- {numbers_parser-4.15.1 → numbers_parser-4.16.1}/src/numbers_parser/xrefs.py +0 -0
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: numbers-parser
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.16.1
|
|
4
4
|
Summary: Read and write Apple Numbers spreadsheets
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
5
|
+
Author-email: Jon Connell <python@figsandfudge.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/masaccio/numbers-parser
|
|
8
|
+
Project-URL: documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
|
|
17
9
|
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Requires-
|
|
21
|
-
Requires-Dist: protobuf (>=4.0,<6.0)
|
|
22
|
-
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
23
|
-
Requires-Dist: python-snappy (>=0.7,<0.8)
|
|
24
|
-
Requires-Dist: setuptools (>=70.0.0)
|
|
25
|
-
Requires-Dist: sigfig (>=1.3.3,<2.0.0)
|
|
26
|
-
Project-URL: Documentation, https://github.com/masaccio/numbers-parser/blob/main/README.md
|
|
27
|
-
Project-URL: Repository, https://github.com/masaccio/numbers-parser
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: <4.0,>=3.9
|
|
28
13
|
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.rst
|
|
15
|
+
Requires-Dist: compact-json<2.0.0,>=1.1.3
|
|
16
|
+
Requires-Dist: protobuf<6.0,>=4.0
|
|
17
|
+
Requires-Dist: python-snappy<1.0,>=0.7
|
|
18
|
+
Requires-Dist: sigfig<2.0.0,>=1.3.3
|
|
19
|
+
Requires-Dist: setuptools>=70.0.0
|
|
20
|
+
Requires-Dist: importlib-resources>=6.1
|
|
21
|
+
Requires-Dist: enum-tools>=0.11
|
|
22
|
+
Requires-Dist: python-dateutil<3.0.0.0,>=2.9.0.post0
|
|
23
|
+
Dynamic: license-file
|
|
29
24
|
|
|
30
25
|
# numbers-parser
|
|
31
26
|
|
|
@@ -485,8 +480,11 @@ The following limitations are expected to always remain:
|
|
|
485
480
|
- Password-encrypted documents cannot be opened. You must first re-save without
|
|
486
481
|
a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
|
|
487
482
|
A UnsupportedError exception is raised when such documents are opened.
|
|
483
|
+
- Due to changes in the format of Numbers documents, decoding of category groups
|
|
484
|
+
(introduced in `numbers-parser` version 4.16) is supported only for documents
|
|
485
|
+
created by Numbers 12.0 and later. No warnings are issued for earlier
|
|
486
|
+
Numbers documents.
|
|
488
487
|
|
|
489
488
|
## License
|
|
490
489
|
|
|
491
490
|
All code in this repository is licensed under the [MIT License](https://github.com/masaccio/numbers-parser/blob/master/LICENSE.rst).
|
|
492
|
-
|
|
@@ -456,6 +456,10 @@ The following limitations are expected to always remain:
|
|
|
456
456
|
- Password-encrypted documents cannot be opened. You must first re-save without
|
|
457
457
|
a password to read (see [issue 88](https://github.com/masaccio/numbers-parser/issues/88) for details).
|
|
458
458
|
A UnsupportedError exception is raised when such documents are opened.
|
|
459
|
+
- Due to changes in the format of Numbers documents, decoding of category groups
|
|
460
|
+
(introduced in `numbers-parser` version 4.16) is supported only for documents
|
|
461
|
+
created by Numbers 12.0 and later. No warnings are issued for earlier
|
|
462
|
+
Numbers documents.
|
|
459
463
|
|
|
460
464
|
## License
|
|
461
465
|
|
|
@@ -1,73 +1,85 @@
|
|
|
1
|
-
[
|
|
2
|
-
authors = [
|
|
1
|
+
[project]
|
|
2
|
+
authors = [
|
|
3
|
+
{name = "Jon Connell", email = "python@figsandfudge.com"},
|
|
4
|
+
]
|
|
5
|
+
license = "MIT"
|
|
6
|
+
requires-python = "<4.0,>=3.9"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"compact-json<2.0.0,>=1.1.3",
|
|
9
|
+
"protobuf<6.0,>=4.0",
|
|
10
|
+
"python-snappy<1.0,>=0.7",
|
|
11
|
+
"sigfig<2.0.0,>=1.3.3",
|
|
12
|
+
"setuptools>=70.0.0",
|
|
13
|
+
"importlib-resources>=6.1",
|
|
14
|
+
"enum-tools>=0.11",
|
|
15
|
+
"python-dateutil<3.0.0.0,>=2.9.0.post0",
|
|
16
|
+
]
|
|
3
17
|
classifiers = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
18
|
+
"Topic :: Office/Business :: Financial :: Spreadsheet",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
7
21
|
]
|
|
8
22
|
description = "Read and write Apple Numbers spreadsheets"
|
|
9
|
-
documentation = "https://github.com/masaccio/numbers-parser/blob/main/README.md"
|
|
10
|
-
license = "MIT"
|
|
11
23
|
name = "numbers-parser"
|
|
12
|
-
packages = [{include = "numbers_parser", from = "src"}]
|
|
13
24
|
readme = "README.md"
|
|
25
|
+
version = "4.16.1"
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
14
28
|
repository = "https://github.com/masaccio/numbers-parser"
|
|
15
|
-
|
|
29
|
+
documentation = "https://github.com/masaccio/numbers-parser/blob/main/README.md"
|
|
16
30
|
|
|
17
|
-
[
|
|
31
|
+
[project.scripts]
|
|
18
32
|
cat-numbers = "numbers_parser._cat_numbers:main"
|
|
19
33
|
unpack-numbers = "numbers_parser._unpack_numbers:main"
|
|
20
34
|
csv2numbers = "numbers_parser._csv2numbers:main"
|
|
21
35
|
|
|
22
|
-
[
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
[tool.poetry.group.docs.dependencies]
|
|
53
|
-
sphinx = ">= 7.3"
|
|
54
|
-
enum-tools = ">=0.11"
|
|
55
|
-
sphinx-toolbox = ">=3.5"
|
|
56
|
-
sphinx-nefertiti = ">=0.7"
|
|
57
|
-
sphinx-markdown-builder = ">=0.6"
|
|
58
|
-
sphinx-copybutton = ">=0.5"
|
|
59
|
-
|
|
60
|
-
[tool.poetry.group.bootstrap]
|
|
61
|
-
optional = true
|
|
62
|
-
|
|
63
|
-
[tool.poetry.group.bootstrap.dependencies]
|
|
64
|
-
pyobjc-core = ">=10.2"
|
|
65
|
-
pyobjc-framework-Cocoa = ">=10.2"
|
|
66
|
-
py2app = ">=0.28"
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"gprof2dot<2023.0.0,>=2022.7.29",
|
|
39
|
+
"line-profiler<5.0.0,>=4.0.3",
|
|
40
|
+
"mock>=5.1.0",
|
|
41
|
+
"pytest>=7.2.0",
|
|
42
|
+
"pytest-check>=1.0",
|
|
43
|
+
"pytest-console-scripts<2.0.0,>=1.3.1",
|
|
44
|
+
"pytest-cov>=4.0,>=5.0",
|
|
45
|
+
"pytest-xdist<4.0.0,>=3.3.1",
|
|
46
|
+
"ruff",
|
|
47
|
+
"tox<5.0.0,>=4.11.4",
|
|
48
|
+
"python-magic>=0.4",
|
|
49
|
+
"tqdm>=4.66",
|
|
50
|
+
"colorama<1.0.0,>=0.4.6",
|
|
51
|
+
"pympler<2.0,>=1.1",
|
|
52
|
+
]
|
|
53
|
+
docs = [
|
|
54
|
+
"sphinx>=7.3",
|
|
55
|
+
"enum-tools>=0.11",
|
|
56
|
+
"sphinx-toolbox>=3.5",
|
|
57
|
+
"sphinx-nefertiti>=0.7",
|
|
58
|
+
"sphinx-markdown-builder>=0.6",
|
|
59
|
+
"sphinx-copybutton>=0.5",
|
|
60
|
+
]
|
|
61
|
+
bootstrap = [
|
|
62
|
+
"pyobjc-core>=10.2",
|
|
63
|
+
"pyobjc-framework-Cocoa>=10.2",
|
|
64
|
+
"py2app>=0.28",
|
|
65
|
+
]
|
|
67
66
|
|
|
68
67
|
[build-system]
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
requires = ["setuptools >= 61.0"]
|
|
69
|
+
build-backend = "setuptools.build_meta"
|
|
70
|
+
|
|
71
|
+
# [tool.setuptools]
|
|
72
|
+
# exclude-package-data = { "*" = ["tests*"]}
|
|
73
|
+
# include-package-data = false
|
|
74
|
+
# packages.find.include = ['numbers-parser/src*']
|
|
75
|
+
# packages.find.exclude = ['tests*']
|
|
76
|
+
# package-dir = { "" = "src" }
|
|
77
|
+
|
|
78
|
+
[tool.setuptools.packages.find]
|
|
79
|
+
where = ["src"] # list of folders that contain the packages (["."] by default)
|
|
80
|
+
include = ["numbers_parser*"] # package names should match these glob patterns (["*"] by default)
|
|
81
|
+
exclude = ["*tests*"] # exclude packages matching these glob patterns (empty by default)
|
|
82
|
+
namespaces = false # to disable scanning PEP 420 namespaces (true by default)
|
|
71
83
|
|
|
72
84
|
[tool.coverage.run]
|
|
73
85
|
branch = true
|
|
@@ -189,3 +201,4 @@ ban-relative-imports = "all"
|
|
|
189
201
|
"src/build/protodump.py" = ["PLR2004", "INP001", "PTH", "S110", "N806"]
|
|
190
202
|
"src/debug/**" = ["INP001"]
|
|
191
203
|
"tests/**" = ["PLR2004", "S101", "D103", "ANN201", "ANN001"]
|
|
204
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
from subprocess import PIPE, Popen
|
|
4
|
+
|
|
5
|
+
# Code pattern in AArch64:
|
|
6
|
+
#
|
|
7
|
+
# mov w8, #325
|
|
8
|
+
# strh w8, [sp, #8]
|
|
9
|
+
# str x23, [sp]
|
|
10
|
+
# adrp x2, 2590 ; 0x14e0000
|
|
11
|
+
# add x2, x2, #3776 ; Objc cfstring ref: @"GETPIVOTDATA"
|
|
12
|
+
#
|
|
13
|
+
# TSCEFunction_GETPIVOTDATA::evaluateWithContext(...
|
|
14
|
+
|
|
15
|
+
# Additional required code pattern in AArch64 for Numbers 14.2:
|
|
16
|
+
#
|
|
17
|
+
# mov w0, #319
|
|
18
|
+
# bl TSCEFormulaCreationMagic::function_3arg(...
|
|
19
|
+
# ;
|
|
20
|
+
# ; Approx. 20 lines
|
|
21
|
+
# ;
|
|
22
|
+
# TSCEFormulaCreationMagic::TEXTBETWEEN(...
|
|
23
|
+
|
|
24
|
+
if len(sys.argv) != 3:
|
|
25
|
+
print(f"Usage: {sys.argv[0]} framework-file output.py", file=sys.stderr)
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
framework = sys.argv[1]
|
|
29
|
+
output_map = sys.argv[2]
|
|
30
|
+
|
|
31
|
+
if framework.endswith(".s"):
|
|
32
|
+
with open(framework, "rb") as fh:
|
|
33
|
+
disassembly = fh.readlines()
|
|
34
|
+
else:
|
|
35
|
+
objdump = Popen( # noqa: S603
|
|
36
|
+
[ # noqa: S607
|
|
37
|
+
"objdump",
|
|
38
|
+
"--disassemble",
|
|
39
|
+
"--no-addresses",
|
|
40
|
+
"--no-print-imm-hex",
|
|
41
|
+
"--no-show-raw-insn",
|
|
42
|
+
"--macho",
|
|
43
|
+
"--objc-meta-data",
|
|
44
|
+
framework,
|
|
45
|
+
],
|
|
46
|
+
stdout=PIPE,
|
|
47
|
+
)
|
|
48
|
+
cxxfilt = Popen(["c++filt"], stdin=objdump.stdout, stdout=PIPE) # noqa: S603, S607
|
|
49
|
+
objdump.stdout.close()
|
|
50
|
+
disassembly = str(cxxfilt.communicate()[0]).split("\\n")
|
|
51
|
+
|
|
52
|
+
arg = None
|
|
53
|
+
line_count = 0
|
|
54
|
+
tsce_functions = {}
|
|
55
|
+
function_refs = {}
|
|
56
|
+
|
|
57
|
+
previous_line = ""
|
|
58
|
+
for line in disassembly:
|
|
59
|
+
line = str(line).replace("\\t", " ") # noqa: PLW2901
|
|
60
|
+
if m := re.search(r"mov *w8, #(\d+)", line):
|
|
61
|
+
arg = m.group(1)
|
|
62
|
+
line_count = 0
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if arg is not None:
|
|
66
|
+
line_count += 1
|
|
67
|
+
if line_count > 30:
|
|
68
|
+
arg = None
|
|
69
|
+
line_count = 0
|
|
70
|
+
|
|
71
|
+
if m := re.search(r'x2, x2.*Objc cfstring ref: @"([A-Z0-9\.]+)"', line):
|
|
72
|
+
if arg is not None and line_count <= 8:
|
|
73
|
+
func = m.group(1).replace("_", ".")
|
|
74
|
+
print(f"Found cstring {func} = {arg}")
|
|
75
|
+
function_refs[func] = arg
|
|
76
|
+
arg = None
|
|
77
|
+
line_count = 0
|
|
78
|
+
elif m := re.search(r"bl *TSCEFormulaCreationMagic::function_3arg\(", line):
|
|
79
|
+
if m := re.search(r"mov *w0, #(\d+)", previous_line):
|
|
80
|
+
arg = m.group(1)
|
|
81
|
+
line_count = 0
|
|
82
|
+
elif (m := re.search(r"TSCEFormulaCreationMagic::(\w+)\(", line)) and arg is not None:
|
|
83
|
+
func = m.group(1).replace("_", ".")
|
|
84
|
+
print(f"Found TSCEFormulaCreationMagic {func} = {arg}")
|
|
85
|
+
function_refs[func] = arg
|
|
86
|
+
arg = None
|
|
87
|
+
line_count = 0
|
|
88
|
+
elif m := re.search(r"TSCEFunction_(\w+)::evaluateWithContext", line):
|
|
89
|
+
func = m.group(1).replace("_", ".")
|
|
90
|
+
print(f"Found TSCEFunction {func}")
|
|
91
|
+
tsce_functions[func] = True
|
|
92
|
+
|
|
93
|
+
previous_line = line
|
|
94
|
+
|
|
95
|
+
function_refs = dict(sorted(function_refs.items(), key=lambda x: int(x[1])))
|
|
96
|
+
with open(output_map, "w") as fh:
|
|
97
|
+
fh.write("FUNCTION_MAP = {\n")
|
|
98
|
+
if "ABS" not in function_refs:
|
|
99
|
+
fh.write(' 1: "ABS",\n')
|
|
100
|
+
for func_name, func_id in function_refs.items():
|
|
101
|
+
if func in tsce_functions:
|
|
102
|
+
fh.write(f' {func_id}: "{func_name}",\n')
|
|
103
|
+
|
|
104
|
+
fh.write("}\n")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
lldb script to dump TSPersistence registry mapping from iWork apps.
|
|
3
|
+
|
|
4
|
+
Licensed under the MIT license.
|
|
5
|
+
|
|
6
|
+
Copyright 2020-2022 Peter Sobot
|
|
7
|
+
Copyright 2022 Jon Connell
|
|
8
|
+
Copyright 2022 SheetJS LLC
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import lldb
|
|
16
|
+
|
|
17
|
+
from debug.lldbutil import print_stacktrace
|
|
18
|
+
|
|
19
|
+
if len(sys.argv) != 3:
|
|
20
|
+
msg = f"Usage: {sys.argv[0]} exe-file output.json"
|
|
21
|
+
raise (ValueError(msg))
|
|
22
|
+
|
|
23
|
+
exe = sys.argv[1]
|
|
24
|
+
output = sys.argv[2]
|
|
25
|
+
|
|
26
|
+
debugger = lldb.SBDebugger.Create()
|
|
27
|
+
debugger.SetAsync(False)
|
|
28
|
+
target = debugger.CreateTargetWithFileAndArch(exe, None)
|
|
29
|
+
|
|
30
|
+
# # Note: original script also created breakpoints on _handleAEOpenEvent
|
|
31
|
+
# # but that is too early in Numbers 12.1
|
|
32
|
+
# target.BreakpointCreateByName("-[NSApplication _sendFinishLaunchingNotification]")
|
|
33
|
+
# target.BreakpointCreateByName("-[NSApplication _crashOnException:]")
|
|
34
|
+
|
|
35
|
+
# # Note: original script skipped [CKContainer containerWithIdentifier:]
|
|
36
|
+
# target.BreakpointCreateByRegex("CloudKit")
|
|
37
|
+
|
|
38
|
+
target.BreakpointCreateByName("_sendFinishLaunchingNotification")
|
|
39
|
+
target.BreakpointCreateByName("_handleAEOpenEvent:")
|
|
40
|
+
# To get around the fact that we don't have iCloud entitlements when running re-signed code,
|
|
41
|
+
# let's break in the CloudKit code and early exit the function before it can raise an exception:
|
|
42
|
+
target.BreakpointCreateByName("[CKContainer containerWithIdentifier:]")
|
|
43
|
+
# In later Keynote versions, 'containerWithIdentifier' isn't called directly, but we can break on similar methods:
|
|
44
|
+
# Note: this __lldb_unnamed_symbol hack was determined by painstaking experimentation. It will break again for sure.
|
|
45
|
+
target.BreakpointCreateByRegex("___lldb_unnamed_symbol[0-9]+", "CloudKit")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
process = target.LaunchSimple(None, None, os.getcwd())
|
|
49
|
+
if not process:
|
|
50
|
+
raise ValueError("Failed to launch process: " + exe)
|
|
51
|
+
|
|
52
|
+
if process.GetState() == lldb.eStateExited:
|
|
53
|
+
msg = f"LLDB was unable to stop process! {process}"
|
|
54
|
+
raise ValueError(msg)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
while process.GetState() == lldb.eStateStopped:
|
|
58
|
+
thread = process.GetThreadAtIndex(0)
|
|
59
|
+
frame = thread.GetSelectedFrame()
|
|
60
|
+
if frame.name == "-[NSApplication _crashOnException:]":
|
|
61
|
+
msg = f"Process crashed at {frame.name}"
|
|
62
|
+
raise ValueError(msg)
|
|
63
|
+
|
|
64
|
+
stop_reason = thread.GetStopReason()
|
|
65
|
+
|
|
66
|
+
if stop_reason == lldb.eStopReasonException:
|
|
67
|
+
print_stacktrace(thread)
|
|
68
|
+
function = frame.GetFunction()
|
|
69
|
+
function_or_symbol = function if function else frame.GetSymbol()
|
|
70
|
+
msg = f"Exception at {frame.name}"
|
|
71
|
+
raise ValueError(msg)
|
|
72
|
+
if stop_reason != lldb.eStopReasonBreakpoint:
|
|
73
|
+
process.Continue()
|
|
74
|
+
continue
|
|
75
|
+
if frame.name[-8:] == "CloudKit":
|
|
76
|
+
thread.ReturnFromFrame(
|
|
77
|
+
thread.GetSelectedFrame(),
|
|
78
|
+
lldb.SBValue().CreateValueFromExpression("0", ""),
|
|
79
|
+
)
|
|
80
|
+
process.Continue()
|
|
81
|
+
elif frame.name == "-[NSApplication _sendFinishLaunchingNotification]":
|
|
82
|
+
registry = frame.EvaluateExpression("[TSPRegistry sharedRegistry]")
|
|
83
|
+
error = registry.GetError()
|
|
84
|
+
if error.fail or registry.description is None:
|
|
85
|
+
continue
|
|
86
|
+
# raise (ValueError("Failed to extract registry"))
|
|
87
|
+
split = [
|
|
88
|
+
x.strip().split(" -> ")
|
|
89
|
+
for x in registry.description.split("{")[1].split("}")[0].split("\n")
|
|
90
|
+
if x.strip()
|
|
91
|
+
]
|
|
92
|
+
json_str = json.dumps(
|
|
93
|
+
dict(sorted([(int(a), b.split(" ")[-1]) for a, b in split if "null" not in b])),
|
|
94
|
+
indent=2,
|
|
95
|
+
)
|
|
96
|
+
with open(output, "w") as fh:
|
|
97
|
+
fh.write(json_str)
|
|
98
|
+
break
|
|
99
|
+
else:
|
|
100
|
+
process.Continue()
|
|
101
|
+
finally:
|
|
102
|
+
process.Kill()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import Cocoa
|
|
4
|
+
|
|
5
|
+
if len(sys.argv) != 2:
|
|
6
|
+
msg = f"Usage: {sys.argv[0]} fontmap.py"
|
|
7
|
+
raise (ValueError(msg))
|
|
8
|
+
|
|
9
|
+
mapping_py = sys.argv[1]
|
|
10
|
+
|
|
11
|
+
with open(mapping_py, "w") as fh:
|
|
12
|
+
manager = Cocoa.NSFontManager.sharedFontManager()
|
|
13
|
+
font_families = list(manager.availableFontFamilies())
|
|
14
|
+
|
|
15
|
+
print("FONT_NAME_TO_FAMILY = {", file=fh)
|
|
16
|
+
for family in sorted(font_families):
|
|
17
|
+
fonts = manager.availableMembersOfFontFamily_(family)
|
|
18
|
+
for font in fonts:
|
|
19
|
+
print(f' "{font[0]}": "{family}",', file=fh)
|
|
20
|
+
print(' "Calibri": "Calibri",', file=fh)
|
|
21
|
+
print(' "Cambria": "Cambria",', file=fh)
|
|
22
|
+
print("}", file=fh)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
if len(sys.argv) != 3:
|
|
6
|
+
msg = f"Usage: {sys.argv[0]} mapping.json mapping.py"
|
|
7
|
+
raise (ValueError(msg))
|
|
8
|
+
|
|
9
|
+
mapping_json = sys.argv[1]
|
|
10
|
+
mapping_py = sys.argv[2]
|
|
11
|
+
|
|
12
|
+
modules = []
|
|
13
|
+
module_import_output = ""
|
|
14
|
+
module_files_output = ""
|
|
15
|
+
mapping_output = ""
|
|
16
|
+
for filename in os.listdir("src/numbers_parser/generated"):
|
|
17
|
+
if "pb2" in filename:
|
|
18
|
+
module = filename.replace("_pb2.py", "")
|
|
19
|
+
modules.append(module)
|
|
20
|
+
module_import_output += f"from numbers_parser.generated import {module}_pb2 as {module}\n"
|
|
21
|
+
module_files_output += f" {module},\n"
|
|
22
|
+
|
|
23
|
+
with open(mapping_json) as fh:
|
|
24
|
+
mappings = json.load(fh)
|
|
25
|
+
|
|
26
|
+
for index, symbol in sorted(mappings.items(), key=lambda x: int(x[0])):
|
|
27
|
+
mapping_output += f' "{index}": "{symbol}",\n'
|
|
28
|
+
|
|
29
|
+
OUTPUT_CODE = f"""{module_import_output.strip()}
|
|
30
|
+
|
|
31
|
+
PROTO_FILES = [
|
|
32
|
+
{module_files_output.strip()}
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
TSPRegistryMapping = {{
|
|
36
|
+
{mapping_output.strip()}
|
|
37
|
+
}}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def compute_maps():
|
|
41
|
+
name_class_map = {{}}
|
|
42
|
+
|
|
43
|
+
def add_nested_types(message_type):
|
|
44
|
+
for name in dict(message_type.DESCRIPTOR.nested_types_by_name):
|
|
45
|
+
child_type = getattr(message_type, name)
|
|
46
|
+
name_class_map[child_type.DESCRIPTOR.full_name] = child_type
|
|
47
|
+
add_nested_types(child_type)
|
|
48
|
+
|
|
49
|
+
for file in PROTO_FILES:
|
|
50
|
+
for message_name in dict(file.DESCRIPTOR.message_types_by_name):
|
|
51
|
+
message_type = getattr(file, message_name)
|
|
52
|
+
name_class_map[message_type.DESCRIPTOR.full_name] = message_type
|
|
53
|
+
add_nested_types(message_type)
|
|
54
|
+
|
|
55
|
+
id_name_map = {{}}
|
|
56
|
+
name_id_map = {{}}
|
|
57
|
+
for k, v in list(TSPRegistryMapping.items()):
|
|
58
|
+
if v in name_class_map: # pragma: no branch
|
|
59
|
+
id_name_map[int(k)] = name_class_map[v]
|
|
60
|
+
if v not in name_id_map:
|
|
61
|
+
name_id_map[v] = int(k)
|
|
62
|
+
|
|
63
|
+
return name_class_map, id_name_map, name_id_map
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
NAME_CLASS_MAP, ID_NAME_MAP, NAME_ID_MAP = compute_maps()
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
with open(mapping_py, "w") as fh:
|
|
70
|
+
fh.write(OUTPUT_CODE)
|