files.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. """Contains writer for writing nbconvert output to filesystem."""
  2. # Copyright (c) IPython Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. import io
  5. import os
  6. import glob
  7. from traitlets import Unicode, observe
  8. from ipython_genutils.path import link_or_copy, ensure_dir_exists
  9. from ipython_genutils.py3compat import unicode_type
  10. from .base import WriterBase
  11. class FilesWriter(WriterBase):
  12. """Consumes nbconvert output and produces files."""
  13. build_directory = Unicode("",
  14. help="""Directory to write output(s) to. Defaults
  15. to output to the directory of each notebook. To recover
  16. previous default behaviour (outputting to the current
  17. working directory) use . as the flag value."""
  18. ).tag(config=True)
  19. relpath = Unicode(
  20. help="""When copying files that the notebook depends on, copy them in
  21. relation to this path, such that the destination filename will be
  22. os.path.relpath(filename, relpath). If FilesWriter is operating on a
  23. notebook that already exists elsewhere on disk, then the default will be
  24. the directory containing that notebook."""
  25. ).tag(config=True)
  26. # Make sure that the output directory exists.
  27. @observe('build_directory')
  28. def _build_directory_changed(self, change):
  29. new = change['new']
  30. if new:
  31. ensure_dir_exists(new)
  32. def __init__(self, **kw):
  33. super(FilesWriter, self).__init__(**kw)
  34. self._build_directory_changed({'new': self.build_directory})
  35. def _makedir(self, path):
  36. """Make a directory if it doesn't already exist"""
  37. if path:
  38. self.log.info("Making directory %s", path)
  39. ensure_dir_exists(path)
  40. def write(self, output, resources, notebook_name=None, **kw):
  41. """
  42. Consume and write Jinja output to the file system. Output directory
  43. is set via the 'build_directory' variable of this instance (a
  44. configurable).
  45. See base for more...
  46. """
  47. # Verify that a notebook name is provided.
  48. if notebook_name is None:
  49. raise TypeError('notebook_name')
  50. # Pull the extension and subdir from the resources dict.
  51. output_extension = resources.get('output_extension', None)
  52. # Get the relative path for copying files
  53. resource_path = resources.get('metadata', {}).get('path', '')
  54. relpath = self.relpath or resource_path
  55. build_directory = self.build_directory or resource_path
  56. # Write all of the extracted resources to the destination directory.
  57. # NOTE: WE WRITE EVERYTHING AS-IF IT'S BINARY. THE EXTRACT FIG
  58. # PREPROCESSOR SHOULD HANDLE UNIX/WINDOWS LINE ENDINGS...
  59. items = resources.get('outputs', {}).items()
  60. if items:
  61. self.log.info("Support files will be in %s", os.path.join(resources.get('output_files_dir',''), ''))
  62. for filename, data in items:
  63. # Determine where to write the file to
  64. dest = os.path.join(build_directory, filename)
  65. path = os.path.dirname(dest)
  66. self._makedir(path)
  67. # Write file
  68. self.log.debug("Writing %i bytes to support file %s", len(data), dest)
  69. with io.open(dest, 'wb') as f:
  70. f.write(data)
  71. # Copy referenced files to output directory
  72. if build_directory:
  73. for filename in self.files:
  74. # Copy files that match search pattern
  75. for matching_filename in glob.glob(filename):
  76. # compute the relative path for the filename
  77. if relpath != '':
  78. dest_filename = os.path.relpath(matching_filename, relpath)
  79. else:
  80. dest_filename = matching_filename
  81. # Make sure folder exists.
  82. dest = os.path.join(build_directory, dest_filename)
  83. path = os.path.dirname(dest)
  84. self._makedir(path)
  85. # Copy if destination is different.
  86. if not os.path.normpath(dest) == os.path.normpath(matching_filename):
  87. self.log.info("Copying %s -> %s", matching_filename, dest)
  88. link_or_copy(matching_filename, dest)
  89. # Determine where to write conversion results.
  90. if output_extension is not None:
  91. dest = notebook_name + output_extension
  92. else:
  93. dest = notebook_name
  94. dest = os.path.join(build_directory, dest)
  95. # Write conversion results.
  96. self.log.info("Writing %i bytes to %s", len(output), dest)
  97. if isinstance(output, unicode_type):
  98. with io.open(dest, 'w', encoding='utf-8') as f:
  99. f.write(output)
  100. else:
  101. with io.open(dest, 'wb') as f:
  102. f.write(output)
  103. return dest