Collection of mostly command line tools / PHP scripts. Somewhat out of date.

⌈⌋ ⎇ branch:  scripts + snippets


Artifact [da438aef61]

Artifact da438aef6126b01f84a2b327dcd4e9c73db988f8:

  • Executable file inkscape/pmd2inks — part of check-in [6b16bde070] at 2022-10-11 07:12:08 on branch trunk — separate handler for notebook/page sectioning (user: mario size: 7608)

#!/usr/bin/env python3
# encoding: utf-8
# api: python
##type: cli
# category: convert
# title: plugin meta to .inx
# description: Convert plugin description to Inkscape .inx
# license: PD
# version: 0.7
# state: beta
# depends: pluginconf (>= 0.7.6)
# config: -
# pylint: disable=missing-docstring, invalid-name, no-else-return
# doc: https://gitlab.com/inkscape/extensions/-/blob/master/inkscape.extension.rng
#
# Quickly converts a PMD comment block to .inx descriptor.
# Mapping some types and modes.
#
#  * Needs manual { xml: </injection> } for closing notebooks/pages.
#  * Introduces type=label for inserting a <label>
#  * Outputs a sample add_argument() list as well.
#


import sys
import re
import pluginconf


meta = {}
xml_template = """<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
    <name>{name}</name>
    <description>{desc}</description>
    <!--<schema:softwareVersion xmlns:schema="https://schema.org/">{version}</schema:softwareVersion>-->
    <category>{submenu}</category>
    <id>{id}</id>
    <dependency type="executable" location="inx">{fn}</dependency>
    <label appearance="header">{header}</label>
    {params}
    <effect needs-live-preview="false">
        <object-type>all</object-type>
        <effects-menu>
            <submenu name="{submenu}" />
        </effects-menu>
        <menu-tip>{desc}</menu-tip>
    </effect>
    {export}
    <script>
        <command location="inx" interpreter="python">{fn}</command>
    </script>
<!--
{args}
-->
</inkscape-extension>
"""
map = {
    "type": {
        "select": "select",
        "color": "color",
        "path": "path",
        "int": "int",
        "float": "float",
        "range": "int",
        "bool": "bool",
        "file": "path",
        "str": "string",
        "text": "string",
        "tab": "notebook",
        "group": "optiongroup",
        "label": "label",
        "notebook": "notebook",
    },
    "mode": {
        "bool": "checkbox",
        "color": "color",
        "range": "slider",
        "float": "float",
    },
    "appearance": {
        "text": "multiline",
        "range": "full",
        "enum": "combo",
        "select": "combo",
    },
    "translatable": {
        "0": "no",
        "1": "yes",
    },
    "arg": {
        "string": "str",
        "str": "str",
        "int": "int",
        "float": "float",
        "bool": "inkex.Boolean",
        "color": "inkex.Color",
    }
}
bool_true_match = re.compile(r"^(1|yes|true)", re.I).match

notebook = None
class new_notebook:
    """ nestable notebook pages / tabs """
    tab = ""
    param = ""
    prev = None
    curr = None
    
    def __init__(self, param, o):
        self.param = param
        self.nest("", o)
        self.curr = self
        self.prev = notebook

    def nest(self, name, o):
        if name == self.tab:
            return
        if self.tab:
            yield from self.end(name)
        if name:
            yield f"""
                 <page name="{name}" gui-text="{o.get("tab_desc") or name}">
            """
        self.tab = name

    def end(self, name, all=True):
        yield """</page>"""
        if self.param and not name:
            self.param = ""
            yield """</param>"""
            notebook = self.prev
        if all and self.prev:
            self.prev.end(name, all)

def enum_item(k, v, _add="", xml="option"):
    return f"""<{xml} {_add} value="{k}">{v}</{xml}>"""

def param(o):
    global notebook
    # arguments
    raw_type = o.get("type")
    _type = raw_type or "str"
    _type = map["type"].get(_type, "string")
    _name = o.get("name", "_0")
    _val = o.get("value", "")
    _desc = o.get("description", "…")
    _tab = o.get("category") or o.get("class") or o.get("page")
    # optional attributes via {_add}
    _add = ""
    for prop in "mode", "appearance", "translatable":
        if map[prop].get(raw_type) and not o.get(prop):
            o[prop] = map[prop][raw_type]
        if o.get(prop):
            _add = _add + f"{prop}=\"{o[prop]}\" "
    if o.get("help"):
        _add += f"gui-description=\"{o['help']}\""
    if bool_true_match(o.get("hidden", "")):
        _add += 'gui-hidden="true" '
    # value mapping
    if _type == "bool":
        _val = "true" if bool_true_match( _val) else "false"
    # raw output
    if notebook:
        yield from notebook.nest(_tab, o)
    if "xml" in o:
        yield o["xml"]
    elif "label" in o or _type == "label":
        text = o.get("label") or _desc
        if re.match(r"^[#{$]", text):
            text = meta[re.sub(r"^[#{$]+|[:}$]*$", "", text)] # in-place reference
        yield f"""
            <label {_add}>{text}</label>
        """
    # variants
    elif _type == "select":
        _option_add = 'translatable="no" ' if not bool_true_match(o.get("translatable", "yes")) else ''
        ls = "\n            ".join([enum_item(k, v, _option_add) for k, v in o.get("select", {}).items()])
        yield f"""
            <param name="{_name}" type="optiongroup" {_add} gui-text="{_desc}">
            {ls}
            </param>
        """
    elif _type == "notebook":
        notebook = new_notebook(_name, o)
        yield f"""
            <param name="{_name}" type="{_type}" {_add} gui-text="{_desc}">
        """
    elif _type in ("int", "float"):
        yield f"""
            <param name="{_name}" type="{_type}" min="{o.get("min",0)}" max="{o.get("max",100)}" precision="{o.get("precision",1)}" {_add} gui-text="{_desc}">{_val}</param>
        """
    else:
        yield f"""
            <param name="{_name}" type="{_type}" {_add} gui-text="{_desc}">{_val}</param>
        """

def inx_export(m, xml="output"):
    if not "inx_export" in m:
        return ""
    _ext, _mime, _type, _tip = re.split(r"\s*,\s*", m["inx_export"])
    return f"""
    <!--{xml}>
        <extension>{_ext}</extension>
        <mimetype>{_mime}</mimetype>
        <filetypename>{_type}</filetypename>
        <filetypetooltip>{_tip}</filetypetooltip>
        <dataloss>true</dataloss>
    </{xml}-->
    """

def add_arg(o):
    if not o.get("name"):
        return ""
    _type = map["arg"].get(o["type"], "str")
    _name = o.get("name")
    _val = o.get("value", "")
    if _type == "inkex.Boolean":
        _val = "True" if bool_true_match(_val) else "False"
    elif _type not in ("int", "float"):
        _val = '"' + _val + '"'
    return f"""        pars.add_argument("-"+"-{_name}", type={_type}, dest="{_name}", default={_val}, help="{o.get("description","")}")"""


def generate_inx(m):
    """ roughly convert builtin plugin description to .inx """
    params = []
    for o in m["config"]:
        for line in param(o):
            params.append(line)
    if notebook.curr:
        params = params + list(notebook.end("", True))
    args = list(
        filter(
            None,
            [add_arg(o) for o in m["config"] if o["type"] != "label"]
        )
    )
    return xml_template.format(
        name=m.get("title", "Unnamed"),
        desc=m.get("description", "extension script"),
        version=m.get("version"),
        id=m["id"],
        header=m.get("description", "random plugin"),
        params="\n    ".join(params),
        submenu=m.get("category", "Else").title(),
        export=inx_export(m),
        fn=re.sub(".+/", "", m["fn"]),
        args="\n".join(args)
    )


if len(sys.argv) == 2:
    meta = pluginconf.plugin_meta(fn=sys.argv[1], max_length=8192)
    print(
        generate_inx(
            meta
        )
    )
else:
    print("Syntax: pmd2inx script.py")