#!/usr/bin/python3
"""fpaste - a cli frontend for the paste.fedoraproject.org pastebin."""

# Copyright 2008, 2010 Fedora Unity Project (http://fedoraunity.org)
# Author: Jason 'zcat' Farrell <farrellj@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import urllib.request
import urllib.parse
import urllib.error
import subprocess
from optparse import OptionParser, OptionGroup, SUPPRESS_HELP
from subprocess import CalledProcessError


VERSION = '0.5.0.0'
USER_AGENT = 'fpaste/' + VERSION
APIKEY = urllib.parse.urlencode({'apikey': '5uZ30dTZE1a5V0WYhNwcMddBRDpk6UzuzMu-APKM38iMHacxdA0n4vCqA34avNyt'})
SERVER_URL = 'https://paste.centos.org'
FPASTE_URL = 'https://paste.centos.org/api/create?' + APIKEY

# cmd name, command, command2 fallback, command3 fallback, ...
release_cmdlist = [
    ('OS Release',
     '''lsb_release -ds''',
     '''cat /etc/*-release | uniq''',
     'cat /etc/issue',
     'cat /etc/motd'
     ),
]

kernel_cmdlist = [
    ('Kernel', '''uname -r'''
     ),
    ('Kernel cmdline', '''cat /proc/cmdline'''
     ),
]

desktop_cmdlist = [
    ('Desktop(s) Running',
     '''ps -eo comm= | grep -E '(gnome-session|startkde|startactive|xfce.?-session|fluxbox|blackbox|hackedbox|ratpoison|enlightenment|icewm-session|od-session|wmaker|wmx|openbox-lxde|openbox-gnome-session|openbox-kde-session|mwm|e16|fvwm|xmonad|sugar-session|mate-session|lxqt-session|cinnamon|lxdm-session|awesome|phosh|sway|Hyperland)' '''
     ),
    ('Desktop(s) Installed',
     '''ls -m /usr/share/{xsessions,wayland-sessions}/ | sed 's/\\.desktop//g' '''
     ),
    ('Session Type',
     '''env | grep 'XDG_SESSION_TYPE' | sed 's/.*=//' '''
     ),
]

cpu_cmdlist = [
('CPU Model',
     '''grep 'model name' /proc/cpuinfo | awk -F: '{print $2}' | uniq -c |
     sed -re 's/^ +//' ''',
     '''grep 'model name' /proc/cpuinfo'''
     ),
    ('64-bit Support',
     '''grep -q ' lm ' /proc/cpuinfo && echo Yes || echo No'''
     ),
    ('Hardware Virtualization Support',
     '''grep -Eq '(vmx|svm)' /proc/cpuinfo && echo Yes || echo No'''
     ),
]

selinux_cmdlist = [
   ('SELinux Status',
     '''sestatus''',
     '''/usr/sbin/sestatus''',
     '''getenforce''',
     '''grep -v '^#' /etc/sysconfig/selinux'''
     ),
    ('SELinux Errors',
     '''selinuxenabled && journalctl --no-hostname --since yesterday |grep avc: | grep -Eo comm="[^ ]+" | sort |uniq -c |sort -rn'''
     ),
]

mem_cmdlist = [
    ('Memory usage', '''free -hm''', 'free'
     ),
    ('ZRAM usage', '''zramctl --output-all'''
     ),
]

perf_cmdlist = [
    ('Load average',       '''uptime'''
     ),
    ('Pressure Stall Information',
     '''grep -R . /proc/pressure/'''
     ),
    ('Top 5 CPU hogs', '''ps axuScnh | awk '$2!=''' + str(os.getpid()) + '''' | sort -rnk3 | head -5'''
     ),
    ('Top 5 Memory hogs', '''ps axuScnh | sort -rnk4 | head -5'''
     ),
]

storage_cmdlist = [
    ('block devices', '''lsblk -o NAME,FSTYPE,SIZE,FSUSE%,MOUNTPOINT,UUID,MIN-IO,SCHED,DISC-GRAN,MODEL'''
     ),
]

pci_cmdlist = [
    ('PCI devices', '''lspci -nn''', '''/sbin/lspci -nn''', '''lspci''', '''/sbin/lspci'''
     ),
]

pci_verbose_cmdlist = [
    ('PCI devices', '''lspci -vvnnk''', '''/sbin/lspci -vvnnkk'''
     ),
]

usb_cmdlist = [
    ('USB devices', '''lsusb''', '''/sbin/lsusb'''
     ),
]

usb_verbose_cmdlist = [
    ('USB devices', '''lsusb -vv -t''', '''/sbin/lsusb -vv -t'''
     ),
]

audio_cmdlist = [
    ('PCI Audio devices', '''lspci |  grep -i -E 'audio' | cut -b1-7 | xargs -i lspci -vnnks {} | grep -v "<access denied>"'''
     ),
    ('Audio devices', '''cat /proc/asound/cards'''
     ),
    ('User audio services', '''systemctl --user --no-pager status wireplumber pipewire* | sed "s/$(hostname)/ahost/"'''
     ),
]

gpu_cmdlist = [
    ('PCI Video Card', '''lspci |  grep -i -E 'vga' | cut -b1-7 | xargs -i lspci -vnnks {} | grep -v "<access denied>"'''
     ),
    ('GL Support', '''glxinfo -B | grep -E "OpenGL version|OpenGL renderer"'''
     ),
]

drm_cmdlist = [
    ('DRM Information',
     '''journalctl -k -b --no-hostname | grep -o 'kernel:.*drm.*$' | cut -d ' ' -f 2- '''
     ),
]

xorg_cmdlist = [
     ('Xorg modules',
     '''grep LoadModule /var/log/Xorg.0.log ~/.local/share/xorg/Xorg.0.log | cut -d \\" -f 2 | xargs'''
     ),
    ('Xorg errors',
     '''grep '^\\[.*(EE)' /var/log/Xorg.0.log ~/.local/share/xorg/Xorg.0.log | cut -d ':' -f 2- '''
     ),
]

dnf_cmdlist = [
    ('DNF Repositories',
     '''dnf repolist''',
     '''ls -l /etc/yum.repos.d''',
     '''grep -v '^#' /etc/yum.conf'''
     ),
    ('DNF Extras',         '''dnf -C list extras'''
     ),
    ('Last 20 packages installed', '''rpm -qa --nodigest --nosignature --last | head -20'''),
    # ('Installed packages', '''rpm -qa --nodigest --nosignature | sort''', '''dpkg -l''') ]
]

efiboot_cmdlist = [
   ('EFI boot manager output', '''efibootmgr -v'''
    ),
]

reboot_cmdlist = [
    ('Last few reboots',   '''last -x -n10 reboot runlevel'''
     ),
]

kernelbuffer_cmdlist = [
    ('Kernel buffer tail', '''journalctl --no-hostname -k --lines 50'''
     ),
]

net_cmdlist = [
    ('PCI Network devices', '''lspci |  grep -i -E 'net' | cut -b1-7 | xargs -i lspci -vnnks {} | grep -v "<access denied>"'''
     ),
    ('Network status', '''ip -br addr | awk '{print $1" " $2}' | column -t'''
     ),
]

btrfs_cmdlist = [
    ('btrfs usage', '''btrfs filesystem usage -T /'''
     ),
    ('btrfs-progs', '''rpm -q btrfs-progs'''
     ),
    ('btrfs mounts', '''grep btrfs /proc/mounts'''
     ),
    ('FSUUID', '''findmnt -n -o UUID $(stat -c '%m' "/")'''
     ),
    ('btrfs allocations', '''FSUUID=$(findmnt -n -o UUID $(stat -c '%m' "/")); grep -R . /sys/fs/btrfs/"$FSUUID"/allocation/ | sed "s/^.*allocation//"'''
     ),
    ('btrfs features', '''FSUUID=$(findmnt -n -o UUID $(stat -c '%m' "/")); grep -R . /sys/fs/btrfs/"$FSUUID"/features/ | sed "s/^.*features//"'''
     ),
    ('btrfs checksum', '''FSUUID=$(findmnt -n -o UUID $(stat -c '%m' "/")); cat /sys/fs/btrfs/"$FSUUID"/checksum'''
     ),
    ('Kernel messages', '''journalctl -k -o short-monotonic --no-hostname | grep "Linux version\\| ata\\|Btrfs\\|BTRFS\\|] hd\\| scsi\\| sd\\| sdhci\\| mmc\\| nvme\\| usb\\| vd"'''
     ),
   ]

def is_text(text, maxCheck=100, pctPrintable=0.75):
    """
    Check to see if a majority of the characters are printable.

    Returns true if maxCheck evenly distributed chars in text are >=
    pctPrintable% text chars.
    """
    # e.g.: /bin/* ranges between 19% and 42% printable
    from string import printable
    if type(text) == bytes:
        text = text.decode("utf-8", "replace")
    nchars = len(text)
    if nchars == 0:
        return False
    ncheck = min(nchars, maxCheck)
    inc = float(nchars) / ncheck
    i = 0.0
    nprintable = 0
    while i < nchars:
        if text[int(i)] in printable:
            nprintable += 1
        i += inc
    pct = float(nprintable) / ncheck
    return (pct >= pctPrintable)


def confirm(prompt="OK?"):
    """Prompt user for yes/no input and return True or False."""
    prompt += " [y/N]: "
    try:
        ans = input(prompt)
    except EOFError:    # already read sys.stdin and hit EOF
        # rebind sys.stdin to user tty (unix-only)
        try:
            mytty = os.ttyname(sys.stdout.fileno())
            sys.stdin = open(mytty)
            ans = input()
        except:  # noqa
            print(
                "could not rebind sys.stdin to %s after sys.stdin EOF" %
                mytty,
                file=sys.stderr)
            return False

    if ans.lower().startswith("y"):
        return True
    else:
        return False


def paste(text, options):
    """Send text to paste server and return the URL."""
    if not text:
        print("No text to send.", file=sys.stderr)
        return False

    # if sent data exceeds maxlength, server dies without error returned, so,
    # we'll truncate the input here, until the server decides to truncate
    # instead of die
    data = urllib.parse.urlencode(
        {'lang': options.lang,
         'text': text,
         'title': options.title,
         'name': options.author,
         'private': options.private,
         'expire': options.life
         })
    pasteSizeKiB = len(data) / 1024.0

    # 512KiB appears to be the current hard limit (20110404); old limit was
    # 16MiB
    if pasteSizeKiB >= 512:
        print(
            "WARNING: your paste size (%.1fKiB) is very large and may be rejected by the server. A pastebin is NOT a file hosting service!" %
            (pasteSizeKiB),
            file=sys.stderr)
    # verify that it's most likely *non-binary* data being sent.
    if not is_text(text):
        print(
            "WARNING: your paste looks a lot like binary data instead of text.",
            file=sys.stderr)
        if not confirm("Send binary data anyway?"):
            return False

    req = urllib.request.Request(
        url=FPASTE_URL,
        data=data.encode('ascii'),
        headers={
            'User-agent': USER_AGENT})
    if options.proxy:
        if options.debug:
            print("Using proxy: %s" % options.proxy, file=sys.stderr)
        req.set_proxy(options.proxy, 'http')

    print("Uploading (%.1fKiB)..." % pasteSizeKiB, file=sys.stderr)
    try:
        f = urllib.request.urlopen(req)
    except urllib.error.URLError as e:
        if hasattr(e, 'reason'):
            print("Error Uploading: %s" % e.reason, file=sys.stderr)
        elif hasattr(e, 'code'):
            print("Server Error: %d - %s" % (e.code, e.msg), file=sys.stderr)
            if options.debug:
                print(f.read())
        return False

    try:
        response = f.read().decode("utf-8", "replace")
    except ValueError as e:
        print(
            "Error: Server did not return a correct response: " + str(e),
            file=sys.stderr)
        return False

    return response


def btrfsinfo(show_stderr=False, show_successful_cmds=True,
              show_failed_cmds=True):
    """
    Get btrfs related info.
    """
    return getinfo(release_cmdlist + kernel_cmdlist + storage_cmdlist + btrfs_cmdlist)


def sysinfo(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get general system related info.
    """
    return getinfo(release_cmdlist + cpu_cmdlist + kernel_cmdlist + desktop_cmdlist + selinux_cmdlist + mem_cmdlist + perf_cmdlist + storage_cmdlist + pci_cmdlist + usb_cmdlist + gpu_cmdlist + drm_cmdlist + xorg_cmdlist + audio_cmdlist + net_cmdlist + kernelbuffer_cmdlist + reboot_cmdlist + dnf_cmdlist + efiboot_cmdlist)


def sysinfo_short(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get shortened system related info.
    """
    return getinfo(release_cmdlist + cpu_cmdlist + kernel_cmdlist + desktop_cmdlist + mem_cmdlist + storage_cmdlist + pci_cmdlist + usb_cmdlist + gpu_cmdlist + audio_cmdlist + net_cmdlist)

def pci_verbose_info(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get verbose listing of PCI devices.
    """
    return getinfo(release_cmdlist + kernel_cmdlist + pci_verbose_cmdlist)

def usb_verbose_info(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get verbose listing of USB devices.
    """
    return getinfo(release_cmdlist + kernel_cmdlist + usb_verbose_cmdlist)


def audioinfo(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get general audio info.
    """
    return getinfo(release_cmdlist + kernel_cmdlist + audio_cmdlist)


def videoinfo(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get general video info.
    """
    return getinfo(release_cmdlist + kernel_cmdlist + desktop_cmdlist + xorg_cmdlist + gpu_cmdlist)


def netinfo(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get general network device info.
    """
    return getinfo(release_cmdlist + kernel_cmdlist + net_cmdlist)


def diskinfo(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get general disk related info.
    """
    return getinfo(release_cmdlist + kernel_cmdlist + storage_cmdlist)


def dnfinfo(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get repository and package information.
    """
    return getinfo(release_cmdlist + dnf_cmdlist)


def getinfo(cmdlist, show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """Return commonly requested system info."""
    # 'ps' output below has been anonymized: -n for uid vs username, and -c for
    # short processname

    si = []

    print("Gathering system info", end=' ', file=sys.stderr)
    for cmds in cmdlist:
        cmdname = cmds[0]
        cmd = ""
        for cmd in cmds[1:]:
            sys.stderr.write('.')  # simple progress feedback
            p = subprocess.Popen(
                cmd,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE)
            try:
                (out, err) = p.communicate(timeout=300)
            except subprocess.TimeoutExpired:
                p.kill()
                (out, err) = p.communicate()
            if not p.returncode == 0:
                if show_stderr:
                    if err:
                        print(
                            "sysinfo Error: the cmd \"%s\" returned %d with stderr: %s" %
                            (cmd, p.returncode, err), file=sys.stderr)
                    else:
                        print(
                            "sysinfo Error: the cmd \"%s\" returned %d without errors" %
                            (cmd, p.returncode), file=sys.stderr)
                    print("Trying next fallback cmd...", file=sys.stderr)
            if p.returncode == 0 and out:
                break
        if out:
            if show_successful_cmds:
                si.append(('%s (%s)' % (cmdname, cmd), out))
            else:
                si.append(('%s' % cmdname, out))
        else:
            if show_failed_cmds:
                si.append(
                    ('%s (without results: "%s")' %
                     (cmdname,
                      '" AND "'.join(
                          cmds[
                              1:])),
                        out))
            else:
                si.append(('%s' % cmdname, out))
    print(" ", end='\n', file=sys.stderr)
    # return in readable indented format
    sistr = "=== fpaste %s System Information ===\n" % VERSION
    for cmdname, output in si:
        sistr += "* %s:\n" % cmdname
        if not output:
            sistr += "     N/A\n\n"
        else:
            for line in output.decode("utf-8", "replace").split('\n'):
                sistr += "     %s\n" % line

    return sistr


def summarize_text(text):
    # use beginning/middle/end content snippets as a description summary. 120 char limit
    # "36chars ... 36chars ... 36chars" == 118 chars
    # TODO: nuking whitespace in huge text files might be expensive; optimize
    # for b/m/e segments only
    sniplen = 36
    seplen = len(" ... ")
    tsum = ""
    text = " ".join(text.split())   # nuke whitespace
    tlen = len(text)

    if tlen < sniplen + seplen:
        tsum += text
    if tlen >= sniplen + seplen:
        tsum += text[0:sniplen] + " ..."
    if tlen >= (sniplen * 2) + seplen:
        tsum += " " + text[tlen / 2 - (sniplen / 2):(tlen / 2) + (sniplen / 2)] + " ..."
    if tlen >= (sniplen * 3) + (seplen * 2):
        tsum += " " + text[-sniplen:]
    # print >> sys.stderr, str(len(tsum)) + ": " + tsum

    return tsum


def main():
    """Main work function."""
    validSyntaxOpts = [
        "text",
        "html5",
        "css",
        "javascript",
        "php",
        "python",
        "ruby",
        "lua",
        "bash",
        "erlang",
        "go",
        "c",
        "cpp",
        "diff",
        "latex",
        "sql",
        "xml",
        "0",
        "4cs",
        "6502acme",
        "6502kickass",
        "6502tasm",
        "68000devpac",
        "abap",
        "actionscript",
        "actionscript3",
        "ada",
        "aimms",
        "algol68",
        "apache",
        "applescript",
        "apt_sources",
        "arm",
        "asm",
        "asymptote",
        "asp",
        "autoconf",
        "autohotkey",
        "autoit",
        "avisynth",
        "awk",
        "bascomavr",
        "basic4gl",
        "bbcode",
        "bf",
        "bibtex",
        "blitzbasic",
        "bnf",
        "boo",
        "c_loadrunner",
        "c_mac",
        "c_winapi",
        "caddcl",
        "cadlisp",
        "cfdg",
        "cfm",
        "chaiscript",
        "chapel",
        "cil",
        "clojure",
        "cmake",
        "cobol",
        "coffeescript",
        "cpp-winapi",
        "csharp",
        "cuesheet",
        "d",
        "dart",
        "dcs",
        "dcl",
        "dcpu16",
        "delphi",
        "div",
        "dos",
        "dot",
        "e",
        "ecmascript",
        "eiffel",
        "email",
        "epc",
        "euphoria",
        "ezt",
        "f1",
        "falcon",
        "fo",
        "fortran",
        "freebasic",
        "freeswitch",
        "fsharp",
        "gambas",
        "gdb",
        "genero",
        "genie",
        "gettext",
        "glsl",
        "gml",
        "gnuplot",
        "groovy",
        "gwbasic",
        "haskell",
        "haxe",
        "hicest",
        "hq9plus",
        "html4strict",
        "icon",
        "idl",
        "ini",
        "inno",
        "intercal",
        "io",
        "ispfpanel",
        "j",
        "java",
        "java5",
        "jcl",
        "jquery",
        "klonec",
        "klonecpp",
        "kotlin",
        "lb",
        "ldif",
        "lisp",
        "llvm",
        "locobasic",
        "logcat",
        "logtalk",
        "lolcode",
        "lotusformulas",
        "lotusscript",
        "lscript",
        "lsl2",
        "m68k",
        "magiksf",
        "make",
        "mapbasic",
        "matlab",
        "mirc",
        "mmix",
        "modula2",
        "modula3",
        "mpasm",
        "mxml",
        "mysql",
        "nagios",
        "netrexx",
        "newlisp",
        "nginx",
        "nimrod",
        "nsis",
        "oberon2",
        "objc",
        "objeck",
        "ocaml",
        "octave",
        "oobas",
        "oorexx",
        "oracle11",
        "oracle8",
        "oxygene",
        "oz",
        "parasail",
        "parigp",
        "pascal",
        "pcre",
        "per",
        "perl",
        "perl6",
        "pf",
        "pic16",
        "pike",
        "pixelbender",
        "pli",
        "plsql",
        "postgresql",
        "postscript",
        "povray",
        "powerbuilder",
        "powershell",
        "proftpd",
        "progress",
        "prolog",
        "properties",
        "providex",
        "purebasic",
        "pys60",
        "q",
        "qbasic",
        "qml",
        "racket",
        "rails",
        "rbs",
        "rebol",
        "reg",
        "rexx",
        "robots",
        "rpmspec",
        "rsplus",
        "rust",
        "sas",
        "scala",
        "scheme",
        "scilab",
        "scl",
        "sdlbasic",
        "smalltalk",
        "smarty",
        "spark",
        "sparql",
        "standardml",
        "stonescript",
        "systemverilog",
        "tcl",
        "teraterm",
        "thinbasic",
        "tsql",
        "typoscript",
        "unicon",
        "uscript",
        "upc",
        "urbi",
        "vala",
        "vb",
        "vbnet",
        "vbscript",
        "vedit",
        "verilog",
        "vhdl",
        "vim",
        "visualfoxpro",
        "visualprolog",
        "whitespace",
        "whois",
        "winbatch",
        "xbasic",
        "xorg_conf",
        "xpp",
        "yaml",
        "z80",
        "zxbasic"
    ]
    validClipboardSelectionOpts = ['primary', 'secondary', 'clipboard']
    ext2lang_map = {
        'sh': 'bash',
        'bash': 'bash',
        'bib': 'bibtex',
        'c': 'c',
        'h': 'c',
        'hpp': 'cpp',
        'cls': 'latex',
        'cpp': 'cpp',
        'css': 'css',
        'diff': 'diff',
        'html': 'html5',
        'htm': 'html5',
        'ini': 'ini',
        'java': 'java',
        'js': 'javascript',
        'jsp': 'html5',
        'lua': 'lua',
        'm': 'octave',
        'mat': 'matlab',
        'mbox': 'email',
        'pl': 'perl',
        'plt': 'gnuplot',
        'php': 'php',
        'php3': 'php',
        'py': 'python',
        'rb': 'ruby',
        'rhtml': 'html',
        'spec': 'rpmspec',
        'sql': 'sql',
        'sqlite': 'sql',
        'sty': 'latex',
        'tcl': 'tcl',
        'tex': 'latex',
        'xml': 'xml',
        'yaml': 'yaml'
    }

    usage = """\
Usage: %%prog [OPTION]... [FILE]...
  Send text file(s), stdin, or clipboard to the Fedora community pastebin at %s and return the URL.

  It is often useful to be able to easily paste text to the Fedora Pastebin at
  http://paste.fedoraproject.org and this simple utility will do that and
  return the resulting URL so that people may examine the output. This can
  hopefully help folks who are for some reason stuck without a graphical
  interface, working remotely, or any other reason they may be unable to paste
  something into the pastebin using a web browser.

  Examples:
  %%prog file1.txt file2.txt
  dmesg | %%prog
  (prog1; prog2; prog3) | fpaste
  %%prog --sysinfo --confirm
  %%prog -t "debug output" -l python foo.py""" % SERVER_URL

    parser = OptionParser(usage=usage, version='%prog ' + VERSION)
    parser.add_option(
        '',
        '--debug',
        dest='debug',
        help=SUPPRESS_HELP,
        action="store_true",
        default=False)
    parser.add_option('', '--proxy', dest='proxy', help=SUPPRESS_HELP)

    # pastebin-specific options first
    fpasteOrg_group = OptionGroup(parser, "paste.fedoraproject.org Options")
    fpasteOrg_group.add_option(
        '-t',
        '--title',
        dest='title',
        help='title of paste; defaults to UNTITLED',
        metavar='"TITLE"')
    fpasteOrg_group.add_option(
        '-a',
        '--author',
        dest='author',
        help='author name; empty by default',
        metavar='"AUTHOR"')
    fpasteOrg_group.add_option(
        '-r',
        '--private',
        dest='private',
        help='make paste private; defaults to 1',
        metavar='PRIVATE')
    fpasteOrg_group.add_option(
        '-l',
        dest='lang',
        help='language of content for syntax highlighting; default is "%default"; use "list" to show all ' + str(len(validSyntaxOpts)) + ' supported langs',
        metavar='"LANGUAGE"')
    fpasteOrg_group.add_option(
        '-x',
        dest='life',
        help='life of paste in minutes; default is 1 day (maximum)',
        metavar='LIFE')

    parser.add_option_group(fpasteOrg_group)
    # other options
    fpasteProg_group = OptionGroup(parser, "Input/Output Options")
    fpasteProg_group.add_option(
        '-i',
        '--clipin',
        dest='clipin',
        help='read paste text from current X clipboard selection [requires: xsel]',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '-w',
        '--wayland-clipin',
        dest='wclipin',
        help='read paste text from Wayland selection [requires the wl-clipboard package]',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '-o',
        '--clipout',
        dest='clipout',
        help='save returned paste URL to all available clipboards',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--input-selection',
        dest='selection',
        help='specify which X clipboard to use. valid options: "primary" (default; middle-mouse-button paste), "secondary" (uncommon), or "clipboard" (ctrl-v paste)',
        metavar='CLIP')
    fpasteProg_group.add_option(
        '',
        '--fullpath',
        dest='fullpath',
        help='use pathname VS basename for file description(s)',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--pasteself',
        dest='pasteself',
        help='paste this script itself',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--sysinfo',
        dest='sysinfo',
        help='paste system information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--sysinfo-short',
        dest='sysinfo_short',
        help='paste shortened system information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--audioinfo',
        '--sysinfo-audio',
        dest='audioinfo',
        help='paste general audio information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--videoinfo',
        '--sysinfo-video',
        dest='videoinfo',
        help='paste general video information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--diskinfo',
        '--sysinfo-disk',
        dest='diskinfo',
        help='paste general disk information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--netinfo',
        '--sysinfo-net',
        dest='netinfo',
        help='paste general network information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--dnfinfo',
        '--sysinfo-dnf',
        dest='dnfinfo',
        help='paste repository and package information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--pci-verbose',
        '--sysinfo-pci-verbose',
        dest='pci_verbose_info',
        help='paste verbose pci information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--usb-verbose',
        '--sysinfo-usb-verbose',
        dest='usb_verbose_info',
        help='paste verbose usb information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--btrfsinfo',
        '--sysinfo-btrfs',
        dest='btrfsinfo',
        help='paste btrfs related system information',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--printonly',
        dest='printonly',
        help='print paste, but do not send',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--confirm',
        dest='confirm',
        help='print paste, and prompt for confirmation before sending',
        action="store_true",
        default=False)
    fpasteProg_group.add_option(
        '',
        '--raw-url',
        dest='rawurl',
        help='print raw url',
        action="store_true",
        default=False)
    parser.add_option_group(fpasteProg_group)

# Let default be anonymous.
#    p = subprocess.Popen('whoami', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#    (out, err) = p.communicate ()
#    if p.returncode == 0 and out:
#        user = out[0:-1]
#    else:
#        print >> sys.stderr, "WARNING Could not run whoami. Posting anonymously."

    parser.set_defaults(
        lang='text',
        life='1440',
        selection='primary',
        title="UNTITLED",
        private="1",
        author=""
    )
    (options, args) = parser.parse_args()

    if options.lang.lower() == 'list':
        print('Valid language syntax options:')
        for opt in validSyntaxOpts:
            print(opt)
        sys.exit(0)
    if options.clipin:
        if not os.access('/usr/bin/xsel', os.X_OK):
            # TODO: try falling back to xclip or dbus
            parser.error(
                'OOPS - the clipboard options currently depend on "/usr/bin/xsel", which does not appear to be installed')
    if options.wclipin:
        if not os.access('/usr/bin/wl-paste', os.X_OK):
            parser.error(
                'OOPS - the Wayland clipboard option currently depends on "/usr/bin/wl-paste", which does not appear to be installed')
    if (options.clipin or options.wclipin) and args:
        parser.error(
            "Sending both clipboard contents AND files is not supported. Use -i OR -w OR filename(s)")
    for optk, optv, opts in [('language', options.lang, validSyntaxOpts), ('clipboard selection', options.selection, validClipboardSelectionOpts)]:
        if optv not in opts:
            parser.error(
                "'%s' is not a valid %s option.\n\tVALID OPTIONS: %s" %
                (optv, optk, ', '.join(opts)))

    fileargs = args
    if options.fullpath:
        fileargs = [os.path.abspath(x) for x in args]
    else:
        # remove potentially non-anonymous path info from file path
        # descriptions
        fileargs = [os.path.basename(x) for x in args]

    # guess lang for some common file extensions, if all file exts similar,
    # and lang not changed from default
    if options.lang == 'text':
        all_exts_similar = False
        ext_prev = ""
        for i in range(0, len(args)):
            all_exts_similar = True
            ext = os.path.splitext(args[i])[1].lstrip(os.extsep)
            if i > 0 and ext != ext_prev:
                all_exts_similar = False
                break
            ext_prev = ext
        if all_exts_similar and ext in list(ext2lang_map.keys()):
            options.lang = ext2lang_map[ext]

    # get input from mutually exclusive sources, though they *could* be
    # combined
    text = ""
    if options.clipin:
        xselcmd = 'xsel -o --%s' % options.selection
        # text = os.popen(xselcmd).read()
        p = subprocess.Popen(
            xselcmd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        (text, err) = p.communicate()
        text = text.decode("utf-8", "replace")
        if p.returncode != 0:
            if options.debug:
                print(err, file=sys.stderr)
            parser.error(
                "'xsel' failure. this usually means you're not running X")
        if not text:
            parser.error("%s clipboard is empty" % options.selection)
    if options.wclipin:
        cmd = 'wl-paste'
        # text = os.popen(xselcmd).read()
        p = subprocess.Popen(
            cmd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        (text, err) = p.communicate()
        text = text.decode("utf-8", "replace")
        if p.returncode != 0:
            if options.debug:
                print(err, file=sys.stderr)
            parser.error(
                "'wl-paste' failure. this usually means you're not running Wayland")
        if not text:
            parser.error("%s clipboard is empty" % options.selection)
    elif options.pasteself:
        text = open(sys.argv[0]).read()
        options.lang = 'python'
    elif options.sysinfo:
        text = sysinfo(options.debug)
    elif options.sysinfo_short:
        text = sysinfo_short(options.debug)
    elif options.pci_verbose_info:
        text = pci_verbose_info(options.debug)
    elif options.usb_verbose_info:
        text = usb_verbose_info(options.debug)
    elif options.audioinfo:
        text = audioinfo(options.debug)
    elif options.videoinfo:
        text = videoinfo(options.debug)
    elif options.diskinfo:
        text = diskinfo(options.debug)
    elif options.netinfo:
        text = netinfo(options.debug)
    elif options.dnfinfo:
        text = dnfinfo(options.debug)
    elif options.btrfsinfo:
        text = btrfsinfo(options.debug)
    elif not args:   # read from stdin if no file args supplied
        try:
            input_text = sys.stdin.buffer.read()
            text += input_text.decode("utf-8", "replace")
        except KeyboardInterrupt:
            print(
                "\nUSAGE REMINDER:\n   fpaste waits for input when run without file arguments.\n   Paste your text, then press <Ctrl-D> on a new line to upload.\n   Try `fpaste --help' for more information.\nExiting...",
                file=sys.stderr)
            sys.exit(1)
    else:
        for i, f in enumerate(args):
            if not os.access(f, os.R_OK):
                parser.error("file '%s' is not readable" % f)
            if (len(args) > 1):     # separate multiple files with header
                text += '#' * 78 + '\n'
                text += '### file %d of %d: %s\n' % (i + 1,
                                                     len(args),
                                                     fileargs[i])
                text += '#' * 78 + '\n'
            text += open(f).read()
    if options.debug:
        print('lang: "%s"' % options.lang)
        print('text (%d): "%s ..."' % (len(text), text[:80]))

    if options.printonly or options.confirm:
        try:
            if is_text(text):
                # when piped to less, sometimes fails with [Errno 32] Broken
                # pipe
                print(text)
            else:
                print("DATA")
        except IOError:
            pass
    if options.printonly:   # print only what would be sent, and exit
        sys.exit(0)
    elif options.confirm:   # print what would be sent, and ask for permission
        if not confirm("OK to send?"):
            sys.exit(1)

    url = paste(text, options)
    if url:
        # Try to save URL in clipboard, and warn but don't throw error
        if options.clipout:
            if not os.access('/usr/bin/xsel', os.X_OK):
                print(
                    'OOPS - the clipboard options currently depend on "/usr/bin/xsel", which does not appear to be installed',
                    file=sys.stderr)
            else:
                # Copy the url in all the valid clipboard options
                for selection in validClipboardSelectionOpts:
                    xselcmd = 'xsel -i --%s' % selection
                    p = subprocess.Popen(
                        xselcmd,
                        shell=True,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        stdin=subprocess.PIPE)
                    (out, err) = p.communicate(input=url.encode('utf-8'))
                    if p.returncode != 0:
                        if options.debug:
                            print(err, file=sys.stderr)
                        print(
                            "WARNING: URL not saved to %s" % selection,
                            file=sys.stderr)

            # Copy to Wayland clipboard
            if not os.access('/usr/bin/wl-copy', os.X_OK):
                print(
                    'OOPS - the clipboard options currently depend on "/usr/bin/wl-copy", which does not appear to be installed',
                    file=sys.stderr)
            else:
                wcmd = ['wl-copy', url]
                try:
                    subprocess.run(wcmd, check=True)
                except CalledProcessError as cpe:
                    if options.debug:
                        print("{} errored with return code {}".format(
                            cpe.cmd, cpe.returncode), file=sys.stderr)
                        print("\n" + cpe.stderr.decode(), file=sys.stderr)
                    print(
                        "WARNING: URL not saved to Wayland clipboard",
                        file=sys.stderr)

        if options.rawurl:
            rawurl = url.replace("view/", "view/raw/")
            print(rawurl)
        else:
            print(url)

    else:
        sys.exit(1)

    if options.pasteself:
        print(
            "install fpaste to local ~/bin dir by running:    mkdir -p ~/bin; curl " + url + "/raw -o ~/bin/fpaste && chmod +x ~/bin/fpaste",
            file=sys.stderr)

    sys.exit(0)


if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print("\ninterrupted.")
        sys.exit(1)
