@Test public void setResolver() throws IOException { ValueResolver resolver = new ValueResolver() { @Override public Object resolve(final Object context) { return 1; } @Override public Object resolve(final Object context, final String name) { return 1; } @Override public Set<Entry<String, Object>> propertySet(final Object context) { return null; } }; Map<String, Object> hash = new HashMap<>(); hash.put("foo", "bar"); Context ctx = Context.newBuilder(hash).resolver(resolver).build(); assertEquals(1, ctx.get("foo")); assertEquals(1, ctx.get("bar")); }
/** * Creates a child context. * * @param parent The parent context. Required. * @param model The target value. Resolved as '.' or 'this' inside templates. Required. * @return A child context. */ private static Context child(final Context parent, final Object model) { notNull(parent, "A parent context is required."); Context child = new Context(model); child.extendedContext = new Context(new HashMap<String, Object>()); child.parent = parent; child.data = parent.data; return child; }
/** * Creates a root context. * * @param model The target value. Resolved as '.' or 'this' inside templates. Required. * @return A root context. */ private static Context root(final Object model) { Context root = new Context(model); root.extendedContext = new Context(new HashMap<String, Object>()); root.parent = null; root.storage = new HashMap<String, Object>(); root.storage.put(PARTIALS, new HashMap<String, Template>()); return root; }
/** * Creates a root context. * * @param model The target value. Resolved as '.' or 'this' inside templates. Required. * @return A root context. */ private static Context root(final Object model) { Context root = new Context(model); root.extendedContext = new Context(new HashMap<String, Object>()); root.parent = null; root.data = new HashMap<String, Object>(); root.data.put(PARTIALS, new HashMap<String, Template>()); root.data.put(INVOCATION_STACK, new LinkedList<TemplateSource>()); return root; }
@SuppressWarnings("unchecked") @Override protected void merge(final Context context, final Writer writer) throws IOException { if (body == null) { return; } final String helperName; Helper<Object> helper = helper(name); Template template = body; final Object childContext; Context currentScope = context; if (helper == null) { childContext = transform(context.get(name)); if (inverted) { helperName = UnlessHelper.NAME; } else if (childContext instanceof Iterable) { helperName = EachHelper.NAME; } else if (childContext instanceof Boolean) { helperName = IfHelper.NAME; } else if (childContext instanceof Lambda) { helperName = WithHelper.NAME; template = Lambdas.compile( handlebars, (Lambda<Object, Object>) childContext, context, template, startDelimiter, endDelimiter); } else { helperName = WithHelper.NAME; currentScope = Context.newContext(context, childContext); } // A built-in helper might be override it. helper = handlebars.helper(helperName); } else { helperName = name; childContext = transform(determineContext(context)); } Options options = new Options.Builder(handlebars, helperName, TagType.SECTION, currentScope, template) .setInverse(inverse == null ? Template.EMPTY : inverse) .setParams(params(currentScope)) .setHash(hash(context)) .build(); options.data(Context.PARAM_SIZE, this.params.size()); CharSequence result = helper.apply(childContext, options); if (!isEmpty(result)) { writer.append(result); } }
@Override public CharSequence apply(final Object context, final Options options) throws IOException { if (context == null) { return null; } if (options.params.length <= 0) { return context.toString(); } Context ctx = Context.newBuilder(options.context, context).build(); Object lookup = ctx.get(options.param(0).toString()); if (lookup == null) { return null; } return lookup.toString(); }
private CharSequence iterableContext(Iterable<?> context, Options options) throws IOException { if (options.isFalsy(context)) { return options.inverse(); } StringBuilder buffer = new StringBuilder(); Iterator<?> iterator = reverse(context); int index = 0; Context parent = options.context; while (iterator.hasNext()) { Object element = iterator.next(); boolean first = index == 0; boolean even = index % 2 == 0; boolean last = !iterator.hasNext(); Context current = Context.newContext(parent, element) .data("index", index) .data("first", first ? "first" : "") .data("last", last ? "last" : "") .data("odd", even ? "" : "odd") .data("even", even ? "even" : ""); buffer.append(options.fn(current)); index++; } return buffer; }
/** * Build a context stack. * * @return A new context stack. */ public Context build() { if (context.resolver == null) { if (context.parent != null) { // Set resolver from parent. context.resolver = context.parent.resolver; } else { // Set default value resolvers: Java Bean like and Map resolvers. context.setResolver(new CompositeValueResolver(ValueResolver.VALUE_RESOLVERS)); } // Expand resolver to the extended context. if (context.extendedContext != null) { context.extendedContext.resolver = context.resolver; } } return context; }
@Override public void execute() throws MojoExecutionException, MojoFailureException { File basedir = new File(prefix); TemplateLoader loader = new FileTemplateLoader(basedir, suffix); Handlebars handlebars = new Handlebars(loader); File output = new File(this.output); PrintWriter writer = null; boolean error = true; InputStream runtimeIS = getClass().getResourceAsStream("/handlebars.runtime.js"); try { writer = new PrintWriter(output); if (includeRuntime) { IOUtil.copy(runtimeIS, writer); } List<File> files = FileUtils.getFiles(basedir, "/**/*" + suffix, null); getLog().info("Compiling templates..."); getLog().debug("Options:"); getLog().debug(" output: " + output); getLog().debug(" prefix: " + prefix); getLog().debug(" suffix: " + suffix); getLog().debug(" minimize: " + minimize); getLog().debug(" includeRuntime: " + includeRuntime); Context nullContext = Context.newContext(null); for (File file : files) { String templateName = file.getPath().replace(prefix, "").replace(suffix, ""); if (templateName.startsWith("/")) { templateName = templateName.substring(1); } getLog().debug("compiling: " + templateName); Template template = handlebars.compileInline("{{precompile \"" + templateName + "\"}}"); Options opts = new Options.Builder(handlebars, TagType.VAR, nullContext, template).build(); writer.append("// Source: ").append(file.getPath()).append("\n"); writer.append(PrecompileHelper.INSTANCE.apply(templateName, opts)).append("\n\n"); } writer.flush(); IOUtil.close(writer); if (minimize) { minimize(output); } if (files.size() > 0) { getLog().info(" templates were saved in: " + output); error = false; } else { getLog().warn(" no templates were found"); } } catch (IOException ex) { throw new MojoFailureException("Can't scan directory " + basedir, ex); } finally { IOUtil.close(runtimeIS); IOUtil.close(writer); if (error) { output.delete(); } } }
private CharSequence hashContext(Object context, Options options) throws IOException { StringBuilder buffer = new StringBuilder(); Context parent = options.context; Iterator<Map.Entry<String, Object>> iterator = reverse(options.propertySet(context)); while (iterator.hasNext()) { Map.Entry<String, Object> entry = iterator.next(); Context current = Context.newContext(parent, entry.getValue()).data("key", entry.getKey()); buffer.append(options.fn(current)); } return buffer; }
public EngineHelper(Node node) { HashMap contextVariables = new HashMap(); contextVariables.put("node", node); this.node = node; this.context = Context.newBuilder(contextVariables) .resolver( MethodValueResolver.INSTANCE, MapValueResolver.INSTANCE, JavaBeanValueResolver.INSTANCE, FieldValueResolver.INSTANCE) .build(); }
/** Destroy this context by cleaning up instance attributes. */ public void destroy() { model = null; if (parent == null) { // Root context is the owner of the storage. if (data != null) { data.clear(); } } if (extendedContext != null) { extendedContext.destroy(); } parent = null; resolver = null; data = null; }
/** Destroy this context by cleaning up instance attributes. */ public void destroy() { model = null; if (parent == null) { // Root context are owner of the storage. if (storage != null) { storage.clear(); } } if (extendedContext != null) { extendedContext.destroy(); } parent = null; resolver = null; storage = null; }
/** * Lookup the given key inside the context stack. * * <ul> * <li>Objects and hashes should be pushed onto the context stack. * <li>All elements on the context stack should be accessible. * <li>Multiple sections per template should be permitted. * <li>Failed context lookups should be considered falsey. * <li>Dotted names should be valid for Section tags. * <li>Dotted names that cannot be resolved should be considered falsey. * <li>Dotted Names - Context Precedence: Dotted names should be resolved against former * resolutions. * </ul> * * @param key The object key. * @return The value associated to the given key or <code>null</code> if no value is found. */ public Object get(final String key) { if (".".equals(key) || "this".equals(key)) { return model; } if (key.startsWith("../")) { return parent == null ? null : parent.get(key.substring("../".length())); } String[] path = toPath(key); Object value = get(path); if (value == null) { // No luck, check the extended context. value = get(extendedContext, key); // No luck, but before checking at the parent scope we need to check for // the 'this' qualifier. If present, no look up will be done. if (value == null && !path[0].equals("this")) { value = get(parent, key); } } return value == NULL ? null : value; }
/** * Lookup the given key inside the context stack. * * <ul> * <li>Objects and hashes should be pushed onto the context stack. * <li>All elements on the context stack should be accessible. * <li>Multiple sections per template should be permitted. * <li>Failed context lookups should be considered falsey. * <li>Dotted names should be valid for Section tags. * <li>Dotted names that cannot be resolved should be considered falsey. * <li>Dotted Names - Context Precedence: Dotted names should be resolved against former * resolutions. * </ul> * * @param key The object key. * @return The value associated to the given key or <code>null</code> if no value is found. */ public Object get(final String key) { // '.' or 'this' if (MUSTACHE_THIS.equals(key) || THIS.equals(key)) { return model; } // '..' if (key.equals(PARENT)) { return parent == null ? null : parent.model; } // '../' if (key.startsWith(PARENT_ATTR)) { return parent == null ? null : parent.get(key.substring(PARENT_ATTR.length())); } String[] path = toPath(key); Object value = get(path); if (value == null) { // No luck, check the extended context. value = get(extendedContext, key); // No luck, check the data context. if (value == null && data != null) { String dataKey = key.charAt(0) == '@' ? key.substring(1) : key; // simple data keys will be resolved immediately, complex keys need to go down and using a // new context. value = data.get(dataKey); if (value == null && path.length > 1) { // for complex keys, a new data context need to be created per invocation, // bc data might changes per execution. Context dataContext = Context.newBuilder(data).resolver(MapValueResolver.INSTANCE).build(); // don't extend the lookup further. dataContext.data = null; value = dataContext.get(dataKey); // destroy it! dataContext.destroy(); } } // No luck, but before checking at the parent scope we need to check for // the 'this' qualifier. If present, no look up will be done. if (value == null && !path[0].equals(THIS)) { value = get(parent, key); } } return value == NULL ? null : value; }
/** * Combine all the map entries into the context stack. * * @param model The model data. * @return This builder. */ public Builder combine(final Map<String, Object> model) { context.combine(model); return this; }
/** * Set the value resolver and propagate it to the extendedContext. * * @param resolver The value resolver. */ private void setResolver(final ValueResolver resolver) { this.resolver = resolver; extendedContext.resolver = resolver; }
/** * Look for the specified key in an external context. * * @param external The external context. * @param key The associated key. * @return The associated value or null if not found. */ private Object get(final Context external, final String key) { return external == null ? null : external.get(key); }
@Override protected void doExecute() throws Exception { notNull(bundle, "The bundle's name parameter is required."); notNull(output, "The output parameter is required."); Handlebars handlebars = new Handlebars(); Context context = Context.newContext(null); URL[] classpath = projectClasspath(); new File(output).mkdirs(); getLog().info("Converting bundles..."); getLog().debug("Options:"); getLog().debug(" output: " + output); getLog().debug(" merge: " + merge); getLog().debug(" amd: " + amd); getLog().debug(" classpath: " + join(classpath, File.pathSeparator)); StringBuilder buffer = new StringBuilder(); // 1. find all the bundles List<File> bundles = bundles(this.bundle, classpath); // 2. hash for i18njs helper Map<String, Object> hash = new HashMap<String, Object>(); hash.put("wrap", false); if (classpath.length > 0) { hash.put("classLoader", new URLClassLoader(classpath, getClass().getClassLoader())); } // 3. get the base name from the bundle String basename = FileUtils.removePath(this.bundle.replace(".", "/")); Collections.sort(bundles); for (File bundle : bundles) { // bundle name String bundleName = FileUtils.removeExtension(bundle.getName()); getLog().debug("converting: " + bundle.getName()); // extract locale from bundle name String locale = bundleName.substring(basename.length()); if (locale.startsWith("_")) { locale = locale.substring(1); } // set bundle name hash.put("bundle", this.bundle); Options options = new Options.Builder(handlebars, TagType.VAR, context, Template.EMPTY) .setHash(hash) .build(); // convert to JS buffer.append(I18nHelper.i18nJs.apply(locale, options)); if (!merge) { FileUtils.fileWrite( new File(output, bundleName + ".js"), encoding, wrap(bundleName, buffer, amd)); buffer.setLength(0); getLog().debug(" => " + bundleName + ".js"); } else { buffer.append("\n"); } } if (merge && buffer.length() > 0) { FileUtils.fileWrite(new File(output, basename + ".js"), wrap(basename, buffer, amd)); getLog().debug(" =>" + basename + ".js"); } }
/** * Combine the given model using the specified name. * * @param name The variable's name. Required. * @param model The model data. * @return This builder. */ public Builder combine(final String name, final Object model) { context.combine(name, model); return this; }
/** * Creates a new context builder. * * @param model The model data. */ private Builder(final Object model) { context = Context.root(model); }
/** * Creates a new context builder. * * @param parent The parent context. Required. * @param model The model data. */ private Builder(final Context parent, final Object model) { context = Context.child(parent, model); }
/** * Set the value resolvers to use. * * @param resolvers The value resolvers. Required. * @return This builder. */ public Builder resolver(final ValueResolver... resolvers) { notEmpty(resolvers, "At least one value-resolver must be present."); context.setResolver(new CompositeValueResolver(resolvers)); return this; }
@Override Object doParse(final Context scope, final Object param) { return scope.get((String) param); }