Skip to content

Commit 11d7e73

Browse files
committed
run: Support multiple .zuliprc files.
Add optional positional argument for organization name. Allow changing alias name. Update error messages. Tests updated. Docs not yet updated.
1 parent 47c34fd commit 11d7e73

File tree

2 files changed

+85
-27
lines changed

2 files changed

+85
-27
lines changed

tests/cli/test_run.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,10 @@ def unreadable_dir(tmp_path: Path) -> Generator[Tuple[Path, Path], None, None]:
374374
"path_to_use, expected_exception",
375375
[
376376
("unreadable", "PermissionError"),
377-
("goodnewhome", "FileNotFoundError"),
378377
],
379-
ids=["valid_path_but_cannot_be_written_to", "path_does_not_exist"],
378+
ids=[
379+
"valid_path_but_cannot_be_written_to",
380+
],
380381
)
381382
def test_main_cannot_write_zuliprc_given_good_credentials(
382383
monkeypatch: pytest.MonkeyPatch,
@@ -406,8 +407,9 @@ def test_main_cannot_write_zuliprc_given_good_credentials(
406407

407408
expected_line = (
408409
"\x1b[91m"
409-
f"{expected_exception}: .zuliprc could not be created "
410-
f"at {Path(zuliprc_path) / '.config' / 'zulip' / '.zuliprc'}"
410+
f"{expected_exception}: "
411+
f"{Path(zuliprc_path) / '.config' / 'zulip' / '.zuliprc'}"
412+
" could not be created."
411413
"\x1b[0m"
412414
)
413415
assert lines[-1] == expected_line
@@ -587,7 +589,7 @@ def test__write_zuliprc__fail_file_exists(
587589

588590
error_message = _write_zuliprc(path, api_key=key, server_url=url, login_id=id)
589591

590-
assert error_message == ".zuliprc already exists at " + str(path)
592+
assert error_message == f"{path} already exists."
591593

592594

593595
@pytest.mark.parametrize(

zulipterminal/cli/run.py

+78-22
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
120120
parser = argparse.ArgumentParser(
121121
description=description, formatter_class=formatter_class
122122
)
123+
parser.add_argument(
124+
"org-name",
125+
nargs="?",
126+
action="store",
127+
default="",
128+
help="Enter a unique name for your zulip organization profile. "
129+
"It can be an acronym or a nickname. This will serve "
130+
"as a local alias for your organization's configuration profile.",
131+
)
123132
parser.add_argument(
124133
"-v",
125134
"--version",
@@ -132,7 +141,7 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
132141
"-c",
133142
action="store",
134143
help="config file downloaded from your zulip "
135-
"organization (default: ~/.config/zulip/.zuliprc)",
144+
"organization (default: ~/.config/zulip/org-name.zuliprc)",
136145
)
137146
parser.add_argument(
138147
"--theme",
@@ -265,9 +274,9 @@ def get_api_key(realm_url: str) -> Optional[Tuple[str, str, str]]:
265274
return None
266275

267276

268-
def fetch_zuliprc(zuliprc_path: Path) -> None:
277+
def fetch_zuliprc(zuliprc_path: Path, org_name: str) -> Path:
269278
print(
270-
f"{in_color('red', f'.zuliprc file was not found at {zuliprc_path}')}"
279+
f"{in_color('red', f'{zuliprc_path} was not found.')}"
271280
f"\nPlease enter your credentials to login into your Zulip organization."
272281
f"\n"
273282
f"\nNOTE: The {in_color('blue', 'Zulip server URL')}"
@@ -292,6 +301,9 @@ def fetch_zuliprc(zuliprc_path: Path) -> None:
292301
login_data = get_api_key(realm_url)
293302

294303
preferred_realm_url, login_id, api_key = login_data
304+
if org_name == "":
305+
zuliprc_path = default_zuliprc_path()
306+
zuliprc_path = _prompt_org_name_change(zuliprc_path, org_name)
295307
save_zuliprc_failure = _write_zuliprc(
296308
zuliprc_path,
297309
login_id=login_id,
@@ -302,32 +314,60 @@ def fetch_zuliprc(zuliprc_path: Path) -> None:
302314
print(f"Generated API key saved at {zuliprc_path}")
303315
else:
304316
exit_with_error(save_zuliprc_failure)
317+
return zuliprc_path
318+
319+
320+
def _prompt_org_name_change(zuliprc_path: Path, org_name: str) -> Path:
321+
if org_name == "":
322+
update_org_name = styled_input(
323+
"Do you wish to assign an alias to refer to this server? [y/N] "
324+
)
325+
else:
326+
update_org_name = styled_input(
327+
f"You have set the alias '{zuliprc_path.stem}' for this server."
328+
f" Do you wish to use a different alias? [y/N] "
329+
)
330+
if update_org_name.lower() in ["y", "yes"]:
331+
new_org_name = styled_input("Enter new alias: ")
332+
zuliprc_path = default_zuliprc_path(new_org_name)
333+
return zuliprc_path
305334

306335

307336
def _write_zuliprc(
308-
to_path: Path, *, login_id: str, api_key: str, server_url: str
337+
to_path: Path,
338+
*,
339+
login_id: Optional[str] = None,
340+
api_key: Optional[str] = None,
341+
server_url: Optional[str] = None,
342+
file_contents: Optional[str] = None,
309343
) -> str:
310344
"""
311345
Writes a .zuliprc file, returning a non-empty error string on failure
312346
Only creates new private files; errors if file already exists
313347
"""
314348
try:
349+
to_path.parent.mkdir(parents=True, exist_ok=True)
315350
with open(
316351
os.open(to_path, os.O_CREAT | os.O_WRONLY | os.O_EXCL, 0o600), "w"
317352
) as f:
318-
f.write(f"[api]\nemail={login_id}\nkey={api_key}\nsite={server_url}")
353+
if file_contents is not None:
354+
f.write(file_contents)
355+
else:
356+
f.write(f"[api]\nemail={login_id}\nkey={api_key}\nsite={server_url}")
319357
return ""
320358
except FileExistsError:
321-
return f".zuliprc already exists at {to_path}"
359+
return f"{to_path} already exists."
322360
except OSError as ex:
323-
return f"{ex.__class__.__name__}: .zuliprc could not be created at {to_path}"
361+
return f"{ex.__class__.__name__}: {to_path} could not be created."
324362

325363

326-
def parse_zuliprc(zuliprc_str: str) -> Dict[str, SettingData]:
327-
zuliprc_path = Path(zuliprc_str).expanduser()
364+
def parse_zuliprc(
365+
zuliprc_path: Path, org_name: str
366+
) -> Tuple[Dict[str, SettingData], Path]:
367+
zuliprc_path = zuliprc_path.expanduser()
328368
while not path.exists(zuliprc_path):
329369
try:
330-
fetch_zuliprc(zuliprc_path)
370+
zuliprc_path = fetch_zuliprc(zuliprc_path, org_name)
331371
# Invalid user inputs (e.g. pressing arrow keys) may cause ValueError
332372
except (OSError, ValueError):
333373
# Remove zuliprc file if created.
@@ -345,13 +385,15 @@ def parse_zuliprc(zuliprc_str: str) -> Dict[str, SettingData]:
345385
print(
346386
in_color(
347387
"red",
348-
"ERROR: Please ensure your .zuliprc is NOT publicly accessible:\n"
388+
"ERROR: Please ensure your {2} is NOT publicly accessible:\n"
349389
" {0}\n"
350390
"(it currently has permissions '{1}')\n"
351391
"This can often be achieved with a command such as:\n"
352392
" chmod og-rwx {0}\n"
353-
"Consider regenerating the [api] part of your .zuliprc to ensure "
354-
"your account is secure.".format(zuliprc_path, stat.filemode(mode)),
393+
"Consider regenerating the [api] part of your {2} to ensure "
394+
"your account is secure.".format(
395+
zuliprc_path, stat.filemode(mode), zuliprc_path.name
396+
),
355397
)
356398
)
357399
sys.exit(1)
@@ -361,9 +403,13 @@ def parse_zuliprc(zuliprc_str: str) -> Dict[str, SettingData]:
361403
try:
362404
res = zuliprc.read(zuliprc_path)
363405
if len(res) == 0:
364-
exit_with_error(f"Could not access .zuliprc file at {zuliprc_path}")
406+
exit_with_error(
407+
f"Could not access {zuliprc_path.name} file at {zuliprc_path.parent}"
408+
)
365409
except configparser.MissingSectionHeaderError:
366-
exit_with_error(f"Failed to parse .zuliprc file at {zuliprc_path}")
410+
exit_with_error(
411+
f"Failed to parse {zuliprc_path.name} file at {zuliprc_path.parent}"
412+
)
367413

368414
# Initialize with default settings
369415
settings = {
@@ -376,7 +422,7 @@ def parse_zuliprc(zuliprc_str: str) -> Dict[str, SettingData]:
376422
for conf in config:
377423
settings[conf] = SettingData(config[conf], ConfigSource.ZULIPRC)
378424

379-
return settings
425+
return settings, zuliprc_path
380426

381427

382428
def list_themes() -> str:
@@ -388,7 +434,7 @@ def list_themes() -> str:
388434
suffix += "[default theme]"
389435
text += f" {theme} {suffix}\n"
390436
return text + (
391-
"Specify theme in .zuliprc file or override "
437+
"Specify theme in a .zuliprc file or override "
392438
"using -t/--theme options on command line."
393439
)
394440

@@ -402,6 +448,10 @@ def xdg_config_home() -> Path:
402448
return Path.home() / ".config"
403449

404450

451+
def default_zuliprc_path(org_name: Optional[str] = "") -> Path:
452+
return xdg_config_home() / "zulip" / f"{org_name}.zuliprc"
453+
454+
405455
def main(options: Optional[List[str]] = None) -> None:
406456
"""
407457
Launch Zulip Terminal.
@@ -440,10 +490,13 @@ def main(options: Optional[List[str]] = None) -> None:
440490
print(list_themes())
441491
sys.exit(0)
442492

493+
org_name = getattr(args, "org-name")
443494
if args.config_file:
444-
zuliprc_path = args.config_file
495+
if org_name != "":
496+
exit_with_error("Cannot use --config-file and org-name together")
497+
zuliprc_path = Path(args.config_file)
445498
else:
446-
zuliprc_path = xdg_config_home() / "zulip" / ".zuliprc"
499+
zuliprc_path = default_zuliprc_path(org_name)
447500

448501
print(
449502
"Detected:"
@@ -452,7 +505,7 @@ def main(options: Optional[List[str]] = None) -> None:
452505
)
453506

454507
try:
455-
zterm = parse_zuliprc(zuliprc_path)
508+
zterm, zuliprc_path = parse_zuliprc(zuliprc_path, org_name)
456509

457510
### Validate footlinks settings (not from command line)
458511
if (
@@ -528,7 +581,10 @@ def main(options: Optional[List[str]] = None) -> None:
528581
helper_text = (
529582
["Valid values are:"]
530583
+ [f" {option}" for option in valid_remaining_values]
531-
+ [f"Specify the {setting} option in .zuliprc file."]
584+
+ [
585+
f"Specify the {setting} option "
586+
f"in {Path(zuliprc_path).name} file."
587+
]
532588
)
533589
exit_with_error(
534590
"Invalid {} setting '{}' was specified {}.".format(
@@ -577,7 +633,7 @@ def print_setting(setting: str, data: SettingData, suffix: str = "") -> None:
577633
boolean_settings[setting] = zterm[setting].value == valid_boolean_values[0]
578634

579635
Controller(
580-
config_file=zuliprc_path,
636+
config_file=str(zuliprc_path),
581637
maximum_footlinks=maximum_footlinks,
582638
theme_name=theme_to_use.value,
583639
theme=theme_data,

0 commit comments

Comments
 (0)