001/* 002 * JDrupes MDoclet 003 * Copyright 2013 Raffael Herzog 004 * Copyright (C) 2017 Michael N. Lipp 005 * 006 * This program is free software; you can redistribute it and/or modify it 007 * under the terms of the GNU General Public License as published by 008 * the Free Software Foundation; either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, but 012 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 013 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * for more details. 015 * 016 * You should have received a copy of the GNU General Public License along 017 * with this program; if not, see <http://www.gnu.org/licenses/>. 018 */ 019package org.jdrupes.mdoclet; 020 021import java.io.File; 022import java.nio.charset.Charset; 023import java.nio.charset.IllegalCharsetNameException; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.HashSet; 027import java.util.Iterator; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Set; 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033 034import org.jdrupes.mdoclet.processors.FlexmarkProcessor; 035 036import com.sun.javadoc.DocErrorReporter; 037import com.sun.tools.doclets.standard.Standard; 038 039/** 040 * Processes and stores the command line options. 041 */ 042public class Options { 043 044 public static final String OPT_MARKDOWN_PROCESSOR = "-markdown-processor"; 045 public static final String OPT_DISABLE_HIGHLIGHT = "-disable-highlight"; 046 public static final String OPT_DISABLE_AUTO_HIGHLIGHT = "-disable-auto-highlight"; 047 public static final String OPT_HIGHLIGHT_STYLE = "-highlight-style"; 048 public static final String OPT_ENCODING = "-encoding"; 049 public static final String OPT_OVERVIEW = "-overview"; 050 public static final String OPT_OUTPUT_DIR = "-d"; 051 public static final String OPT_STYLESHEETFILE = "-stylesheetfile"; 052 public static final String OPT_TAG = "-tag"; 053 054 private static final Pattern OPT_TAG_PATTERN 055 = Pattern.compile("(?<tag>.*?)(?<!\\\\):(?<flags>[^:]*)(:(?<title>.*))?"); 056 057 private File overviewFile = null; 058 private Charset encoding = null; 059 private File destinationDir = null; 060 private File stylesheetFile = null; 061 private boolean highlightEnabled = true; 062 private String highlightStyle = null; 063 private Set<String> markedDownTags = new HashSet<>(); 064 065 private static MarkdownProcessor processor = new FlexmarkProcessor(); 066 private static boolean processorUsed = false; 067 private static List<String[]> processorOptions = new ArrayList<>(); 068 069 public static int optionLength(String option) { 070 071 if (option.startsWith("-M") || option.startsWith("-T")) { 072 return 1; 073 } 074 switch (option) { 075 case OPT_MARKDOWN_PROCESSOR: 076 case OPT_HIGHLIGHT_STYLE: 077 case OPT_DISABLE_HIGHLIGHT: 078 return 2; 079 case OPT_DISABLE_AUTO_HIGHLIGHT: 080 return processor.isSupportedOption 081 (MarkdownProcessor.INTERNAL_OPT_DISABLE_AUTO_HIGHLIGHT) + 1; 082 default: 083 return Standard.optionLength(option); 084 } 085 } 086 087 /** 088 * As specified by the Doclet specification. 089 * 090 * @param options The command line options. 091 * @param errorReporter An error reporter to print messages. 092 * 093 * @return `true` if the options are valid. 094 * 095 * @see com.sun.javadoc.Doclet#validOptions(String[][], com.sun.javadoc.DocErrorReporter) 096 */ 097 public static boolean validOptions(String[][] options, DocErrorReporter errorReporter) { 098 String[][] forwardedOptions = new Options().load(options, errorReporter); 099 if ( forwardedOptions != null ) { 100 return Standard.validOptions(options, errorReporter); 101 } 102 else { 103 return false; 104 } 105 } 106 107 /** 108 * Loads the options from the command line. 109 * 110 * @param options The command line options. 111 * @param errorReporter The error reporter for printing messages. 112 * 113 * @return The options to be forwarded to the standard doclet. 114 */ 115 public String[][] load(String[][] options, DocErrorReporter errorReporter) { 116 LinkedList<String[]> optionsList = new LinkedList<>(); 117 for (String[] opt: options) { 118 optionsList.add(Arrays.copyOf(opt, opt.length)); 119 } 120 Iterator<String[]> optionsIter = optionsList.iterator(); 121 while ( optionsIter.hasNext() ) { 122 if ( !handleOption(optionsIter, errorReporter) ) { 123 return null; 124 } 125 } 126 return optionsList.toArray(new String[optionsList.size()][]); 127 } 128 129 protected boolean handleOption(Iterator<String[]> optionsIter, DocErrorReporter errorReporter) { 130 String[] opt = optionsIter.next(); 131 switch (opt[0]) { 132 133 // Standard options that we need to know about (i.e. copy) 134 case OPT_ENCODING: 135 try { 136 encoding = Charset.forName(opt[1]); 137 } 138 catch ( IllegalCharsetNameException e ) { 139 errorReporter.printError("Illegal charset: " + opt[1]); 140 return false; 141 } 142 return true; 143 144 case OPT_OUTPUT_DIR: 145 if ( destinationDir != null ) { 146 errorReporter.printError(OPT_OUTPUT_DIR + " may only be specified once"); 147 } 148 setDestinationDir(new File(opt[1])); 149 return true; 150 151 case OPT_STYLESHEETFILE: 152 if ( stylesheetFile != null ) { 153 errorReporter.printError(OPT_STYLESHEETFILE + " may only specified once"); 154 } 155 setStylesheetFile(new File(opt[1])); 156 return true; 157 158 case OPT_TAG: 159 Matcher matcher = OPT_TAG_PATTERN.matcher(opt[1]); 160 if (!matcher.matches()) { 161 return true; 162 } 163 if (!matcher.group("flags").contains("M")) { 164 return true; 165 } 166 markedDownTags.add(matcher.group("tag").replace("\\", "")); 167 opt[1] = matcher.group("tag") 168 + ":" + matcher.group("flags").replace("M", ""); 169 if (matcher.group("title") != null) { 170 opt[1] += ":" + matcher.group("title"); 171 } 172 return true; 173 174 // Standard options that we consume (i.e. don't forward) 175 case OPT_OVERVIEW: 176 if ( getOverviewFile() != null ) { 177 errorReporter.printError(OPT_OVERVIEW + " may only be specified once"); 178 return false; 179 } 180 setOverviewFile(new File(opt[1])); 181 optionsIter.remove(); 182 return true; 183 184 // Our own options 185 case OPT_MARKDOWN_PROCESSOR: 186 if (processorUsed) { 187 errorReporter.printError("Markdown processor cannot be changed" 188 + " after setting its options"); 189 return false; 190 } 191 MarkdownProcessor p = createProcessor(opt, errorReporter); 192 if (p == null) { 193 return false; 194 } 195 processor = p; 196 return true; 197 198 case OPT_DISABLE_HIGHLIGHT: 199 highlightEnabled = false; 200 optionsIter.remove(); 201 return true; 202 203 case OPT_DISABLE_AUTO_HIGHLIGHT: 204 if (processor.isSupportedOption 205 (MarkdownProcessor.INTERNAL_OPT_DISABLE_AUTO_HIGHLIGHT) == -1) { 206 return false; 207 } 208 processorOptions.add(new String[] 209 { MarkdownProcessor.INTERNAL_OPT_DISABLE_AUTO_HIGHLIGHT }); 210 optionsIter.remove(); 211 return true; 212 213 case OPT_HIGHLIGHT_STYLE: 214 if ( highlightStyle != null ) { 215 errorReporter.printError("Only one " + OPT_HIGHLIGHT_STYLE 216 + " option allowed"); 217 return false; 218 } 219 highlightStyle = opt[1]; 220 optionsIter.remove(); 221 222 default: 223 break; 224 } 225 226 if (opt[0].startsWith("-M")) { 227 // Processor (type) may not be changed after providing options for it 228 processorUsed = true; 229 String[] pOpt = unpackOption(opt[0].substring(2)); 230 if (processor.isSupportedOption(pOpt[0]) == -1) { 231 return false; 232 } 233 processorOptions.add(pOpt); 234 optionsIter.remove(); 235 return true; 236 } 237 238 return true; 239 } 240 241 private static String[] unpackOption(String option) { 242 List<String> opts = new ArrayList<>(); 243 String[] eSplit = option.split("=", 2); 244 opts.add(eSplit[0]); 245 if (eSplit.length > 1) { 246 String[] cSplit = eSplit[1].split("(?<!\\\\),"); 247 Arrays.stream(cSplit).map(s -> s.replace("\\,", ",")) 248 .forEach(s -> opts.add(s)); 249 } 250 return opts.toArray(new String[opts.size()]); 251 } 252 253 private MarkdownProcessor createProcessor(String[] opt, DocErrorReporter errorReporter) { 254 try { 255 @SuppressWarnings("unchecked") 256 Class<MarkdownProcessor> mpc = (Class<MarkdownProcessor>) 257 getClass().getClassLoader().loadClass(opt[1]); 258 return (MarkdownProcessor)mpc.newInstance(); 259 } catch (ClassNotFoundException | InstantiationException 260 | IllegalAccessException | ClassCastException e) { 261 errorReporter.printError("Markdown processor \"" + opt[1] 262 + "\" cannot be loaded (" + e.getMessage() 263 + "), check name and docletpath"); 264 return null; 265 } 266 } 267 268 /** 269 * Returns the processor selected by the options. 270 * 271 * @return the processor 272 */ 273 public MarkdownProcessor getProcessor() { 274 return processor; 275 } 276 277 /** 278 * Returns the markdown processor options filtered out in 279 * {@link #load(String[][], DocErrorReporter)}. 280 * 281 * @return the options 282 */ 283 public String[][] getProcessorOptions() { 284 return processorOptions.toArray(new String[processorOptions.size()][]); 285 } 286 287 /** 288 * Returns the names of the tags that have been seen in the `-tag` options 289 * with an "`M`" flag. 290 * 291 * @return the tags 292 */ 293 public String[] getMarkedDownTags() { 294 return markedDownTags.toArray(new String[markedDownTags.size()]); 295 } 296 297 /** 298 * Gets the overview file. 299 * 300 * @return The overview file. 301 */ 302 public File getOverviewFile() { 303 return overviewFile; 304 } 305 306 /** 307 * Sets the overview file. 308 * 309 * @param overviewFile The overview file. 310 */ 311 public void setOverviewFile(File overviewFile) { 312 this.overviewFile = overviewFile; 313 } 314 315 /** 316 * Gets the source encoding. 317 * 318 * @return The source encoding. 319 */ 320 public Charset getEncoding() { 321 return encoding != null ? encoding : Charset.defaultCharset(); 322 } 323 324 /** 325 * Sets the source encoding. 326 * 327 * @param encoding The source encoding. 328 */ 329 public void setEncoding(Charset encoding) { 330 this.encoding = encoding; 331 } 332 333 /** 334 * Gets the destination directory. 335 * 336 * @return The destination directory. 337 */ 338 public File getDestinationDir() { 339 if ( destinationDir == null ) { 340 destinationDir = new File(System.getProperty("user.dir")); 341 } 342 return destinationDir; 343 } 344 345 /** 346 * Sets the destination directory. 347 * 348 * @param destinationDir The destination directory 349 */ 350 public void setDestinationDir(File destinationDir) { 351 this.destinationDir = destinationDir; 352 } 353 354 /** 355 * Gets the CSS stylesheet file. 356 * 357 * @return The stylesheet file. 358 */ 359 public File getStylesheetFile() { 360 return stylesheetFile; 361 } 362 363 /** 364 * Sets the CSS stylesheet file. 365 * 366 * @param stylesheetFile The stylesheet file. 367 */ 368 public void setStylesheetFile(File stylesheetFile) { 369 this.stylesheetFile = stylesheetFile; 370 } 371 372 public boolean isHighlightEnabled() { 373 return highlightEnabled; 374 } 375 376 public void setHighlightEnabled(boolean highlightEnabled) { 377 this.highlightEnabled = highlightEnabled; 378 } 379 380 public String getHighlightStyle() { 381 return highlightStyle != null ? highlightStyle : "default"; 382 } 383 384 public void setHighlightStyle(String highlightStyle) { 385 this.highlightStyle = highlightStyle; 386 } 387 388}