#!/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.4.4.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, ...
sysinfo_cmdlist = [
    ('OS Release',
     '''lsb_release -ds''',
     '''cat /etc/*-release | uniq''',
     'cat /etc/issue',
     'cat /etc/motd'
     ),
    ('Kernel',             '''uname -r ; cat /proc/cmdline'''
     ),
    ('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)' '''
     ),
    ('Desktop(s) Installed',
     '''ls -m /usr/share/xsessions/ | sed 's/\\.desktop//g' '''
     ),
    ('SELinux Status',
     '''sestatus''',
     '''/usr/sbin/sestatus''',
     '''getenforce''',
     '''grep -v '^#' /etc/sysconfig/selinux'''
     ),
    ('SELinux Errors',
     '''selinuxenabled && journalctl --since yesterday |grep avc: | grep -Eo comm="[^ ]+" | sort |uniq -c |sort -rn'''
     ),
    ('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'''
     ),
    ('Load average',       '''uptime'''),
    ('Pressure Stall Information',
     '''grep -R . /proc/pressure/'''),
    ('Memory usage',       '''free -m''', 'free'),
    ('ZRAM usage',       '''zramctl --output-all'''),
    ('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'''),
    ('Disk space usage',   '''df -hT''', 'df -h', 'df'),
    ('Block devices',      '''blkid''', '''/sbin/blkid'''),
    ('PCI devices',        '''lspci -nn''', '''/sbin/lspci -nn''', '''lspci''', '''/sbin/lspci'''),
    ('USB devices',        '''lsusb''', '''/sbin/lsusb'''),
    ('DRM Information',
     '''journalctl -k -b | grep -o 'kernel:.*drm.*$' | cut -d ' ' -f 2- '''
     ),
    ('Xorg modules',
     '''grep LoadModule /var/log/Xorg.0.log ~/.local/share/xorg/Xorg.0.log | cut -d \\" -f 2 | xargs'''
     ),
    ('GL Support',
     '''glxinfo | grep -E "OpenGL version|OpenGL renderer"'''
     ),
    ('Xorg errors',
     '''grep '^\\[.*(EE)' /var/log/Xorg.0.log ~/.local/share/xorg/Xorg.0.log | cut -d ':' -f 2- '''
     ),
    ('Kernel buffer tail', '''dmesg | tail'''),
    ('Last few reboots',   '''last -x -n10 reboot runlevel'''),
    ('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''') ]
    ('EFI boot manager output', '''efibootmgr -v''')]

btrfs_cmdlist = [
    ('Kernel', '''uname -r ; cat /proc/cmdline'''
     ),
    ('btrfs-progs', '''rpm -q btrfs-progs'''
     ),
    ('btrfs mounts', '''grep btrfs /proc/mounts'''
     ),
    ('block devices', '''lsblk -o NAME,FSTYPE,SIZE,FSUSE%,MOUNTPOINT,UUID,MIN-IO,SCHED,DISC-GRAN,MODEL'''
     ),
    ('Kernel messages', '''journalctl -k -o short-monotonic --no-hostname | grep "Linux version\\| ata\\|Btrfs\\|BTRFS\\|] hd\\| scsi\\| sd\\| sdhci\\| mmc\\| nvme\\| usb\\| vd"'''
     ),
    ('btrfs usage',   '''btrfs filesystem usage -T /'''),
    ('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'''),
]


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(btrfs_cmdlist)


def sysinfo(show_stderr=False, show_successful_cmds=True,
            show_failed_cmds=True):
    """
    Get general system related info.
    """
    return getinfo(sysinfo_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))

    # 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.

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: wl-clip]',
        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(
        '',
        '--btrfsinfo',
        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)
    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.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)

        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)
