I never really used it much - generating C code with Python print statements turned out to look uglier than I thought it would, and most of my use cases were better handled with X macros (https://en.wikipedia.org/wiki/X_Macro) or by generating a separate file and #include'ing it. But your description reminded me of it; you might like the idea.
In retrospect, we could have done worse, like use C++ :-).
One of the things it did was to generate the function prototypes so that a typical .h file would look like this:
#!from base_cc import *
#""// Clase: ${project_part_name}
#""// Proyecto: ${project_name}
#!import time
#""// Fecha: ${time.strftime("%A, %d de %B de %Y, %H:%M:%S %Z", time.localtime(time.time()))}
#""${cc_copyright_notice("GPL", "2001, 2002, 2003, 2007, 2010")}
#""#ifndef _H_${project_part_name}
#""#define _H_${project_part_name}
#""${cc_function_includes_for_class()}
#""${cc_variable_global_declarations_for_class()}
#""class ${project_part_name}
{
#""${cc_variable_declarations_for_class()}
#""${cc_function_protos_for_class()}
};
#""${cc_operator_protos_for_class()}
#endif
The .cc file was similar, calling Python functions to get the function definitions from separate files: I built one directory for each class, where I would put each big function or several related small functions in its own file, and they would be found and combined automatically.For each class, there was a vars.cc file which contained member variables, with their declaration line, initialization and destruction in consecutive lines: the declaration went to the .h file, the initialization to the constructor, and the destruction to the destructor. This way I avoided forgetting to release something and had a good overview of all variables and their life cycles.
Here is an example of generating constant names from a Python list:
#!for token_id in lexer_table:
#" " case TOKEN_ID_${token_id[1]}:
#' ' cerr << "\n --- [" << index_of_current_character
#" " << "] TOKEN_ID_${token_id[1]} '" << buffer << "'"
#' ' << endl << flush;
#' ' break;
The same idea was also used in the Bison/Yacc grammar file: #!%token <markup> T_QMATH_DIRECTIVE
%token <markup> T_DIRECTIVE_DEFINITION
%token <markup> T_DIRECTIVE_CONTEXT
for d in qmath_directives:
#" "%token <markup> T_DIRECTIVE_${d.upper()}
I've used it also for other languages like Java where it cuts down the boilerplate. For instance, the package names are automatically generated from the file path.