0

How can I expand one line to multiple lines, pairing two attributes?

Sample input (my input has nearly a thousand lines):

Mission Rewards
----------------------------------
Mission: [id: 1      , name: "1. Spirefall"                     , location: "Ashrock"           , rewards: (Dreadnaught + 10 gold + item[1051]), reward_levels: (1)]
Mission: [id: 5      , name: "5. Bombardment"                   , location: "Ashrock"           , rewards: (Peacekeeper + 14 gold + item[1041], 100 gold), reward_levels: (1, 1)]
Mission: [id: 1081   , name: "Melek's Machinations"             , location: "Swamp Lands"       , rewards: (Dominion Shard x25 + 350 gold + 40 sp, Dominion Shard x25 + 40 sp), reward_levels: (1, 10)]
Mission: [id: 1281   , name: "Minerva's Purge - Legion"         , location: "Swamp Lands"       , rewards: (500 gold, Benediction, Salvage Repository, Grail Defender, Grail Defender), reward_levels: (1, 1, 10, 25, 50)]

Wanted output:

Mission Rewards
----------------------------------
1. Spirefall (Ashrock)
    1: Dreadnaught + 10 gold + item[1051]

5. Bombardment (Ashrock)
    1: Peacekeeper + 14 gold + item[1041]
    1: 100 gold

Melek's Machinations (Swamp Lands)
    1: Dominion Shard x25 + 350 gold + 40 sp
    10: Dominion Shard x25 + 40 sp

Minerva's Purge - Legion (Swamp Lands)
    1: 500 gold
    1: Benediction
    10: Salvage Repository
    25: Grail Defender
    50: Grail Defender

The format is:

<header>

<mission name> (mission location)
    <reward level>: <reward>
    <reward level>: <reward>
    <reward level>: <reward>

<mission name> (mission location)
    <reward level>: <reward>
    <reward level>: <reward>
    <reward level>: <reward>

Starting with the input file, I can:

  1. go to line 2 (:2)
  2. start recording a macro (qa)
  3. go to beginning of line (^)
  4. delete till first double quote (dt")
  5. delete first double quote (x)
  6. find next double quote (f")
  7. delete till next double quote (dt")
  8. delete double quote under cursor (x)
  9. add formatting (i ()
  10. return to command mode (<ESC>)
  11. find next double quote (f")
  12. replace double quote with formatting (r))
  13. return to command mode (<ESC>)
  14. end recording macro (q)
  15. run macro at each line (type j^@a until all lines processed)

at this point, I have:

Mission Rewards
----------------------------------
1. Spirefall (Ashrock)           , rewards: (Dreadnaught + 10 gold + item[1051]), reward_levels: (1)]
5. Bombardment (Ashrock)           , rewards: (Peacekeeper + 14 gold + item[1041], 100 gold), reward_levels: (1, 1)]
Melek's Machinations (Swamp Lands)       , rewards: (Dominion Shard x25 + 350 gold + 40 sp, Dominion Shard x25 + 40 sp), reward_levels: (1, 10)]
Minerva's Purge - Legion (Swamp Lands)       , rewards: (500 gold, Benediction, Salvage Repository, Grail Defender, Grail Defender), reward_levels: (1, 1, 10, 25, 50)]

How can I pair the reward_levels with rewards as in the wanted output?

4
  • 1
    The same way you did the rest, with macros.
    – romainl
    Commented Jan 27 at 19:49
  • @romainl can you be more specific? which commands should I look into? I am fairly new to Vim and can't think of any ways with the commands I currently know Commented Jan 27 at 19:54
  • This question screams :substitute (as in Vivian's answer). When I was getting started with Vim, I used macros a lot. I hardly use them anymore.
    – Friedrich
    Commented Jan 28 at 16:49
  • Do you still have something open in your question? How can we help you further? Otherwise maybe could you accept one of the answers using the v button next to the arrow voting buttons. It allow the question to rest :-) Commented Feb 6 at 5:41

2 Answers 2

3

Here's my strategy with macros.

Our first macro will make the header:

qh
0/id: /e+<enter>
dwO<esc>pgelC. <esc>/name: "/e<enter>
di"kp/location: "/e<enter>
di"ka ()<esc>Po<esc>
q

I've broken it up a bit for readability, but the gist is to grab the relevant parts by searching and pasting them in the line above.

Now we'll build the rewards table by getting the levels:

ql
/reward_levels: (/e<enter>dibO<esc>p
A, <esc>:s/, /: \r/g<enter>
''ms
>}
q

This also marks the first rewards level line with the s mark.

Lastly, we put the rewards in place. This part can get finicky with 3-digit numbers (and a recursive macro could solve it), but it should get things close enough:

qr
/rewards: (/e<enter>dib
O<esc>p
A, <esc>:s/, /\r/g<enter>''
<C-v>}k$d
's$p
/Mission: /<enter>dd
q

This uses block-selection to cut and paste the rewards.

There's a little cleanup left of trailing spaces (and you might want to 'svip:s/:\ze[^ ]/ <enter> to help with the 2-digit numbers).

Lastly, you can make a macro that does all 3 and a little cleanup:

:let @q = '@h@l@r'

Voilà. All of these are "normal" editing commands that you would use regularly, just wrapped up in a long sequence of macros.

On your file, I get

1. 1. Spirefall (Ashrock)
    1: Dreadnaught + 10 gold + item[1051]



5. 5. Bombardment (Ashrock)
    1: Peacekeeper + 14 gold + item[1041]
    1: 100 gold




1081. Melek's Machinations (Swamp Lands)
    1: Dominion Shard x25 + 350 gold + 40 sp
    10:Dominion Shard x25 + 40 sp            




1281. Minerva's Purge - Legion (Swamp Lands)
    1: 500 gold
    1: Benediction
    10:Salvage Repository 
    25:Grail Defender     
    50:Grail Defender     








Here's a copy-paste-able way to get the macros:

  1. Copy the chunk of code below and paste it into a new vim buffer (:new)
  2. Source that buffer (change to it and :source)
  3. Go back to your edits and do something like gg/Mission: /<enter>@q. Then you can type @q until your file is processed.
let @h = "0/id: /e+\<enter>dwO\<esc>pgelC. \<esc>/name: \"/e\<enter>di\"kp/location: \"/e\<enter>di\"kA ()\<esc>Po\<esc>"
let @l = "/reward_levels: (/e\<enter>dibO\<esc>pA, \<esc>:s/, /: \\r/g\<enter>''ms>}"
let @r = "/rewards: (/e\<enter>dibO\<esc>pA, \<esc>:s/, /\\r/g\<enter>''\<C-v>}k$d's$p/Mission: /\<enter>dd"
let @q = '@h@l@r'
2

I would do with substitution:

:%s/^\(.*rewards: ([^)]\{-}\)\(, \)\?\([^,)]\+\))\(, reward_levels: ([^)]\{-}\)\(, \)\?\([^,)]\+\))]/\1)\4)]\r    \6: \3

Or using magic:

:%s/^\v(.*rewards: \([^)]{-})(, )?([^,)]+)\)(, reward_levels: \([^)]{-})(, )?([^,)]+)\)]/\1)\4)]\r    \6: \3

Then I would repeat :%s a number of time to consume all the attributes. Or better you could run 10@: to repeat 10 times the substitution (the last : command).

And finally:

:%s/\s*, rewards: (), reward_levels: ()]//

To get rid of the empty parts that we don't want.

Remark: For performance reason you could do the cleaning after each or some of the initial substitution to reduce the number of matching.

Explanation

The idea is that at each substitution the last item in each list is identified and used to be inserted in the list of sub items.

There are identified with the expression: \v\([^)]{-}(, )?([^,)]+)\)

  • \([^)]{-} match the first part ({-} is non greedy to not match all)
  • (, )? match optionally the ,
  • ([^,)]+) match the term
  • \) match the closing )

Final Advice

Substitution is a very powerful tool in Vim and it can be difficult to master. The following two have helped me a lot:

  • \v the magic mode that simplify the complex pattern reducing the need of escaping special keys
  • the trace plugin that visualize the substitution while typing it. It help you to identify when you mistype something or why a substitution fail to achieve what you want.
1
  • 1
    While this does work, I certainly wouldn't have been able to come up with this myself. I will wait some time before accepting in case there are better answers Commented Jan 27 at 23:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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