#----------------------------------------------------------------------------- # Copyright (c) 2012 - 2019, Anaconda, Inc., and Bokeh Contributors. # All rights reserved. # # The full license is in the file LICENSE.txt, distributed with this software. #----------------------------------------------------------------------------- ''' Abstract base class for subcommands that output to a file (or stdout). ''' #----------------------------------------------------------------------------- # Boilerplate #----------------------------------------------------------------------------- from __future__ import absolute_import, division, print_function, unicode_literals import logging log = logging.getLogger(__name__) #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Standard library imports from abc import abstractmethod import argparse import io # External imports # Bokeh imports from bokeh.util.string import decode_utf8 from ..subcommand import Subcommand from ..util import build_single_handler_applications, die #----------------------------------------------------------------------------- # Globals and constants #----------------------------------------------------------------------------- __all__ = ( 'FileOutputSubcommand', ) #----------------------------------------------------------------------------- # General API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Dev API #----------------------------------------------------------------------------- [docs]class FileOutputSubcommand(Subcommand): ''' Abstract subcommand to output applications as some type of file. ''' extension = None # subtype must set this to file extension [docs] @classmethod def files_arg(cls, output_type_name): ''' Returns a positional arg for ``files`` to specify file inputs to the command. Subclasses should include this to their class ``args``. Example: .. code-block:: python class Foo(FileOutputSubcommand): args = ( FileOutputSubcommand.files_arg("FOO"), # more args for Foo ) + FileOutputSubcommand.other_args() ''' return ('files', dict( metavar='DIRECTORY-OR-SCRIPT', nargs='+', help=("The app directories or scripts to generate %s for" % (output_type_name)), default=None )) [docs] @classmethod def other_args(cls): ''' Return args for ``-o`` / ``--output`` to specify where output should be written, and for a ``--args`` to pass on any additional command line args to the subcommand. Subclasses should append these to their class ``args``. Example: .. code-block:: python class Foo(FileOutputSubcommand): args = ( FileOutputSubcommand.files_arg("FOO"), # more args for Foo ) + FileOutputSubcommand.other_args() ''' return ( (('-o', '--output'), dict( metavar='FILENAME', action='append', type=str, help="Name of the output file or - for standard output." )), ('--args', dict( metavar='COMMAND-LINE-ARGS', nargs=argparse.REMAINDER, help="Any command line arguments remaining are passed on to the application handler", )), ) [docs] def filename_from_route(self, route, ext): ''' ''' if route == "/": base = "index" else: base = route[1:] return "%s.%s" % (base, ext) [docs] def invoke(self, args): ''' ''' argvs = { f : args.args for f in args.files} applications = build_single_handler_applications(args.files, argvs) if args.output is None: outputs = [] else: outputs = list(args.output) # copy so we can pop from it if len(outputs) > len(applications): die("--output/-o was given too many times (%d times for %d applications)" % (len(outputs), len(applications))) for (route, app) in applications.items(): doc = app.create_document() if len(outputs) > 0: filename = outputs.pop(0) else: filename = self.filename_from_route(route, self.extension) self.write_file(args, filename, doc) [docs] def write_file(self, args, filename, doc): ''' ''' contents = self.file_contents(args, doc) if filename == '-': print(decode_utf8(contents)) else: with io.open(filename, "w", encoding="utf-8") as file: file.write(decode_utf8(contents)) self.after_write_file(args, filename, doc) # can be overridden optionally [docs] def after_write_file(self, args, filename, doc): ''' ''' pass [docs] @abstractmethod def file_contents(self, args, doc): ''' Subtypes must override this to return the contents of the output file for the given doc. ''' raise NotImplementedError("file_contents") #----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Code #-----------------------------------------------------------------------------