# Copyright (C) 2014 Dignity Health
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES
# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE
# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC
# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR
# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT
# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR
# MAKES NO WARRANTY AND HAS NO LIABILITY ARISING FROM ANY USE OF THE
# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES.
# Brief: A module for configuring gpi thru the ~/.gpirc file
import os
import traceback
import configparser
# gpi
from .associate import Bindings, BindCatalogItem
from gpi import VERSION
from .logger import manager
# start logger for this module
log = manager.getLogger(__name__)
GPIRC_FILENAME = '.gpirc'
# for windows
# GPIRC_FILENAME = 'gpi.conf'
### ENVIRONMENT VARIABLES
USER_HOME = os.environ['HOME']
USER_LIB_BASE_PATH_DEFAULT = USER_HOME+'/gpi'
try:
USER_LIB_PATH_DEFAULT = USER_LIB_BASE_PATH_DEFAULT+'/'+os.environ['USER']
except KeyError:
USER_LIB_PATH_DEFAULT = ''
ANACONDA_PREFIX='/opt/anaconda1anaconda2anaconda3' # is this needed?
GPI_PREFIX = os.path.dirname(os.path.realpath(__file__))
# for windows
# USER_HOME = os.path.expanduser('~')
GPI_NET_PATH_DEFAULT = USER_HOME
GPI_DATA_PATH_DEFAULT = USER_HOME
GPI_FOLLOW_CWD = True
GPI_LIBRARY_PATH_DEFAULT = [os.path.join(GPI_PREFIX, 'node-libs'), USER_LIB_BASE_PATH_DEFAULT] # distro default
###############################################################################
[docs]class ConfigManager(object):
'''An object that can load and generate the gpi config file.
The gpi config file can potentially hold configs for:
library paths,
network dir path,
data dir path,
plugin paths,
filetype-node associations,
canvas window start size,
UI style
'''
def __init__(self):
# general
self._g_import_check = True
# root dirs for organizing gpi related files.
self._c_networkDir = GPI_NET_PATH_DEFAULT
self._c_dataDir = GPI_DATA_PATH_DEFAULT
self._c_configFileName = os.path.expanduser('~/'+GPIRC_FILENAME)
# all the fix'ns for an initial lib
self._c_userLibraryBasePath = os.path.expanduser(USER_LIB_BASE_PATH_DEFAULT)
self._c_userLibraryPath = os.path.expanduser(USER_LIB_PATH_DEFAULT)
self._c_userLibraryPath_def = self._c_userLibraryPath+'/default'
self._c_userLibraryPath_def_GPI = self._c_userLibraryPath_def+'/GPI'
self._c_userLibraryPath_init = self._c_userLibraryPath+'/__init__.py'
self._c_userLibraryPath_def_init = self._c_userLibraryPath_def+'/__init__.py'
self._c_userLibraryPath_def_node = self._c_userLibraryPath_def_GPI+'/MyNode_GPI.py'
# env vars
self._c_gpi_lib_path = list(GPI_LIBRARY_PATH_DEFAULT)
self._c_gpi_follow_cwd = GPI_FOLLOW_CWD
self._new_node_template_file = os.path.join(GPI_PREFIX, 'nodeTemplate_GPI.py')
# make vars
self._make_libs = []
self._make_lib_dirs = []
self._make_inc_dirs = []
self._make_cflags = []
# try to read the config file
try:
self.loadConfigFile()
except:
log.error("The config file failed to load, using defaults. "+str(traceback.format_exc()))
def __str__(self):
msg = ''
# general
msg += 'GENERAL:\n'
for o in dir(self):
if o.startswith('_g_'):
msg += str(o) + ': ' + str(getattr(self, o)) + '\n'
# path
msg += 'PATH:\n'
for o in dir(self):
if o.startswith('_c_'):
msg += str(o) + ': ' + str(getattr(self, o)) + '\n'
# even though bindings are external print them here for convenience
msg += 'ASSOCIATIONS:\n'
for v in sorted([str(x) for x in list(Bindings.values())]):
msg += str(v) + '\n'
# makefile modifications
msg += 'MAKE:\n'
for o in dir(self):
if o.startswith('_make_'):
msg += str(o) + ': ' + str(getattr(self, o)) + '\n'
return msg
@property
def IMPORT_CHECK(self):
return self._g_import_check
@property
def GPI_NET_PATH(self):
return self._c_networkDir
@property
def GPI_DATA_PATH(self):
return self._c_dataDir
@property
def GPI_FOLLOW_CWD(self):
return self._c_gpi_follow_cwd
@property
def GPI_LIBRARY_PATH(self):
return self._c_gpi_lib_path
@property
def GPI_NEW_NODE_TEMPLATE_FILE(self):
return self._new_node_template_file
@property
def MAKE_LIBS(self):
return self._make_libs
@property
def MAKE_LIB_DIRS(self):
return self._make_lib_dirs
@property
def MAKE_INC_DIRS(self):
return self._make_inc_dirs
@property
def MAKE_CFLAGS(self):
return self._make_cflags
def generateUserLib(self):
self.initLibDir(self._c_userLibraryBasePath)
self.initLibDir(self._c_userLibraryPath)
self.initLibDir(self._c_userLibraryPath_def)
self.initLibDir(self._c_userLibraryPath_def_GPI)
self.initLibFile(self._c_userLibraryPath_init)
self.initLibFile(self._c_userLibraryPath_def_init)
if os.path.exists(self._c_userLibraryPath_def_node):
log.dialog('The user library example node: '+str(self._c_userLibraryPath_def_node) + ' already exists, skipping.')
else:
with open(self._c_userLibraryPath_def_node, 'w') as initfile:
log.dialog('Writing the example node: '+str(self._c_userLibraryPath_def_node) + '')
initfile.write(self.exampleNodeCode())
def exampleNodeCode(self):
header = '# GPI (v'+str(VERSION)+') auto-generated library file.\n#\n'
filename = '# FILE: '+str(self._c_userLibraryPath_def_node)+'\n#\n'
buf = '''# For node API examples (i.e. widgets and ports) look at the
# core.interfaces.Template node.
import gpi
class ExternalNode(gpi.NodeAPI):
\'\'\'About text goes here...
\'\'\'
def initUI(self):
# Widgets
self.addWidget('PushButton', 'MyPushButton', toggle=True)
# IO Ports
self.addInPort('in1', 'NPYarray')
self.addOutPort('out1', 'NPYarray')
return 0
def compute(self):
data = self.getData('in1')
# algorithm code...
self.setData('out1', data)
return 0'''
return header+filename+buf
def initLibDir(self, path):
if os.path.exists(path):
log.dialog('The user library path: '+str(path) + ' already exists, skipping.')
else:
log.dialog('Writing the user library path: '+str(path) + '')
os.mkdir(path)
def initLibFile(self, path):
if os.path.exists(path):
log.dialog('The user library file: '+str(path) + ' already exists, skipping.')
else:
log.dialog('Writing the user library file: '+str(path) + '')
with open(path, 'w') as initfile:
initfile.write('# GPI (v'+str(VERSION)+') auto-generated library file.\n')
def generateConfigFile(self, overwrite=False):
# check for existing config file
# -force user to remove, its safer
if self.configFileExists() and not overwrite:
log.dialog('Config file: '+str(self.configFilePath()) + ' already exists, skipping.')
return
with open(self._c_configFileName, 'w') as configfile:
# Header
configfile.write('# GPI (v'+str(VERSION)+') configuration file.\n')
configfile.write('# Uncomment an option to activate it.\n')
config = configparser.RawConfigParser()
# Makefile mods
configfile.write('\n[GENERAL]\n')
configfile.write('# Add nodes to the library only if they \'import\'.\n')
configfile.write('# GPI loads faster if this check is disabled.\n')
configfile.write('#IMPORT_CHECK = False\n')
# PATH Section
configfile.write('\n[PATH]\n')
configfile.write('# Add library paths for GPI nodes.\n')
configfile.write('# Multiple paths are delimited with a \':\'.\n')
configfile.write('# (e.g. [default] LIB_DIRS = ~/gpi:'+GPI_PREFIX+'/gpi/node-libs/).\n')
configfile.write('\n# A list of directories where nodes can be found.\n')
configfile.write('# -To enable the exercises add \''+GPI_PREFIX+'/lib/gpi/doc/Training/exercises\'.\n')
configfile.write('#LIB_DIRS = '+ ':'.join(GPI_LIBRARY_PATH_DEFAULT) + '\n')
configfile.write('\n# Network file browser starts in this directory.\n')
configfile.write('#NET_DIR = '+ GPI_NET_PATH_DEFAULT + '\n')
configfile.write('\n# Widget file browser starts in this directory.\n')
configfile.write('#DATA_DIR = '+ GPI_DATA_PATH_DEFAULT + '\n')
configfile.write('\n# Follow the user\'s cwd. If True, the widget and network directories\n')
configfile.write('# will change with the user input. If False, the browsers will alwasy open\n')
configfile.write('# to the NET_DIR and DATA_DIR.\n')
configfile.write('#FOLLOW_CWD = '+ str(GPI_FOLLOW_CWD)+ '\n')
#configfile.write('\n# A list of directories where plugins can be found.\n')
#configfile.write('#PLUGIN_DIRS = '+ ':'.join(GPI_PLUGIN_PATH_DEFAULT) + '\n')
# File-type Association Section
configfile.write('\n[ASSOCIATIONS]\n')
configfile.write('# Add file-type associations with nodes.\n')
configfile.write('# ex. (file extension, node name, widget name)\n')
# add default associations
cnt = 0
for key in sorted(Bindings.keys()):
item = Bindings.get(key)
configfile.write('#BIND_'+str(cnt) + ' = ' + str(item.asTuple()) + '\n')
cnt += 1
# Makefile mods
configfile.write('\n[MAKE]\n')
configfile.write('# Modify the gpi-make to include new libraries, library paths,\n')
configfile.write('# and include paths.\n')
configfile.write('# Example: (if blas is in \'/usr\' and lapack in \'/opt/lapack\'\n')
configfile.write('# g++ -I /usr/include -I /opt/lapack/include -L /usr/lib -L /opt/lapack/lib \n')
configfile.write('# -c x.cpp -lblas -llapack -o x.so -D_MY_MACRO_=helloworld -D_ANOTHER_\n')
configfile.write('#LIBS = blas:lapack\n')
configfile.write('#INC_DIRS = /usr/include:/opt/lapack/include\n')
configfile.write('#LIB_DIRS = /usr/lib:/opt/lapack/lib\n')
configfile.write('#CFLAGS = -D_MY_MACRO_=helloworld:-D_ANOTHER_\n')
log.dialog(str(self._c_configFileName)+' written.')
def loadConfigFile(self):
# load private vars from config file
if not self.configFileExists():
log.info("loadConfigFile(): config file " + str(self._c_configFileName) + " doesn't exist, skipping.")
return
config = configparser.ConfigParser()
config.read(self._c_configFileName)
# print parse-able info
#for s in config.sections():
# log.warn(str(config.items(s)))
# actual paths and config options
ap = lambda x: os.path.realpath(os.path.expanduser(x)) # single dirs
aps = lambda x: [ ap(p) for p in x.split(':') ] # multi-dirs
ch = config.has_option # if config has the option...
cg = config.get
oh = lambda x: x in os.environ
oe = os.environ
if config.has_section('GENERAL'):
parm = self.parseMultiOPTS(config, 'GENERAL', 'IMPORT_CHECK', 'GPI_IMPORT_CHECK')
if parm:
if parm[0].lower() == 'true':
self._g_import_check = True
elif parm[0].lower() == 'false':
self._g_import_check = False
# PATH section
# Precedence is set by this config file, then env vars, then defaults.
if config.has_section('PATH'):
parm = self.parseMultiOPTS(config, 'PATH', 'LIB_DIRS', 'GPI_LIBRARY_PATH')
if parm:
parm = self.checkDirs(parm, 'PATH::LIB_DIRS')
self._c_gpi_lib_path = parm
parm = self.parseMultiOPTS(config, 'PATH', 'NET_DIR', 'GPI_NET_PATH')
if parm:
parm = self.checkDirs(parm, 'PATH::NET_DIR')
self._c_networkDir = parm[0] # only single dir
parm = self.parseMultiOPTS(config, 'PATH', 'DATA_DIR', 'GPI_DATA_PATH')
if parm:
parm = self.checkDirs(parm, 'PATH::DATA_DIR')
self._c_dataDir = parm[0] # only single dir
parm = self.parseMultiOPTS(config, 'PATH', 'FOLLOW_CWD', 'GPI_FOLLOW_CWD')
if parm:
if parm[0].lower() == 'true':
self._c_gpi_follow_cwd = True
elif parm[0].lower() == 'false':
self._c_gpi_follow_cwd = False
# parm = self.parseMultiOPTS(config, 'PATH', 'PLUGIN_DIRS', 'GPI_PLUGIN_PATH')
# if parm:
# parm = self.checkDirs(parm, 'PATH::PLUGIN_DIRS')
# self._c_gpi_plugin_path = parm
# File-type Association Section
if config.has_section('ASSOCIATIONS'):
for item in config.items('ASSOCIATIONS'):
t = eval(str(item[1]))
if item[0].lower().startswith('BIND_'.lower()):
if len(t) != 3:
log.error(str(self._c_configFileName) + ': error in assignment: ' + str(item))
continue
if (type(t) is not tuple) or (type(t[0]) is not str) or (type(t[1]) is not str) or (type(t[2]) is not str):
log.error(str(self._c_configFileName) + ': error in assignment: ' + str(item))
else:
Bindings.append(BindCatalogItem(t))
# Makefile Section
if config.has_section('MAKE'):
parm = self.parseMultiOPTS(config, 'MAKE', 'LIBS', 'GPI_MAKE_LIBS')
if parm:
self._make_libs = parm
parm = self.parseMultiOPTS(config, 'MAKE', 'LIB_DIRS', 'GPI_MAKE_LIB_PATH')
if parm:
parm = self.checkDirs(parm, 'MAKE::LIB_DIRS')
self._make_lib_dirs = parm
parm = self.parseMultiOPTS(config, 'MAKE', 'INC_DIRS', 'GPI_MAKE_INC_PATH')
if parm:
parm = self.checkDirs(parm, 'MAKE::INC_DIRS')
self._make_inc_dirs = parm
parm = self.parseMultiOPTS(config, 'MAKE', 'CFLAGS', 'GPI_MAKE_CFLAGS')
if parm:
self._make_cflags = parm
log.dialog(str(self._c_configFileName) + ' has been loaded.')
def parseMultiOPTS(self, config, section, option, env_opt=None, warnOnENV=True):
# actual paths and config options
#ap = lambda x: os.path.realpath(os.path.expanduser(x)) # single dirs
ap = lambda x: x
aps = lambda x: [ ap(p) for p in x.split(':') ] # multi-dirs
ch = config.has_option # if config has the option...
cg = config.get
oh = lambda x: x in os.environ
oe = os.environ
if ch(section, option):
return aps(cg(section, option))
elif env_opt:
if oh(env_opt):
if warnOnENV:
log.warn('Setting from user environment. - '+str(env_opt))
return aps(oe[env_opt])
def checkDirs(self, l, opt):
ap = lambda x: os.path.realpath(os.path.expanduser(x)) # single dirs
# check each dir in the list and warn on non-existing dirs
out = []
for d in l:
de = ap(d) # expand paths
out.append(de)
if not os.path.isdir(de):
log.warn('User Config: \''+str(opt)+'\': \''+str(d)+'\' is not a directory.')
return out
def configFileExists(self):
return os.path.isfile(self._c_configFileName)
def configFilePath(self):
return self._c_configFileName
def userLibPath(self):
return self._c_userLibraryPath
# activate this upon first import
Config = ConfigManager()
#print Config