mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-08-14 04:26:12 +00:00
Merge pull request #714 from opensourcerouting/cli_magic_defpy
CLI magic: part 1 (DEFPY)
This commit is contained in:
commit
1e84e9a697
1
.gitignore
vendored
1
.gitignore
vendored
@ -61,6 +61,7 @@ debian/quagga.prerm.debhelper
|
||||
debian/quagga.substvars
|
||||
debian/quagga/
|
||||
debian/tmp/
|
||||
*.pyc
|
||||
*.swp
|
||||
cscope.*
|
||||
*.pb.h
|
||||
|
@ -9,7 +9,9 @@ SUBDIRS = lib qpb fpm @ZEBRA@ @LIBRFP@ @RFPTEST@ \
|
||||
DIST_SUBDIRS = lib qpb fpm zebra bgpd ripd ripngd ospfd ospf6d ldpd \
|
||||
isisd watchfrr vtysh ospfclient doc m4 pkgsrc redhat tests \
|
||||
solaris pimd nhrpd eigrpd @LIBRFP@ @RFPTEST@ tools snapcraft \
|
||||
babeld
|
||||
babeld \
|
||||
python \
|
||||
# end
|
||||
|
||||
EXTRA_DIST = aclocal.m4 SERVICES REPORTING-BUGS \
|
||||
update-autotools \
|
||||
|
@ -1,6 +1,8 @@
|
||||
## Process this file with automake to produce Makefile.in.
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
|
||||
include ../common.am
|
||||
|
||||
if ENABLE_BGP_VNC
|
||||
#o file to keep linker happy
|
||||
BGP_VNC_RFP_LIB=rfapi/rfapi_descriptor_rfp_utils.o @top_builddir@/$(LIBRFP)/librfp.a
|
||||
@ -116,5 +118,8 @@ examplesdir = $(exampledir)
|
||||
dist_examples_DATA = bgpd.conf.sample bgpd.conf.sample2 \
|
||||
bgpd.conf.vnc.sample
|
||||
|
||||
bgp_vty.o: bgp_vty_clippy.c
|
||||
bgp_debug.o: bgp_debug_clippy.c
|
||||
|
||||
EXTRA_DIST = BGP4-MIB.txt
|
||||
|
||||
|
@ -299,7 +299,7 @@ bgp_debug_list_conf_print (struct vty *vty, const char *desc, struct list *list)
|
||||
}
|
||||
|
||||
static void
|
||||
bgp_debug_list_add_entry(struct list *list, const char *host, struct prefix *p)
|
||||
bgp_debug_list_add_entry(struct list *list, const char *host, const struct prefix *p)
|
||||
{
|
||||
struct bgp_debug_filter *filter;
|
||||
|
||||
@ -313,7 +313,8 @@ bgp_debug_list_add_entry(struct list *list, const char *host, struct prefix *p)
|
||||
else if (p)
|
||||
{
|
||||
filter->host = NULL;
|
||||
filter->p = p;
|
||||
filter->p = prefix_new();
|
||||
prefix_copy (filter->p, p);
|
||||
}
|
||||
|
||||
listnode_add(list, filter);
|
||||
@ -347,7 +348,7 @@ bgp_debug_list_remove_entry(struct list *list, const char *host, struct prefix *
|
||||
}
|
||||
|
||||
static int
|
||||
bgp_debug_list_has_entry(struct list *list, const char *host, struct prefix *p)
|
||||
bgp_debug_list_has_entry(struct list *list, const char *host, const struct prefix *p)
|
||||
{
|
||||
struct bgp_debug_filter *filter;
|
||||
struct listnode *node, *nnode;
|
||||
@ -898,10 +899,14 @@ DEFUN (no_debug_bgp_keepalive_peer,
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
#ifndef VTYSH_EXTRACT_PL
|
||||
#include "bgp_debug_clippy.c"
|
||||
#endif
|
||||
|
||||
/* debug bgp bestpath */
|
||||
DEFUN (debug_bgp_bestpath_prefix,
|
||||
DEFPY (debug_bgp_bestpath_prefix,
|
||||
debug_bgp_bestpath_prefix_cmd,
|
||||
"debug bgp bestpath <A.B.C.D/M|X:X::X:X/M>",
|
||||
"debug bgp bestpath <A.B.C.D/M|X:X::X:X/M>$bestpath",
|
||||
DEBUG_STR
|
||||
BGP_STR
|
||||
"BGP bestpath\n"
|
||||
@ -909,30 +914,16 @@ DEFUN (debug_bgp_bestpath_prefix,
|
||||
"IPv6 prefix\n")
|
||||
|
||||
{
|
||||
int idx_ipv4_ipv6_prefixlen = 3;
|
||||
struct prefix *argv_p;
|
||||
int ret;
|
||||
|
||||
argv_p = prefix_new();
|
||||
ret = str2prefix (argv[idx_ipv4_ipv6_prefixlen]->arg, argv_p);
|
||||
if (!ret)
|
||||
{
|
||||
prefix_free(argv_p);
|
||||
vty_out (vty, "%% Malformed Prefix%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
|
||||
if (!bgp_debug_bestpath_prefixes)
|
||||
bgp_debug_bestpath_prefixes = list_new ();
|
||||
|
||||
if (bgp_debug_list_has_entry(bgp_debug_bestpath_prefixes, NULL, argv_p))
|
||||
if (bgp_debug_list_has_entry(bgp_debug_bestpath_prefixes, NULL, bestpath))
|
||||
{
|
||||
vty_out (vty, "BGP bestptah debugging is already enabled for %s%s", argv[idx_ipv4_ipv6_prefixlen]->arg, VTY_NEWLINE);
|
||||
vty_out (vty, "BGP bestpath debugging is already enabled for %s%s", bestpath_str, VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
bgp_debug_list_add_entry(bgp_debug_bestpath_prefixes, NULL, argv_p);
|
||||
bgp_debug_list_add_entry(bgp_debug_bestpath_prefixes, NULL, bestpath);
|
||||
|
||||
if (vty->node == CONFIG_NODE)
|
||||
{
|
||||
@ -941,7 +932,7 @@ DEFUN (debug_bgp_bestpath_prefix,
|
||||
else
|
||||
{
|
||||
TERM_DEBUG_ON (bestpath, BESTPATH);
|
||||
vty_out (vty, "BGP bestpath debugging is on for %s%s", argv[idx_ipv4_ipv6_prefixlen]->arg, VTY_NEWLINE);
|
||||
vty_out (vty, "BGP bestpath debugging is on for %s%s", bestpath_str, VTY_NEWLINE);
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
|
@ -777,6 +777,10 @@ bgp_clear_star_soft_out (struct vty *vty, const char *name)
|
||||
}
|
||||
|
||||
|
||||
#ifndef VTYSH_EXTRACT_PL
|
||||
#include "bgp_vty_clippy.c"
|
||||
#endif
|
||||
|
||||
/* BGP global configuration. */
|
||||
|
||||
DEFUN (bgp_multiple_instance_func,
|
||||
@ -993,7 +997,7 @@ DEFUN (no_router_bgp,
|
||||
|
||||
/* BGP router-id. */
|
||||
|
||||
DEFUN (bgp_router_id,
|
||||
DEFPY (bgp_router_id,
|
||||
bgp_router_id_cmd,
|
||||
"bgp router-id A.B.C.D",
|
||||
BGP_STR
|
||||
@ -1001,23 +1005,11 @@ DEFUN (bgp_router_id,
|
||||
"Manually configured router identifier\n")
|
||||
{
|
||||
VTY_DECLVAR_CONTEXT(bgp, bgp);
|
||||
int idx_ipv4 = 2;
|
||||
int ret;
|
||||
struct in_addr id;
|
||||
|
||||
ret = inet_aton (argv[idx_ipv4]->arg, &id);
|
||||
if (! ret)
|
||||
{
|
||||
vty_out (vty, "%% Malformed bgp router identifier%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
bgp_router_id_static_set (bgp, id);
|
||||
|
||||
bgp_router_id_static_set (bgp, router_id);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
DEFUN (no_bgp_router_id,
|
||||
DEFPY (no_bgp_router_id,
|
||||
no_bgp_router_id_cmd,
|
||||
"no bgp router-id [A.B.C.D]",
|
||||
NO_STR
|
||||
@ -1026,28 +1018,18 @@ DEFUN (no_bgp_router_id,
|
||||
"Manually configured router identifier\n")
|
||||
{
|
||||
VTY_DECLVAR_CONTEXT(bgp, bgp);
|
||||
int idx_router_id = 3;
|
||||
int ret;
|
||||
struct in_addr id;
|
||||
|
||||
if (argc > idx_router_id)
|
||||
if (router_id_str)
|
||||
{
|
||||
ret = inet_aton (argv[idx_router_id]->arg, &id);
|
||||
if (! ret)
|
||||
{
|
||||
vty_out (vty, "%% Malformed BGP router identifier%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
if (! IPV4_ADDR_SAME (&bgp->router_id_static, &id))
|
||||
if (! IPV4_ADDR_SAME (&bgp->router_id_static, &router_id))
|
||||
{
|
||||
vty_out (vty, "%% BGP router-id doesn't match%s", VTY_NEWLINE);
|
||||
return CMD_WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
id.s_addr = 0;
|
||||
bgp_router_id_static_set (bgp, id);
|
||||
router_id.s_addr = 0;
|
||||
bgp_router_id_static_set (bgp, router_id);
|
||||
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
11
common.am
11
common.am
@ -3,6 +3,17 @@
|
||||
# tree.
|
||||
#
|
||||
|
||||
AM_V_CLIPPY = $(am__v_CLIPPY_$(V))
|
||||
am__v_CLIPPY_ = $(am__v_CLIPPY_$(AM_DEFAULT_VERBOSITY))
|
||||
am__v_CLIPPY_0 = @echo " CLIPPY " $@;
|
||||
am__v_CLIPPY_1 =
|
||||
|
||||
SUFFIXES = _clippy.c
|
||||
.c_clippy.c:
|
||||
$(AM_V_at)$(MAKE) -C $(top_builddir)/$(CLIPPYDIR) clippy
|
||||
$(AM_V_CLIPPY)$(top_builddir)/$(CLIPPYDIR)/clippy $(top_srcdir)/python/clidef.py $< > $@.tmp
|
||||
@{ test -f $@ && diff $@.tmp $@ >/dev/null 2>/dev/null; } && rm $@.tmp || mv $@.tmp $@
|
||||
|
||||
if HAVE_PROTOBUF
|
||||
|
||||
# Uncomment to use an non-system version of libprotobuf-c.
|
||||
|
134
configure.ac
134
configure.ac
@ -23,7 +23,36 @@ dnl Get hostname and other information.
|
||||
dnl -----------------------------------
|
||||
AC_CANONICAL_BUILD()
|
||||
AC_CANONICAL_HOST()
|
||||
AC_CANONICAL_TARGET()
|
||||
|
||||
AS_IF([test "$host" != "$build"], [
|
||||
if test "$srcdir" = "."; then
|
||||
AC_MSG_ERROR([cross-compilation is only possible with builddir separate from srcdir. create a separate directory and run as .../path-to-frr/configure.])
|
||||
fi
|
||||
test -d hosttools || mkdir hosttools
|
||||
abssrc="`cd \"${srcdir}\"; pwd`"
|
||||
|
||||
AC_MSG_NOTICE([...])
|
||||
AC_MSG_NOTICE([... cross-compilation: creating hosttools directory and self-configuring for build platform tools])
|
||||
AC_MSG_NOTICE([... use HOST_CPPFLAGS / HOST_CFLAGS / HOST_LDFLAGS if neccessary])
|
||||
AC_MSG_NOTICE([...])
|
||||
|
||||
( CPPFLAGS="$HOST_CPPFLAGS"; \
|
||||
CFLAGS="$HOST_CFLAGS"; \
|
||||
LDFLAGS="$HOST_LDFLAGS"; \
|
||||
cd hosttools; "${abssrc}/configure" "--host=$build" "--build=$build"; )
|
||||
|
||||
AC_MSG_NOTICE([...])
|
||||
AC_MSG_NOTICE([... cross-compilation: finished self-configuring for build platform tools])
|
||||
AC_MSG_NOTICE([...])
|
||||
|
||||
build_clippy="false"
|
||||
CLIPPYDIR="hosttools/lib"
|
||||
], [
|
||||
build_clippy="true"
|
||||
CLIPPYDIR="lib"
|
||||
])
|
||||
AC_SUBST(CLIPPYDIR)
|
||||
AM_CONDITIONAL([BUILD_CLIPPY], [$build_clippy])
|
||||
|
||||
# Disable portability warnings -- our automake code (in particular
|
||||
# common.am) uses some constructs specific to gmake.
|
||||
@ -123,6 +152,28 @@ AC_DEFUN([AC_C_FLAG], [{
|
||||
AC_LANG_POP(C)
|
||||
}])
|
||||
|
||||
AC_DEFUN([AC_LINK_IFELSE_FLAGS], [{
|
||||
AC_LANG_PUSH(C)
|
||||
ac_cflags_save="$CFLAGS"
|
||||
ac_libs_save="$LIBS"
|
||||
CFLAGS="$CFLAGS $1"
|
||||
LIBS="$LIBS $2"
|
||||
AC_LINK_IFELSE(
|
||||
[$3],
|
||||
[
|
||||
AC_MSG_RESULT([yes])
|
||||
CFLAGS="$ac_cflags_save"
|
||||
LIBS="$ac_libs_save"
|
||||
$5
|
||||
], [
|
||||
AC_MSG_RESULT([no])
|
||||
CFLAGS="$ac_cflags_save"
|
||||
LIBS="$ac_libs_save"
|
||||
$4
|
||||
])
|
||||
AC_LANG_POP(C)
|
||||
}])
|
||||
|
||||
dnl ICC won't bail on unknown options without -diag-error 10006
|
||||
dnl need to do this first so we get useful results for the other options
|
||||
AC_C_FLAG([-diag-error 10006])
|
||||
@ -409,6 +460,77 @@ if test "x${enable_dev_build}" = "xyes"; then
|
||||
fi
|
||||
AM_CONDITIONAL([DEV_BUILD], [test "x$enable_dev_build" = "xyes"])
|
||||
|
||||
#
|
||||
# Python for clippy
|
||||
#
|
||||
AS_IF([test "$host" = "$build"], [
|
||||
PYTHONCONFIG=""
|
||||
|
||||
# ordering:
|
||||
# 1. try python3, but respect the user's preference on which minor ver
|
||||
# 2. try python, which might be py3 or py2 again on the user's preference
|
||||
# 3. try python2 (can really only be 2.7 but eh)
|
||||
# 4. try 3.5 > 3.4 > 3.3 > 3.2 > 2.7 through pkg-config (no user pref)
|
||||
#
|
||||
# (AX_PYTHON_DEVEL has no clue about py3 vs py2)
|
||||
# (AX_PYTHON does not do what we need)
|
||||
|
||||
AC_CHECK_TOOLS([PYTHONCONFIG], [python3-config python-config python2-config])
|
||||
if test -n "$PYTHONCONFIG"; then
|
||||
PYTHON_CFLAGS="`\"${PYTHONCONFIG}\" --includes`"
|
||||
PYTHON_LIBS="`\"${PYTHONCONFIG}\" --libs`"
|
||||
|
||||
AC_MSG_CHECKING([whether we found a working Python version])
|
||||
AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([
|
||||
#include <Python.h>
|
||||
#if PY_VERSION_HEX < 0x02070000
|
||||
#error python too old
|
||||
#endif
|
||||
int main(void);
|
||||
],
|
||||
[
|
||||
{
|
||||
Py_Initialize();
|
||||
return 0;
|
||||
}
|
||||
])], [
|
||||
PYTHONCONFIG=""
|
||||
unset PYTHON_LIBS
|
||||
unset PYTHON_CFLAGS
|
||||
])
|
||||
fi
|
||||
|
||||
if test -z "$PYTHONCONFIG"; then
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.5, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.4, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.3, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-3.2, [], [
|
||||
PKG_CHECK_MODULES([PYTHON], python-2.7, [], [
|
||||
AC_MSG_FAILURE([could not find python-config or pkg-config python, please install Python development files from libpython-dev or similar])
|
||||
])])])])])
|
||||
|
||||
|
||||
AC_MSG_CHECKING([whether we found a working Python version])
|
||||
AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([
|
||||
#include <Python.h>
|
||||
#if PY_VERSION_HEX < 0x02070000
|
||||
#error python too old
|
||||
#endif
|
||||
int main(void);
|
||||
],
|
||||
[
|
||||
{
|
||||
Py_Initialize();
|
||||
return 0;
|
||||
}
|
||||
])], [
|
||||
AC_MSG_FAILURE([could not find python-config or pkg-config python, please install Python development files from libpython-dev or similar])
|
||||
])
|
||||
fi
|
||||
])
|
||||
AC_SUBST(PYTHON_CFLAGS)
|
||||
AC_SUBST(PYTHON_LIBS)
|
||||
|
||||
#
|
||||
# Logic for protobuf support.
|
||||
#
|
||||
@ -1422,7 +1544,9 @@ dnl ------------------
|
||||
dnl check C-Ares library
|
||||
dnl ------------------
|
||||
if test "${NHRPD}" != ""; then
|
||||
PKG_CHECK_MODULES([CARES], [libcares])
|
||||
PKG_CHECK_MODULES([CARES], [libcares], , [
|
||||
AC_MSG_ERROR([trying to build nhrpd, but libcares not found. install c-ares and its -dev headers.])
|
||||
])
|
||||
fi
|
||||
|
||||
|
||||
@ -1437,15 +1561,14 @@ if test "${enable_snmp}" != ""; then
|
||||
SNMP_LIBS="`${NETSNMP_CONFIG} --agent-libs`"
|
||||
SNMP_CFLAGS="`${NETSNMP_CONFIG} --base-cflags`"
|
||||
AC_MSG_CHECKING([whether we can link to Net-SNMP])
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM([
|
||||
AC_LINK_IFELSE_FLAGS([$SNMP_CFLAGS], [$SNMP_LIBS], [AC_LANG_PROGRAM([
|
||||
int main(void);
|
||||
],
|
||||
[
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
])],[AC_MSG_RESULT(yes)],[
|
||||
AC_MSG_RESULT(no)
|
||||
])], [
|
||||
AC_MSG_ERROR([--enable-snmp given but not usable])])
|
||||
case "${enable_snmp}" in
|
||||
yes)
|
||||
@ -1852,6 +1975,7 @@ AC_CONFIG_FILES([Makefile lib/Makefile qpb/Makefile zebra/Makefile ripd/Makefile
|
||||
redhat/Makefile
|
||||
tools/Makefile
|
||||
pkgsrc/Makefile
|
||||
python/Makefile
|
||||
fpm/Makefile
|
||||
redhat/frr.spec
|
||||
snapcraft/Makefile
|
||||
|
2
debian/control
vendored
2
debian/control
vendored
@ -3,7 +3,7 @@ Section: net
|
||||
Priority: optional
|
||||
Maintainer: Christian Hammers <ch@debian.org>
|
||||
Uploaders: Florian Weimer <fw@debian.org>
|
||||
Build-Depends: debhelper (>= 7.0.50~), libncurses5-dev, libreadline-dev, texlive-latex-base, texlive-generic-recommended, libpam0g-dev | libpam-dev, libcap-dev, texinfo (>= 4.7), imagemagick, ghostscript, groff, po-debconf, autotools-dev, hardening-wrapper, libpcre3-dev, gawk, chrpath, libsnmp-dev, git, dh-autoreconf, libjson0, libjson0-dev, dh-systemd, libsystemd-dev, python-ipaddr, bison, flex, libc-ares-dev
|
||||
Build-Depends: debhelper (>= 7.0.50~), libncurses5-dev, libreadline-dev, texlive-latex-base, texlive-generic-recommended, libpam0g-dev | libpam-dev, libcap-dev, texinfo (>= 4.7), imagemagick, ghostscript, groff, po-debconf, autotools-dev, hardening-wrapper, libpcre3-dev, gawk, chrpath, libsnmp-dev, git, dh-autoreconf, libjson0, libjson0-dev, dh-systemd, libsystemd-dev, python-ipaddr, bison, flex, libc-ares-dev, python3-dev
|
||||
Standards-Version: 3.9.6
|
||||
Homepage: http://www.frr.net/
|
||||
XS-Testsuite: autopkgtest
|
||||
|
@ -46,8 +46,7 @@ Install newer version of autoconf and automake (Package versions are too old)
|
||||
sudo make install
|
||||
cd ..
|
||||
|
||||
Install `Python 2.7` in parallel to default 2.6 (needed for `make check` to
|
||||
run unittests).
|
||||
Install `Python 2.7` in parallel to default 2.6
|
||||
Make sure you've install EPEL (`epel-release` as above). Then install current
|
||||
`python2.7` and `pytest`
|
||||
|
||||
|
121
doc/cli.md
121
doc/cli.md
@ -107,6 +107,127 @@ Automatic assignment of variable names works by applying the following rules:
|
||||
These rules should make it possible to avoid manual varname assignment in 90%
|
||||
of the cases.
|
||||
|
||||
DEFPY
|
||||
-----
|
||||
|
||||
`DEFPY(...)` is an enhanced version of `DEFUN()` which is preprocessed by
|
||||
` python/clidef.py`. The python script parses the command definition string,
|
||||
extracts variable names and types, and generates a C wrapper function that
|
||||
parses the variables and passes them on. This means that in the CLI function
|
||||
body, you will receive additional parameters with appropriate types.
|
||||
|
||||
This is best explained by an example:
|
||||
|
||||
```
|
||||
DEFPY(func, func_cmd, "[no] foo bar A.B.C.D (0-99)$num", "...help...")
|
||||
|
||||
=>
|
||||
|
||||
func(self, vty, argc, argv, /* standard CLI arguments */
|
||||
|
||||
const char *no, /* unparsed "no" */
|
||||
struct in_addr bar, /* parsed IP address */
|
||||
const char *bar_str, /* unparsed IP address */
|
||||
long num, /* parsed num */
|
||||
const char *num_str) /* unparsed num */
|
||||
```
|
||||
|
||||
Note that as documented in the previous section, "bar" is automatically
|
||||
applied as variable name for "A.B.C.D". The python code then detects this
|
||||
is an IP address argument and generates code to parse it into a
|
||||
`struct in_addr`, passing it in `bar`. The raw value is passed in `bar_str`.
|
||||
The range/number argument works in the same way with the explicitly given
|
||||
variable name.
|
||||
|
||||
### Type rules
|
||||
|
||||
| Token(s) | Type | Value if omitted by user |
|
||||
|--------------------------|-------------|--------------------------|
|
||||
| `A.B.C.D` | `struct in_addr` | 0.0.0.0 |
|
||||
| `X:X::X:X` | `struct in6_addr` | :: |
|
||||
| `A.B.C.D + X:X::X:X` | `const union sockunion *` | NULL |
|
||||
| `A.B.C.D/M` | `const struct prefix_ipv4 *` | NULL |
|
||||
| `X:X::X:X/M` | `const struct prefix_ipv6 *` | NULL |
|
||||
| `A.B.C.D/M + X:X::X:X/M` | `const struct prefix *` | NULL |
|
||||
| `(0-9)` | `long` | 0 |
|
||||
| `VARIABLE` | `const char *` | NULL |
|
||||
| `word` | `const char *` | NULL |
|
||||
| _all other_ | `const char *` | NULL |
|
||||
|
||||
Note the following details:
|
||||
|
||||
* not all parameters are pointers, some are passed as values.
|
||||
* when the type is not `const char *`, there will be an extra `_str` argument
|
||||
with type `const char *`.
|
||||
* you can give a variable name not only to `VARIABLE` tokens but also to
|
||||
`word` tokens (e.g. constant words). This is useful if some parts of a
|
||||
command are optional. The type will be `const char *`.
|
||||
* `[no]` will be passed as `const char *no`.
|
||||
* pointers will be NULL when the argument is optional and the user did not
|
||||
use it.
|
||||
* if a parameter is not a pointer, but is optional and the user didn't use it,
|
||||
the default value will be passed. Check the `_str` argument if you need to
|
||||
determine whether the parameter was omitted.
|
||||
* if the definition contains multiple parameters with the same variable name,
|
||||
they will be collapsed into a single function parameter. The python code
|
||||
will detect if the types are compatible (i.e. IPv4 + IPv6 variantes) and
|
||||
choose a corresponding C type.
|
||||
* the standard DEFUN parameters (self, vty, argc, argv) are still present and
|
||||
can be used. A DEFUN can simply be **edited into a DEFPY without further
|
||||
changes and it will still work**; this allows easy forward migration.
|
||||
* a file may contain both DEFUN and DEFPY statements.
|
||||
|
||||
### Getting a parameter dump
|
||||
|
||||
The clidef.py script can be called to get a list of DEFUNs/DEFPYs with
|
||||
the parameter name/type list:
|
||||
|
||||
```
|
||||
lib/clippy python/clidef.py --all-defun --show lib/plist.c > /dev/null
|
||||
```
|
||||
|
||||
The generated code is printed to stdout, the info dump to stderr. The
|
||||
`--all-defun` argument will make it process DEFUN blocks as well as DEFPYs,
|
||||
which is useful prior to converting some DEFUNs. **The dump does not list
|
||||
the `_str` arguments** to keep the output shorter.
|
||||
|
||||
Note that the clidef.py script cannot be run with python directly, it needs
|
||||
to be run with _clippy_ since the latter makes the CLI parser available.
|
||||
|
||||
### Include & Makefile requirements
|
||||
|
||||
A source file that uses DEFPY needs to include the `_clippy.c` file **before
|
||||
all DEFPY statements**:
|
||||
|
||||
```
|
||||
/* GPL header */
|
||||
#include ...
|
||||
|
||||
...
|
||||
|
||||
#include "filename_clippy.c"
|
||||
|
||||
DEFPY(...)
|
||||
DEFPY(...)
|
||||
|
||||
install_element(...)
|
||||
```
|
||||
|
||||
This dependency needs to be marked in Makefile.am: (there is no ordering
|
||||
requirement)
|
||||
|
||||
```
|
||||
include ../common.am
|
||||
|
||||
# ...
|
||||
|
||||
# if linked into a LTLIBRARY (.la/.so):
|
||||
filename.lo: filename_clippy.c
|
||||
|
||||
# if linked into an executable or static library (.a):
|
||||
filename.o: filename_clippy.c
|
||||
```
|
||||
|
||||
Doc Strings
|
||||
-----------
|
||||
Each token in a command definition should be documented with a brief doc
|
||||
|
@ -1,5 +1,7 @@
|
||||
## Process this file with automake to produce Makefile.in.
|
||||
|
||||
include ../common.am
|
||||
|
||||
AM_CPPFLAGS = -I.. -I$(top_srcdir) -I$(top_srcdir)/lib -I$(top_builddir)/lib
|
||||
AM_CFLAGS = $(WERROR)
|
||||
DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\"
|
||||
@ -9,6 +11,7 @@ command_lex.h: command_lex.c
|
||||
@if test ! -f $@; then rm -f command_lex.c; else :; fi
|
||||
@if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) command_lex.c; else :; fi
|
||||
command_parse.lo: command_lex.h
|
||||
clippy-command_parse.$(OBJEXT): command_lex.h
|
||||
|
||||
lib_LTLIBRARIES = libfrr.la
|
||||
libfrr_la_LDFLAGS = -version-info 0:0:0
|
||||
@ -87,13 +90,36 @@ pkginclude_HEADERS = \
|
||||
|
||||
noinst_HEADERS = \
|
||||
plist_int.h \
|
||||
log_int.h
|
||||
log_int.h \
|
||||
clippy.h \
|
||||
# end
|
||||
|
||||
noinst_PROGRAMS = grammar_sandbox
|
||||
if BUILD_CLIPPY
|
||||
noinst_PROGRAMS += clippy
|
||||
endif
|
||||
|
||||
grammar_sandbox_SOURCES = grammar_sandbox_main.c
|
||||
grammar_sandbox_LDADD = libfrr.la
|
||||
|
||||
clippy_SOURCES = \
|
||||
defun_lex.l \
|
||||
command_parse.y \
|
||||
command_lex.l \
|
||||
command_graph.c \
|
||||
command_py.c \
|
||||
memory.c \
|
||||
graph.c \
|
||||
vector.c \
|
||||
clippy.c \
|
||||
# end
|
||||
clippy_CPPFLAGS = -D_GNU_SOURCE
|
||||
clippy_CFLAGS = $(PYTHON_CFLAGS)
|
||||
clippy_LDADD = $(PYTHON_LIBS)
|
||||
clippy-command_graph.$(OBJEXT): route_types.h
|
||||
|
||||
plist.lo: plist_clippy.c
|
||||
|
||||
EXTRA_DIST = \
|
||||
queue.h \
|
||||
command_lex.h \
|
||||
|
137
lib/clippy.c
Normal file
137
lib/clippy.c
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* clippy (CLI preparator in python) main executable
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <Python.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <wchar.h>
|
||||
#include "getopt.h"
|
||||
|
||||
#include "command_graph.h"
|
||||
#include "clippy.h"
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define pychar wchar_t
|
||||
static wchar_t *wconv(const char *s)
|
||||
{
|
||||
size_t outlen = mbstowcs(NULL, s, 0);
|
||||
wchar_t *out = malloc((outlen + 1) * sizeof(wchar_t));
|
||||
mbstowcs(out, s, outlen + 1);
|
||||
out[outlen] = 0;
|
||||
return out;
|
||||
}
|
||||
#else
|
||||
#define pychar char
|
||||
#define wconv(x) x
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
pychar **wargv;
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03040000 /* 3.4 */
|
||||
Py_SetStandardStreamEncoding("UTF-8", NULL);
|
||||
#endif
|
||||
Py_SetProgramName(wconv(argv[0]));
|
||||
PyImport_AppendInittab("_clippy", command_py_init);
|
||||
|
||||
Py_Initialize();
|
||||
|
||||
wargv = malloc(argc * sizeof(pychar *));
|
||||
for (int i = 1; i < argc; i++)
|
||||
wargv[i - 1] = wconv(argv[i]);
|
||||
PySys_SetArgv(argc - 1, wargv);
|
||||
|
||||
const char *pyfile = argc > 1 ? argv[1] : NULL;
|
||||
FILE *fp;
|
||||
if (pyfile) {
|
||||
fp = fopen(pyfile, "r");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "%s: %s\n", pyfile, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
fp = stdin;
|
||||
char *ver = strdup(Py_GetVersion());
|
||||
char *cr = strchr(ver, '\n');
|
||||
if (cr)
|
||||
*cr = ' ';
|
||||
fprintf(stderr, "clippy interactive shell\n(Python %s)\n", ver);
|
||||
free(ver);
|
||||
PyRun_SimpleString("import rlcompleter, readline\n"
|
||||
"readline.parse_and_bind('tab: complete')");
|
||||
}
|
||||
|
||||
if (PyRun_AnyFile(fp, pyfile)) {
|
||||
if (PyErr_Occurred())
|
||||
PyErr_Print();
|
||||
else
|
||||
printf("unknown python failure (?)\n");
|
||||
return 1;
|
||||
}
|
||||
Py_Finalize();
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
for (int i = 1; i < argc; i++)
|
||||
free(wargv[i - 1]);
|
||||
#endif
|
||||
free(wargv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* and now for the ugly part... provide simplified logging functions so we
|
||||
* don't need to link libzebra (which would be a circular build dep) */
|
||||
|
||||
#ifdef __ASSERT_FUNCTION
|
||||
#undef __ASSERT_FUNCTION
|
||||
#endif
|
||||
|
||||
#include "log.h"
|
||||
#include "zassert.h"
|
||||
|
||||
#define ZLOG_FUNC(FUNCNAME) \
|
||||
void FUNCNAME(const char *format, ...) \
|
||||
{ \
|
||||
va_list args; \
|
||||
va_start(args, format); \
|
||||
vfprintf (stderr, format, args); \
|
||||
fputs ("\n", stderr); \
|
||||
va_end(args); \
|
||||
}
|
||||
|
||||
ZLOG_FUNC(zlog_err)
|
||||
ZLOG_FUNC(zlog_warn)
|
||||
ZLOG_FUNC(zlog_info)
|
||||
ZLOG_FUNC(zlog_notice)
|
||||
ZLOG_FUNC(zlog_debug)
|
||||
|
||||
void
|
||||
_zlog_assert_failed (const char *assertion, const char *file,
|
||||
unsigned int line, const char *function)
|
||||
{
|
||||
fprintf(stderr, "Assertion `%s' failed in file %s, line %u, function %s",
|
||||
assertion, file, line, (function ? function : "?"));
|
||||
abort();
|
||||
}
|
||||
|
||||
void memory_oom (size_t size, const char *name)
|
||||
{
|
||||
abort();
|
||||
}
|
28
lib/clippy.h
Normal file
28
lib/clippy.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* clippy (CLI preparator in python)
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _FRR_CLIPPY_H
|
||||
#define _FRR_CLIPPY_H
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
extern PyObject *clippy_parse(PyObject *self, PyObject *args);
|
||||
extern PyMODINIT_FUNC command_py_init(void);
|
||||
|
||||
#endif /* _FRR_CLIPPY_H */
|
@ -207,6 +207,10 @@ struct cmd_node
|
||||
int argc __attribute__ ((unused)), \
|
||||
struct cmd_token *argv[] __attribute__ ((unused)) )
|
||||
|
||||
#define DEFPY(funcname, cmdname, cmdstr, helpstr) \
|
||||
DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
|
||||
funcdecl_##funcname
|
||||
|
||||
#define DEFUN(funcname, cmdname, cmdstr, helpstr) \
|
||||
DEFUN_CMD_FUNC_DECL(funcname) \
|
||||
DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, 0, 0) \
|
||||
@ -274,6 +278,9 @@ struct cmd_node
|
||||
#define ALIAS_SH_DEPRECATED(daemon, funcname, cmdname, cmdstr, helpstr) \
|
||||
DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED, daemon)
|
||||
|
||||
#else /* VTYSH_EXTRACT_PL */
|
||||
#define DEFPY(funcname, cmdname, cmdstr, helpstr) \
|
||||
DEFUN(funcname, cmdname, cmdstr, helpstr)
|
||||
#endif /* VTYSH_EXTRACT_PL */
|
||||
|
||||
/* Some macroes */
|
||||
|
@ -44,6 +44,8 @@
|
||||
* struct parser_ctx is needed for the bison forward decls.
|
||||
*/
|
||||
%code requires {
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
@ -189,7 +191,7 @@ start:
|
||||
|
||||
varname_token: '$' WORD
|
||||
{
|
||||
$$ = XSTRDUP (MTYPE_LEX, $2);
|
||||
$$ = $2;
|
||||
}
|
||||
| /* empty */
|
||||
{
|
||||
|
336
lib/command_py.c
Normal file
336
lib/command_py.c
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* clippy (CLI preparator in python) wrapper for FRR command_graph
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#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;
|
||||
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
|
||||
|
||||
/* plumbing types */
|
||||
item(FORK_TKN)
|
||||
item(JOIN_TKN)
|
||||
item(START_TKN)
|
||||
item(END_TKN)
|
||||
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;
|
||||
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
|
||||
|
||||
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);
|
||||
|
||||
Py_INCREF(&typeobj_graph_node);
|
||||
PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node);
|
||||
Py_INCREF(&typeobj_graph);
|
||||
PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph);
|
||||
initret(pymod);
|
||||
}
|
265
lib/defun_lex.l
Normal file
265
lib/defun_lex.l
Normal file
@ -0,0 +1,265 @@
|
||||
%{
|
||||
/*
|
||||
* clippy (CLI preparator in python) C pseudo-lexer
|
||||
* Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; see the file COPYING; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* This is just enough of a lexer to make rough sense of a C source file.
|
||||
* It handles C preprocessor directives, strings, and looks for FRR-specific
|
||||
* idioms (aka DEFUN).
|
||||
*
|
||||
* There is some preliminary support for documentation comments for DEFUNs.
|
||||
* They would look like this (note the ~): (replace \ by /)
|
||||
*
|
||||
* \*~ documentation for foobar_cmd
|
||||
* * parameter does xyz
|
||||
* *\
|
||||
* DEFUN(foobar_cmd, ...)
|
||||
*
|
||||
* This is intended for user documentation / command reference. Don't put
|
||||
* code documentation in it.
|
||||
*/
|
||||
|
||||
/* ignore harmless bug in old versions of flex */
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
|
||||
#include "config.h"
|
||||
#include <Python.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "command_graph.h"
|
||||
#include "clippy.h"
|
||||
|
||||
#define ID 258
|
||||
#define PREPROC 259
|
||||
#define OPERATOR 260
|
||||
#define STRING 261
|
||||
#define COMMENT 262
|
||||
#define SPECIAL 263
|
||||
|
||||
#define DEFUNNY 270
|
||||
#define INSTALL 271
|
||||
#define AUXILIARY 272
|
||||
|
||||
int comment_link;
|
||||
char string_end;
|
||||
|
||||
char *value;
|
||||
|
||||
static void extendbuf(char **what, const char *arg)
|
||||
{
|
||||
if (!*what)
|
||||
*what = strdup(arg);
|
||||
else {
|
||||
size_t vall = strlen(*what), argl = strlen(arg);
|
||||
*what = realloc(*what, vall + argl + 1);
|
||||
memcpy(*what + vall, arg, argl);
|
||||
(*what)[vall + argl] = '\0';
|
||||
}
|
||||
}
|
||||
#define extend(x) extendbuf(&value, x)
|
||||
|
||||
%}
|
||||
|
||||
ID [A-Za-z0-9_]+
|
||||
OPERATOR [!%&/\[\]{}=?:^|\*.;><~'\\+-]
|
||||
SPECIAL [(),]
|
||||
|
||||
%pointer
|
||||
%option yylineno
|
||||
%option noyywrap
|
||||
%option noinput
|
||||
%option nounput
|
||||
%option outfile="defun_lex.c"
|
||||
%option prefix="def_yy"
|
||||
%option 8bit
|
||||
|
||||
%s linestart
|
||||
%x comment
|
||||
%x linecomment
|
||||
%x preproc
|
||||
%x rstring
|
||||
%%
|
||||
BEGIN(linestart);
|
||||
|
||||
\n BEGIN(linestart);
|
||||
|
||||
<INITIAL,linestart,preproc>"/*" comment_link = YY_START; extend(yytext); BEGIN(comment);
|
||||
<comment>[^*\n]* extend(yytext);
|
||||
<comment>"*"+[^*/\n]* extend(yytext);
|
||||
<comment>\n extend(yytext);
|
||||
<comment>"*"+"/" extend(yytext); BEGIN(comment_link); return COMMENT;
|
||||
|
||||
<INITIAL,linestart,preproc>"//" comment_link = YY_START; extend(yytext); BEGIN(linecomment);
|
||||
<linecomment>[^\n]* extend(yytext);
|
||||
<linecomment>\n BEGIN((comment_link == INITIAL) ? linestart : comment_link); return COMMENT;
|
||||
|
||||
<linestart># BEGIN(preproc);
|
||||
<preproc>\n BEGIN(INITIAL); return PREPROC;
|
||||
<preproc>[^\n\\]+ extend(yytext);
|
||||
<preproc>\\\n extend(yytext);
|
||||
<preproc>\\+[^\n] extend(yytext);
|
||||
|
||||
[\"\'] string_end = yytext[0]; extend(yytext); BEGIN(rstring);
|
||||
<rstring>[\"\'] {
|
||||
extend(yytext);
|
||||
if (yytext[0] == string_end) {
|
||||
BEGIN(INITIAL);
|
||||
return STRING;
|
||||
}
|
||||
}
|
||||
<rstring>\\\n /* ignore */
|
||||
<rstring>\\. extend(yytext);
|
||||
<rstring>[^\\\"\']+ extend(yytext);
|
||||
|
||||
"DEFUN" value = strdup(yytext); return DEFUNNY;
|
||||
"DEFUN_NOSH" value = strdup(yytext); return DEFUNNY;
|
||||
"DEFUN_HIDDEN" value = strdup(yytext); return DEFUNNY;
|
||||
"DEFPY" value = strdup(yytext); return DEFUNNY;
|
||||
"ALIAS" value = strdup(yytext); return DEFUNNY;
|
||||
"ALIAS_HIDDEN" value = strdup(yytext); return DEFUNNY;
|
||||
"install_element" value = strdup(yytext); return INSTALL;
|
||||
"VTYSH_TARGETS" value = strdup(yytext); return AUXILIARY;
|
||||
"VTYSH_NODESWITCH" value = strdup(yytext); return AUXILIARY;
|
||||
|
||||
[ \t\n]+ /* ignore */
|
||||
\\ /* ignore */
|
||||
{ID} BEGIN(INITIAL); value = strdup(yytext); return ID;
|
||||
{OPERATOR} BEGIN(INITIAL); value = strdup(yytext); return OPERATOR;
|
||||
{SPECIAL} BEGIN(INITIAL); value = strdup(yytext); return SPECIAL;
|
||||
. /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0];
|
||||
|
||||
%%
|
||||
|
||||
static int yylex_clr(char **retbuf)
|
||||
{
|
||||
int rv = def_yylex();
|
||||
*retbuf = value;
|
||||
value = NULL;
|
||||
return rv;
|
||||
}
|
||||
|
||||
static PyObject *get_args(void)
|
||||
{
|
||||
PyObject *pyObj = PyList_New(0);
|
||||
PyObject *pyArg = NULL;
|
||||
|
||||
char *tval;
|
||||
int depth = 1;
|
||||
int token;
|
||||
|
||||
while ((token = yylex_clr(&tval)) != YY_NULL) {
|
||||
if (token == SPECIAL && tval[0] == '(') {
|
||||
free(tval);
|
||||
break;
|
||||
}
|
||||
if (token == COMMENT) {
|
||||
free(tval);
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "invalid input!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while ((token = yylex_clr(&tval)) != YY_NULL) {
|
||||
if (token == COMMENT) {
|
||||
free(tval);
|
||||
continue;
|
||||
}
|
||||
if (token == SPECIAL) {
|
||||
if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) {
|
||||
if (pyArg)
|
||||
PyList_Append(pyObj, pyArg);
|
||||
pyArg = NULL;
|
||||
if (tval[0] == ')') {
|
||||
free(tval);
|
||||
break;
|
||||
}
|
||||
free(tval);
|
||||
continue;
|
||||
}
|
||||
if (tval[0] == '(')
|
||||
depth++;
|
||||
if (tval[0] == ')')
|
||||
depth--;
|
||||
}
|
||||
if (!pyArg)
|
||||
pyArg = PyList_New(0);
|
||||
PyList_Append(pyArg, PyUnicode_FromString(tval));
|
||||
free(tval);
|
||||
}
|
||||
return pyObj;
|
||||
}
|
||||
|
||||
/* _clippy.parse() -- read a C file, returning a list of interesting bits.
|
||||
* note this ditches most of the actual C code. */
|
||||
PyObject *clippy_parse(PyObject *self, PyObject *args)
|
||||
{
|
||||
const char *filename;
|
||||
if (!PyArg_ParseTuple(args, "s", &filename))
|
||||
return NULL;
|
||||
|
||||
FILE *fd = fopen(filename, "r");
|
||||
if (!fd)
|
||||
return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
|
||||
|
||||
char *tval;
|
||||
int token;
|
||||
yyin = fd;
|
||||
value = NULL;
|
||||
|
||||
PyObject *pyCont = PyDict_New();
|
||||
PyObject *pyObj = PyList_New(0);
|
||||
PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename));
|
||||
PyDict_SetItemString(pyCont, "data", pyObj);
|
||||
|
||||
while ((token = yylex_clr(&tval)) != YY_NULL) {
|
||||
int lineno = yylineno;
|
||||
PyObject *pyItem = NULL, *pyArgs;
|
||||
switch (token) {
|
||||
case DEFUNNY:
|
||||
case INSTALL:
|
||||
case AUXILIARY:
|
||||
pyArgs = get_args();
|
||||
pyItem = PyDict_New();
|
||||
PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval));
|
||||
PyDict_SetItemString(pyItem, "args", pyArgs);
|
||||
break;
|
||||
case COMMENT:
|
||||
if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3))
|
||||
break;
|
||||
pyItem = PyDict_New();
|
||||
PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT"));
|
||||
PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval));
|
||||
break;
|
||||
case PREPROC:
|
||||
pyItem = PyDict_New();
|
||||
PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC"));
|
||||
PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval));
|
||||
break;
|
||||
}
|
||||
if (pyItem) {
|
||||
PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno));
|
||||
PyList_Append(pyObj, pyItem);
|
||||
}
|
||||
free(tval);
|
||||
}
|
||||
def_yylex_destroy();
|
||||
fclose(fd);
|
||||
return pyCont;
|
||||
}
|
1352
lib/plist.c
1352
lib/plist.c
File diff suppressed because it is too large
Load Diff
3
python/Makefile.am
Normal file
3
python/Makefile.am
Normal file
@ -0,0 +1,3 @@
|
||||
EXTRA_DIST = \
|
||||
clidef.py \
|
||||
clippy/__init__.py
|
254
python/clidef.py
Normal file
254
python/clidef.py
Normal file
@ -0,0 +1,254 @@
|
||||
# FRR CLI preprocessor (DEFPY)
|
||||
#
|
||||
# Copyright (C) 2017 David Lamparter for NetDEF, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; see the file COPYING; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
import clippy, traceback, sys, os
|
||||
from collections import OrderedDict
|
||||
from functools import reduce
|
||||
from pprint import pprint
|
||||
from string import Template
|
||||
from io import StringIO
|
||||
|
||||
# the various handlers generate output C code for a particular type of
|
||||
# CLI token, choosing the most useful output C type.
|
||||
|
||||
class RenderHandler(object):
|
||||
def __init__(self, token):
|
||||
pass
|
||||
def combine(self, other):
|
||||
if type(self) == type(other):
|
||||
return other
|
||||
return StringHandler(None)
|
||||
|
||||
deref = ''
|
||||
drop_str = False
|
||||
|
||||
class StringHandler(RenderHandler):
|
||||
argtype = 'const char *'
|
||||
decl = Template('const char *$varname = NULL;')
|
||||
code = Template('$varname = argv[_i]->arg;')
|
||||
drop_str = True
|
||||
|
||||
class LongHandler(RenderHandler):
|
||||
argtype = 'long'
|
||||
decl = Template('long $varname = 0;')
|
||||
code = Template('''\
|
||||
char *_end;
|
||||
$varname = strtol(argv[_i]->arg, &_end, 10);
|
||||
_fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
|
||||
|
||||
# A.B.C.D/M (prefix_ipv4) and
|
||||
# X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
|
||||
# struct prefix:
|
||||
|
||||
class PrefixBase(RenderHandler):
|
||||
def combine(self, other):
|
||||
if type(self) == type(other):
|
||||
return other
|
||||
if type(other) in [Prefix4Handler, Prefix6Handler, PrefixGenHandler]:
|
||||
return PrefixGenHandler(None)
|
||||
return StringHandler(None)
|
||||
deref = '&'
|
||||
class Prefix4Handler(PrefixBase):
|
||||
argtype = 'const struct prefix_ipv4 *'
|
||||
decl = Template('struct prefix_ipv4 $varname = { };')
|
||||
code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
|
||||
class Prefix6Handler(PrefixBase):
|
||||
argtype = 'const struct prefix_ipv6 *'
|
||||
decl = Template('struct prefix_ipv6 $varname = { };')
|
||||
code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
|
||||
class PrefixGenHandler(PrefixBase):
|
||||
argtype = 'const struct prefix *'
|
||||
decl = Template('struct prefix $varname = { };')
|
||||
code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
|
||||
|
||||
# same for IP addresses. result is union sockunion.
|
||||
class IPBase(RenderHandler):
|
||||
def combine(self, other):
|
||||
if type(self) == type(other):
|
||||
return other
|
||||
if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
|
||||
return IPGenHandler(None)
|
||||
return StringHandler(None)
|
||||
class IP4Handler(IPBase):
|
||||
argtype = 'struct in_addr'
|
||||
decl = Template('struct in_addr $varname = { INADDR_ANY };')
|
||||
code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
|
||||
class IP6Handler(IPBase):
|
||||
argtype = 'struct in6_addr'
|
||||
decl = Template('struct in6_addr $varname = IN6ADDR_ANY_INIT;')
|
||||
code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
|
||||
class IPGenHandler(IPBase):
|
||||
argtype = 'const union sockunion *'
|
||||
decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
|
||||
code = Template('''\
|
||||
if (argv[_i]->text[0] == 'X') {
|
||||
s__$varname.sa.sa_family = AF_INET6;
|
||||
_fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
|
||||
$varname = &s__$varname;
|
||||
} else {
|
||||
s__$varname.sa.sa_family = AF_INET;
|
||||
_fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
|
||||
$varname = &s__$varname;
|
||||
}''')
|
||||
|
||||
def mix_handlers(handlers):
|
||||
def combine(a, b):
|
||||
if a is None:
|
||||
return b
|
||||
return a.combine(b)
|
||||
return reduce(combine, handlers, None)
|
||||
|
||||
handlers = {
|
||||
'WORD_TKN': StringHandler,
|
||||
'VARIABLE_TKN': StringHandler,
|
||||
'RANGE_TKN': LongHandler,
|
||||
'IPV4_TKN': IP4Handler,
|
||||
'IPV4_PREFIX_TKN': Prefix4Handler,
|
||||
'IPV6_TKN': IP6Handler,
|
||||
'IPV6_PREFIX_TKN': Prefix6Handler,
|
||||
}
|
||||
|
||||
# core template invoked for each occurence of DEFPY.
|
||||
templ = Template('''/* $fnname => "$cmddef" */
|
||||
DEFUN_CMD_FUNC_DECL($fnname)
|
||||
#define funcdecl_$fnname static int ${fnname}_magic(\\
|
||||
const struct cmd_element *self __attribute__ ((unused)),\\
|
||||
struct vty *vty __attribute__ ((unused)),\\
|
||||
int argc __attribute__ ((unused)),\\
|
||||
struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
|
||||
funcdecl_$fnname;
|
||||
DEFUN_CMD_FUNC_TEXT($fnname)
|
||||
{
|
||||
int _i;
|
||||
unsigned _fail = 0, _failcnt = 0;
|
||||
$argdecls
|
||||
for (_i = 0; _i < argc; _i++) {
|
||||
if (!argv[_i]->varname)
|
||||
continue;
|
||||
_fail = 0;$argblocks
|
||||
if (_fail)
|
||||
vty_out (vty, "%% invalid input for %s: %s%s",
|
||||
argv[_i]->varname, argv[_i]->arg, VTY_NEWLINE);
|
||||
_failcnt += _fail;
|
||||
}
|
||||
if (_failcnt)
|
||||
return CMD_WARNING;
|
||||
return ${fnname}_magic(self, vty, argc, argv$arglist);
|
||||
}
|
||||
|
||||
''')
|
||||
|
||||
# invoked for each named parameter
|
||||
argblock = Template('''
|
||||
if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
|
||||
$code
|
||||
}''')
|
||||
|
||||
def process_file(fn, ofd, dumpfd, all_defun):
|
||||
filedata = clippy.parse(fn)
|
||||
|
||||
for entry in filedata['data']:
|
||||
if entry['type'] == 'DEFPY' or (all_defun and entry['type'].startswith('DEFUN')):
|
||||
cmddef = entry['args'][2]
|
||||
for i in cmddef:
|
||||
assert i.startswith('"') and i.endswith('"')
|
||||
cmddef = ''.join([i[1:-1] for i in cmddef])
|
||||
|
||||
graph = clippy.Graph(cmddef)
|
||||
args = OrderedDict()
|
||||
for token, depth in clippy.graph_iterate(graph):
|
||||
if token.type not in handlers:
|
||||
continue
|
||||
if token.varname is None:
|
||||
continue
|
||||
arg = args.setdefault(token.varname, [])
|
||||
arg.append(handlers[token.type](token))
|
||||
|
||||
#print('-' * 76)
|
||||
#pprint(entry)
|
||||
#clippy.dump(graph)
|
||||
#pprint(args)
|
||||
|
||||
params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
|
||||
argdefs = []
|
||||
argdecls = []
|
||||
arglist = []
|
||||
argblocks = []
|
||||
doc = []
|
||||
|
||||
def do_add(handler, varname, attr = ''):
|
||||
argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
|
||||
argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
|
||||
arglist.append(', %s%s' % (handler.deref, varname))
|
||||
if attr == '':
|
||||
at = handler.argtype
|
||||
if not at.startswith('const '):
|
||||
at = '. . . ' + at
|
||||
doc.append('\t%-26s %s' % (at, varname))
|
||||
|
||||
for varname in args.keys():
|
||||
handler = mix_handlers(args[varname])
|
||||
#print(varname, handler)
|
||||
if handler is None: continue
|
||||
do_add(handler, varname)
|
||||
code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
|
||||
strblock = ''
|
||||
if not handler.drop_str:
|
||||
do_add(StringHandler(None), '%s_str' % (varname), ' __attribute__ ((unused))')
|
||||
strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
|
||||
argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
|
||||
|
||||
if dumpfd is not None:
|
||||
if len(arglist) > 0:
|
||||
dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
|
||||
else:
|
||||
dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
|
||||
|
||||
params['argdefs'] = ''.join(argdefs)
|
||||
params['argdecls'] = ''.join(argdecls)
|
||||
params['arglist'] = ''.join(arglist)
|
||||
params['argblocks'] = ''.join(argblocks)
|
||||
ofd.write(templ.substitute(params))
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
|
||||
argp.add_argument('--all-defun', action = 'store_const', const = True,
|
||||
help = 'process DEFUN() statements in addition to DEFPY()')
|
||||
argp.add_argument('--show', action = 'store_const', const = True,
|
||||
help = 'print out list of arguments and types for each definition')
|
||||
argp.add_argument('-o', type = str, metavar = 'OUTFILE',
|
||||
help = 'output C file name')
|
||||
argp.add_argument('cfile', type = str)
|
||||
args = argp.parse_args()
|
||||
|
||||
dumpfd = None
|
||||
if args.o is not None:
|
||||
ofd = StringIO()
|
||||
if args.show:
|
||||
dumpfd = sys.stdout
|
||||
else:
|
||||
ofd = sys.stdout
|
||||
if args.show:
|
||||
dumpfd = sys.stderr
|
||||
|
||||
process_file(args.cfile, ofd, dumpfd, args.all_defun)
|
||||
|
||||
if args.o is not None:
|
||||
clippy.wrdiff(args.o, ofd)
|
64
python/clippy/__init__.py
Normal file
64
python/clippy/__init__.py
Normal file
@ -0,0 +1,64 @@
|
||||
# FRR CLI preprocessor
|
||||
#
|
||||
# Copyright (C) 2017 David Lamparter for NetDEF, Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 2 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; see the file COPYING; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
import _clippy
|
||||
from _clippy import parse, Graph, GraphNode
|
||||
|
||||
def graph_iterate(graph):
|
||||
'''iterator yielding all nodes of a graph
|
||||
|
||||
nodes arrive in input/definition order, graph circles are avoided.
|
||||
'''
|
||||
|
||||
queue = [(graph.first(), frozenset(), 0)]
|
||||
while len(queue) > 0:
|
||||
node, stop, depth = queue.pop(0)
|
||||
yield node, depth
|
||||
|
||||
join = node.join()
|
||||
if join is not None:
|
||||
queue.insert(0, (join, stop.union(frozenset([node])), depth))
|
||||
join = frozenset([join])
|
||||
|
||||
stop = join or stop
|
||||
nnext = node.next()
|
||||
for n in reversed(nnext):
|
||||
if n not in stop and n is not node:
|
||||
queue.insert(0, (n, stop, depth + 1))
|
||||
|
||||
def dump(graph):
|
||||
'''print out clippy.Graph'''
|
||||
|
||||
for i, depth in graph_iterate(graph):
|
||||
print('\t%s%s %r' % (' ' * (depth * 2), i.type, i.text))
|
||||
|
||||
def wrdiff(filename, buf):
|
||||
'''write buffer to file if contents changed'''
|
||||
|
||||
expl = ''
|
||||
if hasattr(buf, 'getvalue'):
|
||||
buf = buf.getvalue()
|
||||
old = None
|
||||
try: old = open(filename, 'r').read()
|
||||
except: pass
|
||||
if old == buf:
|
||||
# sys.stderr.write('%s unchanged, not written\n' % (filename))
|
||||
return
|
||||
with open('.new.' + filename, 'w') as out:
|
||||
out.write(buf)
|
||||
os.rename('.new.' + filename, filename)
|
@ -143,6 +143,12 @@ Requires(post): /sbin/install-info
|
||||
BuildRequires: gcc texi2html texinfo patch libcap-devel groff
|
||||
BuildRequires: readline readline-devel ncurses ncurses-devel
|
||||
BuildRequires: json-c-devel bison >= 2.7 flex make
|
||||
%if 0%{?rhel} && 0%{?rhel} < 7
|
||||
#python27-devel is available from ius community repo for RedHat/CentOS 6
|
||||
BuildRequires: python27-devel
|
||||
%else
|
||||
BuildRequires: python-devel >= 2.7
|
||||
%endif
|
||||
Requires: ncurses json-c initscripts
|
||||
%if %{with_pam}
|
||||
BuildRequires: pam-devel
|
||||
|
@ -1,3 +1,5 @@
|
||||
include ../common.am
|
||||
|
||||
PYTHON ?= python
|
||||
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
@ -28,6 +30,8 @@ else
|
||||
BGP_VNC_RFP_LIB =
|
||||
endif
|
||||
|
||||
lib/cli/test_cli.o: lib/cli/test_cli_clippy.c
|
||||
|
||||
check_PROGRAMS = \
|
||||
lib/test_buffer \
|
||||
lib/test_checksum \
|
||||
|
@ -39,7 +39,7 @@ int dump_args(struct vty *vty, const char *descr,
|
||||
vty_out (vty, "%s with %d args.%s", descr, argc, VTY_NEWLINE);
|
||||
for (i = 0; i < argc; i++)
|
||||
{
|
||||
vty_out (vty, "[%02d]: %s%s", i, argv[i]->arg, VTY_NEWLINE);
|
||||
vty_out (vty, "[%02d] %s@%s: %s%s", i, argv[i]->text, argv[i]->varname, argv[i]->arg, VTY_NEWLINE);
|
||||
}
|
||||
|
||||
return CMD_SUCCESS;
|
||||
|
@ -21,21 +21,38 @@
|
||||
|
||||
#include <zebra.h>
|
||||
|
||||
#include "prefix.h"
|
||||
#include "common_cli.h"
|
||||
|
||||
DUMMY_DEFUN(cmd0, "arg ipv4 A.B.C.D");
|
||||
DUMMY_DEFUN(cmd1, "arg ipv4m A.B.C.D/M");
|
||||
DUMMY_DEFUN(cmd2, "arg ipv6 X:X::X:X");
|
||||
DUMMY_DEFUN(cmd2, "arg ipv6 X:X::X:X$foo");
|
||||
DUMMY_DEFUN(cmd3, "arg ipv6m X:X::X:X/M");
|
||||
DUMMY_DEFUN(cmd4, "arg range (5-15)");
|
||||
DUMMY_DEFUN(cmd5, "pat a < a|b>");
|
||||
DUMMY_DEFUN(cmd6, "pat b <a|b A.B.C.D$bar>");
|
||||
DUMMY_DEFUN(cmd7, "pat c <a | b|c> A.B.C.D");
|
||||
DUMMY_DEFUN(cmd8, "pat d { foo A.B.C.D|bar X:X::X:X| baz }");
|
||||
DUMMY_DEFUN(cmd8, "pat d { foo A.B.C.D$foo|bar X:X::X:X$bar| baz } [final]");
|
||||
DUMMY_DEFUN(cmd9, "pat e [ WORD ]");
|
||||
DUMMY_DEFUN(cmd10, "pat f [key]");
|
||||
DUMMY_DEFUN(cmd11, "alt a WORD");
|
||||
DUMMY_DEFUN(cmd12, "alt a A.B.C.D");
|
||||
DUMMY_DEFUN(cmd13, "alt a X:X::X:X");
|
||||
DUMMY_DEFUN(cmd14, "pat g { foo A.B.C.D$foo|foo|bar X:X::X:X$bar| baz } [final]");
|
||||
|
||||
#include "tests/lib/cli/test_cli_clippy.c"
|
||||
|
||||
DEFPY(magic_test, magic_test_cmd,
|
||||
"magic (0-100) {ipv4net A.B.C.D/M|X:X::X:X$ipv6}",
|
||||
"1\n2\n3\n4\n5\n")
|
||||
{
|
||||
char buf[256];
|
||||
vty_out(vty, "def: %s%s", self->string, VTY_NEWLINE);
|
||||
vty_out(vty, "num: %ld%s", magic, VTY_NEWLINE);
|
||||
vty_out(vty, "ipv4: %s%s", prefix2str(ipv4net, buf, sizeof(buf)), VTY_NEWLINE);
|
||||
vty_out(vty, "ipv6: %s%s", inet_ntop(AF_INET6, &ipv6, buf, sizeof(buf)), VTY_NEWLINE);
|
||||
return CMD_SUCCESS;
|
||||
}
|
||||
|
||||
void test_init(int argc, char **argv)
|
||||
{
|
||||
@ -47,6 +64,7 @@ void test_init(int argc, char **argv)
|
||||
install_element (ENABLE_NODE, &cmd3_cmd);
|
||||
install_element (ENABLE_NODE, &cmd4_cmd);
|
||||
install_element (ENABLE_NODE, &cmd5_cmd);
|
||||
install_element (ENABLE_NODE, &cmd6_cmd);
|
||||
install_element (ENABLE_NODE, &cmd7_cmd);
|
||||
install_element (ENABLE_NODE, &cmd8_cmd);
|
||||
install_element (ENABLE_NODE, &cmd9_cmd);
|
||||
@ -62,4 +80,6 @@ void test_init(int argc, char **argv)
|
||||
uninstall_element (ENABLE_NODE, &cmd13_cmd);
|
||||
install_element (ENABLE_NODE, &cmd13_cmd);
|
||||
}
|
||||
install_element (ENABLE_NODE, &cmd14_cmd);
|
||||
install_element (ENABLE_NODE, &magic_test_cmd);
|
||||
}
|
||||
|
@ -9,16 +9,16 @@ test# echo
|
||||
test#
|
||||
test# arg ipv4 1.2.3.4
|
||||
cmd0 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv4
|
||||
[02]: 1.2.3.4
|
||||
[00] arg@(null): arg
|
||||
[01] ipv4@(null): ipv4
|
||||
[02] A.B.C.D@ipv4: 1.2.3.4
|
||||
test# arg ipv4 1.2.
|
||||
A.B.C.D 02
|
||||
test# arg ipv4 1.2.3.4
|
||||
cmd0 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv4
|
||||
[02]: 1.2.3.4
|
||||
[00] arg@(null): arg
|
||||
[01] ipv4@(null): ipv4
|
||||
[02] A.B.C.D@ipv4: 1.2.3.4
|
||||
test# arg ipv4 1.2.3
|
||||
% [NONE] Unknown command: arg ipv4 1.2.3
|
||||
test# arg ipv4 1.2.3.4.5
|
||||
@ -30,16 +30,16 @@ test# arg ipv4 blah
|
||||
test#
|
||||
test# arg ipv4m 1.2.3.0/24
|
||||
cmd1 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv4m
|
||||
[02]: 1.2.3.0/24
|
||||
[00] arg@(null): arg
|
||||
[01] ipv4m@(null): ipv4m
|
||||
[02] A.B.C.D/M@ipv4m: 1.2.3.0/24
|
||||
test# arg ipv4m 1.2.
|
||||
A.B.C.D/M 02
|
||||
test# arg ipv4m 1.2.3.0/24
|
||||
cmd1 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv4m
|
||||
[02]: 1.2.3.0/24
|
||||
[00] arg@(null): arg
|
||||
[01] ipv4m@(null): ipv4m
|
||||
[02] A.B.C.D/M@ipv4m: 1.2.3.0/24
|
||||
test# arg ipv4m 1.2.3/9
|
||||
% [NONE] Unknown command: arg ipv4m 1.2.3/9
|
||||
test# arg ipv4m 1.2.3.4.5/6
|
||||
@ -57,35 +57,35 @@ test# arg ipv4m 1.2.3.0/9a
|
||||
test#
|
||||
test# arg ipv6 de4d:b33f::cafe
|
||||
cmd2 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv6
|
||||
[02]: de4d:b33f::cafe
|
||||
[00] arg@(null): arg
|
||||
[01] ipv6@(null): ipv6
|
||||
[02] X:X::X:X@foo: de4d:b33f::cafe
|
||||
test# arg ipv6 de4d:b3
|
||||
X:X::X:X 02
|
||||
test# arg ipv6 de4d:b33f::caf
|
||||
X:X::X:X 02
|
||||
test# arg ipv6 de4d:b33f::cafe
|
||||
cmd2 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv6
|
||||
[02]: de4d:b33f::cafe
|
||||
[00] arg@(null): arg
|
||||
[01] ipv6@(null): ipv6
|
||||
[02] X:X::X:X@foo: de4d:b33f::cafe
|
||||
test# arg ipv6 de4d:b3
|
||||
test# arg ipv6 de4d:b33f::caf
|
||||
X:X::X:X 02
|
||||
test# arg ipv6 de4d:b33f::cafe
|
||||
cmd2 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv6
|
||||
[02]: de4d:b33f::cafe
|
||||
[00] arg@(null): arg
|
||||
[01] ipv6@(null): ipv6
|
||||
[02] X:X::X:X@foo: de4d:b33f::cafe
|
||||
test# arg ipv6 de4d:b33f:z::cafe
|
||||
% [NONE] Unknown command: arg ipv6 de4d:b33f:z::cafe
|
||||
test# arg ipv6 de4d:b33f:cafe:
|
||||
% [NONE] Unknown command: arg ipv6 de4d:b33f:cafe:
|
||||
test# arg ipv6 ::
|
||||
cmd2 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv6
|
||||
[02]: ::
|
||||
[00] arg@(null): arg
|
||||
[01] ipv6@(null): ipv6
|
||||
[02] X:X::X:X@foo: ::
|
||||
test# arg ipv6 ::/
|
||||
% [NONE] Unknown command: arg ipv6 ::/
|
||||
test# arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0
|
||||
@ -94,38 +94,38 @@ test# arg ipv6 12::34::56
|
||||
% [NONE] Unknown command: arg ipv6 12::34::56
|
||||
test# arg ipv6m dead:beef:cafe::/64
|
||||
cmd3 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv6m
|
||||
[02]: dead:beef:cafe::/64
|
||||
[00] arg@(null): arg
|
||||
[01] ipv6m@(null): ipv6m
|
||||
[02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64
|
||||
test# arg ipv6m dead:be
|
||||
X:X::X:X/M 02
|
||||
test# arg ipv6m dead:beef:cafe:
|
||||
X:X::X:X/M 02
|
||||
test# arg ipv6m dead:beef:cafe::/64
|
||||
cmd3 with 3 args.
|
||||
[00]: arg
|
||||
[01]: ipv6m
|
||||
[02]: dead:beef:cafe::/64
|
||||
[00] arg@(null): arg
|
||||
[01] ipv6m@(null): ipv6m
|
||||
[02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64
|
||||
test#
|
||||
test# arg range 4
|
||||
% [NONE] Unknown command: arg range 4
|
||||
test# arg range 5
|
||||
cmd4 with 3 args.
|
||||
[00]: arg
|
||||
[01]: range
|
||||
[02]: 5
|
||||
[00] arg@(null): arg
|
||||
[01] range@(null): range
|
||||
[02] (5-15)@range: 5
|
||||
test# arg range 9
|
||||
(5-15) 02
|
||||
test# arg range 9
|
||||
cmd4 with 3 args.
|
||||
[00]: arg
|
||||
[01]: range
|
||||
[02]: 9
|
||||
[00] arg@(null): arg
|
||||
[01] range@(null): range
|
||||
[02] (5-15)@range: 9
|
||||
test# arg range 15
|
||||
cmd4 with 3 args.
|
||||
[00]: arg
|
||||
[01]: range
|
||||
[02]: 15
|
||||
[00] arg@(null): arg
|
||||
[01] range@(null): range
|
||||
[02] (5-15)@range: 15
|
||||
test# arg range 16
|
||||
% [NONE] Unknown command: arg range 16
|
||||
test# arg range -1
|
||||
@ -146,7 +146,8 @@ test# pa
|
||||
test# papat
|
||||
% Command incomplete.
|
||||
test# pat
|
||||
a c d e f
|
||||
a b c d e f
|
||||
g
|
||||
test# pat
|
||||
% Command incomplete.
|
||||
test#
|
||||
@ -154,17 +155,17 @@ test# pat a
|
||||
% Command incomplete.
|
||||
test# pat a a
|
||||
cmd5 with 3 args.
|
||||
[00]: pat
|
||||
[01]: a
|
||||
[02]: a
|
||||
[00] pat@(null): pat
|
||||
[01] a@(null): a
|
||||
[02] a@(null): a
|
||||
test# pat a
|
||||
a 02
|
||||
b 03
|
||||
test# pat a b
|
||||
cmd5 with 3 args.
|
||||
[00]: pat
|
||||
[01]: a
|
||||
[02]: b
|
||||
[00] pat@(null): pat
|
||||
[01] a@(null): a
|
||||
[02] b@(null): b
|
||||
test# pat a c
|
||||
% There is no matched command.
|
||||
test# pat a c
|
||||
@ -176,10 +177,10 @@ test# pat c a
|
||||
% Command incomplete.
|
||||
test# pat c a 1.2.3.4
|
||||
cmd7 with 4 args.
|
||||
[00]: pat
|
||||
[01]: c
|
||||
[02]: a
|
||||
[03]: 1.2.3.4
|
||||
[00] pat@(null): pat
|
||||
[01] c@(null): c
|
||||
[02] a@(null): a
|
||||
[03] A.B.C.D@(null): 1.2.3.4
|
||||
test# pat c b 2.3.4
|
||||
% [NONE] Unknown command: pat c b 2.3.4
|
||||
test# pat c c
|
||||
@ -195,72 +196,72 @@ test# pat d
|
||||
% Command incomplete.
|
||||
test# pat d foo 1.2.3.4
|
||||
cmd8 with 4 args.
|
||||
[00]: pat
|
||||
[01]: d
|
||||
[02]: foo
|
||||
[03]: 1.2.3.4
|
||||
[00] pat@(null): pat
|
||||
[01] d@(null): d
|
||||
[02] foo@(null): foo
|
||||
[03] A.B.C.D@foo: 1.2.3.4
|
||||
test# pat d foo
|
||||
% Command incomplete.
|
||||
test# pat d noooo
|
||||
% [NONE] Unknown command: pat d noooo
|
||||
test# pat d bar 1::2
|
||||
cmd8 with 4 args.
|
||||
[00]: pat
|
||||
[01]: d
|
||||
[02]: bar
|
||||
[03]: 1::2
|
||||
[00] pat@(null): pat
|
||||
[01] d@(null): d
|
||||
[02] bar@(null): bar
|
||||
[03] X:X::X:X@bar: 1::2
|
||||
test# pat d bar 1::2 foo 3.4.5.6
|
||||
cmd8 with 6 args.
|
||||
[00]: pat
|
||||
[01]: d
|
||||
[02]: bar
|
||||
[03]: 1::2
|
||||
[04]: foo
|
||||
[05]: 3.4.5.6
|
||||
[00] pat@(null): pat
|
||||
[01] d@(null): d
|
||||
[02] bar@(null): bar
|
||||
[03] X:X::X:X@bar: 1::2
|
||||
[04] foo@(null): foo
|
||||
[05] A.B.C.D@foo: 3.4.5.6
|
||||
test# pat d ba
|
||||
bar 04
|
||||
baz 06
|
||||
test# pat d baz
|
||||
cmd8 with 3 args.
|
||||
[00]: pat
|
||||
[01]: d
|
||||
[02]: baz
|
||||
[00] pat@(null): pat
|
||||
[01] d@(null): d
|
||||
[02] baz@(null): baz
|
||||
test# pat d foo 3.4.5.6 baz
|
||||
cmd8 with 5 args.
|
||||
[00]: pat
|
||||
[01]: d
|
||||
[02]: foo
|
||||
[03]: 3.4.5.6
|
||||
[04]: baz
|
||||
[00] pat@(null): pat
|
||||
[01] d@(null): d
|
||||
[02] foo@(null): foo
|
||||
[03] A.B.C.D@foo: 3.4.5.6
|
||||
[04] baz@(null): baz
|
||||
test#
|
||||
test# pat e
|
||||
cmd9 with 2 args.
|
||||
[00]: pat
|
||||
[01]: e
|
||||
[00] pat@(null): pat
|
||||
[01] e@(null): e
|
||||
test# pat e f
|
||||
cmd9 with 3 args.
|
||||
[00]: pat
|
||||
[01]: e
|
||||
[02]: f
|
||||
[00] pat@(null): pat
|
||||
[01] e@(null): e
|
||||
[02] WORD@e: f
|
||||
test# pat e f g
|
||||
% [NONE] Unknown command: pat e f g
|
||||
test# pat e 1.2.3.4
|
||||
cmd9 with 3 args.
|
||||
[00]: pat
|
||||
[01]: e
|
||||
[02]: 1.2.3.4
|
||||
[00] pat@(null): pat
|
||||
[01] e@(null): e
|
||||
[02] WORD@e: 1.2.3.4
|
||||
test#
|
||||
test# pat f
|
||||
cmd10 with 2 args.
|
||||
[00]: pat
|
||||
[01]: f
|
||||
[00] pat@(null): pat
|
||||
[01] f@(null): f
|
||||
test# pat f foo
|
||||
% [NONE] Unknown command: pat f foo
|
||||
test# pat f key
|
||||
cmd10 with 3 args.
|
||||
[00]: pat
|
||||
[01]: f
|
||||
[02]: key
|
||||
[00] pat@(null): pat
|
||||
[01] f@(null): f
|
||||
[02] key@(null): key
|
||||
test#
|
||||
test# alt a
|
||||
test# alt a a
|
||||
@ -268,18 +269,18 @@ test# alt a a
|
||||
X:X::X:X 02
|
||||
test# alt a ab
|
||||
cmd11 with 3 args.
|
||||
[00]: alt
|
||||
[01]: a
|
||||
[02]: ab
|
||||
[00] alt@(null): alt
|
||||
[01] a@(null): a
|
||||
[02] WORD@a: ab
|
||||
test# alt a 1
|
||||
test# alt a 1.2
|
||||
A.B.C.D 02
|
||||
WORD 02
|
||||
test# alt a 1.2.3.4
|
||||
cmd12 with 3 args.
|
||||
[00]: alt
|
||||
[01]: a
|
||||
[02]: 1.2.3.4
|
||||
[00] alt@(null): alt
|
||||
[01] a@(null): a
|
||||
[02] A.B.C.D@a: 1.2.3.4
|
||||
test# alt a 1
|
||||
test# alt a 1:2
|
||||
WORD 02
|
||||
@ -290,16 +291,16 @@ test# alt a 1:2::
|
||||
X:X::X:X 02
|
||||
test# alt a 1:2::3
|
||||
cmd13 with 3 args.
|
||||
[00]: alt
|
||||
[01]: a
|
||||
[02]: 1:2::3
|
||||
[00] alt@(null): alt
|
||||
[01] a@(null): a
|
||||
[02] X:X::X:X@a: 1:2::3
|
||||
test#
|
||||
test# conf t
|
||||
test(config)# do pat d baz
|
||||
cmd8 with 3 args.
|
||||
[00]: pat
|
||||
[01]: d
|
||||
[02]: baz
|
||||
[00] pat@(null): pat
|
||||
[01] d@(null): d
|
||||
[02] baz@(null): baz
|
||||
test(config)# exit
|
||||
test#
|
||||
test# show run
|
||||
|
@ -67,10 +67,10 @@ zebra_fpm_la_SOURCES += zebra_fpm_netlink.c
|
||||
endif
|
||||
if HAVE_PROTOBUF
|
||||
zebra_fpm_la_SOURCES += zebra_fpm_protobuf.c
|
||||
endif
|
||||
if DEV_BUILD
|
||||
zebra_fpm_la_SOURCES += zebra_fpm_dt.c
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
EXTRA_DIST = if_ioctl.c if_ioctl_solaris.c if_netlink.c \
|
||||
|
Loading…
Reference in New Issue
Block a user