view src/nabble/view/web/template/MacroSourceNamespace.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children
line wrap: on
line source

package nabble.view.web.template;

import nabble.model.Site;
import nabble.modules.ModuleManager;
import nabble.naml.compiler.Command;
import nabble.naml.compiler.CommandSpec;
import nabble.naml.compiler.CompileException;
import nabble.naml.compiler.IPrintWriter;
import nabble.naml.compiler.Interpreter;
import nabble.naml.compiler.JavaCommand;
import nabble.naml.compiler.Macro;
import nabble.naml.compiler.Meaning;
import nabble.naml.compiler.Namespace;
import nabble.naml.compiler.ParamMeaning;
import nabble.naml.compiler.Program;
import nabble.naml.compiler.ScopedInterpreter;
import nabble.naml.compiler.Source;
import nabble.naml.compiler.Template;
import nabble.naml.compiler.Usage;
import nabble.naml.dom.Attribute;
import nabble.naml.dom.Container;
import nabble.naml.dom.Element;
import nabble.naml.dom.ElementName;
import nabble.naml.dom.EmptyElement;
import nabble.naml.dom.Naml;
import nabble.naml.namespaces.BasicNamespace;
import nabble.naml.namespaces.CommandDoc;
import nabble.naml.namespaces.ListSequence;
import nabble.naml.namespaces.StringList;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


@Namespace(
	name = "macro_source",
	global = true
)
public class MacroSourceNamespace extends ServletNamespaceUtils {

	private Site site;
	private String[] baseClassArray;
	private String baseClasses;
	private String nextBreadcrumbs;
	private String[] navigationBreadcrumbs;
	private Meaning meaning;
	private String macroOverriddenId;
	private String macroWhichOverridesId;

	MacroSourceNamespace(Site site, String id, String base, String breadcrumbs) {
		this.site = site;
		Program program = site.getProgram();
		meaning = program.getMeaning(id);
		if( meaning == null )
			throw new NullPointerException("meaning is null for: "+id);

		this.baseClasses = base;
		this.baseClassArray = buildBaseClassArray(base);
		this.nextBreadcrumbs = buildNextBreadcrumbs(id, breadcrumbs);

		boolean hasBaseClasses = base != null && base.length() > 0;
		if (hasBaseClasses) {
			try {
				Meaning root = program.getMeaning(navigationBreadcrumbs[0]);
				program.getTemplate(root.getName(), this.baseClassArray);
			} catch (CompileException e) {
				throw new RuntimeException(e);
			}
		}

		if (meaning instanceof Macro) {
			Macro macro = (Macro) meaning;
			Macro overridden = program.getMacroOverriddenBy(macro);
			this.macroOverriddenId = overridden != null? overridden.getId() : null;
			Macro overrides = program.getMacroWhichOverrides(macro);
			this.macroWhichOverridesId = overrides != null? overrides.getId() : null;
		}
	}

	static String[] buildBaseClassArray(String base) {
		boolean hasBaseClasses = base != null && base.length() > 0;
		String[] baseArray = hasBaseClasses? base.split("-") : new String[0];

		String basicNamespace = nabble.naml.namespaces.BasicNamespace.class.getName();
		String nabbleNamespace = nabble.view.web.template.NabbleNamespace.class.getName();

		if (baseArray.length >=2 && baseArray[0].equals(basicNamespace) && baseArray[1].equals(nabbleNamespace)) {
			return baseArray;
		} else {
			String[] array = new String[baseArray.length+2];
			array[0] = basicNamespace;
			array[1] = nabbleNamespace;

			int i = 2;
			for (String className : baseArray) {
				array[i++] = className;
			}
			return array;
		}
	}

	private String buildNextBreadcrumbs(String id, String breadcrumbs) {
		if (breadcrumbs == null) {
			navigationBreadcrumbs = new String[] { id };
			return id;
		} else {
			String[] breadcrumbParts = breadcrumbs.split("-");
			navigationBreadcrumbs = new String[breadcrumbParts.length+1];
			int i = 0;
			for (String s : breadcrumbParts) {
				navigationBreadcrumbs[i++] = s;
			}
			navigationBreadcrumbs[i] = meaning.getId();
			return breadcrumbs + '-' + navigationBreadcrumbs[i];
		}
	}

	@Command public void id(IPrintWriter out,Interpreter interp) {
		out.print(meaning.getId());
	}

	@Command public void name(IPrintWriter out,Interpreter interp) {
		out.print(meaning.getName());
	}

	@Command public void macro_overridden_id(IPrintWriter out,Interpreter interp) {
		out.print(macroOverriddenId);
	}

	@Command public void macro_which_overrides_id(IPrintWriter out,Interpreter interp) {
		out.print(macroWhichOverridesId);
	}

	@Command public void is_compiled(IPrintWriter out,Interpreter interp) {
		out.print(meaning instanceof Macro && site.getProgram().isCompiled(meaning));
	}

	public static final CommandSpec documentation = requiresServletNamespace;

	@Command public void documentation(IPrintWriter out,Interpreter interp)
		throws ServletException
	{
		String name = meaning.getName();
		Collection<String> namespaces = meaning.getRequiredNamespaces();
		Site site = NabbleNamespace.current().site();
		Template template = site.getTemplate(DocNamespace.getDocName(name,namespaces), BasicNamespace.class, NabbleNamespace.class, DocNamespace.class);
		if( template == null && !namespaces.isEmpty() )
			template = site.getTemplate(DocNamespace.getDocName(name,Collections.<String>emptySet()), BasicNamespace.class, NabbleNamespace.class, DocNamespace.class);
		if( template == null ) {
			if( meaning instanceof JavaCommand ) {
				JavaCommand jc = (JavaCommand)meaning;
				CommandDoc doc = jc.getMethod().getAnnotation(CommandDoc.class);

				String value = doc == null? "" : doc.value();
				String[] params = doc == null? new String[0] : doc.params();
				String[] seeAlso = doc == null? new String[0] : doc.seeAlso();

				template = site.getTemplate("binary doc", BasicNamespace.class, NabbleNamespace.class, DocNamespace.BinaryDocNamespace.class);
				if( template != null ) {
					DocNamespace.BinaryDocNamespace ns = new DocNamespace.BinaryDocNamespace(site, jc, value, params, seeAlso);
					template.run(out,Collections.<String,Object>emptyMap(),new BasicNamespace(template), new NabbleNamespace(site), ns);
					return;
				}
			}
			template = site.getTemplate("doc not found",BasicNamespace.class,NabbleNamespace.class,DocNamespace.class);
		}
		DocNamespace ns = new DocNamespace();
		template.run(out,Collections.<String,Object>emptyMap(), new BasicNamespace(template), new NabbleNamespace(site), ns);
	}

	public static final CommandSpec navigation_breadcrumbs = CommandSpec.DO;

	@Command public void navigation_breadcrumbs(IPrintWriter out, ScopedInterpreter<Parts> interp) {
		List<CommandInfo> infos = new ArrayList<CommandInfo>();
		Program program = site.getProgram();
		StringBuilder breadCrumbs = new StringBuilder();
		for (String id : navigationBreadcrumbs) {
			Meaning m = program.getMeaning(id);
			// Skip broken meanings in the breadcrumbs
			if (m != null)
				infos.add(new CommandInfo(m,baseClasses,breadCrumbs.length() == 0? null : breadCrumbs.toString()));
			if( breadCrumbs.length() > 0 )
				breadCrumbs.append('-');
			breadCrumbs.append(id);
		}
		out.print(interp.getArg(new Parts(infos),"do"));
	}

	@Command public void source(IPrintWriter out, Interpreter interp) {
		out.print(getSource(meaning));
	}

	@Command public void source_path(IPrintWriter out, Interpreter interp) {
		String path = null;
		if (meaning instanceof Macro) {
			path = "/template/NamlEditor$ViewFile.jtp?file="+ ((Macro) meaning).source.toString();
		}
		out.print(path);
	}

	private static String getSource(Meaning m) {
		if (m instanceof Macro) {
			String source = ((Macro) m).source.toString();
			return source.endsWith(".naml")? source : source + ".naml";
		}
		return null;
	}

	@Command public void has_macro_overridden(IPrintWriter out,Interpreter interp) {
		out.print(macroOverriddenId != null);
	}

	@Command public void has_macro_which_overrides(IPrintWriter out,Interpreter interp) {
		out.print(macroWhichOverridesId != null);
	}

	@Command public void is_override(IPrintWriter out,Interpreter interp) {
		out.print(isOverride(meaning));
	}

	private static boolean isOverride(Meaning meaning) {
		if( !(meaning instanceof Macro) )
			return false;
		return ((Macro) meaning).isOverride();
	}

	@Command public void is_custom_tweak(IPrintWriter out,Interpreter interp) {
		out.print(isCustomTweak(meaning));
	}

	@Command public void is_configuration_tweak(IPrintWriter out,Interpreter interp) {
		out.print(isConfigurationTweak(meaning));
	}

	private static boolean isCustomTweak(Meaning meaning) {
		if( !(meaning instanceof Macro) )
			return false;
		Source source = ((Macro) meaning).source;
		return ModuleManager.isCustomTweak(source);
	}

	private static boolean isConfigurationTweak(Meaning meaning) {
		if( !(meaning instanceof Macro) )
			return false;
		Source source = ((Macro) meaning).source;
		return ModuleManager.isConfigurationTweak(source);
	}

	@Command public void is_binary(IPrintWriter out,Interpreter interp) {
		out.print(!(meaning instanceof Macro));
	}

	@Command public void tweak_file_contents(IPrintWriter out,Interpreter interp) {
		String file = null;
		if (meaning instanceof Macro) {
			Macro macro = (Macro) meaning;
			if (isCustomTweak(meaning)) {
				file = macro.source.content;
			} else {
				file = macro.element.toString();
				file = file.replace("<macro ", "<override_macro ");
				file = file.replace("</macro", "</override_macro");
				file = file.replace("<subroutine ", "<override_subroutine ");
				file = file.replace("</subroutine", "</override_subroutine");
				file = file.replace("<translation ", "<override_translation ");
				file = file.replace("</translation", "</override_translation");
			}
		}
		out.print(file);
	}

	@Command public void macro_opening_tag(IPrintWriter out,Interpreter interp) {
		String tag = null;
		if (meaning instanceof Macro) {
			Macro macro = (Macro) meaning;
			tag = macro.element.openingTag();
			if (!isCustomTweak(meaning)) {
				tag = tag.replace("<macro ", "<override_macro ");
				tag = tag.replace("<subroutine ", "<override_subroutine ");
				tag = tag.replace("<translation ", "<override_translation ");
			}
		}
		out.print(tag);
	}

	public static final CommandSpec rows = CommandSpec.DO;

	@Command public void rows(IPrintWriter out,ScopedInterpreter<Rows> interp)
		throws IOException, ServletException
	{
		if (meaning instanceof Macro) {
			Macro m = (Macro) meaning;
			List<CommandInfo> infos = new ArrayList<CommandInfo>();
			List<Parts> rows = new ArrayList<Parts>();
			buildElement(m.element, infos, rows, false);
			if (infos.size() > 0)
				rows.add(new Parts(infos));

			Object block = interp.getArg(new Rows(rows, m),"do");
			out.print(block);
		}
	}

	private void traverse(Naml naml, List<CommandInfo> parts, List<Parts> rows, boolean isAttribute) {
		for (Object o : naml) {
			if (o instanceof EmptyElement) {
				buildEmptyElement((EmptyElement) o, parts, rows, isAttribute);
			} else if (o instanceof Container) {
				buildElement((Container) o, parts, rows, isAttribute);
			} else {
				processString(o.toString(), parts, rows);
			}
		}
	}

	private void buildEmptyElement(EmptyElement e, List<CommandInfo> parts, List<Parts> rows, boolean isAttribute) {
		startTag(parts, isAttribute);
		extractParts(e, parts);
		extractAttributes(e, parts, rows);
		finishTag(e, parts, rows, isAttribute);
	}

	private void buildElement(Element e, List<CommandInfo> parts, List<Parts> rows, boolean isAttribute) {
		startTag(parts, isAttribute);
		extractParts(e, parts);

		if (e.name().endsWithDot())
			parts.add(new CommandInfo("."));

		extractAttributes(e, parts, rows);
		finishTag(e, parts, rows, isAttribute);
		if (e instanceof Container) {
			Container container = (Container) e;
			traverse(container.contents(), parts, rows, isAttribute);
			closingTag(container, isAttribute, parts);
		}
	}

	private void closingTag(Container e, boolean isAttribute, List<CommandInfo> parts) {
		String closingTag = e
			.closingTag()
			.replace("<",isAttribute?"[":"&lt;")
			.replace(">",isAttribute?"]":"&gt;");
		parts.add(new CommandInfo(closingTag));
	}

	private void finishTag(Element e, List<CommandInfo> infos, List<Parts> rows, boolean isAttribute) {
		processString(e.spaceAtEndOfOpeningTag(), infos, rows);
		infos.add(new CommandInfo(
			(e instanceof EmptyElement? '/': "") +
	        (isAttribute? "]":"&gt;")
		));
	}

	private void startTag(List<CommandInfo> parts, boolean isAttribute) {
		parts.add(new CommandInfo(isAttribute?"[":"&lt;"));
	}

	private void extractAttributes(Element e, List<CommandInfo> parts, List<Parts> rows) {
		for (Attribute attribute : e.attributes()) {
			processString(attribute.spaceBeforeName(), parts, rows);
			parts.add(new CommandInfo(
				attribute.name() +
				attribute.spaceAfterName() +
				'=' +
				attribute.quote())
			);
			traverse(attribute.value(), parts, rows, true);
			parts.add(new CommandInfo(String.valueOf(attribute.quote())));
		}
	}

	private void extractParts(Element e, List<CommandInfo> parts) {
		int i = 0;
		List<Macro> currentMacroPath = getCurrentMacroPath();
		Usage currentUsage = site.getProgram().getUsage(baseClassArray, currentMacroPath);
		for (ElementName.Part p : e.name().parts()) {
			if (i++ > 0)
				parts.add(new CommandInfo("."));
			Meaning meaning = site.getProgram().getMeaning(p, currentUsage);
			if (meaning != null) {
				boolean isArgument = meaning instanceof ParamMeaning;
				boolean isOverridden = "overridden".equals(p.text());
				if (isArgument || isOverridden) {
					parts.add(new CommandInfo(p.text()));
				} else {
					parts.add(new CommandInfo(meaning, baseClasses, nextBreadcrumbs));
				}
			} else
				parts.add(new CommandInfo(p.text()));
		}
	}

	private List<Macro> getCurrentMacroPath() {
		List<Macro> macroPath = new ArrayList<Macro>();
		Program program = site.getProgram();
		for (String id : navigationBreadcrumbs) {
			macroPath.add((Macro) program.getMeaning(id));
		}
		return macroPath;
	}

	private void processString(String e, List<CommandInfo> infos, List<Parts> rows) {
		StringBuilder builder = new StringBuilder();
		int i = 0;
		while (i < e.length()) {
			char c = e.charAt(i);
			if (c == ' ')
				builder.append("&ensp;");
			else if (c == '\t')
				builder.append("&nbsp;&nbsp;&nbsp;&nbsp;");
			else if ((c == '\r' && e.charAt(i+1) == '\n') || c == '\n') {
				if (builder.length() > 0) {
					infos.add(new CommandInfo(builder.toString()));
					builder.setLength(0);
				}
				rows.add(new Parts(new ArrayList<CommandInfo>(infos)));
				infos.clear();
				if (c == '\r')
					i++; // CRLF
			} else if (c == '<') {
				builder.append("&lt;");
			} else if (c == '>') {
				builder.append("&gt;");
			} else if (c == '&') {
				builder.append("&amp;");
			} else
				builder.append(c);
			i++;
		}
		if (builder.length() > 0)
			infos.add(new CommandInfo(builder.toString()));
	}

	static String csv(Collection<String> strings) {
		StringBuilder b = new StringBuilder();
		for (String s : strings) {
			if (b.length() > 0)
				b.append(", ");
			b.append(s);
		}
		return b.toString();
	}

	@Namespace (
		name = "macro_row_list",
		global = true
	)
	public static final class Rows extends ListSequence<Parts>
	{

		private final Macro macro;

		Rows(List<Parts> rows, Macro macro) {
			super(rows);
			this.macro = macro;
		}

		@Command public void line_number(IPrintWriter out,Interpreter interp) {
			out.print(index + 1);
		}

		@Command public void file_line_number(IPrintWriter out,Interpreter interp) {
			out.print(macro.element.lineNumber() + index + 1);
		}

		public static final CommandSpec current_row = CommandSpec.DO;

		@Command public void current_row(IPrintWriter out,ScopedInterpreter<Parts> interp) {
			out.print(interp.getArg(get(),"do"));
		}

		@Command public void next_row(IPrintWriter out,Interpreter interp) {
			next_element(out,interp);
		}
	}

	@Namespace (
		name = "macro_parts_list",
		global = true
	)
	public static final class Parts extends ListSequence<CommandInfo> {

		Parts(List<CommandInfo> infos) {
			super(infos);
		}

		public static final CommandSpec parts = CommandSpec.DO;

		@Command public void parts(IPrintWriter out,ScopedInterpreter<CommandInfo> interp) {
			out.print( interp.getArg(get(),"do") );
		}

		@Command public void is_blank(IPrintWriter out, Interpreter interp) {
			out.print(elements.size() == 0);
		}
	}

	public static final CommandSpec macro_usages = CommandSpec.DO;

	@Command public void macro_usages(IPrintWriter out,ScopedInterpreter<MacroUsages> interp)
		throws IOException, ServletException, CompileException
	{
		List<Commands> macros = new ArrayList<Commands>();
		Set<Usage> usages = site.getProgram().getUsages(meaning);
		if (usages == null)
			usages = new HashSet<Usage>();
		for (Usage u : usages) {
			List<CommandInfo> infos = new ArrayList<CommandInfo>();
			StringBuilder breadcrumbs = new StringBuilder();
			String usageBase = asBaseParam(u.baseIds());
			for (Macro m : u.macroPath()) {
				infos.add(new CommandInfo(m, usageBase, breadcrumbs.length() == 0? null : breadcrumbs.toString()));

				if (breadcrumbs.length() > 0)
					breadcrumbs.append('-');
				breadcrumbs.append(m.getId());
			}
			infos.add(new CommandInfo(meaning, usageBase, breadcrumbs.toString()));
			macros.add(new Commands(infos));
		}
		Object block = interp.getArg(new MacroUsages(macros),"do");
		out.print(block);
	}

	static String asBaseParam(String[] base) {
		StringBuilder b = new StringBuilder();
		for (String c : base) {
			if (b.length() > 0)
				b.append('-');
			b.append(c);
		}
		return b.toString();
	}

	static String asBreadcrumbsParam(List<Macro> macroPath) {
		StringBuilder breadcrumbs = new StringBuilder();
		for (Macro p : macroPath) {
			if (breadcrumbs.length() > 0)
				breadcrumbs.append('-');
			breadcrumbs.append(p.getId());
		}
		return breadcrumbs.toString();
	}

	@Namespace (
		name = "macro_usages",
		global = true
	)
	public static final class MacroUsages extends ListSequence<Commands>
	{
		public MacroUsages(List<Commands> elements) {
			super(elements);
		}

		public static final CommandSpec current_usage = CommandSpec.DO;

		@Command public void current_usage(IPrintWriter out,ScopedInterpreter<Commands> interp) {
			out.print(interp.getArg(get(),"do"));
		}
	}

	@Namespace (
		name = "command_list",
		global = true
	)
	public static final class Commands extends ListSequence<CommandInfo>
	{
		public Commands(List<CommandInfo> elements) {
			super(elements);
		}

		public static final CommandSpec current_command = CommandSpec.DO;

		@Command public void current_command(IPrintWriter out,ScopedInterpreter<CommandInfo> interp) {
			out.print(interp.getArg(get(),"do"));
		}
	}

	@Namespace (
		name = "command_info",
		global = false
	)
	public static final class CommandInfo {
		private final String id;
		private final String name;
		private final String tag;
		private final String source;
		private final int fileLineNumber;
		private final String baseClasses;
		private final String breadcrumbs;
		private final String requiredNamespaces;
		private final boolean isCustomTweak;
		private final boolean isMacro;
		private final boolean isBinary;
		private final String namespaceClass;
		private final String[] parameterNames;

		static final Comparator<? super CommandInfo> MACRO_NAME_COMPARATOR = new Comparator<MacroSourceNamespace.CommandInfo>() {
			public int compare(MacroSourceNamespace.CommandInfo o1, MacroSourceNamespace.CommandInfo o2) {
				return o1.name.compareTo(o2.name);
			}
		};

		public CommandInfo(String name) {
			this.id = null;
			this.tag = name;
			this.name = name;
			this.isMacro = false;
			this.isBinary = false;
			this.source = null;
			this.fileLineNumber = 0;
			this.baseClasses = null;
			this.breadcrumbs = null;
			this.requiredNamespaces = null;
			this.isCustomTweak = false;
			this.namespaceClass = null;
			this.parameterNames = null;
		}

		public CommandInfo(Meaning meaning, String baseClasses, String breadcrumbs) {
			this.id = meaning.getId();
			this.name = meaning.getName();
			this.tag = this.name.startsWith("translation:")? "t" : this.name;
			this.isMacro = meaning instanceof Macro;
			this.isBinary = meaning instanceof JavaCommand;
			this.source = getSource(meaning);
			this.fileLineNumber = isMacro? ((Macro) meaning).element.lineNumber() + 1 : 0;
			this.baseClasses = baseClasses;
			this.breadcrumbs = breadcrumbs;
			this.requiredNamespaces = csv(meaning.getRequiredNamespaces());
			this.isCustomTweak = isCustomTweak(meaning);
			this.namespaceClass = meaning instanceof JavaCommand? ((JavaCommand) meaning).getMethod().getDeclaringClass().getSimpleName() : null;
			this.parameterNames = isMacro? ((Macro) meaning).getParameterNames() : meaning instanceof JavaCommand? ((JavaCommand) meaning).getParameterNames() : null;
		}

		@Command("id") public void _id(IPrintWriter out,Interpreter interp) {
			out.print(id);
		}

		@Command("name") public void _name(IPrintWriter out,Interpreter interp) {
			out.print(name);
		}

		@Command("tag") public void _tag(IPrintWriter out,Interpreter interp) {
			out.print(tag);
		}

		@Command("source") public void _source(IPrintWriter out,Interpreter interp) {
			out.print(source);
		}

		@Command public void naml_breadcrumbs(IPrintWriter out,Interpreter interp) {
			out.print(breadcrumbs);
		}

		@Command public void base(IPrintWriter out,Interpreter interp) {
			out.print(baseClasses);
		}

		@Command public void file_line_number(IPrintWriter out,Interpreter interp) {
			out.print(fileLineNumber);
		}

		@Command public void required_namespaces(IPrintWriter out,Interpreter interp) {
			out.print(requiredNamespaces);
		}

		@Command public void is_custom_tweak(IPrintWriter out,Interpreter interp) {
			out.print(isCustomTweak);
		}

		@Command public void is_macro(IPrintWriter out,Interpreter interp) {
			out.print(isMacro);
		}

		@Command public void namespace_class(IPrintWriter out,Interpreter interp) {
			out.print(namespaceClass);
		}

		@Command public void is_binary(IPrintWriter out,Interpreter interp) {
			out.print(isBinary);
		}

		@Command public void has_parameters(IPrintWriter out,Interpreter interp) {
			out.print(parameterNames != null && parameterNames.length > 0);
		}

		public static final CommandSpec parameter_names = CommandSpec.DO;

		@Command public void parameter_names(IPrintWriter out, ScopedInterpreter<StringList> interp) {
			out.print(interp.getArg(new StringList(Arrays.asList(parameterNames)),"do"));
		}
	}
}