-
Notifications
You must be signed in to change notification settings - Fork 1
FormatStyle Design
FormatStyle (formerly FileFormat) controls how configuration blocks are formatted and merged in dot2net. It uses a clear two-phase approach:
- Format Phase: Individual config blocks are formatted with prefixes/suffixes
- Merge Phase: Multiple formatted blocks are combined together
This design ensures that formatting is applied exactly once, avoiding the double-formatting issues that existed in earlier versions.
type FormatStyle struct {
Name string
// Format Phase (applied to individual config blocks)
FormatLinePrefix string // Prefix added to each line
FormatLineSuffix string // Suffix added to each line
FormatLineSeparator string // Separator between lines
FormatBlockPrefix string // Prefix added to entire block
FormatBlockSuffix string // Suffix added to entire block
// Merge Phase (applied when combining multiple blocks)
MergeBlockSeparator string // Separator between blocks
MergeResultPrefix string // Prefix added to entire merged result
MergeResultSuffix string // Suffix added to entire merged result
// Legacy fields (v0.6.x compatibility, removed in v0.7.0)
LinePrefix string // Falls back to FormatLinePrefix
LineSuffix string // Falls back to FormatLineSuffix
LineSeparator string // Falls back to FormatLineSeparator
BlockPrefix string // Falls back to FormatBlockPrefix
BlockSuffix string // Falls back to FormatBlockSuffix
BlockSeparator string // Falls back to MergeBlockSeparator
}The Format Phase processes a single config block (template output) and applies line-level and block-level formatting:
Input: ["router ospf", "network 10.0.1.0/24 area 0"]
Step 1 - formatConfigLines():
├─ Split into lines: ["router ospf", "network 10.0.1.0/24 area 0"]
├─ Apply to each line: FormatLinePrefix + line + FormatLineSuffix
└─ Join with: FormatLineSeparator
Example (format_lineprefix=" ", format_linesuffix=";", format_lineseparator="\n"):
" router ospf;\n network 10.0.1.0/24 area 0;"
Step 2 - formatSingleConfigBlock():
├─ Take formatted lines from Step 1
├─ Wrap with: FormatBlockPrefix + content + FormatBlockSuffix
Example (format_blockprefix="BEGIN\n", format_blocksuffix="\nEND"):
"BEGIN\n router ospf;\n network 10.0.1.0/24 area 0;\nEND"
Output: Fully formatted single config block
The Merge Phase combines multiple already-formatted blocks:
Input: ["Block1", "Block2", "Block3"] (already formatted in Format Phase)
Step 1 - mergeConfigBlocks():
├─ Join blocks with: MergeBlockSeparator
Example (merge_blockseparator="\n---\n"):
"Block1\n---\nBlock2\n---\nBlock3"
Step 2 - Wrap result (optional):
├─ If MergeResultPrefix or MergeResultSuffix is set:
└─ Wrap with: MergeResultPrefix + merged + MergeResultSuffix
Example (merge_resultprefix="# Config Start\n", merge_resultsuffix="\n# Config End"):
"# Config Start\nBlock1\n---\nBlock2\n---\nBlock3\n# Config End"
Output: Fully merged and wrapped result
Format is applied ONCE - Config blocks are formatted during the Format Phase and stored. The Merge Phase only combines these pre-formatted blocks; it never re-applies Format Phase formatting.
This prevents double-formatting issues where the same formatting rules were incorrectly applied multiple times.
Always used when processing any ConfigTemplate:
- Interface templates
- Node templates
- Connection templates
- Network templates
Every template output goes through Format Phase formatting before being stored or used.
Only used when combining multiple config blocks:
-
Sorter Templates (
style: sort):- file: "frr.conf" style: sort sort_group: "frr_config" format: frr_format
Merges all blocks collected in
sort_groupinto a single file. -
blocks.before / blocks.after:
- file: "startup.sh" scope: node class: [router] blocks: before: ["interfaces_startup"] # Merged before node template after: ["cleanup_commands"] # Merged after node template format: shell_format
Child configs are formatted individually, then merged.
Used to generate vtysh command-line invocations:
format:
- name: frr_vtysh
format_lineseparator: "\" -c \""
format_blockprefix: "vtysh -c \"conf t\" -c \""
format_blocksuffix: "\""Template output:
["router ospf", "network 10.0.1.0/24 area 0"]
Result:
vtysh -c "conf t" -c "router ospf" -c "network 10.0.1.0/24 area 0"Used for exec command lists:
format:
- name: clab_cmd
format_lineprefix: " - "
merge_blockseparator: "\n"Template output (multiple blocks):
Block 1: ["/usr/lib/frr/frr start"]
Block 2: ["vtysh -c 'conf t' -c 'router ospf'"]
Result:
- /usr/lib/frr/frr start
- vtysh -c 'conf t' -c 'router ospf'Used for node command definitions:
format:
- name: tn_spec_cmd
format_lineprefix: " - cmd: "
merge_blockseparator: "\n"Template output:
["ip addr add 10.0.1.1/24 dev net0", "ip link set net0 up"]
Result:
- cmd: ip addr add 10.0.1.1/24 dev net0
- cmd: ip link set net0 upformat:
- name: complex_format
# Format Phase
format_lineprefix: " " # 4-space indent
format_linesuffix: ";" # Semicolon after each line
format_lineseparator: "\n" # Newline between lines
format_blockprefix: " config {\n" # Block starts with "config {"
format_blocksuffix: "\n }" # Block ends with "}"
# Merge Phase
merge_blockseparator: "\n\n" # Blank line between blocks
merge_resultprefix: "# Generated Configuration\n"
merge_resultsuffix: "\n# End Configuration"Processing example:
Input Block 1: ["option1 value1", "option2 value2"]
Input Block 2: ["option3 value3"]
Format Phase (Block 1):
config {
option1 value1;
option2 value2;
}
Format Phase (Block 2):
config {
option3 value3;
}
Merge Phase:
# Generated Configuration
config {
option1 value1;
option2 value2;
}
config {
option3 value3;
}
# End Configuration
formatConfigLines(lines []string, fs *FormatStyle) string
- Location:
pkg/model/format.go - Applies line-level formatting
- Returns a single string with all lines formatted and joined
formatSingleConfigBlock(content string, fs *FormatStyle) string
- Location:
pkg/model/format.go - Applies block-level formatting
- Wraps the content with block prefix/suffix
mergeConfigBlocks(blocks []string, fs *FormatStyle) string
- Location:
pkg/model/format.go - Joins multiple pre-formatted blocks
- Optionally wraps with result prefix/suffix
- Important: Does NOT re-apply Format Phase formatting
The SorterConfigBlocks structure stores formatted blocks with priority:
type ConfigBlock struct {
Block string // Already formatted in Format Phase
Priority int // For ordering within group
}
func (scb *SorterConfigBlocks) getConfigBlocks(ns types.NameSpacer, group string) []string {
// Sort by priority, then return pre-formatted blocks
// These blocks are then passed to mergeConfigBlocks()
}Legacy field names are supported through fallback mechanism:
func (fs *FormatStyle) getFormatLinePrefix() string {
if fs.FormatLinePrefix != "" {
return fs.FormatLinePrefix
}
return fs.LinePrefix // Legacy fallback
}All modules provide both new and legacy fields:
&types.FormatStyle{
Name: "frr_vtysh",
// New fields (v0.6.0+)
FormatLineSeparator: "\" -c \"",
// Legacy fields (v0.6.x compatibility)
LineSeparator: "\" -c \"",
}Legacy fields will be removed:
-
lineprefix,linesuffix,lineseparator -
blockprefix,blocksuffix,blockseparator
Users must migrate to format_* and merge_* prefixed fields.
dot2net build -c input.yaml input.dot --verboseLook for these messages:
-
"Format Phase - formatConfigLines(...)"- Line formatting applied -
"Format Phase - formatSingleConfigBlock(...)"- Block formatting applied -
"Merge Phase - mergeConfigBlocks(...)"- Multiple blocks merged
1. Double formatting: Pre-v0.6.0 issue, now fixed
- Symptom: Format prefix/suffix applied twice
- Solution: Upgrade to v0.6.0+
2. Missing block separator:
- Symptom: Blocks merged without separator
-
Solution: Set
merge_blockseparatorappropriately
3. Unexpected line breaks:
- Symptom: Extra newlines in output
-
Solution: Check
format_lineseparatorandmerge_blockseparator
- YAML Configuration - Complete YAML config reference
- Template System - Template syntax and features
- CHANGELOG.md - v0.6.0 release notes with migration guide