Source code for main

#
##
##  This file is part of pyFormex 2.5  (Wed Mar 24 20:37:37 CET 2021)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: http://pyformex.org
##  Project page:  http://savannah.nongnu.org/projects/pyformex/
##  Copyright 2004-2021 (C) Benedict Verhegghe (bverheg@gmail.com)
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##

# This is the only pyFormex module that is imported by the main script,
# so this is the place to put startup code

"""pyFormex main module

This module contains the main function of pyFormex, which is run by the
startup script.
"""

import sys
import os

import pyformex as pf
from pyformex.options import parseOptions


[docs]def showWhereIAm(): """Show where pyFormex is installed""" print(pf.fullVersion()) print(f"pyFormex executable: {pf.executable}") print(f"pyFormex installation ({pf.installtype}): {pf.pyformexdir}") print(f"Python sys.path: {sys.path}")
[docs]def run_docmodule(module): """Print autogenerated documentation for the module. module is a pyFormex module dotted path. The leading pyformex. may be omitted. """ from . import py2rst return py2rst.do_module(module)
[docs]def loadUserConfig(): # noqa: C901 """Load the pyFormex configuration Notes ----- This function should be called to create a proper configuration when pyFormex is imported in Python and not started from the pyformex command. """ pf.logger.info("Loading configuration files") from pyformex import Path from pyformex.config import Config # Set the config files if pf.options.nodefaultconfig: sysprefs = [] userprefs = [] else: sysprefs = [pf.cfg['siteprefs']] userprefs = [pf.cfg['userprefs']] if pf.cfg['localprefs'].exists(): userprefs.append(pf.cfg['localprefs']) sysprefs = [f for f in sysprefs if f.exists()] userprefs = [f for f in userprefs if f.exists()] if pf.options.config: userprefs.append(Path(pf.options.config).expanduser()) if len(userprefs) == 0: # We should always have a place to store the user preferences userprefs = [pf.cfg['userprefs']] # Use last one to save preferences pf.debug(f"System Preference Files: {sysprefs}", pf.DEBUG.CONFIG) pf.debug(f"User Preference Files: {userprefs}", pf.DEBUG.CONFIG) pf.preffile = Path.resolve(userprefs.pop()) # Read sysprefs as reference for f in sysprefs: pf.debug(f"Reading config file {f}", pf.DEBUG.CONFIG) pf.cfg.load(f) # Set this as reference config pf.refcfg = pf.cfg pf.debug("=" * 60, pf.DEBUG.CONFIG) pf.debug(f"RefConfig: {pf.refcfg}", pf.DEBUG.CONFIG) pf.cfg = Config(default=pf.refcfg) # Read userprefs as reference for f in userprefs: if f.exists(): pf.debug(f"Reading config file {f}", pf.DEBUG.CONFIG) pf.cfg.load(f) else: pf.debug(f"Skip non-existing config file {f}", pf.DEBUG.CONFIG) if pf.preffile.exists(): pf.debug(f"Reading config file {pf.preffile}", pf.DEBUG.CONFIG) pf.cfg.load(pf.preffile) else: # Create the config file pf.debug(f"Creating config file {pf.preffile}", pf.DEBUG.CONFIG) try: Path.mkdir(pf.preffile.parent, parents=True, exist_ok=True) pf.preffile.touch() except Exception: pf.startup_warnings += ( f"Could not create the user configuration file {pf.preffile}.\n" "User preferences will not be saved.\n") pf.preffile = None # Set this as preferences config pf.prefcfg = pf.cfg pf.debug("=" * 60, pf.DEBUG.CONFIG) pf.debug(f"Config: {pf.prefcfg}", pf.DEBUG.CONFIG) pf.cfg = Config(default=pf.prefcfg) # Fix incompatible changes in configuration from pyformex.cmdtools import apply_config_changes apply_config_changes(pf.prefcfg)
########################### main ################################
[docs]def run(args=[]): # noqa: C901 """The pyFormex main function. After pyFormex launcher script has correctly set up the Python import paths, this function is executed. It is responsible for reading the configuration file(s), processing the command line options and starting the application. The basic configuration file is 'pyformexrc' located in the pyFormex main directory. It should always be present and be left unchanged. If you want to make changes, copy (parts of) this file to another location where you can change them. Then make sure pyFormex reads you modifications file. By default, pyFormex will try to read the following configuration files if they are present (and in this order):: default settings: <pyformexdir>/pyformexrc (always loaded) system-wide settings: /etc/pyformex.conf user settings: <configdir>/pyformex/pyformex.conf local settings $PWD/.pyformexrc Also, an extra config file can be specified in the command line, using the --config option. The system-wide and user settings can be skipped by using the --nodefaultconfig option. Config files are loaded in the above order. Settings always override those loaded from a previous file. When pyFormex exits, the preferences that were changed are written to the last read config file. Changed settings are those that differ from the settings obtained after loading all but the last config file. If none of the optional config files exists, a new user settings file will be created, and an error will occur if the <configdir> path is not writable. """ if isinstance(args, (str, bytes)): args = args.split() if not pf.executable.stem == 'pyformex': # We did not do options parsing yet: do it now # NOTICE: --pathlib option is ignored here! if not parseOptions(args): return 1 ## Process options that do not need/want config ## if pf.options.help: pf.print_help() return 0 if pf.options.docmodule is not None: pf.sphinx = True # Flag to pyformex_gts for a in pf.options.docmodule: run_docmodule(a) # This is an all_exclusive option !! # So we immediately return return 0 if pf.options.experimental: sys.path.insert(1, str(pf.pyformexdir / 'experimental')) ## Process special options that can be combined, but return ## if any is used ret = False if pf.options.whereami: showWhereIAm() ret = True if pf.options.debugitems: print(pf.debugItems()) ret = True if pf.options.detect: from pyformex import software showWhereIAm() print("Detecting installed helper software") print(software.reportSoftware()) ret = True if pf.options.listmodules is not None: from pyformex.cmdtools import list_modules print('\n'.join(list_modules(pf.options.listmodules))) ret = True if pf.options.pytest is not None: from pyformex.cmdtools import run_pytest run_pytest(pf.options.pytest) ret = True if pf.options.doctest is not None: from pyformex.cmdtools import run_doctest run_doctest(pf.options.doctest) ret = True if pf.options.remove: from pyformex.cmdtools import remove_pyFormex showWhereIAm() remove_pyFormex(pf.pyformexdir, pf.executable) # After this option, we can not continue, # so this should be the last option processed ret = True if ret: # Return for special options return 0 ## Migrate the user configuration files ## # TODO: this could become a command tool?? # migrateUserConfig() ## Load the user configuration ## loadUserConfig() ## Process special options which do not start pyFormex ## but depend on the user configuration if pf.options.search or pf.options.listfiles: from pyformex.utils import sourceFiles args = pf.options.args extended = False if len(args) > 0: opts = [a for a in args if a.startswith('-')] args = [a for a in args if a not in opts] if '-a' in opts: opts.remove('-a') extended = True if pf.options.search: search = args.pop(0) if len(args) > 0: files = args else: files = sourceFiles(relative=True, extended=extended) if pf.options.listfiles: print('\n'.join(files)) else: if "'" in search: search.replace("'", "\'") print(f"SEARCH = [{search}]", file=sys.stderr) cmd = 'grep %s "%s" %s' % ( ' '.join(opts), search, ''.join([" '%s'" % f for f in files]) ) os.system(cmd) return 0 ## If we get here, we want to start pyFormex ## Process options that override the config ## from pyformex import software, utils from pyformex.cmdtools import savePreferences # Config settings that are overridden by the matching option config_option = { 'gui/bindings': 'bindings', 'gui/redirect': 'redirect', 'opengl/version': 'opengl', } for setting in config_option: option = config_option[setting] value = getattr(pf.options,option) if value is not None and value != pf.cfg[setting]: print(f"Override config value '{setting}' with value '{value}'") pf.cfg[setting] = value delattr(pf.options, option) # avoid abuse utils.setSaneLocale() ## Check required modules ## software.Module.has('numpy', fatal=True) # minimal supported numpy version software.Module.require('numpy', pf.cfg['numpy']) # Initialize the libraries if pf.options.uselib is None: pf.options.uselib = pf.cfg['uselib'] # Force initialisation of the library from pyformex import lib # noqa: F401 # TODO: # without this, we get a crash. Maybe config related? pf.cfg['gui/startup_warning'] = None ## Activate the warning filters from pyformex.cmdtools import activateWarningFilters activateWarningFilters() # Make sure pf.PF is a Project from pyformex.project import Project pf.PF = Project() # Add a configured syspath if pf.cfg['syspath']: sys.path.extend(pf.cfg['syspath']) # Set application paths pf.debug("Loading AppDirs", pf.DEBUG.INFO) from pyformex import apps apps.setAppDirs() # Start the GUI if needed # Importing the gui should be done after the config is set !! # Set default --nogui if first remaining argument is a pyformex script. args = pf.options.args if pf.options.gui is None: pf.options.gui = not (len(args) > 0 and utils.is_pyFormex(args[0])) if pf.options.gui: if pf.options.mesa: os.environ['LIBGL_ALWAYS_SOFTWARE'] = '1' from pyformex.gui import guimain pf.debug("GUI version", pf.DEBUG.INFO) res = guimain.startGUI(args) if res != 0: print(f"Could not start the pyFormex GUI: {res}") return res # EXIT # Display the startup warnings and messages if pf.startup_warnings: if pf.cfg['startup_warnings']: pf.warning(pf.startup_warnings) else: print( "*** WARNING ***\n" + pf.startup_warnings + "****************\n" ) if pf.startup_messages: print(pf.startup_messages) if pf.options.debuglevel & pf.DEBUG.INFO: # NOTE: inside an if to avoid computing the report when not printed pf.debug(software.reportSoftware(), pf.DEBUG.INFO) # # Qt may have changed the locale. # Since a LC_NUMERIC setting other than C may cause lots of troubles # with reading and writing files (formats become incompatible!) # we put it back to a sane setting # utils.setSaneLocale() # Startup done pf.started = True print(f"pyFormex started from {pf.executable}") # Prepend the inline script if pf.options.script: args[0:0] = ['-c', pf.options.script] # Prepend the autorun script ar = pf.cfg['autorun'] if ar and ar.exists(): args[0:0] = [ar] # remaining args are interpreted as scripts/apps and their parameters res = 0 if args: pf.debug(f"Remaining args: {args}", pf.DEBUG.INFO) from pyformex.script import processArgs res = processArgs(args) if res: if pf.options.gui: print("There was an error while executing a script") else: return res # EXIT else: pf.debug(f"stdin is a tty: {sys.stdin.isatty()}", pf.DEBUG.INFO) # Play script from stdin # Can we check for interactive session: stdin connected to terminal? # from script import playScript # playScript(sys.stdin) # after processing all args, and we have a gui, go into interactive mode if pf.options.gui and pf.app: res = guimain.runGUI() # Save the preferences that have changed if not savePreferences(): print("!!! Preferences could not be saved !!!") elif pf.options.interactive: import readline # optional, will allow Up/Down/History in the console import code variables = globals().copy() variables.update(locals()) shell = code.InteractiveConsole(variables) shell.interact() # Exit pf.logger.info(f"{pf.fullVersion()} exit with status {res}") return res
# End