Skip to content

Commit 0a1909c

Browse files
committed
lint-hotkeys: Apply context to linting hotkeys.md.
Add checks for clashing keys in the same context. Added new `context` argument. Generates a new file `hotkeys-by-context.md`. Refactored all the functions to take any field as an input, either category / context, by passing newly created variables. Added FileNotFoundError exception. Removed `Esc` from `ALL_MESSAGES` to pass linting.
1 parent 5cd1582 commit 0a1909c

File tree

3 files changed

+135
-48
lines changed

3 files changed

+135
-48
lines changed

docs/hotkeys.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
|Scroll up|<kbd>PgUp</kbd> / <kbd>K</kbd>|
2525
|Scroll down|<kbd>PgDn</kbd> / <kbd>J</kbd>|
2626
|Go to bottom / Last message|<kbd>End</kbd> / <kbd>G</kbd>|
27-
|Narrow to all messages|<kbd>a</kbd> / <kbd>Esc</kbd>|
27+
|Narrow to all messages|<kbd>a</kbd>|
2828
|Narrow to all direct messages|<kbd>P</kbd>|
2929
|Narrow to all starred messages|<kbd>f</kbd>|
3030
|Narrow to messages in which you're mentioned|<kbd>#</kbd>|

tools/lint-hotkeys

Lines changed: 133 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ from collections import defaultdict
66
from pathlib import Path, PurePath
77
from typing import Dict, List, Tuple
88

9+
from typing_extensions import Literal
10+
911
from zulipterminal.config.keys import (
1012
HELP_CATEGORIES,
13+
HELP_CONTEXTS,
1114
KEY_BINDINGS,
1215
display_keys_for_command,
1316
)
@@ -17,34 +20,62 @@ KEYS_FILE = (
1720
Path(__file__).resolve().parent.parent / "zulipterminal" / "config" / "keys.py"
1821
)
1922
KEYS_FILE_NAME = KEYS_FILE.name
20-
OUTPUT_FILE = Path(__file__).resolve().parent.parent / "docs" / "hotkeys.md"
21-
OUTPUT_FILE_NAME = OUTPUT_FILE.name
23+
HOTKEYS_GROUPED_BY_CATEGORIES_FILE = (
24+
Path(__file__).resolve().parent.parent / "docs" / "hotkeys.md"
25+
)
26+
HOTKEYS_GROUPED_BY_CATEGORIES_FILE_NAME = HOTKEYS_GROUPED_BY_CATEGORIES_FILE.name
27+
HOTKEYS_GROUPED_BY_CONTEXTS_FILE = (
28+
Path(__file__).resolve().parent.parent / "docs" / "hotkeys-by-context.md"
29+
)
30+
HOTKEYS_GROUPED_BY_CONTEXTS_FILE_NAME = HOTKEYS_GROUPED_BY_CONTEXTS_FILE.name
2231
SCRIPT_NAME = PurePath(__file__).name
2332
HELP_TEXT_STYLE = re.compile(r"^[a-zA-Z /()',&@#:_-]*$")
2433

2534
# Exclude keys from duplicate keys checking
2635
KEYS_TO_EXCLUDE = ["q", "e", "m", "r"]
2736

2837

29-
def main(fix: bool) -> None:
38+
def main(fix: bool, context: bool) -> None:
39+
"""
40+
Handles the arguments and calls the respective functions
41+
"""
3042
if fix:
31-
generate_hotkeys_file()
32-
else:
33-
lint_hotkeys_file()
43+
generate_hotkeys_file(
44+
help_groups=HELP_CATEGORIES,
45+
grouped_keys=read_help_groups("key_category"),
46+
output_file=HOTKEYS_GROUPED_BY_CATEGORIES_FILE,
47+
output_file_name=HOTKEYS_GROUPED_BY_CATEGORIES_FILE_NAME,
48+
grouping="category",
49+
)
50+
if context:
51+
generate_hotkeys_file(
52+
help_groups=HELP_CONTEXTS,
53+
grouped_keys=read_help_groups("key_context"),
54+
output_file=HOTKEYS_GROUPED_BY_CONTEXTS_FILE,
55+
output_file_name=HOTKEYS_GROUPED_BY_CONTEXTS_FILE_NAME,
56+
grouping="context",
57+
)
58+
if not fix and not context:
59+
lint_hotkeys_file(
60+
grouped_keys_by_category=read_help_groups("key_category"),
61+
grouped_keys_by_context=read_help_groups("key_context"),
62+
output_file=HOTKEYS_GROUPED_BY_CATEGORIES_FILE,
63+
output_file_name=HOTKEYS_GROUPED_BY_CATEGORIES_FILE_NAME,
64+
)
3465

3566

36-
def lint_hotkeys_file() -> None:
67+
def lint_hotkeys_by_group(
68+
grouped_keys: Dict[str, List[Tuple[str, List[str]]]],
69+
help_groups: Dict[str, str],
70+
grouping: str,
71+
) -> int:
3772
"""
38-
Lint KEYS_FILE for key description, then compare if in sync with
39-
existing OUTPUT_FILE
73+
Lint KEYS_FILE for key description and key combinations
4074
"""
41-
hotkeys_file_string = get_hotkeys_file_string()
42-
# To lint keys description
43-
error_flag = 0
44-
categories = read_help_categories()
45-
for action in HELP_CATEGORIES:
75+
error_flag: int = 0
76+
for action in help_groups:
4677
check_duplicate_keys_list: List[str] = []
47-
for help_text, key_combinations_list in categories[action]:
78+
for help_text, key_combinations_list in grouped_keys[action]:
4879
check_duplicate_keys_list.extend(key_combinations_list)
4980
various_key_combinations = " / ".join(key_combinations_list)
5081
# Check description style
@@ -65,49 +96,85 @@ def lint_hotkeys_file() -> None:
6596
]
6697
if len(duplicate_keys) != 0:
6798
print(
68-
f"Duplicate key combination for keys {duplicate_keys} for category ({HELP_CATEGORIES[action]}) detected"
99+
f"Duplicate key combination for keys {duplicate_keys} for ({grouping}: {help_groups[action]}) detected"
69100
)
70101
error_flag = 1
102+
return error_flag
103+
104+
105+
def lint_hotkeys_file(
106+
grouped_keys_by_category: Dict[str, List[Tuple[str, List[str]]]],
107+
grouped_keys_by_context: Dict[str, List[Tuple[str, List[str]]]],
108+
output_file: Path,
109+
output_file_name: str,
110+
) -> None:
111+
"""
112+
Lint KEYS_FILE for key descriptions, category and context.
113+
Then compare if in sync with existing OUTPUT_FILE.
114+
"""
115+
error_flag: int = 0
116+
error_flag |= lint_hotkeys_by_group(
117+
grouped_keys_by_category, HELP_CATEGORIES, "category"
118+
)
119+
error_flag |= lint_hotkeys_by_group(
120+
grouped_keys_by_context, HELP_CONTEXTS, "context"
121+
)
71122
if error_flag == 1:
72123
print(f"Rerun this command after resolving errors in config/{KEYS_FILE_NAME}")
73124
else:
74125
print("No hotkeys linting errors")
75-
if not output_file_matches_string(hotkeys_file_string):
126+
hotkeys_file_string = get_hotkeys_file_string(
127+
HELP_CATEGORIES, grouped_keys_by_category
128+
)
129+
if not output_file_matches_string(
130+
hotkeys_file_string, output_file, output_file_name
131+
):
76132
print(
77-
f"Run './tools/{SCRIPT_NAME} --fix' to update {OUTPUT_FILE_NAME} file"
133+
f"Run './tools/{SCRIPT_NAME} --fix' to update {output_file_name} file"
78134
)
79135
error_flag = 1
80136
sys.exit(error_flag)
81137

82138

83-
def generate_hotkeys_file() -> None:
139+
def generate_hotkeys_file(
140+
help_groups: Dict[str, str],
141+
grouped_keys: Dict[str, List[Tuple[str, List[str]]]],
142+
output_file: Path,
143+
output_file_name: str,
144+
grouping: str,
145+
) -> None:
84146
"""
85147
Generate OUTPUT_FILE based on help text description and
86148
shortcut key combinations in KEYS_FILE
87149
"""
88-
hotkeys_file_string = get_hotkeys_file_string()
89-
output_file_matches_string(hotkeys_file_string)
90-
write_hotkeys_file(hotkeys_file_string)
91-
print(f"Hot Keys list saved in {OUTPUT_FILE}")
150+
hotkeys_file_string = get_hotkeys_file_string(help_groups, grouped_keys)
151+
output_file_matches_string(hotkeys_file_string, output_file, output_file_name)
152+
write_hotkeys_file(hotkeys_file_string, output_file)
153+
if grouping == "category":
154+
print(f"Hotkeys list saved in {output_file}")
155+
else:
156+
print(f"Hotkeys list grouped by {grouping} saved in {output_file}")
92157

93158

94-
def get_hotkeys_file_string() -> str:
159+
def get_hotkeys_file_string(
160+
help_groups: Dict[str, str],
161+
grouped_keys: Dict[str, List[Tuple[str, List[str]]]],
162+
) -> str:
95163
"""
96164
Construct string in form for output to OUTPUT_FILE based on help text
97165
description and shortcut key combinations in KEYS_FILE
98166
"""
99-
categories = read_help_categories()
100167
hotkeys_file_string = (
101168
f"<!--- Generated automatically by tools/{SCRIPT_NAME} -->\n"
102169
"<!--- Do not modify -->\n\n# Hot Keys\n"
103170
)
104-
for action in HELP_CATEGORIES:
171+
for group in help_groups:
105172
hotkeys_file_string += (
106-
f"## {HELP_CATEGORIES[action]}\n"
173+
f"## {help_groups[group]}\n"
107174
"|Command|Key Combination|\n"
108175
"| :--- | :---: |\n"
109176
)
110-
for help_text, key_combinations_list in categories[action]:
177+
for help_text, key_combinations_list in grouped_keys[group]:
111178
various_key_combinations = " / ".join(
112179
[
113180
" + ".join([f"<kbd>{key}</kbd>" for key in key_combination.split()])
@@ -119,34 +186,48 @@ def get_hotkeys_file_string() -> str:
119186
return hotkeys_file_string
120187

121188

122-
def output_file_matches_string(hotkeys_file_string: str) -> bool:
123-
with open(OUTPUT_FILE) as output_file:
124-
content_is_identical = hotkeys_file_string == output_file.read()
125-
if content_is_identical:
126-
print(f"{OUTPUT_FILE_NAME} file already in sync with config/{KEYS_FILE_NAME}")
127-
return True
128-
else:
129-
print(f"{OUTPUT_FILE_NAME} file not in sync with config/{KEYS_FILE_NAME}")
189+
def output_file_matches_string(
190+
hotkeys_file_string: str, output_file: Path, output_file_name: str
191+
) -> bool:
192+
"""
193+
Read the content of the existing OUTPUT_FILE as a string
194+
and check if it matches the newly generated hotkeys_file_string
195+
"""
196+
try:
197+
with open(output_file) as output_file_object:
198+
content_is_identical = hotkeys_file_string == output_file_object.read()
199+
if content_is_identical:
200+
print(
201+
f"{output_file_name} file already in sync with config/{KEYS_FILE_NAME}"
202+
)
203+
return True
204+
else:
205+
print(f"{output_file_name} file not in sync with config/{KEYS_FILE_NAME}")
206+
return False
207+
except FileNotFoundError:
208+
print(f"{output_file_name} does not exist")
130209
return False
131210

132211

133-
def read_help_categories() -> Dict[str, List[Tuple[str, List[str]]]]:
212+
def read_help_groups(
213+
key_group: Literal["key_category", "key_context"]
214+
) -> Dict[str, List[Tuple[str, List[str]]]]:
134215
"""
135-
Get all help categories from KEYS_FILE
216+
Get all help keys grouped by key_group (categories / contexts) from KEYS_FILE
136217
"""
137-
categories = defaultdict(list)
218+
grouped_keys = defaultdict(list)
138219
for cmd, item in KEY_BINDINGS.items():
139-
categories[item["key_category"]].append(
220+
grouped_keys[item[key_group]].append(
140221
(item["help_text"], display_keys_for_command(cmd))
141222
)
142-
return categories
223+
return grouped_keys
143224

144225

145-
def write_hotkeys_file(hotkeys_file_string: str) -> None:
226+
def write_hotkeys_file(hotkeys_file_string: str, output_file: Path) -> None:
146227
"""
147-
Write hotkeys_file_string variable once to OUTPUT_FILE
228+
Write hotkeys_file_string variable once to the target output file
148229
"""
149-
with open(OUTPUT_FILE, "w") as hotkeys_file:
230+
with open(output_file, "w") as hotkeys_file:
150231
hotkeys_file.write(hotkeys_file_string)
151232

152233

@@ -158,8 +239,14 @@ if __name__ == "__main__":
158239
parser.add_argument(
159240
"--fix",
160241
action="store_true",
161-
help=f"Generate {OUTPUT_FILE_NAME} file by extracting key description and key "
242+
help=f"Generate {HOTKEYS_GROUPED_BY_CATEGORIES_FILE_NAME} file by extracting key description and key "
243+
f"combination from config/{KEYS_FILE_NAME} file",
244+
)
245+
parser.add_argument(
246+
"--context",
247+
action="store_true",
248+
help=f"Generate {HOTKEYS_GROUPED_BY_CONTEXTS_FILE_NAME} file by extracting key description and key "
162249
f"combination from config/{KEYS_FILE_NAME} file",
163250
)
164251
args = parser.parse_args()
165-
main(args.fix)
252+
main(args.fix, args.context)

zulipterminal/config/keys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ class KeyBinding(TypedDict):
220220
'key_context': 'stream_view',
221221
},
222222
'ALL_MESSAGES': {
223-
'keys': ['a', 'esc'],
223+
'keys': ['a'],
224224
'help_text': 'Narrow to all messages',
225225
'key_category': 'navigation',
226226
'key_context': 'general',

0 commit comments

Comments
 (0)