1#-----------------------------------------------------------------------------
2# Copyright (c) 2012 - 2021, Anaconda, Inc., and Bokeh Contributors.
3# All rights reserved.
4#
5# The full license is in the file LICENSE.txt, distributed with this software.
6#-----------------------------------------------------------------------------
7''' Provides a base class for defining subcommands of the Bokeh command
8line application.
9
10'''
11
12#-----------------------------------------------------------------------------
13# Boilerplate
14#-----------------------------------------------------------------------------
15import logging # isort:skip
16log = logging.getLogger(__name__)
17
18#-----------------------------------------------------------------------------
19# Imports
20#-----------------------------------------------------------------------------
21
22# Standard library imports
23from abc import ABCMeta, abstractmethod
24from argparse import ArgumentParser, Namespace
25from typing import Union
26
27#-----------------------------------------------------------------------------
28# Globals and constants
29#-----------------------------------------------------------------------------
30
31__all__ = (
32    'Subcommand',
33)
34
35#-----------------------------------------------------------------------------
36# General API
37#-----------------------------------------------------------------------------
38
39#-----------------------------------------------------------------------------
40# Dev API
41#-----------------------------------------------------------------------------
42
43class Subcommand(metaclass=ABCMeta):
44    ''' Abstract base class for subcommands
45
46    Subclasses should implement an ``invoke(self, args)`` method that accepts
47    a set of argparse processed arguments as input.
48
49    Subclasses should also define the following class attributes:
50
51    * ``name`` a name for this subcommand
52
53    * ``help`` a help string for argparse to use for this subcommand
54
55    * ``args`` the parameters to pass to ``parser.add_argument``
56
57    The format of the ``args`` should be a sequence of tuples of the form:
58
59    .. code-block:: python
60
61        ('argname', dict(
62            metavar='ARGNAME',
63            nargs='+',
64        ))
65
66    Example:
67
68        A simple subcommand "foo" might look like this:
69
70        .. code-block:: python
71
72            class Foo(Subcommand):
73
74                name = "foo"
75                help = "performs the Foo action"
76                args = (
77                    ('--yell', dict(
78                        action='store_true',
79                        help="Make it loud",
80                    )),
81                )
82
83                def invoke(self, args):
84                    if args.yell:
85                        print("FOO!")
86                    else:
87                        print("foo")
88
89        Then executing ``bokeh foo --yell`` would print ``FOO!`` at the console.
90
91    '''
92
93    # specifying static typing of instance attributes
94    # see https://stackoverflow.com/a/51191130
95    name: str
96    help: str
97
98    def __init__(self, parser: ArgumentParser) -> None:
99        ''' Initialize the subcommand with its parser
100
101        Args:
102            parser (Parser) : an Argparse ``Parser`` instance to configure
103                with the args for this subcommand.
104
105        This method will automatically add all the arguments described in
106        ``self.args``. Subclasses can perform any additional customizations
107        on ``self.parser``.
108
109        '''
110        self.parser = parser
111        args = getattr(self, 'args', ())
112        for arg in args:
113            flags = arg[0]
114            if not isinstance(flags, tuple):
115                flags = (flags,)
116            self.parser.add_argument(*flags, **arg[1])
117
118    @abstractmethod
119    def invoke(self, args: Namespace) -> Union[bool, None]:
120        ''' Takes over main program flow to perform the subcommand.
121
122        *This method must be implemented by subclasses.*
123        subclassed overwritten methods return different types:
124        bool: Build
125        None: FileOutput (subclassed by HTML, SVG and JSON. PNG overwrites FileOutput.invoke method), Info, Init, \
126                Sampledata, Secret, Serve, Static
127
128
129        Args:
130            args (argparse.Namespace) : command line arguments for the subcommand to parse
131
132        Raises:
133            NotImplementedError
134
135        '''
136        raise NotImplementedError("implement invoke()")
137
138#-----------------------------------------------------------------------------
139# Private API
140#-----------------------------------------------------------------------------
141
142#-----------------------------------------------------------------------------
143# Code
144#-----------------------------------------------------------------------------
145