Enable anonymous downloads on the host and harden Strings.fmt
Host (applied with root@192.168.88.30) - [service] REQUIRE_SIGNIN_VIEW = false in /etc/gitea/app.ini (backup written alongside), Gitea restarted. Verified anonymously: repo page 200, API 200, and a release asset downloads at full size (50.8 MB AppImage). Localization safety (Open Polish) - Strings.placeholder_count() counts conversions and ignores %% escapes. - Strings.fmt() validates the count and fails safe (warns, returns the template) instead of raising an engine error when a key's placeholders drift. - Strings.validate() lints the table for a trailing %, unsupported conversions, and mixed positional/sequential placeholders. - Tests cover the counter, the fail-safe path, a clean table, and a formatting pass over every placeholder key with type-correct arguments. Docs - TODO: distribution, release-pipeline, and open-polish sections are complete and moved to DONE; the v0.2.4-v0.2.8 backfill is closed as superseded. - README/MEMORY updated. Version 0.2.14.
This commit is contained in:
1 parent
a2a0ded527
commit
74f14014b2
7 files changed
+182
-44
No files matched your search
@@ -34,12 +34,69 @@ static func lookup(key: String) -> String:
|
||||
_ensure_loaded()
|
||||
return TABLE.get(key, key)
|
||||
|
||||
## Number of `%` conversion placeholders in a template, ignoring `%%` escapes.
|
||||
static func placeholder_count(template: String) -> int:
|
||||
var count := 0
|
||||
var i := 0
|
||||
while i < template.length():
|
||||
if template[i] == "%":
|
||||
if i + 1 < template.length() and template[i + 1] == "%":
|
||||
i += 2
|
||||
continue
|
||||
count += 1
|
||||
i += 1
|
||||
return count
|
||||
|
||||
## Format a string by key with positional arguments.
|
||||
##
|
||||
## GDScript's `%` binds arguments by order, so a key whose placeholder count drifts
|
||||
## from its call site would push an engine error. Validate first and fail safe: log
|
||||
## a warning and return the raw template instead of crashing.
|
||||
static func fmt(key: String, args: Array) -> String:
|
||||
var template := lookup(key)
|
||||
if template == key:
|
||||
return template
|
||||
var needed := placeholder_count(template)
|
||||
if needed != args.size():
|
||||
push_warning("Strings.fmt(%s): template needs %d args, got %d" % [key, needed, args.size()])
|
||||
return template
|
||||
return template % args
|
||||
|
||||
## Lint the loaded table for formatting hazards. Returns an array of problems.
|
||||
## Checks: trailing `%`, invalid conversions, and mixed positional (`%1$s`) and
|
||||
## sequential placeholders in one template.
|
||||
static func validate() -> Array[String]:
|
||||
_ensure_loaded()
|
||||
var problems: Array[String] = []
|
||||
for key in TABLE.keys():
|
||||
var template := str(TABLE[key])
|
||||
var positional := template.contains("$")
|
||||
var sequential := 0
|
||||
var i := 0
|
||||
while i < template.length():
|
||||
if template[i] != "%":
|
||||
i += 1
|
||||
continue
|
||||
if i + 1 >= template.length():
|
||||
problems.append("%s: trailing %% escape" % key)
|
||||
break
|
||||
var next := template[i + 1]
|
||||
if next == "%":
|
||||
i += 2
|
||||
continue
|
||||
var j := i + 1
|
||||
while j < template.length() and template[j] in "0123456789.$":
|
||||
if template[j] == "$":
|
||||
positional = true
|
||||
j += 1
|
||||
if j >= template.length() or not (template[j] in "sdfxXv"):
|
||||
problems.append("%s: unsupported conversion near '%%%s'" % [key, template.substr(i + 1, 3)])
|
||||
elif not positional:
|
||||
sequential += 1
|
||||
i = j + 1
|
||||
if positional and sequential > 0:
|
||||
problems.append("%s: mixes positional (%%1$s) and sequential (%%s) placeholders" % key)
|
||||
return problems
|
||||
|
||||
func _init() -> void:
|
||||
push_error("Strings is a static utility class; use Strings.lookup / Strings.fmt.")
|
||||
Reference in new issue
Block a user