Source code

Revision control

Copy as Markdown

Other Tools

# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""Console script for glean_parser."""
import datetime
import io
from pathlib import Path
import sys
import click
import json
import glean_parser
from . import coverage as mod_coverage
from . import data_review as mod_data_review
from . import lint
from . import translate as mod_translate
from . import validate_ping
from . import translation_options
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
@click.command(context_settings=CONTEXT_SETTINGS)
@click.argument(
"input",
type=click.Path(exists=False, dir_okay=False, file_okay=True, readable=True),
nargs=-1,
)
@click.option(
"--output",
"-o",
type=click.Path(dir_okay=True, file_okay=False, writable=True),
nargs=1,
required=True,
)
@click.option(
"--format",
"-f",
type=click.Choice(list(mod_translate.OUTPUTTERS.keys())),
required=True,
)
@click.option(
"--option",
"-s",
help="Backend-specific option. Must be of the form key=value.\
Pass 'help' for valid options",
type=str,
multiple=True,
required=False,
is_eager=True,
callback=translation_options.translate_options,
)
@click.option(
"--allow-reserved",
is_flag=True,
help=(
"If provided, allow the use of reserved fields. "
"Should only be set when building the Glean library itself."
),
)
@click.option(
"--allow-missing-files",
is_flag=True,
help=("Do not treat missing input files as an error."),
)
@click.option(
"--require-tags",
is_flag=True,
help=("Require tags to be specified for metrics and pings."),
)
@click.option(
"--expire-by-version",
help="Expire metrics by version, with the provided major version.",
type=click.INT,
required=False,
)
def translate(
input,
format,
output,
option,
allow_reserved,
allow_missing_files,
require_tags,
expire_by_version,
):
"""
Translate metrics.yaml and pings.yaml files to other formats.
"""
option_dict = {}
for opt in option:
key, val = opt.split("=", 1)
option_dict[key] = val
sys.exit(
mod_translate.translate(
[Path(x) for x in input],
format,
Path(output),
option_dict,
{
"allow_reserved": allow_reserved,
"allow_missing_files": allow_missing_files,
"require_tags": require_tags,
"expire_by_version": expire_by_version,
},
)
)
@click.command()
@click.option(
"--schema",
"-s",
type=str,
nargs=1,
required=True,
help=("HTTP url or file path to Glean ping schema. If remote, will cache to disk."),
)
def check(schema):
"""
Validate the contents of a Glean ping.
The ping contents are read from stdin, and the validation errors are
written to stdout.
"""
sys.exit(
validate_ping.validate_ping(
io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8"),
io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8"),
schema_url=schema,
)
)
@click.command()
@click.argument(
"input",
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True),
nargs=-1,
)
@click.option(
"--allow-reserved",
is_flag=True,
help=(
"If provided, allow the use of reserved fields. "
"Should only be set when building the Glean library itself."
),
)
@click.option(
"--allow-missing-files",
is_flag=True,
help=("Do not treat missing input files as an error."),
)
@click.option(
"--require-tags",
is_flag=True,
help=("Require tags to be specified for metrics and pings."),
)
def glinter(input, allow_reserved, allow_missing_files, require_tags):
"""
Runs a linter over the metrics.
"""
sys.exit(
lint.glinter(
[Path(x) for x in input],
{
"allow_reserved": allow_reserved,
"allow_missing_files": allow_missing_files,
"require_tags": require_tags,
},
)
)
@click.command()
@click.argument(
"input",
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True),
nargs=-1,
)
@click.option(
"--allow-reserved",
is_flag=True,
help=(
"If provided, allow the use of reserved fields. "
"Should only be set when building the Glean library itself."
),
)
@click.option(
"--allow-missing-files",
is_flag=True,
help=("Do not treat missing input files as an error."),
)
@click.option(
"--require-tags",
is_flag=True,
help=("Require tags to be specified for metrics and pings."),
)
def dump(input, allow_reserved, allow_missing_files, require_tags):
"""
Dump the list of metrics/pings as JSON to stdout.
"""
results = glean_parser.parser.parse_objects(
[Path(x) for x in input],
{
"allow_reserved": allow_reserved,
"allow_missing_files": allow_missing_files,
"require_tags": require_tags,
},
)
errs = list(results)
assert len(errs) == 0
metrics = {
metric.identifier(): metric.serialize()
for category, probes in results.value.items()
for probe_name, metric in probes.items()
}
def date_serializer(o):
if isinstance(o, datetime.datetime):
return o.isoformat()
print(
json.dumps(
metrics,
sort_keys=True,
indent=2,
separators=(",", ": "),
default=date_serializer,
)
)
@click.command()
@click.option(
"-c",
"--coverage_file",
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True),
required=True,
multiple=True,
)
@click.argument(
"metrics_files",
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True),
nargs=-1,
)
@click.option(
"-o",
"--output",
type=click.Path(exists=False, dir_okay=False, file_okay=True, writable=True),
required=True,
)
@click.option(
"--format",
"-f",
type=click.Choice(list(mod_coverage.OUTPUTTERS.keys())),
required=True,
)
@click.option(
"--allow-reserved",
is_flag=True,
help=(
"If provided, allow the use of reserved fields. "
"Should only be set when building the Glean library itself."
),
)
def coverage(coverage_file, metrics_files, format, output, allow_reserved):
"""
Produce a coverage analysis file given raw coverage output and a set of
metrics.yaml files.
"""
sys.exit(
mod_coverage.coverage(
[Path(x) for x in coverage_file],
[Path(x) for x in metrics_files],
format,
Path(output),
{
"allow_reserved": allow_reserved,
},
)
)
@click.command()
@click.argument("bug", type=str)
@click.argument(
"metrics_files",
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True),
nargs=-1,
)
def data_review_request(bug, metrics_files):
"""
Generate a skeleton Data Review Request for all metrics in METRICS_FILES
whose bug_numbers fields contain the provided BUG string.
For example, providing "1694739" matches
To ensure substrings don't match, the provided bug string will match only
if it is bounded by non-word characters.
Prints to stdout.
"""
sys.exit(mod_data_review.generate(bug, [Path(x) for x in metrics_files]))
@click.group()
@click.version_option(glean_parser.__version__, prog_name="glean_parser")
def main(args=None):
"""Command line utility for glean_parser."""
pass
main.add_command(translate)
main.add_command(check)
main.add_command(glinter)
main.add_command(dump)
main.add_command(coverage)
main.add_command(data_review_request, "data-review")
def main_wrapper(args=None):
"""
A simple wrapper around click's `main` to display the glean_parser version
when there is an error.
"""
try:
main(args=args)
except SystemExit as e:
if e.code != 0:
print(
f"ERROR running glean_parser v{glean_parser.__version__}",
file=sys.stderr,
)
raise
if __name__ == "__main__":
main_wrapper() # pragma: no cover