Hugo YAML 2 TOML
This is a specific use case script but I’m archiving it for my notes. As I was learning about Hugo yaml, toml, and front matter - I made mistakes. I’ve decided to use toml for everything, which means I needed an easier way to convert yaml to toml. This script allows conversion both ways, but it’s not a true yaml to toml converter. Instead it converts specific front matter formatting which is yaml to toml (and vice versa).
Environments
Quick referenc,e to install environments on windows:
cd scriptswhatever
python -m venv yournewenv
cd yournewenv
scripts\activate
python -m pip install -r requirements.txt
Linux/mac is very similar:
cd scriptswhatever
python3 -m venv yournewenv
cd yournewenv
source bin\activate
python3 -m pip install -r requirements.txt
Requirements
requirements.txt
pillow==10.3.0
PyYAML==6.0.1
toml==0.10.2
ttkbootstrap==1.10.1
The Script
import tkinter as tk
from tkinter import ttk, messagebox
import toml
import yaml
from io import StringIO
import ttkbootstrap as tb
VERSION = 1
class ConverterApp:
def __init__(self, root):
self.root = root
self.root.title("TOML/YAML Converter")
# Set the ttkbootstrap theme
self.style = tb.Style("cosmo")
# Configure the main window to expand
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
self.root.rowconfigure(1, weight=0)
self.root.rowconfigure(2, weight=1)
# Input textarea
self.input_text = tk.Text(root, height=15)
self.input_text.grid(row=0, column=0, padx=10, pady=5, sticky="nsew")
# Conversion options
self.conversion_option = tk.StringVar(value="yaml_to_toml")
self.radio_frame = ttk.Frame(root)
self.radio_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
self.radio_frame.columnconfigure(0, weight=1)
self.radio_frame.columnconfigure(1, weight=1)
self.radio_frame.columnconfigure(2, weight=1)
self.yaml_to_toml_radio = ttk.Radiobutton(self.radio_frame, text="YAML to TOML", variable=self.conversion_option, value="yaml_to_toml")
self.yaml_to_toml_radio.grid(row=0, column=0, padx=5, sticky="e")
self.toml_to_yaml_radio = ttk.Radiobutton(self.radio_frame, text="TOML to YAML", variable=self.conversion_option, value="toml_to_yaml")
self.toml_to_yaml_radio.grid(row=1, column=0, padx=5, sticky="e")
self.convert_button = ttk.Button(self.radio_frame, text="Convert", command=self.convert)
self.convert_button.grid(row=2, column=0, padx=5, pady=5, sticky="e")
# Output textarea
self.output_text = tk.Text(root, height=15)
self.output_text.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
def extract_comments_and_data(self, text):
"""
Extract comments and data separately
"""
lines = text.split("\n")
extracted_lines = []
for line in lines:
if "#" in line:
data_part, comment_part = line.split("#", 1)
extracted_lines.append((data_part.strip(), comment_part.strip()))
else:
extracted_lines.append((line.strip(), None))
return extracted_lines
def reinsert_comments(self, converted_lines, comments):
"""
Reinsert comments into the converted lines
"""
result_lines = []
converted_lines_split = converted_lines.split("\n")
comment_index = 0
for line in converted_lines_split:
if "=" in line:
key = line.split("=")[0].strip()
elif ":" in line:
key = line.split(":")[0].strip()
else:
result_lines.append(line)
continue
while comment_index < len(comments):
data_part, comment_part = comments[comment_index]
if data_part and data_part.startswith(key):
if comment_part:
result_lines.append(f"{line} # {comment_part}")
else:
result_lines.append(line)
comment_index += 1
break
elif not data_part:
result_lines.append(f"# {comment_part}")
comment_index += 1
else:
comment_index += 1
return "\n".join(result_lines)
def convert(self):
input_data = self.input_text.get("1.0", tk.END).strip()
if not input_data:
messagebox.showwarning("Warning", "Input field is empty!")
return
# Determine the format and strip the delimiters
input_format = None
if input_data.startswith("---"):
input_format = "yaml"
input_data = input_data.replace("---", "").strip()
elif input_data.startswith("+++"):
input_format = "toml"
input_data = input_data.replace("+++", "").strip()
# Extract comments and data
comments = self.extract_comments_and_data(input_data)
try:
if self.conversion_option.get() == "yaml_to_toml":
if input_format == "yaml":
data = yaml.safe_load("\n".join([line[0] for line in comments if line[0]]))
output_data = toml.dumps(data)
output_data = f"+++\n{output_data}+++"
else:
raise ValueError("Input does not appear to be valid YAML")
else:
if input_format == "toml":
data = toml.loads("\n".join([line[0] for line in comments if line[0]]))
output_data = yaml.dump(data, sort_keys=False, default_flow_style=False)
output_data = f"---\n{output_data}---"
else:
raise ValueError("Input does not appear to be valid TOML")
# Add comments back to the converted data
output_data = self.reinsert_comments(output_data, comments)
self.output_text.delete("1.0", tk.END)
self.output_text.insert(tk.END, output_data)
except (toml.TomlDecodeError, yaml.YAMLError, ValueError) as e:
messagebox.showerror("Error", f"Invalid input format:\n{e}")
except Exception as e:
messagebox.showerror("Error", f"An error occurred:\n{e}")
def main():
root = tb.Window(themename="cosmo")
app = ConverterApp(root)
root.mainloop()
if __name__ == "__main__":
main()