How to use the script
- Save the script: Save the code above as
generate_menu.pyin the root directory of your Hugo project. - Ensure content structure: Verify that your
content/directory is structured logically.
For sections, use_index.mdfiles. - Create a theme partial: Create a file at
layouts/partials/menu.htmlin your theme. This partial will iterate through the menu data generated by the script.
layouts/partials/menu.html
{{ if .Site.Menus.main }}
<ul>
{{ range .Site.Menus.main }}
{{ if .HasChildren }}
<li class="has-children">
<a href="{{ .PageRef }}">{{ .Name }}</a>
<ul>
{{ range .Children }}
<li><a href="{{ .PageRef }}">{{ .Name }}</a></li>
{{ end }}
</ul>
</li>
{{ else }}
<li><a href="{{ .PageRef }}">{{ .Name }}</a></li>
{{ end }}
{{ end }}
</ul>
{{ end }}
Reference the partial: Include the partial in your theme’s header or layout file:
layouts/partials/header.html
<nav>
{{ partial "menu.html" . }}
</nav>
Run the script: From your terminal, run the Python script.
python generate_menu.py
import os
import yaml
def generate_menu(content_path, menu_config):
"""
Recursively builds a menu structure based on the content directory.
"""
menu_items = []
# Get a sorted list of directories and files
items = sorted(os.listdir(content_path))
# Process directories first
for item in items:
item_path = os.path.join(content_path, item)
if os.path.isdir(item_path):
# A directory is a section; it may have an _index.md
section_index = os.path.join(item_path, '_index.md')
if os.path.exists(section_index):
# The section itself can be a menu item
menu_item = {
'identifier': item,
'name': item.replace('-', ' ').title(),
'pageRef': f'/{item}',
'weight': 100, # A default weight for sections
'children': generate_menu(item_path, menu_config) # Recurse for nested sections
}
menu_items.append(menu_item)
# Process single pages in the current directory
for item in items:
item_path = os.path.join(content_path, item)
if os.path.isfile(item_path) and item.endswith('.md') and not item.startswith('_index'):
# A regular markdown file is a menu page
page_name = os.path.splitext(item)[0]
menu_item = {
'identifier': page_name,
'name': page_name.replace('-', ' ').title(),
'pageRef': f'/{os.path.basename(content_path)}/{page_name}',
'weight': 200 # A default weight for pages
}
menu_items.append(menu_item)
return menu_items
def build_menu_config(hugo_root_dir, menu_name='main'):
"""
Builds the complete YAML menu configuration.
"""
content_path = os.path.join(hugo_root_dir, 'content')
if not os.path.exists(content_path):
print("Error: 'content' directory not found.")
return
menu_data = generate_menu(content_path, {})
# Add a Home menu item at the top
home_menu = {
'identifier': 'home',
'name': 'Home',
'pageRef': '/',
'weight': 1
}
menu_data.insert(0, home_menu)
# Format for Hugo's config structure
final_config = {menu_name: menu_data}
# Write to a menu.yaml file in config/_default/
config_dir = os.path.join(hugo_root_dir, 'config', '_default')
os.makedirs(config_dir, exist_ok=True)
output_file = os.path.join(config_dir, 'menu.yaml')
with open(output_file, 'w') as f:
yaml.dump(final_config, f, sort_keys=False, allow_unicode=True)
print(f"Successfully generated menu config at: {output_file}")
if __name__ == '__main__':
# Set the root of your Hugo project
hugo_root = os.path.dirname(os.path.abspath(__file__))
build_menu_config(hugo_root)
