try:
# framework is running
from .startup_choice import *
except ImportError as _excp:
# class is imported by itself
if (
'attempted relative import with no known parent package' in str(_excp)
or 'No module named \'omfit_classes\'' in str(_excp)
or "No module named '__main__.startup_choice'" in str(_excp)
):
from startup_choice import *
else:
raise
from omfit_classes.omfit_base import OMFITtree, OMFITexpression
from omfit_classes.omfit_ascii import OMFITascii
from omfit_classes.omfit_path import OMFITpath
from omfit_classes.omfit_namelist import OMFITnamelist
from omfit_classes import OMFITx
__all__ = ['OMFITlatex']
[docs]class OMFITlatex(OMFITtree):
"""
Class used to manage LaTeX projects in OMFIT.
For testing, please see: samples/sample_tex.tex, samples/sample_bib.bib, samples/placeholder.jpb, and
samples/bib_style_d3dde.bst .
:param main: string
Filename of the main .tex file that will be compiled.
:param mainroot: string
Filename without the .tex extension. Leave as None to let this be determined
automatically. Only use this option if you have a confusing filename that breaks the automatic determinator,
like blah.tex.tex (hint: try not to give your files confusing names).
:param local_build: bool
Deploy pdflatex job locally instead of deploying to remote server specified in SETTINGS
:param export_path: string
Your LaTeX project can be quickly exported to this path if it is defined. The project can be
automatically exported after build if this is defined. Can be defined later in the settings namelist.
:param export_after_build: bool
Automatically export after building if export_path is defined. Can be updated later in the settings namelist.
:param hide_style_files: bool
Put style files in hidden __sty__ sub tree in OMFIT (they still deploy to top level folder on disk)
:param debug: bool
Some data will be saved to OMFIT['scratch']
"""
def __init__(
self,
root=None,
main='main.tex',
mainroot=None,
local_build=True,
export_path=None,
export_after_build=False,
hide_style_files=True,
debug=False,
**kw,
):
printd('Initializing omfit_latex instance with main file = {:}'.format(main), topic='latex')
OMFITtree.__init__(self, **kw)
self.latex_debug = debug
# Set up basic definitions and structure
self.ascii_types = ['tex', 'txt', 'bbl', 'bib', 'aux', 'log', 'bst', 'sh', 'dat']
self.aux_types = ['bbl', 'aux', 'log', 'out', 'backup', 'blg', 'gz']
self.style_types = ['cls', 'sty', 'clo', 'bst']
self.no_load = ['OMFIT_run_command.sh', 'OMFITlatex_settings.dat']
self.pdflatex_flags = '-halt-on-error' # This string is inserted between pdflatex and command line arguments
self.hide_style_files = hide_style_files # Not in settings because ugly to change after init.
self.__mainroot = mainroot
self.__export_path = export_path
self.__main = main
self.__export_after_build = export_after_build
self.__hide_style_files = hide_style_files
# Define sub-folder to isolate LaTeX stuff from other I/O from the module to avoid accidentally picking up
# unrelated files
self.latex_folder = 'OMFITlatex_{}'.format(id(self))
printd('OMFITlatex self.latex_folder = {}'.format(self.latex_folder), topic='latex')
# Figure out the parent module root
self.root = OMFIT if root is None else root
# Deal with servers an directories
self.lookup_dirs_and_servers()
if local_build:
self.server = 'localhost'
self.tunnel = ''
self.remotedir = SERVER['localhost']['workDir']
# Load settings and sub-folders
self.prepare_settings()
printd('Done with OMFITlatex initialization', topic='latex')
return
[docs] def prepare_settings(self):
main = self.__main
mainroot = self.__mainroot
export_path = self.__export_path
export_after_build = self.__export_after_build
hide_style_files = self.__hide_style_files
# Prepare settings
try:
self.setdefault('settings', OMFITnamelist('OMFITlatex_settings.dat'))
except OSError:
# This can happen if the class is being initialized while OMFIT is still loading.
# The project load sequence will put settings in for us, so we can skip this part.
printd('Unable to prepare OMFITlatex settings at this time.', topic='latex')
return
printd('Preparing OMFITlatex settings...', topic='latex')
# These sub-folders should also be loaded by the project if that's what's happening
self['__aux__'] = OMFITtree()
if hide_style_files:
self['__sty__'] = OMFITtree()
# Set up name of main file
if main[-4:] != os.extsep + 'tex':
self['settings']['main'] = main + os.extsep + 'tex'
else:
self['settings']['main'] = main
# Main file is a path to a file on disk, not just a name: attempt to load existing project.
if os.sep in self['settings']['main']:
if os.path.exists(self['settings']['main']) and os.path.isfile(self['settings']['main']):
# The attempt to load a project found a valid file
printd('Given initial main file as input...', topic='latex')
main_dir = os.path.split(self['settings']['main'])[0]
self['settings']['main'] = os.path.split(self['settings']['main'])[1]
if export_path is None:
export_path = main_dir
self.grab_project(main_dir=main_dir, get_tex=True)
printd(
'Loaded LaTeX project from main file: {:}, export_path = {:}'.format(self['settings']['main'], export_path),
topic='latex',
)
else:
# User attempted to load an existing project, but the file was not valid.
printw(
'Warning: could not load LaTeX project using file {:}. Please check path and try again.'.format(
self['settings']['main']
)
)
self['settings']['export_path'] = os.path.split(self['settings']['main'])[0]
self['settings']['main'] = os.path.split(self['settings']['main'])[1] # Reduce to filename w/o path
# Set up root filename of main file
if mainroot is None:
self['settings']['mainroot'] = self['settings']['main'].split(os.extsep + 'tex')[0]
else:
self['settings']['mainroot'] = mainroot
self['settings']['main'] = OMFITexpression("return_variable = parent['mainroot'] + '.tex'")
self['settings']['outfile'] = OMFITexpression("return_variable = parent['mainroot'] + '.pdf'")
self['settings']['export_path'] = export_path
self['settings']['export_after_build'] = export_after_build
self['settings']['default_build_sequence'] = 'full'
self['settings']['default_clean_before_build'] = False
printd('Done preparing OMFITlatex settings.', topic='latex')
[docs] def lookup_dirs_and_servers(self, overwrite=False):
"""
Looks up workdir, remotedir, tunnel, and server
Tries to link to module or top-level settings to get information.
:param overwrite: bool
Overwrite existing attributes. Otherwise, attributes with valid definitions will not be updated.
"""
printd('Looking up directory and server information...', topic='latex')
# Link to module settings and define workdir, etc.
try:
module_settings = self.root['SETTINGS']
_ = module_settings['SETUP']['workDir']
_ = module_settings['REMOTE_SETUP']
except (KeyError, TypeError):
printd(' Drawing dir & server info from MainSettings...', topic='latex')
ref_workdir = evalExpr(OMFIT['MainSettings']['SETUP']['workDir'])
ref_remotedir = evalExpr(SERVER['default']['workDir'])
tunnel = evalExpr(SERVER['default']['tunnel'])
server = evalExpr(SERVER['default']['server'])
else:
printd(' Drawing dir & server info from parent module SETTINGS...', topic='latex')
ref_workdir = evalExpr(module_settings['SETUP']['workDir'])
ref_remotedir = evalExpr(module_settings['REMOTE_SETUP']['workDir'])
tunnel = evalExpr(module_settings['REMOTE_SETUP']['tunnel'])
server = evalExpr(module_settings['REMOTE_SETUP']['server'])
printd(
f' Found ref_workdir = {repr(ref_workdir)}, ref_remotedir = {repr(ref_remotedir)}, '
f'server = {repr(server)}, tunnel = {repr(tunnel)}',
topic='latex',
)
# If workdir and remotedir are valid directories, extend them by adding the latex_folder
if ref_workdir is None or isinstance(ref_workdir, NamelistName):
# Not sure why it comes back as NamelistName sometimes, but it does.
workdir = None
else:
workdir = ref_workdir + os.sep + self.latex_folder
if ref_remotedir is None or isinstance(ref_workdir, NamelistName):
remotedir = None
else:
remotedir = ref_remotedir + os.sep + self.latex_folder
# If workdir and remotedir are missing, signal that we shouldn't trust tunnel and server
if workdir is None and remotedir is None:
printd(
"Failed to find useful info about directories & servers. This can happen during project loading. ",
topic='latex',
)
tunnel = server = None
# Load data
if (getattr(self, 'workdir', None) is None) or overwrite:
self.workdir = workdir
printd(f' Set self.workdir = {self.workdir}', topic='latex')
if (getattr(self, 'remotedir', None) is None) or overwrite:
self.remotedir = remotedir
printd(f' Set self.remotedir = {self.remotedir}', topic='latex')
if (getattr(self, 'tunnel', None) is None) or overwrite:
self.tunnel = tunnel
printd(f' Set self.tunnel = {self.tunnel}', topic='latex')
if (getattr(self, 'server', None) is None) or overwrite:
self.server = server
printd(f' Set self.server = {self.server}', topic='latex')
def __call__(self, **kw):
return self.run(**kw)
[docs] def grab_project(self, main_dir=None, get_tex=False):
"""
Grabs project files during init or after build
:param main_dir: string
Directory with the files to gather
:param get_tex: bool
Gather .tex files as well (only recommended during init)
"""
if main_dir is None:
if self.workdir is None:
self.lookup_dirs_and_servers()
main_dir = self.workdir
destination = self
depth = 0
def gp_loader(gp_dir, gp_destination=self, gp_depth=0):
printd('Loading LaTeX project from {:} ...'.format(gp_dir), topic='latex')
files = glob.glob(gp_dir + os.sep + '*')
if gp_depth > 20:
printw('Warning: grab project loader reached maximum recursion depth and aborted.')
return
for f in files:
printd(' Load {}: {} ... '.format(os.path.split(f)[1], f), topic='latex')
d1 = gp_destination
if os.path.isdir(f):
# Is a directory, load contents
printd(' This is a directory (current recursion depth = {:})'.format(gp_depth), topic='latex')
f2 = os.path.split(f)[1]
gp_loader(gp_dir + os.sep + f2, d1.setdefault(f2, OMFITtree()), gp_depth + 1)
else:
# Not a directory, load file
if os.path.splitext(f)[1].strip('.') in self.aux_types:
# Hide aux files in separate folder
printd(' This is an aux file. (recur depth = {})'.format(gp_depth), topic='latex')
d1 = d1.setdefault('__aux__', OMFITtree())
elif os.path.splitext(f)[1].strip('.') in self.style_types and self.hide_style_files:
# Hide style files in separate folder
printd(' This is an style file. (recur depth = {})'.format(gp_depth), topic='latex')
d1 = d1.setdefault('__sty__', OMFITtree())
elif os.path.split(f)[1] in self.no_load:
# Skip certain files
printd(' This file should be skipped. (recur depth = {})'.format(gp_depth), topic='latex')
continue
elif os.path.splitext(f)[1].strip('.') == 'tex':
if get_tex:
printd(
' This is a tex file, and get_tex=True, so it will be gathered. ' '(recur depth = {})'.format(gp_depth),
topic='latex',
)
else:
printd(
' This is a tex file, and get_tex=False, so it will be SKIPPED. ' '(recur depth = {})'.format(gp_depth),
topic='latex',
)
continue
else:
printd(' This is a regular file (recur depth = {})'.format(gp_depth), topic='latex')
try:
if os.path.splitext(f)[1].strip('.') in self.ascii_types:
d1[os.path.split(f)[1]] = OMFITascii(f)
else:
d1[os.path.split(f)[1]] = OMFITpath(f)
except OMFITexception:
printe('Could not load %s' % f)
if not files:
printd(' This folder appears to be empty: {:}'.format(gp_dir), topic='latex')
return # End of gp_loader()
gp_loader(main_dir, destination, depth)
return
[docs] def export(self, export_path=None):
"""
This is a wrapper for .deploy() that uses the export_path in settings by default.
:param export_path: string
The path to which files should be exported.
"""
# from OMFITx import executable
if self.workdir is None:
self.lookup_dirs_and_servers()
if export_path is None:
export_path = self['settings']['export_path']
if export_path is None:
printe("Cannot export until export_path is defined. Please set self['settings']['export_path']")
else:
printi('Exporting OMFITlatex project to {:}...'.format(self['settings']['export_path']))
printi(
' Note: this should happen automatically after building if export_path is set, \n'
'but you can pass export_path to this function as a keyword to export to \n'
'somewhere else without changing the default export location. Another \n'
'advantage is that manual export only copies the essential files and not \n'
'all the LaTeX aux and log files. You can turn off export_after_build. and \n'
'just export manually.'
)
self.deploy(filename=export_path, updateExistingDir=True)
# Cleanup hidden files
command = '\n'.join(
['cd {:}'.format(export_path), 'rm -rf __aux__', 'mv __sty__/* .', 'rm -rf __sty__', 'rm -rf __*__', 'rm OMFITsave.txt']
)
OMFITx.executable(
self.root,
inputs=[],
outputs=[],
executable=command,
clean=False,
server='localhost',
tunnel='',
remotedir=self.workdir,
workdir=self.workdir,
)
return
[docs] def load_sample(self):
"""
Loads sample LaTeX source files for use as a template, example, or test case
"""
sample_path = os.path.abspath(os.sep.join([OMFITsrc, '..', 'samples', 'tex', ''])) + os.sep
self.setdefault('figures', OMFITtree())['placeholder.jpg'] = OMFITpath('{:}figures/placeholder.jpg'.format(sample_path))
self.setdefault('includes', OMFITtree())['include_me.tex'] = OMFITascii('{:}includes/include_me.tex'.format(sample_path))
self['includes'].setdefault('subsubfolder_lol', OMFITtree())['subsubsub.tex'] = OMFITascii(
'{:}includes/subsubfolder_lol/subsubsub.tex'.format(sample_path)
)
self['sample_tex.tex'] = OMFITascii('{:}sample_tex.tex'.format(sample_path))
self['sample_bib.bib'] = OMFITascii('{:}sample_bib.bib'.format(sample_path))
self['bib_style_d3dde.bst'] = OMFITascii('{:}bib_style_d3dde.bst'.format(sample_path))
return
[docs] def debug(self):
"""Prints info about the project."""
len1 = 30 # Short length
len2 = len1 * 2 # Long length
form = '{{:{:}}} = {{:}}'.format(len1)
print('=' * len2)
print('Debug info for OMFITlatex class in omfit_latex.py')
print('-' * len2)
print(form.format('self.__class__.__name__', self.__class__.__name__))
print(form.format("Main file, self['settings']['main']", self['settings']['main']))
print(form.format("Expected output file, self['settings']['outfile']", self['settings']['outfile']))
print(form.format('self.keys()', list(self.keys())))
print(form.format('self.workdir', self.workdir))
print('=' * len2)
return
[docs] def clear_aux(self):
"""Deletes aux files, like .log, .aux, .blg, etc."""
self['__aux__'] = OMFITtree()
return
[docs] def build_clean(self):
"""Wrapper for build that calls clear_aux first and then calls build with clean keyword."""
self.clear_aux()
self.build(clean_before_build=True, sequence='full')
return
[docs] def build_pdflatex(self):
"""Wrapper for build that selects the pdflatex sequence."""
self.build(sequence='pdflatex')
return
[docs] def build_bibtex(self):
"""Wrapper for build that selects the bibtex sequence."""
self.build(sequence='bibtex')
return
[docs] def build_full(self):
"""Wrapper for build that selects the full seqeunce."""
self.build(sequence='full')
return
[docs] def check_main_file(self):
"""
Makes sure the file defined by self['settings']['main'] is present in inputs.
Backup: check if self['settings']['mainroot'] might work.
:return: bool
main file is okay (self['settings']['main'] is present) or might be okay
(self['settings']['mainroot'] is present).
"""
if self['settings']['main'] in list(self.keys()):
printd('Main file is present. Everything is okay.', topic='latex')
return True
else:
printd('Main file not found. Checking for mainroot...', topic='latex')
filenames = [self[k].filename.split(os.sep)[-1] for k in list(self.keys())]
rootnames = [os.extsep.join(filename.split(os.extsep)[:-1]) for filename in filenames]
printd(' File name roots (extensions removed): {:}'.format(rootnames), topic='latex')
if self['settings']['mainroot'] in rootnames:
printw('LaTeX warning: You may need to set the name of the main LaTeX file.')
printd('mainroot was in rootnames, so this might be okay, or you might have a bad day.', topic='latex')
return True
else:
printw('LaTeX warning: Main file name not present in inputs. LaTeX will not run!')
return False
[docs] def search_for_tex_files(self):
"""
:return: List of .tex files in self
"""
filenames = [self[k].filename.split(os.sep)[-1] for k in list(self.keys())]
printd(' Searching, filenames = {:}'.format(filenames), topic='latex')
tex_files = [filename for filename in filenames if filename.split(os.extsep)[-1].lower() == 'tex']
printd(' Search result: tex_files = {:}'.format(tex_files), topic='latex')
return tex_files
[docs] def build(self, clean_before_build=None, sequence=None):
"""
:param clean_before_build: bool
clear temporary workDir before deploying; None pulls value from settings.
:param sequence: string
'full', 'pdflatex', 'bibtex', '2pdflatex'; None pulls value from settings.
"""
printd('Building latex project ({:})...'.format(self['settings']['main']), topic='latex')
if None in [self.workdir, self.remotedir, self.tunnel, self.server]:
self.lookup_dirs_and_servers()
if sequence is None:
sequence = self['settings']['default_build_sequence']
if clean_before_build is None:
clean_before_build = self['settings']['default_clean_before_build']
pdflatex_outputs = [
self['settings']['mainroot'] + '.pdf',
self['settings']['mainroot'] + '.aux',
self['settings']['mainroot'] + '.log',
self['settings']['mainroot'] + '.out',
]
bibtex_outputs = [self['settings']['mainroot'] + '.bbl', self['settings']['mainroot'] + '.blg']
if not self.check_main_file():
tex_files = self.search_for_tex_files()
if len(tex_files):
self['settings']['mainroot'] = '.'.join(tex_files[0].split('.')[:-1])
print('Main LaTeX file definition was invalid. Main file updated to be: {:}'.format(self['settings']['main']))
printd("Update main file by changing ['settings']['mainroot'] (no extension)", topic='latex')
else:
printw('Could not find any .tex files; cannot automatically update main file.')
# Define the main commands. Some sequences will repeat these commands.
mov_command = 'mv %s{,.backup} || true' % self['settings']['outfile']
bib_command = 'bibtex {:} || true'.format(self['settings']['mainroot'])
tex_command = 'pdflatex {flags:} {args:}'.format(flags=self.pdflatex_flags, args=self['settings']['mainroot'])
if sequence == 'pdflatex':
# Commands for building LaTeX project (just one pass of pdflatex, for minor changes)
latex_commands = ['echo "OMFIT is trying to build a latex project (one pass pdflatex)..."', mov_command, tex_command]
outputs = pdflatex_outputs
elif sequence == '2pdflatex':
# Commands for building LaTeX project (two passes of pdflatex, for changes that don't affect bibliography)
latex_commands = [
'echo "OMFIT is trying to build a latex project (two pass pdflatex)..."',
mov_command,
tex_command,
tex_command,
]
outputs = pdflatex_outputs
elif sequence == 'bibtex':
# Commands for building LaTeX project (just bibtex, in case you want to go through the sequence manually)
latex_commands = ['echo "OMFIT is trying to build a latex project (solo bibtex)..."', mov_command, bib_command]
outputs = bibtex_outputs
else:
# Commands for building LaTeX project (full build) (this is the default if the if/elif statements all fail)
latex_commands = [
'echo "OMFIT is trying to build a latex project (FULL BUILD)..."',
mov_command,
tex_command,
bib_command,
tex_command,
tex_command,
]
outputs = pdflatex_outputs + bibtex_outputs
# Make sure outputs exist so that OMFITx.executable doesn't fail if one of them isn't generated
for output_file in outputs:
latex_commands = ['touch {}'.format(output_file)] + latex_commands
latex_commands = ['%s || exit %d' % (v, k + 1) for k, v in enumerate(latex_commands)]
command = '\n'.join(latex_commands)
inputs = [self[k] for k in list(self.keys()) if hasattr(self[k], 'filename') and self[k].filename]
def extend_inputs_from_subfolder(folder, sub, e_inputs, e_outputs, pre='', depth=0):
if depth > 20:
printw('Maximum recursion depth reached in extend_inputs_from_subfolder(). Aborting...', topic='latex')
return e_inputs, e_outputs
if isinstance(folder[sub], OMFITtree):
printd(' Checking subfolder {:} ... (recursion depth = {:})'.format(sub, depth), topic='latex')
i2 = [
(
folder[sub][kk],
'{:}{:}{:}{:}'.format(pre, sub, os.sep, os.path.split(folder[sub][kk].filename)[1])
.replace('__aux__', '')
.replace('__sty__', ''),
) # Prevent nested __aux__/__aux__/__aux__/...
for kk in list(folder[sub].keys())
if hasattr(folder[sub][kk], 'filename') and folder[sub][kk].filename
]
e_inputs += i2
e_outputs += [ii[1].replace('.tex', '.*') for ii in i2 if ii[1].endswith('.tex')]
printd(' Added more files from subfolder {:}: {:}'.format(sub, i2), topic='latex')
for kk in list(folder[sub].keys()):
e_inputs, e_outputs = extend_inputs_from_subfolder(folder[sub], kk, e_inputs, e_outputs, pre + sub + os.sep, depth + 1)
return e_inputs, e_outputs
for k in list(self.keys()):
if k in ['__aux__', '__sty__']:
# Special treatment; don't deploy an __aux__ folder but instead dump aux files into main folder.
# Subfolders in __aux__ will not be supported nor should they exist.
inputs += [
(self[k][m], os.path.basename(self[k][m].filename).replace(k, ''))
for m in list(self[k].keys())
if hasattr(self[k][m], 'filename') and self[k][m].filename
]
else:
inputs, outputs = extend_inputs_from_subfolder(self, k, inputs, outputs, '', 0)
printd('Executing LaTeX commands, \n\n inputs = \n{:} \n\n outputs = \n{:}'.format(inputs, outputs), topic='latex')
if self.latex_debug:
oscratch = OMFIT['scratch'].setdefault('omfit_latex_test', OMFITtree())
exa = oscratch.setdefault('executable_arguments', OMFITtree())
exa['inputs'] = inputs
exa['outputs'] = outputs
exa['command'] = command
OMFITx.executable(
self.root,
inputs=inputs,
outputs=outputs,
executable=command,
clean=clean_before_build,
server=self.server,
tunnel=self.tunnel,
remotedir=self.remotedir,
workdir=self.workdir,
ignoreReturnCode=False,
)
self.grab_project(main_dir=self.workdir)
printd('Items in OMFITlatex project after build: {:}'.format(list(self.keys())), topic='latex')
if self['settings']['export_path'] is not None and self['settings']['export_after_build']:
self.export()
return
[docs] def run(self):
"""
Shortcut for opening the output file. If the output file has not been generated yet, a build will be attempted.
This lets the user easily open the output from the top level context menu for the OMFITlatex instance, which is
convenient.
"""
if self['settings']['outfile'] not in list(self.keys()):
self.build()
filename = self[self['settings']['outfile']]
if OMFITaux['GUI'] is not None:
OMFITaux['GUI'].openFile(thisObject=filename)
else:
program = OMFIT['MainSettings']['SETUP']['Extensions'].get('pdf', None)
if program is not None:
subprocess.Popen("{} '{}'".format(program, filename), shell=True)
else:
printe('Could not identify a program for opening PDF output. You can try to open the file manually:')
print(filename)
return
[docs] def open_gui(self):
OMFIT['scratch']['__latexGUI__'].run(tex=self, singleGUIinstance=False)
return
def __popup_menu__(self):
def run_and_update(command):
getattr(self, command)()
OMFITaux['GUI'].update_treeGUI()
return [
['LaTeX>controls...', self.open_gui],
['LaTeX>Build (default sequence)', lambda command='build': run_and_update(command)],
['LaTeX>Full Build (pdflatex, bibtex, pdflatex, pdflatex)', lambda command='build_full': run_and_update(command)],
['LaTeX>Clear aux files', lambda command='clear_aux': run_and_update(command)],
['LaTeX>Open PDF', self.run],
['LaTeX>Export project', self.export],
]