mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-05-30 15:42:25 +00:00
✨ Support named style closing tags
This commit is contained in:
@@ -308,6 +308,7 @@ match logger.file_runtime_state() {
|
|||||||
- `TextFormatter` / `TextFormatterConfig` 提供 `style_markup = disabled | builtin | full`, 可决定是否解析 style markup 以及是否启用 custom style tag
|
- `TextFormatter` / `TextFormatterConfig` 提供 `style_markup = disabled | builtin | full`, 可决定是否解析 style markup 以及是否启用 custom style tag
|
||||||
- `target_style_markup` 与 `fields_style_markup` 可独立控制 `target` 和 `fields` 是否解析 style markup
|
- `target_style_markup` 与 `fields_style_markup` 可独立控制 `target` 和 `fields` 是否解析 style markup
|
||||||
- `message` 支持轻量 inline style tag: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
- `message` 支持轻量 inline style tag: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
||||||
|
- 闭合同时支持简写 `</>` 与具名闭合 `</red>`, `</danger>`, `</b>`
|
||||||
- 内置语义标签包括: `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, `<muted>`
|
- 内置语义标签包括: `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, `<muted>`
|
||||||
- 运行期样式标签 API: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
- 运行期样式标签 API: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
||||||
- 样式标签优先级: formatter 局部 `style_tags` > 全局 style tag registry > 内置标签
|
- 样式标签优先级: formatter 局部 `style_tags` > 全局 style tag registry > 内置标签
|
||||||
|
|||||||
@@ -107,6 +107,30 @@ test "text formatter supports nested inline tags" {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "text formatter supports named closing tags" {
|
||||||
|
let rec = record(Level::Info, "<red>boom</red>")
|
||||||
|
inspect(
|
||||||
|
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)),
|
||||||
|
content="\u{001b}[31mboom\u{001b}[0m",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "text formatter supports mixed short and named closing tags" {
|
||||||
|
let rec = record(Level::Info, "<red><b>fatal</b></red>")
|
||||||
|
inspect(
|
||||||
|
format_text(rec, formatter=text_formatter(show_level=false, show_target=false, color_mode=ColorMode::Always)),
|
||||||
|
content="\u{001b}[31;1mfatal\u{001b}[0m",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "text formatter keeps unmatched named closing tags as plain text" {
|
||||||
|
let rec = record(Level::Info, "boom</red>")
|
||||||
|
inspect(
|
||||||
|
format_text(rec, formatter=text_formatter(show_level=false, show_target=false)),
|
||||||
|
content="boom</red>",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
test "text formatter supports hex inline colors" {
|
test "text formatter supports hex inline colors" {
|
||||||
let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>")
|
let rec = record(Level::Info, "<#ff0000>hot</> <bg:#010203>bg</>")
|
||||||
inspect(
|
inspect(
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ test {
|
|||||||
- `style_markup` / `style_markup`: `disabled`, `builtin`, `full`
|
- `style_markup` / `style_markup`: `disabled`, `builtin`, `full`
|
||||||
- `target_style_markup` / `target_style_markup`, `fields_style_markup` / `fields_style_markup`: `disabled`, `builtin`, `full`
|
- `target_style_markup` / `target_style_markup`, `fields_style_markup` / `fields_style_markup`: `disabled`, `builtin`, `full`
|
||||||
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
- inline style tags / inline 样式标签: `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, `<bg:#202020>...</>`
|
||||||
|
- closing tags / 闭合标签: `</>` 以及具名闭合 `</red>`, `</danger>`, `</b>`
|
||||||
- builtin semantic tags / 内置语义标签: `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, `<muted>`
|
- builtin semantic tags / 内置语义标签: `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, `<muted>`
|
||||||
- runtime style tags / 运行期样式标签: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
- runtime style tags / 运行期样式标签: `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, `define_alias(...)`
|
||||||
- style tag priority / 标签优先级: formatter local `style_tags` > global style tag registry > builtin tags
|
- style tag priority / 标签优先级: formatter local `style_tags` > global style tag registry > builtin tags
|
||||||
|
|||||||
+47
-3
@@ -160,6 +160,11 @@ priv struct StyledSegment {
|
|||||||
style : InlineStyle
|
style : InlineStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
priv struct StyleFrame {
|
||||||
|
tag : String
|
||||||
|
style : InlineStyle
|
||||||
|
}
|
||||||
|
|
||||||
fn code_unit(ch : Char) -> Int {
|
fn code_unit(ch : Char) -> Int {
|
||||||
ch.to_int()
|
ch.to_int()
|
||||||
}
|
}
|
||||||
@@ -529,12 +534,41 @@ fn push_plain_segment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pop_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
|
||||||
|
let normalized = normalize_style_tag_name(tag)
|
||||||
|
if stack.length() <= 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i = stack.length() - 1; i > 0; i = i - 1 {
|
||||||
|
if stack[i].tag == normalized {
|
||||||
|
while stack.length() - 1 >= i {
|
||||||
|
ignore(stack.pop())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_named_style_frame(stack : Array[StyleFrame], tag : String) -> Bool {
|
||||||
|
let normalized = normalize_style_tag_name(tag)
|
||||||
|
if stack.length() <= 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i = stack.length() - 1; i > 0; i = i - 1 {
|
||||||
|
if stack[i].tag == normalized {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[StyledSegment] {
|
fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[StyledSegment] {
|
||||||
let segments : Array[StyledSegment] = []
|
let segments : Array[StyledSegment] = []
|
||||||
let buffer = StringBuilder::new()
|
let buffer = StringBuilder::new()
|
||||||
let stack : Array[InlineStyle] = [default_inline_style()]
|
let stack : Array[StyleFrame] = [{ tag: "", style: default_inline_style() }]
|
||||||
let chars = input.to_array()
|
let chars = input.to_array()
|
||||||
let current_style = fn() { stack[stack.length() - 1] }
|
let current_style = fn() { stack[stack.length() - 1].style }
|
||||||
let flush = fn() { push_plain_segment(segments, buffer, current_style()) }
|
let flush = fn() { push_plain_segment(segments, buffer, current_style()) }
|
||||||
let append_raw = fn(start : Int, finish : Int) {
|
let append_raw = fn(start : Int, finish : Int) {
|
||||||
for i = start; i < finish; i = i + 1 {
|
for i = start; i < finish; i = i + 1 {
|
||||||
@@ -569,10 +603,20 @@ fn parse_inline_markup(input : String, formatter : TextFormatter) -> Array[Style
|
|||||||
}
|
}
|
||||||
continue end + 1
|
continue end + 1
|
||||||
}
|
}
|
||||||
|
if tag.has_prefix("/") {
|
||||||
|
let close_tag = tag[1:].to_owned()
|
||||||
|
if close_tag != "" && has_named_style_frame(stack, close_tag) {
|
||||||
|
flush()
|
||||||
|
ignore(pop_named_style_frame(stack, close_tag))
|
||||||
|
} else {
|
||||||
|
append_raw(i, end + 1)
|
||||||
|
}
|
||||||
|
continue end + 1
|
||||||
|
}
|
||||||
match apply_inline_tag(current_style(), tag, formatter) {
|
match apply_inline_tag(current_style(), tag, formatter) {
|
||||||
Some(next_style) => {
|
Some(next_style) => {
|
||||||
flush()
|
flush()
|
||||||
stack.push(next_style)
|
stack.push({ tag: normalize_style_tag_name(tag), style: next_style })
|
||||||
}
|
}
|
||||||
None => append_raw(i, end + 1)
|
None => append_raw(i, end + 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ match logger.file_runtime_state() {
|
|||||||
- `TextFormatter` and `TextFormatterConfig` also include `style_markup = disabled | builtin | full` so callers can choose whether style markup is parsed and whether custom tags are active.
|
- `TextFormatter` and `TextFormatterConfig` also include `style_markup = disabled | builtin | full` so callers can choose whether style markup is parsed and whether custom tags are active.
|
||||||
- `target_style_markup` and `fields_style_markup` independently control whether `target` and `fields` are parsed for style markup.
|
- `target_style_markup` and `fields_style_markup` independently control whether `target` and `fields` are parsed for style markup.
|
||||||
- `message` also supports lightweight inline style tags such as `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, and `<bg:#202020>...</>`.
|
- `message` also supports lightweight inline style tags such as `<red>...</>`, `<b>...</>`, `<#ff0000>...</>`, and `<bg:#202020>...</>`.
|
||||||
|
- Closing tags now support both the short form `</>` and named closing tags such as `</red>`, `</danger>`, and `</b>`.
|
||||||
- Builtin semantic tags now include `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, and `<muted>`.
|
- Builtin semantic tags now include `<accent>`, `<info>`, `<success>`, `<warning>`, `<danger>`, and `<muted>`.
|
||||||
- Runtime style-tag APIs now include `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, and `define_alias(...)`.
|
- Runtime style-tag APIs now include `TextStyle`, `StyleTagRegistry`, `style_tag_registry()`, `default_style_tag_registry()`, `set_tag(...)`, and `define_alias(...)`.
|
||||||
- Style-tag lookup priority is formatter-local `style_tags` > global style tag registry > builtin tags.
|
- Style-tag lookup priority is formatter-local `style_tags` > global style tag registry > builtin tags.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ version 0.4.0
|
|||||||
- feat: support `sink.text_formatter.style_markup` in JSON config parsing and serialization
|
- feat: support `sink.text_formatter.style_markup` in JSON config parsing and serialization
|
||||||
- feat: add builtin semantic style tags such as `accent`, `info`, `success`, `warning`, `danger`, and `muted`
|
- feat: add builtin semantic style tags such as `accent`, `info`, `success`, `warning`, `danger`, and `muted`
|
||||||
- feat: add independent `target_style_markup` and `fields_style_markup` controls for `TextFormatter` and `TextFormatterConfig`
|
- feat: add independent `target_style_markup` and `fields_style_markup` controls for `TextFormatter` and `TextFormatterConfig`
|
||||||
|
- feat: support named closing tags like `</red>` alongside the existing short close `</>`
|
||||||
|
|
||||||
### Test
|
### Test
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ version 0.4.0
|
|||||||
- test: cover disabled markup mode, builtin-only mode, and config-driven `style_markup` behavior
|
- test: cover disabled markup mode, builtin-only mode, and config-driven `style_markup` behavior
|
||||||
- test: cover builtin semantic tag rendering and confirm user overrides still take precedence
|
- test: cover builtin semantic tag rendering and confirm user overrides still take precedence
|
||||||
- test: cover target and field markup rendering, plus config roundtrip for the new formatter markup scopes
|
- test: cover target and field markup rendering, plus config roundtrip for the new formatter markup scopes
|
||||||
|
- test: cover named closing tags, mixed short/named closing, and unmatched named-close fallback behavior
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ version 0.4.0
|
|||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
- `Auto` currently uses a conservative rule: if `NO_COLOR` exists, ANSI is disabled; otherwise ANSI is enabled
|
- `Auto` currently uses a conservative rule: if `NO_COLOR` exists, ANSI is disabled; otherwise ANSI is enabled
|
||||||
- inline style markup currently supports short close `</>` only
|
- inline style markup supports both short close `</>` and named closing tags like `</red>`
|
||||||
- unknown or invalid inline tags currently fall back to plain text and do not raise formatter errors
|
- unknown or invalid inline tags currently fall back to plain text and do not raise formatter errors
|
||||||
- formatter-local tag lookup currently takes precedence over global tag lookup, and global lookup takes precedence over builtin tags
|
- formatter-local tag lookup currently takes precedence over global tag lookup, and global lookup takes precedence over builtin tags
|
||||||
- JSON config currently supports concrete style objects only; alias-style declarations remain runtime-only
|
- JSON config currently supports concrete style objects only; alias-style declarations remain runtime-only
|
||||||
|
|||||||
Reference in New Issue
Block a user