lsst.pex.config  18.1.0-3-g6b74884
history.py
Go to the documentation of this file.
1 # This file is part of pex_config.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (http://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 
22 __all__ = ('Color', 'format')
23 
24 import os
25 import re
26 import sys
27 
28 
29 class Color:
30  """A controller that determines whether strings should be colored.
31 
32  Parameters
33  ----------
34  text : `str`
35  Text content to print to a terminal.
36  category : `str`
37  Semantic category of the ``text``. See `categories` for possible values.
38 
39  Raises
40  ------
41  RuntimeError
42  Raised when the ``category`` is not a key of ``Color.categories``.
43 
44  Notes
45  -----
46  The usual usage is ``Color(string, category)`` which returns a string that
47  may be printed; categories are given by the keys of `Color.categories`.
48 
49  `Color.colorize` may be used to set or retrieve whether the user wants
50  color. It always returns `False` when `sys.stdout` is not attached to a
51  terminal.
52  """
53 
54  categories = dict(
55  NAME="blue",
56  VALUE="yellow",
57  FILE="green",
58  TEXT="red",
59  FUNCTION_NAME="blue",
60  )
61  """Mapping of semantic labels to color names (`dict`).
62 
63  Notes
64  -----
65  The default categories are:
66 
67  - ``'NAME'``
68  - ``'VALUE'``
69  - ``'FILE'``
70  - ``'TEXT'``
71  - ``'FUNCTION_NAME'``
72  """
73 
74  colors = {
75  "black": 0,
76  "red": 1,
77  "green": 2,
78  "yellow": 3,
79  "blue": 4,
80  "magenta": 5,
81  "cyan": 6,
82  "white": 7,
83  }
84  """Mapping of color names to terminal color codes (`dict`).
85  """
86 
87  _colorize = True
88 
89  def __init__(self, text, category):
90  try:
91  color = Color.categories[category]
92  except KeyError:
93  raise RuntimeError("Unknown category: %s" % category)
94 
95  self.rawText = str(text)
96  x = color.lower().split(";")
97  self.color, bold = x.pop(0), False
98  if x:
99  props = x.pop(0)
100  if props in ("bold",):
101  bold = True
102 
103  try:
104  self._code = "%s" % (30 + Color.colors[self.color])
105  except KeyError:
106  raise RuntimeError("Unknown colour: %s" % self.color)
107 
108  if bold:
109  self._code += ";1"
110 
111  @staticmethod
112  def colorize(val=None):
113  """Get or set whether the string should be colorized.
114 
115  Parameters
116  ----------
117  val : `bool` or `dict`, optional
118  The value is usually a bool, but it may be a dict which is used
119  to modify Color.categories
120 
121  Returns
122  -------
123  shouldColorize : `bool`
124  If `True`, the string should be colorized. A string **will not** be
125  colorized if standard output or standard error are not attached to
126  a terminal or if the ``val`` argument was `False`.
127 
128  Only strings written to a terminal are colorized.
129  """
130 
131  if val is not None:
132  Color._colorize = val
133 
134  if isinstance(val, dict):
135  unknown = []
136  for k in val:
137  if k in Color.categories:
138  if val[k] in Color.colors:
139  Color.categories[k] = val[k]
140  else:
141  print("Unknown colour %s for category %s" % (val[k], k), file=sys.stderr)
142  else:
143  unknown.append(k)
144 
145  if unknown:
146  print("Unknown colourizing category: %s" % " ".join(unknown), file=sys.stderr)
147 
148  return Color._colorize if sys.stdout.isatty() else False
149 
150  def __str__(self):
151  if not self.colorize():
152  return self.rawText
153 
154  base = "\033["
155 
156  prefix = base + self._code + "m"
157  suffix = base + "m"
158 
159  return prefix + self.rawText + suffix
160 
161 
162 def _colorize(text, category):
163  text = Color(text, category)
164  return str(text)
165 
166 
167 def format(config, name=None, writeSourceLine=True, prefix="", verbose=False):
168  """Format the history record for a configuration, or a specific
169  configuration field.
170 
171  Parameters
172  ----------
173  config : `lsst.pex.config.Config`
174  A configuration instance.
175  name : `str`, optional
176  The name of a configuration field to specifically format the history
177  for. Otherwise the history of all configuration fields is printed.
178  writeSourceLine : `bool`, optional
179  If `True`, prefix each printout line with the code filename and line
180  number where the configuration event occurred. Default is `True`.
181  prefix : `str`, optional
182  A prefix for to add to each printout line. This prefix occurs first,
183  even before any source line. The default is an empty string.
184  verbose : `bool`, optional
185  Default is `False`.
186  """
187 
188  if name is None:
189  for i, name in enumerate(config.history.keys()):
190  if i > 0:
191  print()
192  print(format(config, name))
193 
194  outputs = []
195  for value, stack, label in config.history[name]:
196  output = []
197  for frame in stack:
198  if frame.function in ("__new__", "__set__", "__setattr__", "execfile", "wrapper") or \
199  os.path.split(frame.filename)[1] in ("argparse.py", "argumentParser.py"):
200  if not verbose:
201  continue
202 
203  line = []
204  if writeSourceLine:
205  line.append(["%s" % ("%s:%d" % (frame.filename, frame.lineno)), "FILE", ])
206 
207  line.append([frame.content, "TEXT", ])
208  if False:
209  line.append([frame.function, "FUNCTION_NAME", ])
210 
211  output.append(line)
212 
213  outputs.append([value, output])
214 
215  # Find the maximum widths of the value and file:lineNo fields.
216  if writeSourceLine:
217  sourceLengths = []
218  for value, output in outputs:
219  sourceLengths.append(max([len(x[0][0]) for x in output]))
220  sourceLength = max(sourceLengths)
221 
222  valueLength = len(prefix) + max([len(str(value)) for value, output in outputs])
223 
224  # Generate the config history content.
225  msg = []
226  fullname = "%s.%s" % (config._name, name) if config._name is not None else name
227  msg.append(_colorize(re.sub(r"^root\.", "", fullname), "NAME"))
228  for value, output in outputs:
229  line = prefix + _colorize("%-*s" % (valueLength, value), "VALUE") + " "
230  for i, vt in enumerate(output):
231  if writeSourceLine:
232  vt[0][0] = "%-*s" % (sourceLength, vt[0][0])
233 
234  output[i] = " ".join([_colorize(v, t) for v, t in vt])
235 
236  line += ("\n%*s" % (valueLength + 1, "")).join(output)
237  msg.append(line)
238 
239  return "\n".join(msg)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:167
def colorize(val=None)
Definition: history.py:112
def __init__(self, text, category)
Definition: history.py:89