chemql-python 1.6.1__tar.gz → 1.6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {chemql_python-1.6.1 → chemql_python-1.6.2}/PKG-INFO +69 -2
  2. {chemql_python-1.6.1 → chemql_python-1.6.2}/README.md +68 -1
  3. {chemql_python-1.6.1 → chemql_python-1.6.2}/pyproject.toml +1 -1
  4. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/__init__.py +94 -8
  5. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/codecql.py +2 -2
  6. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql_python.egg-info/PKG-INFO +69 -2
  7. {chemql_python-1.6.1 → chemql_python-1.6.2}/LICENSE +0 -0
  8. {chemql_python-1.6.1 → chemql_python-1.6.2}/setup.cfg +0 -0
  9. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/__main__.py +0 -0
  10. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/build.py +0 -0
  11. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/colors.json +0 -0
  12. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/data/BondEnergies.json +0 -0
  13. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/data/MoleculeBonds.json +0 -0
  14. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/data/Molecules.json +0 -0
  15. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/data/PeriodicTableJSON.json +0 -0
  16. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/data/Reactions.json +0 -0
  17. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
  18. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
  19. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/html/periodic_table.html +0 -0
  20. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/README.md +0 -0
  21. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/collections.cql +0 -0
  22. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/datetime.cql +0 -0
  23. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/decimal.cql +0 -0
  24. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/fractions.cql +0 -0
  25. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/json.cql +0 -0
  26. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/math/statistics.cql +0 -0
  27. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/math.cql +0 -0
  28. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/pathlib.cql +0 -0
  29. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/random.cql +0 -0
  30. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql/stdlib/text.cql +0 -0
  31. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql_python.egg-info/SOURCES.txt +0 -0
  32. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql_python.egg-info/dependency_links.txt +0 -0
  33. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql_python.egg-info/entry_points.txt +0 -0
  34. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql_python.egg-info/requires.txt +0 -0
  35. {chemql_python-1.6.1 → chemql_python-1.6.2}/src/chemql_python.egg-info/top_level.txt +0 -0
  36. {chemql_python-1.6.1 → chemql_python-1.6.2}/tests/test_codecql.py +0 -0
  37. {chemql_python-1.6.1 → chemql_python-1.6.2}/tests/test_external_molecule_lookup.py +0 -0
  38. {chemql_python-1.6.1 → chemql_python-1.6.2}/tests/test_public_api.py +0 -0
  39. {chemql_python-1.6.1 → chemql_python-1.6.2}/tests/test_reactions.py +0 -0
  40. {chemql_python-1.6.1 → chemql_python-1.6.2}/tests/test_stdlib.py +0 -0
  41. {chemql_python-1.6.1 → chemql_python-1.6.2}/tests/test_structured_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: A chemistry-focused query language and REPL
5
5
  Author-email: Mohammad Rakibul Islam <rakibulofficial1017@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -20,7 +20,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
20
20
  balance equations, and combine chemistry queries with Python and shell
21
21
  commands.
22
22
 
23
- - **Version:** `1.6.1`
23
+ - **Version:** `1.6.2`
24
24
  - **PyPI distribution:** `chemql-python`
25
25
  - **Python import / CLI name:** `chemql`
26
26
  - **Author:** Mohammad Rakibul Islam
@@ -889,6 +889,73 @@ available before loading the package. `pathlib::read_text` and
889
889
  `pathlib::write_text` access files with the permissions of the ChemQL
890
890
  process.
891
891
 
892
+ ## Importing Package Members
893
+
894
+ ChemQL allows you to import individual members from a package while retaining access to the package's other members through its qualified namespace.
895
+
896
+ ### Importing a Single Member
897
+
898
+ Use `load MEMBER from PACKAGE` to import a specific member into the current scope.
899
+
900
+ ```chemql
901
+ load sin from math
902
+
903
+ sin{10}
904
+ ```
905
+
906
+ The imported member can be called directly without specifying its package name.
907
+
908
+ ### Importing with an Alias
909
+
910
+ You can use `as` to assign a different name to an imported member.
911
+
912
+ ```chemql
913
+ load cos from math as c
914
+
915
+ c{10}
916
+ ```
917
+
918
+ This is useful when you want a shorter name or want to avoid naming conflicts.
919
+
920
+ ### Qualified Namespace Access
921
+
922
+ Importing an individual member does not prevent you from accessing other members of its package. ChemQL can initialize the package as needed and resolve qualified function calls using the package namespace.
923
+
924
+ ```chemql
925
+ load sin from math
926
+
927
+ # Call the imported member directly
928
+ sin{10}
929
+
930
+ # Access other package members
931
+ math::cos{10}
932
+ math::sin{30}
933
+ ```
934
+
935
+ You can therefore combine direct member imports with qualified calls in the same program.
936
+
937
+ ### Importing an Entire Package
938
+
939
+ To load a package explicitly, use `load PACKAGE` or assign it an alias with `as`.
940
+
941
+ ```chemql
942
+ load math
943
+
944
+ math::sin{10}
945
+ math::cos{10}
946
+ ```
947
+
948
+ You can also assign a package alias:
949
+
950
+ ```chemql
951
+ load math as MATH
952
+
953
+ MATH::sin{10}
954
+ MATH::cos{10}
955
+ ```
956
+
957
+ **Note:** ChemQL's package initialization and namespace resolution allow qualified package functions to remain accessible even when you initially import only one member. Importing a member is therefore a convenience for direct access, not a restriction on the rest of the package.
958
+
892
959
  ## Built-in help
893
960
 
894
961
  The help system includes a general index and topic/function pages:
@@ -6,7 +6,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
6
6
  balance equations, and combine chemistry queries with Python and shell
7
7
  commands.
8
8
 
9
- - **Version:** `1.6.1`
9
+ - **Version:** `1.6.2`
10
10
  - **PyPI distribution:** `chemql-python`
11
11
  - **Python import / CLI name:** `chemql`
12
12
  - **Author:** Mohammad Rakibul Islam
@@ -875,6 +875,73 @@ available before loading the package. `pathlib::read_text` and
875
875
  `pathlib::write_text` access files with the permissions of the ChemQL
876
876
  process.
877
877
 
878
+ ## Importing Package Members
879
+
880
+ ChemQL allows you to import individual members from a package while retaining access to the package's other members through its qualified namespace.
881
+
882
+ ### Importing a Single Member
883
+
884
+ Use `load MEMBER from PACKAGE` to import a specific member into the current scope.
885
+
886
+ ```chemql
887
+ load sin from math
888
+
889
+ sin{10}
890
+ ```
891
+
892
+ The imported member can be called directly without specifying its package name.
893
+
894
+ ### Importing with an Alias
895
+
896
+ You can use `as` to assign a different name to an imported member.
897
+
898
+ ```chemql
899
+ load cos from math as c
900
+
901
+ c{10}
902
+ ```
903
+
904
+ This is useful when you want a shorter name or want to avoid naming conflicts.
905
+
906
+ ### Qualified Namespace Access
907
+
908
+ Importing an individual member does not prevent you from accessing other members of its package. ChemQL can initialize the package as needed and resolve qualified function calls using the package namespace.
909
+
910
+ ```chemql
911
+ load sin from math
912
+
913
+ # Call the imported member directly
914
+ sin{10}
915
+
916
+ # Access other package members
917
+ math::cos{10}
918
+ math::sin{30}
919
+ ```
920
+
921
+ You can therefore combine direct member imports with qualified calls in the same program.
922
+
923
+ ### Importing an Entire Package
924
+
925
+ To load a package explicitly, use `load PACKAGE` or assign it an alias with `as`.
926
+
927
+ ```chemql
928
+ load math
929
+
930
+ math::sin{10}
931
+ math::cos{10}
932
+ ```
933
+
934
+ You can also assign a package alias:
935
+
936
+ ```chemql
937
+ load math as MATH
938
+
939
+ MATH::sin{10}
940
+ MATH::cos{10}
941
+ ```
942
+
943
+ **Note:** ChemQL's package initialization and namespace resolution allow qualified package functions to remain accessible even when you initially import only one member. Importing a member is therefore a convenience for direct access, not a restriction on the rest of the package.
944
+
878
945
  ## Built-in help
879
946
 
880
947
  The help system includes a general index and topic/function pages:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "chemql-python"
7
- version = "1.6.1"
7
+ version = "1.6.2"
8
8
  authors = [
9
9
  { name = "Mohammad Rakibul Islam", email = "rakibulofficial1017@gmail.com" }
10
10
  ]
@@ -68,7 +68,7 @@ __all__ = [
68
68
  "__version__"
69
69
  ]
70
70
 
71
- __version__ = "1.6.1"
71
+ __version__ = "1.6.2"
72
72
 
73
73
  # Persistent preprocessor/function state for the current ChemQL session.
74
74
  DEFINITIONS = {}
@@ -1094,6 +1094,24 @@ def _expand_function_calls(text):
1094
1094
  if match:
1095
1095
  name = match.group(1)
1096
1096
  resolved_name = _resolve_package_qualified_name(name)
1097
+ # Enforce explicit package loading BEFORE checking FUNCTIONS.
1098
+ # Otherwise a function registered while internally initializing
1099
+ # a package bypasses the qualified-access restriction below.
1100
+ if "::" in resolved_name:
1101
+ package_name = resolved_name.rsplit("::", 1)[0]
1102
+ root_package = package_name.split("::", 1)[0]
1103
+ explicitly_loaded_packages = globals().get(
1104
+ "CHEMQL_EXPLICITLY_LOADED_PACKAGES", set()
1105
+ )
1106
+ if (
1107
+ package_name not in explicitly_loaded_packages
1108
+ and root_package not in explicitly_loaded_packages
1109
+ ):
1110
+ raise ChemQLError(
1111
+ f"Unknown function `{name}`"
1112
+ if NO_ANSI else
1113
+ f"Unknown function \\033[1m{name}\\033[22m"
1114
+ )
1097
1115
  opening = i + match.end() - 1
1098
1116
  end = find_matching(text, opening, "{", "}")
1099
1117
  args = _split_function_arguments(text[opening + 1:end])
@@ -1103,6 +1121,47 @@ def _expand_function_calls(text):
1103
1121
  value = _call_function(resolved_name, args)
1104
1122
  elif "::" not in resolved_name and resolved_name in BUILTIN_FUNCTIONS:
1105
1123
  value = _call_builtin(resolved_name, args)
1124
+ elif (
1125
+ "::" not in resolved_name
1126
+ and resolved_name in CHEMQL_VARIABLES
1127
+ and callable(python_globals.get(resolved_name))
1128
+ ):
1129
+ # `load member from package` binds the actual public member
1130
+ # in the global scope. Imported function wrappers therefore
1131
+ # need to be callable through the same `name{args}` syntax
1132
+ # as registered ChemQL functions.
1133
+ imported_callable = python_globals[resolved_name]
1134
+ values = [evaluate_python(arg) for arg in args]
1135
+ value = imported_callable(*values)
1136
+ elif "::" in resolved_name:
1137
+ # A qualified call is permitted only after the user has
1138
+ # explicitly loaded its package (possibly under an alias).
1139
+ # `_ensure_package_namespace()` is also used internally by
1140
+ # `load MEMBER from package`; that internal initialization
1141
+ # must not make every member of the package accessible.
1142
+ package_name = resolved_name.rsplit("::", 1)[0]
1143
+ root_package = package_name.split("::", 1)[0]
1144
+ explicitly_loaded_packages = globals().get(
1145
+ "CHEMQL_EXPLICITLY_LOADED_PACKAGES", set()
1146
+ )
1147
+ # Explicit imports are tracked separately from package bindings:
1148
+ # internal package initialization and incidental bindings must not
1149
+ # expose sibling members through qualified calls.
1150
+ explicitly_loaded = (
1151
+ package_name in explicitly_loaded_packages
1152
+ or root_package in explicitly_loaded_packages
1153
+ )
1154
+ if explicitly_loaded:
1155
+ try:
1156
+ _ensure_package_namespace(package_name)
1157
+ except (ImportError, OSError, ValueError):
1158
+ pass
1159
+ if explicitly_loaded and resolved_name in FUNCTIONS:
1160
+ if not _namespace_function_is_callable(resolved_name):
1161
+ raise PermissionError((f"Function `{name}` is private" if NO_ANSI else f"Function \033[1m{name}\033[22m is private"))
1162
+ value = _call_function(resolved_name, args)
1163
+ else:
1164
+ raise ChemQLError((f"Unknown function `{name}`" if NO_ANSI else f"Unknown function \033[1m{name}\033[22m"))
1106
1165
  elif (
1107
1166
  "::" not in resolved_name
1108
1167
  and isinstance(python_globals.get(resolved_name), type)
@@ -4571,6 +4630,9 @@ def _find_unquoted_marker(text, marker, start=0):
4571
4630
  # Canonical ChemQL package names and user-defined aliases are deliberately
4572
4631
  # tracked separately. An alias must never replace a canonical package binding.
4573
4632
  CHEMQL_PACKAGE_BINDINGS = {}
4633
+ # Tracks packages explicitly imported by the user. Internal namespace initialization
4634
+ # for `load MEMBER from package` must not grant qualified-call access.
4635
+ CHEMQL_EXPLICITLY_LOADED_PACKAGES = set()
4574
4636
  CHEMQL_PACKAGE_SOURCE_LOADED = set()
4575
4637
 
4576
4638
 
@@ -4706,22 +4768,42 @@ def _ensure_package_namespace(package_name, allow_namespace_only=False):
4706
4768
  globals()["_CURRENT_VISIBILITY"] = old_visibility
4707
4769
  CHEMQL_PACKAGE_SOURCE_LOADED.add(package_name)
4708
4770
 
4709
- # A namespace-only parent is a canonical container, not an independently
4710
- # loadable package unless it has its own .cql source file.
4711
- if path is not None:
4712
- CHEMQL_PACKAGE_BINDINGS.setdefault(package_name, package_name)
4771
+ # Merely resolving a package namespace must not count as a user-visible
4772
+ # package import. `_execute_load_statement` records bindings explicitly.
4713
4773
  return namespace
4714
4774
 
4715
4775
 
4716
4776
  def _execute_load_statement(statement):
4717
- """Load a ChemQL .cql package, optionally binding an explicit alias."""
4777
+ """Load a package, optionally alias it or import one public member."""
4778
+ from_match = re.fullmatch(
4779
+ r"load\s+([A-Za-z_]\w*)\s+from\s+([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)(?:\s+as\s+([A-Za-z_]\w*))?",
4780
+ statement,
4781
+ )
4782
+ if from_match:
4783
+ member_name, package_name, alias = from_match.groups()
4784
+ binding_name = alias or member_name
4785
+ namespace = _ensure_package_namespace(package_name)
4786
+ if member_name not in namespace._chemql_public:
4787
+ raise ImportError(f"Package `{package_name}` has no public member `{member_name}`")
4788
+ value = getattr(namespace, member_name)
4789
+ # Bind only the requested member, under either its original name or
4790
+ # the explicit alias. Loading the source is necessary to resolve it,
4791
+ # but does not create a package-name binding in the user's globals.
4792
+ if binding_name in CHEMQL_PACKAGE_BINDINGS:
4793
+ raise ImportError(f"Name `{binding_name}` is already bound as a package name; cannot import `{member_name}` from `{package_name}`.")
4794
+ if binding_name in CHEMQL_VARIABLES:
4795
+ raise ImportError(f"Name `{binding_name}` is already defined; cannot import `{member_name}` from `{package_name}`.")
4796
+ python_globals[binding_name] = value
4797
+ CHEMQL_VARIABLES.add(binding_name)
4798
+ return True
4799
+
4718
4800
  match = re.fullmatch(
4719
4801
  r"load\s+([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)(?:\s+as\s+([A-Za-z_]\w*))?",
4720
4802
  statement,
4721
4803
  )
4722
4804
  if not match:
4723
4805
  if statement.startswith("load"):
4724
- raise SyntaxError(("Expected `load package::name` or `load package::name as alias`" if NO_ANSI else "Expected \033[1mload package::name\033[22m or \033[1mload package::name as alias\033[22m"))
4806
+ raise SyntaxError(("Expected `load package`, `load package as alias`, or `load MEMBER from package [as alias]`" if NO_ANSI else "Expected \033[1mload package\033[22m, \033[1mload package as alias\033[22m, or \033[1mload MEMBER from package [as alias]\033[22m"))
4725
4807
  return False
4726
4808
 
4727
4809
  package_name, alias = match.groups()
@@ -4827,6 +4909,10 @@ def _execute_load_statement(statement):
4827
4909
  CHEMQL_PACKAGE_BINDINGS[package_name] = package_name
4828
4910
  python_globals[package_name] = namespace
4829
4911
  CHEMQL_VARIABLES.add(package_name)
4912
+
4913
+ # Only `load package [as alias]` reaches this point. Member imports return
4914
+ # earlier, so they never grant qualified access to sibling package members.
4915
+ CHEMQL_EXPLICITLY_LOADED_PACKAGES.add(package_name)
4830
4916
  return True
4831
4917
 
4832
4918
 
@@ -6538,7 +6624,7 @@ class ChemqlLexer(Lexer):
6538
6624
  CONTROL_WORDS = {
6539
6625
  "if", "elif", "else", "for", "while", "break", "continue", "endblock",
6540
6626
  "endfor", "endwhile", "endif", "endrisk", "endstruct", "endrecord", "endenum",
6541
- "endfunc", "enddefine", "func", "define", "struct", "record", "risk",
6627
+ "endfunc", "enddefine", "func", "define", "struct", "record", "risk", "static",
6542
6628
  "fix", "cleanup", "signal", "import", "as", "return", "public", "private",
6543
6629
  "required", "namespace", "endnamespace", "enum", "from"
6544
6630
  }
@@ -10,7 +10,7 @@ import shutil
10
10
  import subprocess
11
11
  import time
12
12
 
13
- VERSION = "0.0.9"
13
+ VERSION = "0.0.10"
14
14
 
15
15
 
16
16
  def _write_system_clipboard(text):
@@ -275,7 +275,7 @@ COMMANDS = {
275
275
  CONTROL_KEYWORDS = {
276
276
  "if", "elif", "else", "endif", "for", "while", "in", "endfor",
277
277
  "endwhile", "break", "continue", "endblock", "from", "as", "public",
278
- "private", "required", "struct", "record", "enum", "endstruct",
278
+ "private", "required", "struct", "record", "enum", "endstruct", "static",
279
279
  "endrecord", "endenum", "namespace", "endnamespace",
280
280
  }
281
281
  WORD_OPERATORS = {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chemql-python
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: A chemistry-focused query language and REPL
5
5
  Author-email: Mohammad Rakibul Islam <rakibulofficial1017@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -20,7 +20,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
20
20
  balance equations, and combine chemistry queries with Python and shell
21
21
  commands.
22
22
 
23
- - **Version:** `1.6.1`
23
+ - **Version:** `1.6.2`
24
24
  - **PyPI distribution:** `chemql-python`
25
25
  - **Python import / CLI name:** `chemql`
26
26
  - **Author:** Mohammad Rakibul Islam
@@ -889,6 +889,73 @@ available before loading the package. `pathlib::read_text` and
889
889
  `pathlib::write_text` access files with the permissions of the ChemQL
890
890
  process.
891
891
 
892
+ ## Importing Package Members
893
+
894
+ ChemQL allows you to import individual members from a package while retaining access to the package's other members through its qualified namespace.
895
+
896
+ ### Importing a Single Member
897
+
898
+ Use `load MEMBER from PACKAGE` to import a specific member into the current scope.
899
+
900
+ ```chemql
901
+ load sin from math
902
+
903
+ sin{10}
904
+ ```
905
+
906
+ The imported member can be called directly without specifying its package name.
907
+
908
+ ### Importing with an Alias
909
+
910
+ You can use `as` to assign a different name to an imported member.
911
+
912
+ ```chemql
913
+ load cos from math as c
914
+
915
+ c{10}
916
+ ```
917
+
918
+ This is useful when you want a shorter name or want to avoid naming conflicts.
919
+
920
+ ### Qualified Namespace Access
921
+
922
+ Importing an individual member does not prevent you from accessing other members of its package. ChemQL can initialize the package as needed and resolve qualified function calls using the package namespace.
923
+
924
+ ```chemql
925
+ load sin from math
926
+
927
+ # Call the imported member directly
928
+ sin{10}
929
+
930
+ # Access other package members
931
+ math::cos{10}
932
+ math::sin{30}
933
+ ```
934
+
935
+ You can therefore combine direct member imports with qualified calls in the same program.
936
+
937
+ ### Importing an Entire Package
938
+
939
+ To load a package explicitly, use `load PACKAGE` or assign it an alias with `as`.
940
+
941
+ ```chemql
942
+ load math
943
+
944
+ math::sin{10}
945
+ math::cos{10}
946
+ ```
947
+
948
+ You can also assign a package alias:
949
+
950
+ ```chemql
951
+ load math as MATH
952
+
953
+ MATH::sin{10}
954
+ MATH::cos{10}
955
+ ```
956
+
957
+ **Note:** ChemQL's package initialization and namespace resolution allow qualified package functions to remain accessible even when you initially import only one member. Importing a member is therefore a convenience for direct access, not a restriction on the rest of the package.
958
+
892
959
  ## Built-in help
893
960
 
894
961
  The help system includes a general index and topic/function pages:
File without changes
File without changes