mirror of
https://gitlab.com/ceda_ei/firefox-web-apps
synced 2025-11-06 12:10:06 +01:00
Migrate setup.sh to python
This commit is contained in:
49
fwa/config.py
Normal file
49
fwa/config.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
import toml
|
||||
|
||||
|
||||
class _Config:
|
||||
DIR = Path.home() / ".config" / "fwa"
|
||||
FILE = DIR / "config.toml"
|
||||
ICON_DIR = DIR / "icons"
|
||||
BIN_DIR = Path.home() / ".local" / "bin"
|
||||
|
||||
def __init__(self):
|
||||
for directory in (self.DIR, self.ICON_DIR, self.BIN_DIR):
|
||||
if not directory.exists():
|
||||
directory.mkdir(parents=True)
|
||||
|
||||
if not self.FILE.exists():
|
||||
self.FILE.touch()
|
||||
|
||||
with open(self.FILE) as f:
|
||||
self.conf = toml.load(f)
|
||||
|
||||
def write_config(self):
|
||||
with open(self.FILE, "w") as f:
|
||||
toml.dump(self.conf, f)
|
||||
|
||||
|
||||
Config = _Config()
|
||||
|
||||
|
||||
def _get_firefox_bin() -> str:
|
||||
"""
|
||||
Returns a firefox binary name that exists in PATH
|
||||
"""
|
||||
for name in (
|
||||
"firefox",
|
||||
"firefox-developer-edition",
|
||||
"firefox-esr",
|
||||
"firefox-nightly",
|
||||
"firefox-beta",
|
||||
"firefox-trunk",
|
||||
):
|
||||
if shutil.which(name):
|
||||
return name
|
||||
raise OSError("Firefox binary not found in $PATH")
|
||||
|
||||
|
||||
FIREFOX_BINARY = _get_firefox_bin()
|
||||
4
fwa/log.py
Normal file
4
fwa/log.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from rich.console import Console
|
||||
|
||||
out = Console(stderr=False)
|
||||
err = Console(stderr=True)
|
||||
148
fwa/setup.py
Normal file
148
fwa/setup.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from prompt_toolkit import prompt
|
||||
from prompt_toolkit.completion import PathCompleter
|
||||
import typer
|
||||
|
||||
from fwa.config import FIREFOX_BINARY, Config
|
||||
from fwa.log import err, out
|
||||
from fwa.validation import BoolStrValidator, ProfilePathValidator, str_to_bool
|
||||
|
||||
|
||||
DEFAULT_PROFILE_NAME = "firefox-web-apps"
|
||||
|
||||
|
||||
def bool_prompt(message: str, default: Optional[bool] = None) -> bool:
|
||||
return str_to_bool(
|
||||
prompt(
|
||||
message,
|
||||
validator=BoolStrValidator(default=default),
|
||||
),
|
||||
default=default,
|
||||
)
|
||||
|
||||
|
||||
def enable_user_chrome(profile: Path):
|
||||
with open(profile / "user.js", "a") as f:
|
||||
f.write("\n")
|
||||
f.write(
|
||||
'user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true);'
|
||||
)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
def configure_user_chrome(
|
||||
profile: Path, hide_tabs: bool, hide_main_toolbar: bool, disable_url_bar: bool
|
||||
):
|
||||
chrome_dir = profile / "chrome"
|
||||
|
||||
if not chrome_dir.exists():
|
||||
chrome_dir.mkdir()
|
||||
|
||||
v_collapse = []
|
||||
if hide_tabs:
|
||||
v_collapse.append("#tabbrowser-tabs")
|
||||
v_collapse.append("#TabsToolbar")
|
||||
|
||||
if hide_main_toolbar:
|
||||
v_collapse.append("#nav-bar")
|
||||
|
||||
|
||||
with open(chrome_dir / "userChrome.css", "a") as f:
|
||||
if v_collapse:
|
||||
f.write(", ".join(v_collapse))
|
||||
f.write("{ visibility: collapse !important; }\n")
|
||||
|
||||
if disable_url_bar:
|
||||
f.write(".urlbarView { display: none !important; }\n")
|
||||
f.write(".urlbar-input-box { visibility: hidden !important; }\n")
|
||||
|
||||
|
||||
def setup(
|
||||
firefox_profile: Annotated[
|
||||
Optional[Path],
|
||||
typer.Option(
|
||||
"-f", "--firefox-profile", help="Path to existing firefox profile"
|
||||
),
|
||||
] = None,
|
||||
new_profile_name: Annotated[
|
||||
Optional[str],
|
||||
typer.Option("-n", "--new-profile", help="Name for a new firefox profile"),
|
||||
] = None,
|
||||
):
|
||||
"""
|
||||
This program will optionally create a firefox profile and configure it to
|
||||
use with Firefox Web Apps
|
||||
"""
|
||||
if firefox_profile is None:
|
||||
if new_profile_name is None:
|
||||
# prompt for new profile name
|
||||
use_existing = bool_prompt(
|
||||
"Use an existing profile for apps? (y/N):", False
|
||||
)
|
||||
if use_existing:
|
||||
firefox_profile = Path(
|
||||
prompt(
|
||||
"Enter path to existing profile: ",
|
||||
validator=ProfilePathValidator,
|
||||
completer=PathCompleter(),
|
||||
complete_while_typing=True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
new_profile_name = DEFAULT_PROFILE_NAME
|
||||
|
||||
if new_profile_name:
|
||||
firefox_profile = Path.home() / ".mozilla" / "firefox" / new_profile_name
|
||||
if firefox_profile.exists():
|
||||
err.print(
|
||||
f"[red bold]Firefox Profile {str(firefox_profile)!r} already exists"
|
||||
)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
out.print("[green]Creating Firefox Profile.")
|
||||
subprocess.run(
|
||||
[
|
||||
FIREFOX_BINARY,
|
||||
"-CreateProfile",
|
||||
f"{new_profile_name} {firefox_profile}",
|
||||
]
|
||||
)
|
||||
out.print("[green]Created Firefox Profile")
|
||||
|
||||
else:
|
||||
if not firefox_profile.exists():
|
||||
err.print(f"[red bold]{str(firefox_profile)!r} does not exist")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
if not firefox_profile.is_dir():
|
||||
err.print(f"[red bold]{str(firefox_profile)!r} is not a directory")
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
out.print("[green]Writing config...\n")
|
||||
Config.conf["firefox-profile"] = str(firefox_profile)
|
||||
Config.write_config()
|
||||
|
||||
out.print("[green]Enabling userChrome.css support...\n")
|
||||
enable_user_chrome(firefox_profile)
|
||||
|
||||
out.print("[green]Configuring userChrome.css...\n")
|
||||
|
||||
hide_tabs = bool_prompt("Do you want to hide tabs? (Y/n)", True)
|
||||
if hide_main_toolbar := bool_prompt("Do you want to hide main toolbar? (y/N)", False):
|
||||
hide_url_bar = False
|
||||
else:
|
||||
hide_url_bar = bool_prompt("Do you want to disable the URL bar? (Y/n)", True)
|
||||
|
||||
configure_user_chrome(
|
||||
firefox_profile,
|
||||
hide_tabs,
|
||||
hide_main_toolbar,
|
||||
hide_url_bar,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
typer.run(setup)
|
||||
37
fwa/validation.py
Normal file
37
fwa/validation.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from pathlib import Path
|
||||
from prompt_toolkit.validation import ValidationError, Validator
|
||||
|
||||
|
||||
def str_to_bool(string, default=None):
|
||||
normalized = string.lower().strip()
|
||||
if normalized in ("yes", "y"):
|
||||
return True
|
||||
if normalized in ("no", "n"):
|
||||
return False
|
||||
if default is not None and normalized == "":
|
||||
return default
|
||||
raise ValueError("could not convert string to bool: {string!r}")
|
||||
|
||||
|
||||
class BoolStrValidator(Validator):
|
||||
def __init__(self, default=None):
|
||||
self.default = default
|
||||
|
||||
def validate(self, document):
|
||||
try:
|
||||
str_to_bool(document.text, self.default)
|
||||
return True
|
||||
except ValueError:
|
||||
raise ValidationError(
|
||||
cursor_position=0, message="Invalid value. Please enter y/n"
|
||||
)
|
||||
|
||||
|
||||
def _is_existing_dir(path):
|
||||
return Path(path).is_dir()
|
||||
|
||||
|
||||
ProfilePathValidator = Validator.from_callable(
|
||||
_is_existing_dir,
|
||||
error_message="Directory does not exist",
|
||||
)
|
||||
Reference in New Issue
Block a user