Skip to content

Commit

Permalink
Merge pull request #43 from thushjandan/download-specific-version
Browse files Browse the repository at this point in the history
Add possibility to download a specific docset version
  • Loading branch information
Morpheus636 authored Oct 6, 2022
2 parents 713b6a3 + bd035c5 commit 3fd3511
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 14 deletions.
75 changes: 64 additions & 11 deletions src/zeal/docset.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os
import shutil
import urllib

import bs4

from . import config, downloads, exceptions

LATEST_VERSION = "latest"

def list_all(docset_dir: str = config.docset_dir) -> list:
"""List the docsets in the docset_dir.
Expand All @@ -20,11 +22,12 @@ def list_all(docset_dir: str = config.docset_dir) -> list:
return installed_docsets


def download(docset_name: str, feeds_dir: str, docset_dir: str = config.docset_dir) -> None:
def download(docset_name: str, feeds_dir: str, docset_version: str = LATEST_VERSION, docset_dir: str = config.docset_dir) -> None:
"""Download a docset by its feed name.
: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_version: String, the docset version to download. Default: zeal.docset.LATEST_VERSION
:param docset_dir: String, the directory Zeal reads docsets from. Default: filesystem.docset_dir
:return: None
"""
Expand All @@ -33,24 +36,28 @@ def download(docset_name: str, feeds_dir: str, docset_dir: str = config.docset_d
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):
if file.endswith(".xml"):
available_docsets.add(file)

# Find the correct docset .xml file
for file in available_docsets:
if file.startswith(docset_name):
docset_xml_path = os.path.join(feeds_dir, file)
break
# Get docset xml file
docset_xml_path = _get_docset_xml(docset_name, feeds_dir)

# Extract the URL and download it to the docset dir
with open(docset_xml_path, "r") as file:
file_contents = file.read()
soup = bs4.BeautifulSoup(file_contents, "lxml")
urls = soup.find_all("url")
url = urls[0].getText()
# Adjust URL if version is specified
if docset_version != LATEST_VERSION:
# Verify if version is available in feed
if soup.find("other-versions") and soup.findAll(string=docset_version):
parsed_uri = urllib.parse.urlparse(url)
file_name = os.path.basename(parsed_uri.path)
url = f"{parsed_uri.scheme}://{parsed_uri.netloc}/feeds/zzz/versions/{docset_name}/{docset_version}/{file_name}"
else:
raise exceptions.DocsetNotExistsError(
f"Version {docset_version} does not exist for {docset_name}"
)

downloads.download_and_extract(url, docset_dir)


Expand All @@ -66,3 +73,49 @@ def remove(docset_name: str, docset_dir: str = config.docset_dir):
f"The docset to remove '{docset_name}' cannot be removed because it is not installed on your system."
)
shutil.rmtree(os.path.join(docset_dir, f"{docset_name}.docset"))


def get_docset_versions(docset_name: str, feeds_dir: str):
"""Returns a list of available versions of a particular docset.
:param docset_name: String, the name of the docset to remove
:param feeds_dir: String, the feeds directory - use get_feeds() to create it and get its location.
:return: List of version as string
"""
docset_xml_path = _get_docset_xml(docset_name, feeds_dir)

# Extract the URL and download it to the docset dir
with open(docset_xml_path, "r") as file:
file_contents = file.read()
soup = bs4.BeautifulSoup(file_contents, "lxml")
# Verify if version is available in feed
if soup.find("other-versions"):
soup_docset_versions = soup.findAll('version')
return [docset_version.get_text() for docset_version in soup_docset_versions]


def _get_docset_xml(docset_name: str, feeds_dir: str):
"""Returns the correct docset xml file
:param docset_name: String, the name of the docset to remove
:param feeds_dir: String, the feeds directory - use get_feeds() to create it and get its location.
:return: the docset xml file path
"""
# Get a list of docset .xml files
available_docsets = set()
for file in os.listdir(feeds_dir):
if file.endswith(".xml"):
available_docsets.add(file)

# Find the correct docset .xml file
docset_xml_path = None
for file in available_docsets:
if file.startswith(docset_name):
docset_xml_path = os.path.join(feeds_dir, file)
break
if docset_xml_path is None:
raise exceptions.DocsetNotExistsError(
f"The docset '{docset_name}' cannot be found in the feeds to download."
)

return docset_xml_path
5 changes: 5 additions & 0 deletions src/zeal/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ class DocsetNotInstalledError(ZealException):
"""The docset name passed to docset.remove() is not installed, and cannot be removed."""

pass

class DocsetNotExistsError(ZealException):
"""The docset name or version string passed to docset.install() does not exists."""

pass
28 changes: 25 additions & 3 deletions src/zeal_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ def main():
"install", help="Install one or more docsets. See `zeal-cli install --help`"
)
install_command.add_argument(
"docsets", nargs="*", help="A list of docset names, separated by a space."
"docsets", nargs="*", help=(
"A list of docset names, separated by a space. "
"A specific version of a docset can be selected for installation by following "
"the docset name with an equals (=) and the version of the docset to select"
)
)

subparsers.add_parser("list", help="Prints a list of installed docsets")

search_command = subparsers.add_parser("search", help="Prints a list of installed docsets")
search_command.add_argument(
"docset", nargs=1, help="A name of a docset."
)

remove_command = subparsers.add_parser(
"remove", help="Delete one or more docsets. See `zeal-cli remove --help`"
)
Expand Down Expand Up @@ -65,8 +74,15 @@ def main():
print("Getting list of available docsets")
feeds = zeal.downloads.get_feeds()
for docset in args.docsets:
docset_name = docset
docset_version = zeal.docset.LATEST_VERSION
# Parse version string if defined
if '=' in docset:
docset_info = docset.split('=', 1)
docset_name = docset_info[0]
docset_version = docset_info[1]
print(f"Installing docset: {docset}")
zeal.docset.download(docset, feeds)
zeal.docset.download(docset_name, feeds, docset_version=docset_version)
print(f"Successfully installed docset: {docset}")
print("Cleaning up")
shutil.rmtree(feeds)
Expand All @@ -75,7 +91,13 @@ def main():
install_command.print_help()

elif args.action == "list":
print(*zeal.docset.list_all(), sep="\n")
print(*zeal.docset.list_all(), sep="\n")

elif args.action == "search":
feeds = zeal.downloads.get_feeds()
docset_name = args.docset[0]
docset_versions = zeal.docset.get_docset_versions(docset_name, feeds)
print(f"Available versions for docset {docset_name}: {', '.join(docset_versions)}")

elif args.action == "remove":
if args.docsets:
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/test_docset.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ def test_download_docset():
with pytest.raises(src.zeal.exceptions.DocsetAlreadyInstalledError):
src.zeal.docset.download("Django", feeds_path, docset_dir=docset_dir)

def test_download_docset_by_version():
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_version="2.2.7", 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_version="2.2.7", docset_dir=docset_dir)

def test_delete_docset():
with tempfile.TemporaryDirectory() as data_dir:
Expand All @@ -56,3 +69,15 @@ def test_delete_docset():
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"))


def test_list_docset_version():
with tempfile.TemporaryDirectory() as data_dir:
# Setup the tempdir
feeds_dir = os.path.join(data_dir, "feeds")
os.mkdir(feeds_dir)
feeds_path = src.zeal.downloads.get_feeds(feeds_dir)
# Test that for a docset a list of version strings is returned
docset_versions = src.zeal.docset.get_docset_versions("Django", feeds_path)
assert isinstance(docset_versions, list)
assert all(isinstance(version, str) for version in docset_versions)

0 comments on commit 3fd3511

Please sign in to comment.