def main(): """ The main function. Check given arguments get feed and run given command. """ argv = docopt.docopt(__doc__, version='informant v{}'.format(__version__)) InformantConfig().set_argv(argv) InformantConfig().debug_print = ui.debug_print InformantConfig().readlist = fs.read_datfile() config = InformantConfig().get_config() ui.debug_print('cli args: {}'.format(argv)) if 'feeds' in config: feed = [] for config_feed in config['feeds']: feed += Feed(config_feed).entries else: feed = Feed().entries if not feed: ui.warn_print('no news feed items, informant is performing no action') sys.exit() feed = sorted(feed, key=lambda k: k.timestamp, reverse=True) if argv.get(CHECK_CMD): check_cmd(feed) elif argv.get(LIST_CMD): list_cmd(feed) elif argv.get(READ_CMD): read_cmd(feed) sys.exit()
def format_list_item(entry, index): """ Returns a formatted string with the entry's index number, title, and right-aligned timestamp. Unread items are bolded""" bold = InformantConfig().colors['BOLD'] clear = InformantConfig().colors['CLEAR'] terminal_width = shutil.get_terminal_size().columns timestamp = str(entry.pretty_date) wrap_width = terminal_width - len(timestamp) - 1 heading = str(index) + ': ' + entry.title wrapped_heading = textwrap.wrap(heading, wrap_width) padding = terminal_width - len(wrapped_heading[0] + timestamp) if entry.has_been_read(): return ( wrapped_heading[0] + ' ' * (padding) + timestamp + '\n'.join(wrapped_heading[1:]) ) else: return ( bold + wrapped_heading[0] + clear + ' ' * (padding) + timestamp + bold + '\n'.join(wrapped_heading[1:]) + clear )
def fetch(self): feed = None if InformantConfig().get_argv_use_cache(): cachefile = InformantConfig().get_cachefile() os.umask( 0o0002 ) # unrestrict umask so we can cache with proper permissions try: session = CacheControl(requests.Session(), cache=FileCache(cachefile, filemode=0o0664, dirmode=0o0775)) feed = feedparser.parse(session.get(self.url).content) except Exception as e: ui.err_print('Unable to read cache information: {}'.format(e)) feed = feedparser.parse(self.url) else: feed = feedparser.parse(self.url) if feed.bozo: ui.err_print('Encountered feed error: {}'.format( feed.bozo_exception)) sys.exit(255) else: return feed
def main(): argv = docopt.docopt(__doc__, version='informant v{}'.format(__version__)) InformantConfig().set_argv(argv) InformantConfig().debug_print = ui.debug_print InformantConfig().readlist = fs.read_datfile() run() sys.exit()
def read_cmd(feed): """ Run the read command. Print news items and mark them as read. """ argv = InformantConfig().get_argv() if argv.get(READALL_OPT): for entry in feed: entry.mark_as_read() else: if argv[ITEM_ARG]: try: item = int(argv[ITEM_ARG]) entry = feed[item] except ValueError: for entry in feed: if entry.title == item: break #NOTE: this will read the oldest unread item if no matches are found ui.pretty_print_item(entry) entry.mark_as_read() else: unread_entries = list() for entry in feed: if not entry.has_been_read(): unread_entries.insert(0, entry) for entry in unread_entries: ui.pretty_print_item(entry) entry.mark_as_read() if entry is not unread_entries[-1]: read_next = ui.prompt_yes_no('Read next item?', 'yes') if read_next in ('n', 'no'): break else: print('No more unread items') fs.save_datfile()
def mark_as_read(self): """ Save this entry to mark it as read. """ readlist = InformantConfig().readlist if self.has_been_read(): return title = self.title date = self.timestamp readlist.append(str(date.timestamp()) + '|' + title)
def warn_print(*args, **kwargs): """ Same as builtin print but output to stderr with yellow color and "WARNING" preamble. """ yellow = InformantConfig().colors['YELLOW'] clear = InformantConfig().colors['CLEAR'] msg = yellow + 'WARN: ' + clear for arg in args: msg += arg print(msg, file=sys.stderr, **kwargs)
def err_print(*args, **kwargs): """ Same as builtin print but output to stderr with red color and "ERROR" preamble. """ red = InformantConfig().colors['RED'] clear = InformantConfig().colors['CLEAR'] msg = red + 'ERROR: ' + clear for arg in args: msg += arg print(msg, file=sys.stderr, **kwargs)
def pacman_msg(*args, **kwargs): """ Same as print but include yellow color and "informant" preamble so the message is clear in pacman. """ yellow = InformantConfig().colors['YELLOW'] clear = InformantConfig().colors['CLEAR'] msg = yellow + ':: informant: ' + clear for arg in args: msg += arg print(msg, **kwargs)
def has_been_read(self): """ Check if this entry has been read and return True or False. """ debug = InformantConfig().get_argv_debug() readlist = InformantConfig().readlist if debug: ui.debug_print(readlist) title = self.title date = self.timestamp if str(date.timestamp()) + '|' + title in readlist: return True return False
def list_cmd(feed): """ Run the list command. Print out a list of recent news item titles. """ argv = InformantConfig().get_argv() if argv.get(REV_OPT): feed_list = reversed(feed) else: feed_list = feed index = 0 for entry in feed_list: if not argv.get(UNREAD_OPT) \ or (argv.get(UNREAD_OPT) and not entry.has_been_read()): print(ui.format_list_item(entry, index)) index += 1
def fetch(self): feed = None if InformantConfig().get_argv_clear_cache(): ui.debug_print('Clearing cache') fs.clear_cachefile() if InformantConfig().get_argv_use_cache(): ui.debug_print('Checking cache in {}'.format( InformantConfig().get_cachefile())) cachefile = InformantConfig().get_cachefile() os.umask( 0o0002 ) # unrestrict umask so we can cache with proper permissions try: session = CacheControl(requests.Session(), cache=FileCache(cachefile, filemode=0o0664, dirmode=0o0775)) feed = feedparser.parse(session.get(self.url).content) except Exception as e: ui.err_print('Unable to read cache information: {}'.format(e)) ui.debug_print('Falling back to fetching feed') feed = feedparser.parse(self.url) else: feed = feedparser.parse(self.url) if feed.bozo: e = feed.bozo_exception if isinstance(e, URLError): # most likely this is an internet issue (no connection) ui.warn_print('News could not be fetched for {}'.format( self.name if self.name is not None else self.url)) ui.debug_print('URLError: {}'.format(e.reason)) else: # I think this is most likely to be a malformed feed ui.err_print('Encountered feed error: {}'.format( feed.bozo_exception)) ui.debug_print('bozo message: {}'.format( feed.bozo_exception.getMessage())) # In either of these error cases we probably shouldn't return error # so the pacman hook won't hold up an operation. # Here return an empty set of entries in case only one of multiple # feeds failed to fetch try: feed = feedparser.util.FeedParserDict() feed.update({'entries': []}) except Exception as e: ui.err_print('Unexpected error: {}'.format(e)) sys.exit() return feed
def save_datfile(): """ Save the readlist to the datfile """ debug = InformantConfig().get_argv_debug() readlist = InformantConfig().readlist if debug: return filename = InformantConfig().get_savefile() try: # then open as write to save updated list with open(filename, 'wb') as pickle_file: pickle.dump(readlist, pickle_file) pickle_file.close() except PermissionError: ui.err_print('Unable to save read information, please re-run with \ correct permissions to access "{}".'.format(filename)) sys.exit(255)
def has_been_read(self): """ Check if this entry has been read and return True or False. """ readlist = InformantConfig().readlist #ui.debug_print('readlist: {}'.format(readlist)) title = self.title date = self.timestamp if str(date.timestamp()) + '|' + title in readlist: return True return False
def running_from_pacman(): """ Return True if the parent process is pacman """ debug = InformantConfig().get_argv_debug() ppid = os.getppid() p_name = subprocess.check_output(['ps', '-p', str(ppid), '-o', 'comm=']) p_name = p_name.decode().rstrip() if debug: debug_print('informant: running from: {}'.format(p_name)) return p_name == 'pacman'
def save_datfile(): """ Save the readlist to the datfile """ debug = InformantConfig().get_argv_debug() readlist = InformantConfig().readlist if debug: ui.debug_print('running in debug mode, will not update readlist') return filename = InformantConfig().get_savefile() try: # then open as write to save updated list with open(filename, 'wb') as pickle_file: pickle.dump(readlist, pickle_file) pickle_file.close() except PermissionError: ui.err_print('Unable to save read information, please re-run with \ correct permissions to access "{}".'.format(filename)) sys.exit( 255 ) # this should never block pacman because the hook should run with root/sudo
def clear_cachefile(): """ Empty the cachefile directory """ cache_dir = InformantConfig().get_cachefile() pattern = os.path.join(cache_dir, '*') ui.debug_print('Removing based on pattern: {}'.format(pattern)) for filename in glob.glob(pattern): if os.path.isdir(filename): shutil.rmtree(filename) else: os.remove(filename)
def read_datfile(): """ Return the saved readlist from the datfile """ debug = InformantConfig().get_argv_debug() filename = InformantConfig().get_savefile() if debug: ui.debug_print('Getting datfile from "{}"'.format(filename)) try: with open(filename, 'rb') as pickle_file: try: readlist = pickle.load(pickle_file) pickle_file.close() if isinstance(readlist, tuple): # backwards compatibility with informant < 0.4.0 save data readlist = readlist[1] except (EOFError, ValueError): readlist = [] except (FileNotFoundError, PermissionError): readlist = [] return readlist
def pretty_print_item(entry): """ Print out the given entry, replacing some markup to make it look nicer. If the '--raw' option has been provided then the markup will not be replaced. """ argv = InformantConfig().get_argv() title = entry.title body = entry.body bold = InformantConfig().colors['BOLD'] clear = InformantConfig().colors['CLEAR'] timestamp = str(entry.pretty_date) if not argv.get(RAW_OPT): #if not using raw also bold title title = bold + title + clear h2t = html2text.HTML2Text() h2t.inline_links = False h2t.body_width = 85 body = h2t.handle(body) if entry.feed_name is not None: feed_name = '({})'.format(entry.feed_name) print('{}\n{}\n{}\n\n{}'.format(title, feed_name, timestamp, body)) else: print('{}\n{}\n\n{}'.format(title, timestamp, body))
def run(): """ The main function. Check given arguments get feed and run given command. """ argv = InformantConfig().get_argv() config = InformantConfig().get_config() if argv.get(DEBUG_OPT): ui.debug_print('cli args: {}'.format(argv)) if 'feeds' in config: feed = [] for config_feed in config['feeds']: feed += Feed(config_feed).entries else: feed = Feed().entries feed = sorted(feed, key=lambda k: k.timestamp, reverse=True) if argv.get(CHECK_CMD): check_cmd(feed) elif argv.get(LIST_CMD): list_cmd(feed) elif argv.get(READ_CMD): read_cmd(feed)
def debug_print(*args, **kwargs): """ Same as builtin print but output to stderr and only when the debug option is provided. """ if InformantConfig().get_argv_debug(): print(*args, file=sys.stderr, **kwargs)