File: vernum-updater.rb

package info (click to toggle)
twittering-mode 2.0.0%2Bgit20120325-1
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 2,144 kB
  • sloc: lisp: 16,858; ruby: 533; makefile: 132; sh: 65
file content (208 lines) | stat: -rwxr-xr-x 4,848 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#!/usr/bin/env ruby

require 'optparse'
require 'tempfile'
require 'pathname'
require 'fileutils'

def y_or_n_p(msg)
  answer = nil

  while answer == nil
    print "#{msg} [yes or no]: "
    $stdout.flush
    ans = $stdin.readline.strip
    case ans
    when /yes/
      answer = true
      break
    when /no/
      answer = false
      break
    else
      puts "Please type 'yes' or 'no'."
      puts
    end
  end

  answer
end

class FileContext
  def initialize(lines, cur_line_idx,
                 match_col_start, match_col_end, rplstr)
    @lines = lines
    @cur_line_idx = cur_line_idx
    @match_col_start = match_col_start
    @match_col_end = match_col_end
    @rplstr = rplstr
  end

  def inspect
    cur_line = @lines[@cur_line_idx][1].dup
    len = @match_col_end - @match_col_start
    rplstr = "\033[1m\033[4m" + cur_line[@match_col_start, len] + "\033[0m"
    cur_line[@match_col_start, len] = rplstr
    lines = @lines.dup
    lines[@cur_line_idx] = [@lines[@cur_line_idx][0], cur_line]
    
    lineno_width = lines[lines.size - 1][0].to_s.size
    lines.map{|line| sprintf("%#{lineno_width}d: %s", line[0], line[1])}.join.rstrip
  end

  def line
    @lines[@cur_line_idx][1]
  end

  def lineno
    @lines[@cur_line_idx][0]
  end

  def replaced_line
    cur_line = @lines[@cur_line_idx][1].dup
    len = @match_col_end - @match_col_start
    cur_line[@match_col_start, len] = @rplstr
    cur_line
  end
end

class FileReplacer
  def initialize(file, opt)
    @infile = File.open(file, "r")
    @outfile = Tempfile.new("fileupdater")
    @opt = opt

    @lineno = 1
    # current line is the first element of after_buffer
    @before_buffer = []
    @after_buffer = []
    (@opt[:context] + 1).times do
      self.fetch_line(true)
    end
  end
  
  def replace!()
    while @after_buffer.size > 0
      pos = 0
      cur_line_str = @after_buffer.first[1]
      while @opt[:regexp].match(cur_line_str[pos..(cur_line_str.size)])
        context = FileContext.new(@before_buffer + @after_buffer,
                                  @before_buffer.size,
                                  pos + $~.begin(0),
                                  pos + $~.end(0), @opt[:replace])
        rpl = yield(context) || cur_line_str
        if rpl != cur_line_str
          @after_buffer[0][1] = rpl
        end
        pos = $~.begin(0) + 1
      end
      @outfile.print(@after_buffer.first[1])

      self.fetch_line
    end
    
    @infile.close
    @outfile.close
    FileUtils.copy(@outfile.path, @infile.path)
  end

  def fetch_line(beginning_of_file = false)
    eof_reached = false
    line = nil
    begin
      line = @infile.readline
    rescue EOFError => e
      eof_reached = true
    end
    
    if line
      @after_buffer.push([@lineno, line])
      @lineno += 1
    end

    if ! beginning_of_file &&
        (eof_reached || @after_buffer.size > @opt[:context] + 1)
      @before_buffer.push(@after_buffer.shift)
      @before_buffer.shift if @before_buffer.size > @opt[:context]
    end

    @after_buffer.first
  end
end

def update(file, opt)
  base = Pathname.pwd
  path = Pathname.new(file).realpath.to_s
  replacer = FileReplacer.new(file,
                              :regexp => Regexp.compile(Regexp.quote(opt[:prev_version])),
                              :replace => opt[:next_version],
                              :context => opt[:context])
  replacer.replace! do |context|
    puts "==== file: #{Pathname.new(path).relative_path_from(base).to_s} ===="
    puts context.inspect
    puts
    puts "\033[34m\033[1mbefore\033[0m: #{context.line}"
    puts "\033[31m\033[1mafter\033[0m : #{context.replaced_line}"
    puts
    if y_or_n_p("Update this line?")
      context.replaced_line
    else
      context.line
    end
  end
rescue Errno::ENOENT => e
  nil
end

def parse_args(argv)
  parser = OptionParser.new
  opt = Hash.new
  print_help = Proc.new do |msg|
    puts msg
    puts
    puts parser.help
    exit
  end

  opt[:context] = 3
  parser.on('-C', '--context=NUM',
            "\n\t\tPrint NUM lines around matching lines."+
            " Default value is #{opt[:context]}") do |num|
    opt[:context] = num.to_i
    opt[:context] = 3 if opt[:context] < 3
  end

  parser.on('-p', '--prev-version=NUM',
            "\n\t\tPrevious version number.") do |prev|
    opt[:prev_version] = prev
  end

  parser.on('-n', '--next-version=NUM',
            "\n\t\tNext version number.") do |next_|
    opt[:next_version] = next_
  end

  parser.parse!(argv)

  unless opt[:prev_version]
    print_help.call("Previous version number must be specified")
  end

  unless opt[:next_version]
    print_help.call("Next version number must be specified")
  end

  opt
end

def main(argv)
  opt = parse_args(argv)

  argv.each do |file|
    update(file, opt)
  end
end

if __FILE__ == $0
  main(ARGV.dup)
end