#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2018, Anaconda, Inc. All rights reserved.
#
# Powered by the Bokeh Development Team.
#
# 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
#-----------------------------------------------------------------------------