You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

83 lines
3.9 KiB

4 years ago
  1. #############################################################################
  2. # Copyright (c) 2018, Voila Contributors #
  3. # Copyright (c) 2018, QuantStack #
  4. # #
  5. # Distributed under the terms of the BSD 3-Clause License. #
  6. # #
  7. # The full license is in the file LICENSE, distributed with this software. #
  8. #############################################################################
  9. import os
  10. import json
  11. from jupyter_core.paths import jupyter_path
  12. ROOT = os.path.dirname(__file__)
  13. STATIC_ROOT = os.path.join(ROOT, 'static')
  14. # if the directory above us contains the following paths, it means we are installed in dev mode (pip install -e .)
  15. DEV_MODE = os.path.exists(os.path.join(ROOT, '../setup.py')) and os.path.exists(os.path.join(ROOT, '../share'))
  16. def collect_template_paths(
  17. nbconvert_template_paths,
  18. static_paths,
  19. tornado_template_paths,
  20. template_name='default'):
  21. """
  22. Voila supports custom templates for rendering notebooks.
  23. For a specified template name, `collect_template_paths` collects
  24. - nbconvert template paths,
  25. - static paths,
  26. - tornado template paths,
  27. by looking in the standard Jupyter data directories (PREFIX/share/jupyter/voila/templates)
  28. with different prefix values (user directory, sys prefix, and then system prefix) which
  29. allows users to override templates locally.
  30. The function will recursively load the base templates upon which the specified template
  31. may be based.
  32. """
  33. # We look at the usual jupyter locations, and for development purposes also
  34. # relative to the package directory (first entry, meaning with highest precedence)
  35. search_directories = []
  36. if DEV_MODE:
  37. search_directories.append(os.path.abspath(os.path.join(ROOT, '..', 'share', 'jupyter', 'voila', 'templates')))
  38. search_directories.extend(jupyter_path('voila', 'templates'))
  39. found_at_least_one = False
  40. for search_directory in search_directories:
  41. template_directory = os.path.join(search_directory, template_name)
  42. if os.path.exists(template_directory):
  43. found_at_least_one = True
  44. conf = {}
  45. conf_file = os.path.join(template_directory, 'conf.json')
  46. if os.path.exists(conf_file):
  47. with open(conf_file) as f:
  48. conf = json.load(f)
  49. # For templates that are not named 'default', we assume the default base_template is 'default'
  50. # that means that even the default template could have a base_template when explicitly given.
  51. if template_name != 'default' or 'base_template' in conf:
  52. collect_template_paths(
  53. nbconvert_template_paths,
  54. static_paths,
  55. tornado_template_paths,
  56. conf.get('base_template', 'default'))
  57. extra_nbconvert_path = os.path.join(template_directory, 'nbconvert_templates')
  58. nbconvert_template_paths.insert(0, extra_nbconvert_path)
  59. extra_static_path = os.path.join(template_directory, 'static')
  60. static_paths.insert(0, extra_static_path)
  61. extra_template_path = os.path.join(template_directory, 'templates')
  62. tornado_template_paths.insert(0, extra_template_path)
  63. # We don't look at multiple directories, once a directory with a given name is found at a
  64. # given level of precedence (for instance user directory), we don't look further (for instance
  65. # in sys.prefix)
  66. break
  67. if not found_at_least_one:
  68. paths = "\n\t".join(search_directories)
  69. raise ValueError('No template sub-directory with name %r found in the following paths:\n\t%s' % (template_name, paths))