diff --git a/src/zeal_feeds/main.py b/src/zeal_feeds/main.py index 5a67415..85413cd 100644 --- a/src/zeal_feeds/main.py +++ b/src/zeal_feeds/main.py @@ -18,7 +18,8 @@ from rich.progress import Progress from rich.spinner import Spinner -from zeal_feeds import ApplicationError, user_contrib, zeal +from zeal_feeds import ApplicationError, user_contrib +from zeal_feeds.zeal import Zeal def main(): @@ -82,6 +83,8 @@ def install(args) -> str | None: if missing_docsets: return f"Failed to find the following docsets: {', '.join(missing_docsets)}" + zeal = Zeal.from_config() + installed_docsets = set(zeal.installed_docsets()) for docset in found_docsets.values(): if docset is None: @@ -92,7 +95,7 @@ def install(args) -> str | None: archive = _download_archive(docset) spinner = Spinner("simpleDots", f"Installing {docset.name}") with Live(spinner): - zeal.install(docset, archive) + zeal.install_docset(docset, archive) archive.unlink() return None diff --git a/src/zeal_feeds/zeal.py b/src/zeal_feeds/zeal.py index 44a05ec..69913e0 100644 --- a/src/zeal_feeds/zeal.py +++ b/src/zeal_feeds/zeal.py @@ -21,6 +21,8 @@ from . import ApplicationError from .user_contrib import DocSet +FEED_URL = "https://zealusercontributions.vercel.app/api/docsets/{name}.xml" + @attrs.define class MetaData: @@ -33,51 +35,57 @@ class MetaData: urls: list[str] -FEED_URL = "https://zealusercontributions.vercel.app/api/docsets/{name}.xml" - - -@functools.cache -def docset_install_path() -> Path: - """Get the path where Docsets are installed to.""" - if sys.platform == "win32": - install_path = _windows_docset_install_path() - else: - install_path = _linux_docset_install_path() - if not install_path.exists(): - install_path.mkdir(parents=True) - return install_path - - -def installed_docsets() -> Iterator[str]: - """List of locally installed DocSets.""" - docset_path = docset_install_path() - - for meta_json in docset_path.glob("*/meta.json"): - metadata = json.load(meta_json.open("r")) - yield metadata["name"] - - -def install(docset: DocSet, tarball: Path): - """Install a docset into the Zeal data directory.""" - # TODO: don't install if docset already installed - docset_path = docset_install_path() - - with tarfile.open(tarball) as docset_archive: - docset_folder = docset_archive.next() - if not (docset_folder and re.match(r"\w+\.docset", docset_folder.name)): - raise ApplicationError(f"Unexpected contents for {docset.name} archive") - docset_archive.extractall(docset_path) - - converter = Converter() - meta_json = docset_path / docset_folder.name / "meta.json" - metadata = MetaData( - name=docset.name, - title=docset.title, - version=docset.version, - urls=docset.urls, - feed_url=FEED_URL.format(name=docset.name), - ) - json.dump(converter.unstructure(metadata), meta_json.open("w"), indent=2) +@attrs.define +class Zeal: + docset_path: Path + + @classmethod + def from_config(cls): + """Get the path where Docsets are installed to.""" + if sys.platform == "win32": + docset_path = _windows_docset_install_path() + else: + docset_path = _linux_docset_install_path() + if not docset_path.exists(): + docset_path.mkdir(parents=True) + return cls(docset_path) + + def installed_docsets(self) -> Iterator[str]: + """List of locally installed DocSets.""" + for meta_json in self.docset_path.glob("*/meta.json"): + metadata = json.load(meta_json.open("r")) + yield metadata["name"] + + def install_docset(self, docset: DocSet, tarball: Path) -> None: + """Install a docset into the Zeal data directory.""" + # TODO: don't install if docset already installed + with tarfile.open(tarball) as docset_archive: + docset_folder = docset_archive.next() + if not (docset_folder and docset_folder.name.endswith(".docset")): + raise ApplicationError(f"Unexpected contents for {docset.name} archive") + docset_archive.extractall(self.docset_path) + + # ensure docset folder given the correct name + expected_folder_name = f"{docset.name}.docset" + if docset_folder.name != expected_folder_name: + destination = self.docset_path / expected_folder_name + if destination.exists(): + raise ApplicationError( + f"Destination folder already exists: {destination}" + ) + source = self.docset_path / docset_folder.name + source.rename(destination) + + converter = Converter() + meta_json = self.docset_path / expected_folder_name / "meta.json" + metadata = MetaData( + name=docset.name, + title=docset.title, + version=docset.version, + urls=docset.urls, + feed_url=FEED_URL.format(name=docset.name), + ) + json.dump(converter.unstructure(metadata), meta_json.open("w"), indent=2) def _linux_docset_install_path() -> Path: diff --git a/tests/data/wxPython.tgz b/tests/data/wxPython.tgz new file mode 100644 index 0000000..335ac47 Binary files /dev/null and b/tests/data/wxPython.tgz differ diff --git a/tests/test_zeal.py b/tests/test_zeal.py new file mode 100644 index 0000000..e2f81af --- /dev/null +++ b/tests/test_zeal.py @@ -0,0 +1,27 @@ +"""Test functionality in the `zeal` module.""" + +from zeal_feeds.zeal import Zeal +from zeal_feeds.user_contrib import DocSet, DocSetAuthor + + +def test_docset_install(data_folder, tmp_path) -> None: + + docset_name = "wxPython" + docset_archive = "wxPython.tgz" + + zeal = Zeal(tmp_path) + docset_tarball = data_folder / docset_archive + + docset_meta = DocSet( + name=docset_name, + author=DocSetAuthor( + name="", + link="", + ), + archive=docset_archive, + version="", + ) + + zeal.install_docset(docset_meta, docset_tarball) + print("Directory contents:", list(tmp_path.iterdir())) + assert (tmp_path / f"{docset_name}.docset").is_dir()