diff --git a/.gitignore b/.gitignore index 894a44c..ac3dec0 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ venv.bak/ # mypy .mypy_cache/ + +# IDE +.idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..335ea9d --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 The Python Packaging Authority + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..80c597e --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# PythonHomework +[Introduction to Python] Homework Repository + +# How to use +* pip install . +* rss-reader rss-reader.py "https://news.yahoo.com/rss/" --limit 2 --json +--to-pdf C:\Users\User_name\Desktop +* --date prints cached news that were parsed previously from the given URL +Creates folder cache and saves news in JSON files format +file name = date (like 20191125.json) +* For --to-pdf argument: specify the path to the folder +where 'news.pdf/cached_news.pdf' file will be saved. +The file will be overwritten after restarting the program. +Make sure to copy that file if you need it. Same thing with --to-html argument. +Also --to-html uses pictures from websites, so they wont be displayed without +internet connection +* Btw i use fonts for .pdf files to avoid encoding issues, +hope they will be installed correctly by 'pip install .' + + +# Parameters +* --help (show this help message and exit) +* --limit LIMIT (limit news topics if this parameter provided) +* --json (prints result as JSON in stdout) +* --verbose (outputs verbose status messages) +* --version (print version info) +* --date (It should take a date in YYYYmmdd format. For example: + --date 20191020The new from the specified day will be printed out. + If the news are not found error will be returned.) +* --to-pdf TO_PDF (It should take the path of the directory where new PDF file will be saved) +* --to-html TO_HTML (It should take the path of the directory where new HTML file will be saved) + +# JSON structure +feed = { + 'Title': 'feed title', + 'Published': 'date', + 'Summary': 'news description', + 'Link': 'original link to news', + 'Url': 'url of rss feed' + 'Image': 'original link to the image' +} + +# Progress +- [x] [Iteration 1] One-shot command-line RSS reader. +- [x] [Iteration 2] Distribution +- [x] [Iteration 3] News caching +- [x] [Iteration 4] Format converter +- [x] * [Iteration 5] Output colorization +- [ ] * [Iteration 6] Web-server \ No newline at end of file diff --git a/app/RSSReader.py b/app/RSSReader.py new file mode 100644 index 0000000..882a290 --- /dev/null +++ b/app/RSSReader.py @@ -0,0 +1,181 @@ +""" + Contains class RSSReader which receives arguments from cmd + and allows to parse URL with RSS feed and print it in stdout + in different formats +""" + +import os +import json + +import feedparser +from bs4 import BeautifulSoup +import dateutil.parser as dateparser +from colorama import init +from colorama import Fore +import requests + +from app.rss_exception import RSSException + + +class RSSReader: + """ Reads news from RSS url and prints them """ + + def __init__(self, url, limit, date, logger, colorize=None): + self.url = url + self.limit = limit + self.date = date + self.logger = logger + self.colorize = colorize + init() # colorama + + def get_feed(self): + """ Returns parsed feed and caches it""" + response = requests.get(self.url).text + news_feed = feedparser.parse(response) + for entry in news_feed.entries[:self.limit]: + self.cache_news_json(entry) + self.logger.info('News has been cached') + if not news_feed.entries: + raise RSSException('Did not parse any news') + return news_feed.entries[:self.limit] + + def print_feed(self, entries): + """ Prints feed in stdout """ + + self.logger.info('Printing feed') + + if self.colorize: + for entry in entries: + print(f'{Fore.GREEN}========================================================{Fore.RESET}') + print(f'{Fore.GREEN}Title:{Fore.RESET} {entry.title}') + print(f'{Fore.GREEN}Published:{Fore.RESET} {entry.published}') + print(f'{Fore.GREEN}Summary:{Fore.RESET} {BeautifulSoup(entry.summary, "html.parser").text}') + print(f'{Fore.GREEN}Image:{Fore.RESET} {self.get_img_url(entry.summary)}') + print(f'{Fore.GREEN}Link:{Fore.RESET} {entry.link}') + print(f'{Fore.GREEN}========================================================{Fore.RESET}') + else: + for entry in entries: + print('========================================================') + print(f'Title: {entry.title}') + print(f'Published: {entry.published}', end='\n\n') + print(f'Summary: {BeautifulSoup(entry.summary, "html.parser").text}', end='\n\n') + print(f'Image: {self.get_img_url(entry.summary)}') + print(f'Link: {entry.link}') + print('========================================================') + + def get_img_url(self, summary): + """ Parses image url from in rss feed """ + soup = BeautifulSoup(summary, 'html.parser') + img = soup.find('img') + if img: + img_url = img['src'] + return img_url + else: + return None + + def print_feed_json(self, entries): + """ Prints feed in stdout in JSON format """ + + self.logger.info('Printing feed in JSON format') + + for entry in entries: + feed = self.to_dict(entry) + if self.colorize: + print(Fore.GREEN + json.dumps(feed, indent=2, ensure_ascii=False) + Fore.RESET, end=',\n') + else: + print(json.dumps(feed, indent=2, ensure_ascii=False), end=',\n') + + def to_dict(self, entry): + """ Converts entry to dict() format """ + + feed = dict() + feed['Title'] = entry.title + feed['Published'] = entry.published + feed['Summary'] = BeautifulSoup(entry.summary, "html.parser").text + feed['Link'] = entry.link + feed['Url'] = self.url + feed['Image'] = self.get_img_url(entry.summary) + return feed + + def cache_news_json(self, entry): + """ Saves all printed news in JSON format (path = 'cache/{publication_date}.json')""" + + date = dateparser.parse(entry.published, fuzzy=True).strftime('%Y%m%d') + directory_path = 'cache' + os.path.sep + if not os.path.exists(directory_path): + self.logger.info('Creating directory cache') + os.mkdir(directory_path) + + file_path = directory_path + date + '.json' + + feed = self.to_dict(entry) + news = list() + try: + with open(file_path, encoding='utf-8') as rf: + news = json.load(rf) + if feed in news: + # already cached + return + except FileNotFoundError: + self.logger.info('Creating new .json file') + except json.JSONDecodeError: + self.logger.info('Empty JSON file') + + with open(file_path, 'w', encoding='utf-8') as wf: + news.append(feed) + json.dump(news, wf, indent=2) + + def get_cached_json_news(self): + """ Returns the list of cached news with date from arguments """ + + file_path = 'cache' + os.path.sep + self.date + '.json' + cached_news = list() + try: + with open(file_path) as rf: + news = json.load(rf) + for new in news: + if new['Url'] == self.url: + cached_news.append(new) + if not cached_news: + # News with such url have not been found + raise FileNotFoundError + return cached_news[:self.limit] + except FileNotFoundError: + if self.colorize: + print(f'{Fore.RED}There are no cached news with such date by this url{Fore.RESET}') + else: + print('There are no cached news with such date by this url') + except json.JSONDecodeError: + # Empty json file + # Or no news by needed url + if self.colorize: + print(f'{Fore.RED}There are no cached news with such date by this url{Fore.RESET}') + else: + print('There are no cached news with such date by this url') + return False + + def print_cached_feed(self, cached_feed): + """ Prints saved news in stdout """ + + self.logger.info('Printing cached feed') + for new in cached_feed: + if self.colorize: + print(f'{Fore.GREEN}---------------------------------------------------------{Fore.RESET}') + for key, value in new.items(): + print(f'{Fore.GREEN}{key}:{Fore.RESET} {value}') + print(f'{Fore.GREEN}---------------------------------------------------------{Fore.RESET}') + else: + print('---------------------------------------------------------') + for key, value in new.items(): + print(f'{key}: {value}') + print('---------------------------------------------------------') + + def print_cached_feed_json(self, cached_feed): + """ Prints saved news in stdout in JSON format """ + + self.logger.info('Printing cached feed in JSON format') + for new in cached_feed: + if self.colorize: + print(Fore.GREEN + json.dumps(new, indent=2) + Fore.RESET, end=',\n') + else: + print(json.dumps(new, indent=2), end=',\n') diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__main__.py b/app/__main__.py new file mode 100644 index 0000000..f3834cd --- /dev/null +++ b/app/__main__.py @@ -0,0 +1,6 @@ +""" Package entry point """ + +from app.rss_reader import main + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/app/argparser.py b/app/argparser.py new file mode 100644 index 0000000..a96eefa --- /dev/null +++ b/app/argparser.py @@ -0,0 +1,77 @@ +""" + Contains ArgParser class which allows parse arguments from cmd +""" + +import argparse + + +__version__ = '0.5.0' + + +class ArgParser: + """ Reads arguments """ + + def __init__(self): + self.args = self.parse_args() + + def parse_args(self): + """ Reads arguments from the cmd and returns them """ + + argparser = argparse.ArgumentParser(description='One-shot command-line RSS reader', prog='rss-reader') + argparser.add_argument( + 'url', + type=str, + help='Input RSS url containing news' + ) + argparser.add_argument( + '--limit', + type=int, + default=None, + help='Sets a limit for news output (default - no limit)' + ) + argparser.add_argument( + '--json', + action='store_true', + help='Prints feed in JSON format in stdout' + ) + argparser.add_argument( + '--version', + action='version', + version=f'%(prog)s version {__version__}', + default=None, + help='Prints version of program' + ) + argparser.add_argument( + '--verbose', + action='store_true', + help='Prints all logs in stdout' + ) + argparser.add_argument( + '--date', + type=str, + help='It should take a date in YYYYmmdd format. For example: --date 20191020' + 'The new from the specified day will be printed out. If the news are not found error will be returned.' + ) + argparser.add_argument( + '--to-pdf', + dest='to_pdf', + type=str, + help='It should take the path of the directory where new PDF file will be saved' + ) + argparser.add_argument( + '--to-html', + dest='to_html', + type=str, + help='It should take the path of the directory where new HTML file will be saved' + ) + argparser.add_argument( + '--colorize', + action='store_true', + help='Prints the result of the utility in colorized mode' + ) + args = argparser.parse_args() + return args + + def get_args(self): + """ Returns arguments """ + return self.args diff --git a/app/fonts/NotoSans-Black.cw127.pkl b/app/fonts/NotoSans-Black.cw127.pkl new file mode 100644 index 0000000..d4c9c38 Binary files /dev/null and b/app/fonts/NotoSans-Black.cw127.pkl differ diff --git a/app/fonts/NotoSans-Black.pkl b/app/fonts/NotoSans-Black.pkl new file mode 100644 index 0000000..51359bd Binary files /dev/null and b/app/fonts/NotoSans-Black.pkl differ diff --git a/app/fonts/NotoSans-Black.ttf b/app/fonts/NotoSans-Black.ttf new file mode 100644 index 0000000..2806e6d Binary files /dev/null and b/app/fonts/NotoSans-Black.ttf differ diff --git a/app/fonts/NotoSans-Thin.cw127.pkl b/app/fonts/NotoSans-Thin.cw127.pkl new file mode 100644 index 0000000..1fd3234 Binary files /dev/null and b/app/fonts/NotoSans-Thin.cw127.pkl differ diff --git a/app/fonts/NotoSans-Thin.pkl b/app/fonts/NotoSans-Thin.pkl new file mode 100644 index 0000000..9dbc50c Binary files /dev/null and b/app/fonts/NotoSans-Thin.pkl differ diff --git a/app/fonts/NotoSans-Thin.ttf b/app/fonts/NotoSans-Thin.ttf new file mode 100644 index 0000000..f14dd6a Binary files /dev/null and b/app/fonts/NotoSans-Thin.ttf differ diff --git a/app/html_converter.py b/app/html_converter.py new file mode 100644 index 0000000..bedf9bd --- /dev/null +++ b/app/html_converter.py @@ -0,0 +1,119 @@ +""" + Contains class HTMLConverter which receives path + where it will save news in HTML format +""" + + +import os + +from app.RSSReader import RSSReader + + +class HTMLConverter: + """ Writes news in HTML file """ + + def __init__(self, url, limit, date, to_html, logger): + self.url = url + self.limit = limit + self.date = date + self.to_html = to_html + self.logger = logger + self.rss_reader = RSSReader(self.url, self.limit, self.date, self.logger) + + def create_html_code(self): + """ Creates HTML code which contains news information """ + + news = self.rss_reader.get_feed() + + html_head = self.get_html_head() + html_foot = self.get_html_foot() + html_entries = self.get_html_entries(news) + + html_code = html_head + html_entries + html_foot + self.logger.info('HTML code has been created') + return html_code + + def create_html_code_from_cache(self): + """ Creates HTML code which contains cached news information """ + cached_feed = self.rss_reader.get_cached_json_news() + + html_head = self.get_html_head() + html_foot = self.get_html_foot() + html_entries = self.get_html_entries_from_cache(cached_feed) + + html_code = html_head + html_entries + html_foot + return html_code + + def get_html_head(self): + """ Returns HTML header code """ + + html_head = ''' + + +

+ ''' + return html_head + + def get_html_entries(self, news): + """ Returns HTML code with news info """ + + html_entry = '' + html_entries = '' + for new in news: + entry = self.rss_reader.to_dict(new) + html_entry = f''' + Title: {entry['Title']}
+ Published: {entry['Published']}
+ Summary: {entry['Summary']}
+ Link: {entry['Link']}
+ Url: {entry['Url']}
+

+ ''' + html_entries += html_entry + return html_entries + + def get_html_entries_from_cache(self, entries): + """ Returns HTML code with cached news info """ + + html_entry = '' + html_entries = '' + for entry in entries: + html_entry = f''' + Title: {entry['Title']}
+ Published: {entry['Published']}
+ Summary: {entry['Summary']}
+ Link: {entry['Link']}
+ Url: {entry['Url']}
+

+ ''' + html_entries += html_entry + return html_entries + + def get_html_foot(self): + """ Returns HTML footer code """ + + html_foot = ''' +

+ + + ''' + return html_foot + + def write_to_html(self): + """ Writes HTML code to news.html file """ + + if self.date: + file_name = 'cached_news.html' + html_code = self.create_html_code_from_cache() + else: + file_name = 'news.html' + html_code = self.create_html_code() + + try: + file_path = self.to_html + os.path.sep + file_name + with open(file_path, 'w', encoding='utf-8') as wf: + wf.write(html_code) + except FileNotFoundError: + self.logger.info(f'Path to file {file_path} not found') + else: + self.logger.info('News has been written to HTML file') diff --git a/app/pdf/news.pdf b/app/pdf/news.pdf new file mode 100644 index 0000000..7270336 Binary files /dev/null and b/app/pdf/news.pdf differ diff --git a/app/pdf_converter.py b/app/pdf_converter.py new file mode 100644 index 0000000..b116f0c --- /dev/null +++ b/app/pdf_converter.py @@ -0,0 +1,156 @@ +""" + Contains class PDFConverter which receives path + where it will save news in PDF format +""" + +import os +import json +import warnings +from datetime import datetime + +from bs4 import BeautifulSoup +import urllib.error +import urllib.request +import fpdf + +from app.RSSReader import RSSReader + + +# To protect your fragile mind from BeautifulSoup warnings +warnings.filterwarnings("ignore") + + +class PDFConverter: + """ Writes news in PDF file """ + + def __init__(self, url, limit, date, to_pdf, logger, news=None): + """ Sets up fonts for PDF file """ + + self.url = url + self.limit = limit + self.date = date + self.to_pdf = to_pdf + self.news = news + self.logger = logger + + fpdf.set_global('SYSTEM_TTFONTS', os.path.join(os.path.dirname(__file__), 'fonts')) + self.pdf = fpdf.FPDF() + self.pdf.add_font('NotoSans-Black', '', 'NotoSans-Black.ttf', uni=True) + self.pdf.add_font('NotoSans-Thin', '', 'NotoSans-Thin.ttf', uni=True) + + def write_json_to_pdf(self): + """ Writes cached JSON news into PDF file """ + + self.write_title('Cached RSS news') + + file_path = 'cache' + os.path.sep + self.date + '.json' + with open(file_path, encoding='utf-8') as rf: + news = json.load(rf) + + for new in news: + self.create_cells(new) + self.pdf.ln(10) + self.pdf.add_page() + try: + file_path = self.to_pdf + os.path.sep + 'cached_news.pdf' + self.pdf.output(file_path) + except FileNotFoundError: + self.logger.info(f'Path to file {file_path} not found') + else: + self.logger.info('Cached news has been written to PDF file') + + def write_to_pdf(self): + """ Writes news into PDF file """ + + if not self.news: + return + + self.write_title('RSS news') + + rss_reader = RSSReader(self.url, self.limit, self.date, self.logger) + for new in self.news: + new = rss_reader.to_dict(new) + self.create_cells(new) + self.pdf.ln(10) + self.pdf.add_page() + try: + file_path = self.to_pdf + os.path.sep + 'news.pdf' + self.pdf.output(file_path) + except FileNotFoundError: + self.logger.info(f'Path to file {file_path} not found') + else: + self.logger.info('News has been written to PDF file') + + def write_title(self, title): + """ Writes title of PDF file """ + + self.pdf.set_font('NotoSans-Black', size=16) + self.pdf.add_page() + self.pdf.set_title('News') + self.pdf.cell(200, 10, txt=title, ln=1, align='C') + self.pdf.cell(0, 10, ln=1) + + def create_cells(self, new): + """ Creates cells in PDF file with news content """ + + img_path = None + for key, value in new.items(): + self.pdf.set_font('NotoSans-Black', size=12) + self.pdf.cell(25, 5, txt=key + ': ', ln=0, align='L') + self.pdf.set_font('NotoSans-Thin', size=12) + if key == 'Image': + self.pdf.multi_cell(0, 5, txt=BeautifulSoup(value, 'html.parser').text) + img_path = self.download_image(value) + else: + self.pdf.multi_cell(0, 5, txt=value) + if img_path: + self.write_image(img_path) + + def download_image(self, img_url): + """ Downloads image form given url and returns path """ + + img = None + directory_path = 'images' + os.path.sep + if not os.path.exists(directory_path): + self.logger.info('Creating directory images') + os.mkdir(directory_path) + + img_name = self.create_image_name() + try: + img = urllib.request.urlopen(img_url).read() + except ValueError: + self.logger.info('Failed image download') + return None + except urllib.error.URLError: + self.logger.info('The attempt to establish a connection was unsuccessful because ' + 'Due to incorrect response of already connected computer') + + try: + img_path = os.path.abspath('') + os.path.sep + directory_path + img_name + with open(img_path, "wb") as out: + out.write(img) + except FileNotFoundError: + self.logger.info('Could not write image to folder images') + except TypeError: + self.logger.info('NoneType image') + else: + self.logger.info('Image has been downloaded') + return img_path + + def write_image(self, img_path): + """ Writes image to pdf file """ + + try: + self.pdf.image(img_path) + except SyntaxError: + return None + except RuntimeError: + return None + self.pdf.multi_cell(0, 10, txt=f'{img_path}') + + def create_image_name(self): + """ Creates name for image using current time (YearMonthDay_HoursMinutesSeconds_Milliseconds.jpg) """ + + img_name = datetime.today().strftime('%Y%m%d_%H%M%S_%f') + img_name += '.jpg' + return img_name diff --git a/app/rss_exception.py b/app/rss_exception.py new file mode 100644 index 0000000..43e7af4 --- /dev/null +++ b/app/rss_exception.py @@ -0,0 +1,9 @@ +""" + Contains RSSException class for exceptions +""" + + +class RSSException(Exception): + """ For rss exceptions """ + def __init__(self, message=''): + self.message = message diff --git a/app/rss_reader.py b/app/rss_reader.py new file mode 100644 index 0000000..5c4c101 --- /dev/null +++ b/app/rss_reader.py @@ -0,0 +1,95 @@ +""" + Controls the launch of the rss-reader program +""" + +import logging + +import dateutil.parser as dateparser + +from app.argparser import ArgParser +from app.RSSReader import RSSReader +from app.pdf_converter import PDFConverter +from app.html_converter import HTMLConverter +from app.rss_exception import RSSException + + +def main(): + """ Reads arguments and displays news """ + + arguments = ArgParser() + args = arguments.get_args() + + if args.verbose: + logger = get_logger() + else: + logger = logging.getLogger() + + logger.info('Start') + + if args.version: + print(args.version) + logger.info('Exit') + + rss_reader = RSSReader(args.url, args.limit, args.date, logger, args.colorize) + + if args.date: + try: + dateparser.parse(args.date, fuzzy=True).strftime('%Y%m%d') + except dateparser._parser.ParserError: + print('Invalid date format') + logger.info('Exit') + return + cached_feed = rss_reader.get_cached_json_news() + if cached_feed: + print('Cached news', args.date) + if args.json: + rss_reader.print_cached_feed_json(cached_feed) + else: + rss_reader.print_cached_feed(cached_feed) + if args.to_pdf: + pdf_converter = PDFConverter(args.url, args.limit, args.date, args.to_pdf, logger) + pdf_converter.write_json_to_pdf() + if args.to_html: + html_converter = HTMLConverter(args.url, args.limit, args.date, args.to_html, logger) + html_converter.write_to_html() + logger.info('Exit') + return + + try: + feed = rss_reader.get_feed() + except RSSException as rss_exc: + print(rss_exc) + return + + if args.to_pdf: + pdf_converter = PDFConverter(args.url, args.limit, args.date, args.to_pdf, logger, news=feed) + pdf_converter.write_to_pdf() + + if args.to_html: + html_converter = HTMLConverter(args.url, args.limit, args.date, args.to_html, logger) + html_converter.write_to_html() + + if args.json: + rss_reader.print_feed_json(feed) + else: + rss_reader.print_feed(feed) + + logger.info('Exit') + + +def get_logger(): + """ Returns logger with DEBUG level for creating logs in stdout """ + + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + + formatter = logging.Formatter('%(levelname)s: %(message)s') + + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + return logger + + +if __name__ == '__main__': + main() diff --git a/app/test_RSSReader.py b/app/test_RSSReader.py new file mode 100644 index 0000000..d325d4d --- /dev/null +++ b/app/test_RSSReader.py @@ -0,0 +1,48 @@ +""" + Tests for RSSReader class methods +""" + +import unittest + +from app.rss_reader import get_logger +from app.RSSReader import RSSReader +from app.rss_exception import RSSException + + +class TestRSSReader(unittest.TestCase): + """ Some sort of genius tests """ + + def setUp(self): + url = 'https://www.google.com/' + limit = 5 + date = '20191130' + logger = get_logger() + + self.rss_reader = RSSReader(url, limit, date, logger) + + def test_get_feed(self): + with self.assertRaises(RSSException): + self.rss_reader.get_feed() + + def test_get_img_url(self): + summary = '

Ilhan Oma' \
+                  'r GOP challenger banned from Twitter after saying she should be "tried for treason and ' \
+                  'hanged”Danielle Stella campaign account ' \ + 'also tweeted a picture of a stick figure being hanged with a link to a blog post about her com' \ + 'ments.


' + + url = 'http://l1.yimg.com/uu/api/res/1.2/Q65f_fAoZ1bUNUPEZ9TzMQ--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/http:' \ + '//d.yimg.com/hd/cp-video-transcode/1009217/645bffba-5098-49ad-b36f-8213083345d7/ea5c7e80-890b-5ad5-' \ + '9f04-e4005fb40434/data_3_0.jpg?s=343dee8421a4018296c99715b74b3886&c=c9bed65751ca54a241ff888a51cc4be0&a' \ + '=tripleplay4us&mr=0' + + self.assertEqual(self.rss_reader.get_img_url(summary), url) + + +if __name__ == '__main__': + unittest.main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..954f024 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +feedparser # rss parsing +requests # http requests +bs4 # for xml and html +python-dateutil # for parsing date from news +fpdf # conversion of news in PDF file \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..cffff7d --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +import os + +from setuptools import setup, find_packages + + +here = os.path.abspath(os.path.dirname(__file__)) + +# Get the long description from the README file +with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + + +setup( + name='rss-reader', + version='0.5.0', + description='A simple Python3.8 rss reader', + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/introduction-to-python-bsuir-2019/PythonHomework', + author='Dydyshko Andrey', + author_email='dydyshko1999@gmail.com', + keywords='simple rss reader', + packages=find_packages(), + package_data={ + 'app': [ + 'fonts/NotoSans-Black.ttf', + 'fonts/NotoSans-Thin.ttf' + ] + }, + include_package_data=True, + python_requires='>=3.8', + install_requires=['feedparser>=6.0.0b1', 'requests', 'bs4', 'python-dateutil', 'fpdf', 'setuptools-git', + 'colorama'], + entry_points={ + 'console_scripts': [ + 'rss-reader=app.__main__:main', + ], + }, + project_urls={ + 'Bug Reports': 'https://github.com/introduction-to-python-bsuir-2019/PythonHomework/issues', + 'Source': 'https://github.com/introduction-to-python-bsuir-2019/PythonHomework', + }, +) \ No newline at end of file