|  | // Copyright 2014 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // 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 com.google.devtools.build.docgen; | 
|  |  | 
|  | import com.google.common.base.Joiner; | 
|  | import com.google.common.base.Preconditions; | 
|  | import com.google.common.base.Predicate; | 
|  | import com.google.common.collect.Iterables; | 
|  | import com.google.common.collect.LinkedListMultimap; | 
|  | import com.google.common.collect.ListMultimap; | 
|  | import com.google.common.collect.Ordering; | 
|  | import com.google.devtools.build.docgen.DocgenConsts.RuleType; | 
|  | import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; | 
|  | import com.google.devtools.build.lib.packages.RuleClass; | 
|  | import java.io.File; | 
|  | import java.io.IOException; | 
|  | import java.util.ArrayList; | 
|  | import java.util.HashMap; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  | import java.util.TreeSet; | 
|  |  | 
|  | /** | 
|  | * A class to assemble documentation for the Build Encyclopedia. This class uses | 
|  | * {@link BuildDocCollector} to extract documentation fragments from rule classes. | 
|  | */ | 
|  | public abstract class BuildEncyclopediaProcessor { | 
|  | protected static final Predicate<String> RULE_WORTH_DOCUMENTING = new Predicate<String>() { | 
|  | @Override | 
|  | public boolean apply(String name) { | 
|  | return !name.contains("$"); | 
|  | } | 
|  | }; | 
|  |  | 
|  | protected ConfiguredRuleClassProvider ruleClassProvider; | 
|  |  | 
|  | /** | 
|  | * Creates the BuildEncyclopediaProcessor instance. The ruleClassProvider parameter | 
|  | * is used for rule class hierarchy and attribute checking. | 
|  | * | 
|  | */ | 
|  | public BuildEncyclopediaProcessor(ConfiguredRuleClassProvider ruleClassProvider) { | 
|  | this.ruleClassProvider = Preconditions.checkNotNull(ruleClassProvider); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Collects and processes all the rule and attribute documentation in inputDirs and | 
|  | * generates the Build Encyclopedia into the outputDir. | 
|  | * | 
|  | * @param inputDirs list of directory to scan for document in the source code | 
|  | * @param outputRootDir output directory where to write the build encyclopedia | 
|  | * @param blackList optional path to a file listing rules to not document | 
|  | */ | 
|  | public abstract void generateDocumentation(List<String> inputDirs, String outputDir, | 
|  | String blackList) throws BuildEncyclopediaDocException, IOException; | 
|  |  | 
|  | /** | 
|  | * POD class for containing lists of rule families separated into language-specific and generic as | 
|  | * returned by {@link #assembleRuleFamilies(Iterable<RuleDocumentation>) assembleRuleFamilies}. | 
|  | */ | 
|  | protected static class RuleFamilies { | 
|  | public List<RuleFamily> langSpecific; | 
|  | public List<RuleFamily> generic; | 
|  | public List<RuleFamily> all; | 
|  |  | 
|  | public RuleFamilies(List<RuleFamily> langSpecific, List<RuleFamily> generic, | 
|  | List<RuleFamily> all) { | 
|  | this.langSpecific = langSpecific; | 
|  | this.generic = generic; | 
|  | this.all = all; | 
|  | } | 
|  | } | 
|  |  | 
|  | protected RuleFamilies assembleRuleFamilies(Iterable<RuleDocumentation> docEntries) | 
|  | throws BuildEncyclopediaDocException, IOException { | 
|  | // Separate rule families into language-specific and generic ones. | 
|  | Set<String> langSpecificRuleFamilyNames = new TreeSet<>(); | 
|  | Set<String> genericRuleFamilyNames = new TreeSet<>(); | 
|  | separateRuleFamilies(docEntries, langSpecificRuleFamilyNames, genericRuleFamilyNames); | 
|  |  | 
|  | // Create a mapping of rules based on rule type and family. | 
|  | Map<String, ListMultimap<RuleType, RuleDocumentation>> ruleMapping = new HashMap<>(); | 
|  | createRuleMapping(docEntries, ruleMapping); | 
|  |  | 
|  | // Create lists of RuleFamily objects that will be used to generate the documentation. | 
|  | // The separate language-specific and general rule families will be used to generate | 
|  | // the Overview page while the list containing all rule families will be used to | 
|  | // generate all other documentation. | 
|  | List<RuleFamily> langSpecificRuleFamilies = | 
|  | filterRuleFamilies(ruleMapping, langSpecificRuleFamilyNames); | 
|  | List<RuleFamily> genericRuleFamilies = | 
|  | filterRuleFamilies(ruleMapping, genericRuleFamilyNames); | 
|  | List<RuleFamily> allRuleFamilies = new ArrayList<>(langSpecificRuleFamilies); | 
|  | allRuleFamilies.addAll(genericRuleFamilies); | 
|  | return new RuleFamilies(langSpecificRuleFamilies, genericRuleFamilies, allRuleFamilies); | 
|  | } | 
|  |  | 
|  | private List<RuleFamily> filterRuleFamilies( | 
|  | Map<String, ListMultimap<RuleType, RuleDocumentation>> ruleMapping, | 
|  | Set<String> ruleFamilyNames) { | 
|  | List<RuleFamily> ruleFamilies = new ArrayList<>(ruleFamilyNames.size()); | 
|  | for (String name : ruleFamilyNames) { | 
|  | ListMultimap<RuleType, RuleDocumentation> ruleTypeMap = ruleMapping.get(name); | 
|  | ruleFamilies.add(new RuleFamily(ruleTypeMap, name)); | 
|  | } | 
|  | return ruleFamilies; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a mapping of rules based on rule type and family. | 
|  | */ | 
|  | private void createRuleMapping(Iterable<RuleDocumentation> docEntries, | 
|  | Map<String, ListMultimap<RuleType, RuleDocumentation>> ruleMapping) | 
|  | throws BuildEncyclopediaDocException { | 
|  | for (RuleDocumentation ruleDoc : docEntries) { | 
|  | RuleClass ruleClass = ruleClassProvider.getRuleClassMap().get(ruleDoc.getRuleName()); | 
|  | if (ruleClass != null) { | 
|  | String ruleFamily = ruleDoc.getRuleFamily(); | 
|  | if (!ruleMapping.containsKey(ruleFamily)) { | 
|  | ruleMapping.put(ruleFamily, LinkedListMultimap.<RuleType, RuleDocumentation>create()); | 
|  | } | 
|  | if (ruleClass.isDocumented()) { | 
|  | ruleMapping.get(ruleFamily).put(ruleDoc.getRuleType(), ruleDoc); | 
|  | } | 
|  | } else { | 
|  | throw ruleDoc.createException("Can't find RuleClass for " + ruleDoc.getRuleName()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Separates all rule families in docEntries into language-specific rules and generic rules. | 
|  | */ | 
|  | private void separateRuleFamilies(Iterable<RuleDocumentation> docEntries, | 
|  | Set<String> langSpecific, Set<String> generic) | 
|  | throws BuildEncyclopediaDocException { | 
|  | for (RuleDocumentation ruleDoc : docEntries) { | 
|  | if (ruleDoc.isLanguageSpecific()) { | 
|  | if (generic.contains(ruleDoc.getRuleFamily())) { | 
|  | throw ruleDoc.createException("The rule is marked as being language-specific, but other " | 
|  | + "rules of the same family have already been marked as being not."); | 
|  | } | 
|  | langSpecific.add(ruleDoc.getRuleFamily()); | 
|  | } else { | 
|  | if (langSpecific.contains(ruleDoc.getRuleFamily())) { | 
|  | throw ruleDoc.createException("The rule is marked as being generic, but other rules of " | 
|  | + "the same family have already been marked as being language-specific."); | 
|  | } | 
|  | generic.add(ruleDoc.getRuleFamily()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Helper method for displaying an warning message about undocumented rules. | 
|  | * | 
|  | * @param rulesWithoutDocumentation Undocumented rules to list in the warning message. | 
|  | */ | 
|  | protected static void warnAboutUndocumentedRules(Iterable<String> rulesWithoutDocumentation) { | 
|  | Iterable<String> undocumentedRules = Iterables.filter(rulesWithoutDocumentation, | 
|  | RULE_WORTH_DOCUMENTING); | 
|  | System.err.printf("WARNING: The following rules are undocumented: [%s]\n", | 
|  | Joiner.on(", ").join(Ordering.<String>natural().immutableSortedCopy(undocumentedRules))); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the {@link RuleLinkExpander} for the provided {@link RuleDocumentationAttributes}. | 
|  | * | 
|  | * <p>This method is used to set the {@link RuleLinkExpander} for common attributes, such as | 
|  | * those defined in {@link PredefinedAttributes}, so that rule references in the docs for those | 
|  | * attributes can be expanded. | 
|  | * | 
|  | * @param attributes The map containing the RuleDocumentationAttributes, keyed by attribute name. | 
|  | * @param expander The RuleLinkExpander to set in each of the RuleDocumentationAttributes. | 
|  | * @return A map of name to RuleDocumentationAttribute with the RuleLinkExpander set for each | 
|  | *     attribute. | 
|  | */ | 
|  | protected static Map<String, RuleDocumentationAttribute> expandCommonAttributes( | 
|  | Map<String, RuleDocumentationAttribute> attributes, RuleLinkExpander expander) { | 
|  | Map<String, RuleDocumentationAttribute> expanded = new HashMap<>(attributes.size()); | 
|  | for (Map.Entry<String, RuleDocumentationAttribute> entry : attributes.entrySet()) { | 
|  | RuleDocumentationAttribute attribute = entry.getValue(); | 
|  | attribute.setRuleLinkExpander(expander); | 
|  | expanded.put(entry.getKey(), attribute); | 
|  | } | 
|  | return expanded; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Writes the {@link Page} using the provided file name in the specified output directory. | 
|  | * | 
|  | * @param page The page to write. | 
|  | * @param outputDir The output directory to write the file. | 
|  | * @param fileName The name of the file to write the page to. | 
|  | * @throws IOException | 
|  | */ | 
|  | protected static void writePage(Page page, String outputDir, String fileName) throws IOException { | 
|  | page.write(new File(outputDir + "/" + fileName)); | 
|  | } | 
|  | } |