@@ -16,6 +16,7 @@ import (
1616 "os"
1717 "path/filepath"
1818 "regexp"
19+ "sort"
1920 "text/template"
2021)
2122
@@ -33,9 +34,19 @@ const (
3334// collapsing keeps the output to a single separating blank line.
3435var blankRuns = regexp .MustCompile (`\n{3,}` )
3536
36- // funcs are template helpers. dict builds a map from alternating key/value
37- // arguments so a Dockerfile can pass options to a shared partial, e.g.
38- // {{ template "apt.dockerfile" dict "SystemPython" false }}.
37+ // funcs are template helpers shared by every partial.
38+ //
39+ // dict builds a map from alternating key/value arguments so a port can pass
40+ // options to a partial; optList reads one such option back out. list/without/
41+ // concat/sortStrings manipulate string slices so a partial can expose a
42+ // tweakable list — e.g. apt.dockerfile builds its package set from a base list,
43+ // then lets a port drop or add entries:
44+ //
45+ // {{ template "apt.dockerfile" dict "Exclude" (list "eza") }}
46+ // {{ template "apt.dockerfile" dict "Include" (list "neovim") }}
47+ //
48+ // A port needing no options invokes the partial with "." like every other
49+ // partial — optList turns the resulting nil data into empty lists.
3950var funcs = template.FuncMap {
4051 "dict" : func (pairs ... any ) (map [string ]any , error ) {
4152 if len (pairs )% 2 != 0 {
@@ -51,6 +62,56 @@ var funcs = template.FuncMap{
5162 }
5263 return m , nil
5364 },
65+ // optList reads a string-slice option from a partial's data (the dict a
66+ // port passes), tolerating nil data and absent keys. This keeps the nil
67+ // check on the receiving side, so a port with no options can invoke the
68+ // partial with "." instead of an empty dict.
69+ "optList" : func (data any , key string ) []string {
70+ m , ok := data .(map [string ]any )
71+ if ! ok {
72+ return nil
73+ }
74+ v , _ := m [key ].([]string )
75+ return v
76+ },
77+ // list collects its arguments into a slice, e.g. list "a" "b" "c".
78+ "list" : func (items ... string ) []string {
79+ return items
80+ },
81+ // concat appends every slice into one, skipping nil/absent operands so a
82+ // partial can fold in an optional .Include without a nil guard.
83+ "concat" : func (lists ... []string ) []string {
84+ var out []string
85+ for _ , l := range lists {
86+ out = append (out , l ... )
87+ }
88+ return out
89+ },
90+ // without returns the elements of s that are not in drop. A nil/absent
91+ // drop (an unset .Exclude) leaves s unchanged.
92+ "without" : func (s , drop []string ) []string {
93+ if len (drop ) == 0 {
94+ return s
95+ }
96+ skip := make (map [string ]struct {}, len (drop ))
97+ for _ , d := range drop {
98+ skip [d ] = struct {}{}
99+ }
100+ out := make ([]string , 0 , len (s ))
101+ for _ , v := range s {
102+ if _ , ok := skip [v ]; ! ok {
103+ out = append (out , v )
104+ }
105+ }
106+ return out
107+ },
108+ // sortStrings returns a sorted copy, so a partial's rendered list stays
109+ // deterministic regardless of where .Include entries were appended.
110+ "sortStrings" : func (s []string ) []string {
111+ out := append ([]string (nil ), s ... )
112+ sort .Strings (out )
113+ return out
114+ },
54115}
55116
56117func main () {
0 commit comments