From 52c48a11f02238471db620118ec2528afd4b870d Mon Sep 17 00:00:00 2001
From: Adar Nimrod <nimrod@shore.co.il>
Date: Sun, 3 Oct 2021 00:01:54 +0300
Subject: [PATCH] Planet reader.

This is something I wanted to do for a long time. Basically it's a nice
UI for me to browse some of the sites I frequent (sort of a news
reader), but geared towards a terminal. So this will just open a
terminal web browser (although a graphical browser can be used just as
well) with one of the configured sites so I don't have to type much.
There's tab completion for even less typing. Honestly, it's been on my
todo list from the days I used to carry with me my PocketCHIP and I
could have just deleted this task a long time ago. But I recently
learned about Python's webbrowser module and I wanted to try it out. I
also learned how to do tab completion in Bash so that's nice too.
---
 .bash_completion.d/planet  |  25 +++++++++
 .config/planet/config.yaml |  25 +++++++++
 Documents/bin/planet       | 106 +++++++++++++++++++++++++++++++++++++
 3 files changed, 156 insertions(+)
 create mode 100644 .bash_completion.d/planet
 create mode 100644 .config/planet/config.yaml
 create mode 100755 Documents/bin/planet

diff --git a/.bash_completion.d/planet b/.bash_completion.d/planet
new file mode 100644
index 0000000..756b4f4
--- /dev/null
+++ b/.bash_completion.d/planet
@@ -0,0 +1,25 @@
+# vim: ft=bash
+
+_planet () {
+    local cur prev words cword opts
+    _init_completion || return
+    opts='-h --help -l --list -C --config -b --browser'
+    browsers='mozilla firefox netscape galeon epiphany skipstone kfmclient
+    konqueror kfm mosaic opera grail links elinks lynx w3m windows-default
+    macosx safari google-chrome chrome chromium chromium-browser'
+
+    if [[ $prev == -C ]] || [[ $prev == --config ]]
+    then
+        _filedir '*.yaml|yml'
+    elif [[ $prev == -b ]] || [[ $prev == --browser ]]
+    then
+        COMPREPLY=($(compgen -W "$browsers" -- "$cur"))
+    elif [[ $cur == -* ]]
+    then
+        COMPREPLY=($(compgen -W "$opts" -- "$cur"))
+    else
+        COMPREPLY=($(compgen -W "$(planet -l)" -- "$cur"))
+    fi
+}
+
+complete -F _planet planet
diff --git a/.config/planet/config.yaml b/.config/planet/config.yaml
new file mode 100644
index 0000000..8866675
--- /dev/null
+++ b/.config/planet/config.yaml
@@ -0,0 +1,25 @@
+---
+general:
+  # For the list of available browsers, consult
+  # https://docs.python.org/3/library/webbrowser.html?highlight=webbrowser#webbrowser.register
+  browser: w3m
+sites:
+  shore: https://www.shore.co.il/blog/
+  #openbsd: https://openbsdnow.org/
+  openbsd: https://undeadly.org/
+  fedora: http://fedoraplanet.org/
+  debian: https://planet.debian.org/
+  gnome: http://planet.gnome.org/
+  kde: http://planetkde.org/
+  python: http://planetpython.org/
+  freedesktop: http://planet.freedesktop.org/
+  freebsd: http://planet.freedesktop.org/
+  kernel: http://planet.kernel.org/
+  foss-il: http://planet.hamakor.org.il/
+  sysadmin: http://planetsysadmin.com/
+  fsf: http://www.fsf.org/blogs/recent-blog-posts
+  hn: https://news.ycombinator.com/
+  phoronix: https://www.phoronix.com/scan.php?page=home
+  openbsd-current: http://www.openbsd.org/faq/current.html
+  debian-transitions: https://release.debian.org/transitions/index.html
+  lobsters: https://lobste.rs/
diff --git a/Documents/bin/planet b/Documents/bin/planet
new file mode 100755
index 0000000..f83e5f9
--- /dev/null
+++ b/Documents/bin/planet
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+"""Planets and aggregators reader."""
+
+import argparse
+import pathlib
+import sys
+import webbrowser
+import xdg.BaseDirectory  # pylint: disable=import-error
+import yaml
+
+
+EXAMPLE_CONFIG = """---
+general:
+  # For the list of available browsers, consult
+  # https://docs.python.org/3/library/webbrowser.html?highlight=webbrowser#webbrowser.register
+  browser: w3m
+sites:
+  shore: https://www.shore.co.il/blog/
+"""
+
+
+def build_arg_parser():
+    """Build and return the argument parser."""
+    parser = argparse.ArgumentParser(description=__doc__)
+
+    parser.add_argument(
+        "-C",
+        "--config",
+        type=pathlib.Path,
+        help="Use a different configuration file.",
+    )
+    parser.add_argument(
+        "-l", "--list", action="store_true", help="List available sites."
+    )
+    parser.add_argument("-b", "--browser", help="Use a specific browser.")
+    parser.add_argument("site", nargs="?", help="Name of the site to open.")
+    return parser
+
+
+def get_config(path=None):
+    """Returns a configuration dictionary.
+
+    If a path is passed, that file path is used. Otherwise, use the default
+    path. If path is None and the default doesn't exist, an example file is
+    created."""
+    if path:
+        path = path.expanduser()
+        if not path.exists() and not path.is_file():
+            arg_parser.error("Configuration file does not exist.")
+
+    else:
+        base_dir = pathlib.Path(xdg.BaseDirectory.save_config_path("planet"))
+        path = base_dir / "config.yaml"
+    if not path.exists():
+        with open(path, "w", encoding="utf-8") as configfile:
+            configfile.write(EXAMPLE_CONFIG)
+            arg_parser.error(
+                f"Missing config file, generated an example one at {path}."
+            )
+    try:
+        with open(path, "r", encoding="utf-8") as configfile:
+            config = yaml.safe_load(configfile)
+    except Exception as exception:  # pylint: disable=broad-except
+        arg_parser.error(str(exception))
+    if "sites" not in config or not isinstance(config["sites"], dict):
+        arg_parser.error(
+            "Config file missing 'sites' key or 'sites' is not a dictionary."
+        )
+    return config
+
+
+def list_sites(config):
+    """Prints a list of sites from the config."""
+    for site in config["sites"].keys():
+        print(site)
+
+
+def open_site(config, site, browser_name=None):
+    """Opens a site from the config in a webbrowser."""
+    if browser_name == "":
+        browser_name = None
+    elif browser_name is not None:
+        pass
+    elif "general" in config and "browser" in config["general"]:
+        browser_name = config["general"]["browser"]
+    else:
+        browser_name = None
+    browser = webbrowser.get(using=browser_name)
+    if site not in config["sites"]:
+        arg_parser.error(f"Unknown site {site}.")
+    browser.open(config["sites"][site])
+
+
+if __name__ == "__main__":
+    arg_parser = build_arg_parser()
+    args = arg_parser.parse_args()
+    if not args.list and not args.site:
+        arg_parser.error(
+            "You must specify either site name or -l to list the available sites."  # noqa: E501
+        )
+    conf = get_config(args.config)
+    if args.list:
+        list_sites(conf)
+    else:
+        open_site(conf, args.site, args.browser)
+    sys.exit()
-- 
GitLab