"""
Configuration Module for Sabbath School Lessons
This module handles loading and validating configuration from a YAML file.
"""
import os
import yaml
from datetime import datetime
[docs]
class Config:
"""Handles configuration loading and validation for Sabbath School lessons."""
def __init__(self, config_path):
"""
Initialize with a path to a config file
Args:
config_path (str): Path to YAML config file
"""
self.config_path = config_path
self.config = self.load_config()
[docs]
def load_config(self):
"""
Load configuration from YAML file
Returns:
dict: Validated configuration dictionary
Raises:
Exception: If config loading or validation fails
"""
try:
with open(self.config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# Handle optional fields with defaults
if 'front_cover_svg' not in config:
config['front_cover_svg'] = None
if 'back_cover_svg' not in config:
config['back_cover_svg'] = None
# Handle reproduction config or set defaults
if 'reproduce' not in config:
config['reproduce'] = {}
reproduce_config = config['reproduce']
# Set reproduction defaults if not specified
if not reproduce_config.get('year'):
reproduce_config['year'] = 1905 # Default historical year
if not reproduce_config.get('quarter'):
reproduce_config['quarter'] = "q2" # Default historical quarter
if not reproduce_config.get('start_lesson'):
reproduce_config['start_lesson'] = 1
if not reproduce_config.get('stop_lesson'):
reproduce_config['stop_lesson'] = None
# Set default quarter start date if not provided
if not reproduce_config.get('quarter_start_date'):
# Calculate first day of target quarter
year = config['year']
quarter = config['quarter']
quarter_month = ((int(quarter[1]) - 1) * 3) + 1 # q1->1, q2->4, q3->7, q4->10
reproduce_config['quarter_start_date'] = f"{year}-{quarter_month:02d}-01"
return self.validate_config(config)
except Exception as e:
raise Exception(f"Error loading configuration: {str(e)}")
[docs]
def validate_config(self, config):
"""
Validates the configuration data
Args:
config (dict): Configuration dictionary
Returns:
dict: Validated configuration dictionary
Raises:
ValueError: If validation fails
"""
required_fields = ['year', 'quarter', 'language']
# Check required fields
for field in required_fields:
if field not in config:
raise ValueError(f"Missing required field in configuration: {field}")
# Validate year
try:
year = int(config['year'])
if year < 1880 or year > datetime.now().year + 1: # Allow current year + 1
raise ValueError(f"Year {year} is out of supported range")
except ValueError:
raise ValueError(f"Invalid year format: {config['year']}")
# Validate quarter
if config['quarter'] not in ['q1', 'q2', 'q3', 'q4']:
raise ValueError(f"Quarter must be one of: q1, q2, q3, q4, not {config['quarter']}")
# Validate language (simple check for now)
if not isinstance(config['language'], str) or len(config['language']) < 2:
raise ValueError(f"Invalid language code: {config['language']}")
# Check if language is supported
from .utils.language_utils import LanguageConfig
if config['language'] not in LanguageConfig.SUPPORTED_LANGUAGES:
print(f"Warning: Language '{config['language']}' is not officially supported. Using English as fallback.")
# Check language config path if specified
if 'language_config_path' in config:
language_path = config['language_config_path']
if not os.path.exists(language_path):
print(f"Warning: Language configuration file '{language_path}' not found.")
print(f"Generating a template language file for '{config['language']}'...")
# Try to generate the language template
try:
from .bin.generate_config import generate_language_template
lang_dir = os.path.dirname(language_path)
generate_language_template(config['language'], lang_dir)
except Exception as e:
print(f"Error generating language template: {e}")
print("Using default language settings.")
# Validate reproduction settings if present
if 'reproduce' in config:
reproduce = config['reproduce']
# Validate reproduction year
if 'year' in reproduce:
try:
r_year = int(reproduce['year'])
if r_year < 1880 or r_year > datetime.now().year:
raise ValueError(f"Reproduction year {r_year} is out of supported range")
except ValueError:
raise ValueError(f"Invalid reproduction year format: {reproduce['year']}")
# Validate reproduction quarter
if 'quarter' in reproduce and reproduce['quarter'] not in ['q1', 'q2', 'q3', 'q4']:
raise ValueError(f"Reproduction quarter must be one of: q1, q2, q3, q4, not {reproduce['quarter']}")
# Validate start_lesson if present
if 'start_lesson' in reproduce:
try:
start_lesson = int(reproduce['start_lesson'])
if start_lesson < 1:
raise ValueError(f"Start lesson must be at least 1, not {start_lesson}")
except ValueError:
raise ValueError(f"Invalid start lesson format: {reproduce['start_lesson']}")
# Validate stop_lesson if present and not None
if 'stop_lesson' in reproduce and reproduce['stop_lesson'] is not None:
try:
stop_lesson = int(reproduce['stop_lesson'])
if stop_lesson < 1:
raise ValueError(f"Stop lesson must be at least 1, not {stop_lesson}")
# Check if stop_lesson is greater than or equal to start_lesson
if 'start_lesson' in reproduce and stop_lesson < reproduce['start_lesson']:
raise ValueError(f"Stop lesson ({stop_lesson}) must be greater than or equal to start lesson ({reproduce['start_lesson']})")
except ValueError:
raise ValueError(f"Invalid stop lesson format: {reproduce['stop_lesson']}")
# Validate quarter_start_date format if present
if 'quarter_start_date' in reproduce:
try:
# Check if the date string is in the correct format
datetime.strptime(reproduce['quarter_start_date'], '%Y-%m-%d')
except ValueError:
raise ValueError(f"Invalid quarter start date format: {reproduce['quarter_start_date']}. Use YYYY-MM-DD format.")
# Check if output directory exists, create if not
output_dir = os.path.dirname(config['output_file'])
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
return config
[docs]
def get_github_paths(self):
"""
Generate GitHub repository paths based on configuration
Returns:
dict: Dictionary with GitHub paths
"""
# Use reproduction settings if they exist, otherwise use main settings
if 'reproduce' in self.config and self.config['reproduce'].get('year'):
year = int(self.config['reproduce']['year'])
quarter = self.config['reproduce']['quarter']
else:
year = int(self.config['year'])
quarter = self.config['quarter']
decade = f"{year // 10 * 10}s"
lang = self.config['language']
base_url = f"https://raw.githubusercontent.com/SabbathSchool/lessons/refs/heads/master/{decade}/{year}/{quarter}/{lang}"
contents_url = f"{base_url}/contents.json"
front_matter_url = f"{base_url}/front-matter.md"
back_matter_url = f"{base_url}/back-matter.md"
return {
'base_url': base_url,
'contents_url': contents_url,
'front_matter_url': front_matter_url,
'back_matter_url': back_matter_url
}
def __getitem__(self, key):
"""Allow dictionary-like access to config values"""
return self.config[key]
[docs]
def get(self, key, default=None):
"""Get a config value with a default"""
return self.config.get(key, default)