ggblab 0.9.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. ggblab/__init__.py +44 -0
  2. ggblab/_version.py +4 -0
  3. ggblab/comm.py +243 -0
  4. ggblab/construction.py +179 -0
  5. ggblab/errors.py +142 -0
  6. ggblab/ggbapplet.py +293 -0
  7. ggblab/parser.py +486 -0
  8. ggblab/persistent_counter.py +175 -0
  9. ggblab/schema.py +114 -0
  10. ggblab/utils.py +109 -0
  11. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/build_log.json +730 -0
  12. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/install.json +5 -0
  13. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/package.json +210 -0
  14. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/schemas/ggblab/package.json.orig +205 -0
  15. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/schemas/ggblab/plugin.json +8 -0
  16. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/lib_index_js.bbfa36bc62ee08eb62b2.js +465 -0
  17. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/lib_index_js.bbfa36bc62ee08eb62b2.js.map +1 -0
  18. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/remoteEntry.2d29364aef8b527d773e.js +568 -0
  19. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/remoteEntry.2d29364aef8b527d773e.js.map +1 -0
  20. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/style.js +4 -0
  21. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/style_index_js.aab9f5416f41ce79cac3.js +492 -0
  22. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/style_index_js.aab9f5416f41ce79cac3.js.map +1 -0
  23. ggblab-0.9.3.dist-info/METADATA +768 -0
  24. ggblab-0.9.3.dist-info/RECORD +26 -0
  25. ggblab-0.9.3.dist-info/WHEEL +4 -0
  26. ggblab-0.9.3.dist-info/licenses/LICENSE +29 -0
ggblab/schema.py ADDED
@@ -0,0 +1,114 @@
1
+ import requests
2
+ import os
3
+ import xmlschema
4
+ import io
5
+ # from pprint import pprint
6
+
7
+ class ggb_schema:
8
+ """GeoGebra XML schema loader and validator.
9
+
10
+ Manages the GeoGebra XML schema (XSD) for validating and parsing .ggb
11
+ construction files. The schema is automatically downloaded from the
12
+ official GeoGebra site and cached locally for offline use.
13
+
14
+ The schema enables:
15
+ - XML validation of GeoGebra constructions
16
+ - Conversion between XML and Python dictionaries
17
+ - Type-safe parsing of construction elements
18
+
19
+ Attributes:
20
+ url (str): URL of the GeoGebra common.xsd schema file
21
+ local_path (str): Local cache path for the downloaded schema
22
+ schema_content (str): Raw XSD content as string
23
+ schema (xmlschema.XMLSchema): Compiled schema object for validation
24
+
25
+ Example:
26
+ >>> schema = ggb_schema()
27
+ >>> # Schema is loaded and ready for use
28
+ >>> data_dict = schema.schema.to_dict(xml_string)
29
+
30
+ Note:
31
+ The schema is downloaded once and cached in xsd/common.xsd.
32
+ Delete the cache to force re-download on next instantiation.
33
+ """
34
+ url = 'http://www.geogebra.org/apps/xsd/common.xsd'
35
+ local_path = 'xsd/common.xsd'
36
+
37
+ def __init__(self):
38
+ """Initialize the schema loader and load/cache the XSD file.
39
+
40
+ Downloads the GeoGebra schema from the official URL if not already
41
+ cached locally. Creates the cache directory if it doesn't exist.
42
+
43
+ Raises:
44
+ xmlschema.validators.exceptions.XMLSchemaValidationError: If schema is invalid.
45
+ Exception: If schema download or loading fails.
46
+ """
47
+ # Ensure the cache directory exists
48
+ os.makedirs(os.path.dirname(self.local_path), exist_ok=True)
49
+ self.schema_content = cache_schema_locally(self.url, self.local_path)
50
+
51
+ # Assuming you have a geogebra.xml file (from an unzipped .ggb file)
52
+ # and the XSD files (ggb.xsd and common.xsd) downloaded locally
53
+ # or you can use the URL for the schema
54
+
55
+ # Create a schema instance (it automatically handles imported common.xsd)
56
+ try:
57
+ self.schema = xmlschema.XMLSchema(io.StringIO(self.schema_content))
58
+
59
+ # Convert the XML data to a Python dictionary
60
+ # data_dict = ggb_schema.to_dict(io.StringIO(r))
61
+
62
+ # Pretty print the resulting dictionary
63
+ # pprint(data_dict)
64
+
65
+ except xmlschema.validators.exceptions.XMLSchemaValidationError as e:
66
+ print(f"XML validation error: {e}")
67
+ except Exception as e:
68
+ print(f"An error occurred: {e}")
69
+
70
+
71
+ def cache_schema_locally(schema_url, local_file_path):
72
+ """Download and cache a schema file from URL.
73
+
74
+ Downloads an XML schema from the specified URL and saves it to a local
75
+ file for offline use. If the file already exists, uses the cached version
76
+ instead of re-downloading.
77
+
78
+ Args:
79
+ schema_url (str): URL of the schema file to download.
80
+ local_file_path (str): Path where the schema should be cached.
81
+
82
+ Returns:
83
+ str: Content of the schema file, or None if download fails.
84
+
85
+ Examples:
86
+ >>> content = cache_schema_locally(
87
+ ... 'http://example.com/schema.xsd',
88
+ ... 'cache/schema.xsd'
89
+ ... )
90
+ Using local cached file: cache/schema.xsd
91
+
92
+ Note:
93
+ Future enhancement: Add logic to check file age or Last-Modified
94
+ header to refresh stale cached schemas.
95
+ """
96
+ if os.path.exists(local_file_path):
97
+ print(f"Using local cached file: {local_file_path}")
98
+ with open(local_file_path, 'r', encoding='utf-8') as f:
99
+ return f.read()
100
+
101
+ print(f"Local file not found. Downloading from: {schema_url}")
102
+ try:
103
+ response = requests.get(schema_url)
104
+ response.raise_for_status() # Raise an exception for bad status codes
105
+
106
+ # Save the content to the local file
107
+ with open(local_file_path, 'w', encoding='utf-8') as f:
108
+ f.write(response.text)
109
+ print(f"Successfully downloaded and saved to: {local_file_path}")
110
+ return response.text
111
+
112
+ except requests.exceptions.RequestException as e:
113
+ print(f"Error downloading schema: {e}")
114
+ return None
ggblab/utils.py ADDED
@@ -0,0 +1,109 @@
1
+ """Common utility functions for ggblab.
2
+
3
+ Python's Design Grievances
4
+ ==========================
5
+
6
+ This module exists because Python's standard library refuses to include basic
7
+ utilities that every developer needs. Here are some legitimate complaints:
8
+
9
+ 1. **flatten() is not standardized**
10
+
11
+ Despite being one of the most common operations, Python forces you to either:
12
+ - Use itertools.chain.from_iterable() (only 1-level deep)
13
+ - Install more-itertools (external dependency)
14
+ - Write it yourself every single time
15
+
16
+ JavaScript has Array.flat(). Why doesn't Python have list.flatten()?
17
+
18
+ The excuse: "Strings are iterable, so it's ambiguous."
19
+ The reality: This is a solvable problem. Just treat str/bytes as atomic by default.
20
+
21
+ 2. **String as Iterable: The Perpetual Footgun**
22
+
23
+ str being iterable causes endless bugs:
24
+
25
+ >>> def process(items):
26
+ ... return [x for item in items for x in item]
27
+ >>> process(['abc', 'def']) # Expected: ['abc', 'def']
28
+ ['a', 'b', 'c', 'd', 'e', 'f'] # Oops!
29
+
30
+ We have to write `isinstance(x, (str, bytes))` checks EVERYWHERE.
31
+ This is a design flaw, not a feature.
32
+
33
+ 3. **Pattern Matching Underutilized (Since Python 3.10)**
34
+
35
+ Python 3.10 introduced structural pattern matching with beautiful tuple unpacking:
36
+
37
+ >>> match edges:
38
+ ... case []:
39
+ ... return "no edges"
40
+ ... case [single]:
41
+ ... return f"one edge: {single}"
42
+ ... case [first, second]:
43
+ ... return f"two edges: {first}, {second}"
44
+ ... case [first, *rest]:
45
+ ... return f"multiple edges starting with {first}"
46
+
47
+ Yet most Python code still uses:
48
+ - if len(edges) == 0: ...
49
+ - if len(edges) == 1: x = edges[0]; ...
50
+ - if len(edges) >= 2: first, second = edges[0], edges[1]; ...
51
+
52
+ Why? Because Python educators haven't caught up with modern features.
53
+ The language evolves, but teaching materials stay stuck in 2015.
54
+
55
+ 4. **Education Gap: Modern Python Features Ignored**
56
+
57
+ Python keeps adding excellent features that go unused:
58
+ - Walrus operator (:=) for cleaner loops
59
+ - Union types (X | Y instead of Union[X, Y])
60
+ - Structural pattern matching (match/case)
61
+ - Positional-only parameters (def f(x, /))
62
+
63
+ But most tutorials, Stack Overflow answers, and even production code
64
+ still use ancient patterns. This is educator negligence, plain and simple.
65
+
66
+ If you introduce a feature, TEACH IT. Otherwise, what's the point?
67
+
68
+ Now, the actual utilities:
69
+ """
70
+
71
+ from collections.abc import Iterable
72
+
73
+
74
+ def flatten(items):
75
+ """Recursively flatten nested iterables.
76
+
77
+ Converts nested structures like [[1, [2, 3]], 4] into [1, 2, 3, 4].
78
+ Strings and bytes are treated as atomic elements (not iterated).
79
+
80
+ Note: This function exists because Python refuses to standardize it.
81
+ Yes, we have to explicitly check for str/bytes because Python
82
+ decided strings should be iterable. Thanks for that footgun.
83
+
84
+ Args:
85
+ items: Any iterable that may contain nested iterables.
86
+
87
+ Yields:
88
+ Flattened items from the nested structure.
89
+
90
+ Examples:
91
+ >>> list(flatten([1, [2, 3], [[4], 5]]))
92
+ [1, 2, 3, 4, 5]
93
+
94
+ >>> list(flatten(['a', ['b', 'c'], 'd']))
95
+ ['a', 'b', 'c', 'd']
96
+
97
+ >>> list(flatten([1, [2, [3, [4]]]]))
98
+ [1, 2, 3, 4]
99
+
100
+ # Without the str check, this would break:
101
+ >>> list(flatten(['hello', 'world']))
102
+ ['hello', 'world'] # Not ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
103
+ """
104
+ for item in items:
105
+ # The infamous "str is iterable" check we all have to write
106
+ if isinstance(item, Iterable) and not isinstance(item, (str, bytes)):
107
+ yield from flatten(item)
108
+ else:
109
+ yield item