LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
LSST Data Management Base Package
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 software is dual licensed under the GNU General Public License and also
10 # under a 3-clause BSD license. Recipients may choose which of these licenses
11 # to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12 # respectively. If you choose the GPL option then the following text applies
13 # (but note that there is still no warranty even if you opt for BSD instead):
14 #
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 
28 __all__ = ('Color', 'format')
29 
30 import os
31 import re
32 import sys
33 
34 
35 class Color:
36  """A controller that determines whether strings should be colored.
37 
38  Parameters
39  ----------
40  text : `str`
41  Text content to print to a terminal.
42  category : `str`
43  Semantic category of the ``text``. See `categories` for possible
44  values.
45 
46  Raises
47  ------
48  RuntimeError
49  Raised when the ``category`` is not a key of ``Color.categories``.
50 
51  Notes
52  -----
53  The usual usage is ``Color(string, category)`` which returns a string that
54  may be printed; categories are given by the keys of `Color.categories`.
55 
56  `Color.colorize` may be used to set or retrieve whether the user wants
57  color. It always returns `False` when `sys.stdout` is not attached to a
58  terminal.
59  """
60 
61  categories = dict(
62  NAME="blue",
63  VALUE="yellow",
64  FILE="green",
65  TEXT="red",
66  FUNCTION_NAME="blue",
67  )
68  """Mapping of semantic labels to color names (`dict`).
69 
70  Notes
71  -----
72  The default categories are:
73 
74  - ``'NAME'``
75  - ``'VALUE'``
76  - ``'FILE'``
77  - ``'TEXT'``
78  - ``'FUNCTION_NAME'``
79  """
80 
81  colors = {
82  "black": 0,
83  "red": 1,
84  "green": 2,
85  "yellow": 3,
86  "blue": 4,
87  "magenta": 5,
88  "cyan": 6,
89  "white": 7,
90  }
91  """Mapping of color names to terminal color codes (`dict`).
92  """
93 
94  _colorize = True
95 
96  def __init__(self, text, category):
97  try:
98  color = Color.categories[category]
99  except KeyError:
100  raise RuntimeError("Unknown category: %s" % category)
101 
102  self.rawTextrawText = str(text)
103  x = color.lower().split(";")
104  self.color, bold = x.pop(0), False
105  if x:
106  props = x.pop(0)
107  if props in ("bold",):
108  bold = True
109 
110  try:
111  self._code_code = "%s" % (30 + Color.colors[self.color])
112  except KeyError:
113  raise RuntimeError("Unknown colour: %s" % self.color)
114 
115  if bold:
116  self._code_code += ";1"
117 
118  @staticmethod
119  def colorize(val=None):
120  """Get or set whether the string should be colorized.
121 
122  Parameters
123  ----------
124  val : `bool` or `dict`, optional
125  The value is usually a bool, but it may be a dict which is used
126  to modify Color.categories
127 
128  Returns
129  -------
130  shouldColorize : `bool`
131  If `True`, the string should be colorized. A string **will not** be
132  colorized if standard output or standard error are not attached to
133  a terminal or if the ``val`` argument was `False`.
134 
135  Only strings written to a terminal are colorized.
136  """
137 
138  if val is not None:
139  Color._colorize = val
140 
141  if isinstance(val, dict):
142  unknown = []
143  for k in val:
144  if k in Color.categories:
145  if val[k] in Color.colors:
146  Color.categories[k] = val[k]
147  else:
148  print("Unknown colour %s for category %s" % (val[k], k), file=sys.stderr)
149  else:
150  unknown.append(k)
151 
152  if unknown:
153  print("Unknown colourizing category: %s" % " ".join(unknown), file=sys.stderr)
154 
155  return Color._colorize if sys.stdout.isatty() else False
156 
157  def __str__(self):
158  if not self.colorizecolorize():
159  return self.rawTextrawText
160 
161  base = "\033["
162 
163  prefix = base + self._code_code + "m"
164  suffix = base + "m"
165 
166  return prefix + self.rawTextrawText + suffix
167 
168 
169 def _colorize(text, category):
170  text = Color(text, category)
171  return str(text)
172 
173 
174 def format(config, name=None, writeSourceLine=True, prefix="", verbose=False):
175  """Format the history record for a configuration, or a specific
176  configuration field.
177 
178  Parameters
179  ----------
180  config : `lsst.pex.config.Config`
181  A configuration instance.
182  name : `str`, optional
183  The name of a configuration field to specifically format the history
184  for. Otherwise the history of all configuration fields is printed.
185  writeSourceLine : `bool`, optional
186  If `True`, prefix each printout line with the code filename and line
187  number where the configuration event occurred. Default is `True`.
188  prefix : `str`, optional
189  A prefix for to add to each printout line. This prefix occurs first,
190  even before any source line. The default is an empty string.
191  verbose : `bool`, optional
192  Default is `False`.
193  """
194 
195  if name is None:
196  for i, name in enumerate(config.history.keys()):
197  if i > 0:
198  print()
199  print(format(config, name))
200 
201  outputs = []
202  for value, stack, label in config.history.get(name, []):
203  output = []
204  for frame in stack:
205  if frame.function in ("__new__", "__set__", "__setattr__", "execfile", "wrapper") or \
206  os.path.split(frame.filename)[1] in ("argparse.py", "argumentParser.py"):
207  if not verbose:
208  continue
209 
210  line = []
211  if writeSourceLine:
212  line.append(["%s" % ("%s:%d" % (frame.filename, frame.lineno)), "FILE", ])
213 
214  line.append([frame.content, "TEXT", ])
215  if False:
216  line.append([frame.function, "FUNCTION_NAME", ])
217 
218  output.append(line)
219 
220  outputs.append([value, output])
221 
222  if outputs:
223  # Find the maximum widths of the value and file:lineNo fields.
224  if writeSourceLine:
225  sourceLengths = []
226  for value, output in outputs:
227  sourceLengths.append(max([len(x[0][0]) for x in output]))
228  sourceLength = max(sourceLengths)
229 
230  valueLength = len(prefix) + max([len(str(value)) for value, output in outputs])
231 
232  # Generate the config history content.
233  msg = []
234  fullname = "%s.%s" % (config._name, name) if config._name is not None else name
235  msg.append(_colorize(re.sub(r"^root\.", "", fullname), "NAME"))
236  for value, output in outputs:
237  line = prefix + _colorize("%-*s" % (valueLength, value), "VALUE") + " "
238  for i, vt in enumerate(output):
239  if writeSourceLine:
240  vt[0][0] = "%-*s" % (sourceLength, vt[0][0])
241 
242  output[i] = " ".join([_colorize(v, t) for v, t in vt])
243 
244  line += ("\n%*s" % (valueLength + 1, "")).join(output)
245  msg.append(line)
246 
247  return "\n".join(msg)
int max
def __init__(self, text, category)
Definition: history.py:96
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174