import csv
import json
import os
import sys
[docs]
def read_template_parts_list(template_parts_list_file):
"""
Reads a file containing a list of template parts to be concatenated.
Parameters:
template_parts_list_file (str): Path to the .csv or .txt file
containing the list.
Returns:
list of str: A list of file paths as strings.
Raises:
ValueError: If the file extension is not supported.
FileNotFoundError: If the file does not exist.
"""
template_parts_list = []
if template_parts_list_file.endswith('.csv'):
template_parts_list = read_template_parts_list_csv(
template_parts_list_file)
else:
raise ValueError(
f"Unsupported file extension: {template_parts_list_file}")
for template_part in template_parts_list:
if not os.path.exists(template_part):
raise FileNotFoundError(f"File not found: {template_part}")
if not template_part.endswith('.json'):
raise ValueError(f"File must be in json format: {template_part}")
return template_parts_list
[docs]
def read_template_parts_list_csv(template_parts_list_file):
"""
Reads file paths from a CSV file. Assumes the first column contains
the paths.
Parameters:
template_parts_list_file (str): Path to the CSV file.
Returns:
list of str: A list of strings from the first column of each row.
Raises:
FileNotFoundError: If the file does not exist.
"""
with open(template_parts_list_file, 'r') as f:
reader = csv.reader(f)
return [row[0] for row in reader if row]
[docs]
def read_template_part(template_parts_file):
"""
Reads a JSON template parts file and extracts the 'groupfield' and
its details.
Parameters:
template_parts_file (str): Path to a JSON file representing a
template parts.
Returns:
content (dict): The parsed JSON content.
Raises:
FileNotFoundError: If the JSON file does not exist.
json.JSONDecodeError: If the JSON structure is invalid.
"""
try:
with open(template_parts_file, 'r') as f:
template_parts_content = json.load(f)
check_template_parts_structure(template_parts_content)
return template_parts_content
except KeyError as e:
raise ValueError(f"Invalid template parts file: Missing key {e}")
[docs]
def check_template_parts_structure(template_parts_file_content):
"""
Validates the structure of a template parts content against Elab
format rules.
Parameters:
template_parts_file_content (dict): The JSON-parsed content of
the template parts.
Returns:
bool: True if the structure is valid.
Raises:
ValueError: If the structure does not meet the required format.
Warning: If some fields do not have corresponding 'groupfield'
mappings.
"""
# Check if the required keys are present
required_keys = ['elabftw', 'extra_fields']
for key in required_keys:
if key not in template_parts_file_content:
raise ValueError(f"Invalid template parts file: Missing key {key}")
# Check if 'elabftw' contains 'extra_fields_groups'
if 'extra_fields_groups' not in template_parts_file_content['elabftw']:
raise ValueError("Invalid template parts file: Missing "
"'extra_fields_groups' in 'elabftw'")
if not isinstance(template_parts_file_content['elabftw']
['extra_fields_groups'], list):
raise ValueError("Invalid template parts file: 'extra_fields_groups' "
"should be a list")
if not template_parts_file_content['elabftw']['extra_fields_groups']:
raise ValueError("Invalid template parts file: 'extra_fields_groups' "
"list is empty")
if not all(isinstance(group, dict) for group in
template_parts_file_content['elabftw']['extra_fields_groups']):
raise ValueError("Invalid template parts file: 'extra_fields_groups' "
"should contain"
"dictionaries")
[docs]
def set_content_id(new_id, template_parts_file_content):
"""
Updates the ID of the content in a template parts.
Parameters:
new_id (int): The new ID to assign.
template_parts_file_content (dict): The original template parts
content.
Returns:
dict: The updated template parts content.
"""
# Update the ID in the 'extra_fields_groups'
for group in template_parts_file_content['elabftw']['extra_fields_groups']:
group['id'] = new_id
# Update the ID in the 'extra_fields'
for field in template_parts_file_content['extra_fields'].values():
field['group_id'] = new_id
return template_parts_file_content
[docs]
def concatenate_templates(existing_template_parts_content,
new_template_part_content):
"""
Concatenates the contents of two template parts.
Parameters:
existing_template_parts_content (dict): Content of the existing
template.
new_template_parts_content (dict): Content of the new template
to append.
Returns:
dict: The merged template content.
"""
# Merge the 'extra_fields_groups'
existing_template_parts_content['elabftw']['extra_fields_groups'].extend(
new_template_part_content['elabftw']['extra_fields_groups']
)
# Merge the 'extra_fields'
existing_template_parts_content['extra_fields'].update(
new_template_part_content['extra_fields']
)
return existing_template_parts_content
[docs]
def save_template(full_template_content, template_file_path):
"""
Saves the template content to a JSON file.
Parameters:
full_template_content (dict): The content to save.
template_file_path (str): Path to the output JSON file.
Returns:
None
"""
# Save the content to the file
with open(template_file_path, 'w') as f:
json.dump(full_template_content, f, indent=4)
[docs]
def generate_template(template_parts_list_file, template_file_path):
"""
Generates a full template by merging all template parts listed in a
file.
Parameters:
template_parts_list_file (str): Path to a .csv or .txt file
listing JSON parts files.
template_file_path (str): Path to the output JSON file to save
the template.
Returns:
None
"""
# Read the list of template parts
template_parts_list = read_template_parts_list(template_parts_list_file)
# Initialize the full template content
full_template_content = None
# Iterate through each template part file
for new_id, template_part_file in enumerate(template_parts_list):
# Read the content of the current template part
new_template_part_content = read_template_part(
template_part_file)
new_template_part_content = set_content_id(
new_id + 1,
new_template_part_content)
if full_template_content is None:
full_template_content = new_template_part_content
else:
full_template_content = concatenate_templates(
full_template_content, new_template_part_content)
# Update the ID of the content
# Save the final merged template to a file
save_template(full_template_content, template_file_path)
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python generate_templates.py "
"<template_parts_list_file.csv>"
"<output_template.json>")
else:
generate_template(sys.argv[1], sys.argv[2])