|
| 1 | +# |
| 2 | +# Copyright (C) 2026 Autodesk, Inc. All Rights Reserved. |
| 3 | +# |
| 4 | +# SPDX-License-Identifier: Apache-2.0 |
| 5 | +# |
| 6 | + |
| 7 | +# |
| 8 | +# CMake script-mode file for applying sed-style substitutions. Replaces bash+sed and Python-based approaches with pure CMake. |
| 9 | +# |
| 10 | +# Usage: cmake -DINPUT_FILE=<path> -DOUTPUT_FILE=<path> -DSED_FILE=<path> -P apply_sed_filter.cmake |
| 11 | +# |
| 12 | +# Supports: - Literal s/PATTERN/REPLACEMENT/ substitutions - PCRE negative lookbehind patterns (?<!PREFIX)MATCH via three-pass workaround - Any single-character |
| 13 | +# delimiter after 's' |
| 14 | +# |
| 15 | + |
| 16 | +IF(NOT INPUT_FILE) |
| 17 | + MESSAGE(FATAL_ERROR "INPUT_FILE not specified") |
| 18 | +ENDIF() |
| 19 | +IF(NOT OUTPUT_FILE) |
| 20 | + MESSAGE(FATAL_ERROR "OUTPUT_FILE not specified") |
| 21 | +ENDIF() |
| 22 | +IF(NOT SED_FILE) |
| 23 | + MESSAGE(FATAL_ERROR "SED_FILE not specified") |
| 24 | +ENDIF() |
| 25 | + |
| 26 | +FILE(READ "${INPUT_FILE}" _content) |
| 27 | +FILE(READ "${SED_FILE}" _sed_content) |
| 28 | + |
| 29 | +# Protect semicolons before splitting into CMake list (CMake uses ; as list separator) |
| 30 | +STRING(REPLACE ";" "@@SEMICOLON@@" _sed_content "${_sed_content}") |
| 31 | +STRING(REPLACE "\n" ";" _sed_lines "${_sed_content}") |
| 32 | + |
| 33 | +SET(_placeholder_counter |
| 34 | + 0 |
| 35 | +) |
| 36 | + |
| 37 | +FOREACH( |
| 38 | + _line IN |
| 39 | + LISTS _sed_lines |
| 40 | +) |
| 41 | + # Restore semicolons within each line |
| 42 | + STRING(REPLACE "@@SEMICOLON@@" ";" _line "${_line}") |
| 43 | + STRING(STRIP "${_line}" _line) |
| 44 | + |
| 45 | + IF("${_line}" STREQUAL "" |
| 46 | + OR "${_line}" MATCHES "^#" |
| 47 | + ) |
| 48 | + CONTINUE() |
| 49 | + ENDIF() |
| 50 | + |
| 51 | + # Must start with 's' |
| 52 | + STRING(SUBSTRING "${_line}" 0 1 _prefix) |
| 53 | + IF(NOT "${_prefix}" STREQUAL "s") |
| 54 | + CONTINUE() |
| 55 | + ENDIF() |
| 56 | + |
| 57 | + # Detect delimiter (char after 's') |
| 58 | + STRING(SUBSTRING "${_line}" 1 1 _delim) |
| 59 | + STRING(SUBSTRING "${_line}" 2 -1 _rest) |
| 60 | + |
| 61 | + # Find delimiter separating pattern from replacement |
| 62 | + STRING(FIND "${_rest}" "${_delim}" _split_pos) |
| 63 | + IF(_split_pos EQUAL -1) |
| 64 | + CONTINUE() |
| 65 | + ENDIF() |
| 66 | + STRING(SUBSTRING "${_rest}" 0 ${_split_pos} _pattern) |
| 67 | + MATH(EXPR _repl_start "${_split_pos} + 1") |
| 68 | + STRING(SUBSTRING "${_rest}" ${_repl_start} -1 _repl_rest) |
| 69 | + |
| 70 | + # Find trailing delimiter |
| 71 | + STRING(FIND "${_repl_rest}" "${_delim}" _end_pos) |
| 72 | + IF(_end_pos GREATER -1) |
| 73 | + STRING(SUBSTRING "${_repl_rest}" 0 ${_end_pos} _replacement) |
| 74 | + ELSE() |
| 75 | + SET(_replacement |
| 76 | + "${_repl_rest}" |
| 77 | + ) |
| 78 | + ENDIF() |
| 79 | + |
| 80 | + # Check for PCRE negative lookbehind: (?<!PREFIX)MATCH |
| 81 | + STRING(FIND "${_pattern}" "(?<!" _lb_pos) |
| 82 | + IF(NOT _lb_pos EQUAL -1) |
| 83 | + # Extract the lookbehind prefix |
| 84 | + MATH(EXPR _prefix_start "${_lb_pos} + 4") |
| 85 | + STRING(SUBSTRING "${_pattern}" ${_prefix_start} -1 _after_lb) |
| 86 | + STRING(FIND "${_after_lb}" ")" _lb_end) |
| 87 | + STRING(SUBSTRING "${_after_lb}" 0 ${_lb_end} _lb_prefix) |
| 88 | + |
| 89 | + # Extract the bare match (after the closing paren) |
| 90 | + MATH(EXPR _bare_start "${_lb_end} + 1") |
| 91 | + STRING(SUBSTRING "${_after_lb}" ${_bare_start} -1 _bare_pattern) |
| 92 | + |
| 93 | + # Unescape BRE special chars for literal matching |
| 94 | + STRING(REPLACE "\\." "." _bare_pattern "${_bare_pattern}") |
| 95 | + STRING(REPLACE "\\*" "*" _bare_pattern "${_bare_pattern}") |
| 96 | + STRING(REPLACE "\\/" "/" _bare_pattern "${_bare_pattern}") |
| 97 | + |
| 98 | + # Unescape replacement too |
| 99 | + STRING(REPLACE "\\." "." _replacement "${_replacement}") |
| 100 | + STRING(REPLACE "\\*" "*" _replacement "${_replacement}") |
| 101 | + STRING(REPLACE "\\/" "/" _replacement "${_replacement}") |
| 102 | + |
| 103 | + # The already-qualified form is prefix + bare_pattern |
| 104 | + SET(_qualified |
| 105 | + "${_lb_prefix}${_bare_pattern}" |
| 106 | + ) |
| 107 | + |
| 108 | + # Three-pass workaround: |
| 109 | + SET(_placeholder |
| 110 | + "@@RV_SED_PLACEHOLDER_${_placeholder_counter}@@" |
| 111 | + ) |
| 112 | + MATH(EXPR _placeholder_counter "${_placeholder_counter} + 1") |
| 113 | + |
| 114 | + # Pass 1: Protect already-qualified occurrences |
| 115 | + STRING(REPLACE "${_qualified}" "${_placeholder}" _content "${_content}") |
| 116 | + # Pass 2: Qualify all remaining bare occurrences |
| 117 | + STRING(REPLACE "${_bare_pattern}" "${_replacement}" _content "${_content}") |
| 118 | + # Pass 3: Restore protected occurrences |
| 119 | + STRING(REPLACE "${_placeholder}" "${_qualified}" _content "${_content}") |
| 120 | + ELSE() |
| 121 | + # Literal replacement — unescape BRE special chars |
| 122 | + STRING(REPLACE "\\." "." _pattern "${_pattern}") |
| 123 | + STRING(REPLACE "\\*" "*" _pattern "${_pattern}") |
| 124 | + STRING(REPLACE "\\/" "/" _pattern "${_pattern}") |
| 125 | + |
| 126 | + STRING(REPLACE "${_pattern}" "${_replacement}" _content "${_content}") |
| 127 | + ENDIF() |
| 128 | +ENDFOREACH() |
| 129 | + |
| 130 | +FILE( |
| 131 | + WRITE "${OUTPUT_FILE}" |
| 132 | + "${_content}" |
| 133 | +) |
0 commit comments