File: //snap/google-cloud-cli/394/lib/googlecloudsdk/core/resource/resource_printer.py
# -*- coding: utf-8 -*- #
# Copyright 2015 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Methods for formatting and printing Python objects.
Each printer has three main attributes, all accessible as strings in the
--format='NAME[ATTRIBUTES](PROJECTION)' option:
NAME: str, The printer name.
[ATTRIBUTES]: str, An optional [no-]name[=value] list of attributes. Unknown
attributes are silently ignored. Attributes are added to a printer local
dict indexed by name.
(PROJECTION): str, List of resource names to be included in the output
resource. Unknown names are silently ignored. Resource names are
'.'-separated key identifiers with an implicit top level resource name.
Example:
gcloud compute instances list \
--format='table[box](name, networkInterfaces[0].networkIP)'
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.core import exceptions as core_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties as core_properties
from googlecloudsdk.core.resource import config_printer
from googlecloudsdk.core.resource import csv_printer
from googlecloudsdk.core.resource import diff_printer
from googlecloudsdk.core.resource import flattened_printer
from googlecloudsdk.core.resource import json_printer
from googlecloudsdk.core.resource import list_printer
from googlecloudsdk.core.resource import object_printer
from googlecloudsdk.core.resource import resource_lex
from googlecloudsdk.core.resource import resource_printer_base
from googlecloudsdk.core.resource import resource_printer_types as formats
from googlecloudsdk.core.resource import resource_projection_spec
from googlecloudsdk.core.resource import resource_property
from googlecloudsdk.core.resource import resource_transform
from googlecloudsdk.core.resource import table_printer
from googlecloudsdk.core.resource import yaml_printer
class Error(core_exceptions.Error):
"""Exceptions for this module."""
class UnknownFormatError(Error):
"""Unknown format name exception."""
class ProjectionFormatRequiredError(Error):
"""Projection key missing required format attribute."""
class DefaultPrinter(yaml_printer.YamlPrinter):
"""An alias for YamlPrinter.
An alias for the *yaml* format. To override use *gcloud config set
core/default_format* property.
"""
class DisablePrinter(resource_printer_base.ResourcePrinter):
"""Disables formatted output and does not consume the resources.
Disables formatted output and does not consume the resources. Equivalent to
the *none* format, but also short-circuits early for commands that return
pageable lists.
"""
def __init__(self, *args, **kwargs):
super(DisablePrinter, self).__init__(*args, **kwargs)
self.attributes = {'disable': 1}
class NonePrinter(resource_printer_base.ResourcePrinter):
"""Disables formatted output and consumes the resources.
Disables formatted output and consumes the resources.
"""
class TextPrinter(flattened_printer.FlattenedPrinter):
"""An alias for FlattenedPrinter.
An alias for the *flattened* format.
"""
class MultiPrinter(resource_printer_base.ResourcePrinter):
"""A printer that prints different formats for each projection key.
Each projection key must have a subformat defined by the
:format=FORMAT-STRING attribute. For example,
`--format="multi(data:format=json, info:format='table[box](a, b, c)')"`
formats the *data* field as JSON and the *info* field as a boxed table.
Printer attributes:
separator: Separator string to print between each format. If multiple
resources are provided, the separator is also printed between each
resource.
"""
def __init__(self, *args, **kwargs):
super(MultiPrinter, self).__init__(*args, **kwargs)
# pylint: disable=line-too-long
self.columns = []
# pylint: disable=line-too-long
for col in self.column_attributes.Columns():
if not col.attribute.subformat:
raise ProjectionFormatRequiredError(
'{key} requires format attribute.'.format(
key=resource_lex.GetKeyName(col.key)))
self.columns.append(
(col, Printer(col.attribute.subformat, out=self._out)))
def _AddRecord(self, record, delimit=True):
separator = self.attributes.get('separator', '')
for i, (col, printer) in enumerate(self.columns):
if i != 0 or delimit:
self._out.write(separator)
printer.Print(resource_property.Get(record, col.key))
terminator = self.attributes.get('terminator', '')
if terminator:
self._out.write(terminator)
class PrinterAttributes(resource_printer_base.ResourcePrinter):
"""Attributes for all printers. This docstring is used to generate topic docs.
All formats have these attributes.
Printer attributes:
disable: Disables formatted output and does not consume the resources.
json-decode: Decodes string values that are JSON compact encodings of list
and dictionary objects. This may become the default.
pager: If True, sends output to a pager.
private: Disables log file output. Use this for sensitive resource data
that should not be displayed in log files. Explicit command line IO
redirection overrides this attribute.
transforms: Apply projection transforms to the resource values. The default
is format specific; table-like formats may define default transforms to
certain columns. Use *no-transforms* to disable.
"""
_FORMATTERS = {
formats.CONFIG: config_printer.ConfigPrinter,
formats.CSV: csv_printer.CsvPrinter,
formats.DEFAULT: DefaultPrinter,
formats.DIFF: diff_printer.DiffPrinter,
formats.DISABLE: DisablePrinter,
formats.FLATTENED: flattened_printer.FlattenedPrinter,
formats.GET: csv_printer.GetPrinter,
formats.JSON: json_printer.JsonPrinter,
formats.LIST: list_printer.ListPrinter,
formats.MULTI: MultiPrinter,
formats.NONE: NonePrinter,
formats.OBJECT: object_printer.ObjectPrinter,
formats.TABLE: table_printer.TablePrinter,
formats.TEXT: TextPrinter,
formats.VALUE: csv_printer.ValuePrinter,
formats.YAML: yaml_printer.YamlPrinter,
}
_HIDDEN_FORMATTERS = {}
def RegisterFormatter(format_name, printer, hidden=False):
_FORMATTERS[format_name] = printer
if hidden:
_HIDDEN_FORMATTERS[format_name] = True
def GetFormatRegistry(hidden=False):
"""Returns the (format-name => Printer) format registry dictionary.
Args:
hidden: bool, if True, include the hidden formatters.
Returns:
The (format-name => Printer) format registry dictionary.
"""
return {format_name: _FORMATTERS[format_name] for format_name in
_FORMATTERS if hidden or format_name not in _HIDDEN_FORMATTERS}
def SupportedFormats():
"""Returns a sorted list of supported format names."""
return sorted(_FORMATTERS)
# TODO(b/265207164): Replace this with an abstract factory.
def Printer(print_format, out=None, defaults=None, console_attr=None):
"""Returns a resource printer given a format string.
Args:
print_format: The _FORMATTERS name with optional attributes and projection.
out: Output stream, log.out if None.
defaults: Optional resource_projection_spec.ProjectionSpec defaults.
console_attr: The console attributes for the output stream. Ignored by some
printers. If None then printers that require it will initialize it to
match out.
Raises:
UnknownFormatError: The print_format is invalid.
Returns:
An initialized ResourcePrinter class or None if printing is disabled.
"""
# pylint: disable=g-import-not-at-top
from googlecloudsdk.core.resource import resource_projector
# pylint: enable=g-import-not-at-top
default_format_property = core_properties.VALUES.core.default_format.Get()
# Detect 'default' print format and ensure that
# core/default_format is used instead of YAML if specified.
if print_format.endswith(formats.DEFAULT) and default_format_property:
chosen_print_format = default_format_property
else:
chosen_print_format = print_format
log.debug('Chosen display Format:{}'.format(chosen_print_format))
projector = resource_projector.Compile(
expression=chosen_print_format,
defaults=resource_projection_spec.ProjectionSpec(
defaults=defaults, symbols=resource_transform.GetTransforms()
),
)
printer_name = projector.Projection().Name()
if not printer_name:
# Do not print, do not consume resources.
return None
try:
printer_class = _FORMATTERS[printer_name]
except KeyError:
raise UnknownFormatError("""\
Format must be one of {0}; received [{1}].
For information on output formats:
$ gcloud topic formats
""".format(', '.join(SupportedFormats()), printer_name))
printer = printer_class(out=out,
name=printer_name,
printer=Printer,
projector=projector,
console_attr=console_attr)
return printer
def Print(resources, print_format, out=None, defaults=None, single=False):
"""Prints the given resources.
Args:
resources: A singleton or list of JSON-serializable Python objects.
print_format: The _FORMATTER name with optional projection expression.
out: Output stream, log.out if None.
defaults: Optional resource_projection_spec.ProjectionSpec defaults.
single: If True then resources is a single item and not a list.
For example, use this to print a single object as JSON.
"""
printer = Printer(print_format, out=out, defaults=defaults)
# None means the printer is disabled.
if printer:
printer.Print(resources, single)