ludic.base: bake_expand(inputs, map) and bake_maps(first_input) beside bake_inputs_hash - {map} put, globs expanded in whole-path byte order with dot-names out - so a bake's runner and the check expand a row one way; tests/baked_expand_test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
edc94e49e8
commit
2161f28766
5 changed files with 185 additions and 0 deletions
8
changes/bake-expand.md
Normal file
8
changes/bake-expand.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
bump: minor
|
||||||
|
type: feature
|
||||||
|
**A bake's inputs can follow the data.** `bake_expand(inputs, map)` gives a Bakes row's inputs as they are
|
||||||
|
hashed: `{map}` put as the map's key, and each input with a `*` put as the paths it matches, sorted as whole
|
||||||
|
paths byte by byte, dot-names left out, a glob matching nothing gone. `bake_maps(first_input)` is the maps a
|
||||||
|
`{map}` row covers: each directory under assets/maps holding its first input (`bake_maps_in` under another
|
||||||
|
root). A runner hashes `bake_inputs_hash(bake_expand(row.inputs, map))`, the same path the check takes, so
|
||||||
|
the two cannot disagree on stale; a game's Python tools are its checked twin.
|
||||||
74
packages/ludic.base/baked_expand.ludic
Normal file
74
packages/ludic.base/baked_expand.ludic
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# baked_expand.ludic - a Bakes row's inputs as they are hashed, following the data: `{map}` put as one
|
||||||
|
# map's key, and each input with a `*` put as the paths it matches, sorted as whole paths byte by byte,
|
||||||
|
# dot-names left out. tools/bake/baked.py `expand` is its checked twin, as baked_hash is the hash's: a
|
||||||
|
# runner and the check that expand alike agree on stale. A runner's line: bake_inputs_hash(bake_expand(...)).
|
||||||
|
|
||||||
|
# the inputs with `{map}` as `map` (none when "") and every glob expanded, space-separated
|
||||||
|
@alloc_ok("a bake's inputs: once a bake or a check")
|
||||||
|
export function bake_expand(inputs: string, map: string) -> string {
|
||||||
|
var ins = inputs
|
||||||
|
if len(map) > 0 { ins = Text.replace(inputs, "{map}", map) }
|
||||||
|
let parts = Text.split(ins, " ")
|
||||||
|
var out = ""
|
||||||
|
for i in 0 .. len(parts) {
|
||||||
|
if len(parts[i]) == 0 { continue }
|
||||||
|
var hits = new []string
|
||||||
|
if Text.contains(parts[i], "*") { hits = bk_sorted(bk_glob(parts[i])) } else { push(hits, parts[i]) }
|
||||||
|
for k in 0 .. len(hits) {
|
||||||
|
if len(out) > 0 { out = out + " " }
|
||||||
|
out = out + hits[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
# the maps a `{map}` row covers: each directory under assets/maps holding its first input, sorted
|
||||||
|
export function bake_maps(first_input: string) -> []string { return bake_maps_in("assets/maps", first_input) }
|
||||||
|
# ... under another directory of maps (a test's)
|
||||||
|
@alloc_ok("a bake's inputs: once a bake or a check")
|
||||||
|
export function bake_maps_in(root: string, first_input: string) -> []string {
|
||||||
|
let got = new []string
|
||||||
|
let names = bk_sorted(Fs.list(root))
|
||||||
|
for i in 0 .. len(names) {
|
||||||
|
if len(names[i]) == 0 or names[i][0] == 46 { continue }
|
||||||
|
if Fs.exists(Text.replace(first_input, "{map}", names[i])) { push(got, names[i]) }
|
||||||
|
}
|
||||||
|
return got
|
||||||
|
}
|
||||||
|
|
||||||
|
# every existing path a pattern matches, a directory level at a time
|
||||||
|
@alloc_ok("a bake's inputs: once a bake or a check")
|
||||||
|
function bk_glob(pat: string) -> []string {
|
||||||
|
let segs = Text.split(pat, "/")
|
||||||
|
var at = new []string
|
||||||
|
if len(pat) > 0 and pat[0] == 47 { push(at, "/") } else { push(at, "") }
|
||||||
|
for s in 0 .. len(segs) {
|
||||||
|
if len(segs[s]) == 0 { continue }
|
||||||
|
let next = new []string
|
||||||
|
for a in 0 .. len(at) { bk_glob_step(at[a], segs[s], next) }
|
||||||
|
at = next
|
||||||
|
}
|
||||||
|
let out = new []string
|
||||||
|
for i in 0 .. len(at) { if len(at[i]) > 0 and Fs.exists(at[i]) { push(out, at[i]) } }
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
# one directory level of a glob: a literal segment joined on, a pattern matched against the listing
|
||||||
|
@alloc_ok("a bake's inputs: once a bake or a check")
|
||||||
|
function bk_glob_step(dir: string, seg: string, next: []string) -> void {
|
||||||
|
if not Text.contains(seg, "*") {
|
||||||
|
push(next, bk_join(dir, seg))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var d = dir
|
||||||
|
if len(d) == 0 { d = "." }
|
||||||
|
let names = Fs.list(d)
|
||||||
|
if names == null { return }
|
||||||
|
for k in 0 .. len(names) {
|
||||||
|
if len(names[k]) > 0 and names[k][0] != 46 and bk_match(names[k], seg) { push(next, bk_join(dir, names[k])) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function bk_join(dir: string, name: string) -> string {
|
||||||
|
if len(dir) == 0 { return name }
|
||||||
|
if dir[len(dir) - 1] == 47 { return dir + name }
|
||||||
|
return dir + "/" + name
|
||||||
|
}
|
||||||
53
packages/ludic.base/baked_match.ludic
Normal file
53
packages/ludic.base/baked_match.ludic
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# baked_match.ludic - the two small questions a bake's glob asks (baked_expand.ludic): does a name match a
|
||||||
|
# pattern of literal bytes and `*`, and which of two paths comes first byte by byte (Python's str order)
|
||||||
|
|
||||||
|
# `*` matches any run of bytes, including none; everything else only itself
|
||||||
|
function bk_match(name: string, pat: string) -> bool {
|
||||||
|
var n = 0
|
||||||
|
var p = 0
|
||||||
|
var star = -1
|
||||||
|
var mark = 0
|
||||||
|
while n < len(name) {
|
||||||
|
if p < len(pat) and pat[p] != 42 and pat[p] == name[n] {
|
||||||
|
n += 1
|
||||||
|
p += 1
|
||||||
|
} else if p < len(pat) and pat[p] == 42 {
|
||||||
|
star = p
|
||||||
|
mark = n
|
||||||
|
p += 1
|
||||||
|
} else if star >= 0 {
|
||||||
|
p = star + 1
|
||||||
|
mark += 1
|
||||||
|
n = mark
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while p < len(pat) and pat[p] == 42 { p += 1 }
|
||||||
|
return p == len(pat)
|
||||||
|
}
|
||||||
|
|
||||||
|
# a before b, byte by byte, the shorter first on a tie
|
||||||
|
function bk_before(a: string, b: string) -> bool {
|
||||||
|
var i = 0
|
||||||
|
while i < len(a) and i < len(b) {
|
||||||
|
if a[i] != b[i] { return (a[i] & 255) < (b[i] & 255) }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return len(a) < len(b)
|
||||||
|
}
|
||||||
|
# a list sorted in place by bk_before (a directory's names: a few dozen); null is an empty list
|
||||||
|
@alloc_ok("a bake's inputs: once a bake or a check")
|
||||||
|
function bk_sorted(xs: []string) -> []string {
|
||||||
|
if xs == null { return new []string }
|
||||||
|
for i in 1 .. len(xs) {
|
||||||
|
let x = xs[i]
|
||||||
|
var j = i - 1
|
||||||
|
while j >= 0 and bk_before(x, xs[j]) {
|
||||||
|
xs[j + 1] = xs[j]
|
||||||
|
j -= 1
|
||||||
|
}
|
||||||
|
xs[j + 1] = x
|
||||||
|
}
|
||||||
|
return xs
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,8 @@ import "text_intern.ludic"
|
||||||
import "text_ring.ludic"
|
import "text_ring.ludic"
|
||||||
import "baked.ludic"
|
import "baked.ludic"
|
||||||
import "baked_hash.ludic"
|
import "baked_hash.ludic"
|
||||||
|
import "baked_match.ludic"
|
||||||
|
import "baked_expand.ludic"
|
||||||
import "baked_stream.ludic"
|
import "baked_stream.ludic"
|
||||||
import "rng.ludic"
|
import "rng.ludic"
|
||||||
import "save.ludic"
|
import "save.ludic"
|
||||||
|
|
|
||||||
48
packages/ludic.base/tests/baked_expand_test.ludic
Normal file
48
packages/ludic.base/tests/baked_expand_test.ludic
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# baked_expand_test.ludic - a Bakes row's inputs expanded as tools/bake/baked.py `expand` does: `{map}` over
|
||||||
|
# the map directories holding the first input (dot-directories out), a glob as its matches sorted as whole
|
||||||
|
# paths byte by byte (dot-names out), a glob that matches nothing gone, a literal kept even when missing
|
||||||
|
import "ludic.base"
|
||||||
|
program BakedExpandTest {
|
||||||
|
function root() -> string { return Os.temp_dir() + "/ludic-bake-expand" }
|
||||||
|
function put(path: string) -> void { Fs.write_text(path, "x") }
|
||||||
|
function dirs(ps: []string) -> void { for i in 0 .. len(ps) { Fs.mkdir(root() + ps[i]) } }
|
||||||
|
# maps a and b hold a layers file, c does not, .git is a dot-directory; b's ground has four PNGs
|
||||||
|
function fixture() -> void {
|
||||||
|
dirs(["", "/maps", "/maps/a", "/maps/a/ground", "/maps/b", "/maps/b/ground", "/maps/c", "/maps/.git", "/m", "/m-x"])
|
||||||
|
put(root() + "/maps/a/ground_layers.lres")
|
||||||
|
put(root() + "/maps/a/ground/x.png")
|
||||||
|
put(root() + "/maps/b/ground_layers.lres")
|
||||||
|
put(root() + "/maps/b/ground/z.png")
|
||||||
|
put(root() + "/maps/b/ground/a.png")
|
||||||
|
put(root() + "/maps/b/ground/a-b.png")
|
||||||
|
put(root() + "/maps/b/ground/.hidden.png")
|
||||||
|
put(root() + "/maps/b/ground/notes.txt")
|
||||||
|
put(root() + "/maps/.git/ground_layers.lres")
|
||||||
|
put(root() + "/m/f.txt")
|
||||||
|
put(root() + "/m-x/f.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a {map} row covers each map directory holding its first input, sorted" {
|
||||||
|
fixture()
|
||||||
|
let maps = bake_maps_in(root() + "/maps", root() + "/maps/{map}/ground_layers.lres")
|
||||||
|
expect_eq(len(maps), 2)
|
||||||
|
expect_eq(maps[0], "a")
|
||||||
|
expect_eq(maps[1], "b")
|
||||||
|
}
|
||||||
|
|
||||||
|
test "a map's inputs: {map} put, the glob's matches sorted byte by byte, dot-names out" {
|
||||||
|
fixture()
|
||||||
|
let r = root()
|
||||||
|
let row = r + "/maps/{map}/ground_layers.lres " + r + "/maps/{map}/ground/*.png tools/x.py"
|
||||||
|
let got = bake_expand(row, "b")
|
||||||
|
expect_eq(got, `{r}/maps/b/ground_layers.lres {r}/maps/b/ground/a-b.png {r}/maps/b/ground/a.png {r}/maps/b/ground/z.png tools/x.py`)
|
||||||
|
}
|
||||||
|
|
||||||
|
test "whole paths sort across directories, a glob with no match is gone, a literal stays" {
|
||||||
|
fixture()
|
||||||
|
let r = root()
|
||||||
|
expect_eq(bake_expand(`{r}/m*/f.txt`, ""), `{r}/m-x/f.txt {r}/m/f.txt`)
|
||||||
|
expect_eq(bake_expand(`{r}/none/*.png`, ""), "")
|
||||||
|
expect_eq(bake_expand(`{r}/missing.lres {r}/maps/a/ground/*.png`, ""), `{r}/missing.lres {r}/maps/a/ground/x.png`)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue