Skip to content

Commit 06d4aa0

Browse files
use ContentSwitcher instead of Screen
1 parent b5b52d9 commit 06d4aa0

7 files changed

+242
-236
lines changed

PyRegexPlayground/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ $ pipx install regexplayground
3131
$ regexplayground
3232
```
3333

34-
As yet another alternative, you can install `textual` (see [Textual documentation](https://textual.textualize.io/getting_started/) for more details), clone this repository and run the `pyregex_playground.py` file.
34+
As yet another alternative, you can install `textual==0.85.2` (see [Textual documentation](https://textual.textualize.io/getting_started/) for more details), clone this repository and run the `pyregex_playground.py` file.
3535

3636
Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines). Here are some sample screenshots:
3737

PyRegexPlayground/app_guide.md

+16-25
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,30 @@
1-
# App Guide
1+
### Regex Playground
22

3-
### Playground
4-
5-
You can type the search pattern in the **Compile** input box and press the **Enter** key to execute. For example, `re.compile(r'\d')` to match digit characters. Matching portions will be highlighted in red.
3+
You can type the search pattern in the **Compile** input box and press the **Enter** key (or **Ctrl+r**) to execute. For example, `re.compile(r'\d')` to match digit characters. Matching portions will be highlighted in red.
64

75
The compiled pattern is available via the `pat` variable and you can use `ip` to refer to the input string. You can transform or extract data by typing appropriate expression in the **Action** box. For example, `pat.sub(r'(\g<0>)', ip)` will add parenthesis around the matching portions.
86

97
You can skip the Compile box and directly use the Action box too. For example, `[m.span() for m in re.finditer(r'\d+', ip)]` to get the location of all the matching portions.
108

119
> **Warning:** There is no safeguard against the commands you have typed. They are treated as if you executed them from a Python program.
1210
13-
### Changing ip
14-
15-
The input string is obtained from the `ip.txt` file. You can change contents of this file and press **Ctrl+p** to update the `ip` variable. You'll have to press **Enter** again to update the results for the changed data.
11+
Press **F2** to get back to the Playground from other screens. You can also click the buttons at the top to navigate between the different screens.
1612

17-
If you cloned the repo, you'd be able to easily spot the `ip.txt` file in the same directory as the script you are running. If you installed the app via the `pip` command in a virtual environment, you'll have to locate this file first. For example:
13+
### Changing ip
1814

19-
```bash
20-
# go to the virtual environment
21-
$ find -name pyregex_playground.py
22-
./lib/python3.8/site-packages/regexplayground/pyregex_playground.py
15+
You can press **Ctrl+p** to edit the contents of the input string.
2316

24-
$ find -name ip.txt
25-
./lib/python3.8/site-packages/regexplayground/ip.txt
26-
```
17+
After making the changes, press **Ctrl+r** or navigate to Compile/Action box and press the **Enter** key to update the results for the changed data.
2718

28-
This convoluted procedure will be simplified in the future by adding a widget for accepting multiline user input.
19+
The input string is obtained from the `ip.txt` file located in the app directory. So, changing this file before starting the app is another option.
2920

3021
### Handling errors
3122

3223
Some of the error types are caught. In such cases, the background color of the input boxes will change to red and the error message will be displayed below the corresponding box. Other errors might result in the app crashing.
3324

3425
### Input box shortcuts
3526

36-
* Use mouse click to position the cursor anywhere you like
27+
* Use the mouse to position the cursor anywhere you like
3728
* **** move left by one character
3829
* **** move right by one character
3930
* **Home** or **Ctrl+a** move to the start of the line
@@ -46,28 +37,28 @@ Some of the error types are caught. In such cases, the background color of the i
4637
* **Ctrl+k** delete till the end of the line
4738
* **Backspace** or **Ctrl+h** delete character to the left of the cursor
4839
* **Delete** or **Ctrl+d** delete character under the cursor
49-
* **Enter** submit the code for execution
40+
* **Enter** or **Ctrl+r** submit the code for execution
5041

51-
When there are multiple input boxes in the same screen:
42+
To navigate between widgets in the same screen:
5243

53-
* **Tab** or **** to move to the next box
54-
* **Shift+Tab** or **** to move to the previous box
44+
* **Tab** to move to the next box
45+
* **Shift+Tab** to move to the previous box
5546

5647
### Theme
5748

58-
Press **Ctrl+t** to toggle the theme between light and dark modes.
49+
Press **Ctrl+t** to toggle between light and dark themes.
5950

6051
### App Guide
6152

62-
Press **F1** to display this help text from the playground screen. Press **Ctrl+n** to toggle table of contents. Press **Esc** to go back.
53+
Press **F1** to display this help screen.
6354

6455
### Cheatsheet
6556

66-
Press **F2** to display a cheatsheet for Python regular expressions. Press **Ctrl+n** to toggle table of contents. Press **Esc** to go back.
57+
Press **F3** to display a cheatsheet for Python regular expressions.
6758

6859
### Interactive Examples
6960

70-
Press **F3** for regular expression examples covering various topics. You can modify the code snippets and pressing the **Enter** key will update the corresponding output. Press **Ctrl+r** to discard changes and reload the examples. Press **Esc** to go back.
61+
Press **F4** for regular expression examples covering various topics. You can modify the code snippets and pressing the **Enter** key will update the corresponding output. Press **Ctrl+z** to discard changes and reload the examples.
7162

7263
> **Warning:** There is no safeguard against the commands you have typed. They are treated as if you executed them from a Python program.
7364

PyRegexPlayground/examples.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,70 @@
11
{
2-
"1a": "Examples for Python regular expressions are shown below. You can modify such code snippets and pressing the **Enter** key will update the corresponding output.\n\nAs a good practice, always use **raw strings** to construct the pattern, unless other formats are required. This will avoid conflict between special meaning of the backslash character in regular expressions and string literals.\n\n### Examples for `re.search()`\n\n",
2+
"1a": "Examples for Python regular expressions are shown below. You can modify such code snippets and pressing the [bold]Enter[/] key will update the corresponding output.\n\nAs a good practice, always use [bold]raw strings[/] to construct the pattern, unless other formats are required. This will avoid conflict between special meaning of the backslash character in regular expressions and string literals.\n\n[bold]Examples for [dark_orange3 on grey84]re.search()[/][/]\n",
33
"1b": [
44
"sentence = 'This is a sample string'\n# check if 'sentence' contains the pattern described by RE argument\nbool(re.search(r'is', sentence))",
55
"# ignore case while searching for a match\nbool(re.search(r'this', sentence, flags=re.I))",
66
"# example when pattern isn't found in the input string\nbool(re.search(r'xyz', sentence))",
77
"# use raw byte strings for patterns if input is of byte data type\nbool(re.search(rb'is', b'This is a sample string'))\n"
88
],
9-
"2a": "\n### String and Line anchors\n\n",
9+
"2a": "[bold]String and Line anchors[/]\n",
1010
"2b": [
1111
"# match the start of the input string\nbool(re.search(r'\\Ahi', 'hi hello\\ntop spot'))",
1212
"# match the start of a line\nbool(re.search(r'^top', 'hi hello\\ntop spot', flags=re.M))",
1313
"# match the end of strings\nwords = ['surrender', 'up', 'newer', 'do', 'era', 'eel', 'pest']\n[w for w in words if re.search(r'er\\Z', w)]",
1414
"# check if there's a whole line 'par'\nbool(re.search(r'^par$', 'spare\\npar\\ndare', flags=re.M))\n"
1515
],
16-
"3a": "\n### Examples for `re.findall()`\n\n",
16+
"3a": "[bold]Examples for [dark_orange3 on grey84]re.findall()[/][/]\n",
1717
"3b": [
1818
"# match 'par' with optional 's' at start and optional 'e' at end\nre.findall(r'\\bs?pare?\\b', 'par spar apparent spare part pare')",
1919
"# numbers >= 100 with optional leading zeros\n# Python 3.11 supports possessive quantifiers\n# re.findall(r'\\b0*+\\d{3,}\\b', '0501 035 154 12 26 98234')\nre.findall(r'\\b0*[1-9]\\d{2,}\\b', '0501 035 154 12 26 98234')",
2020
"# if multiple capturing groups are used, each element of output\n# will be a tuple of strings of all the capture groups\nre.findall(r'([^/]+)/([^/,]+),?', '2020/04,1986/Mar')",
2121
"# normal capture group will hinder ability to get the whole match\n# non-capturing group to the rescue\nre.findall(r'\\b\\w*(?:st|in)\\b', 'cost akin more east run against')",
2222
"# useful for debugging purposes as well\nre.findall(r':.*?:', 'green:3.14:teal::brown:oh!:blue')\n"
2323
],
24-
"4a": "\n### Examples for `re.split()`\n\n",
24+
"4a": "[bold]Examples for [dark_orange3 on grey84]re.split()[/][/]\n",
2525
"4b": [
2626
"# split based on one or more digit characters\nre.split(r'\\d+', 'Sample123string42with777numbers')",
2727
"# split based on digit or whitespace characters\nre.split(r'[\\d\\s]+', '**1\\f2\\n3star\\t7 77\\r**')",
2828
"# to include the matching delimiter strings as well in the output\nre.split(r'(\\d+)', 'Sample123string42with777numbers')",
2929
"# multiple capture groups example\n# note that the portion matched by b+ isn't present in the output\nre.split(r'(a+)b+(c+)', '3.14aabccc42')",
3030
"# use non-capturing group if capturing is not needed\nre.split(r'hand(?:y|ful)', '123handed42handy777handful500')\n"
3131
],
32-
"5a": "\n### Backreferencing within the search pattern\n\n",
32+
"5a": "[bold]Backreferencing within the search pattern[/]\n",
3333
"5b": [
3434
"# whole words that have at least one consecutive repeated character\nwords = ['effort', 'flee', 'facade', 'oddball', 'rat', 'tool']\n[w for w in words if re.search(r'\\b\\w*(\\w)\\1\\w*\\b', w)]\n"
3535
],
36-
"6a": "\n### Working with matched portions\n\n",
36+
"6a": "[bold]Working with matched portions[/]\n",
3737
"6b": [
3838
"# re.Match object\nre.search(r'so+n', 'too soon a song snatch')",
3939
"# retrieving entire matched portion, note the use of [0]\nmotivation = 'Doing is often better than thinking of doing.'\nre.search(r'of.*ink', motivation)[0]",
4040
"# capture group example\npurchase = 'coffee:100g tea:250g sugar:75g chocolate:50g'\nm = re.search(r':(.*?)g.*?:(.*?)g.*?chocolate:(.*?)g', purchase)\n# to get the matched portion of the second capture group\nm[2]",
4141
"# to get a tuple of all the capture groups\nm.groups()\n"
4242
],
43-
"7a": "\n### Examples for `re.finditer()`\n\n",
43+
"7a": "[bold]Examples for [dark_orange3 on grey84]re.finditer()[/][/]\n",
4444
"7b": [
4545
"# numbers < 350\nm_iter = re.finditer(r'\\d+', '45 349 651 593 4 204 350')\n[m[0] for m in m_iter if int(m[0]) < 350]",
4646
"# start and end+1 index of each matching portion\nm_iter = re.finditer(r'so+n', 'song too soon snatch')\n[m.span() for m in m_iter]\n"
4747
],
48-
"8a": "\n### Examples for `re.sub()`\n\n",
48+
"8a": "[bold]Examples for [dark_orange3 on grey84]re.sub()[/][/]\n",
4949
"8b": [
5050
"# add something to the start of every line\nip_lines = \"catapults\\nconcatenate\\ncat\"\nre.sub(r'^', r'* ', ip_lines, flags=re.M).splitlines(True)",
5151
"# replace 'par' only at the start of a word\nre.sub(r'\\bpar', r'X', 'par spar apparent spare part')",
5252
"# same as: r'part|parrot|parent'\nre.sub(r'par(en|ro)?t', r'X', 'par part parrot parent')",
5353
"# remove the first two columns where : is delimiter\nre.sub(r'\\A([^:]+:){2}', '', 'apple:123:banana:cherry')\n"
5454
],
55-
"9a": "\n### Backreferencing in the replacement section\n\n",
55+
"9a": "[bold]Backreferencing in the replacement section[/]\n",
5656
"9b": [
5757
"# remove any number of consecutive duplicate words separated by space\n# use \\W+ instead of space to cover cases like 'a;a<-;a'\nre.sub(r'\\b(\\w+)( \\1)+\\b', r'\\1', 'aa a a a 42 f_1 f_1 f_13.14')",
5858
"# add something around the matched strings\nre.sub(r'\\d+', r'(\\g<0>0)', '52 apples and 31 mangoes')",
5959
"# swap words that are separated by a comma\nre.sub(r'(\\w+),(\\w+)', r'\\2,\\1', 'good,bad 42,24')",
6060
"# example with both capturing and non-capturing groups\nre.sub(r'(\\d+)(?:abc)+(\\d+)', r'\\2:\\1', '1000abcabc42 12abcd21')\n"
6161
],
62-
"10a": "\n### Using functions in the replacement section of `re.sub()`\n\n",
62+
"10a": "[bold]Using functions in the replacement section of [dark_orange3 on grey84]re.sub()[/][/]\n",
6363
"10b": [
6464
"# factorial is imported from the math module\nnumbers = '1 2 3 4 5'\ndef fact_num(m): return str(factorial(int(m[0])))\nre.sub(r'\\d+', fact_num, numbers)",
6565
"# using lambda\nre.sub(r'\\d+', lambda m: str(factorial(int(m[0]))), numbers)\n"
6666
],
67-
"11a": "\n### Examples for lookarounds\n\n",
67+
"11a": "[bold]Examples for lookarounds[/]\n",
6868
"11b": [
6969
"# change 'cat' only if it is not followed by a digit character\n# note that the end of string satisfies the given assertion\n# 'catcat' has two matches as the assertion doesn't consume characters\nre.sub(r'cat(?!\\d)', 'dog', 'hey cats! cat42 cat_5 catcat')",
7070
"# change whole word only if it is not preceded by : or -\nre.sub(r'(?<![:-])\\b\\w+\\b', r'X', ':cart <apple -rest ;tea')",
@@ -73,7 +73,7 @@
7373
"# match if 'do' is not there between 'at' and 'par'\nbool(re.search(r'at((?!do).)*par', 'fox,cat,dog,parrot'))",
7474
"# match if 'go' is not there between 'at' and 'par'\nbool(re.search(r'at((?!go).)*par', 'fox,cat,dog,parrot'))\n"
7575
],
76-
"12a": "\n### Examples for `re.compile()`\n\nRegular expressions can be compiled using the `re.compile()` function, which gives back a `re.Pattern` object. The top level `re` module functions are all available as methods for this object. Compiling a regular expression helps if the RE has to be used in multiple places or called upon multiple times inside a loop (speed benefit). By default, Python maintains a small list of recently used RE, so the speed benefit doesn't apply for trivial use cases.\n\n",
76+
"12a": "[bold]Examples for [dark_orange3 on grey84]re.compile()[/][/]\n\nRegular expressions can be compiled using the [dark_orange3 on grey84]re.compile()[/] function, which gives back a [dark_orange3 on grey84]re.Pattern[/] object. The top level [dark_orange3 on grey84]re[/] module functions are all available as methods for this object. Compiling a regular expression helps if the RE has to be used in multiple places or called upon multiple times inside a loop (speed benefit). By default, Python maintains a small list of recently used RE, so the speed benefit doesn't apply for trivial use cases.\n",
7777
"12b": [
7878
"pet = re.compile(r'dog')\ntype(pet)",
7979
"bool(pet.search('They bought a dog'))",
2.44 KB
Loading
3.31 KB
Loading

PyRegexPlayground/pyregex_playground.css

+44-34
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
1-
.header {
2-
dock: top;
3-
text-align: center;
4-
color: black;
5-
background: orange;
1+
Screen {
2+
overflow-y: hidden;
3+
}
4+
5+
#cs_main {
6+
height: 1fr;
7+
}
8+
9+
#ip_edit {
10+
min-height: 5;
11+
height: auto;
12+
border: round green;
13+
border-title-align: center;
14+
}
15+
16+
.playground_ip {
17+
height: 1;
18+
padding: 0 1 0 1;
19+
border: none;
20+
}
21+
22+
.error {
23+
min-height: 0;
24+
height: auto;
25+
width: 100%;
26+
color: red;
27+
padding: 0 1 0 1;
28+
border: round red;
29+
}
30+
31+
.playground_ip_op {
632
width: 100%;
33+
padding: 0 1 0 1;
34+
border: round gray;
35+
border-title-align: center;
736
}
837

938
.container {
@@ -18,6 +47,16 @@
1847
border-title-align: center;
1948
}
2049

50+
Button:focus {
51+
text-style: bold;
52+
}
53+
54+
.buttons {
55+
max-height: 1;
56+
border: none;
57+
width: 1fr;
58+
}
59+
2160
.examples_ip {
2261
height: 1;
2362
padding: 0 1 0 1;
@@ -38,35 +77,6 @@
3877
.examples_md {
3978
margin: 1 2 0 2;
4079
border: none;
41-
}
42-
43-
.playground_ip {
44-
height: 1;
45-
padding: 0 1 0 1;
46-
border: none;
47-
}
48-
49-
.error {
50-
min-height: 0;
51-
height: auto;
5280
width: 100%;
53-
color: red;
54-
padding: 0 1 0 1;
55-
border: round red;
5681
}
5782

58-
.playground_ip_op {
59-
width: 100%;
60-
padding: 0 1 0 1;
61-
border: round gray;
62-
border-title-align: center;
63-
}
64-
65-
Button:focus {
66-
text-style: bold;
67-
}
68-
69-
.buttons {
70-
margin: 1 0 1 0;
71-
width: 30%;
72-
}

0 commit comments

Comments
 (0)