out 0.79__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
out/__init__.py ADDED
@@ -0,0 +1,253 @@
1
+ '''
2
+ out - Simple logging with a few fun features.
3
+ © 2018-25, Mike Miller - Released under the LGPL, version 3+.
4
+ '''
5
+ import os
6
+ import sys
7
+ import logging
8
+ import traceback
9
+
10
+ from .detection import _find_palettes, is_fbterm
11
+
12
+
13
+ # detect environment before loading formatters and themes
14
+ _out_file = sys.stderr
15
+ fg, bg, fx, _level, _is_a_tty = _find_palettes(_out_file)
16
+
17
+
18
+ # now we're ready to import these:
19
+ from .format import (ColorFormatter as _ColorFormatter,
20
+ JSONFormatter as _JSONFormatter)
21
+ from .themes import (render_themes as _render_themes,
22
+ render_styles as _render_styles,
23
+ icons as _icons)
24
+
25
+ __version__ = '0.79'
26
+
27
+ # Allow string as well as constant access. Levels will be added below:
28
+ level_map = {
29
+ 'debug': logging.DEBUG,
30
+ 'info': logging.INFO,
31
+ 'warn': logging.WARN,
32
+ 'warning': logging.WARN,
33
+ 'err': logging.ERROR,
34
+ 'error': logging.ERROR,
35
+ 'critical': logging.FATAL,
36
+ 'fatal': logging.FATAL,
37
+ }
38
+
39
+
40
+ class Logger(logging.Logger):
41
+ '''
42
+ A singleton logger with centralized configuration.
43
+ '''
44
+ default_level = logging.INFO
45
+ __path__ = __path__ # allows python3 -m out.demos to work
46
+ __version__ = __version__
47
+ __name__ = __name__
48
+ __spec__ = None # needed to make -m happy in recent pythons :-/
49
+
50
+ def configure(self, **kwargs):
51
+ ''' Convenience function to set a number of parameters on this logger
52
+ and associated handlers and formatters.
53
+ '''
54
+ for kwarg in kwargs:
55
+ value = kwargs[kwarg]
56
+
57
+ if kwarg == 'level':
58
+ self.set_level(value)
59
+
60
+ elif kwarg == 'default_level':
61
+ self.default_level = level_map.get(value, value)
62
+
63
+ elif kwarg == 'datefmt':
64
+ self.handlers[0].formatter.datefmt = value
65
+
66
+ elif kwarg == 'msgfmt':
67
+ self.handlers[0].formatter._style._fmt = value
68
+
69
+ elif kwarg == 'stream':
70
+ self.handlers[0].stream = value
71
+ _, _, _, tlevel, is_a_tty = _find_palettes(value)
72
+ # probably shouldn't auto configure theme, but it does,
73
+ # skipping currently
74
+ _add_handler(value, is_a_tty, tlevel, theme=None)
75
+
76
+ elif kwarg == 'theme':
77
+ if type(value) is str:
78
+ # this section should be reconciled with _add_handler
79
+ theme = _render_themes(self.handlers[0].stream)[value]
80
+ if value == 'plain':
81
+ fmtr = logging.Formatter(style='{', **theme)
82
+ elif value == 'json':
83
+ tlvl = self.handlers[0]._term_level
84
+ if is_fbterm: hl = False # doesn't work well
85
+ else: hl = bool(tlvl) # highlighting
86
+ fmtr = _JSONFormatter(term_level=tlvl, hl=hl, **theme)
87
+ else:
88
+ fmtr = _ColorFormatter(**theme)
89
+ elif type(value) is dict:
90
+ if 'style' in value or 'icons' in value:
91
+ fmtr = _ColorFormatter(**theme)
92
+ else:
93
+ fmtr = logging.Formatter(style='{', **theme)
94
+ self.handlers[0].setFormatter(fmtr)
95
+
96
+ elif kwarg == 'highlight':
97
+ value = bool(value)
98
+ if value is False: # True value is a highlighter
99
+ self.handlers[0].formatter._highlight = value
100
+
101
+ elif kwarg == 'icons':
102
+ if type(value) is str:
103
+ value = _icons[value]
104
+ self.handlers[0].formatter._theme_icons = value
105
+
106
+ elif kwarg == 'style':
107
+ if type(value) is str:
108
+ value = _render_styles(self.handlers[0].stream)[value]
109
+ self.handlers[0].formatter._theme_style = value
110
+
111
+ elif kwarg == 'lexer':
112
+ try:
113
+ self.handlers[0].formatter.set_lexer(value)
114
+ except AttributeError:
115
+ self.error('lexer: ColorFormatter not available.')
116
+ else:
117
+ raise NameError('unknown keyword argument: %s' % kwarg)
118
+
119
+ def log_config(self):
120
+ ''' Log the current logging configuration. '''
121
+ level = self.level
122
+ debug = self.debug
123
+ debug('out logging config, version: %r', __version__)
124
+ debug(' .name: {}, id: {}', self.name, hex(id(self)))
125
+ debug(' .level: %s (%s)', level_map_int[level], level)
126
+ debug(' .propagate: %s', self.propagate)
127
+ debug(' .default_level: %s (%s)',
128
+ level_map_int[self.default_level], self.default_level)
129
+
130
+ for i, handler in enumerate(self.handlers):
131
+ fmtr = handler.formatter
132
+ debug(' + Handler: %s %r', i, handler)
133
+ debug(' + Formatter: %r', fmtr)
134
+ debug(' .datefmt: %r', fmtr.datefmt)
135
+ debug(' .msgfmt: %r', fmtr._fmt)
136
+ debug(' fmt_style: %s', fmtr._style)
137
+ debug(' theme.styles: %r', fmtr._theme_style)
138
+ debug(' theme.icons: %r', fmtr._theme_icons)
139
+ try:
140
+ debug(' highlighting: %r, %r',
141
+ fmtr._lexer.__class__.__name__,
142
+ fmtr._hl_fmtr.__class__.__name__)
143
+ except AttributeError:
144
+ pass
145
+
146
+ def setLevel(self, level):
147
+ if type(level) is int:
148
+ super().setLevel(level)
149
+ else:
150
+ super().setLevel(level_map.get(level.lower(), level))
151
+ set_level = setLevel
152
+
153
+ def __call__(self, message, *args):
154
+ ''' Call logger directly, without function. '''
155
+ if self.isEnabledFor(self.default_level):
156
+ self._log(self.default_level, message, args)
157
+
158
+
159
+ def add_logging_level(name, value, method_name=None):
160
+ ''' Comprehensively adds a new logging level to the ``logging`` module and
161
+ the currently configured logging class.
162
+
163
+ Derived from: https://stackoverflow.com/a/35804945/450917
164
+ '''
165
+ if not method_name:
166
+ method_name = name.lower()
167
+
168
+ # set levels
169
+ logging.addLevelName(value, name)
170
+ setattr(logging, name, value)
171
+ level_map[name.lower()] = value
172
+
173
+ if value == getattr(logging, 'EXCEPT', None): # needs traceback added
174
+ def log_for_level(self, message='', *args, **kwargs):
175
+ show = kwargs.pop('show', True)
176
+ if self.isEnabledFor(value):
177
+ if show:
178
+ message = message.lstrip() + ' ▾\n'
179
+ message += traceback.format_exc()
180
+ else:
181
+ message = message.lstrip()
182
+ self._log(value, message, args, stacklevel=2, **kwargs)
183
+ else:
184
+ def log_for_level(self, message, *args, **kwargs):
185
+ if self.isEnabledFor(value):
186
+ self._log(value, message, args, stacklevel=2, **kwargs)
187
+
188
+ #~ def logToRoot(message, *args, **kwargs): # may not need
189
+ #~ logging.log(value, message, *args, **kwargs)
190
+
191
+ # set functions
192
+ setattr(logging.getLoggerClass(), method_name, log_for_level)
193
+ #~ setattr(logging, method_name, logToRoot)
194
+
195
+
196
+ def _add_handler(out_file, is_a_tty, level, theme='auto'):
197
+ ''' Repeatable handler config. '''
198
+ if is_fbterm:
199
+ hl = False # doesn't work well
200
+ else:
201
+ hl = level > 1 # highlighting > ANSI_MONOCHROME
202
+ _handler = logging.StreamHandler(stream=out_file)
203
+
204
+ if theme == 'auto':
205
+ _theme_name = 'interactive' if is_a_tty else 'production'
206
+ if os.environ.get('TERM') in ('linux', 'fbterm'):
207
+ _theme_name = 'linux_' + _theme_name
208
+ if os.name == 'nt':
209
+ _theme_name = 'windows_' + _theme_name
210
+ theme = _render_themes(out_file, fg=fg, bg=bg, fx=fx)[_theme_name]
211
+ elif theme is None:
212
+ try:
213
+ fmtr = out.handlers[0].formatter
214
+ theme = dict(
215
+ icons=fmtr._theme_icons, style=fmtr._theme_style,
216
+ fmt=fmtr._style._fmt, datefmt=fmtr.datefmt,
217
+ )
218
+ except AttributeError:
219
+ theme = {}
220
+
221
+ out.handlers = [] # clear any old
222
+ _handler._term_level = level
223
+ _formatter = _ColorFormatter(hl=hl, term_level=level, **theme)
224
+ _handler.setFormatter(_formatter)
225
+ out.addHandler(_handler)
226
+
227
+
228
+ # re-configure root logger
229
+ out = logging.getLogger() # root
230
+ out.name = 'main'
231
+ out.__class__ = Logger # one way to add call()
232
+
233
+ # odd level numbers chosen to avoid commonly configured variations
234
+ add_logging_level('TRACE', 7)
235
+ add_logging_level('NOTE', 27)
236
+ add_logging_level('EXCEPT', logging.ERROR + 3, 'exception')
237
+ add_logging_level('EXCEPT', logging.ERROR + 3, 'exc')
238
+ add_logging_level('FATAL', logging.FATAL)
239
+ level_map_int = {
240
+ val: key
241
+ for key, val in level_map.items()
242
+ }
243
+ out.warn = out.warning # fix warn
244
+ out.set_level('note')
245
+
246
+ _add_handler(_out_file, _is_a_tty, _level)
247
+
248
+
249
+ # save original module for later, in case it's needed.
250
+ out._module = sys.modules[__name__]
251
+
252
+ # Wrap module with instance for direct access
253
+ sys.modules[__name__] = out
out/demos.py ADDED
@@ -0,0 +1,76 @@
1
+ '''
2
+ out - Simple logging with a few fun features.
3
+ © 2018-25, Mike Miller - Released under the LGPL, version 3+.
4
+ '''
5
+ import sys
6
+ import out
7
+
8
+
9
+ def test_levels(full=True):
10
+
11
+ out('no explicit level, should be info.')
12
+ out.trace('trace msg: %s', 'Absurdly voluminous details…')
13
+ out.debug('debug message')
14
+ out.info('info message - Normal feedback')
15
+ out.note('note message - Important positive feedback to remember.')
16
+ out.warn('warn message - Something to worry about.')
17
+
18
+ if full:
19
+ out.critical('critical message - *flatline*')
20
+ out.fatal('fatal message - *flatline*')
21
+ try:
22
+ 1/0
23
+ except Exception:
24
+ out.error('error message - Pow!')
25
+ #~ out.exception('exception message - Kerblooey!')
26
+ out.exc('exc message - Kerblooey!')
27
+
28
+
29
+ out.warn('begin...')
30
+ print('⏵⏵ print std config:')
31
+ out.configure(
32
+ level='trace', # needs to go before to allow console log to be viewed!
33
+ )
34
+ out.log_config()
35
+ print('---------------------------------')
36
+
37
+
38
+ print('⏵⏵ change to stdout:')
39
+ out.configure(
40
+ #~ theme='interactive',
41
+ stream=sys.stdout, # runs console.detection again
42
+ )
43
+ out.log_config()
44
+ print('---------------------------------')
45
+
46
+
47
+ print('⏵⏵ log messages from module:')
48
+ from out import test_mod; test_mod # pyflakes
49
+ print()
50
+
51
+ print('⏵⏵ test levels:')
52
+ test_levels()
53
+ print('---------------------------------')
54
+
55
+
56
+ print('⏵⏵ test different highlighting, lexers:')
57
+ out.configure(lexer='json')
58
+ out.debug('debug message: JSON: %s', '{"data": [null, true, false, "hi", 123]}')
59
+
60
+ out.configure(lexer='xml')
61
+ out.trace('trace message: XML: %s', '<xml><tag attr="woot">text</tag></xml><!-- hi -->')
62
+
63
+ out.configure(lexer='python3')
64
+ out.note('debug message: PyON: %s # hi',
65
+ {'data': [None, True, False, 'hi', 123]})
66
+ out.note('import foo; [ x for x in y ]')
67
+ print('---------------------------------')
68
+
69
+
70
+ print('⏵⏵ test json formatter:')
71
+ out.configure(
72
+ level='info',
73
+ theme='json',
74
+ )
75
+ out('no explicit level')
76
+ out.warn('warn: Heavens to Mergatroyd!')
out/detection.py ADDED
@@ -0,0 +1,20 @@
1
+ '''
2
+ out - Simple logging with a few fun features.
3
+ © 2018-25, Mike Miller - Released under the LGPL, version 3+.
4
+ '''
5
+ from console.constants import TermLevel
6
+ from console.detection import init, is_a_tty, is_fbterm, os_name
7
+ from console.style import ForegroundPalette, BackgroundPalette, EffectsPalette
8
+
9
+
10
+ def _find_palettes(stream):
11
+ ''' Need to configure palettes manually, since we are checking stderr. '''
12
+ level = init(_stream=stream)
13
+ fg = ForegroundPalette(level=level)
14
+ bg = BackgroundPalette(level=level)
15
+ fx = EffectsPalette(level=level)
16
+
17
+ return fg, bg, fx, level, is_a_tty(stream)
18
+
19
+
20
+ TermLevel, is_fbterm, os_name # quiet pyflakes
out/format.py ADDED
@@ -0,0 +1,249 @@
1
+ '''
2
+ out - Simple logging with a few fun features.
3
+ © 2018-25, Mike Miller - Released under the LGPL, version 3+.
4
+
5
+ Message template variables:
6
+
7
+ {name} Name of the logger (logging channel)
8
+ {levelno} Numeric logging level for the message (DEBUG, INFO,
9
+ WARNING, ERROR, CRITICAL)
10
+ {levelname} Text logging level for the message ("DEBUG", "INFO",
11
+ "WARNING", "ERROR", "CRITICAL")
12
+ {pathname} Full pathname of the source file where the logging
13
+ call was issued (if available)
14
+ {filename} Filename portion of pathname
15
+ {module} Module (name portion of filename)
16
+ {lineno)d Source line number where the logging call was issued
17
+ (if available)
18
+ {funcName} Function name
19
+ {created} Time when the LogRecord was created (time.time()
20
+ return value)
21
+ {asctime} Textual time when the LogRecord was created
22
+ {msecs} Millisecond portion of the creation time
23
+ {relativeCreated} Time in milliseconds when the LogRecord was created,
24
+ relative to the time the logging module was loaded
25
+ (typically at application startup time)
26
+ {thread} Thread ID (if available)
27
+ {threadName} Thread name (if available)
28
+ {process} Process ID (if available)
29
+ {message} The result of record.getMessage(), computed just as
30
+ the record is emitted
31
+
32
+ # added:
33
+ {on}…{off} Toggles level-specific style (colors, etc) support.
34
+ {icon} Level-specific icon.
35
+ '''
36
+ import logging
37
+ import re
38
+ from pprint import pformat
39
+
40
+ from . import themes
41
+ from . import fx
42
+ from . import highlight
43
+ from .detection import is_fbterm
44
+
45
+ DATA_SEARCH_LIMIT = 80
46
+ _end = str(fx.end)
47
+ if is_fbterm: # fbterm esc seqs conflict with brace formatting :-/
48
+ _end = _end.replace('}', '}}')
49
+
50
+ # compile searches for data message highlighting
51
+ json_data_search = re.compile(r'\{|\[|"').search
52
+ xml_data_search = re.compile('<').search
53
+ pyt_data_search = re.compile(r'''
54
+ \{ # literal
55
+ | # or
56
+ \[ # left bracket
57
+ | # or...
58
+ \(
59
+ |
60
+ :
61
+ ''', re.VERBOSE).search
62
+
63
+
64
+ class ColorFormatter(logging.Formatter):
65
+ ''' Colors the level-name of a log message according to the level.
66
+
67
+ Arguments:
68
+
69
+ datefmt - strftime datetime template
70
+ fmt - log template
71
+ icons - dict of level:value for icons
72
+ style - dict of level:value for terminal style
73
+ template_style - log template syntax: %, {, $
74
+
75
+ # highlighting
76
+ hl - bool, highlight the message.
77
+ lexer - None, or Pygment's lexer: python3', 'json', etc.
78
+ hl_formatter - None, or pass a configured Pygments formatter.
79
+ code_indent - If highlighting data with newlines, indent N sp.
80
+ '''
81
+ default_msec_format = '%s.%03d' # use period decimal point
82
+
83
+ def __init__(self,
84
+ code_indent=12,
85
+ datefmt=None,
86
+ fmt=None,
87
+ hl=True,
88
+ hl_formatter=None,
89
+ term_level=None,
90
+ icons=None,
91
+ lexer='python3',
92
+ style=None,
93
+ template_style='{',
94
+ ):
95
+ self._theme_style = (
96
+ style if style else themes.render_styles(term_level)['norm']
97
+ )
98
+ self._theme_icons = icons if icons else themes.icons['rounded']
99
+ self._code_indent = code_indent
100
+ self._highlight = self._lexer = None
101
+ if hl:
102
+ if lexer:
103
+ self._highlight = highlight.highlight
104
+ self.set_lexer(lexer)
105
+ self._hl_fmtr = hl_formatter or highlight.get_term_formatter(term_level)
106
+
107
+ super().__init__(fmt=fmt, datefmt=datefmt, style=template_style)
108
+
109
+ def set_lexer(self, name):
110
+ if highlight.get_lexer_by_name:
111
+ self._lexer = highlight.get_lexer_by_name(name)
112
+ self._lexer.ensurenl = False
113
+ if name == 'xml':
114
+ self.data_search = xml_data_search
115
+ elif name == 'json':
116
+ self.data_search = json_data_search
117
+ else:
118
+ self.data_search = pyt_data_search
119
+
120
+ def format(self, record):
121
+ ''' Log color formatting. '''
122
+ levelname = record.levelname # len7 limit
123
+ if levelname == 'CRITICAL':
124
+ levelname = record.levelname = 'FATAL'
125
+ if self.usesTime():
126
+ record.asctime = self.formatTime(record, self.datefmt)
127
+ if record.funcName == '<module>':
128
+ record.funcName = ''
129
+
130
+ # render the message part with arguments
131
+ try: # Allow {} style - need a faster way to determine this:
132
+ message = record.getMessage()
133
+ except TypeError:
134
+ message = record.msg.format(*record.args)
135
+
136
+ # decide to highlight w/ pygments
137
+ # TODO: Highlight args directly and drop text scan? - didn't work well.
138
+ if self._highlight:
139
+ if message.find('\x1b', 0, DATA_SEARCH_LIMIT) > -1:
140
+ pass # found escape, avoid ANSI
141
+ else:
142
+ match = self.data_search(message, 0, DATA_SEARCH_LIMIT)
143
+ if match:
144
+ pos = match.start()
145
+ front, back = message[:pos], message[pos:] # Spliten-Sie
146
+ if front.endswith('\n'): # indent data?
147
+ back = pformat(record.args)
148
+ back = left_indent(back, self._code_indent)
149
+ back = self._highlight(back, self._lexer, self._hl_fmtr)
150
+ message = f'{front}{back}'
151
+
152
+ # style the level, icon
153
+ record.message = message
154
+ record.on = self._theme_style.get(levelname, '')
155
+ record.icon = self._theme_icons.get(levelname, '')
156
+ record.off = _end
157
+ s = self.formatMessage(record)
158
+
159
+ # this needs to be here, Formatter class not very granular.
160
+ if record.exc_info:
161
+ # Cache the traceback text to avoid converting it multiple times
162
+ # (it's constant anyway)
163
+ if not record.exc_text:
164
+ record.exc_text = self.formatException(record.exc_info)
165
+ if record.exc_text:
166
+ if s[-1:] != "\n":
167
+ s = s + "\n"
168
+ s = s + record.exc_text
169
+ if record.stack_info:
170
+ if s[-1:] != "\n":
171
+ s = s + "\n"
172
+ s = s + self.formatStack(record.stack_info)
173
+ return s
174
+
175
+
176
+ class JSONFormatter(logging.Formatter):
177
+ '''
178
+ Formats a log message into line-oriented JSON.
179
+
180
+ The message template format is different.
181
+ It uses simple CSV (no spaces allowed) to define field order, e.g.:
182
+
183
+ fmt='asctime,msecs,levelname,name,funcName,lineno,message'
184
+
185
+ (Currently field order requires Python 3.6, but could be backported.)
186
+ '''
187
+ def __init__(self, datefmt=None, fmt=None, term_level=None, hl=True,
188
+ hl_formatter=None):
189
+ self._fields = fmt.split(',')
190
+ from json import dumps
191
+ self.dumps = dumps
192
+ self._highlight = None
193
+ if hl:
194
+ self._highlight = highlight.highlight
195
+ if self._highlight:
196
+ self._lexer = highlight.get_lexer_by_name('JSON')
197
+ self._hl_formatter = (
198
+ hl_formatter or highlight.get_term_formatter(term_level)
199
+ )
200
+ try:
201
+ super().__init__(fmt=fmt, datefmt=datefmt)
202
+ except ValueError: # py 3.8 :-/
203
+ super().__init__(fmt=fmt, datefmt=datefmt, validate=False)
204
+
205
+ def format(self, record):
206
+ ''' Log color formatting. '''
207
+ levelname = record.levelname
208
+ if levelname == 'CRITICAL':
209
+ levelname = record.levelname = 'FATAL'
210
+ record.asctime = self.formatTime(record, self.datefmt)
211
+
212
+ # render the message part with arguments
213
+ try: # Allow {} style - need a faster way to determine this:
214
+ message = record.getMessage()
215
+ except TypeError:
216
+ message = record.msg.format(*record.args)
217
+ record.message = message
218
+
219
+ fields = self._fields
220
+ data = { name: getattr(record, name) for name in fields }
221
+ if 'asctime' in fields and 'msecs' in fields: # needs option for this
222
+ data['asctime'] += '.{:03.0f}'.format(data.pop('msecs'))
223
+ s = self.dumps(data)
224
+ if self._highlight:
225
+ s = self._highlight(s, self._lexer, self._hl_formatter).rstrip()
226
+
227
+ # this needs to be here, Formatter class isn't very extensible.
228
+ if record.exc_info:
229
+ # Cache the traceback text to avoid converting it multiple times
230
+ # (it's constant anyway)
231
+ if not record.exc_text:
232
+ record.exc_text = self.formatException(record.exc_info)
233
+ if record.exc_text:
234
+ if s[-1:] != "\n":
235
+ s = s + "\n"
236
+ s = s + record.exc_text
237
+ if record.stack_info:
238
+ if s[-1:] != "\n":
239
+ s = s + "\n"
240
+ s = s + self.formatStack(record.stack_info)
241
+ return s
242
+
243
+
244
+ def left_indent(text, indent=12, end='\n'):
245
+ ''' A bit of the ol' ultraviolence :-/ '''
246
+ indent = ' ' * indent
247
+ lines = [indent + line for line in text.splitlines(True)]
248
+ lines.append(end)
249
+ return ''.join(lines)
out/highlight.py ADDED
@@ -0,0 +1,92 @@
1
+ '''
2
+ out - Simple logging with a few fun features.
3
+ © 2018-25, Mike Miller - Released under the LGPL, version 3+.
4
+
5
+ Highlighting with Pygments!
6
+ '''
7
+ try:
8
+ from pygments import highlight
9
+ from pygments.lexers import get_lexer_by_name
10
+ from pygments.token import (Keyword, Name, Comment, String, Error,
11
+ Number, Operator, Punctuation,
12
+ Token, Generic, Whitespace)
13
+ except ImportError:
14
+ highlight = get_lexer_by_name = None
15
+
16
+ from .detection import TermLevel
17
+
18
+
19
+ def get_term_formatter(level):
20
+ ''' Build formatter according to environment. '''
21
+
22
+ term_formatter = None
23
+ if level and highlight:
24
+
25
+ if level >= TermLevel.ANSI_EXTENDED:
26
+
27
+ from pygments.formatters import Terminal256Formatter
28
+ from pygments.style import Style
29
+
30
+ class OutStyle(Style):
31
+ styles = {
32
+ Comment: 'italic ansibrightblack',
33
+ Keyword: 'bold #4ac', # light blue
34
+ Keyword.Constant: 'nobold #3aa', # ansicyan
35
+ Number: 'ansigreen',
36
+
37
+ Name.Tag: '#4ac', # light blue, xml, json
38
+ Name.Attribute: '#4ac', # light blue
39
+
40
+ Operator: 'nobold #b94',
41
+ Operator.Word: 'bold #4ac',
42
+ Punctuation: 'nobold #b94',
43
+
44
+ # not sure about string, hard to find a good warm color
45
+ Generic.String: 'ansired',
46
+ String: 'ansibrightmagenta',
47
+ #~ String: '#b00', # brick red
48
+ #~ String: '#f80', # bright amber
49
+ #~ String: '#d80', # med amber
50
+ }
51
+ term_formatter = Terminal256Formatter(style=OutStyle)
52
+
53
+ elif level >= TermLevel.ANSI_BASIC:
54
+
55
+ from pygments.formatters import TerminalFormatter
56
+
57
+ _default = ('', '')
58
+ color_scheme = {
59
+ Comment.Preproc: _default,
60
+ Name: _default,
61
+ Token: _default,
62
+ Whitespace: _default,
63
+ Generic.Heading: ('**', '**'),
64
+
65
+ Comment: ('brightblack', 'brightblack'),
66
+ Keyword: ('*brightblue*', '*brightblue*'),
67
+ Keyword.Constant: ('cyan', 'cyan'),
68
+ Keyword.Type: ('cyan', 'cyan'),
69
+ Operator: ('yellow', 'yellow'),
70
+ Operator.Word: ('*brightblue*', '*brightblue*'),
71
+
72
+ Name.Builtin: ('cyan', 'cyan'),
73
+ Name.Decorator: ('magenta', 'magenta'),
74
+ Name.Tag: ('brightblue', 'brightblue'),
75
+ Name.Attribute: ('brightblue', 'brightblue'),
76
+
77
+ String: ('brightmagenta', 'brightmagenta'),
78
+ Number: ('green', 'green'),
79
+
80
+ Generic.Deleted: ('red', 'brightred'),
81
+ Generic.Inserted: ('green', 'brightgreen'),
82
+ Generic.Error: ('brightred', 'brightred'),
83
+
84
+ Error: ('_brightred_', '_brightred_'),
85
+ }
86
+
87
+ term_formatter = TerminalFormatter(
88
+ bg='dark',
89
+ colorscheme=color_scheme,
90
+ )
91
+
92
+ return term_formatter