diff --git a/README.parsers.md b/README.parsers.md index eef8f7223f7e5747695d5825f1898094c537d9d2..9c9939fb6df12f7f65cd8db625c6b37729ab4f42 100644 --- a/README.parsers.md +++ b/README.parsers.md @@ -82,8 +82,8 @@ pip install nomad-lab Clone the parser project and install it in development mode: ``` -git clone $parserGitUrl$ nomad-parser-$codeName$ -pip install -e nomad-parser-$codeName$ +git clone $parserGitUrl$ $gitPath$ +pip install -e $gitPath$ ``` Running the parser now, will use the parser's Python code from the clone project. diff --git a/nomad/cli/dev.py b/nomad/cli/dev.py index 152ed99660019a4d7e70290cd1794209711aeb15..d83564c1726aa8253499037f5174ba6654d337d9 100644 --- a/nomad/cli/dev.py +++ b/nomad/cli/dev.py @@ -156,16 +156,14 @@ def parser_metadata(): import yaml import os import os.path + from glob import glob parsers_metadata = {} - parsers_path = 'dependencies/parsers' - for parser_dir in os.listdir(parsers_path): - parser_path = os.path.join(parsers_path, parser_dir) - parser_metadata_file = os.path.join(parser_path, 'metadata.yaml') - if os.path.exists(parser_metadata_file): - with open(parser_metadata_file) as f: - parser_metadata = yaml.load(f, Loader=yaml.FullLoader) - parsers_metadata[parser_dir] = parser_metadata + parsers_path = './dependencies/parsers' + for parser_metadata_file in sorted(glob(f'{parsers_path}/**/metadata.yaml', recursive=True)): + with open(parser_metadata_file) as f: + parser_metadata = yaml.load(f, Loader=yaml.FullLoader) + parsers_metadata[os.path.basename(os.path.dirname(parser_metadata_file))] = parser_metadata parsers_metadata = { key: parsers_metadata[key] @@ -216,66 +214,89 @@ def update_parser_readmes(parser): generic_fn = './README.parsers.md' parser_path = './dependencies/parsers/' - for num, ddir in enumerate(sorted(glob(parser_path + '*/')), start=1): - if parser is not None and parser != ddir.split(os.sep)[-2]: - print(f'Skip {ddir}') - continue - - _, parser_dirname = os.path.split(ddir) - print('{} Working on {}' .format(num, parser_dirname)) + # Open general template + with open(generic_fn, 'r') as generic: # read only + generic_contents = generic.read() - # Open general template - with open(generic_fn, 'r') as generic: # read only - body = generic.read() + # Replace the comment at the top of the gereral template + generic_contents = re.sub( + rf'\*\*\*Note:\*\* This is a general README file for NOMAD parsers, ' + rf'consult the README of specific parser projects for more detailed ' + rf'information!\*\n\n', '', generic_contents) - # Open local YAML metadata file - local_mdata = ddir + 'metadata.yaml' - with open(local_mdata, 'r') as mdata_f: - mdata = yaml.load(mdata_f, Loader=yaml.FullLoader) - if mdata.get('codeName', '').strip() == '': - mdata['codeName'] = os.path.basename(os.path.dirname(ddir)) - if 'preamble' not in mdata: - mdata['preamble'] = '' + def open_metadata(path): + # read local yaml metadata file + with open(path, 'r') as metadata_f: + try: + metadata = yaml.load(metadata_f, Loader=yaml.FullLoader) + except Exception as e: + print(f'Error reading metadata.yaml: {e}') + metadata = None + return metadata + + def replace(metadata, contents, path): + # replace placelder in contents with metadata values + for key in metadata.keys(): + replace = metadata.get(key) + if replace is None: + continue + print(f'\tReplacing {key} with {replace}') + contents = re.sub(rf'\${key}\$', replace, contents) + + # save file + with open(path, 'w') as f: + f.write(contents.strip()) + f.write('\n') + + for local_readme in sorted(glob(f'{parser_path}/*/{local_fn}')): + parser_dir = os.path.dirname(local_readme) + project_name = os.path.basename(parser_dir) + print(f'Working on {parser_dir}') + + contents = generic_contents + # if metadata file under the parser directory exists, it is a single parser project + single = os.path.isfile(os.path.join(parser_dir, 'metadata.yaml')) + + if single: + metadata = open_metadata(os.path.join(parser_dir, 'metadata.yaml')) + # git path is given by nomad-parser-codename + metadata['gitPath'] = f'nomad-parser-{project_name}' + parser_header, parser_specs = '', '' + else: + # replace header for the single parser with that for a group of parsers + parser_header_re = r'(\nThis is a NOMAD parser[\s\S]+?Archive format\.\n)' + parser_header = re.search(parser_header_re, contents).group(1) + group_header = 'This is a collection of the NOMAD parsers for the following $codeName$ codes:\n\n$parserList$' + contents = re.sub(parser_header_re, group_header, contents) + # remove individual parser specs + parser_specs_re = r'(For \$codeLabel\$ please provide[\s\S]+?\$tableOfFiles\$)\n\n' + parser_specs = re.search(parser_specs_re, contents).group(1) + contents = re.sub(parser_specs_re, '', contents) + metadata = dict( + gitPath=f'{project_name}-parsers', + parserGitUrl=f'https://github.com/nomad-coe/{project_name}-parsers.git', + parserSpecific='') + if metadata.get('codeName', '').strip() == '': + metadata['codeName'] = project_name + if 'preamble' not in metadata: + metadata['preamble'] = '' + + # if this is a group of parser, find all individdual parsers and write the + # parser specs + parser_list = '' + for index, local_metadata in enumerate(sorted(glob(f'{parser_dir}/*/*/metadata.yaml'))): + metadata_parser = open_metadata(local_metadata) + # contents is simply the parser header and specs + contents_parser = f'{parser_header}\n{parser_specs}' + replace(metadata_parser, contents_parser, os.path.join(os.path.dirname(local_metadata), local_fn)) + # add the codename to the list of parsers for the group header + codelabel = metadata_parser.get('codeLabel', os.path.basename(os.path.dirname(local_metadata))) + codeurl = metadata_parser.get('codeUrl', '') + parser_list = rf'{parser_list}{index + 1}. [{codelabel}]({codeurl})\n' + metadata['parserList'] = parser_list.strip() # Find & Replace Parser`s metadata on its README file - local_readme = ddir + local_fn - with open(local_readme, 'w') as local: - for key in mdata.keys(): - ignore = ['codeLabelStyle', 'parserDirName'] - if key in ignore: - continue - - find = r'\$' + key + r'\$' - replace = mdata[key] - print(f'\tReplacing {key} with {replace}') - - if key == 'parserSpecific': - if mdata[key] != '': - replace = r'## Parser Specific\n' + replace - - body = re.sub(find, replace, body) - - # Extra: to replace the codeName (there's no YAML key for this) - key = 'codeName' - print('\tReplacing', key) - find = r'\$' + key + r'\$' - replace = parser_dirname - body = re.sub(find, replace, body) - - # Extra: to replace the comment at the top of the gereral template - find = ( - r'\*\*\*Note:\*\* This is a general README file for NOMAD parsers, ' - r'consult the README of specific parser projects for more detailed ' - r'information!\*\n\n') - replace = '' - print('\tReplacing the top comment') - # print('\t', find, ' -> ', replace) - body = re.sub(find, replace, body) - - # save file - local.seek(0) # go to the top - local.write(body.strip()) - local.truncate() + replace(metadata, contents, local_readme) @dev.command(help='Adds a few pieces of data to NOMAD.') diff --git a/tests/test_cli.py b/tests/test_cli.py index 3f47afef29d715e91f3504d36932179ee091728c..708ed990114eb9164458579bd04605dfc8a266f5 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -354,3 +354,16 @@ class TestClient: assert 'Atoms / Molecules' in result.output assert 'DOS' in result.output assert 'Band structures' in result.output + + +@pytest.mark.usefixtures('reset_config') +class TestDev: + + def test_parser_metadata(self): + result = invoke_cli( + cli, ['dev', 'parser-metadata'], catch_exceptions=True) + + assert result.exit_code == 0, result.output + assert 'yambo' in result.output + assert 'lammps' in result.output + assert 'elastic' in result.output