view src/nabble/view/web/template/MacroEditorNamespace.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 fschmidt.util.java.HtmlUtils;
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.Macro;
import nabble.naml.compiler.Meaning;
import nabble.naml.compiler.Namespace;
import nabble.naml.compiler.Program;
import nabble.naml.compiler.Source;
import nabble.naml.compiler.Usage;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Namespace(
	name = "macro_editor",
	global = false
)
public class MacroEditorNamespace {

	private Site site;
	private String rootMacroName;
	private String[] baseClassArray;
	private final Macro macro;

	private static class SaveResults {
		String error;
		String macroId;
		String base;
		String breadcrumbs;
	}

	public MacroEditorNamespace(Site site, String meaningId, String base, String breadcrumbs) {
		this.site = site;
		this.baseClassArray = MacroSourceNamespace.buildBaseClassArray(base);
		this.rootMacroName = getRootMacroName(site.getProgram(), meaningId, base, breadcrumbs);
		if (meaningId == null) {
			macro = null;
		} else {
			Meaning meaning = site.getProgram().getMeaning(meaningId);
			if (meaning == null)
				throw new NullPointerException("Meaning is null for: " + meaningId);
			else if (!(meaning instanceof Macro))
				throw new NullPointerException("Meaning is not a macro: " + meaningId);
			macro = (Macro) meaning;
		}
	}

	private String getRootMacroName(Program program, String meaningId, String base, String breadcrumbs) {
		if (breadcrumbs != null) {
			String firstMeaningId = breadcrumbs.split("-")[0];
			Meaning meaning = program.getMeaning(firstMeaningId);
			return meaning.getName();
		} else if (base != null && meaningId != null) {
			return program.getMeaning(meaningId).getName();
		}
		return null;
	}

	public static final CommandSpec CONTENTS = new CommandSpec.Builder()
		.parameters("contents")
		.build();

	public static final CommandSpec save = CONTENTS;

	@Command public void save(IPrintWriter out, Interpreter interp) {
		String tweak = getContents(interp);

		Map<String, String> originalTweaks = site.getCustomTweaks();
		Map<String, String> newTweaks = new HashMap<String, String>(originalTweaks);
		if (isEditingCustomTweak()) {
			String macroBody = macro.element.toString();
			for( Map.Entry<String,String> entry : originalTweaks.entrySet() ) {
				String filename = entry.getKey();
				String content = entry.getValue();
				int posStart = content.indexOf(macroBody);
				if (posStart >= 0) {
					SaveResults saveResults = saveTweak(tweak, originalTweaks, newTweaks, filename);
					out.print(HtmlUtils.toJson(saveResults));
					return;
				}
			}
		} else {
			String macroName = getMacroName(macro, tweak);
			if (macroName == null) {
				out.print("Error: You must specify the name of the macro.");
				return;
			}
			// tries to find a file with the name of the macro
			String file = newTweaks.get(macroName);
			String newFileContents = file == null? tweak : file + "\n\n" + tweak;

			SaveResults saveResults = saveTweak(newFileContents, originalTweaks, newTweaks, macroName);
			out.print(HtmlUtils.toJson(saveResults));
		}
	}

	private SaveResults saveTweak(String tweak, Map<String, String> originalTweaks, Map<String, String> newTweaks, String filename) {
		SaveResults saveResults =  new SaveResults();
		try {
			Usage usage = saveAndCheck(newTweaks, filename, tweak);
			saveResults.macroId = getMacroId(filename, tweak);
			// If usage is not null, then it was found while saving the tweak.
			// We send this information back to the page so that the javascript
			// can send the user to a page with navigation links.
			if (usage != null) {
				saveResults.base = MacroSourceNamespace.asBaseParam(usage.baseIds());
				saveResults.breadcrumbs = MacroSourceNamespace.asBreadcrumbsParam(usage.macroPath()) ;
			}
		} catch(CompileException e) {
			site.setCustomTweaks(originalTweaks);
			saveResults.error = "Error: " + e.getMessage();
		}
		return saveResults;
	}

	private String getMacroName(Macro macro, String tweak) {
		if (macro != null)
			return macro.getName();
		else {
			Pattern pattern = Pattern.compile("name=\"([^\"]+)\"");
			Matcher matcher = pattern.matcher(tweak);
			if (matcher.find())
				return matcher.group(1);
			else
				return null;
		}
	}

	private Usage saveAndCheck(Map<String, String> newTweaks, String filename, String newFileContents)
		throws CompileException
	{
		newFileContents = trim(newFileContents);
		newTweaks.put(filename, newFileContents);

		boolean isCompiledMacro = macro != null && site.getProgram().isCompiled(macro);

		site.setCustomTweaks(newTweaks);
		if (rootMacroName != null) {
			try {
				// Tries to compile the macro
				site.getProgram().getTemplate(rootMacroName, this.baseClassArray);
			} catch (CompileException e) {
				if (isCompiledMacro)
					throw e;
				else {
					Program program = site.getProgram();
					// Let's try to find a usage for this macro for a better check
					if (!NabbleNamespace.isCompiledAll(program))
						CompileTest.compileAll(program);

					Meaning m = program.getMeaning(macro.getId());
					Set<Usage> usages = program.getUsages(m);
					if (usages == null || usages.size() == 0)
						throw e;
					else {
						Usage usage = usages.iterator().next();
						program.getTemplate(usage.macroPath().get(0).getName(), usage.baseIds());
						return usage;
					}
				}
			}
		} else {
			// Simple XML validation
			site.getProgram().getTemplate("do_nothing");
		}
		return null;
	}

	private String getMacroId(String filename, String macroBody) throws CompileException {
		Program program = site.getProgram();
		if (macro != null && program.getMeaning(macro.getId()) != null)
			return macro.getId();
		Macro m = program.getMacroWhichOverrides(macro);
		if (m != null)
			return m.getId();
		List<Source> sources = site.getProgram().getSources();
		for (Source s : sources) {
			if (s.id.contains(':'+filename)) {
				for (Macro mac : s.getMacros()) {
					if (macroBody.contains(mac.element.toString()))
						return mac.getId();
				}
			}
		}
		throw new RuntimeException("macroId not found");
	}

	public static final CommandSpec revert = CommandSpec.NO_OUTPUT;

	@Command public void revert(IPrintWriter out,Interpreter interp) {
		if (isEditingCustomTweak()) {
			String macroBody = macro.element.toString();
			Map<String, String> tweaks = site.getCustomTweaks();
			Map<String, String> reverted = new HashMap<String, String>(tweaks);
			for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
				String name = entry.getKey();
				String content = entry.getValue();
				int posStart = content.indexOf(macroBody);
				if (posStart >= 0) {
					int posEnd = posStart + macroBody.length();
					String revertedContents = content.substring(0, posStart) + "\n" + content.substring(posEnd);
					if (trim(revertedContents).length() == 0)
						reverted.remove(name);
					else
						reverted.put(name, revertedContents);
					site.setCustomTweaks(reverted);
					break;
				}
			}
		} else
			throw new RuntimeException("Not a tweaked macro");
	}

	private String getContents(Interpreter interp) {
		String c = interp.getArgString("contents");
		c = c.replaceAll("\\u2002", " "); // Converts any &ensp; into simple space (copy and paste issue)
		return c;
	}

	private String trim(String s) {
		return s.replaceAll("(^\\s+)|(\\s+$)","");
	}

	private boolean isEditingCustomTweak() {
		return macro != null && ModuleManager.isCustomTweak(macro.source);
	}
}