diff --git a/htmlcc/__main__.py b/htmlcc/__main__.py
index 05b6179..faa62bb 100644
--- a/htmlcc/__main__.py
+++ b/htmlcc/__main__.py
@@ -16,13 +16,14 @@
from .emitter import Emitter, known_emitters
from .emitter.cgit import CgitEmitter
+from .emitter.stdout import StdoutEmitter
from .parser import Parser
import argparse
# Initialise ArgumentParser
arg_parser = argparse.ArgumentParser(prog='htmlcc', description='Statically compiled HTML templates for C')
-arg_parser.add_argument('--emitter', default='cgit', choices=list(known_emitters.keys()))
+arg_parser.add_argument('--emitter', default='stdout', choices=list(known_emitters.keys()))
arg_parser.add_argument('filename', nargs='+')
args = arg_parser.parse_args()
diff --git a/htmlcc/emitter/stdout.py b/htmlcc/emitter/stdout.py
new file mode 100644
index 0000000..e566896
--- /dev/null
+++ b/htmlcc/emitter/stdout.py
@@ -0,0 +1,86 @@
+# htmlcc - Statically compiled HTML templates for C
+# Copyright (C) 2025 Lee Yingtong Li
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from . import Emitter, register_emitter
+
+class StdoutEmitter(Emitter):
+ """Emitter which prints templates to stdout (useful for testing/demonstration)"""
+
+ def emit_preamble(self) -> None:
+ self.emit(r'''#include
+#include
+#include
+
+void output_variable_encoded(char *variable, bool encode_quotes) {
+ // First compute size of buffer required
+ size_t extra_chars = 0;
+ char *ptr = variable;
+ for (; *ptr != '\0'; ptr++) {
+ if (*ptr == '&') { extra_chars += 4; }
+ if (*ptr == '<') { extra_chars += 3; }
+ if (*ptr == '"' && encode_quotes) { extra_chars += 5; }
+ if (*ptr == '\'' && encode_quotes) { extra_chars += 4; }
+ }
+
+ size_t len = ptr - variable;
+ char *buf = malloc(len + extra_chars + 1);
+
+ // Replace characters
+ char *c_inp = variable;
+ char *c_out = buf;
+ for (; *c_inp != '\0'; c_inp++) {
+ if (*c_inp == '&') {
+ *c_out++ = '&'; *c_out++ = 'a'; *c_out++ = 'm'; *c_out++ = 'p'; *c_out++ = ';';
+ } else if (*c_inp == '<') {
+ *c_out++ = '&'; *c_out++ = 'l'; *c_out++ = 't'; *c_out++ = ';';
+ } else if (*c_inp == '"' && encode_quotes) {
+ *c_out++ = '&'; *c_out++ = 'q'; *c_out++ = 'u'; *c_out++ = 'o'; *c_out++ = 't'; *c_out++ = ';';
+ } else if (*c_inp == '\'' && encode_quotes) {
+ *c_out++ = '&'; *c_out++ = '#'; *c_out++ = '3'; *c_out++ = '9'; *c_out++ = ';';
+ } else {
+ *c_out++ = *c_inp;
+ }
+ }
+ *c_out = '\0';
+
+ printf("%s", buf);
+ free(buf);
+}
+
+void output_variable_as_attr(char *variable) {
+ output_variable_encoded(variable, true);
+}
+
+void output_variable_as_text(char *variable) {
+ output_variable_encoded(variable, false);
+}''')
+
+ def output_literal_string(self, literal_string: str) -> None:
+ self.emit(f'printf("%s", {self.escape_cstr(literal_string)});')
+
+ def output_variable_as_attr(self, variable: str) -> None:
+ self.emit(f'output_variable_as_attr({variable});')
+
+ def output_variable_as_text(self, variable: str) -> None:
+ self.emit(f'output_variable_as_text({variable});')
+
+ def output_variable_formatted(self, format_string: str, variable: str) -> None:
+ self.emit(f'printf({self.escape_cstr(format_string)}, {variable});')
+
+ def output_variable_urlencoded(self, variable: str) -> None:
+ raise NotImplementedError('urlencode filter is not implemented for the stdout emitter')
+
+register_emitter('stdout', StdoutEmitter)