Repository: Morpheus636/zeal-cli (language: python)

Issue:
Function to delete a docset



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/code_analysis_pull.yml b/.github/workflows/code_analysis_pull.yml
index 0f8f0d5..4ea55d6 100644
--- a/.github/workflows/code_analysis_pull.yml
+++ b/.github/workflows/code_analysis_pull.yml
@@ -15,7 +15,7 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v1
         with:
-          python-version: 3.8
+          python-version: 3.9
 
       - name: Setup Poetry
         uses: Gr1N/setup-poetry@v7
diff --git a/.github/workflows/code_analysis_push.yml b/.github/workflows/code_analysis_push.yml
index 9c5b290..b0b9483 100644
--- a/.github/workflows/code_analysis_push.yml
+++ b/.github/workflows/code_analysis_push.yml
@@ -14,7 +14,7 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v1
         with:
-          python-version: 3.8
+          python-version: 3.9
 
       - name: Setup Poetry
         uses: Gr1N/setup-poetry@v7
diff --git a/poetry.lock b/poetry.lock
index 57f1be5..0c1103e 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -90,7 +90,7 @@ unicode_backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
-version = "8.0.3"
+version = "8.0.4"
 description = "Composable command line interface toolkit"
 category = "dev"
 optional = false
@@ -117,7 +117,7 @@ python-versions = "*"
 
 [[package]]
 name = "filelock"
-version = "3.4.2"
+version = "3.6.0"
 description = "A platform independent file lock."
 category = "dev"
 optional = false
@@ -433,8 +433,8 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
 
 [metadata]
 lock-version = "1.1"
-python-versions = "^3.8"
-content-hash = "a72d968df4902bb58d06527c095a0a60da64ef9b12e1327ec398b1c352b8dc9b"
+python-versions = "^3.9"
+content-hash = "681fb0c9740b5eb6232860106390d685ed770fb9f5a7b5da3ffc803c58a0daa3"
 
 [metadata.files]
 atomicwrites = [
@@ -466,8 +466,8 @@ charset-normalizer = [
     {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
 ]
 click = [
-    {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
-    {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
+    {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
+    {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@@ -478,8 +478,8 @@ distlib = [
     {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
 ]
 filelock = [
-    {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
-    {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
+    {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"},
+    {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"},
 ]
 flake8 = [
     {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
diff --git a/pyproject.toml b/pyproject.toml
index 393f57b..d3da137 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ description = ""
 authors = ["Josh Levin (Morpheus636) <josh@joshinshaker.com>"]
 
 [tool.poetry.dependencies]
-python = "^3.8"
+python = "^3.9"
 requests = "^2.27.1"
 beautifulsoup4 = "^4.10.0"
 lxml = "^4.8.0"
@@ -19,7 +19,7 @@ pre-commit = "^2.13.0"
 
 [tool.black]
 line-length = 100
-target-version = ['py38']
+target-version = ['py39']
 include = '\.pyi'
 
 [tool.isort]
diff --git a/src/zeal/docset.py b/src/zeal/docset.py
index b25307c..f1d1cfe 100644
--- a/src/zeal/docset.py
+++ b/src/zeal/docset.py
@@ -2,17 +2,36 @@
 
 import bs4
 
-from . import downloads, filesystem
+from . import downloads, exceptions, filesystem
+
+
+def list_all(docset_dir: str = filesystem.docset_dir) -> list:
+    """List the docsets in the docset_dir.
+
+    :param docset_dir: String, path to the Zeal docset directory. DefaultL filesystem.docset_dir
+    :return: List of docsets (by name, not by path)
+    """
+    files_list = os.listdir(docset_dir)
+    installed_docsets = []
+    for file in files_list:
+        if os.path.isdir(os.path.join(docset_dir, file)) and file.endswith(".docset"):
+            installed_docsets.append(file.removesuffix(".docset"))
+    return installed_docsets
 
 
 def download(docset_name: str, feeds_dir: str, docset_dir: str = filesystem.docset_dir) -> None:
     """Download a docset by its feed name.
 
-    :param docset_name: String, the feed name of the docset to downloadf
+    :param docset_name: String, the feed name of the docset to download
     :param feeds_dir: String, the feeds directory - use get_feeds() to create it and get its location.
     :param docset_dir: String, the directory Zeal reads docsets from. Default: filesystem.docset_dir
     :return: None
     """
+    # Raise an exception if the docset is already installed
+    if docset_name in list_all(docset_dir=docset_dir):
+        raise exceptions.DocsetAlreadyInstalledError(
+            f"The docset '{docset_name}' is already installed."
+        )
     # Get a list of docset .xml files
     available_docsets = set()
     for file in os.listdir(feeds_dir):
@@ -31,19 +50,18 @@ def download(docset_name: str, feeds_dir: str, docset_dir: str = filesystem.docs
         soup = bs4.BeautifulSoup(file_contents, "lxml")
         urls = soup.find_all("url")
         url = urls[0].getText()
-        print(url)
     downloads.download_and_extract(url, docset_dir)
 
 
-def list_all(docset_dir: str = filesystem.docset_dir) -> list:
-    """List the docsets in the docset_dir.
+def remove(docset_name: str, docset_dir: str = filesystem.docset_dir):
+    """
 
-    :param docset_dir: String, path to the Zeal docset directory. DefaultL filesystem.docset_dir
-    :return: List of docsets (by name, not by path)
+    :param docset_name: String, the name of the docset to remove
+    :param docset_dir: Optional: String, the path to the docset directory. Default: filesystem.docset_dir
+    :return:
     """
-    files_list = os.listdir(docset_dir)
-    installed_docsets = []
-    for file in files_list:
-        if file.endswith(".docset"):
-            installed_docsets.append(file.rstrip(".docset"))
-    return installed_docsets
+    if docset_name not in list_all(docset_dir=docset_dir):
+        raise exceptions.DocsetNotInstalledError(
+            f"The docset to remove '{docset_name}' cannot be removed because it is not installed on your system."
+        )
+    os.rmdir(os.path.join(docset_dir, f"{docset_name}.docset"))
diff --git a/src/zeal/exceptions.py b/src/zeal/exceptions.py
index abfb246..6f7b624 100644
--- a/src/zeal/exceptions.py
+++ b/src/zeal/exceptions.py
@@ -1,3 +1,16 @@
 class ZealException(Exception):
     """The base exception for the Zeal CLI project."""
+
+    pass
+
+
+class DocsetAlreadyInstalledError(ZealException):
+    """The docset name passed to docset.install() is already installed."""
+
+    pass
+
+
+class DocsetNotInstalledError(ZealException):
+    """The docset name passed to docset.remove() is not installed, and cannot be removed."""
+
     pass


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 225135b..b87103a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -16,7 +16,7 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v1
         with:
-          python-version: 3.7
+          python-version: 3.9
 
       - name: Setup Poetry
         uses: Gr1N/setup-poetry@v7
diff --git a/tests/unit/test_docset.py b/tests/unit/test_docset.py
index a180867..4926d6c 100644
--- a/tests/unit/test_docset.py
+++ b/tests/unit/test_docset.py
@@ -2,22 +2,13 @@
 import sys
 import tempfile
 
+import pytest
+
 
 sys.path.append(os.path.relpath(__file__) + "/../src")
 import src.zeal  # NOQA: E402
 
 
-def test_download_docset():
-    with tempfile.TemporaryDirectory() as data_dir:
-        feeds_dir = os.path.join(data_dir, "feeds")
-        os.mkdir(feeds_dir)
-        docset_dir = os.path.join(data_dir, "docsets")
-        os.mkdir(docset_dir)
-        feeds_path = src.zeal.downloads.get_feeds(feeds_dir)
-        src.zeal.docset.download("Django", feeds_path, docset_dir=docset_dir)
-        assert os.path.isdir(os.path.join(docset_dir, "Django.docset"))
-
-
 def test_list_all():
     with tempfile.TemporaryDirectory() as docset_dir:
         os.mkdir(os.path.join(docset_dir, "docset1.docset"))
@@ -29,3 +20,39 @@ def test_list_all():
         assert "docset2" in docset_list
         assert "docset3" in docset_list
         assert "not_a_docset" not in docset_list
+
+
+def test_download_docset():
+    with tempfile.TemporaryDirectory() as data_dir:
+        # Setup the tempdir
+        feeds_dir = os.path.join(data_dir, "feeds")
+        os.mkdir(feeds_dir)
+        docset_dir = os.path.join(data_dir, "docsets")
+        os.mkdir(docset_dir)
+        feeds_path = src.zeal.downloads.get_feeds(feeds_dir)
+        # Test that the docset is downloaded to the right place
+        src.zeal.docset.download("Django", feeds_path, docset_dir=docset_dir)
+        assert os.path.isdir(os.path.join(docset_dir, "Django.docset"))
+        with pytest.raises(src.zeal.exceptions.DocsetAlreadyInstalledError):
+            src.zeal.docset.download("Django", feeds_path, docset_dir=docset_dir)
+
+
+def test_delete_docset():
+    with tempfile.TemporaryDirectory() as data_dir:
+        # Setup the tempdir
+        docset_dir = os.path.join(data_dir, "docsets")
+        os.mkdir(docset_dir)
+        # Test that the docset not existing raises an exception
+        with pytest.raises(src.zeal.exceptions.DocsetNotInstalledError):
+            src.zeal.docset.remove("TotallyRealDocset", docset_dir=docset_dir)
+        # Test that a file with the same name still raises the exception
+        with open(os.path.join(docset_dir, "TotallyRealDocset.docset"), "a"):
+            pass
+        with pytest.raises(src.zeal.exceptions.DocsetNotInstalledError):
+            src.zeal.docset.remove("TotallyRealDocset", docset_dir=docset_dir)
+        os.remove(os.path.join(docset_dir, "TotallyRealDocset.docset"))
+
+        # Test that the docset gets removed when it exists.
+        os.mkdir(os.path.join(docset_dir, "TotallyRealDocset.docset"))
+        src.zeal.docset.remove("TotallyRealDocset", docset_dir=docset_dir)
+        assert not os.path.isdir(os.path.join(docset_dir, "TotallyRealDocset.docset"))