summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--bin/hilex/Makefile1
-rw-r--r--bin/hilex/hilex.154
-rw-r--r--bin/hilex/hilex.c2
-rw-r--r--bin/hilex/hilex.h9
-rw-r--r--bin/hilex/html.c135
5 files changed, 200 insertions, 1 deletions
diff --git a/bin/hilex/Makefile b/bin/hilex/Makefile
index aeaa36d4..5f6c1d80 100644
--- a/bin/hilex/Makefile
+++ b/bin/hilex/Makefile
@@ -3,6 +3,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wpedantic
 OBJS += ansi.o
 OBJS += c.o
 OBJS += hilex.o
+OBJS += html.o
 OBJS += irc.o
 OBJS += mdoc.o
 OBJS += text.o
diff --git a/bin/hilex/hilex.1 b/bin/hilex/hilex.1
index 8b8e0ec7..bc345fe1 100644
--- a/bin/hilex/hilex.1
+++ b/bin/hilex/hilex.1
@@ -65,6 +65,60 @@ input lexer if one cannot be inferred.
 .It Cm ansi
 Output ANSI terminal control sequences.
 .
+.It Cm html
+Output HTML
+.Sy <span>
+elements
+within a
+.Sy <pre>
+element.
+The options are as follows:
+.Bl -tag -width "title=..."
+.It Cm anchor
+Output tags as anchor links.
+.
+.It Cm css Ns = Ns Ar url
+With
+.Cm document ,
+output a
+.Sy <link>
+element for the external stylesheet
+.Ar url .
+If unset,
+output default styles in a
+.Sy <style>
+element.
+.
+.It Cm document
+Output an HTML document containing the
+.Sy <pre>
+element.
+.
+.It Cm inline
+Output inline
+.Sy style
+attributes rather than classes.
+.
+.It Cm tab Ns = Ns Ar n
+With
+.Cm document
+or
+.Cm inline ,
+set the
+.Sy tab-size
+property to
+.Ar n .
+.
+.It Cm title Ns = Ns Ar ...
+With
+.Cm document ,
+set the
+.Sy <title>
+element text.
+The default title is the same as
+.Ar name .
+.El
+.
 .It Cm irc
 Output IRC formatting codes.
 The options are as follows:
diff --git a/bin/hilex/hilex.c b/bin/hilex/hilex.c
index 089f85ee..43130f4d 100644
--- a/bin/hilex/hilex.c
+++ b/bin/hilex/hilex.c
@@ -63,6 +63,7 @@ static const struct {
 } Formatters[] = {
 	{ &FormatANSI, "ansi" },
 	{ &FormatDebug, "debug" },
+	{ &FormatHTML, "html" },
 	{ &FormatIRC, "irc" },
 };
 
@@ -135,6 +136,7 @@ int main(int argc, char *argv[]) {
 			name = path;
 		}
 	}
+	if (!opts[Title]) opts[Title] = name;
 	if (!lexer) lexer = matchLexer(name);
 	if (!lexer && text) lexer = &LexText;
 	if (!lexer) errx(EX_USAGE, "cannot infer lexer for %s", name);
diff --git a/bin/hilex/hilex.h b/bin/hilex/hilex.h
index f0e49a78..870c8a3a 100644
--- a/bin/hilex/hilex.h
+++ b/bin/hilex/hilex.h
@@ -51,7 +51,13 @@ extern const struct Lexer LexMdoc;
 extern const struct Lexer LexText;
 
 #define ENUM_OPTION \
-	X(Monospace, "monospace")
+	X(Anchor, "anchor") \
+	X(CSS, "css") \
+	X(Document, "document") \
+	X(Inline, "inline") \
+	X(Monospace, "monospace") \
+	X(Tab, "tab") \
+	X(Title, "title")
 
 enum Option {
 #define X(option, key) option,
@@ -70,4 +76,5 @@ struct Formatter {
 
 extern const struct Formatter FormatANSI;
 extern const struct Formatter FormatDebug;
+extern const struct Formatter FormatHTML;
 extern const struct Formatter FormatIRC;
diff --git a/bin/hilex/html.c b/bin/hilex/html.c
new file mode 100644
index 00000000..cfa42770
--- /dev/null
+++ b/bin/hilex/html.c
@@ -0,0 +1,135 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "hilex.h"
+
+static void htmlEscape(const char *text) {
+	while (*text) {
+		switch (*text) {
+			break; case '"': text++; printf("&quot;");
+			break; case '&': text++; printf("&amp;");
+			break; case '<': text++; printf("&lt;");
+		}
+		size_t len = strcspn(text, "\"&<");
+		if (len) fwrite(text, len, 1, stdout);
+		text += len;
+	}
+}
+
+static const char *Class[ClassCap] = {
+#define X(class) [class] = #class,
+	ENUM_CLASS
+#undef X
+};
+
+static const char *Style[ClassCap] = {
+	[Keyword]      = "color: dimgray;",
+	[Tag]          = "color: inherit;",
+	[Macro]        = "color: green;",
+	[Comment]      = "color: navy;",
+	[String]       = "color: teal;",
+	[StringFormat] = "color: teal; font-weight: bold;",
+};
+
+static void styleTabSize(const char *tab) {
+	printf("-moz-tab-size: ");
+	htmlEscape(tab);
+	printf("; tab-size: ");
+	htmlEscape(tab);
+	printf(";");
+}
+
+static void htmlHeader(const char *opts[]) {
+	if (!opts[Document]) goto body;
+
+	printf("<!DOCTYPE html>\n<title>");
+	if (opts[Title]) htmlEscape(opts[Title]);
+	printf("</title>\n");
+
+	if (opts[CSS]) {
+		printf("<link rel=\"stylesheet\" href=\"");
+		htmlEscape(opts[CSS]);
+		printf("\">\n");
+	} else if (!opts[Inline]) {
+		printf("<style>\n");
+		if (opts[Tab]) {
+			printf("pre.hi { ");
+			styleTabSize(opts[Tab]);
+			printf(" }\n");
+		}
+		for (enum Class class = 0; class < ClassCap; ++class) {
+			if (!Style[class]) continue;
+			printf(".hi.%s { %s }\n", Class[class], Style[class]);
+		}
+		if (opts[Anchor]) {
+			printf(
+				".hi.%s:target { color: goldenrod; outline: none; }\n",
+				Class[Tag]
+			);
+		}
+		printf("</style>\n");
+	}
+
+body:
+	if (opts[Inline] && opts[Tab]) {
+		printf("<pre class=\"hi\" style=\"");
+		styleTabSize(opts[Tab]);
+		printf("\">");
+	} else {
+		printf("<pre class=\"hi\">");
+	}
+}
+
+static void htmlFooter(const char *opts[]) {
+	printf("</pre>");
+	if (opts[Document]) printf("\n");
+}
+
+static void htmlAnchor(const char *opts[], const char *text) {
+	if (opts[Inline]) {
+		printf("<a style=\"%s\" id=\"", Style[Tag]);
+	} else {
+		printf("<a class=\"hi %s\" id=\"", Class[Tag]);
+	}
+	htmlEscape(text);
+	printf("\" href=\"#");
+	htmlEscape(text);
+	printf("\">");
+	htmlEscape(text);
+	printf("</a>");
+}
+
+static void htmlFormat(const char *opts[], enum Class class, const char *text) {
+	if (opts[Anchor] && class == Tag) {
+		htmlAnchor(opts, text);
+	} else if (class == Normal) {
+		htmlEscape(text);
+	} else {
+		if (opts[Inline]) {
+			printf("<span style=\"%s\">", Style[class] ? Style[class] : "");
+		} else {
+			printf("<span class=\"hi %s\">", Class[class]);
+		}
+		htmlEscape(text);
+		printf("</span>");
+	}
+}
+
+const struct Formatter FormatHTML = { htmlHeader, htmlFormat, htmlFooter };