diff --git a/bin/qcms b/bin/qcms
index a7a5b56..c7edab8 100755
--- a/bin/qcms
+++ b/bin/qcms
@@ -21,11 +21,14 @@ import cms
from cms import index
from cms.engine.config.structure import Layout, System
-from cms.engine import project, config, themes, plugins
+from cms.engine import project, themes
+import cms.engine.config
+import cms.engine.project
import pandas as pd
import requests
-
-
+import plugin_ix
+from rich.table import Table
+from rich import print
start = index.start
__doc__ = f"""
Built and designed by Steve L. Nyemba, steve@the-phi.com
@@ -33,8 +36,10 @@ version {meta.__version__}
{meta.__license__}"""
-PASSED = ' '.join(['[',colored('\u2713', 'green'),']'])
-FAILED= ' '.join(['[',colored('\u2717','red'),']'])
+# PASSED = ' '.join(['[',colored('\u2713', 'green'),']'])
+# FAILED= ' '.join(['[',colored('\u2717','red'),']'])
+FAILED = '[ [red] \u2717 [/red] ]'
+PASSED = '[ [green] \u2713 [/green] ]'
INVALID_FOLDER = """
{FAILED} Unable to proceed, could not find project manifest. It should be qcms-manifest.json
@@ -43,6 +48,20 @@ INVALID_FOLDER = """
# handling cli interface
cli = typer.Typer()
+def to_Table(df: pd.DataFrame):
+ """Displays a Pandas DataFrame as a rich table."""
+ table = Table(show_header=True, header_style="bold magenta")
+
+ for col in df.columns:
+ table.add_column(col)
+
+ for _, row in df.iterrows():
+ table.add_row(*row.astype(str).tolist())
+
+ # console.print(table)
+ return table
+
+
def get_manifest (manifest):
if not manifest.endswith('json') and os.path.isdir(manifest):
manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1])
@@ -63,9 +82,12 @@ def _info():
@cli.command(name='setup')
# def set_app (host:str="0.0.0.0",context:str="",port:int=8084,debug=True):
-def set_app (host:Annotated[str,typer.Argument(help="bind host IP address")]="0.0.0.0",
- context:Annotated[str,typer.Argument(help="if behind a proxy server (no forward slash needed)")]="",
- port:Annotated[int,typer.Argument(help="port on which to run the application")]=8084,
+def set_app (
+ manifest:Annotated[str,typer.Argument(help="path to manifest or manifest folder")],
+ port:int=typer.Option(default=None,help="port on which to run the application"),
+ host:str=typer.Option(default='0.0.0.0',help="bind host IP address"),
+ context:str=typer.Option(default='',help="if behind a proxy server (no forward slash needed)"),
+
debug:Annotated[bool,typer.Argument(help="set debug mode on|off")]=True):
"""
Setup application access i.e port, debug, and/or context
@@ -73,27 +95,30 @@ def set_app (host:Annotated[str,typer.Argument(help="bind host IP address")]="0.
"""
global INVALID_FOLDER
- _config = config.get()
+ path = get_manifest(manifest)
+ _config = cms.engine.config.get(path)
if _config :
+ # _system = _config['system']['app']
_app = _config['system']['app']
- _app['host'] = host
- _app['port'] = port
- _app['debug'] = debug
+ _app['host'] = host if host != _app['host'] else _app['host']
+ _app['port'] = port if port else _app['port']
+ _app['debug'] = debug if _app['debug'] != debug else _app['debug']
_config['system']['context'] = context
_config['app'] = _app
- config.write(_config)
- _msg = f"""{PASSED} Successful update, good job !
+ cms.engine.config.write(_config,path)
+ _msg = f"""{PASSED} [bold] {_config['layout']['header']['title']}[/bold]: Successful update, good job !
"""
else:
_msg = INVALID_FOLDER
print (_msg)
@cli.command(name='cloud')
# def set_cloud(path:str): #url:str,uid:str,token:str):
-def set_cloud(path:Annotated[str,typer.Argument(help="path of the auth-file for the cloud")]): #url:str,uid:str,token:str):
+def set_cloud(manifest:Annotated[str,typer.Argument(help="path of the auth-file for the cloud")]): #url:str,uid:str,token:str):
"""
Setup qcms to generate a site from files on nextcloud
The path must refrence auth-file (data-transport)
"""
+ path = get_manifest(manifest)
if os.path.exists(path):
f = open (path)
_auth = json.loads(f.read())
@@ -101,11 +126,11 @@ def set_cloud(path:Annotated[str,typer.Argument(help="path of the auth-file for
url = _auth['url']
if os.path.exists('qcms-manifest.json') :
- _config = config.get()
+ _config = cms.engine.config.get()
_config['system']['source'] = path #{'id':'cloud','auth':{'url':url,'uid':uid,'token':token}}
- config.write(_config)
+ cms.engine.config.write(_config)
title = _config['layout']['header']['title']
- _msg = f"""{PASSED} Successfully update, good job!
+ _msg = f"""{PASSED} [bold]{_config['system']['layout']['header']['title']}[/bold] : successfully update, good job!
{url}
"""
else:
@@ -132,16 +157,16 @@ def secure(
f.write(str(uuid.uuid4()))
f.close()
#
- _config = config.get(manifest)
+ _config = cms.engine.config.get(manifest)
if 'source' not in _config['system']:
_config['system']['source'] = {'id':'disk'}
_config['system']['source']['key'] = keyfile
- config.write(_config,manifest)
- _msg = f"""{PASSED} A key was generated and written to {keyfile}
+ cms.engine.config.write(_config,manifest)
+ _msg = f"""{PASSED} [bold]{_config['system']['layout']['header']['title']}[/bold] : A key was generated and written to {keyfile}
use this key in header to enable reload of the site ...`
"""
else:
- _msg = f"""{FAILED} Could NOT generate a key, because it would seem you already have one
+ _msg = f"""{FAILED} [bold]{_config['system']['layout']['header']['title']}[/bold] : could [bold]NOT[/bold] generate a key, because it would seem you already have one
Please manually delete {keyfile}
@@ -173,8 +198,9 @@ def load(**_args):
return getattr(module,_name) if hasattr(module,_name) else None
+
@cli.command(name='plugins')
-def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file")],
+def plugin_manager (manifest:Annotated[str,typer.Argument(help="path to manifest file")],
show:bool=typer.Option(default=False,help="list plugins loaded"),
add: Annotated[Optional[bool],typer.Option("--register/--unregister",help="add/remove a plugin to manifest use with --pointer option")] = None,
pointer:str=typer.Option(default=None,help="pointer is structured as 'filename.function'")
@@ -183,7 +209,7 @@ def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file
Manage plugins list loaded plugins,
"""
manifest = get_manifest(manifest)
- _config = config.get(manifest)
+ _config = cms.engine.config.get(manifest)
if _config :
_root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins'])
else :
@@ -193,41 +219,52 @@ def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file
_msg = f"""{FAILED} no operation was specified, please use --help option"""
# if 'plugins' in _config :
_plugins = _config['plugins'] if 'plugins' in _config else {}
+
if show :
if not _plugins :
_msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
else:
- # _data = plugins.stats(_plugins)
- _data = cms.Plugin.stats(_plugins)
- print (_data)
- _msg = f"""{PASSED} found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}"""
-
+ _data = []
+ _plugConf = _config['plugins']
+
+ for _name in _plugConf :
+ _log = {"files":_name,"loaded":len(_plugConf[_name]),"logs":json.dumps(_plugConf[_name])}
+ _data.append(_log)
+ _data= pd.DataFrame(_data)
+
+ # # # _data = plugins.stats(_plugins)
+ # # _data = cms.Plugin.stats(_plugins)
+ print (to_Table(_data))
+ _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}"""
+
if add in [True,False] and pointer :
- file,fnName = pointer.split('.')
+
+ file,fnName = pointer.split('.')
# _fnpointer = plugins.load(_root,file+'.py',fnName)
- _fnpointer = cms.Plugin.load(_root,file+'.py',fnName)
- if _fnpointer and add:
+ # _fnpointer = cms.Plugin.load(_root,file+'.py',fnName)
+ _ploader = plugin_ix.Loader(file = os.sep.join([_root,file+'.py']))
+
+ if add and _ploader.has(fnName):
if file not in _plugins :
_plugins[file] = []
if fnName not in _plugins[file] :
_plugins[file].append(fnName)
- _msg = f"""{PASSED} registered {pointer}, use the --show option to list loaded plugins\n{manifest} """
+ _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: registered {pointer}, use the --show option to list loaded plugins"""
else:
- _msg = f"""{FAILED} could not register {pointer}, it already exists\n\t{manifest} """
+ _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: could not register {pointer}, it already exists"""
elif add is False and file in _plugins:
- _plugins[file] = [_name.strip() for _name in _plugins[file] if _name.strip() != fnName.strip() ]
-
- _msg = f"""{PASSED} unregistered {pointer}, use the --show option to list loaded plugins\n{manifest} """
+ _plugins[file] = [_name.strip() for _name in _plugins[file] if _name.strip() != fnName.strip() ]
+ _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: unregistered {pointer}, use the --show option to list loaded plugins """
- #
- # We need to write this down !!
+ # #
+ # # We need to write this down !!
if add in [True,False] :
_config['plugins'] = _plugins
- config.write(_config,manifest)
+ cms.engine.config.write(_config,manifest)
# else:
- # _msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
+ # _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: no plugins are loaded """
print()
print(_msg)
else:
@@ -266,7 +303,7 @@ def create(folder:Annotated[str,typer.Argument(help="path of the project folder"
#
# Setup Project on disk
#
- project.make(folder=folder,config=_config)
+ cms.engine.project.make(folder=folder,config=_config)
print (f"""{PASSED} created project at {folder} """)
else:
print ()
@@ -284,7 +321,7 @@ def reload (
Reload a site/portal given the manifest ...
"""
path = get_manifest(path)
- _config = config.get( path)
+ _config = cms.engine.config.get( path)
if 'source' in _config['system'] and 'key' in _config['system']['source'] :
_spath = _config['system']['source']['key']
# f = open(_config['system']['source']['key'])
@@ -301,7 +338,7 @@ def reload (
url = f"http://localhost:{_port}/reload"
resp = requests.post(url, headers={"key":key})
if resp.status_code == 200 :
- _msg = f"""{PASSED} successfully reloaded {url}"""
+ _msg = f"""{PASSED} [bold]{_config['system']['layout']['header']['title']}[/bold] : successfully reloaded {url}"""
else:
_msg = f"""{FAILED} failed to reload, status code {resp.status_code}\n{url}
"""
@@ -332,7 +369,7 @@ def handle_theme (
"""
manifest = get_manifest(manifest)
- _config = config.get(manifest)
+ _config = cms.engine.config.get(manifest)
_root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']])
if show :
@@ -356,8 +393,8 @@ def handle_theme (
# values.sort()
# _df['installed'] = values
else:
- _df = f"""{FAILED} No themes were found in registry,\ncurl {themes.URL}/api/themes/List (QCMS_HOST_URL should be set)"""
- print (_df)
+ _df = f"""{FAILED} [bold]{_config['system']['layout']['header']['title']}[/bold] : No themes were found in registry,\ncurl {themes.URL}/api/themes/List (QCMS_HOST_URL should be set)"""
+ print (to_Table(_df))
if name and not show:
# we need to install the theme, i.e download it and update the configuration
#
@@ -377,11 +414,11 @@ def handle_theme (
# Let us update the configuration file ...
#
_config['system']['theme'] = name
- config.write(_config,manifest)
- _msg = f"""{PASSED} successfully downloaded {name} \n{PASSED} updated manifest {manifest}
+ cms.engine.config.write(_config,manifest)
+ _msg = f"""{PASSED} [bold]{_config['system']['layout']['header']['title']}[/bold] : successfully downloaded {name} \n{PASSED} updated manifest {manifest}
"""
except Exception as e:
- _msg = f"""{FAILED} operation failed "{str(e)}"
+ _msg = f"""{FAILED} [[bold]{_config['system']['layout']['header']['title']}[/bold] : operation failed "{str(e)}"
"""
pass
diff --git a/cms/__init__.py b/cms/__init__.py
index c0f7b95..53c61c0 100644
--- a/cms/__init__.py
+++ b/cms/__init__.py
@@ -29,45 +29,45 @@ class Plugin :
setattr(wrapper,'method',self._method)
setattr(wrapper,'mimetype',self._mimetype)
return wrapper
- @staticmethod
- def load(_path,_filename,_name) :
- """
- This function will load external module form a given location and return a pointer to a function in a given module
- :path absolute path of the file (considered plugin) to be loaded
- :name name of the function to be applied
- """
- # _path = _args['path'] #os.sep.join([_args['root'],'plugin'])
+ # @staticmethod
+ # def load(_path,_filename,_name) :
+ # """
+ # This function will load external module form a given location and return a pointer to a function in a given module
+ # :path absolute path of the file (considered plugin) to be loaded
+ # :name name of the function to be applied
+ # """
+ # # _path = _args['path'] #os.sep.join([_args['root'],'plugin'])
- if os.path.isdir(_path):
- files = os.listdir(_path)
- if files :
- files = [name for name in files if name.endswith('.py') and name == _filename]
- if files:
- _path = os.sep.join([_path,files[0]])
- else:
- return None
- else:
- return None
- #-- We have a file ...
- # _name = _args['name']
- spec = importlib.util.spec_from_file_location(_filename, _path)
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module)
- #
- # we need to make sure we have the plugin decorator here to make sure
+ # if os.path.isdir(_path):
+ # files = os.listdir(_path)
+ # if files :
+ # files = [name for name in files if name.endswith('.py') and name == _filename]
+ # if files:
+ # _path = os.sep.join([_path,files[0]])
+ # else:
+ # return None
+ # else:
+ # return None
+ # #-- We have a file ...
+ # # _name = _args['name']
+ # spec = importlib.util.spec_from_file_location(_filename, _path)
+ # module = importlib.util.module_from_spec(spec)
+ # spec.loader.exec_module(module)
+ # #
+ # # we need to make sure we have the plugin decorator here to make sure
- return getattr(module,_name) if hasattr(module,_name) else None
+ # return getattr(module,_name) if hasattr(module,_name) else None
- @staticmethod
- def stats (_config) :
- """
- Returns the statistics of the plugins
- """
- _data = []
- for _name in _config :
- _log = {"files":_name,"loaded":len(_config[_name]),"logs":json.dumps(_config[_name])}
- _data.append(_log)
- return pd.DataFrame(_data)
+ # @staticmethod
+ # def stats (_config) :
+ # """
+ # Returns the statistics of the plugins
+ # """
+ # _data = []
+ # for _name in _config :
+ # _log = {"files":_name,"loaded":len(_config[_name]),"logs":json.dumps(_config[_name])}
+ # _data.append(_log)
+ # return pd.DataFrame(_data)
@staticmethod
def call(**_args):
@@ -123,3 +123,56 @@ class Plugin :
return _data,_code,{'Content-Type':_mimeType}
pass
+
+class delegate:
+ def __call__(self,**_args) :
+ _handler= _args['handler'] #-- module handler
+ _request= _args['request']
+ _uri = _args['uri'] #_request.path[1:]
+ #
+ # we need to update the _uri (if context/embeded apps)
+ #
+ _context = _handler.system()['context']
+ if _context :
+ _uri = f'{_context}/{_uri}'
+ _plugins = _handler.plugins()
+ _code = 200
+ if _uri in _plugins :
+ _config = _handler.config() #_args['config']
+
+
+ _pointer = _plugins[_uri]
+ if hasattr(_pointer,'mimetype') and _request.method in _pointer.method:
+ #
+ # we constraint the methods given their execution ...
+ _mimeType = _pointer.mimetype
+ _data = _pointer(request=_request,config=_config)
+ else:
+ _mimeType = 'application/octet-stream'
+ try:
+ _data,_mimeType = _pointer(request=_request,config=_config)
+
+ except Exception as e:
+
+ _data = _pointer(request=_request,config=_config)
+ if type(_data) == pd.DataFrame :
+ _data = _data.to_json(orient='records')
+ elif type(_data) in [dict,list] :
+ _data = json.dumps(_data)
+ pass
+ else:
+ _code = 404
+ #
+ # We should generate a 500 error in this case with a message ...
+ #
+ _mimeType = 'plain/html'
+ _data = f"""
+
+ """
+
+ return _data,_code,{'Content-Type':_mimeType}
diff --git a/cms/disk.py b/cms/disk.py
index 65e94d1..1b7efa6 100644
--- a/cms/disk.py
+++ b/cms/disk.py
@@ -8,6 +8,7 @@ import copy
import mistune
from mistune import markdown
import re
+import plugin_ix
def folders (_path,_config):
"""
@@ -143,29 +144,34 @@ def plugins (**_args):
key = f'{_context}/{key}'
return {key:read}
- _path = _args['path'] #os.sep.join([_args['root'],'plugin'])
- if os.path.isdir(_path):
- files = os.listdir(_path)
- if files :
- files = [name for name in files if name.endswith('.py')]
- if files:
- _uri = [_path,files[0]]
- if _context :
- _uri = [_context] + _uri
- _path = os.sep.join(_uri)
- else:
- return None
- else:
- #
- # LOG: not a file
- return None
- #-- We have a file ...
- _name = _args['name']
- spec = importlib.util.spec_from_file_location(_name, _path)
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module)
+ _path = _args['path'] #os.sep.join([_args['root'],'plugin'])
+ loader = plugin_ix.Loader(file=_path)
+ if _args['name'] in loader.names() :
+ _pointer = loader.get(_args['name'])
+ return _pointer
+ return None
+ # if os.path.isdir(_path):
+ # files = os.listdir(_path)
+ # if files :
+ # files = [name for name in files if name.endswith('.py')]
+ # if files:
+ # _uri = [_path,files[0]]
+ # if _context :
+ # _uri = [_context] + _uri
+ # _path = os.sep.join(_uri)
+ # else:
+ # return None
+ # else:
+ # #
+ # # LOG: not a file
+ # return None
+ # #-- We have a file ...
+ # _name = _args['name']
+ # spec = importlib.util.spec_from_file_location(_name, _path)
+ # module = importlib.util.module_from_spec(spec)
+ # spec.loader.exec_module(module)
- #
- # LOG This plugin ....
- return getattr(module,_name) if hasattr(module,_name) else None
+ # #
+ # # LOG This plugin ....
+ # return getattr(module,_name) if hasattr(module,_name) else None
\ No newline at end of file
diff --git a/cms/index.py b/cms/index.py
index 9dce0c1..de5870b 100644
--- a/cms/index.py
+++ b/cms/index.py
@@ -137,58 +137,10 @@ def _delegate_call(app,key,module,name):
_handler = _getHandler(app,key)
# print (_handler.config()/)
uri = f'api/{module}/{name}'
- return Plugin.call(uri=uri,handler=_handler,request=request)
- # return _delegate(_handler,module,name)
+ # return Plugin.call(uri=uri,handler=_handler,request=request)
+ _delegate = cms.delegate()
+ return _delegate(uri=uri,handler=_handler,request=request)
-def _delegate(_handler,module,name):
- global _route
- uri = '/'.join(['api',module,name])
- # _args = dict(request.args,**{})
- # _args['config'] = _handler.config()
- _plugins = _handler.plugins()
- _context = _handler.system()['context']
- if _context :
- uri = f'{_context}/{uri}'
- _mimeType = 'application/octet-stream'
- if uri not in _plugins :
- _data = {}
- _code = 404
- else:
- pointer = _plugins[uri]
- # if _args :
- # _data = pointer (**_args)
- # else:
- # _data = pointer()
- if hasattr(pointer,'mimetype') :
- _data = pointer(request=request,config=_handler.config())
- _mimeType = pointer.mimetype
- else:
- _data,_mimeType = pointer(request=request,config=_handler.config())
-
- _mimeType = 'application/octet-stream' if not _mimeType else _mimeType
- if type(_data) == pd.DataFrame :
- _data = _data.to_dict(orient='records')
- if type(_data) == list:
- _data = json.dumps(_data)
- _code = 200 if _data else 500
- return _data,_code,{'Content-Type':_mimeType}
-
-# @_app.route("/api//" , methods=['POST'],defaults={'app_id':'main','key':None})
-# @_app.route('//api//',methods=['POST'],defaults={'key':None})
-# @_app.route('///api//',methods=['POST'],defaults={'app_id':'main','key':None})
-
-# @_app.route("///api//", methods=['POST'])
-# @_app.route("/api//",defaults={'app_id':'main','key':None},methods=['POST'])
-# @_app.route("//api//",defaults={'key':None},methods=['POST'])
-
-# def _post (app_id,key,module,name):
-# # global _config
-# # global _route
-# # _handler = _route.get()
-# # app_id = '/'.join([app_id,key]) if key else app_id
-
-# _handler = _getHandler(app_id,key)
-# return _delegate(_handler,module,name)
@_app.route('/version')
def _version ():
@@ -206,7 +158,7 @@ def _reload(key) :
_systemKey = None
elif 'key' in _system['source'] and _system['source']['key']:
_systemKey = _system['source']['key']
- print ([key,_systemKey,_systemKey == key])
+ # print ([key,_systemKey,_systemKey == key])
if key and _systemKey and _systemKey == key :
_handler.reload()
return "",200
@@ -253,18 +205,7 @@ def _POST_CMSPage(app_id,key):
_id = _uri.split('/')[-1].split('.')[0]
else:
_id = request.headers['dom']
- # _args = {'layout':_config['layout']}
- # if 'plugins' in _config:
- # _args['routes'] = _config['plugins']
-
- # _system = _handler.system() #cms.components.get_system(_config)
- # # _html = _handler.html(_uri,_id,_args,_system) #cms.components.html(_uri,_id,_args,_system)
- # _html = _handler.html(_uri,_id)
- # # _system = cms.components.get_system(_config)
- # _args['system'] = _handler.system(skip=['source','app'])
- # e = Environment(loader=BaseLoader()).from_string(_html)
- # _html = e.render(**_args)
if 'read?uri=' in _uri or 'download?doc=' in _uri :
_uri = _uri.split('=')[1]
_args = _route.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))