diff --git a/plugin_exporter/metadata.txt b/plugin_exporter/metadata.txt index 28beb2b..08aa060 100644 --- a/plugin_exporter/metadata.txt +++ b/plugin_exporter/metadata.txt @@ -7,11 +7,11 @@ name=Plugin Exporter qgisMinimumVersion=3.20 qgisMaximumVersion=4.99 description=A QGIS plugin for exporting plugins -version=0.2.2 +version=0.3.0 author=Francis Lapointe email=francis.lapointe5@usherbrooke.ca -about=Plugin Exporter is a QGIS plugin that can export installed plugins into a .csv or .json file. The user can export all the installed plugins or select the plugins they want to export. Plugin Exporter can also use the generated file to install the plugins back in QGIS. Third party repositories are also supported as of v0.2.0. +about=Plugin Exporter is a QGIS plugin that can export installed plugins into a .csv, .json, .md or .pdf file. The user can export all the installed plugins or select the plugins they want to export. Plugin Exporter can also use a .csv or .json file to install the plugins back in QGIS. Third party repositories are also supported as of v0.2.0. tracker=https://github.com/Scriptbash/PluginExporter/issues repository=https://github.com/Scriptbash/PluginExporter @@ -24,6 +24,8 @@ supportsQt6=True hasProcessingProvider=no # Uncomment the following line and add your changelog: changelog= + v0.3.0 + - Add Markdown (.md) and PDF (.pdf) export formats (export only) v0.2.2 - Migrate to pyQt6 v0.2.1 diff --git a/plugin_exporter/plugin_exporter.py b/plugin_exporter/plugin_exporter.py index 3a9e337..5559be0 100644 --- a/plugin_exporter/plugin_exporter.py +++ b/plugin_exporter/plugin_exporter.py @@ -23,7 +23,8 @@ """ from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication -from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtGui import QIcon, QTextDocument +from qgis.PyQt.QtPrintSupport import QPrinter from qgis.PyQt.QtWidgets import QAction, QLabel, QCheckBox from qgis.core import Qgis, QgsSettings from pyplugin_installer.installer_data import repositories @@ -32,7 +33,9 @@ import os.path import csv import json +import html import pathlib +from datetime import datetime # Initialize Qt resources from file resources.py from .resources import * @@ -371,6 +374,22 @@ def export_plugins(self): self.iface.messageBar().pushSuccess( "Success", "Selected plugins were exported successfully." ) + elif file_format == ".md": + with open(output_file, "w", encoding="utf8") as file: + file.write(self.build_markdown(plugin_list, repos)) + self.iface.messageBar().pushSuccess( + "Success", "Selected plugins were exported successfully." + ) + elif file_format == ".pdf": + document = QTextDocument() + document.setHtml(self.build_html(plugin_list, repos)) + printer = QPrinter() + printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) + printer.setOutputFileName(output_file) + document.print(printer) + self.iface.messageBar().pushSuccess( + "Success", "Selected plugins were exported successfully." + ) except IsADirectoryError: self.iface.messageBar().pushMessage( "Error", @@ -485,6 +504,98 @@ def add_repository(self, repo_info): "Success", repo_name + " was added to the repositories." ) + # Collects the third party repositories and every metadata field of each + # selected plugin. Used by the human readable exports (.md and .pdf), which + # are export only: they cannot be imported back. Each plugin keeps all of + # the fields the plugin manager exposes for it (same data as the .json + # export), not just a fixed subset. + def build_report_data(self, plugin_list, repos): + repo_rows = [] + if repos: + for name, value in repos.items(): + if name == "QGIS Official Plugin Repository": + continue + repo_rows.append([name, value["url"]]) + + plugins = [] + for plugin in plugin_list: + # Preserve the metadata key order; fall back to the id for the title + title = plugin.get("name") or plugin.get("id") or "Unknown plugin" + fields = [(str(k), "" if v is None else str(v)) for k, v in plugin.items()] + plugins.append((title, fields)) + return repo_rows, plugins + + # Builds a Markdown document listing the selected plugins + def build_markdown(self, plugin_list, repos): + repo_rows, plugins = self.build_report_data(plugin_list, repos) + generated = datetime.now().strftime("%Y-%m-%d %H:%M") + + def cell(text): + return str(text).replace("|", "\\|").replace("\n", " ") + + lines = [ + "# QGIS Plugins Export", + "", + "_Generated by Plugin Exporter on " + generated + "_", + "", + ] + if repo_rows: + lines += ["## Third party repositories", "", "| Name | URL |", "| --- | --- |"] + for row in repo_rows: + lines.append("| " + " | ".join(cell(c) for c in row) + " |") + lines.append("") + lines += ["## Plugins", ""] + for title, fields in plugins: + lines += [ + "### " + cell(title), + "", + "| Field | Value |", + "| --- | --- |", + ] + for key, value in fields: + lines.append("| " + cell(key) + " | " + cell(value) + " |") + lines.append("") + return "\n".join(lines) + + # Builds an HTML document used to render the PDF export + def build_html(self, plugin_list, repos): + repo_rows, plugins = self.build_report_data(plugin_list, repos) + generated = datetime.now().strftime("%Y-%m-%d %H:%M") + + def esc(text): + return html.escape(str(text)).replace("\n", "
") + + def table(headers, rows): + html_rows = [ + "" + + "".join("" + html.escape(h) + "" for h in headers) + + "" + ] + for row in rows: + html_rows.append( + "" + "".join("" + esc(c) + "" for c in row) + "" + ) + return ( + '' + + "".join(html_rows) + + "
" + ) + + parts = [ + "", + "

QGIS Plugins Export

", + "

Generated by Plugin Exporter on " + html.escape(generated) + "

", + ] + if repo_rows: + parts.append("

Third party repositories

") + parts.append(table(["Name", "URL"], repo_rows)) + parts.append("

Plugins

") + for title, fields in plugins: + parts.append("

" + esc(title) + "

") + parts.append(table(["Field", "Value"], fields)) + parts.append("") + return "".join(parts) + # Disables and enables widgets def toggle_widget(self): if self.dlg.rd_import.isChecked(): @@ -502,7 +613,12 @@ def toggle_widget(self): # Sets the file extension filter for the QgsFileWidget def set_filter(self): - if self.dlg.combo_file_format.currentText() == ".json": + current_format = self.dlg.combo_file_format.currentText() + if current_format == ".json": self.dlg.file_output_export.setFilter("*.json") - elif self.dlg.combo_file_format.currentText() == ".csv": + elif current_format == ".csv": self.dlg.file_output_export.setFilter("*.csv") + elif current_format == ".md": + self.dlg.file_output_export.setFilter("*.md") + elif current_format == ".pdf": + self.dlg.file_output_export.setFilter("*.pdf") diff --git a/plugin_exporter/plugin_exporter_dialog_base.ui b/plugin_exporter/plugin_exporter_dialog_base.ui index 441f353..029732b 100644 --- a/plugin_exporter/plugin_exporter_dialog_base.ui +++ b/plugin_exporter/plugin_exporter_dialog_base.ui @@ -32,6 +32,16 @@ .json + + + .md + + + + + .pdf + +