ecpp(7)
β¨ ecpp β the tntnet(8) template language, bestie edition β¨
sooo this is ecpp(7) from the Tntnet users guide and it's about to be your whole personality, no cap. π
πΈ NAME πΈ
ecpp β the template language for tntnet(8). that's it, that's the name, slay. π
π¦ DESCRIPTION π¦
okay so ecpp is the template language the tntnet system uses to generate dynamic content. literally its whole job. π
A template is just normal content (normally html data) that's been enriched with special tags β and those tags trigger some special handling. that's the vibe. that's the whole vibe. β¨
Here's the part that lowkey blows my mind: one ecpp file gets compiled into a C++ class. one file β one class. The C++ class lands in the namespace component, and an ecpp file that's been compiled into a C++ class is called a component. And the name of the class? it's just the basename of the file. iconic behavior honestly. π
π«Ά request, reply, qparam π«Ά
Every single component has 3 parameters: request, reply and qparam. three besties. let's meet them. π
requestβ holds the info about the client request, like http headers and the url, plus additional parameters specified in the config filetntnet.xml(7). Its type istnt::HttpRequest. π¨replyβ receives the answer from the component. this is where the component can set additional http headers, set cookies, and β most important β generate output. The most important methods here arereply.out()andreply.sout(). Both return astd::ostream, which receives the output of the component. π€
here's the tea tho:reply.sout()has a filter installed on it that translates some characters β the ones with special meanings in html β into the corresponding html entities. the characters are<,>,"and'. this is sooo useful for printing values from variables straight into the html code. π§
qparamβ holds the query parameters parsed from GET- or POST-parameters, or received from other components. Its type istnt::QueryParams. Normally you use a<%args>block to specify the parameters, but there are special cases where it's useful to access these directly. π
π component addressing π
Each component has a unique name. The name is composed from:
- the class name
- the character
'@' - the name of the shared library it's located in
Components can also have internal sub components. The name of the internal sub component is appended to the class name, separated by a dot (.). so like, dot notation but make it components. π
π special rule for line feeds after a </%something>-tag π
okay this one is weird but important: a line feed immediately after a closing tag for all <%something> blocks is ignored. π
so blocks that follow immediately one after another do not generate white space in output β which is often super undesirable. hence the rule. the more you know. β
π errorhandling π
Error handling is done by exception. Tntnet catches all exceptions thrown by components and handles them properly.
- Exceptions must be derived from
std::exception. - Exceptions derived from
tnt::HttpErrorare handled separately. They carry a http return code, which is sent to the client. - Other exceptions derived from
std::exceptionresult in a http error code500 (Internal Server Error).
so fr fr, derive from std::exception or the whole thing falls apart bestie. π
β¨ more coming, don't go anywhere β¨
β¨ TAGS β¨
okay bestie, this is the section where ecpp shows off. sit down, hydrate, we're going through every single tag π
<$ expr $>β prints the expressionexprto the output stream. The characters<,>,,,"and'all have special meanings in html, so they get translated to the corresponding html entities. no raw chaos escaping into your html, we're being responsible today β¨<$$ expr $>β prints the expressionexprto the output stream, but this time without translating the characters that have special meaning in html into html entities. raw, unfiltered, out here living its truth π<? cond ? expr ?>β conditional output. Prints the expressionexprto the output stream ifcondevaluates to true. Characters with special meaning in html get translated to the corresponding html entities. π<?? cond ? expr ?>β conditional output too. Printsexprto the output stream ifcondevaluates to true, and the characters with special meaning in html are not translated to the corresponding html entities. π< component [ arguments ] >β calls the specified component. The output of the component gets printed into the output stream. If the component name does not start with a letter, the ecpp compiler treats it as an expression, which returns the name of the component. And if that expression has spaces in it, you must surround it in brackets. lowkey strict but fair.
The arguments part specifies the parameters the component will receive. Arguments are name-value pairs separated by =. They get put in the qparam parameter of the component and are normally declared in the <%args> block. Values can be specified in 3 forms:
- a plain word without spaces
- a string enclosed in quotation marks
- an expression enclosed in brackets
A single plain word in the argument list is treated as a variable of type cxxtools::QueryParams and a copy is passed to the component. Other parameters get added to this copy. Wanna pass all the parameters of the current component? Just put the variable qparam as a plain word in the argument list. ez π
</component>β the closing tag for a component call. When components are called, this closing tag might occur later. The code in the<%close>block gets placed here.<{...}>β C++ inline processing block. The code in this block is copied into the C++ class unchanged. and listen: a linefeed after the closing tag is not ignored. noted. π<#...#>β comment block. Everything in this block is ignored. nothing to see here, moving on π«Ά<%application [ scope="component|page|shared|global" ] >...</%application>β variables defined here have the lifetime of the application. Application scope is automatically locked. π<%args>...</%args>β defines the GET or POST parameters received by the component. Each argument has a name and optionally a default value. The default value is delimited from the name by=. A single argument definition is followed by a semicolon (;). In the component, a variable with the same name, of typestd::string, is defined, and it receives the value. slay. An argument name can be prefixed by a type definition. The ecpp compiler generates code that tries to convert the value with the cxxtools deserialization operator. If the argument can't be converted, the default value is set. no crash, just vibes π¦ Argument names can be postfixed by empty square brackets. This defines astd::vectorwith the specified type β orstd::stringif no type is specified. This way, multiple values with the same name can be received. If a type is specified, each value gets converted to the target type. π<%attr>...</%attr>β components may define attributes, which can be queried from other components. These values are strings and are defined by specifying a name followed by=and the string value. No type is allowed here. periodt. Another component can fetch a reference to the component usingfetchComp(name).fetchCompis a member of the base classtnt::EcppComponentof components built with ecpp. The component then has a member methodgetAttribute(name), which returns the attribute, or an empty string when it's not found. A different default string can be passed as the second parameter togetAttribute.
Example: A content component specifies a title:
<%attr>
title = "my title";
</%attr>
A component webmain wants to add a title depending on a content component:
<head>
<title>
<$ fetchComp("theContent").getAttribute("title", "default title") $>
</title>
...
To separate the C++ code from the html, the actual doing can be moved to a C++ section. The component can then also be called later to generate the content:
<%cpp>
tnt::Component theContent = fetchComp("theContent");
std::string title = theContent.getAttribute("title", "default title");
</%cpp>
<head>
<title><$ title $></title>
...
<div id="contnent">
<{ theContent(request, reply, qparam); }>
</div>
<%close>...</%close>β code in these tags is placed into the calling component when a closing tag</component>is found. The<%close>receives the same parameters as the corresponding normal component call.
β οΈ bestie this tag is deprecated and should not be used any more. we're not doing that. π«
<%config>...</%config>β web applications often need some configuration, like database names or login information for the database. These configuration variables can be read from thetntnet.xml. Variable names that end with a semicolon are defined as staticstd::stringvariables and filled fromtntnet.xml. A variable can be prepended by a type β the value fromtntnet.xmlis then converted with astd::istream. You can also specify a default value by appending a=and the value to the variable. cute AND practical.
Example:
<%config>
dburl = "sqlite:db=mydbfile.sqlite";
int maxvalue = 10;
</%config>
tntnet.xml:
<dburl>postgresql:dbname=mydb</dburl>
<%cpp>...</%cpp>β C++ processing block. The code between these tags is copied into the C++ class unchanged. A linefeed after the closing tag is ignored. (again β noted. π€)<%def name>...</%def>β defines an internal sub component with the namename, which can be called like other components. main character energy for a lil sub component β¨<%doc>...</%doc>β comment block. Everything in this block is ignored. A linefeed after the closing tag is ignored.<%get>...</%get>β works like a<%args>block, but it receives only GET parameters.<%include>filename</%include>β the specified file is read and compiled. π₯<%param>...</%param>β defines parameters received from calling components. In contrast to query parameters, these variables can be of any type. The syntax (and the underlying technology) is the same as in scoped variables β see the description about scoped variables to see how to define parameters. The main difference is that a parameter variable has no scope, since the parameter is always local to the component. iykyk π§<%out> expr </%out>β same as<$$ ... $>. Prints the contained C++ expressionexpr.<%post>...</%post>β works like a<%args>block, but it receives only POST parameters.<%pre>...</%pre>β defines C++ code that is placed outside the C++ class and outside the namespace definition. This is a good place to define#includedirectives. π<%request [ scope="component|page|shared|global" ] >...</%request>β define request scope variables. Variables defined here have the lifetime of the request.<%session [ scope="component|page|shared|global" ] >...</%session>β variables defined here have the lifetime of the session. Sessions are identified with cookies. If a<%session>block is defined somewhere in a component, a session cookie is sent to the client. πͺ Sessions are automatically locked. π<%securesession [ scope="component|page|shared|global" ] >...</%securesession>β secure session is just like session, but a secure cookie is used to identify the session. Secure cookies are transferred only over a ssl connection from the browser, and hence the variables are only kept in a ssl secured application. If a variable defined here is used in a non ssl page, the variable values are lost after the current request. π tragic but that's the rule.<%sout> expr </%sout>β same as<$ ... $>. Prints the contained C++ expressionexpr. The characters<,>,,,"and', which have special meanings in html, are translated to the corresponding html entities. guarding the html, respectfully π<%thread [ scope="component|page|shared|global" ] >...</%thread>β variables defined here have the lifetime of the thread. Each thread has its own instance of these variables. Thread scope variables do not need to be locked at all, because they are only valid in the current thread. no lock needed, she's local π
β¨ SCOPED VARIABLES β¨
okay bestie, scoped variables are c++ variables whose lifetime is handled by tntnet. They have a lifetime and a scope. The lifetime is defined by the tag used to declare the variable, and the scope is passed as a parameter to the tag. iykyk.
There are 5 different lifetimes for scoped variables:
requestβ the variable is valid in the current request. The tag is<%request>.applicationβ the variable is valid in the application. The tag is<%application>. The application is specified by the shared library of the top level component.sessionβ the variable is valid for the current session. The tag is<%session>. If at least one session variable is declared in the current request, a session cookie is sent to the client.threadβ the variable is valid in the current thread. The tag is<%thread>.paramβ the variable receives parameters. The tag is<%param>.
And 3 scopes:
componentβ the variable is only valid in the same component. This is the default scope.pageβ the variable is shared between the components in a single ecpp file. You can specify multiple internal sub components in a<%def>block. Variables defined in page scope are shared between these sub components.globalorsharedβ variables are shared between all components. If you define the same variable with shared scope in different components, they must have the same type. This is achieved most easily by defining them in a separate file and including them with a<%include>block. The global and shared are just synonyms.
Variables are automatically locked as needed. If you use session variables, tntnet ensures that all requests of the same session are serialized. If you use application variables, tntnet serializes all requests to the same application scope. Request and thread scope variables do not need to be locked at all, because they are not shared between threads.
Syntax of scoped variables π§
Scoped variables are declared with exactly the same syntax as normal variables in c++ code. They can be of any type and are instantiated when needed. Objects which do not have default constructors need to be specified with proper constructor parameters in brackets or separated by '='. The parameters are only used if the variable need to be instantiated. This means that parameters to e.g. application scope variables are only used once. When the same component is called later in the same or another request, the parameters are not used any more.
Examples π
Specify an application-specific shared variable, which is initialized with 0:
<%application>
unsigned count = 0;
</%application>
Specify a variable with a user defined type, which holds the state of the session:
<%session>
MyClass sessionState;
</%session>
Specify a persistent database connection, which is initialized when first needed and hold for the lifetime of the current thread. This variable may be used in other components:
<%thread scope="shared">
tntdb::Connection conn(dburl);
</%thread>
AUTHOR π
This manual page was written by Tommi MΓ€kitalo β¨tommi@tntnet.orgβ©.
SEE ALSO π₯
mwah π this was the ecpp(7) page from Tntnet, dated 2006-07-23 β stay scoped, bestie π«Άβ¨
π¦ where this page comes from
- debian trixie: libtntnet-dev 3.0-4 Β· LGPL-2.1-or-later
- ubuntu noble: libtntnet-dev 3.0-3.1build1 Β· LGPL-2.1-or-later
- ubuntu questing: libtntnet-dev 3.0-4
- ubuntu resolute: libtntnet-dev 3.0-4build1 Β· LGPL-2.1-or-later
- ubuntu stonking: libtntnet-dev 3.0-4build1 Β· LGPL-2.1-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms