/* * Copyright 2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.xd.documentation; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; /** * A tool that checks that for files that will form the whole "concatenated" documentation, any link of the form * <pre> * {@code * link:<file>:<anchor>[<label>] * } * </pre> * indeed points to an <em>explicit</em> and <em>unique across all included files</em> anchor in the given file. * * @author Eric Bottard */ public class AsciidocLinkChecker { private Map<String, String> anchors = new HashMap<String, String>(); private static final Pattern ANCHOR_PATTERN = Pattern.compile("^\\[\\[([a-zA-Z_0-9\\-]+)\\]\\]"); private static final Pattern LINK_PATTERN = Pattern.compile("xref:([a-zA-Z_0-9\\-]+)#([a-zA-Z_0-9\\-]+)"); private Set<Link> links = new HashSet<AsciidocLinkChecker.Link>(); private StringBuilder errors = new StringBuilder();; private int nbErrors = 0; private String rootPath; public AsciidocLinkChecker(String rootPath) { this.rootPath = rootPath; } public static void main(String[] args) throws Exception { AsciidocLinkChecker checker = new AsciidocLinkChecker(args[0]); checker.check(); if (checker.nbErrors > 0) { throw new AssertionError(String.format("There were %d errors.%n%s", checker.nbErrors, checker.errors)); } } private void check() throws IOException, UnsupportedEncodingException { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); for (Resource resource : resolver.getResources(rootPath)) { String current = stripExtension(resource.getFilename()); BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), "UTF-8")); String line = null; int lineNumber = 0; while ((line = reader.readLine()) != null) { lineNumber++; Matcher matcher = ANCHOR_PATTERN.matcher(line); if (matcher.matches()) { String anchor = matcher.group(1); String previous = anchors.put(anchor, current); if (previous != null) { error("Anchor '%s' exists in both %s and %s.%n", anchor, previous, current); } } matcher = LINK_PATTERN.matcher(line); while (matcher.find()) { links.add(new Link(current, lineNumber, matcher.group(1), matcher.group(2))); } } reader.close(); } for (Link link : links) { String target = anchors.get(link.targetAnchor); if (target == null) { error("The link at %s@%d pointing to %s#%s references an undefined anchor.%n", link.owningFile, link.line, link.targetFile, link.targetAnchor); } else if (!target.equals(link.targetFile)) { error( "The link at %s@%d pointing to %s#%s references an anchor that is actually defined in '%s'.%n", link.owningFile, link.line, link.targetFile, link.targetAnchor, target); } } } /** * Report an error. */ private void error(String string, Object... inserts) { errors.append(String.format(string, inserts)); nbErrors++; } private String stripExtension(String filename) { int dot = filename.lastIndexOf('.'); if (dot >= 0) { return filename.substring(0, dot); } else { return filename; } } private static class Link { private String owningFile; private String targetFile; private String targetAnchor; private int line; public Link(String owningFile, int line, String targetFile, String targetAnchor) { this.owningFile = owningFile; this.targetFile = targetFile; this.targetAnchor = targetAnchor; this.line = line; } } }