diff --git a/bin/qcms b/bin/qcms index b7e06f0..72b6001 100755 --- a/bin/qcms +++ b/bin/qcms @@ -47,6 +47,7 @@ INVALID_FOLDER = """ # # handling cli interface cli = typer.Typer() +cli_theme = typer.Typer() def to_Table(df: pd.DataFrame): """Displays a Pandas DataFrame as a rich table.""" @@ -359,10 +360,77 @@ def bootup ( # manifest = os.sep.join([manifest,'qcms-manifest.json']) manifest = get_manifest(manifest) index.start(manifest,port) -@cli.command(name='theme') +@cli_theme .command(name='list') +def theme_list ( + manifest:Annotated[str,typer.Argument(help="path of the manifest file")], + # manifest:str= typer.Option(default=None,help="Optional path to a site, otherwise remote themes will be listed"), + url:str=typer.Option(default="https://dev.the-phi.com/git/qcms/themes.git", help="git/github site where the themes live") + # remote:bool = typer.Option("--remote/--local",help="print list of themes available (remotely)"), + ) : + """ + This function will list themes available remotely in contrast to those installed for a given qcms site + """ + # if not manifest : + _available = themes.List(url) + # _available = [100] + # else: + manifest = get_manifest(manifest) + _layout = cms.engine.config.get(manifest) ['layout'] + # if manifest.endswith('.json') : + manifest = os.sep.join(manifest.split(os.sep)[:-1]) + + path = os.sep.join([manifest, _layout['root']])#,'_assets/themes']) + _installed = themes.Installed(path) + _df = pd.DataFrame() + if _available : + _intCount = len(list(set(_installed) - set(_available))) + + _installed = [PASSED if _name in _installed else FAILED for _name in _available ] + _df = pd.DataFrame({'available':_available,'installed':_installed}) + + print ( to_Table(_df)) + if _intCount : + print (f"{PASSED} found {_intCount } custom theme{'' if _intCount == 1 else 's'} in [bold]{_layout['header']['title']}[/bold]") + else: + print (f"{FAILED} unable to list themes, insure the {url} and qcms-site is valid") + return _df +@cli_theme .command(name='set') +def theme_set ( + manifest:Annotated[str,typer.Argument(help="path of the manifest file")], + name:str = typer.Option(default='default',help='name of the theme to apply'), + url:str=typer.Option(default="https://dev.the-phi.com/git/qcms/themes.git", help="git/github site where the themes live") + ) : + manifest = get_manifest(manifest) + _config = cms.engine.config.get(manifest) + _layout = _config['layout'] + path = os.sep.join([os.sep.join(manifest.split(os.sep)[:-1]), _layout['root']])#,'_assets/themes']) + task = [] + # _msg = "" + try: + + # _df = theme_list(manifest) + _installed = themes.Installed(path) + + if name not in _installed : + themes.Get(name,path) + _config['system']['theme'] = name + cms.engine.config.write(_config,manifest) + task.append("installed") + + else: + task.append( "updated") + themes.UpdateTheme (path,name,url) + _msg = f"{PASSED} successfully {'/'.join(task)} [bold] {name} [/bold] to [bold] {_layout['header']['title']}[/bold]" + + except Exception as e: + print (e) + _msg = f"{FAILED} unable to set theme [bold] {name} [/bold] to {_layout['header']['title']}" + pass + print (_msg) + pass def handle_theme ( manifest:Annotated[str,typer.Argument(help="path of the manifest file")], - show:bool = typer.Option(default=False,help="print list of themes available"), + show:bool = typer.Option(default=False,help="print list of themes available (remotely)"), name:str = typer.Option(default='default',help='name of the theme to apply') ) : """ @@ -375,6 +443,7 @@ def handle_theme ( if show : _available = themes.List() + _df = pd.DataFrame({"available":_available}) # if _df.shape[0] > 0 : if _available : @@ -427,4 +496,5 @@ def handle_theme ( global SYS_ARGS if __name__ == '__main__': + cli.add_typer(cli_theme,name="themes",help="manage themes associated with a site") cli() diff --git a/cms/engine/themes/__init__.py b/cms/engine/themes/__init__.py index 8af40ee..ac78e67 100644 --- a/cms/engine/themes/__init__.py +++ b/cms/engine/themes/__init__.py @@ -9,9 +9,10 @@ default themes will be stored in layout.root._assets.themes, The expected files If the following are not available then we should load the default one. This would avoid crashes but would come at the expense of a lack of consistent visual layout (alas) """ +import git import requests import os -URL = os.environ['QCMS_HOST_URL'] if 'QCMS_HOST_URL' in os.environ else 'https://dev.the-phi.com/qcms' +URL = os.environ['QCMS_HOST_URL'] if 'QCMS_HOST_URL' in os.environ else 'https://dev.the-phi.com/git/qcms/themes.git' def current (_system) : return _system['theme'] def List (_url = URL) : @@ -19,30 +20,51 @@ def List (_url = URL) : calling qcms to list the available URL """ try: - _url = '/'.join([_url,'api','themes','List']) - return requests.get(_url).json() + # _url = '/'.join([_url,'api','themes','List']) + # return requests.get(_url).json() + + remote_refs = git.cmd.Git().ls_remote(_url, heads=True) + + return [ref.split("\t")[1].replace("refs/heads/", "") for ref in remote_refs.splitlines()] + except Exception as e: pass return [] -def Get(theme,_url= URL) : +def Get(theme,path,_url= URL) : """ This function retrieves a particular theme from a remote source The name should be in the list provided by the above function """ try: - _url = '/'.join([_url,'api','themes','Get']) +f'?theme={theme}' + # _url = '/'.join([_url,'api','themes','Get']) +f'?theme={theme}' try: - return requests.get(_url).json() + # return requests.get(_url).json() + path = path if '_assets/themes' in path else os.sep.join([path,'_assets','themes']) + if not os.path.exists(path) : + os.makedirs(path) + + if not os.path.exists ( os.sep.join([path,theme]) ) : + _loc = os.sep.join([path,theme]) + repo = git.Repo.clone_from(_url, _loc,branch=theme,depth=1) except Exception as e: + print(e) pass return None except Exception as e: pass return {} -def installed (path): +def Installed (path): return os.listdir(os.sep.join([path,'_assets','themes'])) - +def UpdateTheme(path,name,url) : + """ + :path root folder of the qcms app/site + :name name of the theme bing updated + :url url of the git repository + """ + _themerepo = os.sep.join([path,'_assets','themes',name]) + repo = git.Repo(_themerepo) + repo.remotes.origin.pull() def Set(theme,_system) : _system['theme'] = theme return _system diff --git a/meta/__init__.py b/meta/__init__.py index e009248..800fec4 100644 --- a/meta/__init__.py +++ b/meta/__init__.py @@ -1,5 +1,5 @@ __author__ = "Steve L. Nyemba" -__version__= "2.2.12" +__version__= "2.2.14" __email__ = "steve@the-phi.com" __license__=""" Copyright 2010 - 2024, Steve L. Nyemba, Vanderbilt University Medical Center diff --git a/setup.py b/setup.py index 74df723..634c93a 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ args = { "packages": find_packages(include=['meta','cms', 'cms.*','.'])} args["keywords"]=['cms','www','https','flask','data-transport'] -args["install_requires"] = ['flask','gitpython','termcolor','flask-session','mistune','typer','data-transport@git+https://github.com/lnyemba/data-transport.git'] +args["install_requires"] = ['flask','gitpython','termcolor','gitpython','flask-session','mistune','typer','data-transport@git+https://github.com/lnyemba/data-transport.git'] args['classifiers'] = ['Topic :: utilities', 'License :: MIT'] args['include_package_data'] = True # args['data_files'] = {