output.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """Cement core output module."""
  2. import os
  3. import sys
  4. import pkgutil
  5. from ..core import backend, exc, interface, handler
  6. from ..utils.misc import minimal_logger
  7. from ..utils import fs
  8. LOG = minimal_logger(__name__)
  9. def output_validator(klass, obj):
  10. """Validates an handler implementation against the IOutput interface."""
  11. members = [
  12. '_setup',
  13. 'render',
  14. ]
  15. interface.validate(IOutput, obj, members)
  16. class IOutput(interface.Interface):
  17. """
  18. This class defines the Output Handler Interface. Classes that
  19. implement this handler must provide the methods and attributes defined
  20. below.
  21. Implementations do *not* subclass from interfaces.
  22. Usage:
  23. .. code-block:: python
  24. from cement.core import output
  25. class MyOutputHandler(object):
  26. class Meta:
  27. interface = output.IOutput
  28. label = 'my_output_handler'
  29. ...
  30. """
  31. # pylint: disable=W0232, C0111, R0903
  32. class IMeta:
  33. """Interface meta-data."""
  34. label = 'output'
  35. """The string identifier of the interface."""
  36. validator = output_validator
  37. """The interface validator function."""
  38. # Must be provided by the implementation
  39. Meta = interface.Attribute('Handler meta-data')
  40. def _setup(app_obj):
  41. """
  42. The _setup function is called during application initialization and
  43. must 'setup' the handler object making it ready for the framework
  44. or the application to make further calls to it.
  45. :param app_obj: The application object.
  46. """
  47. def render(data_dict):
  48. """
  49. Render the data_dict into output in some fashion.
  50. :param data_dict: The dictionary whose data we need to render into
  51. output.
  52. :returns: string or unicode string or None
  53. """
  54. class CementOutputHandler(handler.CementBaseHandler):
  55. """
  56. Base class that all Output Handlers should sub-class from.
  57. """
  58. class Meta:
  59. """
  60. Handler meta-data (can be passed as keyword arguments to the parent
  61. class).
  62. """
  63. label = None
  64. """The string identifier of this handler."""
  65. interface = IOutput
  66. """The interface that this class implements."""
  67. def __init__(self, *args, **kw):
  68. super(CementOutputHandler, self).__init__(*args, **kw)
  69. class TemplateOutputHandler(CementOutputHandler):
  70. """
  71. Base class for template base output handlers.
  72. """
  73. def _load_template_from_file(self, template_path):
  74. template_prefix = self.app._meta.template_dir.rstrip('/')
  75. template_path = template_path.lstrip('/')
  76. full_path = fs.abspath(os.path.join(template_prefix, template_path))
  77. LOG.debug("attemping to load output template from file %s" %
  78. full_path)
  79. if os.path.exists(full_path):
  80. content = open(full_path, 'r').read()
  81. LOG.debug("loaded output template from file %s" %
  82. full_path)
  83. return content
  84. else:
  85. LOG.debug("output template file %s does not exist" %
  86. full_path)
  87. return None
  88. def _load_template_from_module(self, template_path):
  89. template_module = self.app._meta.template_module
  90. template_path = template_path.lstrip('/')
  91. LOG.debug("attemping to load output template '%s' from module %s" %
  92. (template_path, template_module))
  93. # see if the module exists first
  94. if template_module not in sys.modules:
  95. try:
  96. __import__(template_module, globals(), locals(), [], 0)
  97. except ImportError as e:
  98. LOG.debug("unable to import template module '%s'."
  99. % template_module)
  100. return None
  101. # get the template content
  102. try:
  103. content = pkgutil.get_data(template_module, template_path)
  104. LOG.debug("loaded output template '%s' from module %s" %
  105. (template_path, template_module))
  106. return content
  107. except IOError as e:
  108. LOG.debug("output template '%s' does not exist in module %s" %
  109. (template_path, template_module))
  110. return None
  111. def load_template(self, template_path):
  112. """
  113. Loads a template file first from ``self.app._meta.template_dir`` and
  114. secondly from ``self.app._meta.template_module``. The
  115. ``template_dir`` has presedence.
  116. :param template_path: The secondary path of the template *after*
  117. either ``template_module`` or ``template_dir`` prefix (set via
  118. CementApp.Meta)
  119. :returns: The content of the template (str)
  120. :raises: FrameworkError if the template does not exist in either the
  121. ``template_module`` or ``template_dir``.
  122. """
  123. if not template_path:
  124. raise exc.FrameworkError("Invalid template path '%s'." %
  125. template_path)
  126. # first attempt to load from file
  127. content = self._load_template_from_file(template_path)
  128. if content is None:
  129. # second attempt to load from module
  130. content = self._load_template_from_module(template_path)
  131. # if content is None, that means we didn't find a template file in
  132. # either and that is an exception
  133. if content is not None:
  134. return content
  135. else:
  136. raise exc.FrameworkError("Could not locate template: %s" %
  137. template_path)