1# $Id: core.py 8367 2019-08-27 12:09:56Z milde $ 2# Author: David Goodger <goodger@python.org> 3# Copyright: This module has been placed in the public domain. 4 5""" 6Calling the ``publish_*`` convenience functions (or instantiating a 7`Publisher` object) with component names will result in default 8behavior. For custom behavior (setting component options), create 9custom component objects first, and pass *them* to 10``publish_*``/`Publisher`. See `The Docutils Publisher`_. 11 12.. _The Docutils Publisher: http://docutils.sf.net/docs/api/publisher.html 13""" 14from __future__ import print_function 15 16__docformat__ = 'reStructuredText' 17 18import sys 19import pprint 20from docutils import __version__, __version_details__, SettingsSpec 21from docutils import frontend, io, utils, readers, writers 22from docutils.frontend import OptionParser 23from docutils.transforms import Transformer 24from docutils.utils.error_reporting import ErrorOutput, ErrorString 25import docutils.readers.doctree 26 27class Publisher(object): 28 29 """ 30 A facade encapsulating the high-level logic of a Docutils system. 31 """ 32 33 def __init__(self, reader=None, parser=None, writer=None, 34 source=None, source_class=io.FileInput, 35 destination=None, destination_class=io.FileOutput, 36 settings=None): 37 """ 38 Initial setup. If any of `reader`, `parser`, or `writer` are not 39 specified, the corresponding ``set_...`` method should be called with 40 a component name (`set_reader` sets the parser as well). 41 """ 42 43 self.document = None 44 """The document tree (`docutils.nodes` objects).""" 45 46 self.reader = reader 47 """A `docutils.readers.Reader` instance.""" 48 49 self.parser = parser 50 """A `docutils.parsers.Parser` instance.""" 51 52 self.writer = writer 53 """A `docutils.writers.Writer` instance.""" 54 55 for component in 'reader', 'parser', 'writer': 56 assert not isinstance(getattr(self, component), str), ( 57 'passed string "%s" as "%s" parameter; pass an instance, ' 58 'or use the "%s_name" parameter instead (in ' 59 'docutils.core.publish_* convenience functions).' 60 % (getattr(self, component), component, component)) 61 62 self.source = source 63 """The source of input data, a `docutils.io.Input` instance.""" 64 65 self.source_class = source_class 66 """The class for dynamically created source objects.""" 67 68 self.destination = destination 69 """The destination for docutils output, a `docutils.io.Output` 70 instance.""" 71 72 self.destination_class = destination_class 73 """The class for dynamically created destination objects.""" 74 75 self.settings = settings 76 """An object containing Docutils settings as instance attributes. 77 Set by `self.process_command_line()` or `self.get_settings()`.""" 78 79 self._stderr = ErrorOutput() 80 81 def set_reader(self, reader_name, parser, parser_name): 82 """Set `self.reader` by name.""" 83 reader_class = readers.get_reader_class(reader_name) 84 self.reader = reader_class(parser, parser_name) 85 self.parser = self.reader.parser 86 87 def set_writer(self, writer_name): 88 """Set `self.writer` by name.""" 89 writer_class = writers.get_writer_class(writer_name) 90 self.writer = writer_class() 91 92 def set_components(self, reader_name, parser_name, writer_name): 93 if self.reader is None: 94 self.set_reader(reader_name, self.parser, parser_name) 95 if self.parser is None: 96 if self.reader.parser is None: 97 self.reader.set_parser(parser_name) 98 self.parser = self.reader.parser 99 if self.writer is None: 100 self.set_writer(writer_name) 101 102 def setup_option_parser(self, usage=None, description=None, 103 settings_spec=None, config_section=None, 104 **defaults): 105 if config_section: 106 if not settings_spec: 107 settings_spec = SettingsSpec() 108 settings_spec.config_section = config_section 109 parts = config_section.split() 110 if len(parts) > 1 and parts[-1] == 'application': 111 settings_spec.config_section_dependencies = ['applications'] 112 #@@@ Add self.source & self.destination to components in future? 113 option_parser = OptionParser( 114 components=(self.parser, self.reader, self.writer, settings_spec), 115 defaults=defaults, read_config_files=True, 116 usage=usage, description=description) 117 return option_parser 118 119 def get_settings(self, usage=None, description=None, 120 settings_spec=None, config_section=None, **defaults): 121 """ 122 Set and return default settings (overrides in `defaults` dict). 123 124 Set components first (`self.set_reader` & `self.set_writer`). 125 Explicitly setting `self.settings` disables command line option 126 processing from `self.publish()`. 127 """ 128 option_parser = self.setup_option_parser( 129 usage, description, settings_spec, config_section, **defaults) 130 self.settings = option_parser.get_default_values() 131 return self.settings 132 133 def process_programmatic_settings(self, settings_spec, 134 settings_overrides, 135 config_section): 136 if self.settings is None: 137 defaults = (settings_overrides or {}).copy() 138 # Propagate exceptions by default when used programmatically: 139 defaults.setdefault('traceback', True) 140 self.get_settings(settings_spec=settings_spec, 141 config_section=config_section, 142 **defaults) 143 144 def process_command_line(self, argv=None, usage=None, description=None, 145 settings_spec=None, config_section=None, 146 **defaults): 147 """ 148 Pass an empty list to `argv` to avoid reading `sys.argv` (the 149 default). 150 151 Set components first (`self.set_reader` & `self.set_writer`). 152 """ 153 option_parser = self.setup_option_parser( 154 usage, description, settings_spec, config_section, **defaults) 155 if argv is None: 156 argv = sys.argv[1:] 157 # converting to Unicode (Python 3 does this automatically): 158 if sys.version_info < (3, 0): 159 # TODO: make this failsafe and reversible? 160 argv_encoding = (frontend.locale_encoding or 'ascii') 161 argv = [a.decode(argv_encoding) for a in argv] 162 self.settings = option_parser.parse_args(argv) 163 164 def set_io(self, source_path=None, destination_path=None): 165 if self.source is None: 166 self.set_source(source_path=source_path) 167 if self.destination is None: 168 self.set_destination(destination_path=destination_path) 169 170 def set_source(self, source=None, source_path=None): 171 if source_path is None: 172 source_path = self.settings._source 173 else: 174 self.settings._source = source_path 175 # Raise IOError instead of system exit with `tracback == True` 176 # TODO: change io.FileInput's default behaviour and remove this hack 177 try: 178 self.source = self.source_class( 179 source=source, source_path=source_path, 180 encoding=self.settings.input_encoding) 181 except TypeError: 182 self.source = self.source_class( 183 source=source, source_path=source_path, 184 encoding=self.settings.input_encoding) 185 186 def set_destination(self, destination=None, destination_path=None): 187 if destination_path is None: 188 destination_path = self.settings._destination 189 else: 190 self.settings._destination = destination_path 191 self.destination = self.destination_class( 192 destination=destination, destination_path=destination_path, 193 encoding=self.settings.output_encoding, 194 error_handler=self.settings.output_encoding_error_handler) 195 196 def apply_transforms(self): 197 self.document.transformer.populate_from_components( 198 (self.source, self.reader, self.reader.parser, self.writer, 199 self.destination)) 200 self.document.transformer.apply_transforms() 201 202 def publish(self, argv=None, usage=None, description=None, 203 settings_spec=None, settings_overrides=None, 204 config_section=None, enable_exit_status=False): 205 """ 206 Process command line options and arguments (if `self.settings` not 207 already set), run `self.reader` and then `self.writer`. Return 208 `self.writer`'s output. 209 """ 210 exit = None 211 try: 212 if self.settings is None: 213 self.process_command_line( 214 argv, usage, description, settings_spec, config_section, 215 **(settings_overrides or {})) 216 self.set_io() 217 self.document = self.reader.read(self.source, self.parser, 218 self.settings) 219 self.apply_transforms() 220 output = self.writer.write(self.document, self.destination) 221 self.writer.assemble_parts() 222 except SystemExit as error: 223 exit = 1 224 exit_status = error.code 225 except Exception as error: 226 if not self.settings: # exception too early to report nicely 227 raise 228 if self.settings.traceback: # Propagate exceptions? 229 self.debugging_dumps() 230 raise 231 self.report_Exception(error) 232 exit = True 233 exit_status = 1 234 self.debugging_dumps() 235 if (enable_exit_status and self.document 236 and (self.document.reporter.max_level 237 >= self.settings.exit_status_level)): 238 sys.exit(self.document.reporter.max_level + 10) 239 elif exit: 240 sys.exit(exit_status) 241 return output 242 243 def debugging_dumps(self): 244 if not self.document: 245 return 246 if self.settings.dump_settings: 247 print('\n::: Runtime settings:', file=self._stderr) 248 print(pprint.pformat(self.settings.__dict__), file=self._stderr) 249 if self.settings.dump_internals: 250 print('\n::: Document internals:', file=self._stderr) 251 print(pprint.pformat(self.document.__dict__), file=self._stderr) 252 if self.settings.dump_transforms: 253 print('\n::: Transforms applied:', file=self._stderr) 254 print(' (priority, transform class, pending node details, ' 255 'keyword args)', file=self._stderr) 256 print(pprint.pformat( 257 [(priority, '%s.%s' % (xclass.__module__, xclass.__name__), 258 pending and pending.details, kwargs) 259 for priority, xclass, pending, kwargs 260 in self.document.transformer.applied]), file=self._stderr) 261 if self.settings.dump_pseudo_xml: 262 print('\n::: Pseudo-XML:', file=self._stderr) 263 print(self.document.pformat().encode( 264 'raw_unicode_escape'), file=self._stderr) 265 266 def report_Exception(self, error): 267 if isinstance(error, utils.SystemMessage): 268 self.report_SystemMessage(error) 269 elif isinstance(error, UnicodeEncodeError): 270 self.report_UnicodeError(error) 271 elif isinstance(error, io.InputError): 272 self._stderr.write(u'Unable to open source file for reading:\n' 273 u' %s\n' % ErrorString(error)) 274 elif isinstance(error, io.OutputError): 275 self._stderr.write( 276 u'Unable to open destination file for writing:\n' 277 u' %s\n' % ErrorString(error)) 278 else: 279 print(u'%s' % ErrorString(error), file=self._stderr) 280 print(("""\ 281Exiting due to error. Use "--traceback" to diagnose. 282Please report errors to <docutils-users@lists.sf.net>. 283Include "--traceback" output, Docutils version (%s%s), 284Python version (%s), your OS type & version, and the 285command line used.""" % (__version__, 286 docutils.__version_details__ and 287 ' [%s]'%docutils.__version_details__ or '', 288 sys.version.split()[0])), file=self._stderr) 289 290 def report_SystemMessage(self, error): 291 print('Exiting due to level-%s (%s) system message.' % ( 292 error.level, utils.Reporter.levels[error.level]), 293 file=self._stderr) 294 295 def report_UnicodeError(self, error): 296 data = error.object[error.start:error.end] 297 self._stderr.write( 298 '%s\n' 299 '\n' 300 'The specified output encoding (%s) cannot\n' 301 'handle all of the output.\n' 302 'Try setting "--output-encoding-error-handler" to\n' 303 '\n' 304 '* "xmlcharrefreplace" (for HTML & XML output);\n' 305 ' the output will contain "%s" and should be usable.\n' 306 '* "backslashreplace" (for other output formats);\n' 307 ' look for "%s" in the output.\n' 308 '* "replace"; look for "?" in the output.\n' 309 '\n' 310 '"--output-encoding-error-handler" is currently set to "%s".\n' 311 '\n' 312 'Exiting due to error. Use "--traceback" to diagnose.\n' 313 'If the advice above doesn\'t eliminate the error,\n' 314 'please report it to <docutils-users@lists.sf.net>.\n' 315 'Include "--traceback" output, Docutils version (%s),\n' 316 'Python version (%s), your OS type & version, and the\n' 317 'command line used.\n' 318 % (ErrorString(error), 319 self.settings.output_encoding, 320 data.encode('ascii', 'xmlcharrefreplace'), 321 data.encode('ascii', 'backslashreplace'), 322 self.settings.output_encoding_error_handler, 323 __version__, sys.version.split()[0])) 324 325default_usage = '%prog [options] [<source> [<destination>]]' 326default_description = ('Reads from <source> (default is stdin) and writes to ' 327 '<destination> (default is stdout). See ' 328 '<http://docutils.sf.net/docs/user/config.html> for ' 329 'the full reference.') 330 331def publish_cmdline(reader=None, reader_name='standalone', 332 parser=None, parser_name='restructuredtext', 333 writer=None, writer_name='pseudoxml', 334 settings=None, settings_spec=None, 335 settings_overrides=None, config_section=None, 336 enable_exit_status=True, argv=None, 337 usage=default_usage, description=default_description): 338 """ 339 Set up & run a `Publisher` for command-line-based file I/O (input and 340 output file paths taken automatically from the command line). Return the 341 encoded string output also. 342 343 Parameters: see `publish_programmatically` for the remainder. 344 345 - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``. 346 - `usage`: Usage string, output if there's a problem parsing the command 347 line. 348 - `description`: Program description, output for the "--help" option 349 (along with command-line option descriptions). 350 """ 351 pub = Publisher(reader, parser, writer, settings=settings) 352 pub.set_components(reader_name, parser_name, writer_name) 353 output = pub.publish( 354 argv, usage, description, settings_spec, settings_overrides, 355 config_section=config_section, enable_exit_status=enable_exit_status) 356 return output 357 358def publish_file(source=None, source_path=None, 359 destination=None, destination_path=None, 360 reader=None, reader_name='standalone', 361 parser=None, parser_name='restructuredtext', 362 writer=None, writer_name='pseudoxml', 363 settings=None, settings_spec=None, settings_overrides=None, 364 config_section=None, enable_exit_status=False): 365 """ 366 Set up & run a `Publisher` for programmatic use with file-like I/O. 367 Return the encoded string output also. 368 369 Parameters: see `publish_programmatically`. 370 """ 371 output, pub = publish_programmatically( 372 source_class=io.FileInput, source=source, source_path=source_path, 373 destination_class=io.FileOutput, 374 destination=destination, destination_path=destination_path, 375 reader=reader, reader_name=reader_name, 376 parser=parser, parser_name=parser_name, 377 writer=writer, writer_name=writer_name, 378 settings=settings, settings_spec=settings_spec, 379 settings_overrides=settings_overrides, 380 config_section=config_section, 381 enable_exit_status=enable_exit_status) 382 return output 383 384def publish_string(source, source_path=None, destination_path=None, 385 reader=None, reader_name='standalone', 386 parser=None, parser_name='restructuredtext', 387 writer=None, writer_name='pseudoxml', 388 settings=None, settings_spec=None, 389 settings_overrides=None, config_section=None, 390 enable_exit_status=False): 391 """ 392 Set up & run a `Publisher` for programmatic use with string I/O. Return 393 the encoded string or Unicode string output. 394 395 For encoded string output, be sure to set the 'output_encoding' setting to 396 the desired encoding. Set it to 'unicode' for unencoded Unicode string 397 output. Here's one way:: 398 399 publish_string(..., settings_overrides={'output_encoding': 'unicode'}) 400 401 Similarly for Unicode string input (`source`):: 402 403 publish_string(..., settings_overrides={'input_encoding': 'unicode'}) 404 405 Parameters: see `publish_programmatically`. 406 """ 407 output, pub = publish_programmatically( 408 source_class=io.StringInput, source=source, source_path=source_path, 409 destination_class=io.StringOutput, 410 destination=None, destination_path=destination_path, 411 reader=reader, reader_name=reader_name, 412 parser=parser, parser_name=parser_name, 413 writer=writer, writer_name=writer_name, 414 settings=settings, settings_spec=settings_spec, 415 settings_overrides=settings_overrides, 416 config_section=config_section, 417 enable_exit_status=enable_exit_status) 418 return output 419 420def publish_parts(source, source_path=None, source_class=io.StringInput, 421 destination_path=None, 422 reader=None, reader_name='standalone', 423 parser=None, parser_name='restructuredtext', 424 writer=None, writer_name='pseudoxml', 425 settings=None, settings_spec=None, 426 settings_overrides=None, config_section=None, 427 enable_exit_status=False): 428 """ 429 Set up & run a `Publisher`, and return a dictionary of document parts. 430 Dictionary keys are the names of parts, and values are Unicode strings; 431 encoding is up to the client. For programmatic use with string I/O. 432 433 For encoded string input, be sure to set the 'input_encoding' setting to 434 the desired encoding. Set it to 'unicode' for unencoded Unicode string 435 input. Here's how:: 436 437 publish_parts(..., settings_overrides={'input_encoding': 'unicode'}) 438 439 Parameters: see `publish_programmatically`. 440 """ 441 output, pub = publish_programmatically( 442 source=source, source_path=source_path, source_class=source_class, 443 destination_class=io.StringOutput, 444 destination=None, destination_path=destination_path, 445 reader=reader, reader_name=reader_name, 446 parser=parser, parser_name=parser_name, 447 writer=writer, writer_name=writer_name, 448 settings=settings, settings_spec=settings_spec, 449 settings_overrides=settings_overrides, 450 config_section=config_section, 451 enable_exit_status=enable_exit_status) 452 return pub.writer.parts 453 454def publish_doctree(source, source_path=None, 455 source_class=io.StringInput, 456 reader=None, reader_name='standalone', 457 parser=None, parser_name='restructuredtext', 458 settings=None, settings_spec=None, 459 settings_overrides=None, config_section=None, 460 enable_exit_status=False): 461 """ 462 Set up & run a `Publisher` for programmatic use with string I/O. 463 Return the document tree. 464 465 For encoded string input, be sure to set the 'input_encoding' setting to 466 the desired encoding. Set it to 'unicode' for unencoded Unicode string 467 input. Here's one way:: 468 469 publish_doctree(..., settings_overrides={'input_encoding': 'unicode'}) 470 471 Parameters: see `publish_programmatically`. 472 """ 473 pub = Publisher(reader=reader, parser=parser, writer=None, 474 settings=settings, 475 source_class=source_class, 476 destination_class=io.NullOutput) 477 pub.set_components(reader_name, parser_name, 'null') 478 pub.process_programmatic_settings( 479 settings_spec, settings_overrides, config_section) 480 pub.set_source(source, source_path) 481 pub.set_destination(None, None) 482 output = pub.publish(enable_exit_status=enable_exit_status) 483 return pub.document 484 485def publish_from_doctree(document, destination_path=None, 486 writer=None, writer_name='pseudoxml', 487 settings=None, settings_spec=None, 488 settings_overrides=None, config_section=None, 489 enable_exit_status=False): 490 """ 491 Set up & run a `Publisher` to render from an existing document 492 tree data structure, for programmatic use with string I/O. Return 493 the encoded string output. 494 495 Note that document.settings is overridden; if you want to use the settings 496 of the original `document`, pass settings=document.settings. 497 498 Also, new document.transformer and document.reporter objects are 499 generated. 500 501 For encoded string output, be sure to set the 'output_encoding' setting to 502 the desired encoding. Set it to 'unicode' for unencoded Unicode string 503 output. Here's one way:: 504 505 publish_from_doctree( 506 ..., settings_overrides={'output_encoding': 'unicode'}) 507 508 Parameters: `document` is a `docutils.nodes.document` object, an existing 509 document tree. 510 511 Other parameters: see `publish_programmatically`. 512 """ 513 reader = docutils.readers.doctree.Reader(parser_name='null') 514 pub = Publisher(reader, None, writer, 515 source=io.DocTreeInput(document), 516 destination_class=io.StringOutput, settings=settings) 517 if not writer and writer_name: 518 pub.set_writer(writer_name) 519 pub.process_programmatic_settings( 520 settings_spec, settings_overrides, config_section) 521 pub.set_destination(None, destination_path) 522 return pub.publish(enable_exit_status=enable_exit_status) 523 524def publish_cmdline_to_binary(reader=None, reader_name='standalone', 525 parser=None, parser_name='restructuredtext', 526 writer=None, writer_name='pseudoxml', 527 settings=None, settings_spec=None, 528 settings_overrides=None, config_section=None, 529 enable_exit_status=True, argv=None, 530 usage=default_usage, description=default_description, 531 destination=None, destination_class=io.BinaryFileOutput 532 ): 533 """ 534 Set up & run a `Publisher` for command-line-based file I/O (input and 535 output file paths taken automatically from the command line). Return the 536 encoded string output also. 537 538 This is just like publish_cmdline, except that it uses 539 io.BinaryFileOutput instead of io.FileOutput. 540 541 Parameters: see `publish_programmatically` for the remainder. 542 543 - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``. 544 - `usage`: Usage string, output if there's a problem parsing the command 545 line. 546 - `description`: Program description, output for the "--help" option 547 (along with command-line option descriptions). 548 """ 549 pub = Publisher(reader, parser, writer, settings=settings, 550 destination_class=destination_class) 551 pub.set_components(reader_name, parser_name, writer_name) 552 output = pub.publish( 553 argv, usage, description, settings_spec, settings_overrides, 554 config_section=config_section, enable_exit_status=enable_exit_status) 555 return output 556 557def publish_programmatically(source_class, source, source_path, 558 destination_class, destination, destination_path, 559 reader, reader_name, 560 parser, parser_name, 561 writer, writer_name, 562 settings, settings_spec, 563 settings_overrides, config_section, 564 enable_exit_status): 565 """ 566 Set up & run a `Publisher` for custom programmatic use. Return the 567 encoded string output and the Publisher object. 568 569 Applications should not need to call this function directly. If it does 570 seem to be necessary to call this function directly, please write to the 571 Docutils-develop mailing list 572 <http://docutils.sf.net/docs/user/mailing-lists.html#docutils-develop>. 573 574 Parameters: 575 576 * `source_class` **required**: The class for dynamically created source 577 objects. Typically `io.FileInput` or `io.StringInput`. 578 579 * `source`: Type depends on `source_class`: 580 581 - If `source_class` is `io.FileInput`: Either a file-like object 582 (must have 'read' and 'close' methods), or ``None`` 583 (`source_path` is opened). If neither `source` nor 584 `source_path` are supplied, `sys.stdin` is used. 585 586 - If `source_class` is `io.StringInput` **required**: The input 587 string, either an encoded 8-bit string (set the 588 'input_encoding' setting to the correct encoding) or a Unicode 589 string (set the 'input_encoding' setting to 'unicode'). 590 591 * `source_path`: Type depends on `source_class`: 592 593 - `io.FileInput`: Path to the input file, opened if no `source` 594 supplied. 595 596 - `io.StringInput`: Optional. Path to the file or object that produced 597 `source`. Only used for diagnostic output. 598 599 * `destination_class` **required**: The class for dynamically created 600 destination objects. Typically `io.FileOutput` or `io.StringOutput`. 601 602 * `destination`: Type depends on `destination_class`: 603 604 - `io.FileOutput`: Either a file-like object (must have 'write' and 605 'close' methods), or ``None`` (`destination_path` is opened). If 606 neither `destination` nor `destination_path` are supplied, 607 `sys.stdout` is used. 608 609 - `io.StringOutput`: Not used; pass ``None``. 610 611 * `destination_path`: Type depends on `destination_class`: 612 613 - `io.FileOutput`: Path to the output file. Opened if no `destination` 614 supplied. 615 616 - `io.StringOutput`: Path to the file or object which will receive the 617 output; optional. Used for determining relative paths (stylesheets, 618 source links, etc.). 619 620 * `reader`: A `docutils.readers.Reader` object. 621 622 * `reader_name`: Name or alias of the Reader class to be instantiated if 623 no `reader` supplied. 624 625 * `parser`: A `docutils.parsers.Parser` object. 626 627 * `parser_name`: Name or alias of the Parser class to be instantiated if 628 no `parser` supplied. 629 630 * `writer`: A `docutils.writers.Writer` object. 631 632 * `writer_name`: Name or alias of the Writer class to be instantiated if 633 no `writer` supplied. 634 635 * `settings`: A runtime settings (`docutils.frontend.Values`) object, for 636 dotted-attribute access to runtime settings. It's the end result of the 637 `SettingsSpec`, config file, and option processing. If `settings` is 638 passed, it's assumed to be complete and no further setting/config/option 639 processing is done. 640 641 * `settings_spec`: A `docutils.SettingsSpec` subclass or object. Provides 642 extra application-specific settings definitions independently of 643 components. In other words, the application becomes a component, and 644 its settings data is processed along with that of the other components. 645 Used only if no `settings` specified. 646 647 * `settings_overrides`: A dictionary containing application-specific 648 settings defaults that override the defaults of other components. 649 Used only if no `settings` specified. 650 651 * `config_section`: A string, the name of the configuration file section 652 for this application. Overrides the ``config_section`` attribute 653 defined by `settings_spec`. Used only if no `settings` specified. 654 655 * `enable_exit_status`: Boolean; enable exit status at end of processing? 656 """ 657 pub = Publisher(reader, parser, writer, settings=settings, 658 source_class=source_class, 659 destination_class=destination_class) 660 pub.set_components(reader_name, parser_name, writer_name) 661 pub.process_programmatic_settings( 662 settings_spec, settings_overrides, config_section) 663 pub.set_source(source, source_path) 664 pub.set_destination(destination, destination_path) 665 output = pub.publish(enable_exit_status=enable_exit_status) 666 return output, pub 667