# Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # Powershell script to generate snippets from C++ source files. # Usage: generate_snippets.ps1 param( [Parameter(Mandatory = $true)] [string]$source_dir, [Parameter(Mandatory = $true)] [string]$output_dir, [Parameter(Mandatory = $false)][switch] [bool]$verify = $false ) function ParseSnippets { param ( [Parameter(Mandatory = $true)] [string[]]$input_files ) # for each file in $input_files, read the contents of the file into a string, then search the string for patterns delimited by @begin_snippet and @end_snippet. # for each pattern found, extract the snippet name and contents, and save the snippet in a map of snippet name to snippet contents. # then, for each snippet in the map, write the snippet contents to a file in $output_dir with the snippet name as the file name. #$snippet_pattern = '@begin_snippet:\s+(?\w+)\s+(?.*?)@end_snippet' $snippet_map = @{} $snippet_pattern = '@begin_snippet:\s+(?\w+)\s+\n(?.*?)\s+(//|`*/)\s+@end_snippet' foreach ($cpp_file in $input_files) { Write-Host "Scanning source: $cpp_file" $cpp_file_contents = Get-Content $cpp_file -Raw $snippet_matches = [regex]::Matches($cpp_file_contents, $snippet_pattern, 'Singleline') foreach ($snippet_match in $snippet_matches) { Write-Host "Found snippet: $($snippet_match.Groups['snippet_name'].Value)" $snippet_name = $snippet_match.Groups['snippet_name'].Value $snippet_contents = $snippet_match.Groups['snippet_contents'].Value if ($snippet_map[$snippet_name]) { Write-Host "ERROR: Duplicate snippet name: $snippet_name" exit 1 } else { $snippet_map[$snippet_name] = $snippet_contents } } } return (, $snippet_map) } function ProcessSnippetsInFile { param ( [Parameter(Mandatory = $true)] [hashtable]$snippet_map, [Parameter(Mandatory = $true)] [object]$output_file ) $output_file_contents = Get-Content $output_file -Raw $snippet_matches = [regex]::Matches($output_file_contents, '@insert_snippet:\s+(?\w+)', 'Singleline') # if there is no match, we don't need to do anything else. if ($snippet_matches.Count -eq 0) { return $true; } $original_file_contents = $output_file_contents foreach ($snippet_match in $snippet_matches) { $snippet_name = $snippet_match.Groups['snippet_name'].Value Write-Host "Replacing snippet $snippet_name in file $output_file." if (!$snippet_map[$snippet_name]) { Write-Host "ERROR: Unknown snippet name: $snippet_name in file $output_file" return $false } if ($output_file.Extension -eq '.md') { # Remove the existing snippet text, if any. $output_file_contents = [Regex]::Replace($output_file_contents, "\s+``````cpp.+?``````\s+", "`r`n`r`n", 'Singleline') # Insert the snippet text. $snippet_text = $snippet_map[$snippet_name] $output_file_contents = $output_file_contents -replace "\s+", "`r`n``````cpp`r`n$snippet_text`r`n```````r`n`r`n" } elseif ($output_file.Extension -eq '.hpp') { $output_file_contents = $output_file_contents -replace '@insert_snippet:\s+(?\w+)', '$snippet_map[$snippet_name]' } elseif ($output_file.Extension -eq '.cpp') { $output_file_contents = $output_file_contents -replace '@insert_snippet:\s+(?\w+)', '$snippet_map[$snippet_name]' } else { Write-Host "ERROR: Unknown file extension: $output_file" return $false } } # The Regex::Replace above inserts an extra newline at the end of the file. Remove it. $output_file_contents = $output_file_contents -replace "`r`n\s*\Z", "`r`n" $original_contents = $original_file_contents -replace "`r`n\s*\Z", "`r`n" if ($verify) { if ($output_file_contents -ne $original_contents) { Write-Host "ERROR: Snippet contents does not match for file: $output_file." return $false } } elseif (!$verify) { Write-Host "Writing file: $output_file" Set-Content -Path $output_file.FullName -Value $output_file_contents } return $true } $source_dir = Resolve-Path $source_dir $output_dir = Resolve-Path $output_dir $input_files = Get-ChildItem -Path $source_dir -Include *.cpp, *.hpp -Recurse # The snippet generator only processes markdown files currently. $output_files = Get-ChildItem -Path $output_dir -Include *.md -Recurse $snippet_map = @{} $snippet_map = ParseSnippets($input_files) # for each file in $output_files, read the contents of the file, searching for a string @insert_snippet: . Insert the corresponding snippet from the $snippet_map # into the file at that location and write it out. $failed = $false foreach ($output_file in $output_files) { $result = ProcessSnippetsInFile -snippet_map $snippet_map -output_file $output_file if (!$result) { $failed = $true } } if ($failed) { Write-Host "ERROR: Snippet generation failed." Write-Host "`r`nTo fix this error, run the following command locally:" Write-Host "`r`n`r`n`t powershell -ExecutionPolicy Bypass -File eng/scripts/Generate-Snippets.ps1 -source_dir $source_dir -output_dir $output_dir`r`n" Write-Host "`r`nThen, run the following command to verify the changes." Write-Host "`r`n`r`n`t powershell -ExecutionPolicy Bypass -File eng/scripts/Generate-Snippets.ps1 -source_dir $source_dir -output_dir $output_dir -verify`r`n" Write-Host "`r`nFinally, commit the changes and push to the remote branch." exit 1 }