diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d84d65e..f63c5f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: ["3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v2 diff --git a/requirements.txt b/requirements.txt index c8cab03..b7a22ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -feedparser==6.0.6 -beautifulsoup4==4.9.3 -requests==2.25.1 -SnakeMD==0.7.0 -pytest==6.2.4 -pytest-cov==2.12.1 +feedparser~=6.0 +beautifulsoup4~=4.12 +requests~=2.28 +snakemd~=2.0.0b2 +pytest~=7.3 +pytest-cov~=4.0 diff --git a/setup.py b/setup.py index 6858e00..5bbbd62 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="yomu", - version="0.1.0", + version="0.2.0", author="The Renegade Coder", author_email="jeremy.grifski@therenegadecoder.com", description="Generates the README for the 'How to Python Code' repo", @@ -16,7 +16,7 @@ install_requires=[ "feedparser>=6", "beautifulsoup4>=4", - "SnakeMD>=0" + "SnakeMD>=2" ], entry_points={ "console_scripts": [ @@ -25,6 +25,8 @@ }, classifiers=( "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License" diff --git a/yomu/readme.py b/yomu/readme.py index d195ee1..5d24cfc 100644 --- a/yomu/readme.py +++ b/yomu/readme.py @@ -3,9 +3,9 @@ from typing import Optional import feedparser +import requests from bs4 import BeautifulSoup -from snakemd import Document, InlineText, Table - +from snakemd import Document, Inline, Table logger = logging.getLogger(__name__) @@ -22,7 +22,7 @@ def main() -> None: raise ValueError(f'Invalid log level: {loglevel}') logging.basicConfig(level=numeric_level) how_to = HowTo() - how_to.page.output_page("") + how_to.page.dump("README") def _get_log_level() -> str: @@ -70,7 +70,7 @@ def get_series_posts() -> list: return feed -def get_youtube_video(entry) -> InlineText: +def get_youtube_video(entry) -> Inline: """ Generates an InlineText item corresponding to the YouTube video link if it exists. Otherwise, it returns an empty @@ -81,41 +81,84 @@ def get_youtube_video(entry) -> InlineText: """ content = entry.content[0].value soup = BeautifulSoup(content, "html.parser") - target = soup.find("h2", text="Video Summary") + target = soup.find("h2", string="Video Summary") if target: url = target.find_next_sibling().find_all("a")[-1]["href"] - return InlineText("Video", url=url) - return InlineText("") + return Inline("Video", link=url) + return Inline("") def get_slug(title: str, sep: str) -> str: + """ + Converts a title to a slug. + + :param title: title of item + :param sep: the separator to use in place of whitespace + :return: a slug from a title + """ return title.split(":")[0][:-10].lower().replace(" ", sep) -def get_challenge(title: str) -> InlineText: +def verify_url(url: str) -> bool: + """ + Checks that a URL exists. + + :param url: the URL to verify + :return: True if the URL exists; False otherwise + """ + try: + r = requests.get(url) + if r.status_code == 404: + logger.debug(f"URL does not exist: {url}") + return False + except: + logger.warning(f"Issue loading URL: {url}") + return False + return True + + +def get_challenge(title: str) -> Inline: + """ + Retrieves the link to the challenge code samples. + + :param title: the title of the article + :return: the link to the challenge folder, if it exists + """ slug = get_slug(title, "-") base = "https://github.com/TheRenegadeCoder/how-to-python-code/tree/main/challenges/" - challenge = InlineText("Challenge", url=f"{base}{slug}") - if not challenge.verify_url(): - return InlineText("") + challenge = Inline("Challenge", link=f"{base}{slug}") + if not verify_url(challenge._link): + return Inline("") return challenge -def get_notebook(title: str) -> InlineText: +def get_notebook(title: str) -> Inline: + """ + Retrieves the link to the Jupyter Notebook for the article. + + :param title: the title of the article + :return: the link to the notebook, if it exists + """ slug = get_slug(title, "_") base = "https://github.com/TheRenegadeCoder/how-to-python-code/tree/main/notebooks/" - notebook = InlineText("Notebook", f"{base}{slug}.ipynb") - if not notebook.verify_url(): - return InlineText("") + notebook = Inline("Notebook", link=f"{base}{slug}.ipynb") + if not verify_url(notebook._link): + return Inline("") return notebook -def get_test(title: str) -> InlineText: +def get_test(title: str) -> Inline: + """ + Retrieves the test file for the article. + + :param title: the title of the article + :return: the link to the test, if it exists + """ slug = get_slug(title, "_") base = "https://github.com/TheRenegadeCoder/how-to-python-code/tree/main/testing/" - test = InlineText("Test", f"{base}{slug}.py") - if not test.verify_url(): - return InlineText("") + test = Inline("Test", link=f"{base}{slug}.py") + if not verify_url(test._link): + return Inline("") return test @@ -130,10 +173,10 @@ def _load_data(self): self.feed = get_series_posts() def _build_readme(self): - self.page = Document("README") + self.page = Document() # Introduction - self.page.add_header("How to Python - Source Code") + self.page.add_heading("How to Python - Source Code") self.page.add_paragraph(_get_intro_text()) \ .insert_link("How to Python", "https://therenegadecoder.com/series/how-to-python/") \ .insert_link( @@ -153,25 +196,25 @@ def _build_readme(self): "Testing" ] table = Table( - [InlineText(header) for header in headers], + [Inline(header) for header in headers], self.build_table() ) - self.page.add_element(table) + self.page.add_block(table) - def build_table(self) -> list[list[InlineText]]: + def build_table(self) -> list[list[Inline]]: index = 1 body = [] for entry in self.feed: if "Code Snippets" not in entry.title: - article = InlineText("Article", url=entry.link) + article = Inline("Article", link=entry.link) youtube = get_youtube_video(entry) challenge = get_challenge(entry.title) notebook = get_notebook(entry.title) test = get_test(entry.title) body.append([ - InlineText(str(index)), - InlineText(entry.title), - InlineText(entry.published), + Inline(str(index)), + Inline(entry.title), + Inline(entry.published), article, youtube, challenge,