#!/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())