Skip to content

Commit ba8a34d

Browse files
committed
Refactor logging to use go-utils/log package and enhance Go/TSX syntax highlighting
1 parent 0c9672f commit ba8a34d

File tree

8 files changed

+180
-123
lines changed

8 files changed

+180
-123
lines changed

docs/src/components/landing/code-block.tsx

Lines changed: 137 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,119 +3,161 @@
33
import { useEffect, useRef, useState } from "react";
44
import { cn } from "@/lib/cn";
55

6-
// Simple Go syntax highlighter
6+
// HTML-escape a plain text string.
7+
function esc(s: string): string {
8+
return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9+
}
10+
11+
// Tokenize-then-render Go syntax highlighter.
12+
// Pass 1: a single regex with alternation matches tokens in priority order.
13+
// Pass 2: each token maps to an HTML span; unmatched text is plain (escaped).
714
function highlightGo(code: string): string {
8-
// Order matters: do strings first to avoid highlighting keywords inside strings
9-
let result = code;
10-
11-
// Escape HTML first
12-
result = result
13-
.replace(/&/g, "&amp;")
14-
.replace(/</g, "&lt;")
15-
.replace(/>/g, "&gt;");
16-
17-
// Comments (single-line)
18-
result = result.replace(
19-
/(\/\/.*$)/gm,
20-
'<span className="text-fd-muted-foreground/60 italic">$1</span>',
21-
);
15+
const goKeywords = new Set([
16+
"package", "import", "func", "return", "if", "else", "for", "range",
17+
"var", "const", "type", "struct", "interface", "map", "chan", "go",
18+
"defer", "select", "case", "switch", "default", "break", "continue",
19+
"fallthrough", "nil", "true", "false", "err",
20+
]);
21+
const goTypes = new Set([
22+
"string", "int", "int64", "float64", "bool", "error", "byte", "rune", "any",
23+
]);
24+
25+
// Groups: 1=comment, 2=string, 3=backtick-string, 4=word, 5=func-call (UpperWord before '(')
26+
const tokenRe =
27+
/(\/\/.*$)|("(?:[^"\\]|\\.)*")|(`[^`]*`)|(\b[a-zA-Z_]\w*(?:\.\w+)*\b)|\b([A-Z]\w*)\s*(?=\()/gm;
28+
29+
let out = "";
30+
let last = 0;
31+
let m: RegExpExecArray | null;
32+
33+
while ((m = tokenRe.exec(code)) !== null) {
34+
// Append any unmatched text before this token.
35+
if (m.index > last) {
36+
out += esc(code.slice(last, m.index));
37+
}
38+
last = m.index + m[0].length;
39+
40+
if (m[1] != null) {
41+
// Comment
42+
out += `<span class="text-fd-muted-foreground/60 italic">${esc(m[1])}</span>`;
43+
} else if (m[2] != null) {
44+
// Double-quoted string
45+
out += `<span class="text-teal-400">${esc(m[2])}</span>`;
46+
} else if (m[3] != null) {
47+
// Backtick string
48+
out += `<span class="text-teal-400">${esc(m[3])}</span>`;
49+
} else if (m[4] != null) {
50+
const word = m[4];
51+
// Check for "context.Context" style compound types
52+
if (word === "context.Context") {
53+
out += `<span class="text-cyan-400">${esc(word)}</span>`;
54+
} else if (goKeywords.has(word)) {
55+
out += `<span class="text-purple-400 font-medium">${esc(word)}</span>`;
56+
} else if (goTypes.has(word)) {
57+
out += `<span class="text-cyan-400">${esc(word)}</span>`;
58+
} else if (/^[A-Z]/.test(word) && code.slice(last).trimStart().startsWith("(")) {
59+
// Uppercase word followed by '(' — function/method call
60+
out += `<span class="text-blue-400">${esc(word)}</span>`;
61+
} else {
62+
out += esc(word);
63+
}
64+
} else if (m[5] != null) {
65+
out += `<span class="text-blue-400">${esc(m[5])}</span>`;
66+
}
67+
}
2268

23-
// Strings (double-quoted)
24-
result = result.replace(
25-
/("(?:[^"\\]|\\.)*")/g,
26-
'<span className="text-teal-400">$1</span>',
27-
);
69+
// Append remaining text.
70+
if (last < code.length) {
71+
out += esc(code.slice(last));
72+
}
2873

29-
// Backtick strings
30-
result = result.replace(
31-
/(`[^`]*`)/g,
32-
'<span className="text-teal-400">$1</span>',
33-
);
74+
return out;
75+
}
3476

35-
// Keywords
36-
const keywords = [
37-
"package",
38-
"import",
39-
"func",
40-
"return",
41-
"if",
42-
"else",
43-
"for",
44-
"range",
45-
"var",
46-
"const",
47-
"type",
48-
"struct",
49-
"interface",
50-
"map",
51-
"chan",
52-
"go",
53-
"defer",
54-
"select",
55-
"case",
56-
"switch",
57-
"default",
58-
"break",
59-
"continue",
60-
"fallthrough",
61-
"nil",
62-
"true",
63-
"false",
64-
"err",
65-
];
66-
keywords.forEach((kw) => {
67-
const regex = new RegExp(`\\b(${kw})\\b`, "g");
68-
result = result.replace(
69-
regex,
70-
'<span class="text-purple-400 font-medium">$1</span>',
71-
);
72-
});
73-
74-
// Types
75-
const types = [
76-
"string",
77-
"int",
78-
"int64",
79-
"float64",
80-
"bool",
81-
"error",
82-
"byte",
83-
"rune",
84-
"any",
85-
"context\\.Context",
86-
];
87-
types.forEach((t) => {
88-
const regex = new RegExp(`\\b(${t})\\b`, "g");
89-
result = result.replace(regex, '<span class="text-cyan-400">$1</span>');
90-
});
91-
92-
// Function calls
93-
result = result.replace(
94-
/\b([A-Z]\w*)\s*\(/g,
95-
'<span class="text-blue-400">$1</span>(',
96-
);
77+
// Tokenize-then-render TSX/JSX syntax highlighter.
78+
function highlightTSX(code: string): string {
79+
const tsxKeywords = new Set([
80+
"import", "export", "from", "const", "let", "var", "function", "return",
81+
"if", "else", "for", "while", "default", "new", "this", "class",
82+
"extends", "async", "await", "typeof", "instanceof", "null", "undefined",
83+
"true", "false",
84+
]);
85+
86+
// Tokenize: groups in priority order.
87+
// 1=comment, 2=double-string, 3=single-string, 4=backtick-string,
88+
// 5=arrow (=>), 6=JSX tag (<Tag or </Tag), 7=word, 8=curly block {…}
89+
const tokenRe =
90+
/(\/\/.*$)|("(?:[^"\\]|\\.)*")|('(?:[^'\\]|\\.)*')|(`[^`]*`)|(=>)|(<\/?)([\w.]+)|(\b[a-zA-Z_][\w]*\b)|(\{[^}]*\})/gm;
91+
92+
let out = "";
93+
let last = 0;
94+
let m: RegExpExecArray | null;
95+
96+
// Track whether we're inside a JSX tag (between < and >) for prop detection.
97+
while ((m = tokenRe.exec(code)) !== null) {
98+
if (m.index > last) {
99+
out += esc(code.slice(last, m.index));
100+
}
101+
last = m.index + m[0].length;
102+
103+
if (m[1] != null) {
104+
// Comment
105+
out += `<span class="text-fd-muted-foreground/60 italic">${esc(m[1])}</span>`;
106+
} else if (m[2] != null) {
107+
// Double-quoted string
108+
out += `<span class="text-teal-400">${esc(m[2])}</span>`;
109+
} else if (m[3] != null) {
110+
// Single-quoted string
111+
out += `<span class="text-teal-400">${esc(m[3])}</span>`;
112+
} else if (m[4] != null) {
113+
// Backtick string
114+
out += `<span class="text-teal-400">${esc(m[4])}</span>`;
115+
} else if (m[5] != null) {
116+
// Arrow function =>
117+
out += `<span class="text-purple-400">${esc(m[5])}</span>`;
118+
} else if (m[6] != null && m[7] != null) {
119+
// JSX tag: <Tag or </Tag
120+
out += `${esc(m[6])}<span class="text-blue-400">${esc(m[7])}</span>`;
121+
} else if (m[8] != null) {
122+
const word = m[8];
123+
// Check if this word is followed by '=' (JSX prop)
124+
const afterWord = code.slice(last);
125+
if (afterWord.startsWith("=") && !afterWord.startsWith("==")) {
126+
out += `<span class="text-cyan-400">${esc(word)}</span>`;
127+
} else if (tsxKeywords.has(word)) {
128+
out += `<span class="text-purple-400 font-medium">${esc(word)}</span>`;
129+
} else {
130+
out += esc(word);
131+
}
132+
} else if (m[9] != null) {
133+
// Curly block {…} — highlight inner content
134+
const block = m[9];
135+
const inner = block.slice(1, -1);
136+
out += `{<span class="text-amber-300">${esc(inner)}</span>}`;
137+
}
138+
}
97139

98-
// Method calls (after dot)
99-
result = result.replace(
100-
/\.([A-Z]\w*)\s*\(/g,
101-
'.<span class="text-blue-400">$1</span>(',
102-
);
140+
if (last < code.length) {
141+
out += esc(code.slice(last));
142+
}
103143

104-
return result;
144+
return out;
105145
}
106146

107147
interface CodeBlockProps {
108148
code: string;
109149
filename?: string;
110150
className?: string;
111151
showLineNumbers?: boolean;
152+
language?: "go" | "tsx";
112153
}
113154

114155
export function CodeBlock({
115156
code,
116157
filename,
117158
className,
118159
showLineNumbers = true,
160+
language = "go",
119161
}: CodeBlockProps) {
120162
const [copied, setCopied] = useState(false);
121163
const codeRef = useRef<HTMLPreElement>(null);
@@ -132,8 +174,9 @@ export function CodeBlock({
132174
setCopied(true);
133175
};
134176

177+
const highlighter = language === "tsx" ? highlightTSX : highlightGo;
135178
const lines = code.split("\n");
136-
const highlighted = lines.map((line) => highlightGo(line));
179+
const highlighted = lines.map((line) => highlighter(line));
137180

138181
return (
139182
<div

extension/extension.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ func (e *Extension) Init(fapp forge.App) error {
8787
}
8888
e.store = store.New(groveDB)
8989
}
90+
if e.store == nil {
91+
if db, err := vessel.Inject[*grove.DB](fapp.Container()); err == nil {
92+
// Auto-discover default grove.DB from container (matches authsome/cortex pattern).
93+
e.store = store.New(db)
94+
e.Logger().Info("trove: auto-discovered grove.DB from container",
95+
forge.F("driver", db.Driver().Name()),
96+
)
97+
}
98+
}
9099

91100
// Run migrations.
92101
if e.store != nil && !e.config.DisableMigrate {

extension/handler/handler.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package handler
33

44
import (
55
"encoding/json"
6-
"log/slog"
76
"net/http"
87

8+
log "github.com/xraph/go-utils/log"
9+
910
"github.com/xraph/trove"
1011
"github.com/xraph/trove/extension/store"
1112
)
@@ -14,14 +15,14 @@ import (
1415
type Handler struct {
1516
trove *trove.Trove
1617
store *store.Store
17-
logger *slog.Logger
18+
logger log.Logger
1819
mux *http.ServeMux
1920
}
2021

2122
// New creates a new Handler.
22-
func New(t *trove.Trove, s *store.Store, logger *slog.Logger) *Handler {
23+
func New(t *trove.Trove, s *store.Store, logger log.Logger) *Handler {
2324
if logger == nil {
24-
logger = slog.Default()
25+
logger = log.NewNoopLogger()
2526
}
2627
h := &Handler{
2728
trove: t,

extension/hooks/chronicle.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ package hooks
44
import (
55
"context"
66
"fmt"
7-
"log/slog"
7+
8+
log "github.com/xraph/go-utils/log"
89

910
"github.com/xraph/chronicle"
1011
"github.com/xraph/forge"
@@ -14,12 +15,12 @@ import (
1415
// ChronicleHook emits audit events for storage operations via Chronicle.
1516
type ChronicleHook struct {
1617
emitter chronicle.Emitter
17-
logger *slog.Logger
18+
logger log.Logger
1819
}
1920

2021
// NewChronicleHook creates a Chronicle hook, auto-discovering the emitter from DI.
2122
// Returns nil if Chronicle is not available.
22-
func NewChronicleHook(fapp forge.App, logger *slog.Logger) *ChronicleHook {
23+
func NewChronicleHook(fapp forge.App, logger log.Logger) *ChronicleHook {
2324
emitter, err := vessel.Inject[chronicle.Emitter](fapp.Container())
2425
if err != nil {
2526
if logger != nil {
@@ -129,8 +130,8 @@ func (h *ChronicleHook) record(ctx context.Context, action, resource, resourceID
129130
}
130131
if err := builder.Record(); err != nil && h.logger != nil {
131132
h.logger.Warn("failed to record chronicle event",
132-
"action", action,
133-
"error", err,
133+
log.String("action", action),
134+
log.Any("error", err),
134135
)
135136
}
136137
}

0 commit comments

Comments
 (0)