only some comment
This commit is contained in:
parent
f88e32ba32
commit
38e25a847f
3 changed files with 73 additions and 50 deletions
|
@ -99,12 +99,12 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
|
|
||||||
getline(ss, ifaceclass);
|
getline(ss, ifaceclass);
|
||||||
|
|
||||||
// a "_adaptor" is added to class name to distinguish between proxy and adaptor
|
// a "_adaptor" is added to class name to distinguish between proxy and adaptor
|
||||||
ifaceclass += "_adaptor";
|
ifaceclass += "_adaptor";
|
||||||
|
|
||||||
cerr << "generating code for interface " << ifacename << "..." << endl;
|
cerr << "generating code for interface " << ifacename << "..." << endl;
|
||||||
|
|
||||||
// the code from class definiton up to opening of the constructor is generated...
|
// the code from class definiton up to opening of the constructor is generated...
|
||||||
body << "class " << ifaceclass << endl
|
body << "class " << ifaceclass << endl
|
||||||
<< ": public ::DBus::InterfaceAdaptor" << endl
|
<< ": public ::DBus::InterfaceAdaptor" << endl
|
||||||
<< "{" << endl
|
<< "{" << endl
|
||||||
|
@ -114,7 +114,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
<< tab << ": ::DBus::InterfaceAdaptor(\"" << ifacename << "\")" << endl
|
<< tab << ": ::DBus::InterfaceAdaptor(\"" << ifacename << "\")" << endl
|
||||||
<< tab << "{" << endl;
|
<< tab << "{" << endl;
|
||||||
|
|
||||||
// generates code to bind the properties
|
// generates code to bind the properties
|
||||||
for (Xml::Nodes::iterator pi = properties.begin(); pi != properties.end(); ++pi)
|
for (Xml::Nodes::iterator pi = properties.begin(); pi != properties.end(); ++pi)
|
||||||
{
|
{
|
||||||
Xml::Node &property = **pi;
|
Xml::Node &property = **pi;
|
||||||
|
@ -132,7 +132,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
<< ");" << endl;
|
<< ");" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate code to register all methods
|
// generate code to register all methods
|
||||||
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
||||||
{
|
{
|
||||||
Xml::Node &method = **mi;
|
Xml::Node &method = **mi;
|
||||||
|
@ -148,7 +148,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
body << tab << "::DBus::IntrospectedInterface *const introspect() const " << endl
|
body << tab << "::DBus::IntrospectedInterface *const introspect() const " << endl
|
||||||
<< tab << "{" << endl;
|
<< tab << "{" << endl;
|
||||||
|
|
||||||
// generate the introspect arguments
|
// generate the introspect arguments
|
||||||
for (Xml::Nodes::iterator mi = ms.begin(); mi != ms.end(); ++mi)
|
for (Xml::Nodes::iterator mi = ms.begin(); mi != ms.end(); ++mi)
|
||||||
{
|
{
|
||||||
Xml::Node &method = **mi;
|
Xml::Node &method = **mi;
|
||||||
|
@ -182,7 +182,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
body << tab << tab << "static ::DBus::IntrospectedMethod " << ifaceclass << "_methods[] = " << endl
|
body << tab << tab << "static ::DBus::IntrospectedMethod " << ifaceclass << "_methods[] = " << endl
|
||||||
<< tab << tab << "{" << endl;
|
<< tab << tab << "{" << endl;
|
||||||
|
|
||||||
// generate the introspect methods
|
// generate the introspect methods
|
||||||
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
||||||
{
|
{
|
||||||
Xml::Node &method = **mi;
|
Xml::Node &method = **mi;
|
||||||
|
@ -230,7 +230,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
body << tab << tab << tab << "{ 0, 0, 0, 0 }" << endl
|
body << tab << tab << tab << "{ 0, 0, 0, 0 }" << endl
|
||||||
<< tab << tab << "};" << endl;
|
<< tab << tab << "};" << endl;
|
||||||
|
|
||||||
// generate the Introspected interface
|
// generate the Introspected interface
|
||||||
body << tab << tab << "static ::DBus::IntrospectedInterface " << ifaceclass << "_interface = " << endl
|
body << tab << tab << "static ::DBus::IntrospectedInterface " << ifaceclass << "_interface = " << endl
|
||||||
<< tab << tab << "{" << endl
|
<< tab << tab << "{" << endl
|
||||||
<< tab << tab << tab << "\"" << ifacename << "\"," << endl
|
<< tab << tab << tab << "\"" << ifacename << "\"," << endl
|
||||||
|
@ -248,7 +248,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
<< tab << " * property() and property(value) to get and set a particular property" << endl
|
<< tab << " * property() and property(value) to get and set a particular property" << endl
|
||||||
<< tab << " */" << endl;
|
<< tab << " */" << endl;
|
||||||
|
|
||||||
// generate the properties code
|
// generate the properties code
|
||||||
for (Xml::Nodes::iterator pi = properties.begin(); pi != properties.end(); ++pi)
|
for (Xml::Nodes::iterator pi = properties.begin(); pi != properties.end(); ++pi)
|
||||||
{
|
{
|
||||||
Xml::Node &property = **pi;
|
Xml::Node &property = **pi;
|
||||||
|
@ -267,7 +267,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
<< tab << " * you will have to implement them in your ObjectAdaptor" << endl
|
<< tab << " * you will have to implement them in your ObjectAdaptor" << endl
|
||||||
<< tab << " */" << endl;
|
<< tab << " */" << endl;
|
||||||
|
|
||||||
// generate the methods code
|
// generate the methods code
|
||||||
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
||||||
{
|
{
|
||||||
Xml::Node &method = **mi;
|
Xml::Node &method = **mi;
|
||||||
|
@ -342,7 +342,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
body << ", ";
|
body << ", ";
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the method 'out' variables if multibe 'out' values exist
|
// generate the method 'out' variables if multibe 'out' values exist
|
||||||
if (args_out.size() > 1)
|
if (args_out.size() > 1)
|
||||||
{
|
{
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
|
@ -389,7 +389,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
<< tab << "/* signal emitters for this interface" << endl
|
<< tab << "/* signal emitters for this interface" << endl
|
||||||
<< tab << " */" << endl;
|
<< tab << " */" << endl;
|
||||||
|
|
||||||
// generate the signals code
|
// generate the signals code
|
||||||
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
||||||
{
|
{
|
||||||
Xml::Node &signal = **si;
|
Xml::Node &signal = **si;
|
||||||
|
@ -397,7 +397,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
|
|
||||||
body << tab << "void " << signal.get("name") << "(";
|
body << tab << "void " << signal.get("name") << "(";
|
||||||
|
|
||||||
// generate the signal arguments
|
// generate the signal arguments
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
for (Xml::Nodes::iterator a = args.begin(); a != args.end(); ++a, ++i)
|
for (Xml::Nodes::iterator a = args.begin(); a != args.end(); ++a, ++i)
|
||||||
{
|
{
|
||||||
|
@ -476,7 +476,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
<< tab << "/* unmarshalers (to unpack the DBus message before calling the actual interface method)" << endl
|
<< tab << "/* unmarshalers (to unpack the DBus message before calling the actual interface method)" << endl
|
||||||
<< tab << " */" << endl;
|
<< tab << " */" << endl;
|
||||||
|
|
||||||
// generate the unmarshalers
|
// generate the unmarshalers
|
||||||
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
||||||
{
|
{
|
||||||
Xml::Node &method = **mi;
|
Xml::Node &method = **mi;
|
||||||
|
@ -489,7 +489,7 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
<< tab << tab << "::DBus::MessageIter ri = call.reader();" << endl
|
<< tab << tab << "::DBus::MessageIter ri = call.reader();" << endl
|
||||||
<< endl;
|
<< endl;
|
||||||
|
|
||||||
// generate the 'in' variables
|
// generate the 'in' variables
|
||||||
unsigned int i = 1;
|
unsigned int i = 1;
|
||||||
for (Xml::Nodes::iterator ai = args_in.begin(); ai != args_in.end(); ++ai, ++i)
|
for (Xml::Nodes::iterator ai = args_in.begin(); ai != args_in.end(); ++ai, ++i)
|
||||||
{
|
{
|
||||||
|
@ -718,5 +718,5 @@ void generate_adaptor(Xml::Document &doc, const char *filename)
|
||||||
file << head.str ();
|
file << head.str ();
|
||||||
file << body.str ();
|
file << body.str ();
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,26 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* D-Bus++ - C++ bindings for D-Bus
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005-2007 Paolo Durante <shackan@gmail.com>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library 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
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
@ -35,7 +58,7 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
Xml::Node &root = *(doc.root);
|
Xml::Node &root = *(doc.root);
|
||||||
Xml::Nodes interfaces = root["interface"];
|
Xml::Nodes interfaces = root["interface"];
|
||||||
|
|
||||||
// iterate over all interface definitions
|
// iterate over all interface definitions
|
||||||
for (Xml::Nodes::iterator i = interfaces.begin(); i != interfaces.end(); ++i)
|
for (Xml::Nodes::iterator i = interfaces.begin(); i != interfaces.end(); ++i)
|
||||||
{
|
{
|
||||||
Xml::Node &iface = **i;
|
Xml::Node &iface = **i;
|
||||||
|
@ -46,10 +69,10 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
ms.insert(ms.end(), methods.begin(), methods.end());
|
ms.insert(ms.end(), methods.begin(), methods.end());
|
||||||
ms.insert(ms.end(), signals.begin(), signals.end());
|
ms.insert(ms.end(), signals.begin(), signals.end());
|
||||||
|
|
||||||
// gets the name of a interface: <interface name="XYZ">
|
// gets the name of a interface: <interface name="XYZ">
|
||||||
string ifacename = iface.get("name");
|
string ifacename = iface.get("name");
|
||||||
|
|
||||||
// these interface names are skipped.
|
// these interface names are skipped.
|
||||||
if (ifacename == "org.freedesktop.DBus.Introspectable"
|
if (ifacename == "org.freedesktop.DBus.Introspectable"
|
||||||
||ifacename == "org.freedesktop.DBus.Properties")
|
||ifacename == "org.freedesktop.DBus.Properties")
|
||||||
{
|
{
|
||||||
|
@ -61,7 +84,7 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
string nspace;
|
string nspace;
|
||||||
unsigned int nspaces = 0;
|
unsigned int nspaces = 0;
|
||||||
|
|
||||||
// prints all the namespaces defined with <interface name="X.Y.Z">
|
// prints all the namespaces defined with <interface name="X.Y.Z">
|
||||||
while (ss.str().find('.', ss.tellg()) != string::npos)
|
while (ss.str().find('.', ss.tellg()) != string::npos)
|
||||||
{
|
{
|
||||||
getline(ss, nspace, '.');
|
getline(ss, nspace, '.');
|
||||||
|
@ -76,12 +99,12 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
|
|
||||||
getline(ss, ifaceclass);
|
getline(ss, ifaceclass);
|
||||||
|
|
||||||
// a "_proxy" is added to class name to distinguish between proxy and adaptor
|
// a "_proxy" is added to class name to distinguish between proxy and adaptor
|
||||||
ifaceclass += "_proxy";
|
ifaceclass += "_proxy";
|
||||||
|
|
||||||
cerr << "generating code for interface " << ifacename << "..." << endl;
|
cerr << "generating code for interface " << ifacename << "..." << endl;
|
||||||
|
|
||||||
// the code from class definiton up to opening of the constructor is generated...
|
// the code from class definiton up to opening of the constructor is generated...
|
||||||
body << "class " << ifaceclass << endl
|
body << "class " << ifaceclass << endl
|
||||||
<< ": public ::DBus::InterfaceProxy" << endl
|
<< ": public ::DBus::InterfaceProxy" << endl
|
||||||
<< "{" << endl
|
<< "{" << endl
|
||||||
|
@ -91,7 +114,7 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
<< tab << ": ::DBus::InterfaceProxy(\"" << ifacename << "\")" << endl
|
<< tab << ": ::DBus::InterfaceProxy(\"" << ifacename << "\")" << endl
|
||||||
<< tab << "{" << endl;
|
<< tab << "{" << endl;
|
||||||
|
|
||||||
// generates code to connect all the signal stubs; this is still inside the constructor
|
// generates code to connect all the signal stubs; this is still inside the constructor
|
||||||
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
||||||
{
|
{
|
||||||
Xml::Node &signal = **si;
|
Xml::Node &signal = **si;
|
||||||
|
@ -103,15 +126,15 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
<< ");" << endl;
|
<< ");" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the constructor ends here
|
// the constructor ends here
|
||||||
body << tab << "}" << endl
|
body << tab << "}" << endl
|
||||||
<< endl;
|
<< endl;
|
||||||
|
|
||||||
// write public block header for properties
|
// write public block header for properties
|
||||||
body << "public:" << endl << endl
|
body << "public:" << endl << endl
|
||||||
<< tab << "/* properties exported by this interface */" << endl;
|
<< tab << "/* properties exported by this interface */" << endl;
|
||||||
|
|
||||||
// this loop generates all properties
|
// this loop generates all properties
|
||||||
for (Xml::Nodes::iterator pi = properties.begin ();
|
for (Xml::Nodes::iterator pi = properties.begin ();
|
||||||
pi != properties.end (); ++pi)
|
pi != properties.end (); ++pi)
|
||||||
{
|
{
|
||||||
|
@ -138,7 +161,7 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
body << tab << tab << tab << "wi << property_name;" << endl;
|
body << tab << tab << tab << "wi << property_name;" << endl;
|
||||||
body << tab << tab << tab
|
body << tab << tab << tab
|
||||||
<< "::DBus::Message ret = this->invoke_method (call);" << endl;
|
<< "::DBus::Message ret = this->invoke_method (call);" << endl;
|
||||||
// TODO: support invoke_method_NoReply for properties
|
// TODO: support invoke_method_NoReply for properties
|
||||||
body << tab << tab << tab
|
body << tab << tab << tab
|
||||||
<< "::DBus::MessageIter ri = ret.reader ();" << endl;
|
<< "::DBus::MessageIter ri = ret.reader ();" << endl;
|
||||||
body << tab << tab << tab << "::DBus::Variant argout; " << endl;
|
body << tab << tab << tab << "::DBus::Variant argout; " << endl;
|
||||||
|
@ -161,20 +184,20 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
body << tab << tab << tab <<"wi << interface_name;" << endl;
|
body << tab << tab << tab <<"wi << interface_name;" << endl;
|
||||||
body << tab << tab << tab <<"wi << property_name;" << endl;
|
body << tab << tab << tab <<"wi << property_name;" << endl;
|
||||||
body << tab << tab << tab <<"wi << value;" << endl;
|
body << tab << tab << tab <<"wi << value;" << endl;
|
||||||
body << tab << tab << tab <<"::DBus::Message ret = this->invoke_method (call);" << endl;
|
body << tab << tab << tab <<"::DBus::Message ret = this->invoke_method (call);" << endl;
|
||||||
// TODO: support invoke_method_noreply for properties
|
// TODO: support invoke_method_noreply for properties
|
||||||
body << tab << tab << "};" << endl;
|
body << tab << tab << "};" << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write public block header for methods
|
// write public block header for methods
|
||||||
body << "public:" << endl
|
body << "public:" << endl
|
||||||
<< endl
|
<< endl
|
||||||
<< tab << "/* methods exported by this interface," << endl
|
<< tab << "/* methods exported by this interface," << endl
|
||||||
<< tab << " * this functions will invoke the corresponding methods on the remote objects" << endl
|
<< tab << " * this functions will invoke the corresponding methods on the remote objects" << endl
|
||||||
<< tab << " */" << endl;
|
<< tab << " */" << endl;
|
||||||
|
|
||||||
// this loop generates all methods
|
// this loop generates all methods
|
||||||
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
for (Xml::Nodes::iterator mi = methods.begin(); mi != methods.end(); ++mi)
|
||||||
{
|
{
|
||||||
Xml::Node &method = **mi;
|
Xml::Node &method = **mi;
|
||||||
|
@ -222,7 +245,7 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
|
|
||||||
body << method.get("name") << "(";
|
body << method.get("name") << "(";
|
||||||
|
|
||||||
// generate all 'in' arguments for a method signature
|
// generate all 'in' arguments for a method signature
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
for (Xml::Nodes::iterator ai = args_in.begin(); ai != args_in.end(); ++ai, ++i)
|
for (Xml::Nodes::iterator ai = args_in.begin(); ai != args_in.end(); ++ai, ++i)
|
||||||
{
|
{
|
||||||
|
@ -450,14 +473,14 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
<< endl;
|
<< endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write public block header for signals
|
// write public block header for signals
|
||||||
body << endl
|
body << endl
|
||||||
<< "public:" << endl
|
<< "public:" << endl
|
||||||
<< endl
|
<< endl
|
||||||
<< tab << "/* signal handlers for this interface" << endl
|
<< tab << "/* signal handlers for this interface" << endl
|
||||||
<< tab << " */" << endl;
|
<< tab << " */" << endl;
|
||||||
|
|
||||||
// this loop generates all signals
|
// this loop generates all signals
|
||||||
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
||||||
{
|
{
|
||||||
Xml::Node &signal = **si;
|
Xml::Node &signal = **si;
|
||||||
|
@ -465,7 +488,7 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
|
|
||||||
body << tab << "virtual void " << signal.get("name") << "(";
|
body << tab << "virtual void " << signal.get("name") << "(";
|
||||||
|
|
||||||
// this loop generates all argument for a signal
|
// this loop generates all argument for a signal
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
for (Xml::Nodes::iterator ai = args.begin(); ai != args.end(); ++ai, ++i)
|
for (Xml::Nodes::iterator ai = args.begin(); ai != args.end(); ++ai, ++i)
|
||||||
{
|
{
|
||||||
|
@ -505,14 +528,14 @@ void generate_proxy(Xml::Document &doc, const char *filename)
|
||||||
body << ") = 0;" << endl;
|
body << ") = 0;" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write private block header for unmarshalers
|
// write private block header for unmarshalers
|
||||||
body << endl
|
body << endl
|
||||||
<< "private:" << endl
|
<< "private:" << endl
|
||||||
<< endl
|
<< endl
|
||||||
<< tab << "/* unmarshalers (to unpack the DBus message before calling the actual signal handler)" << endl
|
<< tab << "/* unmarshalers (to unpack the DBus message before calling the actual signal handler)" << endl
|
||||||
<< tab << " */" << endl;
|
<< tab << " */" << endl;
|
||||||
|
|
||||||
// generate all the unmarshalers
|
// generate all the unmarshalers
|
||||||
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
for (Xml::Nodes::iterator si = signals.begin(); si != signals.end(); ++si)
|
||||||
{
|
{
|
||||||
Xml::Node &signal = **si;
|
Xml::Node &signal = **si;
|
||||||
|
|
|
@ -39,7 +39,7 @@ const char *header = "\n\
|
||||||
|
|
||||||
const char *dbus_includes = "\n\
|
const char *dbus_includes = "\n\
|
||||||
#include <dbus-c++/dbus.h>\n\
|
#include <dbus-c++/dbus.h>\n\
|
||||||
#include <cassert>\n\n\
|
#include <cassert>
|
||||||
\n\
|
\n\
|
||||||
";
|
";
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue