• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jreleaser / jreleaser / #558

08 Dec 2025 02:56PM UTC coverage: 48.239% (+0.02%) from 48.215%
#558

push

github

aalmiray
feat(core): warn when a name template cannot be resolved

Closes #1960

Closes #1961

299 of 573 new or added lines in 133 files covered. (52.18%)

4 existing lines in 4 files now uncovered.

26047 of 53996 relevant lines covered (48.24%)

0.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.03
/api/jreleaser-model-api/src/main/java/org/jreleaser/mustache/MustacheUtils.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 *
4
 * Copyright 2020-2025 The JReleaser authors.
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     https://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
package org.jreleaser.mustache;
19

20
import com.github.mustachejava.Binding;
21
import com.github.mustachejava.Code;
22
import com.github.mustachejava.DefaultMustacheFactory;
23
import com.github.mustachejava.Mustache;
24
import com.github.mustachejava.MustacheException;
25
import com.github.mustachejava.MustacheFactory;
26
import com.github.mustachejava.TemplateFunction;
27
import com.github.mustachejava.reflect.GuardedBinding;
28
import com.github.mustachejava.reflect.MissingWrapper;
29
import com.github.mustachejava.reflect.ReflectionObjectHandler;
30
import com.github.mustachejava.util.Wrapper;
31
import org.jreleaser.bundle.RB;
32
import org.jreleaser.extensions.api.ExtensionManagerHolder;
33
import org.jreleaser.extensions.api.mustache.MustacheExtensionPoint;
34
import org.jreleaser.logging.JReleaserLogger;
35

36
import java.io.IOException;
37
import java.io.Reader;
38
import java.io.StringReader;
39
import java.io.StringWriter;
40
import java.io.Writer;
41
import java.util.LinkedHashMap;
42
import java.util.LinkedHashSet;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.UUID;
46

47
import static org.jreleaser.util.StringUtils.isNotBlank;
48

49
/**
50
 * @author Andres Almiray
51
 * @since 0.1.0
52
 */
53
@org.jreleaser.infra.nativeimage.annotations.NativeImage
54
public final class MustacheUtils {
55
    private MustacheUtils() {
56
        //noop
57
    }
58

59
    private static Map<String, String> envVars() {
60
        Map<String, String> vars = new LinkedHashMap<>();
1✔
61
        System.getenv().forEach((k, v) -> {
1✔
62
            if (!k.startsWith("JRELEASER_")) {
1✔
63
                vars.put("Env." + k, v);
1✔
64
            }
65
        });
1✔
66
        return vars;
1✔
67
    }
68

69
    public static String applyTemplate(JReleaserLogger logger, Reader reader, TemplateContext context, String templateName) {
70
        StringWriter input = new StringWriter();
1✔
71
        MustacheFactory mf = new MyMustacheFactory(logger);
1✔
72
        Mustache mustache = mf.compile(reader, templateName);
1✔
73
        context.setAll(envVars());
1✔
74
        applyFunctions(context);
1✔
75
        mustache.execute(input, decorate(context.asMap()));
1✔
76
        input.flush();
1✔
77
        return input.toString();
1✔
78
    }
79

80
    private static Map<String, Object> decorate(Map<String, Object> context) {
81
        for (Map.Entry<String, Object> e : new LinkedHashSet<>(context.entrySet())) {
1✔
82
            Object value = e.getValue();
1✔
83

84
            if (value instanceof CharSequence) {
1✔
85
                String val = String.valueOf(value);
1✔
86
                if (val.contains("{{")) {
1✔
87
                    context.put(e.getKey(), (TemplateFunction) s -> val);
1✔
88
                }
89
            }
90
        }
1✔
91
        return context;
1✔
92
    }
93

94
    public static String applyTemplate(JReleaserLogger logger, Reader reader, TemplateContext context) {
95
        return applyTemplate(logger, reader, context, UUID.randomUUID().toString()).trim();
1✔
96
    }
97

98
    public static String applyTemplate(JReleaserLogger logger, String template, TemplateContext context, String templateName) {
NEW
99
        return applyTemplate(logger, new StringReader(template), context, templateName);
×
100
    }
101

102
    public static String applyTemplate(JReleaserLogger logger, String template, TemplateContext context) {
103
        return applyTemplate(logger, new StringReader(template), context, UUID.randomUUID().toString()).trim();
1✔
104
    }
105

106
    public static void applyTemplates(JReleaserLogger logger, Map<String, Object> props, TemplateContext templates) {
107
        applyTemplates(logger, new TemplateContext(props), templates);
1✔
108
    }
1✔
109

110
    public static void applyTemplates(JReleaserLogger logger, TemplateContext props, Map<String, Object> templates) {
111
        for (Map.Entry<String, Object> e : new LinkedHashSet<>(templates.entrySet())) {
1✔
112
            Object value = e.getValue();
1✔
113

114
            if (value instanceof CharSequence) {
1✔
115
                String val = String.valueOf(value);
1✔
116
                if (val.contains("{{") && val.contains("}}")) {
1✔
NEW
117
                    value = applyTemplate(logger, val, props);
×
118
                }
119
            }
120

121
            props.set(e.getKey(), value);
1✔
122
        }
1✔
123
    }
1✔
124

125
    public static void applyTemplates(JReleaserLogger logger, TemplateContext props, TemplateContext templates) {
126
        for (Map.Entry<String, Object> e : new LinkedHashSet<>(templates.entries())) {
1✔
127
            Object value = e.getValue();
1✔
128

129
            if (value instanceof CharSequence) {
1✔
130
                String val = String.valueOf(value);
1✔
131
                if (val.contains("{{") && val.contains("}}")) {
1✔
132
                    value = applyTemplate(logger, val, props);
1✔
133
                }
134
            }
135

136
            props.set(e.getKey(), value);
1✔
137
        }
1✔
138
    }
1✔
139

140
    public static String passThrough(String str) {
141
        return isNotBlank(str) ? "!!" + str + "!!" : str;
1✔
142
    }
143

144
    private static void applyFunctions(TemplateContext props) {
145
        ExtensionManagerHolder.get().findExtensionPoints(MustacheExtensionPoint.class)
1✔
146
            .forEach(ep -> ep.apply(props));
1✔
147
    }
1✔
148

149
    private static class MyMustacheFactory extends DefaultMustacheFactory {
150
        public MyMustacheFactory(JReleaserLogger logger) {
1✔
151
            setObjectHandler(new MyReflectionObjectHandler(logger));
1✔
152
        }
1✔
153

154
        @Override
155
        public void encode(String value, Writer writer) {
156
            if (value.startsWith("!!") && value.endsWith("!!")) {
1✔
157
                try {
158
                    writer.write(value.substring(2, value.length() - 2));
1✔
159
                } catch (IOException e) {
×
160
                    throw new MustacheException(RB.$("ERROR_mustache_write_value", value), e);
×
161
                }
1✔
162
            } else {
163
                super.encode(value, writer);
1✔
164
            }
165
        }
1✔
166
    }
167

168
    private static class MyReflectionObjectHandler extends ReflectionObjectHandler {
169
        private final JReleaserLogger logger;
170

171
        public MyReflectionObjectHandler(JReleaserLogger logger) {
1✔
172
            this.logger = logger;
1✔
173
        }
1✔
174

175
        @Override
176
        public Binding createBinding(String name, com.github.mustachejava.TemplateContext tc, Code code) {
177
            return new GuardedBinding(this, name, tc, code) {
1✔
178
                @Override
179
                protected synchronized Wrapper getWrapper(String name, List<Object> scopes) {
180
                    final Wrapper wrapper = super.getWrapper(name, scopes);
1✔
181

182
                    if (wrapper instanceof MissingWrapper) {
1✔
183
                        logger.warn(RB.$("ERROR_mustache_missing_variable", name));
1✔
184
                    }
185
                    return wrapper;
1✔
186
                }
187
            };
188
        }
189
    }
190
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc