summaryrefslogtreecommitdiffstats
path: root/lib/command_py.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/command_py.c')
-rw-r--r--lib/command_py.c357
1 files changed, 357 insertions, 0 deletions
diff --git a/lib/command_py.c b/lib/command_py.c
new file mode 100644
index 0000000..f8abcf8
--- /dev/null
+++ b/lib/command_py.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * clippy (CLI preparator in python) wrapper for FRR command_graph
+ * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
+ */
+
+/* note: this wrapper is intended to be used as build-time helper. while
+ * it should be generally correct and proper, there may be the occasional
+ * memory leak or SEGV for things that haven't been well-tested.
+ */
+
+/* This file is "exempt" from having
+#include "config.h"
+ * as the first include statement because Python.h also does environment
+ * setup & these trample over each other.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <Python.h>
+#include "structmember.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include "command_graph.h"
+#include "clippy.h"
+
+struct wrap_graph;
+static PyObject *graph_to_pyobj(struct wrap_graph *graph,
+ struct graph_node *gn);
+
+/*
+ * nodes are wrapped as follows:
+ * - instances can only be acquired from a graph
+ * - the same node will return the same wrapper object (they're buffered
+ * through "idx")
+ * - a reference is held onto the graph
+ * - fields are copied for easy access with PyMemberDef
+ */
+struct wrap_graph_node {
+ PyObject_HEAD
+
+ bool allowrepeat;
+ const char *type;
+
+ bool deprecated;
+ bool hidden;
+ const char *text;
+ const char *desc;
+ const char *varname;
+ long long min, max;
+
+ struct graph_node *node;
+ struct wrap_graph *wgraph;
+ size_t idx;
+};
+
+/*
+ * graphs are wrapped as follows:
+ * - they can only be created by parsing a definition string
+ * - there's a table here for the wrapped nodes (nodewrappers), indexed
+ * by "idx" (corresponds to node's position in graph's table of nodes)
+ * - graphs do NOT hold references to nodes (would be circular)
+ */
+struct wrap_graph {
+ PyObject_HEAD
+
+ char *definition;
+ struct graph *graph;
+ struct wrap_graph_node **nodewrappers;
+};
+
+static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyErr_SetString(PyExc_ValueError,
+ "cannot create instances of this type");
+ return NULL;
+}
+
+#define member(name, type) \
+ { \
+ (char *)#name, type, offsetof(struct wrap_graph_node, name), \
+ READONLY, (char *)#name " (" #type ")" \
+ }
+static PyMemberDef members_graph_node[] = {
+ member(allowrepeat, T_BOOL), member(type, T_STRING),
+ member(deprecated, T_BOOL), member(hidden, T_BOOL),
+ member(text, T_STRING), member(desc, T_STRING),
+ member(min, T_LONGLONG), member(max, T_LONGLONG),
+ member(varname, T_STRING), {},
+};
+#undef member
+
+/*
+ * node.next() -- returns list of all "next" nodes.
+ * this will include circles if the graph has them.
+ */
+static PyObject *graph_node_next(PyObject *self, PyObject *args)
+{
+ struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
+ PyObject *pylist;
+
+ if (wrap->node->data
+ && ((struct cmd_token *)wrap->node->data)->type == END_TKN)
+ return PyList_New(0);
+ pylist = PyList_New(vector_active(wrap->node->to));
+ for (size_t i = 0; i < vector_active(wrap->node->to); i++) {
+ struct graph_node *gn = vector_slot(wrap->node->to, i);
+ PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn));
+ }
+ return pylist;
+};
+
+/*
+ * node.join() -- return FORK's JOIN node or None
+ */
+static PyObject *graph_node_join(PyObject *self, PyObject *args)
+{
+ struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
+
+ if (!wrap->node->data
+ || ((struct cmd_token *)wrap->node->data)->type == END_TKN)
+ Py_RETURN_NONE;
+
+ struct cmd_token *tok = wrap->node->data;
+ if (tok->type != FORK_TKN)
+ Py_RETURN_NONE;
+
+ return graph_to_pyobj(wrap->wgraph, tok->forkjoin);
+};
+
+static PyMethodDef methods_graph_node[] = {
+ {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"},
+ {"join", graph_node_join, METH_NOARGS, "outbound join node"},
+ {}};
+
+static void graph_node_wrap_free(void *arg)
+{
+ struct wrap_graph_node *wrap = arg;
+ wrap->wgraph->nodewrappers[wrap->idx] = NULL;
+ Py_DECREF(wrap->wgraph);
+}
+
+static PyTypeObject typeobj_graph_node = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.GraphNode",
+ .tp_basicsize = sizeof(struct wrap_graph_node),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = "struct graph_node *",
+ .tp_new = refuse_new,
+ .tp_free = graph_node_wrap_free,
+ .tp_members = members_graph_node,
+ .tp_methods = methods_graph_node,
+};
+
+static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,
+ struct graph_node *gn)
+{
+ struct wrap_graph_node *wrap;
+ size_t i;
+
+ for (i = 0; i < vector_active(wgraph->graph->nodes); i++)
+ if (vector_slot(wgraph->graph->nodes, i) == gn)
+ break;
+ if (i == vector_active(wgraph->graph->nodes)) {
+ PyErr_SetString(PyExc_ValueError, "cannot find node in graph");
+ return NULL;
+ }
+ if (wgraph->nodewrappers[i]) {
+ PyObject *obj = (PyObject *)wgraph->nodewrappers[i];
+ Py_INCREF(obj);
+ return obj;
+ }
+
+ wrap = (struct wrap_graph_node *)typeobj_graph_node.tp_alloc(
+ &typeobj_graph_node, 0);
+ if (!wrap)
+ return NULL;
+ wgraph->nodewrappers[i] = wrap;
+ Py_INCREF(wgraph);
+
+ wrap->idx = i;
+ wrap->wgraph = wgraph;
+ wrap->node = gn;
+ wrap->type = "NULL";
+ wrap->allowrepeat = false;
+ if (gn->data) {
+ struct cmd_token *tok = gn->data;
+ switch (tok->type) {
+#define item(x) \
+ case x: \
+ wrap->type = #x; \
+ break /* no semicolon */
+
+ item(WORD_TKN); // words
+ item(VARIABLE_TKN); // almost anything
+ item(RANGE_TKN); // integer range
+ item(IPV4_TKN); // IPV4 addresses
+ item(IPV4_PREFIX_TKN); // IPV4 network prefixes
+ item(IPV6_TKN); // IPV6 prefixes
+ item(IPV6_PREFIX_TKN); // IPV6 network prefixes
+ item(MAC_TKN); // MAC address
+ item(MAC_PREFIX_TKN); // MAC address with mask
+ item(ASNUM_TKN); // ASNUM
+
+ /* plumbing types */
+ item(FORK_TKN);
+ item(JOIN_TKN);
+ item(START_TKN);
+ item(END_TKN);
+ item(NEG_ONLY_TKN);
+#undef item
+ default:
+ wrap->type = "???";
+ }
+
+ wrap->deprecated = !!(tok->attr & CMD_ATTR_DEPRECATED);
+ wrap->hidden = !!(tok->attr & CMD_ATTR_HIDDEN);
+ wrap->text = tok->text;
+ wrap->desc = tok->desc;
+ wrap->varname = tok->varname;
+ wrap->min = tok->min;
+ wrap->max = tok->max;
+ wrap->allowrepeat = tok->allowrepeat;
+ }
+
+ return (PyObject *)wrap;
+}
+
+#define member(name, type) \
+ { \
+ (char *)#name, type, offsetof(struct wrap_graph, name), \
+ READONLY, (char *)#name " (" #type ")" \
+ }
+static PyMemberDef members_graph[] = {
+ member(definition, T_STRING),
+ {},
+};
+#undef member
+
+/* graph.first() - root node */
+static PyObject *graph_first(PyObject *self, PyObject *args)
+{
+ struct wrap_graph *gwrap = (struct wrap_graph *)self;
+ struct graph_node *gn = vector_slot(gwrap->graph->nodes, 0);
+ return graph_to_pyobj(gwrap, gn);
+};
+
+static PyMethodDef methods_graph[] = {
+ {"first", graph_first, METH_NOARGS, "first graph node"},
+ {}};
+
+static PyObject *graph_parse(PyTypeObject *type, PyObject *args,
+ PyObject *kwds);
+
+static void graph_wrap_free(void *arg)
+{
+ struct wrap_graph *wgraph = arg;
+
+ graph_delete_graph(wgraph->graph);
+ free(wgraph->nodewrappers);
+ free(wgraph->definition);
+}
+
+static PyTypeObject typeobj_graph = {
+ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.Graph",
+ .tp_basicsize = sizeof(struct wrap_graph),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = "struct graph *",
+ .tp_new = graph_parse,
+ .tp_free = graph_wrap_free,
+ .tp_members = members_graph,
+ .tp_methods = methods_graph,
+};
+
+/* top call / entrypoint for python code */
+static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ const char *def, *doc = NULL;
+ struct wrap_graph *gwrap;
+ static const char *kwnames[] = {"cmddef", "doc", NULL};
+
+ gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0);
+ if (!gwrap)
+ return NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames,
+ &def, &doc))
+ return NULL;
+
+ struct graph *graph = graph_new();
+ struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
+ graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
+
+ struct cmd_element cmd = {.string = def, .doc = doc};
+ cmd_graph_parse(graph, &cmd);
+ cmd_graph_names(graph);
+
+ gwrap->graph = graph;
+ gwrap->definition = strdup(def);
+ gwrap->nodewrappers = calloc(vector_active(graph->nodes),
+ sizeof(gwrap->nodewrappers[0]));
+ return (PyObject *)gwrap;
+}
+
+static PyMethodDef clippy_methods[] = {
+ {"parse", clippy_parse, METH_VARARGS, "Parse a C file"},
+ {NULL, NULL, 0, NULL}};
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef pymoddef_clippy = {
+ PyModuleDef_HEAD_INIT,
+ "_clippy",
+ NULL, /* docstring */
+ -1,
+ clippy_methods,
+};
+#define modcreate() PyModule_Create(&pymoddef_clippy)
+#define initret(val) return val;
+#else
+#define modcreate() Py_InitModule("_clippy", clippy_methods)
+#define initret(val) \
+ do { \
+ if (!val) \
+ Py_FatalError("initialization failure"); \
+ return; \
+ } while (0)
+#endif
+
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+PyMODINIT_FUNC command_py_init(void)
+{
+ PyObject *pymod;
+
+ if (PyType_Ready(&typeobj_graph_node) < 0)
+ initret(NULL);
+ if (PyType_Ready(&typeobj_graph) < 0)
+ initret(NULL);
+
+ pymod = modcreate();
+ if (!pymod)
+ initret(NULL);
+
+ if (PyModule_AddIntMacro(pymod, CMD_ATTR_YANG)
+ || PyModule_AddIntMacro(pymod, CMD_ATTR_HIDDEN)
+ || PyModule_AddIntMacro(pymod, CMD_ATTR_DEPRECATED)
+ || PyModule_AddIntMacro(pymod, CMD_ATTR_NOSH))
+ initret(NULL);
+
+ Py_INCREF(&typeobj_graph_node);
+ PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node);
+ Py_INCREF(&typeobj_graph);
+ PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph);
+ if (!elf_py_init(pymod))
+ initret(NULL);
+ initret(pymod);
+}