<commit_before>#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function # Anticipating the PY3 apocalypse in 2020
import sys, argparse, csv, re, platform # For basic file IO stuff, argument parsing, config file reading, text substitution/ regex, OS identification
from pdb import set_trace as br #For debugging I prefer the c style "break" nomenclature to "trace"
import time as time_lib # for diagnostices
import six
import traceback

# needed for utf-encoding on python 2:
if six.PY2:
	reload(sys)
	sys.setdefaultencoding('utf8')
# for io diagnostics:
start_time = time_lib.time()
# construct main parser:
parser = argparse.ArgumentParser(description="Plot variables from XDMF with matplotlib")
group=parser.add_mutually_exclusive_group(required=True)
parser.add_argument('--quiet','-q',dest='quiet',action='store_const',const=True, help='only display error messages (default full debug messages)')
group.add_argument('--settings','-s',dest='settingsfile',help='A settings file full of plotting options')
group.add_argument('--tree',help='Display layout of available data as found by xdmf',action='store_true',default=False)
parser.add_argument('--threads','-t',dest='threads', help='number of threads to use for parallel operations')
parser.add_argument('--directory','-d',dest='dir',help='The directory to output the graphs to.')
parser.add_argument('--debug',help='show result in window',action='store_true',default=False)
parser.add_argument('files',metavar='frame_###.xmf',nargs='+',help='xdmf files to plot using the settings files')
#define a help flag pseudo overloaded flag that also prints the help of the subparser for the settings file:
def print_help():
	if '-h' in sys.argv or '--help' in sys.argv:
		parser.print_help()
		print('\nBut also there are many settings contained in the plaintext settings file, plot.config:\n')
		settings_parser.print_help()
		sys.exit()
# create subparser for all the plot settings:
settings_parser=argparse.ArgumentParser(description="Input plot settings for matplotlib to use",prog='plot.config parser')

#Define an error printing function for error reporting to terminal STD error IO stream
def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

# import h5py with checking
try:
	import h5py
except Exception as e:
	eprint('Fatal Error: H5PY not found! (used for reading heavy data)')
	traceback.print_exception(type(e),e,sys.exc_info()[2])
	sys.exit()

# import matplotlib with checking
try:
	import matplotlib as mpl
	mpl.use('AGG')#change backend
	import matplotlib.pyplot as plt
	from matplotlib.colorbar import make_axes
	from mpl_toolkits.axes_grid1 import make_axes_locatable
	from matplotlib.colors import LinearSegmentedColormap,is_color_like,LogNorm
except ImportError as e:
	eprint('Fatal Error: matplotlib or parts of it not found!')
	traceback.print_exception(type(e),e,sys.exc_info()[2])
	sys.exit()

#Create type checkers:
def check_bool(value):
	# account for booleans using aNy CombINAtioN of cases
	if value.upper()=='FALSE' or value.upper()=='DISABLE' or value=='0': 
		return False
	elif value.upper()=='TRUE' or value.upper()=='ENABLE' or value=='1':
		return True
	else: 
		raise argparse.ArgumentTypeError("%s is an invalid boolean value" % value)
def check_int(value):
	if int(value):
		return int(value)
	elif value=='auto':
		return 'auto'
	else:
		raise argparse.ArgumentTypeError("%s is an invalid int value" % value)
def check_float(value):
	if float(value):
		return float(value)
	elif value=='auto':
		return 'auto'
	else:
		raise argparse.ArgumentTypeError("%s is an invalid float value" % value)
def check_color(value):
	if is_color_like(value):
		return value
	else:
		raise argparse.ArgumentTypeError("%s is an invalid color value" % value)

# create subparser for settings file arguments:
settings_parser.add_argument('-variable',type=str,metavar='AttributeName',help='The attribute to plot like \'Entropy\', or \'Density\', etc. The name must match the XDMF attribute tags')

# define a list of colormap names that matplotlib has, skipping over the reversed versions
colormaps=[str(m) for m in plt.cm.datad if not m.endswith("_r")]
colormaps.append('hot_desaturated') #because I add this colorbar below
colormaps.append('viridis')
colormaps=sorted(colormaps, key=lambda s: s.lower())

#continue with subparser argument creation:
settings_parser.add_argument('-cmap',choices=colormaps,default='hot_desaturated',help='Colormap to use for colorbar')#done
settings_parser.add_argument('-background_color',type=check_color,default='white',help='color to use as background')#done
settings_parser.add_argument('-text_color',type=check_color,default='black',help='color to use for text and annotations')
settings_parser.add_argument('-cbar_scale',type=str,default='lin',choices=['lin','log'],metavar="{{lin},log}",help='Linear or log scale colormap')
settings_parser.add_argument('-cbar_domain',type=check_float,nargs=2,metavar=("{{auto},min}","{{auto},max}"),default=['auto','auto'],help='The domain of the color bar')
settings_parser.add_argument('-cbar_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',nargs=1,default=True,help='enable or disable colorbar')
settings_parser.add_argument('-cbar_location',type=str,choices=['left','right','top','bottom'],metavar='{\'left\',{\'right\'},\'top\',\'bottom\'}',default='right',help='set the colorbar position')
settings_parser.add_argument('-cbar_width',type=check_float,metavar='float',default='5.0',help='The width of the colorbar')
settings_parser.add_argument('-title',type=str,metavar='{{AttributeName},str}',help='Define the plot title that goes above the plot',default='AttributeName')
settings_parser.add_argument('-title_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='enable or disable the title that goes above the plot')
settings_parser.add_argument('-title_font',type=str,metavar='str',help='choose the font of the plot title')
settings_parser.add_argument('-title_font_size',type=check_int,default=18,metavar='int',help='font size for title')
settings_parser.add_argument('-label_font_size',type=check_int,metavar='int',help='font size for axis labels')
settings_parser.add_argument('-smooth_zones',type=check_bool,choices=[True,False],metavar='{True,{False}}',help='disable or enable zone smoothing')
settings_parser.add_argument('-image_format',type=str,choices=['png','svg','pdf','ps','jpeg','gif','tiff','eps'],default='png',metavar="{{'png'},'svg','pdf','ps','jpeg','gif','tiff','eps'}",help='specify graph output format')
settings_parser.add_argument('-image_size',type=check_int,nargs=2,metavar='int',default=[1280,710],help='specify the size of image')
settings_parser.add_argument('-x_range_km',type=check_float,nargs=2,metavar=("{{auto},min}","{{auto},max}"),default=['auto','auto'],help='The range of the x axis in km')
settings_parser.add_argument('-y_range_km',type=check_float,nargs=2,metavar=("{{auto},min}","{{auto},max}"),default=['auto','auto'],help='The range of the y axis in km')
settings_parser.add_argument('-x_range_label',type=str,nargs=1,metavar='str',default='X ($10^3$ km)',help='The text below the x axis')
settings_parser.add_argument('-y_range_label',type=str,nargs=1,metavar='str',default='Y ($10^3$ km)',help='The text to the left of the y axis')
settings_parser.add_argument('-time_format',type=str,metavar='{{seconds}, s, ms, milliseconds}',nargs=1,default='seconds',choices=["seconds", "s", "ms", "milliseconds"],help='Time format code to use for elapsed time and bounce time')
settings_parser.add_argument('-bounce_time_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='Boolean option for "time since bounce" display')
settings_parser.add_argument('-ctime_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='Boolean option for data creation time display')
settings_parser.add_argument('-elapsed_time_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='Boolean option for "elapsed time" display')
settings_parser.add_argument('-zoom_value',type=check_float,help='The zoom value (percentage of total range) to use if the x or y range is set to \'auto\'')
settings_parser.add_argument('-var_unit',type=str,default='auto',help='The unit to use for the plotted variable')
print_help()#print_help does a hacky help flag overload by intercepting the sys.argv before the parser in order to also print the help for the settings file
#if the help flag isn't there, continue and parse arguments as normal
args=parser.parse_args()

# define an error printing function for error reporting to terminal STD error IO stream
def eprint(*arg, **kwargs):
	print(*arg, file=sys.stderr, **kwargs)

#define a standard printing function that only functions if there is no silence flag on script invocation
def qprint(*arg,**kwargs):
	if not args.quiet:
		print(*arg,**kwargs)

#display help for just the config parser if "help" or "h" appears after -s or --settings
if args.settingsfile and args.settingsfile in ['help','h']:
	settings_parser.print_help()
	sys.exit()

# Define parsed settings
argslist=[i[1:] for i in settings_parser.__dict__['_option_string_actions'].keys()] #generate list of valid arguments to prepend '-' to if it's not the first char
if args.settingsfile and args.settingsfile!='':
	settingsargs=[]
	for super_arg in csv.reader(open(args.settingsfile).read().split('\n'),delimiter=' ',quotechar='"',escapechar='\\'):
		if super_arg[0][0]+super_arg[0][1]=='//': #implement commenting
			continue 
		for arg in super_arg:
			# account for the required '-' needed for argparse
			if arg in argslist:
				settingsargs.append('-'+arg) 
			else:
				settingsargs.append(arg)
	settings=settings_parser.parse_args(settingsargs)
del argslist

#Robustly import an xml writer/parser for parseing the xdmf tree
try:
	from lxml import etree as et
	qprint("Running with lxml.etree")
except ImportError:
	try:
		# Python 2.5
		import xml.etree.cElementTree as et
		import xml.dom.minidom as md
		qprint("Running with cElementTree on Python 2.5+")
	except ImportError:
		try:
		# Python 2.5
			import xml.etree.ElementTree as et
			qprint("Running with ElementTree on Python 2.5+")
		except ImportError:
			try:
				# normal cElementTree install
				import cElementTree as et
				qprint("running with cElementTree")
			except ImportError:
				try:
					# normal ElementTree install
					import elementtree.ElementTree as et
					qprint("running with ElementTree")
				except ImportError as e:
					eprint("Fatal error: Failed to import ElementTree from any known place. XML writing is impossible. ")
					traceback.print_exception(type(e),e,sys.exc_info()[2])
					sys.exit()
# Carefully import numpy for heavy number crunching
try:
	import numpy as np
	def pol2cart(rho, phi):
		x = rho * np.cos(phi)
		y = rho * np.sin(phi)
		return(x, y)
	def cart2pol(x, y):
		rho = np.sqrt(x**2 + y**2)
		phi = np.arctan2(y, x)
		return(rho, phi)
except ImportError as e:
	eprint('Fatal Error: numpy not found! (used to do math faster)')
	traceback.print_exception(type(e),e,sys.exc_info()[2])
	sys.exit()

#create a function to list all valid grid names in the xdmf:
def valid_grid_names():
	gridnames=[]
	for grd in domain.getchildren(): #grd is a grid element
		gridnames.append(grd.get('Name'))
	return gridnames

#create a function to list all valid variables for a given grid:
def valid_variables(gridname):
	variables=[]
	for attribute in domain.findall("*[@Name='"+gridname+"']/Attribute"):
		variables.append(attribute.attrib['Name'])
	return variables

#create a function to list all valid scalars from the currently parsed xdmf tree
def tree():
	qprint('Found valid scalars:')
	for grid in valid_grid_names():
		qprint('\n'+grid,end='')
		variables=valid_variables(grid)
		for i,variable in enumerate(variables):
			qprint(['\n\t',''][i!=0 or i==len(variables)]+variable+(','+'\n\t'*((i+1)%5==0))*(i!=len(variables)-1),end=' ')
	qprint()

for file in args.files:
	domain=et.parse(file).getroot()[0]
	
	if args.tree:<commit_msg>🐛  fix --tree to work with ctime, improve formatting<commit_after>#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function # Anticipating the PY3 apocalypse in 2020
import sys, argparse, csv, re, platform # For basic file IO stuff, argument parsing, config file reading, text substitution/ regex, OS identification
from pdb import set_trace as br #For debugging I prefer the c style "break" nomenclature to "trace"
import time as time_lib # for diagnostices
import six
import traceback

# needed for utf-encoding on python 2:
if six.PY2:
	reload(sys)
	sys.setdefaultencoding('utf8')
# for io diagnostics:
start_time = time_lib.time()
# construct main parser:
parser = argparse.ArgumentParser(description="Plot variables from XDMF with matplotlib")
group=parser.add_mutually_exclusive_group(required=True)
parser.add_argument('--quiet','-q',dest='quiet',action='store_const',const=True, help='only display error messages (default full debug messages)')
group.add_argument('--settings','-s',dest='settingsfile',help='A settings file full of plotting options')
group.add_argument('--tree',help='Display layout of available data as found by xdmf',action='store_true',default=False)
parser.add_argument('--threads','-t',dest='threads', help='number of threads to use for parallel operations')
parser.add_argument('--directory','-d',dest='dir',help='The directory to output the graphs to.')
parser.add_argument('--debug',help='show result in window',action='store_true',default=False)
parser.add_argument('files',metavar='frame_###.xmf',nargs='+',help='xdmf files to plot using the settings files')
#define a help flag pseudo overloaded flag that also prints the help of the subparser for the settings file:
def print_help():
	if '-h' in sys.argv or '--help' in sys.argv:
		parser.print_help()
		print('\nBut also there are many settings contained in the plaintext settings file, plot.config:\n')
		settings_parser.print_help()
		sys.exit()
# create subparser for all the plot settings:
settings_parser=argparse.ArgumentParser(description="Input plot settings for matplotlib to use",prog='plot.config parser')

#Define an error printing function for error reporting to terminal STD error IO stream
def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

# import h5py with checking
try:
	import h5py
except Exception as e:
	eprint('Fatal Error: H5PY not found! (used for reading heavy data)')
	traceback.print_exception(type(e),e,sys.exc_info()[2])
	sys.exit()

# import matplotlib with checking
try:
	import matplotlib as mpl
	mpl.use('AGG')#change backend
	import matplotlib.pyplot as plt
	from matplotlib.colorbar import make_axes
	from mpl_toolkits.axes_grid1 import make_axes_locatable
	from matplotlib.colors import LinearSegmentedColormap,is_color_like,LogNorm
except ImportError as e:
	eprint('Fatal Error: matplotlib or parts of it not found!')
	traceback.print_exception(type(e),e,sys.exc_info()[2])
	sys.exit()

#Create type checkers:
def check_bool(value):
	# account for booleans using aNy CombINAtioN of cases
	if value.upper()=='FALSE' or value.upper()=='DISABLE' or value=='0': 
		return False
	elif value.upper()=='TRUE' or value.upper()=='ENABLE' or value=='1':
		return True
	else: 
		raise argparse.ArgumentTypeError("%s is an invalid boolean value" % value)
def check_int(value):
	if int(value):
		return int(value)
	elif value=='auto':
		return 'auto'
	else:
		raise argparse.ArgumentTypeError("%s is an invalid int value" % value)
def check_float(value):
	if float(value):
		return float(value)
	elif value=='auto':
		return 'auto'
	else:
		raise argparse.ArgumentTypeError("%s is an invalid float value" % value)
def check_color(value):
	if is_color_like(value):
		return value
	else:
		raise argparse.ArgumentTypeError("%s is an invalid color value" % value)

# create subparser for settings file arguments:
settings_parser.add_argument('-variable',type=str,metavar='AttributeName',help='The attribute to plot like \'Entropy\', or \'Density\', etc. The name must match the XDMF attribute tags')

# define a list of colormap names that matplotlib has, skipping over the reversed versions
colormaps=[str(m) for m in plt.cm.datad if not m.endswith("_r")]
colormaps.append('hot_desaturated') #because I add this colorbar below
colormaps.append('viridis')
colormaps=sorted(colormaps, key=lambda s: s.lower())

#continue with subparser argument creation:
settings_parser.add_argument('-cmap',choices=colormaps,default='hot_desaturated',help='Colormap to use for colorbar')#done
settings_parser.add_argument('-background_color',type=check_color,default='white',help='color to use as background')#done
settings_parser.add_argument('-text_color',type=check_color,default='black',help='color to use for text and annotations')
settings_parser.add_argument('-cbar_scale',type=str,default='lin',choices=['lin','log'],metavar="{{lin},log}",help='Linear or log scale colormap')
settings_parser.add_argument('-cbar_domain',type=check_float,nargs=2,metavar=("{{auto},min}","{{auto},max}"),default=['auto','auto'],help='The domain of the color bar')
settings_parser.add_argument('-cbar_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',nargs=1,default=True,help='enable or disable colorbar')
settings_parser.add_argument('-cbar_location',type=str,choices=['left','right','top','bottom'],metavar='{\'left\',{\'right\'},\'top\',\'bottom\'}',default='right',help='set the colorbar position')
settings_parser.add_argument('-cbar_width',type=check_float,metavar='float',default='5.0',help='The width of the colorbar')
settings_parser.add_argument('-title',type=str,metavar='{{AttributeName},str}',help='Define the plot title that goes above the plot',default='AttributeName')
settings_parser.add_argument('-title_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='enable or disable the title that goes above the plot')
settings_parser.add_argument('-title_font',type=str,metavar='str',help='choose the font of the plot title')
settings_parser.add_argument('-title_font_size',type=check_int,default=18,metavar='int',help='font size for title')
settings_parser.add_argument('-label_font_size',type=check_int,metavar='int',help='font size for axis labels')
settings_parser.add_argument('-smooth_zones',type=check_bool,choices=[True,False],metavar='{True,{False}}',help='disable or enable zone smoothing')
settings_parser.add_argument('-image_format',type=str,choices=['png','svg','pdf','ps','jpeg','gif','tiff','eps'],default='png',metavar="{{'png'},'svg','pdf','ps','jpeg','gif','tiff','eps'}",help='specify graph output format')
settings_parser.add_argument('-image_size',type=check_int,nargs=2,metavar='int',default=[1280,710],help='specify the size of image')
settings_parser.add_argument('-x_range_km',type=check_float,nargs=2,metavar=("{{auto},min}","{{auto},max}"),default=['auto','auto'],help='The range of the x axis in km')
settings_parser.add_argument('-y_range_km',type=check_float,nargs=2,metavar=("{{auto},min}","{{auto},max}"),default=['auto','auto'],help='The range of the y axis in km')
settings_parser.add_argument('-x_range_label',type=str,nargs=1,metavar='str',default='X ($10^3$ km)',help='The text below the x axis')
settings_parser.add_argument('-y_range_label',type=str,nargs=1,metavar='str',default='Y ($10^3$ km)',help='The text to the left of the y axis')
settings_parser.add_argument('-time_format',type=str,metavar='{{seconds}, s, ms, milliseconds}',nargs=1,default='seconds',choices=["seconds", "s", "ms", "milliseconds"],help='Time format code to use for elapsed time and bounce time')
settings_parser.add_argument('-bounce_time_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='Boolean option for "time since bounce" display')
settings_parser.add_argument('-ctime_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='Boolean option for data creation time display')
settings_parser.add_argument('-elapsed_time_enabled',type=check_bool,choices=[True,False],metavar='{{True},False}',default=True,help='Boolean option for "elapsed time" display')
settings_parser.add_argument('-zoom_value',type=check_float,help='The zoom value (percentage of total range) to use if the x or y range is set to \'auto\'')
settings_parser.add_argument('-var_unit',type=str,default='auto',help='The unit to use for the plotted variable')
print_help()#print_help does a hacky help flag overload by intercepting the sys.argv before the parser in order to also print the help for the settings file
#if the help flag isn't there, continue and parse arguments as normal
args=parser.parse_args()

# define an error printing function for error reporting to terminal STD error IO stream
def eprint(*arg, **kwargs):
	print(*arg, file=sys.stderr, **kwargs)

#define a standard printing function that only functions if there is no silence flag on script invocation
def qprint(*arg,**kwargs):
	if not args.quiet:
		print(*arg,**kwargs)

#display help for just the config parser if "help" or "h" appears after -s or --settings
if args.settingsfile and args.settingsfile in ['help','h']:
	settings_parser.print_help()
	sys.exit()

# Define parsed settings
argslist=[i[1:] for i in settings_parser.__dict__['_option_string_actions'].keys()] #generate list of valid arguments to prepend '-' to if it's not the first char
if args.settingsfile and args.settingsfile!='':
	settingsargs=[]
	for super_arg in csv.reader(open(args.settingsfile).read().split('\n'),delimiter=' ',quotechar='"',escapechar='\\'):
		if super_arg[0][0]+super_arg[0][1]=='//': #implement commenting
			continue 
		for arg in super_arg:
			# account for the required '-' needed for argparse
			if arg in argslist:
				settingsargs.append('-'+arg) 
			else:
				settingsargs.append(arg)
	settings=settings_parser.parse_args(settingsargs)
del argslist

#Robustly import an xml writer/parser for parseing the xdmf tree
try:
	from lxml import etree as et
	qprint("Running with lxml.etree")
except ImportError:
	try:
		# Python 2.5
		import xml.etree.cElementTree as et
		import xml.dom.minidom as md
		qprint("Running with cElementTree on Python 2.5+")
	except ImportError:
		try:
		# Python 2.5
			import xml.etree.ElementTree as et
			qprint("Running with ElementTree on Python 2.5+")
		except ImportError:
			try:
				# normal cElementTree install
				import cElementTree as et
				qprint("running with cElementTree")
			except ImportError:
				try:
					# normal ElementTree install
					import elementtree.ElementTree as et
					qprint("running with ElementTree")
				except ImportError as e:
					eprint("Fatal error: Failed to import ElementTree from any known place. XML writing is impossible. ")
					traceback.print_exception(type(e),e,sys.exc_info()[2])
					sys.exit()
# Carefully import numpy for heavy number crunching
try:
	import numpy as np
	def pol2cart(rho, phi):
		x = rho * np.cos(phi)
		y = rho * np.sin(phi)
		return(x, y)
	def cart2pol(x, y):
		rho = np.sqrt(x**2 + y**2)
		phi = np.arctan2(y, x)
		return(rho, phi)
except ImportError as e:
	eprint('Fatal Error: numpy not found! (used to do math faster)')
	traceback.print_exception(type(e),e,sys.exc_info()[2])
	sys.exit()

#create a function to list all valid grid names in the xdmf:
def valid_grid_names():
	gridnames=[]
	for grd in domain.findall('Grid'): #grd is a grid element
		gridnames.append(grd.get('Name'))
	return gridnames

#create a function to list all valid variables for a given grid:
def valid_variables(gridname):
	variables=[]
	for attribute in domain.findall("*[@Name='"+gridname+"']/Attribute"):
		variables.append(attribute.attrib['Name'])
	return variables

#create a function to list all valid scalars from the currently parsed xdmf tree
def tree():
	qprint('Found valid scalars:')
	for grid in valid_grid_names():
		qprint(grid,end=':')
		variables=valid_variables(grid)
		for i,variable in enumerate(variables):
			qprint(['\n\t  ',''][i!=0 or i==len(variables)]+variable+(', '+'\n\t  '*((i+1)%5==0))*(i!=len(variables)-1),end='')
		qprint()

for file in args.files:
	domain=et.parse(file).getroot()[0]
	
	if args.tree:
{'fraction_non_alphanumeric': 0.14221507504535708, 'fraction_numerical': 0.005607784924954643, 'mean_word_length': 6.91546997389034, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3028422', 'n_tokens_mistral': 7986, 'n_tokens_neox': 7640, 'n_words': 2070}