Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read n lines above current line in Ruby?

Tags:

ruby

I've got a data file that looks like this:

Things
├── Foo
│  ├── 1. Item One
│  ├── 2. Item Two
│  ├── 3. Item Three
│  ├── 4. Item Four
│  ├── 5. Item Five
│  └── 6. Item Six
├── Bar
│  ├── 1. Item Seven
│  ├── 2. Item Eight
│  ├── 3. Item Nine

What I'm trying to do is find a certain string, the number associated with it, and also the subheading that is a part of ('Foo' or 'Bar')

It's pretty easy to grab the item and the number:

str = "Item One"
data.each_line do |line|
    if line =~ /#{str}/
        /(?<num>\d).\s(?<item>.*)/ =~ line
    end
end

But I'm not sure how to get the subheading. What I was thinking is that once I found the line, I could count up from that point using the number. Is there a readlines or a seek command or some such that could do this?

Appreciate the help!

like image 851
craigeley Avatar asked Dec 20 '22 01:12

craigeley


2 Answers

I came up with below, this seems to work:

data = <<-EOF
Things
├── Foo
│  ├── 1. Item One
│  ├── 2. Item Two
│  ├── 3. Item Three
│  ├── 4. Item Four
│  ├── 5. Item Five
│  └── 6. Item Six
├── Bar
│  ├── 1. Item Seven
│  ├── 2. Item Eight
│  ├── 3. Item Nine
EOF

str = "Item One"
data.lines.each_with_index do |line, i|
    if /(?<num>\d)\.\s+#{str}/ =~ line
        /(?<var>\w+)/ =~ data.lines[i - (n = $~[:num]).to_i] 
        p [n, str, var] # ["1", "Item One", "Foo"]
    end
end

(n = $~[:num]) is needed to store the captured value of num from

if /(?<num>\d)\.\s+#{str}/ =~ line

into a variable (say n) as last match data, represented by global variable $~, will get overwritten during the next regex match taking place in statement

/(?<var>\w+)/ =~ data.lines[i - (num = $~[:num]).to_i]

and unless we store it for later use we will lose the captured value num.

like image 188
Wand Maker Avatar answered Jan 08 '23 01:01

Wand Maker


Here's another way (using @Wand's data):

LAZY_T = "├── " 
target = "Item Four"

str = data.split(/\n#{LAZY_T}/).find { |s| s =~ /\b#{target}\b/ }
str && [str[/[a-zA-Z]+/], str[/(\d+)\.\s#{target}\b/,1]]
  #=> ["Foo", "4"]

The first line pulls out the applicable part of the string ("Foo" or "Bar"), if there is one. The second line extracts the two desired elements.

Note:

LAZY_T.split('').map(&:ord)
  #=> [9500, 9472, 9472, 32]
like image 31
Cary Swoveland Avatar answered Jan 08 '23 03:01

Cary Swoveland