# -*- coding: utf8 -*-
#
# Module LOG
#
# Part of Nutils: open source numerical utilities for Python. Jointly developed
# by HvZ Computational Engineering, TU/e Multiscale Engineering Fluid Dynamics,
# and others. More info at http://nutils.org <info@nutils.org>. (c) 2014
"""
The log module provides print methods ``debug``, ``info``, ``user``,
``warning``, and ``error``, in increasing order of priority. Output is sent to
stdout as well as to an html formatted log file if so configured.
"""
from __future__ import print_function
from . import prop, debug
import sys, time, os, warnings, numpy, re
_KEY = '__logger__'
def _findlogger( frame=None ):
'find logger in call stack'
if frame is None:
frame = sys._getframe(1)
while frame:
logger = frame.f_locals.get(_KEY)
if logger:
return logger
frame = frame.f_back
return SimpleLog
warnings.showwarning = lambda message, category, filename, lineno, *args: warning( '%s: %s\n In %s:%d' % ( category.__name__, message, filename, lineno ) )
[docs]def stack( msg, frames=None ):
'print stack trace'
if frames is None:
frames = debug.callstack( depth=2 )
stream = getstream( attr='error', frame=frames[-1].frame )
print( msg, *reversed(frames), sep='\n', file=stream )
[docs]def SimpleLog( chunks=('',), attr=None ):
'just write to stdout'
sys.stdout.write( ' > '.join( chunks ) )
return sys.stdout
def _path2href( match ):
whitelist = ['.jpg','.png','.svg','.txt'] + getattr( prop, 'plot_extensions', [] )
filename = match.group(0)
ext = match.group(1)
return '<a href="%s">%s</a>' % (filename,filename) if ext not in whitelist \
else '<a href="%s" name="%s" class="plot">%s</a>' % (filename,filename,filename)
[docs]class HtmlStream( object ):
'html line stream'
def __init__( self, chunks, attr, html ):
'constructor'
self.out = SimpleLog( chunks, attr=attr )
self.attr = attr
self.head = ' · '.join( chunks )
self.body = ''
self.html = html
[docs] def write( self, text ):
'write to out and buffer for html'
self.out.write( text )
self.body += text.replace( '<', '<' ).replace( '>', '>' )
def __del__( self ):
'postprocess buffer and write to html'
body = self.body
if self.attr == 'path':
body = re.sub( r'\b\w+([.]\w+)\b', _path2href, body )
if self.attr:
body = '<span class="%s">%s</span>' % ( self.attr, body )
line = '<span class="line">%s</span>' % ( self.head + body )
self.html.write( line )
self.html.flush()
[docs]class HtmlLog( object ):
'html log'
def __init__( self, fileobj, title, depth=1 ):
'constructor'
self.html = fileobj
self.html.write( '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">\n' )
self.html.write( '<html><head>\n' )
self.html.write( '<title>%s</title>\n' % title )
self.html.write( '<script type="text/javascript" src="../../../../../viewer.js" ></script>\n' )
self.html.write( '<link rel="stylesheet" type="text/css" href="../../../../../style.css">\n' )
self.html.write( '<link rel="stylesheet" type="text/css" href="../../../../../custom.css">\n' )
self.html.write( '</head><body><pre>\n' )
self.html.write( '<span id="navbar">goto: <a class="nav_latest" href="../../../../log.html">latest %s</a> | <a class="nav_latestall" href="../../../../../log.html">latest overall</a> | <a class="nav_index" href="../../../../../">index</a></span>\n\n' % title.split()[0] )
self.html.flush()
sys._getframe(depth).f_locals[_KEY] = self
def __call__( self, chunks=('',), attr=None ):
return HtmlStream( chunks, attr, self.html )
def __del__( self ):
'destructor'
self.html.write( '</pre></body></html>\n' )
self.html.close()
[docs]class ContextLog( object ):
'base class'
def __init__( self, depth=1 ):
'constructor'
frame = sys._getframe(depth)
parent = _findlogger( frame )
while isinstance( parent, ContextLog ) and not parent.__enabled:
parent = parent.parent
self.parent = parent
self.__enabled = True
frame.f_locals[_KEY] = self
def __call__( self, chunks=('',), attr=None ):
if self.__enabled:
chunks = (self.text,) + chunks
return self.parent( chunks, attr=attr )
[docs] def disable( self ):
'disable this logger'
self.__enabled = False
def __repr__( self ):
return '%s(%s)' % ( self.__class__.__name__, self )
def __str__( self ):
return '%s > %s' % ( self.parent, self.text ) if self.__enabled else str(self.parent)
[docs]class StaticContextLog( ContextLog ):
'simple text logger'
def __init__( self, text, depth=1 ):
'constructor'
self.text = text
ContextLog.__init__( self, depth=depth+1 )
[docs]class ProgressContextLog( ContextLog ):
'progress bar'
def __init__( self, text, iterable=None, target=None, showpct=True, depth=1 ):
'constructor'
self.msg = text
self.showpct = showpct
self.tint = getattr(prop,'progress_interval',1.)
self.tmax = getattr(prop,'progress_interval_max',numpy.inf)
self.texp = getattr(prop,'progress_interval_scale',2.)
self.t0 = time.time()
self.tnext = self.t0 + min( self.tint, self.tmax )
self.iterable = iterable
self.target = len(iterable) if target is None else target
self.current = 0
ContextLog.__init__( self, depth=depth+1 )
def __iter__( self ):
try:
for i, item in enumerate( self.iterable ):
self.update( i )
yield item
finally:
self.disable()
[docs] def update( self, current ):
'update progress'
self.current = current
if time.time() > self.tnext:
print( file=self(()) )
@property
[docs] def text( self ):
'get text'
self.tint = min( self.tint*self.texp, self.tmax )
self.tnext = time.time() + self.tint
pbar = self.msg + ' %.0f/%.0f' % ( self.current, self.target )
if self.showpct:
pct = 100 * self.current / float(self.target)
pbar += ' (%.0f%%)' % pct
return pbar
# historically grown
context = StaticContextLog
progress = iterate = ProgressContextLog
setup_html = HtmlLog
def getstream( attr=None, frame=None ):
logger = _findlogger(frame)
return logger( attr=attr )
def _mklog( attr, frame=None ):
return lambda *args: print( *args, file=getstream(attr,frame) )
path = _mklog( 'path' )
error = _mklog( 'error' )
warning = _mklog( 'warning' )
user = _mklog( 'user' )
info = _mklog( 'info' )
debug = _mklog( 'debug' )
# vim:shiftwidth=2:foldmethod=indent:foldnestmax=2