chemql-python 1.3.0__tar.gz → 1.3.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.
- {chemql_python-1.3.0/src/chemql_python.egg-info → chemql_python-1.3.2}/PKG-INFO +2 -2
- {chemql_python-1.3.0 → chemql_python-1.3.2}/README.md +1 -1
- {chemql_python-1.3.0 → chemql_python-1.3.2}/pyproject.toml +1 -1
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/__init__.py +244 -74
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/__main__.py +1 -1
- {chemql_python-1.3.0 → chemql_python-1.3.2/src/chemql_python.egg-info}/PKG-INFO +2 -2
- {chemql_python-1.3.0 → chemql_python-1.3.2}/LICENSE +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/setup.cfg +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/data/BondEnergies.json +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/data/MoleculeBonds.json +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/data/Molecules.json +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/data/PeriodicTableJSON.json +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/data/Reactions.json +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/data/THIRD_PARTY_LICENSES/PeriodicTableJSON.txt +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/data/THIRD_PARTY_LICENSES/RHEA.txt +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql/html/periodic_table.html +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql_python.egg-info/SOURCES.txt +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql_python.egg-info/dependency_links.txt +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql_python.egg-info/entry_points.txt +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql_python.egg-info/requires.txt +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/src/chemql_python.egg-info/top_level.txt +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/tests/test_external_molecule_lookup.py +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/tests/test_public_api.py +0 -0
- {chemql_python-1.3.0 → chemql_python-1.3.2}/tests/test_reactions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chemql-python
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -19,7 +19,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
|
|
|
19
19
|
balance equations, and combine chemistry queries with Python and shell
|
|
20
20
|
commands.
|
|
21
21
|
|
|
22
|
-
- **Version:** `1.3.
|
|
22
|
+
- **Version:** `1.3.2`
|
|
23
23
|
- **PyPI distribution:** `chemql-python`
|
|
24
24
|
- **Python import / CLI name:** `chemql`
|
|
25
25
|
- **Author:** Mohammad Rakibul Islam
|
|
@@ -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.3.
|
|
9
|
+
- **Version:** `1.3.2`
|
|
10
10
|
- **PyPI distribution:** `chemql-python`
|
|
11
11
|
- **Python import / CLI name:** `chemql`
|
|
12
12
|
- **Author:** Mohammad Rakibul Islam
|
|
@@ -52,7 +52,7 @@ __all__ = [
|
|
|
52
52
|
"__version__"
|
|
53
53
|
]
|
|
54
54
|
|
|
55
|
-
__version__ = "1.3.
|
|
55
|
+
__version__ = "1.3.2"
|
|
56
56
|
|
|
57
57
|
# Persistent preprocessor/function state for the current ChemQL session.
|
|
58
58
|
DEFINITIONS = {}
|
|
@@ -61,6 +61,7 @@ MACROS = {}
|
|
|
61
61
|
EXECUTION_HISTORY = []
|
|
62
62
|
_DEFINITION_NAME = r"[A-Za-z_][A-Za-z0-9_]*"
|
|
63
63
|
_NO_RETURN = object()
|
|
64
|
+
NO_ANSI = True
|
|
64
65
|
|
|
65
66
|
def _expand_definitions(line):
|
|
66
67
|
"""Replace @NAME literally, including inside strings, using current definitions."""
|
|
@@ -993,10 +994,11 @@ _CHEMQL_RISK_DEPTH = 0
|
|
|
993
994
|
|
|
994
995
|
|
|
995
996
|
def format_error(message, text=None, position=None, length=1):
|
|
996
|
-
output =
|
|
997
|
+
output = ""
|
|
998
|
+
if (text is not None) and (position is not None) and (not NO_ANSI):
|
|
999
|
+
output += f"\033[1;31m{' ' * (position+4)}{'^' * max(1, length)}\n\033[0m"
|
|
1000
|
+
output += f"Error: {message}" if NO_ANSI else f"\033[31mError: {message}\033[0m"
|
|
997
1001
|
|
|
998
|
-
if text is not None and position is not None:
|
|
999
|
-
output += f"\n{text}\n{' ' * position}{'^' * max(1, length)}"
|
|
1000
1002
|
|
|
1001
1003
|
return output
|
|
1002
1004
|
|
|
@@ -2034,7 +2036,7 @@ def _reaction_api_json(url, timeout=REACTION_API_TIMEOUT):
|
|
|
2034
2036
|
request = urllib.request.Request(
|
|
2035
2037
|
url,
|
|
2036
2038
|
headers={
|
|
2037
|
-
"User-Agent": "ChemQL/
|
|
2039
|
+
"User-Agent": f"ChemQL/{__version__} (reaction lookup; contact: chemql-user)",
|
|
2038
2040
|
"Accept": "application/json",
|
|
2039
2041
|
},
|
|
2040
2042
|
)
|
|
@@ -2050,7 +2052,7 @@ def _reaction_api_text(url, timeout=REACTION_API_TIMEOUT):
|
|
|
2050
2052
|
request = urllib.request.Request(
|
|
2051
2053
|
url,
|
|
2052
2054
|
headers={
|
|
2053
|
-
"User-Agent": "ChemQL/
|
|
2055
|
+
"User-Agent": f"ChemQL/{__version__} (reaction lookup; contact: chemql-user)",
|
|
2054
2056
|
"Accept": "text/tab-separated-values,text/plain,*/*",
|
|
2055
2057
|
},
|
|
2056
2058
|
)
|
|
@@ -2293,40 +2295,40 @@ def _formula_counts(formula):
|
|
|
2293
2295
|
if char == "(":
|
|
2294
2296
|
group, index = parse_group(index + 1, True)
|
|
2295
2297
|
if index >= len(formula) or formula[index] != ")":
|
|
2296
|
-
raise ValueError(f"Unclosed parenthesis in formula `{formula}`")
|
|
2298
|
+
raise ValueError((f"Unclosed parenthesis in formula `{formula}`" if NO_ANSI else f"Unclosed parenthesis in formula \033[1m{formula}\033[22m"))
|
|
2297
2299
|
index += 1
|
|
2298
2300
|
match = re.match(r"\d+", formula[index:])
|
|
2299
2301
|
multiplier = int(match.group()) if match else 1
|
|
2300
2302
|
if multiplier <= 0:
|
|
2301
|
-
raise ValueError(f"Invalid atom count in formula `{formula}`")
|
|
2303
|
+
raise ValueError((f"Invalid atom count in formula `{formula}`" if NO_ANSI else f"Invalid atom count in formula \033[1m{formula}\033[22m"))
|
|
2302
2304
|
if match:
|
|
2303
2305
|
index += len(match.group())
|
|
2304
2306
|
for symbol, amount in group.items():
|
|
2305
2307
|
counts[symbol] = counts.get(symbol, 0) + amount * multiplier
|
|
2306
2308
|
continue
|
|
2307
2309
|
if not char.isupper():
|
|
2308
|
-
raise ValueError(f"Invalid chemical formula `{formula}`")
|
|
2310
|
+
raise ValueError((f"Invalid chemical formula `{formula}`" if NO_ANSI else f"Invalid chemical formula \033[1m{formula}\033[22m"))
|
|
2309
2311
|
end = index + 1
|
|
2310
2312
|
if end < len(formula) and formula[end].islower():
|
|
2311
2313
|
end += 1
|
|
2312
2314
|
symbol = formula[index:end]
|
|
2313
2315
|
if symbol not in symbols:
|
|
2314
|
-
raise ValueError(f"Unknown element `{symbol}` in formula `{formula}`")
|
|
2316
|
+
raise ValueError((f"Unknown element `{symbol}` in formula `{formula}`" if NO_ANSI else f"Unknown element `{symbol}` in formula \033[1m{formula}\033[22m"))
|
|
2315
2317
|
index = end
|
|
2316
2318
|
match = re.match(r"\d+", formula[index:])
|
|
2317
2319
|
amount = int(match.group()) if match else 1
|
|
2318
2320
|
if amount <= 0:
|
|
2319
|
-
raise ValueError(f"Invalid atom count in formula `{formula}`")
|
|
2321
|
+
raise ValueError((f"Invalid atom count in formula `{formula}`" if NO_ANSI else f"Invalid atom count in formula \033[1m{formula}\033[22m"))
|
|
2320
2322
|
if match:
|
|
2321
2323
|
index += len(match.group())
|
|
2322
2324
|
counts[symbol] = counts.get(symbol, 0) + amount
|
|
2323
2325
|
if nested:
|
|
2324
|
-
raise ValueError(f"Unclosed parenthesis in formula `{formula}`")
|
|
2326
|
+
raise ValueError((f"Unclosed parenthesis in formula `{formula}`" if NO_ANSI else f"Unclosed parenthesis in formula \033[1m{formula}\033[22m"))
|
|
2325
2327
|
return counts, index
|
|
2326
2328
|
|
|
2327
2329
|
counts, end = parse_group(0)
|
|
2328
2330
|
if end != len(formula) or not counts:
|
|
2329
|
-
raise ValueError(f"Invalid chemical formula `{formula}`")
|
|
2331
|
+
raise ValueError((f"Invalid chemical formula `{formula}`" if NO_ANSI else f"Invalid chemical formula \033[1m{formula}\033[22m"))
|
|
2330
2332
|
return counts
|
|
2331
2333
|
|
|
2332
2334
|
|
|
@@ -2455,7 +2457,7 @@ def queryElement(key, func, value, *params):
|
|
|
2455
2457
|
|
|
2456
2458
|
def queryElementLast(key, func, value, *params):
|
|
2457
2459
|
if key not in elementKeys:
|
|
2458
|
-
raise KeyError(f"There is no key `{key}`")
|
|
2460
|
+
raise KeyError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"))
|
|
2459
2461
|
|
|
2460
2462
|
for element in reversed(ELEMENTS):
|
|
2461
2463
|
if getattr(element[key], func)(value, *params):
|
|
@@ -2466,7 +2468,7 @@ def queryElementLast(key, func, value, *params):
|
|
|
2466
2468
|
|
|
2467
2469
|
def queryElementAll(key, func, value, *params):
|
|
2468
2470
|
if key not in elementKeys:
|
|
2469
|
-
raise KeyError(f"There is no key `{key}`")
|
|
2471
|
+
raise KeyError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"))
|
|
2470
2472
|
|
|
2471
2473
|
found = False
|
|
2472
2474
|
|
|
@@ -2527,7 +2529,7 @@ def sort_results(results, key, descending=False):
|
|
|
2527
2529
|
reverse=descending
|
|
2528
2530
|
)
|
|
2529
2531
|
except (TypeError, AttributeError):
|
|
2530
|
-
raise ValueError(f"Cannot sort by `{key}`")
|
|
2532
|
+
raise ValueError((f"Cannot sort by `{key}`" if NO_ANSI else f"Cannot sort by \033[1m{key}\033[22m"))
|
|
2531
2533
|
|
|
2532
2534
|
def apply_query_operations(results, tokens, keys, positions=None):
|
|
2533
2535
|
i = 0
|
|
@@ -2538,13 +2540,13 @@ def apply_query_operations(results, tokens, keys, positions=None):
|
|
|
2538
2540
|
# sort
|
|
2539
2541
|
if token == "sort":
|
|
2540
2542
|
if i + 1 >= len(tokens):
|
|
2541
|
-
raise ChemQLError("Expected a key after `sort`", *(error_position(positions, i) if positions else (None, 1))) #type:ignore
|
|
2543
|
+
raise ChemQLError(("Expected a key after `sort`" if NO_ANSI else "Expected a key after \033[1msort\033[22m"), *(error_position(positions, i) if positions else (None, 1))) #type:ignore
|
|
2542
2544
|
|
|
2543
2545
|
key = tokens[i + 1]
|
|
2544
2546
|
|
|
2545
2547
|
if key not in keys:
|
|
2546
2548
|
pos, length = error_position(positions, i + 1) if positions else (None, 1)
|
|
2547
|
-
raise ChemQLError(f"There is no key `{key}`", pos, length)
|
|
2549
|
+
raise ChemQLError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"), pos, length)
|
|
2548
2550
|
|
|
2549
2551
|
descending = False
|
|
2550
2552
|
|
|
@@ -2573,17 +2575,17 @@ def apply_query_operations(results, tokens, keys, positions=None):
|
|
|
2573
2575
|
# limit
|
|
2574
2576
|
elif token == "limit":
|
|
2575
2577
|
if i + 1 >= len(tokens):
|
|
2576
|
-
raise ChemQLError("Expected a number after `limit`", *(error_position(positions, i) if positions else (None, 1)))#type:ignore
|
|
2578
|
+
raise ChemQLError(("Expected a number after `limit`" if NO_ANSI else "Expected a number after \033[1mlimit\033[22m"), *(error_position(positions, i) if positions else (None, 1)))#type:ignore
|
|
2577
2579
|
|
|
2578
2580
|
try:
|
|
2579
2581
|
amount = int(tokens[i + 1])
|
|
2580
2582
|
except ValueError:
|
|
2581
2583
|
pos, length = error_position(positions, i + 1) if positions else (None, 1)
|
|
2582
|
-
raise ChemQLError("`limit` requires an integer", pos, length)
|
|
2584
|
+
raise ChemQLError(("`limit` requires an integer" if NO_ANSI else "\033[1mlimit\033[22m requires an integer"), pos, length)
|
|
2583
2585
|
|
|
2584
2586
|
if amount < 0:
|
|
2585
2587
|
pos, length = error_position(positions, i + 1) if positions else (None, 1)
|
|
2586
|
-
raise ChemQLError("`limit` cannot be negative", pos, length)
|
|
2588
|
+
raise ChemQLError(("`limit` cannot be negative" if NO_ANSI else "\033[1mlimit\033[22m cannot be negative"), pos, length)
|
|
2587
2589
|
|
|
2588
2590
|
results = results[:amount] # type: ignore
|
|
2589
2591
|
|
|
@@ -2593,14 +2595,14 @@ def apply_query_operations(results, tokens, keys, positions=None):
|
|
|
2593
2595
|
elif token == "count":
|
|
2594
2596
|
if i + 1 < len(tokens):
|
|
2595
2597
|
pos, length = error_position(positions, i) if positions else (None, 1)
|
|
2596
|
-
raise ChemQLError("`count` must be the final query operation", pos, length)
|
|
2598
|
+
raise ChemQLError(("`count` must be the final query operation" if NO_ANSI else "\033[1mcount\033[22m must be the final query operation"), pos, length)
|
|
2597
2599
|
|
|
2598
2600
|
results = len(results) # type: ignore
|
|
2599
2601
|
i += 1
|
|
2600
2602
|
|
|
2601
2603
|
else:
|
|
2602
2604
|
pos, length = error_position(positions, i) if positions else (None, 1)
|
|
2603
|
-
raise ChemQLError(f"Unknown query operation `{token}`", pos, length)
|
|
2605
|
+
raise ChemQLError((f"Unknown query operation `{token}`" if NO_ANSI else f"Unknown query operation \033[1m{token}\033[22m"), pos, length)
|
|
2604
2606
|
|
|
2605
2607
|
return results
|
|
2606
2608
|
|
|
@@ -2619,7 +2621,7 @@ def convert_value(value, sample):
|
|
|
2619
2621
|
if value.lower() == "false":
|
|
2620
2622
|
return False
|
|
2621
2623
|
|
|
2622
|
-
raise ValueError(f"`{value}` is not a boolean")
|
|
2624
|
+
raise ValueError((f"`{value}` is not a boolean" if NO_ANSI else f"\033[1m{value}\033[22m is not a boolean"))
|
|
2623
2625
|
|
|
2624
2626
|
if isinstance(sample, (int, float)):
|
|
2625
2627
|
|
|
@@ -2632,7 +2634,7 @@ def convert_value(value, sample):
|
|
|
2632
2634
|
return number
|
|
2633
2635
|
|
|
2634
2636
|
except ValueError:
|
|
2635
|
-
raise ValueError(f"`{value}` is not a number")
|
|
2637
|
+
raise ValueError((f"`{value}` is not a number" if NO_ANSI else f"\033[1m{value}\033[22m is not a number"))
|
|
2636
2638
|
|
|
2637
2639
|
return value
|
|
2638
2640
|
|
|
@@ -2652,7 +2654,7 @@ def parse_condition(tokens, index, source, keys, positions=None):
|
|
|
2652
2654
|
negated = operator.casefold() == "not"
|
|
2653
2655
|
if negated:
|
|
2654
2656
|
if index + 3 >= len(tokens):
|
|
2655
|
-
raise ChemQLError("Expected an operator and value after `not`", *(error_position(positions, index + 1) if positions else (None, 1))) # type: ignore
|
|
2657
|
+
raise ChemQLError(("Expected an operator and value after `not`" if NO_ANSI else "Expected an operator and value after \033[1mnot\033[22m"), *(error_position(positions, index + 1) if positions else (None, 1))) # type: ignore
|
|
2656
2658
|
operator = tokens[index + 2]
|
|
2657
2659
|
value = tokens[index + 3]
|
|
2658
2660
|
consumed = index + 4
|
|
@@ -2662,7 +2664,7 @@ def parse_condition(tokens, index, source, keys, positions=None):
|
|
|
2662
2664
|
|
|
2663
2665
|
if key not in keys:
|
|
2664
2666
|
pos, length = error_position(positions, index) if positions else (None, 1)
|
|
2665
|
-
raise ChemQLError(f"There is no key `{key}`", pos, length)
|
|
2667
|
+
raise ChemQLError((f"There is no key `{key}`" if NO_ANSI else f"There is no key \033[1m{key}\033[22m"), pos, length)
|
|
2666
2668
|
|
|
2667
2669
|
if operator in {"like", "like!", "likep"}:
|
|
2668
2670
|
pattern = value
|
|
@@ -2737,7 +2739,7 @@ def parse_condition(tokens, index, source, keys, positions=None):
|
|
|
2737
2739
|
|
|
2738
2740
|
if operator not in functionMap:
|
|
2739
2741
|
pos, length = error_position(positions, index + 1) if positions else (None, 1)
|
|
2740
|
-
raise ChemQLError(f"Unknown operator `{operator}`", pos, length)
|
|
2742
|
+
raise ChemQLError((f"Unknown operator `{operator}`" if NO_ANSI else f"Unknown operator \033[1m{operator}\033[22m"), pos, length)
|
|
2741
2743
|
|
|
2742
2744
|
sample = getattr(source[0], key)
|
|
2743
2745
|
|
|
@@ -2779,7 +2781,7 @@ def parse_primary(tokens, index, source, keys, positions=None):
|
|
|
2779
2781
|
|
|
2780
2782
|
if index >= len(tokens) or tokens[index] != ")":
|
|
2781
2783
|
pos = positions[index][0] if positions and index < len(positions) else None
|
|
2782
|
-
raise ChemQLError("Missing `)`", pos)
|
|
2784
|
+
raise ChemQLError(("Missing `)`" if NO_ANSI else "Missing \033[1m)\033[22m"), pos)
|
|
2783
2785
|
|
|
2784
2786
|
return expression, index + 1
|
|
2785
2787
|
|
|
@@ -2867,7 +2869,7 @@ def parse_query(tokens, source, keys, positions=None):
|
|
|
2867
2869
|
if index < len(tokens) and tokens[index] not in queryOperations:
|
|
2868
2870
|
pos, length = error_position(positions, index) if positions else (None, 1)
|
|
2869
2871
|
raise ChemQLError(
|
|
2870
|
-
f"Unexpected `{tokens[index]}`", pos, length
|
|
2872
|
+
(f"Unexpected `{tokens[index]}`" if NO_ANSI else f"Unexpected \033[1m{tokens[index]}\033[22m"), pos, length
|
|
2871
2873
|
)
|
|
2872
2874
|
|
|
2873
2875
|
return condition, index
|
|
@@ -2950,7 +2952,7 @@ def find_matching(text, start, opening, closing):
|
|
|
2950
2952
|
|
|
2951
2953
|
i += 1
|
|
2952
2954
|
|
|
2953
|
-
raise SyntaxError(f"Missing `{closing}`")
|
|
2955
|
+
raise SyntaxError((f"Missing `{closing}`" if NO_ANSI else f"Missing \033[1m{closing}\033[22m"))
|
|
2954
2956
|
|
|
2955
2957
|
|
|
2956
2958
|
def interpolate_query(text):
|
|
@@ -3273,7 +3275,7 @@ def find_endblock(lines, start):
|
|
|
3273
3275
|
# expand to a ChemQL struct representation that is not Python syntax.
|
|
3274
3276
|
if re.match(r"^\[\s*(?:if|for|while)\b", stripped):
|
|
3275
3277
|
depth += 1
|
|
3276
|
-
raise SyntaxError("Missing block terminator (`endblock`, `endfor`, `endwhile`, or `endif`)")
|
|
3278
|
+
raise SyntaxError(("Missing block terminator (`endblock`, `endfor`, `endwhile`, or `endif`)" if NO_ANSI else "Missing block terminator (\033[1mendblock\033[22m, \033[1mendfor\033[22m, \033[1mendwhile\033[22m, or \033[1mendif\033[22m)"))
|
|
3277
3279
|
|
|
3278
3280
|
|
|
3279
3281
|
def remove_comments(line):
|
|
@@ -3319,7 +3321,7 @@ def execute_for_block(header, body):
|
|
|
3319
3321
|
node = tree.body[0]
|
|
3320
3322
|
|
|
3321
3323
|
if not isinstance(node, ast.For):
|
|
3322
|
-
raise SyntaxError("Expected a `for` block")
|
|
3324
|
+
raise SyntaxError(("Expected a `for` block" if NO_ANSI else "Expected a \033[1mfor\033[22m block"))
|
|
3323
3325
|
|
|
3324
3326
|
iterable = eval(
|
|
3325
3327
|
compile(ast.Expression(node.iter), "<embedded-python>", "eval"),
|
|
@@ -3378,7 +3380,7 @@ def execute_while_block(header, body):
|
|
|
3378
3380
|
node = tree.body[0]
|
|
3379
3381
|
|
|
3380
3382
|
if not isinstance(node, ast.While):
|
|
3381
|
-
raise SyntaxError("Expected a `while` block")
|
|
3383
|
+
raise SyntaxError(("Expected a `while` block" if NO_ANSI else "Expected a \033[1mwhile\033[22m block"))
|
|
3382
3384
|
|
|
3383
3385
|
while eval(
|
|
3384
3386
|
compile(
|
|
@@ -3456,7 +3458,7 @@ def execute_if_block(header, body):
|
|
|
3456
3458
|
|
|
3457
3459
|
else:
|
|
3458
3460
|
raise SyntaxError(
|
|
3459
|
-
f"Invalid conditional `{code}`"
|
|
3461
|
+
(f"Invalid conditional `{code}`" if NO_ANSI else f"Invalid conditional \033[1m{code}\033[22m")
|
|
3460
3462
|
)
|
|
3461
3463
|
|
|
3462
3464
|
if evaluate_python(condition):
|
|
@@ -3801,7 +3803,7 @@ def process_lines(lines):
|
|
|
3801
3803
|
while end < len(lines) and lines[end].strip().casefold() != terminator:
|
|
3802
3804
|
end += 1
|
|
3803
3805
|
if end >= len(lines):
|
|
3804
|
-
raise SyntaxError(f"Missing `{terminator}` for {kind} `{type_name}`")
|
|
3806
|
+
raise SyntaxError((f"Missing `{terminator}` for {kind} `{type_name}`" if NO_ANSI else f"Missing \033[1m{terminator}\033[22m for {kind} \033[1m{type_name}\033[22m"))
|
|
3805
3807
|
fields, defaults = [], {}
|
|
3806
3808
|
for field_line in lines[i + 1:end]:
|
|
3807
3809
|
field_line = remove_comments(field_line).strip()
|
|
@@ -3812,12 +3814,12 @@ def process_lines(lines):
|
|
|
3812
3814
|
raise SyntaxError(f"Invalid {kind} field declaration: {field_line}")
|
|
3813
3815
|
field, default = match.groups()
|
|
3814
3816
|
if field in fields:
|
|
3815
|
-
raise SyntaxError(f"Duplicate field `{field}` in {type_name}")
|
|
3817
|
+
raise SyntaxError((f"Duplicate field `{field}` in {type_name}" if NO_ANSI else f"Duplicate field \033[1m{field}\033[22m in {type_name}"))
|
|
3816
3818
|
fields.append(field)
|
|
3817
3819
|
if default is not None:
|
|
3818
3820
|
defaults[field] = evaluate_python(_expand_definitions(default))
|
|
3819
3821
|
if not fields:
|
|
3820
|
-
raise SyntaxError(f"{kind.title()} `{type_name}` must declare fields")
|
|
3822
|
+
raise SyntaxError((f"{kind.title()} `{type_name}` must declare fields" if NO_ANSI else f"{kind.title()} \033[1m{type_name}\033[22m must declare fields"))
|
|
3821
3823
|
if kind == "struct":
|
|
3822
3824
|
def make_struct(class_name, field_names, field_defaults):
|
|
3823
3825
|
def __init__(self, *args, **kwargs):
|
|
@@ -3826,7 +3828,7 @@ def process_lines(lines):
|
|
|
3826
3828
|
values = dict(zip(field_names, args))
|
|
3827
3829
|
overlap = values.keys() & kwargs.keys()
|
|
3828
3830
|
if overlap:
|
|
3829
|
-
raise TypeError(f"Multiple values for field `{next(iter(overlap))}`")
|
|
3831
|
+
raise TypeError((f"Multiple values for field `{next(iter(overlap))}`" if NO_ANSI else f"Multiple values for field \033[1m{next(iter(overlap))}\033[22m"))
|
|
3830
3832
|
values.update(kwargs)
|
|
3831
3833
|
extra = values.keys() - set(field_names)
|
|
3832
3834
|
if extra:
|
|
@@ -3844,7 +3846,7 @@ def process_lines(lines):
|
|
|
3844
3846
|
from collections import namedtuple
|
|
3845
3847
|
first_default = next((n for n, f in enumerate(fields) if f in defaults), len(fields))
|
|
3846
3848
|
if any(f not in defaults for f in fields[first_default:]):
|
|
3847
|
-
raise SyntaxError(f"Record `{type_name}` fields with defaults must be trailing fields")
|
|
3849
|
+
raise SyntaxError((f"Record `{type_name}` fields with defaults must be trailing fields" if NO_ANSI else f"Record \033[1m{type_name}\033[22m fields with defaults must be trailing fields"))
|
|
3848
3850
|
generated = namedtuple(type_name, fields, defaults=[defaults[f] for f in fields[first_default:]])
|
|
3849
3851
|
generated.__str__ = lambda self, _name=type_name: _name + " -> " + ", ".join(f"{f}: {getattr(self, f)}" for f in self._fields)
|
|
3850
3852
|
RECORDS[type_name] = generated
|
|
@@ -3863,7 +3865,7 @@ def process_lines(lines):
|
|
|
3863
3865
|
if nesting == 0: break
|
|
3864
3866
|
end += 1
|
|
3865
3867
|
if end >= len(lines):
|
|
3866
|
-
raise SyntaxError("Missing `endrisk`")
|
|
3868
|
+
raise SyntaxError(("Missing `endrisk`" if NO_ANSI else "Missing \033[1mendrisk\033[22m"))
|
|
3867
3869
|
sections = {"risk": [], "fix": [], "cleanup": []}
|
|
3868
3870
|
section = "risk"
|
|
3869
3871
|
for risk_line in lines[i + 1:end]:
|
|
@@ -3935,13 +3937,13 @@ def process_lines(lines):
|
|
|
3935
3937
|
while end < len(lines) and lines[end].strip().casefold() != "endfunc":
|
|
3936
3938
|
end += 1
|
|
3937
3939
|
if end >= len(lines):
|
|
3938
|
-
raise SyntaxError(f"Missing `endfunc` for function `{name}`")
|
|
3940
|
+
raise SyntaxError((f"Missing `endfunc` for function `{name}`" if NO_ANSI else f"Missing `endfunc` for function \033[1m{name}\033[22m"))
|
|
3939
3941
|
FUNCTIONS[name] = {"body": [_expand_definitions(source_line) for source_line in lines[i + 1:end]]}
|
|
3940
3942
|
if "." in name:
|
|
3941
3943
|
owner_name, method_name = name.split(".", 1)
|
|
3942
3944
|
owner = STRUCTS.get(owner_name)
|
|
3943
3945
|
if owner is None:
|
|
3944
|
-
raise NameError(f"Cannot define method `{name}`: struct `{owner_name}` does not exist")
|
|
3946
|
+
raise NameError((f"Cannot define method `{name}`: struct `{owner_name}` does not exist" if NO_ANSI else f"Cannot define method `{name}`: struct \033[1m{owner_name}\033[22m does not exist"))
|
|
3945
3947
|
def _make_struct_method(function_name):
|
|
3946
3948
|
def _method(instance, *args):
|
|
3947
3949
|
had_this = "this" in python_globals
|
|
@@ -3978,7 +3980,7 @@ def process_lines(lines):
|
|
|
3978
3980
|
while end < len(lines) and lines[end].strip().casefold() != "enddefine":
|
|
3979
3981
|
end += 1
|
|
3980
3982
|
if end >= len(lines):
|
|
3981
|
-
raise SyntaxError(f"Missing `enddefine` for macro `{name}`")
|
|
3983
|
+
raise SyntaxError((f"Missing `enddefine` for macro `{name}`" if NO_ANSI else f"Missing `enddefine` for macro \033[1m{name}\033[22m"))
|
|
3982
3984
|
MACROS[name] = [_expand_definitions(source_line) for source_line in lines[i + 1:end]]
|
|
3983
3985
|
i = end + 1
|
|
3984
3986
|
continue
|
|
@@ -4006,7 +4008,7 @@ def process_lines(lines):
|
|
|
4006
4008
|
break
|
|
4007
4009
|
close_line += 1
|
|
4008
4010
|
if close_line >= len(lines):
|
|
4009
|
-
raise SyntaxError("Missing closing `///` for multiline comment")
|
|
4011
|
+
raise SyntaxError(("Missing closing `///` for multiline comment" if NO_ANSI else "Missing closing \033[1m///\033[22m for multiline comment"))
|
|
4010
4012
|
suffix = lines[close_line][close_pos + 3:]
|
|
4011
4013
|
lines[i] = prefix
|
|
4012
4014
|
del lines[i + 1:close_line + 1]
|
|
@@ -4030,7 +4032,7 @@ def process_lines(lines):
|
|
|
4030
4032
|
body.append(lines[i])
|
|
4031
4033
|
i += 1
|
|
4032
4034
|
if i >= len(lines):
|
|
4033
|
-
raise SyntaxError("Missing closing `]]]` for Python block")
|
|
4035
|
+
raise SyntaxError(("Missing closing `]]]` for Python block" if NO_ANSI else "Missing closing \033[1m]]]\033[22m for Python block"))
|
|
4034
4036
|
body = [_expand_definitions(source_line) for source_line in body]
|
|
4035
4037
|
code = "\n".join(body)
|
|
4036
4038
|
captured = io.StringIO()
|
|
@@ -4045,7 +4047,7 @@ def process_lines(lines):
|
|
|
4045
4047
|
print(output, end="" if output.endswith("\n") else "\n")
|
|
4046
4048
|
_record_execution("[[[\n" + code + "\n]]]", output, True)
|
|
4047
4049
|
except Exception as exc:
|
|
4048
|
-
message = f"
|
|
4050
|
+
message = f"\033[31mError: {exc}\033[0m"
|
|
4049
4051
|
print(strip_ansi(message) if NO_ANSI else message, file=sys.stderr)
|
|
4050
4052
|
_record_execution("[[[\n" + code + "\n]]]", message, False)
|
|
4051
4053
|
i += 1
|
|
@@ -4059,7 +4061,7 @@ def process_lines(lines):
|
|
|
4059
4061
|
body.append(lines[i])
|
|
4060
4062
|
i += 1
|
|
4061
4063
|
if i >= len(lines):
|
|
4062
|
-
raise SyntaxError("Missing closing
|
|
4064
|
+
raise SyntaxError("Missing closing \033[1m```\033[22m for terminal block")
|
|
4063
4065
|
body = [_expand_definitions(source_line) for source_line in body]
|
|
4064
4066
|
command = "\n".join(body)
|
|
4065
4067
|
result = run_shell_command(command)
|
|
@@ -4082,7 +4084,7 @@ def process_lines(lines):
|
|
|
4082
4084
|
continue
|
|
4083
4085
|
|
|
4084
4086
|
if line == "[endblock]":
|
|
4085
|
-
raise SyntaxError("Unexpected `[endblock]`")
|
|
4087
|
+
raise SyntaxError(("Unexpected `[endblock]`" if NO_ANSI else "Unexpected \033[1m[endblock]\033[22m"))
|
|
4086
4088
|
|
|
4087
4089
|
# Existing Python control-flow blocks.
|
|
4088
4090
|
if is_python_block_header(line):
|
|
@@ -4096,7 +4098,7 @@ def process_lines(lines):
|
|
|
4096
4098
|
elif code.startswith("if "):
|
|
4097
4099
|
execute_if_block(line, body)
|
|
4098
4100
|
else:
|
|
4099
|
-
raise SyntaxError(f"Unsupported block `{code}`")
|
|
4101
|
+
raise SyntaxError((f"Unsupported block `{code}`" if NO_ANSI else f"Unsupported block \033[1m{code}\033[22m"))
|
|
4100
4102
|
i = end + 1
|
|
4101
4103
|
continue
|
|
4102
4104
|
|
|
@@ -4164,7 +4166,7 @@ def process_lines(lines):
|
|
|
4164
4166
|
except Exception as exc:
|
|
4165
4167
|
if _CHEMQL_RISK_DEPTH:
|
|
4166
4168
|
raise
|
|
4167
|
-
message = f"
|
|
4169
|
+
message = f"\033[31mError: {exc}\033[0m"
|
|
4168
4170
|
print(strip_ansi(message) if NO_ANSI else message, file=sys.stderr)
|
|
4169
4171
|
_record_execution(command, message, False)
|
|
4170
4172
|
|
|
@@ -4194,7 +4196,7 @@ def parse_temperature_setting(value):
|
|
|
4194
4196
|
|
|
4195
4197
|
match = re.fullmatch(r"([+-]?(?:\d+(?:\.\d*)?|\.\d+))\s*(?:°\s*)?(celsius|fahrenheit|kelvin|degc|degf|degk|c|f|k)", normalized)
|
|
4196
4198
|
if not match:
|
|
4197
|
-
raise ValueError("Temperature must use C, F, or K, or be `standard` or `room`")
|
|
4199
|
+
raise ValueError(("Temperature must use C, F, or K, or be `standard` or `room`" if NO_ANSI else "Temperature must use C, F, or K, or be \033[1mstandard\033[22m or \033[1mroom\033[22m"))
|
|
4198
4200
|
|
|
4199
4201
|
temperature = float(match.group(1))
|
|
4200
4202
|
unit = match.group(2)
|
|
@@ -4362,7 +4364,7 @@ def react(values):
|
|
|
4362
4364
|
molecule = _resolve_reaction_product(product)
|
|
4363
4365
|
if molecule is None:
|
|
4364
4366
|
label = product.get("name", product["molecule"])
|
|
4365
|
-
return f"Could not resolve product molecule `{label}` for reaction `{reaction.id}`"
|
|
4367
|
+
return (f"Could not resolve product molecule `{label}` for reaction `{reaction.id}`" if NO_ANSI else f"Could not resolve product molecule \033[1m{label}\033[22m for reaction \033[1m{reaction.id}\033[22m")
|
|
4366
4368
|
resolved.append((molecule, product["stoichiometric_coefficient"], product.get("phase")))
|
|
4367
4369
|
|
|
4368
4370
|
if len(resolved) == 1:
|
|
@@ -4465,7 +4467,7 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4465
4467
|
return "Catalysts cleared"
|
|
4466
4468
|
return f"Catalysts set to {', '.join(reaction_state['catalysts'])}"
|
|
4467
4469
|
|
|
4468
|
-
return f"Unknown reaction setting `{setting}`"
|
|
4470
|
+
return (f"Unknown reaction setting `{setting}`" if NO_ANSI else f"Unknown reaction setting \033[1m{setting}\033[22m")
|
|
4469
4471
|
|
|
4470
4472
|
if arguments[0] == "add" and len(arguments) >= 2 and arguments[1].lower() == "catalyst":
|
|
4471
4473
|
if len(arguments) < 3:
|
|
@@ -4523,7 +4525,7 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4523
4525
|
|
|
4524
4526
|
for attribute in return_attributes:
|
|
4525
4527
|
if attribute not in moleculeKeys:
|
|
4526
|
-
return f"There is no key `{attribute}`"
|
|
4528
|
+
return (f"There is no key `{attribute}`" if NO_ANSI else f"There is no key \033[1m{attribute}\033[22m")
|
|
4527
4529
|
|
|
4528
4530
|
if return_index != 2:
|
|
4529
4531
|
return "Usage: findmol <name-or-formula> [return <attribute> ...]"
|
|
@@ -4540,7 +4542,7 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4540
4542
|
result = fetch_pubchem_molecule(value)
|
|
4541
4543
|
|
|
4542
4544
|
if result is None:
|
|
4543
|
-
return f"Could not find molecule `{value}`"
|
|
4545
|
+
return (f"Could not find molecule `{value}`" if NO_ANSI else f"Could not find molecule \033[1m{value}\033[22m")
|
|
4544
4546
|
|
|
4545
4547
|
if isinstance(result, str) and "PubChem is currently unavailable" in result:
|
|
4546
4548
|
return result
|
|
@@ -4576,7 +4578,7 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4576
4578
|
|
|
4577
4579
|
for attribute in return_attributes:
|
|
4578
4580
|
if attribute not in reactionKeys:
|
|
4579
|
-
return f"There is no key `{attribute}`"
|
|
4581
|
+
return (f"There is no key `{attribute}`" if NO_ANSI else f"There is no key \033[1m{attribute}\033[22m")
|
|
4580
4582
|
|
|
4581
4583
|
if return_index != 2:
|
|
4582
4584
|
return "Usage: findre <name-or-id> [return <attribute> ...]"
|
|
@@ -4594,7 +4596,7 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4594
4596
|
if result == [] or result == "No matching item":
|
|
4595
4597
|
result = find_reaction(value, external=True)
|
|
4596
4598
|
if result is None:
|
|
4597
|
-
return f"Could not find reaction `{value}` in Reactions.json, Rhea, or Chempirical"
|
|
4599
|
+
return (f"Could not find reaction `{value}` in Reactions.json, Rhea, or Chempirical" if NO_ANSI else f"Could not find reaction \033[1m{value}\033[22m in Reactions.json, Rhea, or Chempirical")
|
|
4598
4600
|
|
|
4599
4601
|
if return_attributes:
|
|
4600
4602
|
if isinstance(result, list):
|
|
@@ -4621,10 +4623,10 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4621
4623
|
source_name = arguments[1]
|
|
4622
4624
|
|
|
4623
4625
|
if source_name not in SOURCES:
|
|
4624
|
-
return f"There is no source `{source_name}`"
|
|
4626
|
+
return (f"There is no source `{source_name}`" if NO_ANSI else f"There is no source \033[1m{source_name}\033[22m")
|
|
4625
4627
|
|
|
4626
4628
|
current_source = source_name
|
|
4627
|
-
return f"Source set to `{source_name}`"
|
|
4629
|
+
return (f"Source set to `{source_name}`" if NO_ANSI else f"Source set to \033[1m{source_name}\033[22m")
|
|
4628
4630
|
|
|
4629
4631
|
if arguments[0] == "list":
|
|
4630
4632
|
if len(arguments) != 2 or arguments[1] != "all":
|
|
@@ -4660,8 +4662,8 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4660
4662
|
if source_name is None:
|
|
4661
4663
|
return (
|
|
4662
4664
|
"No source selected. "
|
|
4663
|
-
"Specify one, e.g. "
|
|
4664
|
-
"`search all elements atomic_mass < 100`"
|
|
4665
|
+
"Specify one, e.g. "+
|
|
4666
|
+
("`search all elements atomic_mass < 100`" if NO_ANSI else "\033[1msearch all elements atomic_mass < 100\033[22m")
|
|
4665
4667
|
)
|
|
4666
4668
|
|
|
4667
4669
|
source, keys = SOURCES[source_name]
|
|
@@ -4682,7 +4684,7 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4682
4684
|
absolute_index = return_index + 1 + attribute_index
|
|
4683
4685
|
pos, length = error_position(positions, absolute_index) if positions else (None, 1)
|
|
4684
4686
|
return format_error(
|
|
4685
|
-
f"There is no key `{attribute}`",
|
|
4687
|
+
(f"There is no key `{attribute}`" if NO_ANSI else f"There is no key \033[1m{attribute}\033[22m"),
|
|
4686
4688
|
query_text,
|
|
4687
4689
|
pos,
|
|
4688
4690
|
length
|
|
@@ -4718,9 +4720,9 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4718
4720
|
|
|
4719
4721
|
if isinstance(results, int):
|
|
4720
4722
|
if mode != "all":
|
|
4721
|
-
return format_error("`count` cannot be combined with `first` or `last`")
|
|
4723
|
+
return format_error(("`count` cannot be combined with `first` or `last`" if NO_ANSI else "\033[1mcount\033[22m cannot be combined with \033[1mfirst\033[22m or \033[1mlast\033[22m"))
|
|
4722
4724
|
if return_attributes is not None:
|
|
4723
|
-
return format_error("`return` cannot be used with `count`")
|
|
4725
|
+
return format_error(("`return` cannot be used with `count`" if NO_ANSI else "\033[1mreturn\033[22m cannot be used with \033[1mcount\033[22m"))
|
|
4724
4726
|
return results
|
|
4725
4727
|
|
|
4726
4728
|
# `return` is a projection. It intentionally happens after
|
|
@@ -4758,15 +4760,183 @@ def execute(arguments, query_text=None, positions=None):
|
|
|
4758
4760
|
if query_text is not None and positions:
|
|
4759
4761
|
pos, length = error_position(positions, 0)
|
|
4760
4762
|
return format_error(
|
|
4761
|
-
f"Unknown command `{arguments[0]}`",
|
|
4763
|
+
(f"Unknown command `{arguments[0]}`" if NO_ANSI else f"Unknown command \033[1m{arguments[0]}\033[22m"),
|
|
4762
4764
|
query_text,
|
|
4763
4765
|
pos,
|
|
4764
4766
|
length
|
|
4765
4767
|
)
|
|
4766
4768
|
|
|
4767
|
-
return format_error(f"Unknown command `{arguments[0]}`")
|
|
4769
|
+
return format_error((f"Unknown command `{arguments[0]}`" if NO_ANSI else f"Unknown command \033[1m{arguments[0]}\033[22m"))
|
|
4770
|
+
|
|
4771
|
+
|
|
4772
|
+
HELP = (
|
|
4773
|
+
"\x1b[1;94m╭──────────────────────────────────────────────────────────────────────────────╮\x1b[0m\n"
|
|
4774
|
+
f"\x1b[1;94m│\x1b[0m \x1b[1;96m✦ \x1b[1mChemQL\x1b[0m \x1b[97mv{__version__}\x1b[0m \x1b[90m│\x1b[0m \x1b[1m\x1b[97mTHE PYTHON CHEMISTRY QUERY LANGUAGE\x1b[0m \x1b[1;94m│\x1b[0m\n"
|
|
4775
|
+
"\x1b[1;94m│\x1b[0m \x1b[90mQuery chemistry · automate calculations · build reusable programs\x1b[0m \x1b[1;94m│\x1b[0m\n"
|
|
4776
|
+
"\x1b[1;94m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n"
|
|
4777
|
+
"\n"
|
|
4778
|
+
"\x1b[1;93m\x1b[1m▶ QUICK START\x1b[0m\n"
|
|
4779
|
+
" \x1b[92msearch elements ... \x1b[0m \x1b[90m│\x1b[0m Search the periodic table\n"
|
|
4780
|
+
" \x1b[92msearch molecules ... \x1b[0m \x1b[90m│\x1b[0m Search molecule records\n"
|
|
4781
|
+
" \x1b[92msearch reactions ... \x1b[0m \x1b[90m│\x1b[0m Search stored reactions\n"
|
|
4782
|
+
" \x1b[92mfindel \"He\" \x1b[0m \x1b[90m│\x1b[0m Find an element\n"
|
|
4783
|
+
" \x1b[92mfindmol \"Water\" \x1b[0m \x1b[90m│\x1b[0m Find a molecule\n"
|
|
4784
|
+
" \x1b[92mfindre \"Haber process\" \x1b[0m \x1b[90m│\x1b[0m Find a reaction\n"
|
|
4785
|
+
" \x1b[92mbalance H2 + O2 -> H2O \x1b[0m \x1b[90m│\x1b[0m Balance a chemical equation\n"
|
|
4786
|
+
" \x1b[92mhelp or ? \x1b[0m \x1b[90m│\x1b[0m Display this help message\n"
|
|
4787
|
+
" \x1b[92mversion \x1b[0m \x1b[90m│\x1b[0m Display the installed version\n"
|
|
4788
|
+
" \x1b[92mexit \x1b[0m \x1b[90m│\x1b[0m Exit the interactive prompt\n"
|
|
4789
|
+
"\n"
|
|
4790
|
+
"\x1b[1;93m\x1b[1m◆ CHEMICAL DATA & SEARCH\x1b[0m\n"
|
|
4791
|
+
" \x1b[1;96msearch elements [conditions] \x1b[0m \x1b[90m│\x1b[0m Query element data\n"
|
|
4792
|
+
" \x1b[1;96msearch molecules [conditions] \x1b[0m \x1b[90m│\x1b[0m Query molecule data\n"
|
|
4793
|
+
" \x1b[1;96msearch reactions [conditions] \x1b[0m \x1b[90m│\x1b[0m Query stored reaction data\n"
|
|
4794
|
+
" \x1b[1;96msource elements|molecules|reactions\x1b[0m \x1b[90m│\x1b[0m Set the current source\n"
|
|
4795
|
+
" \x1b[1;96mlist all \x1b[0m \x1b[90m│\x1b[0m List available fields and sources\n"
|
|
4796
|
+
" \x1b[95msearch elements symbol = \"Fe\" return name symbol number\x1b[0m\n"
|
|
4797
|
+
" \x1b[95msearch molecules molecular_weight < 50 sort molecular_weight limit 10\x1b[0m\n"
|
|
4798
|
+
" \x1b[95msearch reactions reversible = true return name equation source\x1b[0m\n"
|
|
4799
|
+
"\n"
|
|
4800
|
+
"\x1b[1;93m\x1b[1m◆ FILTERS & LOGIC\x1b[0m\n"
|
|
4801
|
+
" \x1b[1;96m= != > < >= <= \x1b[0m \x1b[90m│\x1b[0m Comparison operators\n"
|
|
4802
|
+
" \x1b[1;96mlike \x1b[0m \x1b[90m│\x1b[0m Case-insensitive wildcard match\n"
|
|
4803
|
+
" \x1b[1;96mlike! \x1b[0m \x1b[90m│\x1b[0m Case-sensitive wildcard match\n"
|
|
4804
|
+
" \x1b[1;96mlikep \x1b[0m \x1b[90m│\x1b[0m Pattern-based matching\n"
|
|
4805
|
+
" \x1b[1;96mhas \x1b[0m \x1b[90m│\x1b[0m Check whether a collection contains an item\n"
|
|
4806
|
+
" \x1b[1;96mand / or \x1b[0m \x1b[90m│\x1b[0m Combine conditions\n"
|
|
4807
|
+
" \x1b[95msearch elements number > 10 and number < 20\x1b[0m\n"
|
|
4808
|
+
" \x1b[95msearch molecules name like \"*water*\"\x1b[0m\n"
|
|
4809
|
+
" \x1b[95msearch reactions reactants has \"O2\" and products has \"H2O\"\x1b[0m\n"
|
|
4810
|
+
"\n"
|
|
4811
|
+
"\x1b[1;93m\x1b[1m◆ RESULT OPERATIONS\x1b[0m\n"
|
|
4812
|
+
" \x1b[92mfirst / last \x1b[0m \x1b[90m│\x1b[0m Select the first or last result\n"
|
|
4813
|
+
" \x1b[92msort FIELD [asc|desc] \x1b[0m \x1b[90m│\x1b[0m Sort matching records\n"
|
|
4814
|
+
" \x1b[92mlimit N \x1b[0m \x1b[90m│\x1b[0m Limit the number of results\n"
|
|
4815
|
+
" \x1b[92mcount \x1b[0m \x1b[90m│\x1b[0m Count matching records\n"
|
|
4816
|
+
" \x1b[92mreturn FIELD ... \x1b[0m \x1b[90m│\x1b[0m Choose fields to display\n"
|
|
4817
|
+
" \x1b[92munion / intersection \x1b[0m \x1b[90m│\x1b[0m Combine result sets\n"
|
|
4818
|
+
" \x1b[95msearch molecules molecular_weight < 20 sort molecular_weight asc limit 5\x1b[0m\n"
|
|
4819
|
+
"\n"
|
|
4820
|
+
"\x1b[1;93m\x1b[1m◆ REACTIONS & CONDITIONS\x1b[0m\n"
|
|
4821
|
+
" \x1b[1;96mbalance H2 + O2 -> H2O \x1b[0m \x1b[90m│\x1b[0m Balance an equation\n"
|
|
4822
|
+
" \x1b[1;96mbalance N2 + H2 <-> NH3 \x1b[0m \x1b[90m│\x1b[0m Balance a reversible equation\n"
|
|
4823
|
+
" \x1b[1;96mreact N2 3H2 \x1b[0m \x1b[90m│\x1b[0m Find a matching stored reaction\n"
|
|
4824
|
+
" \x1b[1;96mset temperature 150C \x1b[0m \x1b[90m│\x1b[0m Set the reaction temperature\n"
|
|
4825
|
+
" \x1b[1;96mset pressure 1atm \x1b[0m \x1b[90m│\x1b[0m Set the reaction pressure\n"
|
|
4826
|
+
" \x1b[1;96mset catalysts iron zinc \x1b[0m \x1b[90m│\x1b[0m Set available catalysts\n"
|
|
4827
|
+
" \x1b[1;96mset catalysts \x1b[0m \x1b[90m│\x1b[0m Clear the catalyst list\n"
|
|
4828
|
+
" \x1b[1;96madd catalyst \"Iron\" \x1b[0m \x1b[90m│\x1b[0m Add a catalyst\n"
|
|
4829
|
+
" \x1b[1;96mremove catalyst \"Iron\" \x1b[0m \x1b[90m│\x1b[0m Remove a catalyst\n"
|
|
4830
|
+
" \x1b[1;96mconditions \x1b[0m \x1b[90m│\x1b[0m Display current conditions\n"
|
|
4831
|
+
" \x1b[2mTemperature units: K, C, F. Pressure units include Pa, bar, and atm.\x1b[0m\n"
|
|
4832
|
+
"\n"
|
|
4833
|
+
"\x1b[1;93m\x1b[1m◆ VARIABLES & DEFINITIONS\x1b[0m\n"
|
|
4834
|
+
" \x1b[95mlet answer = 42 \x1b[0m \x1b[90m│\x1b[0m Create or assign a variable\n"
|
|
4835
|
+
" \x1b[95munset answer \x1b[0m \x1b[90m│\x1b[0m Remove a variable\n"
|
|
4836
|
+
" \x1b[95mvariables \x1b[0m \x1b[90m│\x1b[0m List current variables\n"
|
|
4837
|
+
" \x1b[95mdefine MAX 9 \x1b[0m \x1b[90m│\x1b[0m Define a literal replacement\n"
|
|
4838
|
+
" \x1b[95m@MAX \x1b[0m \x1b[90m│\x1b[0m Use a definition\n"
|
|
4839
|
+
" \x1b[95mdefinitions \x1b[0m \x1b[90m│\x1b[0m List definitions\n"
|
|
4840
|
+
" \x1b[95mremove definition MAX \x1b[0m \x1b[90m│\x1b[0m Remove a definition\n"
|
|
4841
|
+
" \x1b[95mdefine MAX 9\x1b[0m\n"
|
|
4842
|
+
" \x1b[95msearch elements number <= @MAX\x1b[0m\n"
|
|
4843
|
+
"\n"
|
|
4844
|
+
"\x1b[1;93m\x1b[1m◆ FUNCTIONS & MACROS\x1b[0m\n"
|
|
4845
|
+
" \x1b[95mfunc square \x1b[0m \x1b[90m│\x1b[0m Start a function definition\n"
|
|
4846
|
+
" \x1b[95mreturn [\\1 * \\1] \x1b[0m \x1b[90m│\x1b[0m Return a value using argument \\1\n"
|
|
4847
|
+
" \x1b[95mendfunc \x1b[0m \x1b[90m│\x1b[0m Finish a function definition\n"
|
|
4848
|
+
" \x1b[95msquare{5} \x1b[0m \x1b[90m│\x1b[0m Call a function\n"
|
|
4849
|
+
" \x1b[95mdefine double{} \x1b[0m \x1b[90m│\x1b[0m Start a macro definition\n"
|
|
4850
|
+
" \x1b[95menddefine \x1b[0m \x1b[90m│\x1b[0m Finish a macro definition\n"
|
|
4851
|
+
" \x1b[95m@double{5} \x1b[0m \x1b[90m│\x1b[0m Expand and execute a macro\n"
|
|
4852
|
+
" \x1b[95mmacros \x1b[0m \x1b[90m│\x1b[0m List defined macros\n"
|
|
4853
|
+
" \x1b[2mFunction and macro arguments use positional placeholders: \\1, \\2, and so on.\x1b[0m\n"
|
|
4854
|
+
"\n"
|
|
4855
|
+
"\x1b[1;93m\x1b[1m◆ CONTROL FLOW\x1b[0m\n"
|
|
4856
|
+
" \x1b[95mif condition ... endif \x1b[0m \x1b[90m│\x1b[0m Conditional execution\n"
|
|
4857
|
+
" \x1b[95mif / elif / else / endif \x1b[0m \x1b[90m│\x1b[0m Branch between conditions\n"
|
|
4858
|
+
" \x1b[95mfor item in iterable ... endfor\x1b[0m \x1b[90m│\x1b[0m Iterate over values\n"
|
|
4859
|
+
" \x1b[95mwhile condition ... endwhile \x1b[0m \x1b[90m│\x1b[0m Repeat while a condition holds\n"
|
|
4860
|
+
" \x1b[95mbreak / continue \x1b[0m \x1b[90m│\x1b[0m Control loop execution\n"
|
|
4861
|
+
" \x1b[95mfor i in range(5)\x1b[0m\n"
|
|
4862
|
+
" \x1b[95mlet value = i\x1b[0m\n"
|
|
4863
|
+
" \x1b[95mendfor\x1b[0m\n"
|
|
4864
|
+
" \x1b[2mPython-style blocks are also available: [for ...] ... [endblock].\x1b[0m\n"
|
|
4865
|
+
"\n"
|
|
4866
|
+
"\x1b[1;93m\x1b[1m◆ ERROR HANDLING\x1b[0m\n"
|
|
4867
|
+
" \x1b[95mrisk ... endrisk \x1b[0m \x1b[90m│\x1b[0m Run a protected block\n"
|
|
4868
|
+
" \x1b[95mfix \x1b[0m \x1b[90m│\x1b[0m Handle an error from the risk block\n"
|
|
4869
|
+
" \x1b[95mcleanup \x1b[0m \x1b[90m│\x1b[0m Run cleanup whether successful or not\n"
|
|
4870
|
+
" \x1b[95msignal \"message\" \x1b[0m \x1b[90m│\x1b[0m Raise a ChemQL signal\n"
|
|
4871
|
+
" \x1b[95merror \x1b[0m \x1b[90m│\x1b[0m Inspect the error type in a fix block\n"
|
|
4872
|
+
" \x1b[95mrisk\x1b[0m\n"
|
|
4873
|
+
" \x1b[95msignal \"Something went wrong\"\x1b[0m\n"
|
|
4874
|
+
" \x1b[95mfix\x1b[0m\n"
|
|
4875
|
+
" \x1b[95mlet handled = error\x1b[0m\n"
|
|
4876
|
+
" \x1b[95mcleanup\x1b[0m\n"
|
|
4877
|
+
" \x1b[95mlet finished = true\x1b[0m\n"
|
|
4878
|
+
" \x1b[95mendrisk\x1b[0m\n"
|
|
4879
|
+
" \x1b[2mThe variable error contains the error type, or None after success.\x1b[0m\n"
|
|
4880
|
+
"\n"
|
|
4881
|
+
"\x1b[1;93m\x1b[1m◆ STRUCTS & METHODS\x1b[0m\n"
|
|
4882
|
+
" \x1b[95mstruct Name \x1b[0m \x1b[90m│\x1b[0m Define a structured data type\n"
|
|
4883
|
+
" \x1b[95mrecord Name \x1b[0m \x1b[90m│\x1b[0m Define or use record data\n"
|
|
4884
|
+
" \x1b[95mfunc Type.__add__ \x1b[0m \x1b[90m│\x1b[0m Define an operator method\n"
|
|
4885
|
+
" \x1b[2mStruct methods can implement custom behaviour and operator overloads.\x1b[0m\n"
|
|
4886
|
+
"\n"
|
|
4887
|
+
"\x1b[1;93m\x1b[1m◆ PYTHON INTEGRATION\x1b[0m\n"
|
|
4888
|
+
" \x1b[1;96m[x = 10] \x1b[0m \x1b[90m│\x1b[0m Execute a Python statement\n"
|
|
4889
|
+
" \x1b[1;96m[print(x)] \x1b[0m \x1b[90m│\x1b[0m Run Python code\n"
|
|
4890
|
+
" \x1b[1;96m[x * 2] \x1b[0m \x1b[90m│\x1b[0m Evaluate a Python expression\n"
|
|
4891
|
+
" \x1b[95m{{ search elements name = \"Hydrogen\" }}\x1b[0m\n"
|
|
4892
|
+
" \x1b[2mEmbedded {{ ... }} queries return results as values.\x1b[0m\n"
|
|
4893
|
+
" Multiline Python blocks:\n"
|
|
4894
|
+
" \x1b[95m[[[\x1b[0m\n"
|
|
4895
|
+
" \x1b[95mfor x in range(3):\x1b[0m\n"
|
|
4896
|
+
" \x1b[95m print(x)\x1b[0m\n"
|
|
4897
|
+
" \x1b[95m]]]\x1b[0m\n"
|
|
4898
|
+
"\n"
|
|
4899
|
+
"\x1b[1;93m\x1b[1m◆ SHELL, COMMENTS & COMPOSITION\x1b[0m\n"
|
|
4900
|
+
" \x1b[1;96m`command` \x1b[0m \x1b[90m│\x1b[0m Run a system-shell command\n"
|
|
4901
|
+
" \x1b[1;96m```bash ... ``` \x1b[0m \x1b[90m│\x1b[0m Run a multiline shell block\n"
|
|
4902
|
+
" \x1b[1;96m// comment \x1b[0m \x1b[90m│\x1b[0m Single-line comment\n"
|
|
4903
|
+
" \x1b[1;96m/// ... /// \x1b[0m \x1b[90m│\x1b[0m Multiline comment\n"
|
|
4904
|
+
" \x1b[1;96mcommand1; command2 \x1b[0m \x1b[90m│\x1b[0m Execute multiple commands\n"
|
|
4905
|
+
" \x1b[1;96m\\ \x1b[0m \x1b[90m│\x1b[0m Continue input on the next line\n"
|
|
4906
|
+
" \x1b[1;96mview \x1b[0m \x1b[90m│\x1b[0m Open the periodic-table viewer\n"
|
|
4907
|
+
" \x1b[1;96mclear \x1b[0m \x1b[90m│\x1b[0m Clear the terminal\n"
|
|
4908
|
+
"\n"
|
|
4909
|
+
"\x1b[1;93m\x1b[1m◆ EXECUTION HISTORY\x1b[0m\n"
|
|
4910
|
+
" \x1b[92mdump FILE \x1b[0m \x1b[90m│\x1b[0m Export queries and results\n"
|
|
4911
|
+
" \x1b[92mdump all FILE \x1b[0m \x1b[90m│\x1b[0m Export all execution records\n"
|
|
4912
|
+
" \x1b[92mdump good FILE \x1b[0m \x1b[90m│\x1b[0m Export successful executions\n"
|
|
4913
|
+
" \x1b[92mdump bad FILE \x1b[0m \x1b[90m│\x1b[0m Export failed executions\n"
|
|
4914
|
+
" \x1b[92mdump all|good|bad queries FILE\x1b[0m \x1b[90m│\x1b[0m Export query text only\n"
|
|
4915
|
+
" \x1b[92mdump all|good|bad results FILE\x1b[0m \x1b[90m│\x1b[0m Export results only\n"
|
|
4916
|
+
"\n"
|
|
4917
|
+
"\x1b[1;93m\x1b[1m◆ COMMAND-LINE OPTIONS\x1b[0m\n"
|
|
4918
|
+
" \x1b[1;96mchemql \x1b[0m \x1b[90m│\x1b[0m Start the interactive prompt\n"
|
|
4919
|
+
" \x1b[1;96mchemql -c \"COMMAND\" \x1b[0m \x1b[90m│\x1b[0m Execute one command\n"
|
|
4920
|
+
" \x1b[1;96mchemql -f FILE \x1b[0m \x1b[90m│\x1b[0m Execute a file\n"
|
|
4921
|
+
" \x1b[1;96mchemql --version \x1b[0m \x1b[90m│\x1b[0m Display version information\n"
|
|
4922
|
+
" \x1b[1;96mchemql --no-ansi \x1b[0m \x1b[90m│\x1b[0m Disable ANSI styling\n"
|
|
4923
|
+
" \x1b[1;96mchemql -c \"COMMAND\" -o FILE \x1b[0m \x1b[90m│\x1b[0m Save command output to a file\n"
|
|
4924
|
+
" \x1b[1;96mchemql --upgrade [VERSION] \x1b[0m \x1b[90m│\x1b[0m Upgrade ChemQL\n"
|
|
4925
|
+
" \x1b[1;96mchemql --force \x1b[0m \x1b[90m│\x1b[0m Override package-manager protections\n"
|
|
4926
|
+
"\n"
|
|
4927
|
+
"\x1b[1;93m\x1b[1m◆ PYTHON API\x1b[0m\n"
|
|
4928
|
+
" \x1b[95mfrom chemql import execute_query_text, execute_query\x1b[0m\n"
|
|
4929
|
+
" \x1b[95mresult = execute_query_text('search elements symbol = \"O\"')\x1b[0m\n"
|
|
4930
|
+
" \x1b[2mDistribution: chemql-python · Import and CLI name: chemql\x1b[0m\n"
|
|
4931
|
+
"\n"
|
|
4932
|
+
"\x1b[1;93m\x1b[1m⚠ SAFETY NOTES\x1b[0m\n"
|
|
4933
|
+
" \x1b[2mPython blocks and shell commands execute real code with your permissions.\x1b[0m\n"
|
|
4934
|
+
" \x1b[2mOnly run code and commands you trust. External lookups require network access.\x1b[0m\n"
|
|
4935
|
+
"\n"
|
|
4936
|
+
"\x1b[1;94m────────────────────────────────────────────────────────────────────────────────\x1b[0m\n"
|
|
4937
|
+
f"\x1b[90mChemQL {__version__} · Type \x1b[92mhelp\x1b[90m for help, or \x1b[92mexit\x1b[90m to quit.\x1b[0m\n"
|
|
4938
|
+
)
|
|
4768
4939
|
|
|
4769
|
-
HELP = '\x1b[1;94m╭──────────────────────────────────────────────────────────────────────────────╮\x1b[0m\n\x1b[1;94m│\x1b[0m \x1b[1;96m✦ \x1b[1mChemQL\x1b[0m \x1b[97mv1.2.0\x1b[0m \x1b[90m│\x1b[0m \x1b[1m\x1b[97mTHE PYTHON CHEMISTRY QUERY LANGUAGE\x1b[0m \x1b[1;94m│\x1b[0m\n\x1b[1;94m│\x1b[0m \x1b[90mQuery chemical datasets · balance equations · embed Python · automate workflows\x1b[0m \x1b[1;94m│\x1b[0m\n\x1b[1;94m╰──────────────────────────────────────────────────────────────────────────────╯\x1b[0m\n\n\x1b[1;93m\x1b[1m▶ QUICK START\x1b[0m\n \x1b[92msearch elements ... \x1b[0m \x1b[90m│\x1b[0m Query the periodic-table dataset\n \x1b[92msearch molecules ... \x1b[0m \x1b[90m│\x1b[0m Query molecule records\n \x1b[92msearch reactions ... \x1b[0m \x1b[90m│\x1b[0m Query stored reaction records\n \x1b[92mfindel "He" \x1b[0m \x1b[90m│\x1b[0m Find an element by name or symbol\n \x1b[92mfindmol "Water" \x1b[0m \x1b[90m│\x1b[0m Find a molecule by name or formula\n \x1b[92mfindre "Haber process" \x1b[0m \x1b[90m│\x1b[0m Find a reaction by name, ID, or equation\n \x1b[92mlist all \x1b[0m \x1b[90m│\x1b[0m List available fields and sources\n \x1b[92mhelp or ? \x1b[0m \x1b[90m│\x1b[0m Show this help reference\n \x1b[92mversion \x1b[0m \x1b[90m│\x1b[0m Show the installed version\n \x1b[92mexit \x1b[0m \x1b[90m│\x1b[0m Leave the interactive prompt\n\n\x1b[1;93m\x1b[1m◆ QUERY SOURCES & EXAMPLES\x1b[0m\n \x1b[92msearch elements [where] \x1b[0m \x1b[90m│\x1b[0m Search element data\n \x1b[92msearch molecules [where] \x1b[0m \x1b[90m│\x1b[0m Search molecule data\n \x1b[92msearch reactions [where] \x1b[0m \x1b[90m│\x1b[0m Search reaction data\n \x1b[1;96msource elements|molecules|reactions\x1b[0m \x1b[90m│\x1b[0m Set the current source\n \x1b[95msearch elements symbol = "Fe" return name symbol number atomic_mass\x1b[0m\n \x1b[95msearch molecules molecular_weight < 50 sort molecular_weight limit 10\x1b[0m\n \x1b[95msearch reactions reversible = true return name equation source\x1b[0m\n\n\x1b[1;93m\x1b[1m◆ FILTERS & LOGIC\x1b[0m\n \x1b[1;96m= != > < >= <= \x1b[0m \x1b[90m│\x1b[0m Comparison operators\n \x1b[1;96mlike / like! \x1b[0m \x1b[90m│\x1b[0m Case-insensitive / case-sensitive wildcard matching\n \x1b[1;96mlikep / has \x1b[0m \x1b[90m│\x1b[0m Pattern matching / contains an item\n \x1b[1;96mand / or / ( ... ) \x1b[0m \x1b[90m│\x1b[0m Combine conditions; parentheses group logic\n \x1b[95msearch elements number > 10 and number < 20\x1b[0m\n \x1b[95msearch molecules name like "*water*"\x1b[0m\n \x1b[95msearch molecules (name like "*water*" or formula = "H2O") and molecular_weight < 25\x1b[0m\n\n\x1b[1;93m\x1b[1m◆ RESULT OPERATIONS\x1b[0m\n \x1b[92mfirst / last \x1b[0m \x1b[90m│\x1b[0m Return the first or last match\n \x1b[92msort FIELD [asc|desc] \x1b[0m \x1b[90m│\x1b[0m Sort matching records\n \x1b[92mlimit N \x1b[0m \x1b[90m│\x1b[0m Limit the number of results\n \x1b[92mcount \x1b[0m \x1b[90m│\x1b[0m Count matches\n \x1b[92mreturn FIELD ... \x1b[0m \x1b[90m│\x1b[0m Choose fields; one field lists, multiple fields form a table\n \x1b[92munion / intersection \x1b[0m \x1b[90m│\x1b[0m Combine result sets\n \x1b[95msearch molecules molecular_weight < 20 sort molecular_weight asc limit 5\x1b[0m\n \x1b[95msearch elements number < 10 union search elements number > 90\x1b[0m\n\n\x1b[1;93m\x1b[1m◆ REACTION TOOLS & CONDITIONS\x1b[0m\n \x1b[1;96mset temperature 723K|150C|300F|standard|room \x1b[0m \x1b[90m│\x1b[0m Set reaction temperature\n \x1b[1;96mset pressure 101325Pa|2bar|1atm|standard|room \x1b[0m \x1b[90m│\x1b[0m Set reaction pressure\n \x1b[1;96mset catalysts iron zinc \x1b[0m \x1b[90m│\x1b[0m Set the catalyst list\n \x1b[1;96mset catalysts \x1b[0m \x1b[90m│\x1b[0m Clear the catalyst list\n \x1b[1;96madd catalyst "Vanadium oxide"\x1b[0m \x1b[90m│\x1b[0m Add a catalyst\n \x1b[1;96mremove catalyst "Vanadium oxide"\x1b[0m \x1b[90m│\x1b[0m Remove a catalyst\n \x1b[1;96mconditions \x1b[0m \x1b[90m│\x1b[0m Show active temperature, pressure, and catalysts\n \x1b[1;96mreact N2 3H2 \x1b[0m \x1b[90m│\x1b[0m Find a stored reaction matching reactants\n \x1b[1;96mbalance H2 + O2 -> H2O \x1b[0m \x1b[90m│\x1b[0m Balance an irreversible equation\n \x1b[1;96mbalance N2 + H2 <-> NH3 \x1b[0m \x1b[90m│\x1b[0m Balance a reversible equation\n \x1b[2mTemperature units: C, F, K. Pressure units include Pa, N/m^2, Nm^-2, bar, and atm.\x1b[0m\n \x1b[2mPresets: standard = 0 C and 100000 Pa; room = 25 C and 101325 Pa.\x1b[0m\n\n\x1b[1;93m\x1b[1m◆ PREPROCESSOR, FUNCTIONS & MACROS · V1.2.0\x1b[0m\n \x1b[95mdefine MAX 9 \x1b[0m \x1b[90m│\x1b[0m Define a literal replacement used as @MAX\n \x1b[95mdefinitions \x1b[0m \x1b[90m│\x1b[0m List current literal definitions\n \x1b[95mremove definition MAX \x1b[0m \x1b[90m│\x1b[0m Remove a definition\n \x1b[95mdefine MAX 9\x1b[0m\n \x1b[95msearch elements number <= @MAX\x1b[0m\n\n \x1b[95mfunc square \x1b[0m \x1b[90m│\x1b[0m Begin a function; finish it with endfunc\n \x1b[95mreturn [\\1 * \\1] \x1b[0m \x1b[90m│\x1b[0m Return a value using positional argument \\1\n \x1b[95mendfunc \x1b[0m \x1b[90m│\x1b[0m Close the function definition\n \x1b[95msquare{5} \x1b[0m \x1b[90m│\x1b[0m Call the function with brace-delimited arguments\n\n \x1b[95mdefine light{} \x1b[0m \x1b[90m│\x1b[0m Begin a macro; finish it with enddefine\n \x1b[95msearch molecules molecular_weight < \\1 return name formula\x1b[0m\n \x1b[95menddefine \x1b[0m \x1b[90m│\x1b[0m Close the macro definition\n \x1b[95m@light{50} \x1b[0m \x1b[90m│\x1b[0m Expand and execute a macro\n \x1b[2mFunction and macro arguments use positional placeholders: \\1, \\2, and so on.\x1b[0m\n \x1b[2mDefinitions are processed in source order and are local to the current session.\x1b[0m\n\n\x1b[1;93m\x1b[1m◆ PYTHON INTEGRATION\x1b[0m\n \x1b[1;96m[x = 10] \x1b[0m \x1b[90m│\x1b[0m Execute a Python statement\n \x1b[1;96m[print(x)] \x1b[0m \x1b[90m│\x1b[0m Use Python variables across commands\n \x1b[1;96m[x * 2] \x1b[0m \x1b[90m│\x1b[0m Evaluate and display a Python expression\n \x1b[95m{{ search elements name = "Hydrogen" }}\x1b[0m\n \x1b[2mEmbedded {{ ... }} queries return their result as a value.\x1b[0m\n Python control-flow blocks:\n \x1b[95m[if condition] ... [elif condition] ... [else] ... [endblock]\x1b[0m\n \x1b[95m[for item in iterable] ... [endblock]\x1b[0m\n \x1b[95m[while condition] ... [endblock]\x1b[0m\n \x1b[95m[break] and [continue] inside loops\x1b[0m\n Multiline Python block:\n \x1b[95m[[[\x1b[0m\n \x1b[95mfor x in range(3):\x1b[0m\n \x1b[95m print(x)\x1b[0m\n \x1b[95m]]] # close on its own line\x1b[0m\n\n\x1b[1;93m\x1b[1m◆ SHELL, COMMENTS & COMMAND COMPOSITION\x1b[0m\n \x1b[1;96m`command` \x1b[0m \x1b[90m│\x1b[0m Run a command in the persistent system shell\n \x1b[1;96m```bash ... ``` \x1b[0m \x1b[90m│\x1b[0m Run a multiline shell block (also sh, cmd, terminal)\n \x1b[1;96m// comment \x1b[0m \x1b[90m│\x1b[0m Single-line comment\n \x1b[1;96m/// ... /// \x1b[0m \x1b[90m│\x1b[0m Multiline comment\n \x1b[1;96mcommand1; command2 \x1b[0m \x1b[90m│\x1b[0m Run multiple commands on one line\n \x1b[1;96mtrailing backslash \x1b[0m \x1b[90m│\x1b[0m Continue a command on the next line\n \x1b[1;96mview \x1b[0m \x1b[90m│\x1b[0m Open the bundled periodic-table viewer\n \x1b[1;96mclear \x1b[0m \x1b[90m│\x1b[0m Clear the terminal\n\n\x1b[1;93m\x1b[1m◆ HISTORY EXPORT\x1b[0m\n \x1b[92mdump FILE \x1b[0m \x1b[90m│\x1b[0m Export all recorded queries and results\n \x1b[92mdump all|good|bad FILE \x1b[0m \x1b[90m│\x1b[0m Export all, successful, or failed executions\n \x1b[92mdump all|good|bad queries FILE\x1b[0m \x1b[90m│\x1b[0m Export query history\n \x1b[92mdump all|good|bad results FILE\x1b[0m \x1b[90m│\x1b[0m Export result history\n\n\x1b[1;93m\x1b[1m◆ COMMAND-LINE OPTIONS\x1b[0m\n \x1b[1;96mchemql \x1b[0m \x1b[90m│\x1b[0m Start the interactive prompt\n \x1b[1;96mchemql -c "COMMAND" \x1b[0m \x1b[90m│\x1b[0m Execute one command\n \x1b[1;96mchemql -f FILE \x1b[0m \x1b[90m│\x1b[0m Execute commands from a file\n \x1b[1;96mchemql --version \x1b[0m \x1b[90m│\x1b[0m Print the installed version\n \x1b[1;96mchemql --no-ansi \x1b[0m \x1b[90m│\x1b[0m Disable ANSI styling in output\n \x1b[1;96mchemql -c "COMMAND" -o FILE \x1b[0m \x1b[90m│\x1b[0m Write command output to a plain-text file\n \x1b[1;96mchemql --upgrade [VERSION] \x1b[0m \x1b[90m│\x1b[0m Upgrade to latest or a chosen release\n \x1b[1;96mchemql --force \x1b[0m \x1b[90m│\x1b[0m Force upgrade past system-package protections\n\n\x1b[1;93m\x1b[1m◆ PYTHON API\x1b[0m\n \x1b[95mfrom chemql import execute_query_text, execute_query\x1b[0m\n \x1b[95mresult = execute_query_text(\'search elements symbol = "O"\')\x1b[0m\n \x1b[2mDistribution package: chemql-python · import / CLI name: chemql\x1b[0m\n\n\x1b[1;93m\x1b[1m⚠ SAFETY & DATA NOTES\x1b[0m\n \x1b[2mPython blocks and shell commands run real code with your user permissions.\x1b[0m\n \x1b[2mRun only code and commands you trust. External lookups need network access\x1b[0m\n \x1b[2mand can fail when a remote service is unavailable.\x1b[0m\n\n\x1b[1;94m────────────────────────────────────────────────────────────────────────────────\x1b[0m\n\x1b[90mChemQL 1.2.0 · Type \x1b[92mhelp\x1b[90m for this guide, or \x1b[92mexit\x1b[90m to quit.\x1b[0m\n'
|
|
4770
4940
|
|
|
4771
4941
|
import webbrowser
|
|
4772
4942
|
from pathlib import Path
|
|
@@ -5142,7 +5312,7 @@ class ChemQLQuery:
|
|
|
5142
5312
|
return ChemQLQuery(f"{self.query} intersection {other.query}")
|
|
5143
5313
|
|
|
5144
5314
|
def __str__(self):
|
|
5145
|
-
return f"Query of `{self.query}`"
|
|
5315
|
+
return (f"Query of `{self.query}`" if NO_ANSI else f"Query of \033[1m{self.query}\033[22m")
|
|
5146
5316
|
|
|
5147
5317
|
def __repr__(self):
|
|
5148
5318
|
return str(self)
|
|
@@ -5311,7 +5481,7 @@ def _unclosed_expression_delimiters(lines):
|
|
|
5311
5481
|
return expected
|
|
5312
5482
|
|
|
5313
5483
|
|
|
5314
|
-
def main():
|
|
5484
|
+
def main(ANSI: bool = True):
|
|
5315
5485
|
parser = argparse.ArgumentParser(
|
|
5316
5486
|
description="Chemql query and chemistry language"
|
|
5317
5487
|
)
|
|
@@ -5374,7 +5544,7 @@ def main():
|
|
|
5374
5544
|
sys.exit(upgrade_package(version_target, args.force))
|
|
5375
5545
|
|
|
5376
5546
|
global NO_ANSI
|
|
5377
|
-
NO_ANSI = args.no_ansi or bool(args.output)
|
|
5547
|
+
NO_ANSI = not ANSI or args.no_ansi or bool(args.output)
|
|
5378
5548
|
|
|
5379
5549
|
if args.output and args.command is None and args.file is None:
|
|
5380
5550
|
parser.error("-o/--output requires -c/--command or -f/--file")
|
|
@@ -5517,7 +5687,7 @@ def run_interactive():
|
|
|
5517
5687
|
try:
|
|
5518
5688
|
print(_dump_history(first_line.strip()))
|
|
5519
5689
|
except Exception as exc:
|
|
5520
|
-
print(f"Error: {exc}")
|
|
5690
|
+
print(f"Error: {exc}" if NO_ANSI else "\033[31m" + f"Error: {exc}" + "\033[0m")
|
|
5521
5691
|
continue
|
|
5522
5692
|
|
|
5523
5693
|
if not first_line.strip():
|
|
@@ -5652,7 +5822,7 @@ def run_interactive():
|
|
|
5652
5822
|
lines = []
|
|
5653
5823
|
break
|
|
5654
5824
|
except EOFError:
|
|
5655
|
-
message = "Missing `[endblock]`"
|
|
5825
|
+
message = ("Missing `[endblock]`" if NO_ANSI else "Missing \033[1m[endblock]\033[22m")
|
|
5656
5826
|
print("\n" + (message if NO_ANSI else "\033[31mMissing `\033[1m[endblock]\033[22m`\033[0m"))
|
|
5657
5827
|
lines = []
|
|
5658
5828
|
break
|
|
@@ -5675,4 +5845,4 @@ def run_interactive():
|
|
|
5675
5845
|
|
|
5676
5846
|
|
|
5677
5847
|
if __name__ == "__main__":
|
|
5678
|
-
main()
|
|
5848
|
+
main(True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chemql-python
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -19,7 +19,7 @@ SQL-inspired syntax, inspect elements and molecules, look up reactions,
|
|
|
19
19
|
balance equations, and combine chemistry queries with Python and shell
|
|
20
20
|
commands.
|
|
21
21
|
|
|
22
|
-
- **Version:** `1.3.
|
|
22
|
+
- **Version:** `1.3.2`
|
|
23
23
|
- **PyPI distribution:** `chemql-python`
|
|
24
24
|
- **Python import / CLI name:** `chemql`
|
|
25
25
|
- **Author:** Mohammad Rakibul Islam
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|