|
| 1 | +import argparse |
| 2 | +import logging |
| 3 | +from typing import Optional |
| 4 | + |
| 5 | +import feedparser |
| 6 | +from bs4 import BeautifulSoup |
| 7 | +from snakemd import Document, InlineText, Table |
| 8 | + |
| 9 | + |
| 10 | +logger = logging.getLogger(__name__) |
| 11 | + |
| 12 | + |
| 13 | +def main() -> None: |
| 14 | + """ |
| 15 | + The main drop in function for README generation. |
| 16 | +
|
| 17 | + :return: nothing |
| 18 | + """ |
| 19 | + loglevel = _get_log_level() |
| 20 | + numeric_level = getattr(logging, loglevel.upper(), None) |
| 21 | + if not isinstance(numeric_level, int): |
| 22 | + raise ValueError(f'Invalid log level: {loglevel}') |
| 23 | + logging.basicConfig(level=numeric_level) |
| 24 | + how_to = HowTo() |
| 25 | + how_to.page.output_page("") |
| 26 | + |
| 27 | + |
| 28 | +def _get_log_level() -> str: |
| 29 | + """ |
| 30 | + A helper function which gets the log level from |
| 31 | + the command line. Set as warning from default. |
| 32 | +
|
| 33 | + :return: the log level provided by the user |
| 34 | + """ |
| 35 | + parser = argparse.ArgumentParser() |
| 36 | + parser.add_argument( |
| 37 | + "-log", |
| 38 | + "--log", |
| 39 | + default="warning", |
| 40 | + help=( |
| 41 | + "Provide logging level. " |
| 42 | + "Example --log debug', default='warning'" |
| 43 | + ), |
| 44 | + ) |
| 45 | + options = parser.parse_args() |
| 46 | + return options.log |
| 47 | + |
| 48 | + |
| 49 | +def _get_intro_text() -> str: |
| 50 | + return """ |
| 51 | + Welcome to a collection of Jupyter Notebooks from the How to Python series on The Renegade Coder. For |
| 52 | + convenience, you can access all of the articles, videos, challenges, and source code below. Alternatively, I keep |
| 53 | + an enormous article up to date with all these snippets as well. |
| 54 | + """ |
| 55 | + |
| 56 | + |
| 57 | +def get_series_posts() -> list: |
| 58 | + """ |
| 59 | + Collects all posts from the series into a feed. |
| 60 | +
|
| 61 | + :return: a list of posts from the How to Python series |
| 62 | + """ |
| 63 | + index = 1 |
| 64 | + base = "https://therenegadecoder.com/series/how-to-python/feed/?paged=" |
| 65 | + feed = [] |
| 66 | + while (rss := feedparser.parse(f"{base}{index}")).entries: |
| 67 | + feed.extend(rss.entries) |
| 68 | + index += 1 |
| 69 | + logger.debug(f"Collected {len(feed)} posts") |
| 70 | + return feed |
| 71 | + |
| 72 | + |
| 73 | +def get_youtube_video(entry) -> InlineText: |
| 74 | + """ |
| 75 | + Generates an InlineText item corresponding to the YouTube |
| 76 | + video link if it exists. Otherwise, it returns an empty |
| 77 | + InlineText element. |
| 78 | +
|
| 79 | + :param entry: a feedparser entry |
| 80 | + :return: the YouTube video as an InlineText element |
| 81 | + """ |
| 82 | + content = entry.content[0].value |
| 83 | + soup = BeautifulSoup(content, "html.parser") |
| 84 | + target = soup.find("h2", text="Video Summary") |
| 85 | + if target: |
| 86 | + url = target.find_next_sibling().find_all("a")[-1]["href"] |
| 87 | + return InlineText("Video", url=url) |
| 88 | + return InlineText("") |
| 89 | + |
| 90 | + |
| 91 | +def get_slug(title: str, sep: str) -> str: |
| 92 | + return title.split(":")[0][:-10].lower().replace(" ", sep) |
| 93 | + |
| 94 | + |
| 95 | +def get_challenge(title: str) -> InlineText: |
| 96 | + slug = get_slug(title, "-") |
| 97 | + base = "https://github.com/TheRenegadeCoder/how-to-python-code/tree/main/challenges/" |
| 98 | + challenge = InlineText("Challenge", url=f"{base}{slug}") |
| 99 | + if not challenge.verify_url(): |
| 100 | + return InlineText("") |
| 101 | + return challenge |
| 102 | + |
| 103 | + |
| 104 | +def get_notebook(title: str) -> InlineText: |
| 105 | + slug = get_slug(title, "_") |
| 106 | + base = "https://github.com/TheRenegadeCoder/how-to-python-code/tree/main/notebooks/" |
| 107 | + notebook = InlineText("Notebook", f"{base}{slug}.ipynb") |
| 108 | + if not notebook.verify_url(): |
| 109 | + return InlineText("") |
| 110 | + return notebook |
| 111 | + |
| 112 | + |
| 113 | +def get_test(title: str) -> InlineText: |
| 114 | + slug = get_slug(title, "_") |
| 115 | + base = "https://github.com/TheRenegadeCoder/how-to-python-code/tree/main/testing/" |
| 116 | + test = InlineText("Test", f"{base}{slug}.py") |
| 117 | + if not test.verify_url(): |
| 118 | + return InlineText("") |
| 119 | + return test |
| 120 | + |
| 121 | + |
| 122 | +class HowTo: |
| 123 | + def __init__(self): |
| 124 | + self.page: Optional[Document] = None |
| 125 | + self.feed: Optional[list] = None |
| 126 | + self._load_data() |
| 127 | + self._build_readme() |
| 128 | + |
| 129 | + def _load_data(self): |
| 130 | + self.feed = get_series_posts() |
| 131 | + |
| 132 | + def _build_readme(self): |
| 133 | + self.page = Document("README") |
| 134 | + |
| 135 | + # Introduction |
| 136 | + self.page.add_header("How to Python - Source Code") |
| 137 | + self.page.add_paragraph(_get_intro_text()) \ |
| 138 | + .insert_link("How to Python", "https://therenegadecoder.com/series/how-to-python/") \ |
| 139 | + .insert_link( |
| 140 | + "an enormous article", |
| 141 | + "https://therenegadecoder.com/code/python-code-snippets-for-everyday-problems/" |
| 142 | + ) |
| 143 | + |
| 144 | + # Table |
| 145 | + headers = [ |
| 146 | + "Index", |
| 147 | + "Title", |
| 148 | + "Publish Date", |
| 149 | + "Article", |
| 150 | + "Video", |
| 151 | + "Challenge", |
| 152 | + "Notebook", |
| 153 | + "Testing" |
| 154 | + ] |
| 155 | + table = Table( |
| 156 | + [InlineText(header) for header in headers], |
| 157 | + self.build_table() |
| 158 | + ) |
| 159 | + self.page.add_element(table) |
| 160 | + |
| 161 | + def build_table(self) -> list[list[InlineText]]: |
| 162 | + index = 1 |
| 163 | + body = [] |
| 164 | + for entry in self.feed: |
| 165 | + if "Code Snippets" not in entry.title: |
| 166 | + article = InlineText("Article", url=entry.link) |
| 167 | + youtube = get_youtube_video(entry) |
| 168 | + challenge = get_challenge(entry.title) |
| 169 | + notebook = get_notebook(entry.title) |
| 170 | + test = get_test(entry.title) |
| 171 | + body.append([ |
| 172 | + InlineText(str(index)), |
| 173 | + InlineText(entry.title), |
| 174 | + InlineText(entry.published), |
| 175 | + article, |
| 176 | + youtube, |
| 177 | + challenge, |
| 178 | + notebook, |
| 179 | + test |
| 180 | + ]) |
| 181 | + index += 1 |
| 182 | + return body |
| 183 | + |
| 184 | + |
| 185 | +if __name__ == '__main__': |
| 186 | + main() |
0 commit comments