Skip to content

Commit 67488b9

Browse files
Merge pull request #235 from Dan-Ashby/feature/custom-characters-and-brackets
Added custom bracket functionality.
2 parents f16475a + 1d55cd9 commit 67488b9

File tree

4 files changed

+176
-9
lines changed

4 files changed

+176
-9
lines changed

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,47 @@ e.g.
192192
handcalcs.set_option("custom_symbols", {"V_dot": "\\dot{V}", "N_star": "N^{*}"})
193193
```
194194

195-
Will now allow this kind of rendering:
195+
This can also be used to swap substrings or individual characters within the variable name. For example, the following:
196+
197+
```python
198+
handcalcs.set_option("custom_symbols", {"star": "^*", "C": ","})
199+
```
200+
Would render `Mstar_1C2` to $M^*_{1,2}$
201+
202+
This now allow this kind of rendering:
196203

197204
![Custom symbols example showing the use of V_dot and N_star](docs/images/custom_symbols.png)
198205

199206
The docstring in the `handcalcs.set_option()` function demonstrates which options are available and what values they take.
200207
---
201208

209+
#### Custom Brackets (New in v1.?.?)
210+
211+
Functioning similiar to the Custom Symbols, this allows a specified character or string of characters to be swapped for brackets. For example:
212+
```python
213+
handcalcs.set_option("custom_brackets", {
214+
"parenthesis": "ˉ", # macron (ˉ) → parentheses ( )
215+
"square_brackets": "ˍ", # low line (ˍ) → square brackets [ ]
216+
"angle_brackets": "ˆ", # modifier letter circumflex accent (ˆ) → angle brackets ⟨ ⟩
217+
"curly_brackets": "ǂ", # double pipe (ǂ) → curly brackets { }
218+
"pipes": "ǀ", # vertical bar (ǀ) → pipes | |
219+
"double_pipes": "ǁ", # double vertical bar (ǁ) → double pipes ‖ ‖
220+
})
221+
```
222+
Will render the following:<br>
223+
`myvarˉ1ˉ` -> $myvar(1)$<br>
224+
`myvarˍ2ˍ` -> $myvar[2]$<br>
225+
`myvarˆ3ˆ` -> $myvar\langle3\rangle$<br>
226+
`myvarǂ4ǂ` -> $myvar\lbrace4\rbrace$<br>
227+
`myvarǀ5ǀ` -> $myvar|5|$<br>
228+
`myvarǁ6ǁ` -> $myvar\|6\|$<br>
229+
These can be nested as below:<br>
230+
`myvarˉˆ7ˆˉ_ǁ8ǁ` -> $myvar(\langle7\rangle)_{\|8\|}$<br>
231+
232+
Note that the example above utilizes a range of rarely used characters for the mapping, which has the drawback of making the variable names not readily typed on the keyboard. The user could alternatively specify `"square_brackets": "SB"` for mapping to square brackets, for example, such that `myvarSB9SB` would map to $myvar[9]$, but this then makes the variable names less legible.
233+
234+
Note that as valid LaTeX strings require paired brackets, there will always be an equal number of opening and closing brackets, so the function works by sequentially replacing opening and closing brackets. This means you can't create `myvar(1(2))` as this would always map to `myvar(1)(2)`
235+
202236

203237
## Override tags
204238

src/handcalcs/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"greek_exclusions": [],
1212
"param_columns": 3,
1313
"preferred_string_formatter": "L",
14-
"custom_symbols": {}
14+
"custom_symbols": {},
15+
"custom_brackets": {}
1516
}

src/handcalcs/handcalcs.py

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2054,6 +2054,7 @@ def swap_symbolic_calcs(
20542054
functions_on_symbolic_expressions = [
20552055
insert_parentheses,
20562056
swap_custom_symbols,
2057+
swap_custom_brackets,
20572058
swap_math_funcs,
20582059
swap_superscripts,
20592060
swap_chained_fracs,
@@ -2164,15 +2165,117 @@ def swap_custom_symbols(d: deque, **config_options) -> deque:
21642165
custom_symbols = config_options.get("custom_symbols", {})
21652166
new_item = item
21662167
for symbol, latex_symbol in custom_symbols.items():
2167-
if symbol in item:
2168-
new_item = item.replace(symbol, latex_symbol)
2169-
break
2168+
if (
2169+
symbol in new_item
2170+
): # Changed to new_item to allow changes to a string accumulate
2171+
new_item = new_item.replace(
2172+
symbol, latex_symbol
2173+
) # Changed to new_item to allow changes to a string accumulate
2174+
# Removed break to permit multiple replacements
21702175
swapped_items.append(new_item)
21712176
else:
21722177
swapped_items.append(item)
21732178
return swapped_items
21742179

21752180

2181+
def swap_custom_brackets(d: deque, **config_options) -> deque:
2182+
"""
2183+
Swaps custom bracket character or string with their corresponding LaTeX brackets.
2184+
Set with user defined dictionary 'custom_brackets' in config_options
2185+
2186+
Example usage:
2187+
2188+
handcalcs.set_option("custom_brackets", {
2189+
"parenthesis": 'ˉ', # Replace ˉ with consecutively alternating ( and ) characters
2190+
"square_brackets": 'ˍ' # Replaces ˍ with consecutively alternating [ and ] characters
2191+
})
2192+
2193+
Resulting behavior:
2194+
'myvarˉ0ˉ' -> 'myvar(0)'
2195+
'myvarˍ0ˍ' -> 'myvar[0]'
2196+
2197+
Supported bracket types:
2198+
- parenthesis
2199+
- square_brackets
2200+
- angle_brackets
2201+
- curly_brackets
2202+
- pipes
2203+
- double_pipes
2204+
"""
2205+
swapped_items = deque([])
2206+
for item in d:
2207+
if isinstance(item, deque):
2208+
new_item = swap_custom_brackets(item, **config_options)
2209+
swapped_items.append(new_item)
2210+
elif isinstance(item, str):
2211+
custom_brackets = config_options.get("custom_brackets", {})
2212+
new_item = item
2213+
2214+
# Process each bracket type
2215+
for bracket_type, custom_str in custom_brackets.items():
2216+
if custom_str and custom_str in new_item:
2217+
if bracket_type == "parenthesis":
2218+
new_item = _replace_alternating_brackets(
2219+
new_item, custom_str, "(", ")"
2220+
)
2221+
elif bracket_type == "square_brackets":
2222+
new_item = _replace_alternating_brackets(
2223+
new_item, custom_str, "[", "]"
2224+
)
2225+
elif bracket_type == "angle_brackets":
2226+
new_item = _replace_alternating_brackets(
2227+
new_item, custom_str, r"\langle", r"\rangle"
2228+
)
2229+
elif bracket_type == "curly_brackets":
2230+
new_item = _replace_alternating_brackets(
2231+
new_item, custom_str, r"\lbrace", r"\rbrace"
2232+
)
2233+
elif bracket_type == "pipes":
2234+
new_item = _replace_alternating_brackets(
2235+
new_item, custom_str, "|", "|"
2236+
)
2237+
elif bracket_type == "double_pipes":
2238+
new_item = _replace_alternating_brackets(
2239+
new_item, custom_str, r"\|", r"\|"
2240+
)
2241+
2242+
swapped_items.append(new_item)
2243+
else:
2244+
swapped_items.append(item)
2245+
return swapped_items
2246+
2247+
2248+
def _replace_alternating_brackets(
2249+
text: str, custom_str: str, left_bracket: str, right_bracket: str
2250+
) -> str:
2251+
"""
2252+
Helper function to replace alternating occurrences of custom_str with left and right brackets.
2253+
Supports both single and multi-character custom_str strings.
2254+
"""
2255+
if custom_str not in text:
2256+
return text
2257+
2258+
result = ""
2259+
is_left = True # Start with left bracket
2260+
i = 0
2261+
str_len = len(custom_str)
2262+
2263+
while i < len(text):
2264+
# Check if we're at the start of a custom_str occurrence
2265+
if text[i : i + str_len] == custom_str:
2266+
if is_left:
2267+
result += left_bracket
2268+
else:
2269+
result += right_bracket
2270+
is_left = not is_left # Toggle for next occurrence
2271+
i += str_len # Skip past the entire custom_str
2272+
else:
2273+
result += text[i]
2274+
i += 1
2275+
2276+
return result
2277+
2278+
21762279
def swap_log_func(d: deque, calc_results: dict, **config_options) -> deque:
21772280
"""
21782281
Returns a new deque representing 'd' but with any log functions swapped
@@ -2400,21 +2503,20 @@ def extend_subscripts(pycode_as_deque: deque, **config_options) -> deque:
24002503
"""
24012504
swapped_deque = deque([])
24022505
for item in pycode_as_deque:
2403-
discount = 0 # hack to prevent excess braces from swap_long_var_str
24042506
if isinstance(item, deque):
24052507
new_item = extend_subscripts(item) # recursion!
24062508
swapped_deque.append(new_item)
24072509
elif isinstance(item, str) and "_" in item and not "\\int" in item:
2408-
if "\\mathrm{" in item or "\\operatorname{" in item:
2409-
discount = 1
24102510
new_item = ""
24112511
for char in item:
24122512
if char == "_":
24132513
new_item += char
24142514
new_item += "{"
24152515
else:
24162516
new_item += char
2417-
num_braces = new_item.count("{") - discount
2517+
num_braces = new_item.count("{") - new_item.count(
2518+
"}"
2519+
) # count unclosed braces
24182520

24192521
new_item += "}" * num_braces
24202522
swapped_deque.append(new_item)

test_handcalcs/test_handcalcs_file.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060
"param_columns": 3,
6161
"preferred_string_formatter": "L",
6262
"custom_symbols": {"V_dot": "\\dot{V}"},
63+
"custom_brackets": {
64+
"parenthesis": "ˉ",
65+
"square_brackets": "ˍ",
66+
"angle_brackets": "ˆ",
67+
"curly_brackets": "ǂ",
68+
"pipes": "ǀ",
69+
"double_pipes": "ǁ",
70+
}
6371
}
6472

6573
config_underscore_spaces = config_options.copy()
@@ -1289,6 +1297,28 @@ def test_swap_for_greek():
12891297
deque(["lamb", "=", 3]), **config_options
12901298
) == deque(["\\lambda", "=", 3])
12911299

1300+
def test_swap_custom_brackets():
1301+
assert handcalcs.handcalcs.swap_custom_brackets(
1302+
deque(["myvarˉ1ˉ", "=", "1"]), **config_options # Test round brackets
1303+
) == deque(["myvar(1)", "=", "1"])
1304+
assert handcalcs.handcalcs.swap_custom_brackets(
1305+
deque(["myvarˍ2ˍ", "=", "1"]), **config_options # Test square brackets
1306+
) == deque(["myvar[2]", "=", "1"])
1307+
assert handcalcs.handcalcs.swap_custom_brackets(
1308+
deque(["myvarˆ3ˆ", "=", "1"]), **config_options # Test angle brackets
1309+
) == deque([r"myvar\langle3\rangle", "=", "1"])
1310+
assert handcalcs.handcalcs.swap_custom_brackets(
1311+
deque(["myvarǂ4ǂ", "=", "1"]), **config_options # Test curly brackets
1312+
) == deque([r"myvar\lbrace4\rbrace", "=", "1"])
1313+
assert handcalcs.handcalcs.swap_custom_brackets(
1314+
deque(["myvarǀ5ǀ", "=", "1"]), **config_options # Test pipes
1315+
) == deque(["myvar|5|", "=", "1"])
1316+
assert handcalcs.handcalcs.swap_custom_brackets(
1317+
deque(["myvarǁ6ǁ", "=", "1"]), **config_options # Test double pipes
1318+
) == deque([r"myvar\|6\|", "=", "1"])
1319+
assert handcalcs.handcalcs.swap_custom_brackets(
1320+
deque(["myvarˉˆ7ˆˉ_ǁAǁ", "=", "1"]), **config_options # Test mixed brackets
1321+
) == deque([r"myvar(\langle7\rangle)_\|A\|", "=", "1"])
12921322

12931323
def test_test_for_scientific_float():
12941324
assert handcalcs.handcalcs.test_for_scientific_float("1.233e-3") == True

0 commit comments

Comments
 (0)