| 34 | 34 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; |
| 35 | 35 | import static com.keenwrite.processors.text.TextReplacementFactory.replace; |
| 36 | | |
| 37 | | /** |
| 38 | | * Provides a context for configuring a chain of {@link Processor} instances. |
| 39 | | */ |
| 40 | | public final class ProcessorContext { |
| 41 | | |
| 42 | | private final Mutator mMutator; |
| 43 | | |
| 44 | | /** |
| 45 | | * Determines the file type from the path extension. This should only be |
| 46 | | * called when it is known that the file type won't be a definition file |
| 47 | | * (e.g., YAML or other definition source), but rather an editable file |
| 48 | | * (e.g., Markdown, R Markdown, etc.). |
| 49 | | * |
| 50 | | * @param path The path with a file name extension. |
| 51 | | * @return The FileType for the given path. |
| 52 | | */ |
| 53 | | private static FileType lookup( final Path path ) { |
| 54 | | assert path != null; |
| 55 | | |
| 56 | | final var prefix = GLOB_PREFIX_FILE; |
| 57 | | final var keys = sSettings.getKeys( prefix ); |
| 58 | | |
| 59 | | var found = false; |
| 60 | | var fileType = UNKNOWN; |
| 61 | | |
| 62 | | while( keys.hasNext() && !found ) { |
| 63 | | final var key = keys.next(); |
| 64 | | final var patterns = sSettings.getStringSettingList( key ); |
| 65 | | final var predicate = createFileTypePredicate( patterns ); |
| 66 | | |
| 67 | | if( predicate.test( toFile( path ) ) ) { |
| 68 | | // Remove the EXTENSIONS_PREFIX to get the file name extension mapped |
| 69 | | // to a standard name (as defined in the settings.properties file). |
| 70 | | final String suffix = key.replace( prefix + '.', "" ); |
| 71 | | fileType = FileType.from( suffix ); |
| 72 | | found = true; |
| 73 | | } |
| 74 | | } |
| 75 | | |
| 76 | | return fileType; |
| 77 | | } |
| 78 | | |
| 79 | | public boolean isExportFormat( final ExportFormat exportFormat ) { |
| 80 | | return mMutator.mExportFormat == exportFormat; |
| 81 | | } |
| 82 | | |
| 83 | | /** |
| 84 | | * Responsible for populating the instance variables required by the |
| 85 | | * context. |
| 86 | | */ |
| 87 | | public static class Mutator { |
| 88 | | private Path mSourcePath; |
| 89 | | private Path mTargetPath; |
| 90 | | private ExportFormat mExportFormat; |
| 91 | | private Supplier<Boolean> mConcatenate = () -> true; |
| 92 | | private Supplier<String> mChapters = () -> ""; |
| 93 | | |
| 94 | | private Supplier<Path> mThemeDir = USER_DIRECTORY::toPath; |
| 95 | | private Supplier<Locale> mLocale = () -> Locale.ENGLISH; |
| 96 | | |
| 97 | | private Supplier<Map<String, String>> mDefinitions = HashMap::new; |
| 98 | | private Supplier<Map<String, String>> mMetadata = HashMap::new; |
| 99 | | private Supplier<Caret> mCaret = () -> Caret.builder().build(); |
| 100 | | |
| 101 | | private Supplier<Path> mImageDir = USER_DIRECTORY::toPath; |
| 102 | | private Supplier<String> mImageServer = () -> DIAGRAM_SERVER_NAME; |
| 103 | | private Supplier<String> mImageOrder = () -> PERSIST_IMAGES_DEFAULT; |
| 104 | | private Supplier<Path> mCacheDir = USER_CACHE_DIR::toPath; |
| 105 | | private Supplier<Path> mFontDir = () -> getFontDirectory().toPath(); |
| 106 | | |
| 107 | | private Supplier<String> mEnableMode = () -> ""; |
| 108 | | |
| 109 | | private Supplier<String> mSigilBegan = () -> DEF_DELIM_BEGAN_DEFAULT; |
| 110 | | private Supplier<String> mSigilEnded = () -> DEF_DELIM_ENDED_DEFAULT; |
| 111 | | |
| 112 | | private Supplier<Path> mRWorkingDir = USER_DIRECTORY::toPath; |
| 113 | | private Supplier<String> mRScript = () -> ""; |
| 114 | | |
| 115 | | private Supplier<Boolean> mCurlQuotes = () -> true; |
| 116 | | private Supplier<Boolean> mAutoRemove = () -> true; |
| 117 | | |
| 118 | | public void setSourcePath( final Path sourcePath ) { |
| 119 | | assert sourcePath != null; |
| 120 | | mSourcePath = sourcePath; |
| 121 | | } |
| 122 | | |
| 123 | | public void setTargetPath( final Path outputPath ) { |
| 124 | | assert outputPath != null; |
| 125 | | mTargetPath = outputPath; |
| 126 | | } |
| 127 | | |
| 128 | | public void setThemeDir( final Supplier<Path> themeDir ) { |
| 129 | | assert themeDir != null; |
| 130 | | mThemeDir = themeDir; |
| 131 | | } |
| 132 | | |
| 133 | | public void setCacheDir( final Supplier<File> cacheDir ) { |
| 134 | | assert cacheDir != null; |
| 135 | | |
| 136 | | mCacheDir = () -> { |
| 137 | | final var dir = cacheDir.get(); |
| 138 | | |
| 139 | | return (dir == null ? toFile( USER_DATA_DIR ) : dir).toPath(); |
| 140 | | }; |
| 141 | | } |
| 142 | | |
| 143 | | public void setImageDir( final Supplier<File> imageDir ) { |
| 144 | | assert imageDir != null; |
| 145 | | |
| 146 | | mImageDir = () -> { |
| 147 | | final var dir = imageDir.get(); |
| 148 | | |
| 149 | | return (dir == null ? USER_DIRECTORY : dir).toPath(); |
| 150 | | }; |
| 151 | | } |
| 152 | | |
| 153 | | public void setImageOrder( final Supplier<String> imageOrder ) { |
| 154 | | assert imageOrder != null; |
| 155 | | mImageOrder = imageOrder; |
| 156 | | } |
| 157 | | |
| 158 | | public void setImageServer( final Supplier<String> imageServer ) { |
| 159 | | assert imageServer != null; |
| 160 | | mImageServer = imageServer; |
| 161 | | } |
| 162 | | |
| 163 | | public void setFontDir( final Supplier<File> fontDir ) { |
| 164 | | assert fontDir != null; |
| 165 | | |
| 166 | | mFontDir = () -> { |
| 167 | | final var dir = fontDir.get(); |
| 168 | | |
| 169 | | return (dir == null ? USER_DIRECTORY : dir).toPath(); |
| 170 | | }; |
| 171 | | } |
| 172 | | |
| 173 | | public void setEnableMode( final Supplier<String> enableMode ) { |
| 174 | | assert enableMode != null; |
| 175 | | mEnableMode = enableMode; |
| 176 | | } |
| 177 | | |
| 178 | | public void setExportFormat( final ExportFormat exportFormat ) { |
| 179 | | assert exportFormat != null; |
| 180 | | mExportFormat = exportFormat; |
| 181 | | } |
| 182 | | |
| 183 | | public void setConcatenate( final Supplier<Boolean> concatenate ) { |
| 184 | | mConcatenate = concatenate; |
| 185 | | } |
| 186 | | |
| 187 | | public void setChapters( final Supplier<String> chapters ) { |
| 188 | | mChapters = chapters; |
| 189 | | } |
| 190 | | |
| 191 | | public void setLocale( final Supplier<Locale> locale ) { |
| 192 | | assert locale != null; |
| 193 | | mLocale = locale; |
| 194 | | } |
| 195 | | |
| 196 | | /** |
| 197 | | * Sets the list of fully interpolated key-value pairs to use when |
| 198 | | * substituting variable names back into the document as variable values. |
| 199 | | * This uses a {@link Callable} reference so that GUI and command-line |
| 200 | | * usage can insert their respective behaviours. That is, this method |
| 201 | | * prevents coupling the GUI to the CLI. |
| 202 | | * |
| 203 | | * @param supplier Defines how to retrieve the definitions. |
| 204 | | */ |
| 205 | | public void setDefinitions( final Supplier<Map<String, String>> supplier ) { |
| 206 | | assert supplier != null; |
| 207 | | mDefinitions = supplier; |
| 208 | | } |
| 209 | | |
| 210 | | /** |
| 211 | | * Sets metadata to use in the document header. These are made available |
| 212 | | * to the typesetting engine as {@code \documentvariable} values. |
| 213 | | * |
| 214 | | * @param metadata The key/value pairs to publish as document metadata. |
| 215 | | */ |
| 216 | | public void setMetadata( final Supplier<Map<String, String>> metadata ) { |
| 217 | | assert metadata != null; |
| 218 | | mMetadata = metadata.get() == null ? HashMap::new : metadata; |
| 219 | | } |
| 220 | | |
| 221 | | /** |
| 222 | | * Sets document variables to use when building the document. These |
| 223 | | * variables will override existing key/value pairs, or be added as |
| 224 | | * new key/value pairs if not already defined. This allows users to |
| 225 | | * inject variables into the document from the command-line, allowing |
| 226 | | * for dynamic assignment of in-text values when building documents. |
| 227 | | * |
| 228 | | * @param overrides The key/value pairs to add (or override) as variables. |
| 229 | | */ |
| 230 | | public void setOverrides( final Supplier<Map<String, String>> overrides ) { |
| 231 | | assert overrides != null; |
| 232 | | assert mDefinitions != null; |
| 233 | | assert mDefinitions.get() != null; |
| 234 | | |
| 235 | | final var map = overrides.get(); |
| 236 | | |
| 237 | | if( map != null ) { |
| 238 | | mDefinitions.get().putAll( map ); |
| 239 | | } |
| 240 | | } |
| 241 | | |
| 242 | | /** |
| 243 | | * Sets the source for deriving the {@link Caret}. Typically, this is |
| 244 | | * the text editor that has focus. |
| 245 | | * |
| 246 | | * @param caret The source for the currently active caret. |
| 247 | | */ |
| 248 | | public void setCaret( final Supplier<Caret> caret ) { |
| 249 | | assert caret != null; |
| 250 | | mCaret = caret; |
| 251 | | } |
| 252 | | |
| 253 | | public void setSigilBegan( final Supplier<String> sigilBegan ) { |
| 254 | | assert sigilBegan != null; |
| 255 | | mSigilBegan = sigilBegan; |
| 256 | | } |
| 257 | | |
| 258 | | public void setSigilEnded( final Supplier<String> sigilEnded ) { |
| 259 | | assert sigilEnded != null; |
| 260 | | mSigilEnded = sigilEnded; |
| 261 | | } |
| 262 | | |
| 263 | | public void setRWorkingDir( final Supplier<Path> rWorkingDir ) { |
| 264 | | assert rWorkingDir != null; |
| 265 | | mRWorkingDir = rWorkingDir; |
| 266 | | } |
| 267 | | |
| 268 | | public void setRScript( final Supplier<String> rScript ) { |
| 269 | | assert rScript != null; |
| 270 | | mRScript = rScript; |
| 271 | | } |
| 272 | | |
| 273 | | public void setCurlQuotes( final Supplier<Boolean> curlQuotes ) { |
| 274 | | assert curlQuotes != null; |
| 275 | | mCurlQuotes = curlQuotes; |
| 276 | | } |
| 277 | | |
| 278 | | public void setAutoRemove( final Supplier<Boolean> autoRemove ) { |
| 279 | | assert autoRemove != null; |
| 280 | | mAutoRemove = autoRemove; |
| 281 | | } |
| 282 | | |
| 283 | | private boolean isExportFormat( final ExportFormat format ) { |
| 284 | | return mExportFormat == format; |
| 285 | | } |
| 286 | | } |
| 287 | | |
| 288 | | public static GenericBuilder<Mutator, ProcessorContext> builder() { |
| 289 | | return GenericBuilder.of( Mutator::new, ProcessorContext::new ); |
| 290 | | } |
| 291 | | |
| 292 | | /** |
| 293 | | * Creates a new context for use by the {@link ProcessorFactory} when |
| 294 | | * instantiating new {@link Processor} instances. Although all the |
| 295 | | * parameters are required, not all {@link Processor} instances will use |
| 296 | | * all parameters. |
| 297 | | */ |
| 298 | | private ProcessorContext( final Mutator mutator ) { |
| 299 | | assert mutator != null; |
| 300 | | |
| 301 | | mMutator = mutator; |
| 302 | | } |
| 303 | | |
| 304 | | public Path getSourcePath() { |
| 305 | | return mMutator.mSourcePath; |
| 306 | | } |
| 307 | | |
| 308 | | /** |
| 309 | | * Answers what type of input document is to be processed. |
| 310 | | * |
| 311 | | * @return The input document's {@link MediaType}. |
| 312 | | */ |
| 313 | | public MediaType getSourceType() { |
| 314 | | return MediaTypeExtension.fromPath( mMutator.mSourcePath ); |
| 315 | | } |
| 316 | | |
| 317 | | /** |
| 318 | | * Fully qualified file name to use when exporting (e.g., document.pdf). |
| 319 | | * |
| 320 | | * @return Full path to a file name. |
| 321 | | */ |
| 322 | | public Path getTargetPath() { |
| 323 | | return mMutator.mTargetPath; |
| 324 | | } |
| 325 | | |
| 326 | | public ExportFormat getExportFormat() { |
| 327 | | return mMutator.mExportFormat; |
| 328 | | } |
| 329 | | |
| 330 | | public Locale getLocale() { |
| 331 | | return mMutator.mLocale.get(); |
| 332 | | } |
| 333 | | |
| 334 | | /** |
| 335 | | * Returns the variable map of definitions, without interpolation. |
| 336 | | * |
| 337 | | * @return A map to help dereference variables. |
| 338 | | */ |
| 339 | | public Map<String, String> getDefinitions() { |
| 340 | | return mMutator.mDefinitions.get(); |
| 341 | | } |
| 342 | | |
| 343 | | /** |
| 344 | | * Returns the variable map of definitions, with interpolation. |
| 345 | | * |
| 346 | | * @return A map to help dereference variables. |
| 347 | | */ |
| 348 | | public InterpolatingMap getInterpolatedDefinitions() { |
| 349 | | return new InterpolatingMap( |
| 350 | | createDefinitionKeyOperator(), getDefinitions() |
| 351 | | ).interpolate(); |
| 352 | | } |
| 353 | | |
| 354 | | public Map<String, String> getMetadata() { |
| 355 | | return mMutator.mMetadata.get(); |
| 356 | | } |
| 357 | | |
| 358 | | /** |
| 359 | | * Returns the current caret position in the document being edited and is |
| 360 | | * always up-to-date. |
| 361 | | * |
| 362 | | * @return Caret position in the document. |
| 363 | | */ |
| 364 | | public Supplier<Caret> getCaret() { |
| 365 | | return mMutator.mCaret; |
| 366 | | } |
| 367 | | |
| 368 | | /** |
| 369 | | * Returns the directory that contains the file being edited. When |
| 370 | | * {@link Constants#DOCUMENT_DEFAULT} is created, the parent path is |
| 371 | | * {@code null}. This will get absolute path to the file before trying to |
| 372 | | * get te parent path, which should always be a valid path. In the unlikely |
| 373 | | * event that the base path cannot be determined by the path alone, the |
| 374 | | * default user directory is returned. This is necessary for the creation |
| 375 | | * of new files. |
| 376 | | * |
| 377 | | * @return Path to the directory containing a file being edited, or the |
| 378 | | * default user directory if the base path cannot be determined. |
| 379 | | */ |
| 380 | | public Path getBaseDir() { |
| 381 | | final var path = getSourcePath().toAbsolutePath().getParent(); |
| 382 | | return path == null ? DEFAULT_DIRECTORY : path; |
| 383 | | } |
| 384 | | |
| 385 | | FileType getSourceFileType() { |
| 386 | | return lookup( getSourcePath() ); |
| 387 | | } |
| 388 | | |
| 389 | | public Path getThemeDir() { |
| 390 | | return mMutator.mThemeDir.get(); |
| 391 | | } |
| 392 | | |
| 393 | | public Path getImageDir() { |
| 394 | | return mMutator.mImageDir.get(); |
| 395 | | } |
| 396 | | |
| 397 | | public Path getCacheDir() { |
| 398 | | return mMutator.mCacheDir.get(); |
| 399 | | } |
| 400 | | |
| 401 | | public Iterable<String> getImageOrder() { |
| 402 | | assert mMutator.mImageOrder != null; |
| 403 | | |
| 404 | | final var order = mMutator.mImageOrder.get(); |
| 405 | | final var token = order.contains( "," ) ? ',' : ' '; |
| 406 | | |
| 407 | | return Splitter.on( token ).split( token + order ); |
| 408 | | } |
| 409 | | |
| 410 | | public String getImageServer() { |
| 411 | | return mMutator.mImageServer.get(); |
| 412 | | } |
| 413 | | |
| 414 | | public Path getFontDir() { |
| 415 | | return mMutator.mFontDir.get(); |
| 416 | | } |
| 417 | | |
| 418 | | public String getEnableMode() { |
| 419 | | final var processor = new VariableProcessor( IDENTITY, this ); |
| 420 | | final var needles = processor.getDefinitions(); |
| 421 | | final var haystack = mMutator.mEnableMode.get(); |
| 422 | | final var result = replace( haystack, needles ); |
| 423 | | |
| 424 | | // If no replacement was made, then the mode variable isn't set. |
| 425 | | return result.equals( haystack ) ? "" : result; |
| 36 | import static com.keenwrite.util.Strings.sanitize; |
| 37 | |
| 38 | /** |
| 39 | * Provides a context for configuring a chain of {@link Processor} instances. |
| 40 | */ |
| 41 | public final class ProcessorContext { |
| 42 | |
| 43 | private final Mutator mMutator; |
| 44 | |
| 45 | /** |
| 46 | * Determines the file type from the path extension. This should only be |
| 47 | * called when it is known that the file type won't be a definition file |
| 48 | * (e.g., YAML or other definition source), but rather an editable file |
| 49 | * (e.g., Markdown, R Markdown, etc.). |
| 50 | * |
| 51 | * @param path The path with a file name extension. |
| 52 | * @return The FileType for the given path. |
| 53 | */ |
| 54 | private static FileType lookup( final Path path ) { |
| 55 | assert path != null; |
| 56 | |
| 57 | final var prefix = GLOB_PREFIX_FILE; |
| 58 | final var keys = sSettings.getKeys( prefix ); |
| 59 | |
| 60 | var found = false; |
| 61 | var fileType = UNKNOWN; |
| 62 | |
| 63 | while( keys.hasNext() && !found ) { |
| 64 | final var key = keys.next(); |
| 65 | final var patterns = sSettings.getStringSettingList( key ); |
| 66 | final var predicate = createFileTypePredicate( patterns ); |
| 67 | |
| 68 | if( predicate.test( toFile( path ) ) ) { |
| 69 | // Remove the EXTENSIONS_PREFIX to get the file name extension mapped |
| 70 | // to a standard name (as defined in the settings.properties file). |
| 71 | final String suffix = key.replace( prefix + '.', "" ); |
| 72 | fileType = FileType.from( suffix ); |
| 73 | found = true; |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | return fileType; |
| 78 | } |
| 79 | |
| 80 | public boolean isExportFormat( final ExportFormat exportFormat ) { |
| 81 | return mMutator.mExportFormat == exportFormat; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Responsible for populating the instance variables required by the |
| 86 | * context. |
| 87 | */ |
| 88 | public static class Mutator { |
| 89 | private Path mSourcePath; |
| 90 | private Path mTargetPath; |
| 91 | private ExportFormat mExportFormat; |
| 92 | private Supplier<Boolean> mConcatenate = () -> true; |
| 93 | private Supplier<String> mChapters = () -> ""; |
| 94 | |
| 95 | private Supplier<Path> mThemeDir = USER_DIRECTORY::toPath; |
| 96 | private Supplier<Locale> mLocale = () -> Locale.ENGLISH; |
| 97 | |
| 98 | private Supplier<Map<String, String>> mDefinitions = HashMap::new; |
| 99 | private Supplier<Map<String, String>> mMetadata = HashMap::new; |
| 100 | private Supplier<Caret> mCaret = () -> Caret.builder().build(); |
| 101 | |
| 102 | private Supplier<Path> mImageDir = USER_DIRECTORY::toPath; |
| 103 | private Supplier<String> mImageServer = () -> DIAGRAM_SERVER_NAME; |
| 104 | private Supplier<String> mImageOrder = () -> PERSIST_IMAGES_DEFAULT; |
| 105 | private Supplier<Path> mCacheDir = USER_CACHE_DIR::toPath; |
| 106 | private Supplier<Path> mFontDir = () -> getFontDirectory().toPath(); |
| 107 | |
| 108 | private Supplier<String> mModesEnabled = () -> ""; |
| 109 | |
| 110 | private Supplier<String> mSigilBegan = () -> DEF_DELIM_BEGAN_DEFAULT; |
| 111 | private Supplier<String> mSigilEnded = () -> DEF_DELIM_ENDED_DEFAULT; |
| 112 | |
| 113 | private Supplier<Path> mRWorkingDir = USER_DIRECTORY::toPath; |
| 114 | private Supplier<String> mRScript = () -> ""; |
| 115 | |
| 116 | private Supplier<Boolean> mCurlQuotes = () -> true; |
| 117 | private Supplier<Boolean> mAutoRemove = () -> true; |
| 118 | |
| 119 | public void setSourcePath( final Path sourcePath ) { |
| 120 | assert sourcePath != null; |
| 121 | mSourcePath = sourcePath; |
| 122 | } |
| 123 | |
| 124 | public void setTargetPath( final Path outputPath ) { |
| 125 | assert outputPath != null; |
| 126 | mTargetPath = outputPath; |
| 127 | } |
| 128 | |
| 129 | public void setThemeDir( final Supplier<Path> themeDir ) { |
| 130 | assert themeDir != null; |
| 131 | mThemeDir = themeDir; |
| 132 | } |
| 133 | |
| 134 | public void setCacheDir( final Supplier<File> cacheDir ) { |
| 135 | assert cacheDir != null; |
| 136 | |
| 137 | mCacheDir = () -> { |
| 138 | final var dir = cacheDir.get(); |
| 139 | |
| 140 | return (dir == null ? toFile( USER_DATA_DIR ) : dir).toPath(); |
| 141 | }; |
| 142 | } |
| 143 | |
| 144 | public void setImageDir( final Supplier<File> imageDir ) { |
| 145 | assert imageDir != null; |
| 146 | |
| 147 | mImageDir = () -> { |
| 148 | final var dir = imageDir.get(); |
| 149 | |
| 150 | return (dir == null ? USER_DIRECTORY : dir).toPath(); |
| 151 | }; |
| 152 | } |
| 153 | |
| 154 | public void setImageOrder( final Supplier<String> imageOrder ) { |
| 155 | assert imageOrder != null; |
| 156 | mImageOrder = imageOrder; |
| 157 | } |
| 158 | |
| 159 | public void setImageServer( final Supplier<String> imageServer ) { |
| 160 | assert imageServer != null; |
| 161 | mImageServer = imageServer; |
| 162 | } |
| 163 | |
| 164 | public void setFontDir( final Supplier<File> fontDir ) { |
| 165 | assert fontDir != null; |
| 166 | |
| 167 | mFontDir = () -> { |
| 168 | final var dir = fontDir.get(); |
| 169 | |
| 170 | return (dir == null ? USER_DIRECTORY : dir).toPath(); |
| 171 | }; |
| 172 | } |
| 173 | |
| 174 | public void setModesEnabled( final Supplier<String> modesEnabled ) { |
| 175 | assert modesEnabled != null; |
| 176 | mModesEnabled = modesEnabled; |
| 177 | } |
| 178 | |
| 179 | public void setExportFormat( final ExportFormat exportFormat ) { |
| 180 | assert exportFormat != null; |
| 181 | mExportFormat = exportFormat; |
| 182 | } |
| 183 | |
| 184 | public void setConcatenate( final Supplier<Boolean> concatenate ) { |
| 185 | mConcatenate = concatenate; |
| 186 | } |
| 187 | |
| 188 | public void setChapters( final Supplier<String> chapters ) { |
| 189 | mChapters = chapters; |
| 190 | } |
| 191 | |
| 192 | public void setLocale( final Supplier<Locale> locale ) { |
| 193 | assert locale != null; |
| 194 | mLocale = locale; |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Sets the list of fully interpolated key-value pairs to use when |
| 199 | * substituting variable names back into the document as variable values. |
| 200 | * This uses a {@link Callable} reference so that GUI and command-line |
| 201 | * usage can insert their respective behaviours. That is, this method |
| 202 | * prevents coupling the GUI to the CLI. |
| 203 | * |
| 204 | * @param supplier Defines how to retrieve the definitions. |
| 205 | */ |
| 206 | public void setDefinitions( final Supplier<Map<String, String>> supplier ) { |
| 207 | assert supplier != null; |
| 208 | mDefinitions = supplier; |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * Sets metadata to use in the document header. These are made available |
| 213 | * to the typesetting engine as {@code \documentvariable} values. |
| 214 | * |
| 215 | * @param metadata The key/value pairs to publish as document metadata. |
| 216 | */ |
| 217 | public void setMetadata( final Supplier<Map<String, String>> metadata ) { |
| 218 | assert metadata != null; |
| 219 | mMetadata = metadata.get() == null ? HashMap::new : metadata; |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | * Sets document variables to use when building the document. These |
| 224 | * variables will override existing key/value pairs, or be added as |
| 225 | * new key/value pairs if not already defined. This allows users to |
| 226 | * inject variables into the document from the command-line, allowing |
| 227 | * for dynamic assignment of in-text values when building documents. |
| 228 | * |
| 229 | * @param overrides The key/value pairs to add (or override) as variables. |
| 230 | */ |
| 231 | public void setOverrides( final Supplier<Map<String, String>> overrides ) { |
| 232 | assert overrides != null; |
| 233 | assert mDefinitions != null; |
| 234 | assert mDefinitions.get() != null; |
| 235 | |
| 236 | final var map = overrides.get(); |
| 237 | |
| 238 | if( map != null ) { |
| 239 | mDefinitions.get().putAll( map ); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | /** |
| 244 | * Sets the source for deriving the {@link Caret}. Typically, this is |
| 245 | * the text editor that has focus. |
| 246 | * |
| 247 | * @param caret The source for the currently active caret. |
| 248 | */ |
| 249 | public void setCaret( final Supplier<Caret> caret ) { |
| 250 | assert caret != null; |
| 251 | mCaret = caret; |
| 252 | } |
| 253 | |
| 254 | public void setSigilBegan( final Supplier<String> sigilBegan ) { |
| 255 | assert sigilBegan != null; |
| 256 | mSigilBegan = sigilBegan; |
| 257 | } |
| 258 | |
| 259 | public void setSigilEnded( final Supplier<String> sigilEnded ) { |
| 260 | assert sigilEnded != null; |
| 261 | mSigilEnded = sigilEnded; |
| 262 | } |
| 263 | |
| 264 | public void setRWorkingDir( final Supplier<Path> rWorkingDir ) { |
| 265 | assert rWorkingDir != null; |
| 266 | mRWorkingDir = rWorkingDir; |
| 267 | } |
| 268 | |
| 269 | public void setRScript( final Supplier<String> rScript ) { |
| 270 | assert rScript != null; |
| 271 | mRScript = rScript; |
| 272 | } |
| 273 | |
| 274 | public void setCurlQuotes( final Supplier<Boolean> curlQuotes ) { |
| 275 | assert curlQuotes != null; |
| 276 | mCurlQuotes = curlQuotes; |
| 277 | } |
| 278 | |
| 279 | public void setAutoRemove( final Supplier<Boolean> autoRemove ) { |
| 280 | assert autoRemove != null; |
| 281 | mAutoRemove = autoRemove; |
| 282 | } |
| 283 | |
| 284 | private boolean isExportFormat( final ExportFormat format ) { |
| 285 | return mExportFormat == format; |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | public static GenericBuilder<Mutator, ProcessorContext> builder() { |
| 290 | return GenericBuilder.of( Mutator::new, ProcessorContext::new ); |
| 291 | } |
| 292 | |
| 293 | /** |
| 294 | * Creates a new context for use by the {@link ProcessorFactory} when |
| 295 | * instantiating new {@link Processor} instances. Although all the |
| 296 | * parameters are required, not all {@link Processor} instances will use |
| 297 | * all parameters. |
| 298 | */ |
| 299 | private ProcessorContext( final Mutator mutator ) { |
| 300 | assert mutator != null; |
| 301 | |
| 302 | mMutator = mutator; |
| 303 | } |
| 304 | |
| 305 | public Path getSourcePath() { |
| 306 | return mMutator.mSourcePath; |
| 307 | } |
| 308 | |
| 309 | /** |
| 310 | * Answers what type of input document is to be processed. |
| 311 | * |
| 312 | * @return The input document's {@link MediaType}. |
| 313 | */ |
| 314 | public MediaType getSourceType() { |
| 315 | return MediaTypeExtension.fromPath( mMutator.mSourcePath ); |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Fully qualified file name to use when exporting (e.g., document.pdf). |
| 320 | * |
| 321 | * @return Full path to a file name. |
| 322 | */ |
| 323 | public Path getTargetPath() { |
| 324 | return mMutator.mTargetPath; |
| 325 | } |
| 326 | |
| 327 | public ExportFormat getExportFormat() { |
| 328 | return mMutator.mExportFormat; |
| 329 | } |
| 330 | |
| 331 | public Locale getLocale() { |
| 332 | return mMutator.mLocale.get(); |
| 333 | } |
| 334 | |
| 335 | /** |
| 336 | * Returns the variable map of definitions, without interpolation. |
| 337 | * |
| 338 | * @return A map to help dereference variables. |
| 339 | */ |
| 340 | public Map<String, String> getDefinitions() { |
| 341 | return mMutator.mDefinitions.get(); |
| 342 | } |
| 343 | |
| 344 | /** |
| 345 | * Returns the variable map of definitions, with interpolation. |
| 346 | * |
| 347 | * @return A map to help dereference variables. |
| 348 | */ |
| 349 | public InterpolatingMap getInterpolatedDefinitions() { |
| 350 | return new InterpolatingMap( |
| 351 | createDefinitionKeyOperator(), getDefinitions() |
| 352 | ).interpolate(); |
| 353 | } |
| 354 | |
| 355 | public Map<String, String> getMetadata() { |
| 356 | return mMutator.mMetadata.get(); |
| 357 | } |
| 358 | |
| 359 | /** |
| 360 | * Returns the current caret position in the document being edited and is |
| 361 | * always up-to-date. |
| 362 | * |
| 363 | * @return Caret position in the document. |
| 364 | */ |
| 365 | public Supplier<Caret> getCaret() { |
| 366 | return mMutator.mCaret; |
| 367 | } |
| 368 | |
| 369 | /** |
| 370 | * Returns the directory that contains the file being edited. When |
| 371 | * {@link Constants#DOCUMENT_DEFAULT} is created, the parent path is |
| 372 | * {@code null}. This will get absolute path to the file before trying to |
| 373 | * get te parent path, which should always be a valid path. In the unlikely |
| 374 | * event that the base path cannot be determined by the path alone, the |
| 375 | * default user directory is returned. This is necessary for the creation |
| 376 | * of new files. |
| 377 | * |
| 378 | * @return Path to the directory containing a file being edited, or the |
| 379 | * default user directory if the base path cannot be determined. |
| 380 | */ |
| 381 | public Path getBaseDir() { |
| 382 | final var path = getSourcePath().toAbsolutePath().getParent(); |
| 383 | return path == null ? DEFAULT_DIRECTORY : path; |
| 384 | } |
| 385 | |
| 386 | FileType getSourceFileType() { |
| 387 | return lookup( getSourcePath() ); |
| 388 | } |
| 389 | |
| 390 | public Path getThemeDir() { |
| 391 | return mMutator.mThemeDir.get(); |
| 392 | } |
| 393 | |
| 394 | public Path getImageDir() { |
| 395 | return mMutator.mImageDir.get(); |
| 396 | } |
| 397 | |
| 398 | public Path getCacheDir() { |
| 399 | return mMutator.mCacheDir.get(); |
| 400 | } |
| 401 | |
| 402 | public Iterable<String> getImageOrder() { |
| 403 | assert mMutator.mImageOrder != null; |
| 404 | |
| 405 | final var order = mMutator.mImageOrder.get(); |
| 406 | final var token = order.contains( "," ) ? ',' : ' '; |
| 407 | |
| 408 | return Splitter.on( token ).split( token + order ); |
| 409 | } |
| 410 | |
| 411 | public String getImageServer() { |
| 412 | return mMutator.mImageServer.get(); |
| 413 | } |
| 414 | |
| 415 | public Path getFontDir() { |
| 416 | return mMutator.mFontDir.get(); |
| 417 | } |
| 418 | |
| 419 | public String getModesEnabled() { |
| 420 | // Force the processor to select particular sigils. |
| 421 | final var processor = new VariableProcessor( IDENTITY, this ); |
| 422 | final var needles = processor.getDefinitions(); |
| 423 | final var haystack = sanitize( mMutator.mModesEnabled.get() ); |
| 424 | |
| 425 | return needles.containsKey( haystack ) |
| 426 | ? replace( haystack, needles ) |
| 427 | : processor.hasSigils( haystack ) |
| 428 | ? "" |
| 429 | : haystack; |
| 426 | 430 | } |
| 427 | 431 | |