File: //snap/google-cloud-cli/current/lib/googlecloudsdk/command_lib/help_search/rater.py
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Contains a class to rate commands based on relevance."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.command_lib.help_search import lookup
class CommandRater(object):
"""A class to rate the results of searching a command."""
# The below multipliers reflect heuristics for how "important" a term is
# in a command based on where it's found.
_COMMAND_NAME_MULTIPLIER = 1.0 # command name
_ARG_NAME_MULTIPLIER = 0.5 # arg name (positional or flag)
_PATH_MULTIPLIER = 0.5 # the command path
_DEFAULT_MULTIPLIER = 0.25 # anything not controlled by other multipliers.
# This multiplier controls how much a command is penalized for not containing
# a search term.
_NOT_FOUND_MULTIPLIER = 0.1
def __init__(self, results, command):
"""Create a CommandRater.
Args:
results: googlecloudsdk.command_lib.search_help.search_util
.CommandSearchResult, class that holds results.
command: dict, a json representation of a command.
"""
self._command = command
self._terms = results.AllTerms()
self._results = results
def Rate(self):
"""Produce a simple relevance rating for a set of command search results.
Returns a float in the range (0, 1]. For each term that's found, the rating
is multiplied by a number reflecting how "important" its location is, with
command name being the most and flag or positional names being the second
most important, as well as by how many of the search terms were found.
Commands are also penalized if duplicate results in a higher release track
were found.
Returns:
rating: float, the rating of the results.
"""
rating = 1.0
rating *= self._RateForLocation()
rating *= self._RateForTermsFound()
return rating
def _RateForLocation(self):
"""Get a rating based on locations of results."""
rating = 1.0
locations = self._results.FoundTermsMap().values()
for location in locations:
if location == lookup.NAME:
rating *= self._COMMAND_NAME_MULTIPLIER
elif location == lookup.PATH:
rating *= self._PATH_MULTIPLIER
elif (location.split(lookup.DOT)[0] in [lookup.FLAGS, lookup.POSITIONALS]
and location.split(lookup.DOT)[-1] == lookup.NAME):
rating *= self._ARG_NAME_MULTIPLIER
else:
rating *= self._DEFAULT_MULTIPLIER
return rating
def _RateForTermsFound(self):
"""Get a rating based on how many of the searched terms were found."""
rating = 1.0
results = self._results.FoundTermsMap()
for term in self._terms:
if term not in results:
rating *= self._NOT_FOUND_MULTIPLIER
return rating
class CumulativeRater(object):
"""Rates all found commands for relevance."""
def __init__(self):
"""Creates a cumulative rater.
"""
self._found_commands_and_results = []
def AddFoundCommand(self, command, result):
"""Add a command that is a result.
Args:
command: dict, a json representation of a command. MUST already be updated
with the search results.
result: search_util.CommandSearchResults, the results object that goes
with this command.
"""
self._found_commands_and_results.append((command, result))
def RateAll(self):
"""Adds rating to every command found."""
for command, results in self._found_commands_and_results:
rating = CommandRater(results, command).Rate()
command[lookup.RELEVANCE] = rating