Robot Framework
recommendations.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import difflib
17 
18 from robot.utils import seq2str
19 
20 
22 
23  def __init__(self, normalizer=None):
24  self.normalizernormalizer = normalizer or (lambda x: x)
25 
26  def find_and_format(self, name, candidates, message, max_matches=10,
27  check_missing_argument_separator=False):
28  recommendations = self.findfind(name, candidates, max_matches)
29  if recommendations:
30  return self.formatformat(message, recommendations)
31  if check_missing_argument_separator and name:
32  recommendation = self._check_missing_argument_separator_check_missing_argument_separator(name, candidates)
33  if recommendation:
34  return f'{message} {recommendation}'
35  return message
36 
37 
38  def find(self, name, candidates, max_matches=10):
39  if not name or not candidates:
40  return []
41  norm_name = self.normalizernormalizer(name)
42  norm_candidates = self._get_normalized_candidates_get_normalized_candidates(candidates)
43  cutoff = self._calculate_cutoff_calculate_cutoff(norm_name)
44  norm_matches = difflib.get_close_matches(
45  norm_name, norm_candidates, n=max_matches, cutoff=cutoff
46  )
47  return self._get_original_candidates_get_original_candidates(norm_matches, norm_candidates)
48 
49 
58  def format(self, message, recommendations):
59  if recommendations:
60  message += " Did you mean:"
61  for rec in recommendations:
62  message += "\n %s" % rec
63  return message
64 
65  def _get_normalized_candidates(self, candidates):
66  norm_candidates = {}
67  for cand in sorted(candidates):
68  norm = self.normalizernormalizer(cand)
69  norm_candidates.setdefault(norm, []).append(cand)
70  return norm_candidates
71 
72  def _get_original_candidates(self, norm_matches, norm_candidates):
73  candidates = []
74  for match in norm_matches:
75  candidates.extend(norm_candidates[match])
76  return candidates
77 
78 
84  def _calculate_cutoff(self, string, min_cutoff=0.5, max_cutoff=0.85, step=0.03):
85  cutoff = min_cutoff + len(string) * step
86  return min(cutoff, max_cutoff)
87 
88  def _check_missing_argument_separator(self, name, candidates):
89  name = self.normalizernormalizer(name)
90  candidates = self._get_normalized_candidates_get_normalized_candidates(candidates)
91  matches = [c for c in candidates if name.startswith(c)]
92  if not matches:
93  return None
94  candidates = self._get_original_candidates_get_original_candidates(matches, candidates)
95  return (f"Did you try using keyword {seq2str(candidates, lastsep=' or ')} "
96  f"and forgot to use enough whitespace between keyword and arguments?")
def format(self, message, recommendations)
Add recommendations to the given message.
def _check_missing_argument_separator(self, name, candidates)
def _calculate_cutoff(self, string, min_cutoff=0.5, max_cutoff=0.85, step=0.03)
Calculate a cutoff depending on string length.
def _get_original_candidates(self, norm_matches, norm_candidates)
def find(self, name, candidates, max_matches=10)
Return a list of close matches to name from candidates.
def find_and_format(self, name, candidates, message, max_matches=10, check_missing_argument_separator=False)