#!/usr/local/bin/python3.8
R"""Convert source code in the clipboard to document format

Usage: cb-src-to-doc [-h] [-i] [-l LANG] [-f FMT]

optional arguments:
  -h, --help  show this help message and exit
  -i          run in interactive mode
  -l LANG     programming language of source code
  -f FMT      pretty-print document format

This program takes the source code (in the programming language
`LANG`) from the clipboard and converts to a pretty-print version in
the `DOC` format (typically LaTeX or HTML).  The result is copied back
to the clipboard. The `-i` option makes the program run in interactive
graphical user interface mode.

The program is just a simple wrapper of the `pygmentize` command line
tool [1]. The main motivation is to create a version that is a Mac app
that I can easily run from Spotlight, Alfred or other similar app
launchers. The app is implemented `CB-Src-2-Doc.applescript` program.

[1] https://pygments.org/docs/cmdline/

(c) Copyright 2020 Anders Andersen, UiT The Arctic University of Norway

"""

# -------------------
# Import modules used
# -------------------

# Standard Python modules
import sys, re

# Work with clipboard
import pyperclip

# Use subprocess to perform the command line operations
import subprocess


# -----------------------------
# Handle command line arguments
# -----------------------------

# Create argument parser
import argparse
parser = argparse.ArgumentParser(
    description="Convert source code in the clipboard to document format")
parser.add_argument(
    "-i", action="store_true", default=False,
    help="run in interactive mode")
parser.add_argument(
    "-l", default="py3", help="programming language of source code")
parser.add_argument(
    "-f", default="html", help="pretty-print document format")

# Parse the arguemnts
args = parser.parse_args()

# No errors so far
errmsg = ""


# ------------
# The GUI part
# ------------

# Interactive (GUI) mode selected
if args.i:

    from PySide2.QtWidgets import QApplication, QDialog
    from PySide2.QtWidgets import QHBoxLayout, QVBoxLayout
    from PySide2.QtWidgets import QPushButton, QLabel, QLineEdit

    class SrcDocDialog(QDialog):
        
        def __init__(self, app, args, lexers, formatters):
            
            super(SrcDocDialog, self).__init__()
            self.setWindowTitle("Convert src in clipboard to pp format")
            self.app = app
            self.args = args
            self.lexers = lexers
            self.formatters = formatters
            self.choose = QHBoxLayout()
            self.buttons = QHBoxLayout()
            self.layout = QVBoxLayout()

            self.label = QLabel("Choose lexer and formatter:")
            self.layout.addWidget(self.label)

            self.lex = QLineEdit(args.l)
            self.lex.textChanged.connect(self.check_ok)
            self.choose.addWidget(self.lex)
            self.fmt = QLineEdit(args.f)
            self.fmt.textChanged.connect(self.check_ok)
            self.choose.addWidget(self.fmt)
            self.layout.addLayout(self.choose)

            self.button_cancel = QPushButton("Cancel")
            self.button_cancel.setMinimumWidth(85)
            self.buttons.addWidget(self.button_cancel)
            self.button_ok = QPushButton("OK")
            self.button_ok.setMinimumWidth(85)
            self.buttons.addWidget(self.button_ok)
            self.button_ok.setDefault(True)
            self.button_ok.setEnabled(False)
            self.layout.addLayout(self.buttons)

            self.button_cancel.clicked.connect(self.cancel)
            self.button_ok.clicked.connect(self.ok)

            self.setLayout(self.layout)
            self.check_ok()

        def check_ok(self):
            lex = self.lex.text()
            fmt = self.fmt.text()
            if (lex in self.lexers) and (fmt in self.formatters):
                self.button_ok.setEnabled(True)
            else:
                self.button_ok.setEnabled(False)

        def ok(self):
            self.args.l = self.lex.text()
            self.args.f = self.fmt.text()
            self.app.closeAllWindows()

        def cancel(self):
            self.args.l = None
            self.app.closeAllWindows()

    def do_srcdoc_dialog(args, lexers, formatters):
        app = QApplication()
        dialog = SrcDocDialog(app, args, lexers, formatters)
        dialog.show()
        return app, app.exec_()

# ------------------------------------------------
# Interact with the `pygmentize` command line tool
# ------------------------------------------------

# Regular expression to get the lexers/formatters
mexp = re.compile(r"\* ((\w|,| )+):", re.ASCII)
    
def get_available(which):
    """Function to get lexers or formatters

    This function returns either the avalable lexers or the available
    formatters (the `which` argument specify which) of the
    `pygmentize` command line tool.

    """

    # Perform the list command of `pygmentize`
    res = subprocess.run(
        ["pygmentize", "-L", which], text=True, capture_output=True)
    res.check_returncode()

    # Parse the output of the list command
    lst = []
    for line in res.stdout.splitlines():
        m = mexp.match(line)
        if m:
            lst += m.group(1).split(", ")

    # Return the list
    return lst

# Do the `pygmentize` commands
try:
    
    # Get all available lexers (programming languages) and formatters
    lexers = get_available("lexer")
    formatters = get_available("formatter")

    # Interactive GUI mode?
    if args.i:
        try:
            app, status = do_srcdoc_dialog(args, lexers, formatters)
        except Exception as err:
            errmsg = str(err)

        # User selected cancel
        if args.l == None:
            sys.exit(0)

    # Ignore if previous steps failed
    if not errmsg:

        # Check if lexers and formatters are available
        if not args.l in lexers:
            errmsg += "{} is not a valid pygmentize lexer".format(args.l)
        if not args.f in formatters:
            errmsg += "{} is not a valid pygmentize formatter".format(args.f)

    # Perform the `pygmentize` command (input from clipboard)
    if not errmsg:
        res = subprocess.run(
            ["pygmentize", "-l", args.l, "-f", args.f],
            input=pyperclip.paste(), text=True, capture_output=True)
        res.check_returncode()

# Something went wrong
except subprocess.CalledProcessError as err:
    errmsg = err.stderr


# ----------------------
# Did anything go wrong?
# ----------------------

if errmsg:

    if args.i:
    
        class StatusDialog(QDialog):
        
            def __init__(self, app, msg):
                super(StatusDialog, self).__init__()
                self.app = app
                self.setWindowTitle("Error")
                self.text = QPlainTextEdit()
                self.text.setPlaceholderText(msg)
                self.text.setReadOnly(True)
                self.button_ok = QPushButton("OK")
                self.button_ok.clicked.connect(self.ok)
                self.layout = QVBoxLayout()
                self.layout.addWidget(self.text)
                self.layout.addWidget(self.button_ok)
                self.setLayout(self.layout)

            def ok(self):
                self.app.closeAllWindows()

        dialog = StatusDialog(app, errmsg)
        dialog.show()
        status = app.exec_()
        sys.exit(1)

    else:

        print(errmsg, file=sys.stderr)
        sys.exit(1)


# -----------------------------
# Copy result back to clopbaord
# -----------------------------

pyperclip.copy(res.stdout.rstrip())