From 05cae3e4536bdcca1378901673a52cfb07923080 Mon Sep 17 00:00:00 2001 From: Justin Flannery Date: Wed, 6 Sep 2023 23:54:06 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20path=20kwargs=20(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- browsr/_base.py | 23 ++++++++++++++++++++- browsr/_cli.py | 32 ++++++++++++++++++++++++++++-- browsr/universal_directory_tree.py | 6 ++++-- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/browsr/_base.py b/browsr/_base.py index 89e84f6..9e59708 100644 --- a/browsr/_base.py +++ b/browsr/_base.py @@ -6,7 +6,9 @@ import math import pathlib +from copy import copy from dataclasses import dataclass +from os import PathLike from textwrap import dedent from typing import Any, ClassVar, Dict, List, Optional, Union @@ -24,11 +26,26 @@ from textual.reactive import reactive, var from textual.widget import Widget from textual.widgets import Button, DataTable, Static +from upath import UPath from browsr._config import favorite_themes from browsr._utils import FileInfo, handle_github_url +class BrowsrPath(UPath): + """ + A UPath object that can be extended with persisted kwargs + """ + + __path_kwargs__: ClassVar[Dict[str, Any]] = {} + + def __new__(cls, *args: str | PathLike[Any], **kwargs: Any) -> "BrowsrPath": + """ + Create a new BrowsrPath object + """ + return super().__new__(cls, *args, **kwargs, **cls.__path_kwargs__) + + @dataclass class TextualAppContext: """ @@ -39,6 +56,7 @@ class TextualAppContext: config: Optional[Dict[str, Any]] = None debug: bool = False max_file_size: int = 20 + kwargs: Optional[Dict[str, Any]] = None @property def path(self) -> pathlib.Path: @@ -56,8 +74,11 @@ def path(self) -> pathlib.Path: self.file_path = file_path if str(self.file_path).endswith("/"): self.file_path = str(self.file_path)[:-1] + kwargs = self.kwargs or {} + PathClass = copy(BrowsrPath) + PathClass.__path_kwargs__ = kwargs return ( - upath.UPath(self.file_path).resolve() + PathClass(self.file_path).resolve() if self.file_path else pathlib.Path.cwd().resolve() ) diff --git a/browsr/_cli.py b/browsr/_cli.py index 9adf6ca..9a6b1b7 100644 --- a/browsr/_cli.py +++ b/browsr/_cli.py @@ -2,7 +2,7 @@ browsr command line interface """ -from typing import Optional +from typing import Optional, Tuple import click import rich_click @@ -43,10 +43,14 @@ help="Enable extra debugging output", type=click.BOOL, ) +@click.option( + "-k", "--kwargs", multiple=True, help="Key=Value pairs to pass to the filesystem" +) def browsr( path: Optional[str], debug: bool, max_file_size: int, + kwargs: Tuple[str, ...], ) -> None: """ browsr 🗂️ a pleasant file explorer in your terminal @@ -107,6 +111,17 @@ def browsr( browsr az://bucket-name ``` + #### Pass Extra Arguments to Cloud Storage + + Some cloud storage providers require extra arguments to be passed to the + filesystem. For example, to browse an anonymous S3 bucket, you need to pass + the `anon=True` argument to the filesystem. This can be done with the `-k/--kwargs` + argument. + + ```shell + browsr s3://anonymous-bucket -k anon=True + ``` + ### GitHub #### Browse a GitHub repository @@ -160,7 +175,20 @@ def browsr( - **`D`** - Toggle dark mode for the application - **`X`** - Download the file from cloud storage """ - config = TextualAppContext(file_path=path, debug=debug, max_file_size=max_file_size) + extra_kwargs = {} + if kwargs: + for kwarg in kwargs: + try: + key, value = kwarg.split("=") + extra_kwargs[key] = value + except ValueError as ve: + raise click.BadParameter( + message=f"Invalid Key/Value pair: `{kwarg}` - must be in the format Key=Value", + param_hint="kwargs", + ) from ve + config = TextualAppContext( + file_path=path, debug=debug, max_file_size=max_file_size, kwargs=extra_kwargs + ) app = Browsr(config_object=config) app.run() diff --git a/browsr/universal_directory_tree.py b/browsr/universal_directory_tree.py index b7bbc3f..e35d0a5 100644 --- a/browsr/universal_directory_tree.py +++ b/browsr/universal_directory_tree.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing import ClassVar, Iterable, List, Optional +from typing import ClassVar, Iterable, List, Optional, Type from textual.binding import BindingType from textual.widgets._directory_tree import DirEntry @@ -12,7 +12,7 @@ from textual_universal_directorytree import UniversalDirectoryTree from upath import UPath as Path -from browsr._base import vim_cursor_bindings +from browsr._base import BrowsrPath, vim_cursor_bindings class BrowsrDirectoryTree(UniversalDirectoryTree): @@ -20,6 +20,8 @@ class BrowsrDirectoryTree(UniversalDirectoryTree): A DirectoryTree that can handle any filesystem. """ + PATH: Type[BrowsrPath] = BrowsrPath + BINDINGS: ClassVar[List[BindingType]] = [ *UniversalDirectoryTree.BINDINGS, *vim_cursor_bindings,