2

How can I add wrappers to a file based on a pattern?

For instance I have the following:

  ...
  find(css_policy_form_stage3).click
  with_ajax_wait
    expect(css_coverage_type_everquote).to be_visible
  end
  with_ajax_wait
    expect(css_coverage_type_everquote).to be_visible
  end
end
it "Stage 3" do
  select(coverage_type, from: css_coverage_type_everquote)
  find(css_has_auto_insurance).click
  ...

And I want to 'wrap' those "with_ajax_wait" blocks with it "waits" do ... end around them.

i.e. I want to get:

  ...
  find(css_policy_form_stage3).click
  it "waits" do
    with_ajax_wait
      expect(css_coverage_type_everquote).to be_visible
    end
  end
  it "waits" do
    with_ajax_wait
      expect(css_coverage_type_everquote).to be_visible
    end
  do
end
it "Stage 3" do
  select(coverage_type, from: css_coverage_type_everquote)
  find(css_has_auto_insurance).click
  ...

Notes and Assumptions:

  • block to indent code is always 3 lines long (with... expect... and end). It would be nice to allow for more than i inner code line but not required for the simplest case.
  • the block itself should have an extra 2 space indent.
  • there are other end's that are not related (example shown just before "Stage 3"
  • it would be nice to be able to specify an inner pattern also, e.g. only these blocks that have expect starting the code line being indented. I think awk is probably the tool due to the ability to read consecutive lines but I am struggling to know how to write this.

I'm imagining this is a generally useful q&a as adding wrapper within files is not uncommon.

Somewhat similar to my previous question: How to use awk to indent a source file based on simple rules?
However in this case I am adding the wrapper plus the indent.

1

Here's one way with sed:

sed -E '/with_ajax_wait/,/end/{       # if line is in this range
H                                     # append to hold space
/end/!d                               # if it doesn't match end, delete it
//{                                   # if it matches
s/.*//                                # empty the pattern space
x                                     # exchange pattern space w. hold space
s/^(\n)( *)/\2it "waits" do\1\2/      # add first line + initial spacing
s/\n/&  /g                            # re-indent all other lines
G                                     # append hold space to pattern space
s/^(( *).*)/\1\2do/                   # add the closing 'do' + initial spacing
}
}
' infile

so with an input like:

with_ajax_wait
  expect(css_coverage_type_everquote).to be_visible
end
  find(css_policy_form_stage3).click
  with_ajax_wait
    expect(css_coverage_type_everquote).to be_visible
  end
  something here
    with_ajax_wait
      expect(css_coverage_type_everquote).to be_visible
        got some more stuff here to do
          process it
        done
    end
  end

the output is:

it "waits" do
  with_ajax_wait
    expect(css_coverage_type_everquote).to be_visible
  end
do
  find(css_policy_form_stage3).click
  it "waits" do
    with_ajax_wait
      expect(css_coverage_type_everquote).to be_visible
    end
  do
  something here
    it "waits" do
      with_ajax_wait
        expect(css_coverage_type_everquote).to be_visible
          got some more stuff here to do
            process it
          done
      end
    do
  end

It should work with blocks of more than three lines provided your with_ajax_wait blocks always ends with end.
Replace the closing do with end if needed as your example is confusing... (you used end for the first block and do for the second) e.g. this time using BRE and [[:blank:]] instead of (space):

sed '/with_ajax_wait/,/end/{
/with_ajax_wait/{
G
s/\([[:blank:]]*\)\(with_ajax_wait\)\(\n\)/\1it "waits" do\3  \1\2/
p
d
}
//!{
/end/!{
s/^/  /
}
/end/{
G
s/\([[:blank:]]*\)\(end\)\(\n\)/  \1\2\3\1end/
}
}
}
' infile

This one is processing each line in that range separately, the first and the last ones in the range are re-indented and wrappers are added, the rest of the lines are just re-indented.

1

This worked for me, with awk:

awk '/with_ajax_wait/,/end/{          # match the lines between the blocks
  i=substr($0,0,match($0, "[^ ]")-1); # i contains the indented spaces
  if($0~/with_ajax_wait/){            # if it is the starting block
    $0=i"it \"waits\" do\n  "$0       # ... add the starting block before the line
  }else if($0~/end/){                 # if it is the ending block
    $0="  "$0"\n"i"end"               # ... add the end block after the line
  }else{                              # else
    $0="  "$0                         # just indent the line with 2 spaces
  }
}1' file

The explanation is in the comments. The 1 at the end of the whole statement is just a true condition, so that awk prints all lines.

  • Looks good except I am getting extra chrs - ` wit "waits" do with_ajax_wait do select(auto_model_1, from: css_vehicle1_auto_model) end eend expect(css_vehicle1_auto_submodel option_auto_submodel).to be_present wit "waits" do with_ajax_wait do select(auto_submodel, from: css_vehicle1_auto_submodel) end eend` – Michael Durrant Sep 4 '15 at 16:17
  • i.e. wit instead of it and eend instead of end – Michael Durrant Sep 4 '15 at 16:17
  • @MichaelDurrant can you try it again, I think it was the i variable that contained to much in you case in line 2. – chaos Sep 4 '15 at 16:31
1

Try the following SED:

sed '/with_ajax_wait/{/end/!N;N;s/^/  it \"waits\" do\n/;s/$/\nend/;s/\n/\n  /g}'

This works for the example you gave.

Mac OS version

(Tested on OS X 10.8.5) Mac OS doesn't like certain newlines or chaining commands together with semicolons. Use the following instead:

sed -E -e '/with_ajax_wait/{' -e '/end/!N' -e 'N' -e 's/^/  it \"waits\" do\
/' -e 's/$/\
end/' -e 's/\n/\
  /g' -e '}'

Those are literal newlines which you can type at the terminal or copy in.

How it works

The key is the N command. We want to fill SED's memory (the pattern space) with a group of the form

  with_ajax_wait
    // commands
  end

The program traverses the file a line at a time, until it reaches a line matching with_ajax_wait; it then runs the N command, which appends more lines to the pattern space, until it reaches a line matching end. The rest of the program is a sequence of substitutions that carry out the wrapping: we append the it "waits" do line to the start of the block, the do line to the end and then we indent everything.

Limitations

  • This will not work if the word end occurs anywhere inside the with_ajax_wait ... end block. Making that work will require more serious parsing of the code itself. If we can guarantee that the closing end will always be indented by two spaces, then we could make this SED a bit better by replacing {/end/!N with {/\n end/!N or something.
  • The it "waits" do and do commands are always indented by two spaces, even if the text inside is indented by more or less. This is probably easier to fix, at the cost of a more complicated command.
  • Looks good but I get an error. I tried command line and I get $ echo 'ggg' | sed '/with_ajax_wait/{/end/!N;N;s/^/ it \"waits\" do\n/;s/$/\ndo/;s/\n/\n /g}' sed: 1: "/with_ajax_wait/{/end/! ...": bad flag in substitute command: '}' – Michael Durrant Sep 4 '15 at 16:20
  • I'm on osx btw. – Michael Durrant Sep 4 '15 at 16:20
  • @MichaelDurrant I've just added a version that worked for me on Mac OS X. – John Gowers Sep 4 '15 at 22:05

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.