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
##############
...
6
  • awk or perl are better suited than sed
    – Bodo
    Aug 9, 2019 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, 2019 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, 2019 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, 2019 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, 2019 at 13:10

1 Answer 1

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
    
2
  • 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, 2019 at 13:35
  • See the example implementation above. Aug 9, 2019 at 14:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .