package org.xmlsh.jstache;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.xmlsh.tools.mustache.cli.api.MustacheContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xmlsh.annotations.Command;
import org.xmlsh.annotations.Function;
import org.xmlsh.core.AbstractBuiltinFunction;
import org.xmlsh.core.CoreException;
import org.xmlsh.core.InputPort;
import org.xmlsh.core.InvalidArgumentException;
import org.xmlsh.core.Options;
import org.xmlsh.core.Options.OptionDef;
import org.xmlsh.core.Options.OptionDefs;
import org.xmlsh.core.Options.OptionValue;
import org.xmlsh.core.XClassLoader;
import org.xmlsh.core.XCommand;
import org.xmlsh.core.XValue;
import org.xmlsh.core.io.FileInputPort;
import org.xmlsh.core.io.XValueInputPort;
import org.xmlsh.json.JSONUtils;
import org.xmlsh.sh.module.ExternalModule;
import org.xmlsh.sh.module.ModuleConfig;
import org.xmlsh.sh.shell.SerializeOpts;
import org.xmlsh.sh.shell.Shell;
import org.xmlsh.types.XTypeFamily;
import org.xmlsh.types.xtypes.XValueProperty;
import org.xmlsh.util.Util;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.mustachejava.util.HtmlEscaper;
@org.xmlsh.annotations.Module
public class Module extends ExternalModule {
static Logger mLogger = LogManager.getLogger();
public Module(ModuleConfig config, XClassLoader loader)
throws CoreException {
super(config, loader);
mLogger.entry(config, loader);
}
@Override
public void onInit(Shell shell, List<XValue> args) throws Exception {
super.onInit(shell, args);
mLogger.entry(shell, args);
}
@Override
public void onLoad(Shell shell) {
super.onLoad(shell);
mLogger.entry(shell);
}
/*
*
* public void run() throws IOException {
* try {
* getMustacheFactory().setObjectHandler(new JacksonObjectHandler());
* com.github.mustachejava.Mustache mustache = mf.compile(template,
* template_name == null ? "main" : template_name , delimStart , delimEnd );
* mustache.execute(output, getScope());
* } finally {
* template.close();
* output.close();
* }
* }
*
*
* Opt.option ( "--root" , "-R" ,"--template-dir" ).with("Template directory root"),
* Opt.option ( "-f", "--template-file").with("Template file (or '-') "),
* Opt.option ( "-t", "--template","--template-data").with("Template data (inline) "),
* Opt.option ( "-p", "--properties-file").with("Context read from Java properties file" ),
* Opt.option ( "-j", "--json-data").with("Context (inline) as JSON" ),
* Opt.option ( "-J", "--json-file").with("Context read from JSON file" ),
* Opt.option ( "-o", "--output","--output-file").with("Write to output file (or '-')" ),
* Opt.option ( "-n", "--name").with("Template name"),
* Opt.option ( "-S", "--delim-start").with("Delmitar start string [default '{{' ]"),
* Opt.option ( "-E", "--delim-end").with("Delmitar end string [default '}}' ]"),
* Opt.option ( "--json").with("Use JSON encoded data for variable expansion"),
* Opt.option ( "--html").with("Use HTML encoded data for variable expansion"),
* Opt.option ( "-h","--help").with("Help")
* );f
*/
/*
* Experimental option defs
*/
static class Opt {
OptionDefs mDef;
String description;
Opt( String s,String l){
mDef = OptionDefs.parseDefs( s+"="+l );
}
Opt with( String desc ){
description = desc ;
return this ;
}
static Opt option( String l,String r ) {
return new Opt(l,r);
}
OptionDefs def() { return mDef ; }
public String toString() {
StringBuilder sb = new StringBuilder();
for( OptionDef d : mDef ){
sb.append("-").append(d.getName());
if( d.getLongname() != null )
sb.append(" | -").append( d.getLongname() );
sb.append("\t").append(description);
}
return sb.toString();
}
};
static List<Opt> opts = Arrays.asList(
Opt.option("R","template-dir:+").with("Template directory root"),
Opt.option("f","template-file:").with("Template file (or '-') "),
Opt.option("t","template:").with("Template data (inline)"),
Opt.option("d","template-data:").with("Template data (inline)"),
Opt.option("p","properties-file:").with("Context read from Java properties file"),
Opt.option("j","json-data:+").with("Context (inline) as JSON"),
Opt.option("J","json-file:+").with("Context read from JSON file"),
Opt.option("o","output:").with("Write to output file (or '-')"),
Opt.option("n","name:").with("Template Name"),
Opt.option("S","delim-start:").with("Delmitar start string [default '{{' ]"),
Opt.option("E","delim-end:").with("Delmitar end string [default '}}' ]"),
Opt.option("json","json-encoding").with("Use JSON encoded data for variable expansion"),
Opt.option("html","html-encoding").with("Use HTML encoded data for variable expansion"),
Opt.option("h","help").with("Help")
);
public static class Usage {
public List<String> message;
public String header = "Usage: jstache [options] [template] [context]";
Usage( String... msg ){
message = Arrays.asList(msg);
}
public void getOptions( Consumer<String> out ) {
for( Opt o : opts ){
out.accept( o.toString() );
}
}
public void write(Shell sh) {
message.forEach( sh::printOut );
sh.printOut(header);
getOptions( sh::printOut );
}
};
@Command(name = "jstache",names={"mustache"})
public static class jstache extends XCommand {
@SuppressWarnings("serial")
private static Options.OptionDefs optDefs = new OptionDefs() {
{
for( Opt o : opts)
addOptionDefs( o.def() );
}
};
@Override
public
void usage() {
Usage u = new Usage();
u.write(getShell());
}
private MustacheContext mContext = new MustacheContext( Shell.getCurdir() );
private boolean bInputUsed = false ;
private Reader getInputFromFile(XValue v) throws CoreException, IOException{
return getShell().getEnv().getInput(v).asReader(getSerializeOpts());
}
// Get a Template file using the context paths
private Reader getTemplateFromFile(XValue v) throws CoreException, IOException{
if( v.isAtomic() && v.equals("-")){
bInputUsed = true ;
return getShell().getEnv().getStdin().asReader(getSerializeOpts());
}
return mContext.getFileReader(v.toString());
}
@Override
public int run(List<XValue> args) throws Exception {
try {
Options opts = new Options( optDefs );
opts.parse( args );
args = opts.getRemainingArgs();
mContext.addTemplateRoot( Shell.getCurdir() );
for( OptionValue ov : opts.getOpts() ){
String name = ov.getOptionDef().getName() ;
switch( name ){
case "R" :
mContext.addTemplateRoot( getShell().getExplicitFile( ov.getValue().toString() , true , false )) ;
break;
case "f":
case "template-file": {
mContext.setTemplate(getTemplateFromFile(ov.getValue()));
mContext.setTemplate_name(ov.getValue().toString()) ;
break;
}
case "t":
case "template":
mContext.setTemplate( getReader( ov.getValue()));
break;
case "p":
case "properties-file":
mContext.addPropertiesScope(getInputFromFile(ov.getValue()));
break;
case "j":
case "json-data":
addJsonScope( ov.getValue() );
break;
case "J":
case "json-file":
try {
mContext.addJsonScope(getInputFromFile(ov.getValue()));
} catch (Exception e) {
throw new CoreException("Exception parsing JSON from:" + ov.getValue().toString());
}
break;
case "n":
case "name":
mContext.setTemplate_name(ov.toStringValue());
break;
case "ov":
case "output":
mContext.setOutput(getShell().getEnv().getOutput( ov.getValue(), false).asPrintWriter(getSerializeOpts()));
break;
case "S" :
case "delim-start" :
mContext.setDelimStart(ov.toStringValue());
break;
case "E" :
case "delim-end" :
mContext.setDelimEnd(ov.toStringValue());
break ;
case "json" :
break;
case "html" :
mContext.setEncoder((v,w) -> HtmlEscaper.escape(v,w));
break;
case "h" : case "help" :
usage();
return 1;
}
}
// Read name=value pairs
for( XValue a : args){
if( a.isAtomic() ){
mContext.addStringScope( a.toString());
}
else
if( a.isJson()){
mContext.addJsonScope(a.asJson());
}
else
mContext.addObjectScope( a.asObject());
}
if (mContext.getTemplate() == null) {
usage();
return 1;
}
if (mContext.getOutput() == null){
mContext.setOutput(getShell().getEnv().getStdout().asPrintWriter(getSerializeOpts()));
}
} catch( Exception e ){
throw mLogger.throwing(e);
}
try {
mContext.execute();
} finally {
if( mContext.getOutput() != null )
mContext.getOutput().flush();
}
return 0;
}
private void addJsonScope(XValue v) throws InvalidArgumentException, JsonProcessingException, IOException {
if( v.isJson())
mContext.addJsonScope(v.asJson());
else
// TODO - other json convertable types
mContext.addJsonScope(v.toString());
}
private Reader getReader(XValue v) throws CoreException, InvalidArgumentException, IOException {
return new XValueInputPort(v).asReader(getSerializeOpts());
}
}
}