0

In my case I need to edit a big bunch of CMakeLists.txt at once - but I think this problem can be generalized. The questions are:

  1. Which tool is better for the job?
  2. How would I achieve the desired output?

[Optional:]

  1. Is there a way to empty/erase the hold space in sed?
  2. Is there a way to prepend or append lines in the hold space?

Both target_include_directories calls may or may not have identical arguments.

Problem Statement:

Within the range of target_include_directories( and the first ) collect all the lines containing windows indent them (by 4 spaces) and put them before the ).

Now insert $<$<PLATFORM_ID:Windows>: with correct indentation before the block of lines containing windows (inside the range defined above) and append one line containing an indented > after the block of lines containing windows.

Also make sure the last line containing arguments of each block has no semicolon and all the others do.

Research done so far:

The following line collects the lines containing windows and puts them to the right place, but without indentation and without the decoration.

sed ':j;/^$/h;/target_include_directories(/,/)/{/windows/{H;d};/)/{H;x;D;G;bj}}' CMakeLists.txt

Sample data:

...
##############
# Unique Big Block
##############
if(some_condition)
    target_include_directories(foo Public
        arg0floor;
        arg1windowsred;
        arg2chairs;
        arg3bluewindows;
        arg4tables;
        ...
        argnwalls
    )
elseif(some_other_condition)
    target_include_directories(foo Public
        arg0yeast;
        arg1windowsbroken;
        arg2barley;
        arg3wavywindows;
        arg4water;
        ...
        argnsugar
    )
endif()
##############
# Other Unique Big Block
##############
...

Expected output:

...
##############
# Unique Big Block
##############
if(some_condition)
    target_include_directories(foo Public
        arg0floor;
        arg2chairs;
        arg4tables;
        ...
        argnwalls
        $<$<PLATFORM_ID:Windows>:
            arg1windowsred;
            arg3bluewindows;
            ...
            argkwindowsblack
        >
    )
elseif(some_other_condition)
    target_include_directories(foo Public
        arg0yeast;
        arg2barley;
        arg4water;
        ...
        argnsugar
        $<$<PLATFORM_ID:Windows>:
            arg1windowsbroken;
            arg3wavywindows;
            ...
            argkmilkywindows
        >
    )
endif()
##############
# Other Unique Big Block
##############
...
  • awk or perl are better suited than sed – Bodo Aug 9 at 10:01
  • Thank you, I'll search for a solution with awk then. Could you point out some names of the mechanisms in awk I might need for this task? – bigla Aug 9 at 10:09
  • About your optional question 3: Actually, you don't want to empty the hold space, but preload it on each block start: /target_include_directories(.*/{h;s// $<$<PLATFORM_ID:Windows>:/;x;}. Additionally, you seldom need to loop in sed, let sed loop for you: sed '/target_include_directories(.*/{h;s// $<$<PLATFORM_ID:Windows>:/;x;};//,/)/{/windows/{s/^/ /;H;d;};/)/{H;x;};}'. So you were already pretty close. But this comment is just meant for improving your sed understanding. I'd use Python for such a task. – Philippos Aug 9 at 12:07
  • Oh my, i just learned The empty regular expression ‘//’ repeats the last regular expression match @Philippos ... I will meditate over this one-line of beauty. I extended it to my needs a bit sed '/target_include_directories(.*/{h;s// $<$<PLATFORM_ID:Windows>:/;x;};//,/)/{/windows/{s/^/ /;H;d;};/)/{s/^\(\s*\))/\1\1>\n\1)/;H;x;};}'. Thank you so much! – bigla Aug 9 at 13:05
  • That's the idea here: To learn from each other. (-: And I just learned that the multiple spaces inside backticks in my comment were eaten by the editor. Both replacements were supposed to add the indention spaces. – Philippos Aug 9 at 13:10
0
  1. I agree with Bodo, AWK or Perl are better suited. I’d use AWK here, it’s sufficient for the job.

  2. Here are the steps involved. AWK is a record-based pattern-matching language; by default, a record is a line. So in this instance, I’d write a program which would

    1. look for target_include_directories, and note that we’re now in an include block;
    2. when in an include block, match lines containing “windows”, and store them in an array instead of outputting them; other lines are output as-is;
    3. when in an include block, look for the closing parenthesis; when found, output the stored lines, with the platform prefix.

    There’s some extra handling involved to deal with the end-of-line semi-colon; one way to deal with that would be to store the include lines without leading spaces and trailing semi-colons, and output them decorated as appropriate for the context.

    One implementation is as follows:

    #!/usr/bin/gawk -f
    
    # Note the start of a block, clear the memorised includes,
    # print the current line and skip to the next one
    /target_include_directories/ {
        in_include_block = 1
        delete includes
        print
        next
    }
    
    # In a block, if we match "windows", memorise the value without its semi-colon
    in_include_block && /windows/ {
        includes[length(includes)] = gensub(";", "", "g", $1)
    }
    
    # In an include block, when we reach the end, output the memorised includes
    in_include_block && /)/ {
        if (length(includes) > 0) {
            printf "        $<$<PLATFORM_ID:Windows>:"
            for (i = 0; i < length(includes); i++) {
                if (i > 0) {
                    printf ";"
                }
                printf "\n            %s", includes[i]
            }
            print "\n        >"
        }
        in_include_block = 0
    }
    
    # In an include block, if we don't match, print the line
    in_include_block && !/windows/
    
    # Outside include blocks, print
    !in_include_block
    
  • I must admit, that my experience with awk is extremely limited (except of a one-liner I pinned to my workplaces cardboard-wall !x[$0]++). So I assume with denoting that we are in an include block you mean something like (in_include_block=1) right? Then something like if (in_include_block == 1) {/windows/ ...} Well I am already stuck. I especially don't seem to understand how to store things to an array and then print them just before /)/ occurs... – bigla Aug 9 at 13:35
  • See the example implementation above. – Stephen Kitt Aug 9 at 14:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.