correctly handle most of the spec stuff
[opensuse:nmarquess-spec-cleaner.git] / next / spec-cleaner
1 #!/usr/bin/ruby
2 #
3 # Copyright (c) 2009-2011, SUSE
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are met:
8 #
9 #  * Redistributions of source code must retain the above copyright notice,
10 #    this list of conditions and the following disclaimer.
11 #  * Redistributions in binary form must reproduce the above copyright notice,
12 #    this list of conditions and the following disclaimer in the documentation
13 #    and/or other materials provided with the distribution.
14 #  * Neither the name of the <ORGANIZATION> nor the names of its contributors
15 #    may be used to endorse or promote products derived from this software
16 #    without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 # POSSIBILITY OF SUCH DAMAGE.
29 #
30 #
31 # (Licensed under the simplified BSD license)
32 #
33 # Authors:
34 #   Pavol Rusnak <prusnak@opensuse.org>
35 #
36
37 class String
38   attr_reader :_meta_
39   def annotate!(meta)
40     @_meta_ = meta unless meta.empty?
41     self
42   end
43 end
44
45 class Array
46   attr_reader :_meta_
47   def annotate!(meta)
48     @_meta_ = meta unless meta.empty?
49     self
50   end
51 end
52
53 class Package
54
55   attr_accessor :name, :version, :release, :group, :license, :url
56   attr_accessor :summary, :description
57   attr_accessor :sources, :patches
58   attr_accessor :files, :files_include, :buildarch
59   attr_accessor :buildrequires, :requires, :requires_pre, :requires_preun, :requires_post, :requires_postun, :recommends, :suggests, :supplements
60   attr_accessor :provides, :obsoletes, :conflicts
61   attr_accessor :pretrans, :pre, :post, :triggerin, :triggerun, :preun, :postun, :triggerpostin, :triggerpostun, :posttrans, :verifyscript
62
63   def initialize
64     @name = nil
65     @version = nil
66     @release = nil
67     @group = nil
68     @license = nil
69     @url = nil
70     @summary = nil
71     @description = []
72     @sources = {}
73     @patches = {}
74     @files = []
75     @files_include = []
76     @buildarch = nil
77     @buildrequires = []
78     @requires = []
79     @recommends = []
80     @suggests = []
81     @supplements = []
82     @provides = []
83     @obsoletes = []
84     @conflicts = []
85     @requires_pre = []
86     @requires_preun = []
87     @requires_post = []
88     @requires_postun = []
89     @pretrans = nil
90     @pre = nil
91     @post = nil
92     @preun = nil
93     @postun = nil
94     @posttrans = nil
95     @triggerin = {}
96     @triggerun = {}
97     @triggerpostin = {}
98     @triggerpostun = {}
99     @verifyscript = nil
100   end
101
102 end
103
104
105 class Spec
106
107   def initialize
108     @intro = []
109     @packages = {}
110     @prep = nil
111     @build = nil
112     @install = nil
113     @check = nil
114     @clean = nil
115     @changelog = nil
116     @lines = nil
117     @packages = {}
118   end
119
120   def next_section(line)
121     case line
122       when /^%package/, /^%description/, /^%prep/, /^%build/, /^%install/, /^%check/, /^%clean/, /^%changelog/,
123            /^%pretrans/, /^%pre/, /^%post/, /^%preun/, /^%postun/, /^%posttrans/,
124            /^%triggerin/, /^%triggerun/, /^%triggerpostin/, /^%triggerpostun/,
125            /^%verifyscript/, /^%files/
126         true
127      else
128        false
129     end
130   end
131
132   def parse_package(pkg)
133     meta = []
134     @packages[pkg] ||= Package.new
135     loop do
136       break if @lines.empty?
137       line = @lines.shift
138       if next_section(line)
139         @lines.unshift(line)
140         return
141       end
142       case line
143         when /^$/, /^#/, /^%/
144           meta << line
145         when /^Name:/i
146           @packages[pkg].name = line[5..-1].strip.annotate!(meta)
147           meta = []
148         when /^Version:/i
149           @packages[pkg].version = line[8..-1].strip.annotate!(meta)
150           meta = []
151         when /^Release:/i
152           @packages[pkg].release = line[8..-1].strip.annotate!(meta)
153           meta = []
154         when /^Group:/i
155           @packages[pkg].group = line[6..-1].strip.annotate!(meta)
156           meta = []
157         when /^License:/i
158           @packages[pkg].license = line[8..-1].strip.annotate!(meta)
159           meta = []
160         when /^Url:/i
161           @packages[pkg].url = line[4..-1].strip.annotate!(meta)
162           meta = []
163         when /^Summary:/i
164           @packages[pkg].summary = line[8..-1].strip.annotate!(meta)
165           meta = []
166         when /^Source:/i
167           @packages[pkg].sources[0] = line[7..-1].strip.annotate!(meta)
168           meta = []
169         when /^Patch:/i
170           @packages[pkg].patches[0] = line[6..-1].strip.annotate!(meta)
171           meta = []
172         when /^Source(\d+):/i
173           @packages[pkg].sources[ $1.to_i ] = line.gsub(/^Source(\d+):/i, '').strip.annotate!(meta)
174           meta = []
175         when /^Patch(\d+):/i
176           @packages[pkg].patches[ $1.to_i ] = line.gsub(/^Patch(\d+):/i, '').strip.annotate!(meta)
177           meta = []
178         when /^BuildArch:/i
179           @packages[pkg].buildarch = line[14..-1].strip.annotate!(meta)
180           meta = []
181         when /^BuildRequires:/i
182           @packages[pkg].buildrequires << line[14..-1].strip.annotate!(meta)
183           meta = []
184         when /^Requires:/i
185           @packages[pkg].requires << line[9..-1].strip.annotate!(meta)
186           meta = []
187         when /^PreReq:/i
188           @packages[pkg].requires_pre << line[7..-1].strip.annotate!(meta)
189           meta = []
190         when /^Requires\(pre\):/i
191           @packages[pkg].requires_pre << line[14..-1].strip.annotate!(meta)
192           meta = []
193         when /^Requires\(preun\):/i
194           @packages[pkg].requires_preun << line[16..-1].strip.annotate!(meta)
195           meta = []
196         when /^Requires\(post\):/i
197           @packages[pkg].requires_post << line[15..-1].strip.annotate!(meta)
198           meta = []
199         when /^Requires\(postun\):/i
200           @packages[pkg].requires_postun << line[17..-1].strip.annotate!(meta)
201           meta = []
202         when /^Recommends:/i
203           @packages[pkg].recommends << line[11..-1].strip.annotate!(meta)
204           meta = []
205         when /^Suggests:/i
206           @packages[pkg].suggests << line[9..-1].strip.annotate!(meta)
207           meta = []
208         when /^Supplements:/i
209           @packages[pkg].supplements << line[12..-1].strip.annotate!(meta)
210           meta = []
211         when /^Provides:/i
212           @packages[pkg].provides << line[9..-1].strip.annotate!(meta)
213           meta = []
214         when /^Obsoletes:/i
215           @packages[pkg].obsoletes << line[10..-1].strip.annotate!(meta)
216           meta = []
217         when /^Conflicts:/i
218           @packages[pkg].conflicts << line[10..-1].strip.annotate!(meta)
219           meta = []
220       end
221     end
222   end
223
224   def parse_section
225     section = []
226     loop do
227       break if @lines.empty?
228       line = @lines.shift
229       if next_section(line)
230         @lines.unshift(line)
231         break
232       end
233       section << line
234     end
235     section.shift while not section.first.nil? and section.first.empty?
236     section.pop while not section.first.nil? and section.last.empty?
237     section
238   end
239
240   def parse_files_section(line)
241     case line
242       when /^%files\s+-n\s+(\S+)\s+-f(.*)$/
243         @packages['-n ' + $1].instance_variable_set('@files', parse_section())
244         @packages['-n ' + $1].instance_variable_set('@files_include', $2.split(/\s/).select { |x| x != '' and x != '-f' })
245       when /^%files\s+(\S+)\s+-f(.*)$/
246         @packages[$1].instance_variable_set('@files', parse_section())
247         @packages[$1].instance_variable_set('@files_include', $2.split(/\s/).select { |x| x != '' and x != '-f' })
248       when /^%files\s+-f(.*)$/
249         @packages[''].instance_variable_set('@files', parse_section())
250         @packages[''].instance_variable_set('@files_include', $1.split(/\s/).select { |x| x != '' and x != '-f' })
251       when /^%files\s+-n\s+(\S+)$/
252         @packages['-n ' + $1].instance_variable_set('@files', parse_section())
253       when /^%files\s+(\S+)$/
254         @packages[$1].instance_variable_set('@files', parse_section())
255       when /^%files$/
256         @packages[''].instance_variable_set('@files', parse_section())
257     end
258   end
259
260   # checks for scriptlets that can have -n and -p parameters (%pre for example)
261   def check_scriptlet(type, line)
262     case line
263       when /^%#{type}\s+-n\s+(\S+)\s+-p\s(.+)$/
264         section = parse_section()
265         section.annotate!($2.strip)
266         @packages['-n ' + $1].instance_variable_set('@'+type, section)
267       when /^%#{type}\s+-n\s+(\S+)$/
268         section = parse_section()
269         @packages['-n ' + $1].instance_variable_set('@'+type, section)
270       when /^%#{type}\s+-p\s(.+)$/
271         section = parse_section()
272         section.annotate!($1.strip)
273         @packages[''].instance_variable_set('@'+type, section)
274       when /^%#{type}\s+(\S+)$/
275         section = parse_section()
276         @packages[$1].instance_variable_set('@'+type, section)
277       when /^%#{type}$/
278         section = parse_section()
279         @packages[''].instance_variable_set('@'+type, section)
280     end
281   end
282
283   # check triggers
284   def check_trigger(type, line)
285     case line
286       when /^%trigger#{type}\s+-n\s+(\S+)\s+-p\s(.+)\s+--\s+(\.+)$/
287         section = parse_section()
288         section.annotate!($2.strip)
289         @packages['-n ' + $1].instance_variable_get('@trigger'+type)[$3] = section
290       when /^%trigger#{type}\s+-n\s+(\S+)\s+--\s+(.+)$/
291         section = parse_section()
292         @packages['-n ' + $1].instance_variable_get('@trigger'+type)[$2] = section
293       when /^%trigger#{type}\s+-p\s(.+)\s+--\s+(.+)$/
294         section = parse_section()
295         section.annotate!($1.strip)
296         @packages[''].instance_variable_get('@trigger'+type)[$2] = section
297       when /^%trigger#{type}\s+(\S+)\s+--\s+(.+)$/
298         section = parse_section()
299         @packages[$1].instance_variable_get('@trigger'+type)[$2] = section
300       when /^%trigger#{type}\s+--\s+(.+)$/
301         section = parse_section()
302         @packages[''].instance_variable_get('@trigger'+type)[$1] = section
303     end
304   end
305
306   def parse(lines)
307
308     @lines = lines.collect{ |x| x.rstrip }
309
310     loop do
311       break if @lines.empty?
312       line = @lines.shift
313       if line == '' or line =~ /^#/ or line =~ /^%/
314         @intro << line
315       else
316         @lines.unshift(line)
317         break
318       end
319     end
320
321     @intro.shift while not @intro.first.nil? and @intro.first.empty?
322     @intro.pop while not @intro.last.nil? and @intro.last.empty?
323
324     parse_package('')
325
326     until @lines.empty? do
327
328       line = @lines.shift
329       case line
330         when /^%package\s+-n\s+(\S+)$/
331           parse_package('-n ' + $1)
332         when /^%package\s+(\S+)$/
333           parse_package($1)
334         when /^%description\s+-n\s+(\S+)$/
335           @packages['-n ' + $1].description = parse_section()
336         when /^%description\s+(\S+)$/
337           @packages[$1].description = parse_section()
338         when /^%description$/
339           @packages[''].description = parse_section()
340         when /^%prep$/
341           @prep = parse_section()
342         when /^%build$/
343           @build = parse_section()
344         when /^%install$/
345           @install = parse_section()
346         when /^%check$/
347           @check = parse_section()
348         when /^%clean$/
349           @clean = parse_section()
350         when /^%changelog$/
351           @changelog = parse_section()
352         when /^%files/
353           parse_files_section(line)
354       end
355
356       check_scriptlet('pretrans', line)
357       check_scriptlet('pre', line)
358       check_scriptlet('post', line)
359       check_scriptlet('preun', line)
360       check_scriptlet('postun', line)
361       check_scriptlet('posttrans', line)
362       check_scriptlet('verifyscript', line)
363
364       check_trigger('in', line)
365       check_trigger('un', line)
366       check_trigger('postin', line)
367       check_trigger('postun', line)
368
369     end
370
371   end
372
373   def process
374
375   end
376
377
378   def construct_package(id)
379
380     out = []
381     width = 16
382
383     out << '%package ' + id unless id.empty?
384
385     unless @packages[id].name.nil?
386       out += @packages[id].name._meta_ unless @packages[id].name._meta_.nil?
387       out << 'Name: '.ljust(width) + @packages[id].name
388     end
389
390     unless @packages[id].version.nil?
391       out += @packages[id].version._meta_ unless @packages[id].version._meta_.nil?
392       out << 'Version: '.ljust(width) + @packages[id].version
393     end
394
395     unless @packages[id].release.nil?
396       out += @packages[id].release._meta_ unless @packages[id].release._meta_.nil?
397       out << 'Release: '.ljust(width) + @packages[id].release
398     end
399
400     unless @packages[id].license.nil?
401       out += @packages[id].license._meta_ unless @packages[id].license._meta_.nil?
402       out << 'License: '.ljust(width) + @packages[id].license
403     end
404
405     unless @packages[id].summary.nil?
406       out += @packages[id].summary._meta_ unless @packages[id].summary._meta_.nil?
407       out << 'Summary: '.ljust(width) + @packages[id].summary
408     end
409
410     unless @packages[id].url.nil?
411       out += @packages[id].url._meta_ unless @packages[id].url._meta_.nil?
412       out << 'Url: '.ljust(width) + @packages[id].url
413     end
414
415     unless @packages[id].group.nil?
416       out += @packages[id].group._meta_ unless @packages[id].group._meta_.nil?
417       out << 'Group: '.ljust(width) + @packages[id].group
418     end
419
420     @packages[id].sources.keys.sort.each { |x|
421       out += @packages[id].sources[x]._meta_ unless @packages[id].sources[x]._meta_.nil?
422       if x == 0:
423         out << "Source:".ljust(width) + @packages[id].sources[x]
424       else
425         out << "Source#{x}:".ljust(width) + @packages[id].sources[x]
426       end
427     }
428
429     @packages[id].patches.keys.sort.each { |x|
430       out += @packages[id].patches[x]._meta_ unless @packages[id].patches[x]._meta_.nil?
431       out << "Patch#{x}:".ljust(width) + @packages[id].patches[x];
432     }
433
434     @packages[id].buildrequires.each { |x|
435       out += x._meta_ unless x._meta_.nil?
436       out << "BuildRequires:".ljust(width) + x
437     }
438
439     @packages[id].requires.each { |x|
440       out += x._meta_ unless x._meta_.nil?
441       out << "Requires:".ljust(width) + x
442     }
443
444     @packages[id].requires_pre.each { |x|
445       out += x._meta_ unless x._meta_.nil?
446       out << "Requires(pre):".ljust(width) + x
447     }
448
449     @packages[id].requires_preun.each { |x|
450       out += x._meta_ unless x._meta_.nil?
451       out << "Requires(preun):".ljust(width) + x
452     }
453
454     @packages[id].requires_post.each { |x|
455       out += x._meta_ unless x._meta_.nil?
456       out << "Requires(post):".ljust(width) + x
457     }
458
459     @packages[id].requires_postun.each { |x|
460       out += x._meta_ unless x._meta_.nil?
461       out << "Requires(postun):".ljust(width) + x
462     }
463
464     @packages[id].recommends.each { |x|
465       out += x._meta_ unless x._meta_.nil?
466       out << "Recommends:".ljust(width) + x
467     }
468
469     @packages[id].suggests.each { |x|
470       out += x._meta_ unless x._meta_.nil?
471       out << "Suggests:".ljust(width) + x
472     }
473
474     @packages[id].supplements.each { |x|
475       out += x._meta_ unless x._meta_.nil?
476       out << "Supplements:".ljust(width) + x
477     }
478
479     @packages[id].provides.each { |x|
480       out += x._meta_ unless x._meta_.nil?
481       out << "Provides:".ljust(width) + x
482     }
483
484     @packages[id].obsoletes.each { |x|
485       out += x._meta_ unless x._meta_.nil?
486       out << "Obsoletes:".ljust(width) + x
487     }
488
489     @packages[id].conflicts.each { |x|
490       out += x._meta_ unless x._meta_.nil?
491       out << "Conflicts:".ljust(width) + x
492     }
493
494     unless @packages[id].buildarch.nil?
495       out += @packages[id].buildarch._meta_ unless @packages[id].buildarch._meta_.nil?
496       out << 'BuildArch: '.ljust(width) + @packages[id].buildarch
497     end
498
499     out << ''
500     out << '%description' + (id.empty? ? '' : ' ' + id)
501     out += @packages[id].description
502
503     out
504
505   end
506
507   def construct
508     out = []
509
510     out += @intro
511
512     @packages.keys.sort.each{ |x|
513       out << ''
514       out += construct_package(x)
515     }
516
517     unless @prep.nil?
518       out << ''
519       out << '%prep'
520       out += @prep
521     end
522
523     unless @build.nil?
524       out << ''
525       out << '%build'
526       out += @build
527     end
528
529     unless @install.nil?
530       out << ''
531       out << '%install'
532       out += @install
533     end
534
535     unless @check.nil?
536       out << ''
537       out << '%check'
538       out += @check
539     end
540
541     unless @clean.nil?
542       out << ''
543       out << '%clean'
544       out += @clean
545     end
546
547     @packages.keys.sort.each{ |x|
548
549       unless @packages[x].pretrans.nil?
550         out << ''
551         out << '%pretrans' + (x.empty? ? '' : ' ' + x) + (@packages[x].pretrans._meta_.nil? ? '' : ' -p ' + @packages[x].pretrans._meta_)
552         out += @packages[x].pretrans
553       end
554
555       unless @packages[x].pre.nil?
556         out << ''
557         out << '%pre' + (x.empty? ? '' : ' ' + x) + (@packages[x].pre._meta_.nil? ? '' : ' -p ' + @packages[x].pre._meta_)
558         out += @packages[x].pre
559       end
560
561       unless @packages[x].preun.nil?
562         out << ''
563         out << '%preun' + (x.empty? ? '' : ' ' + x) + (@packages[x].preun._meta_.nil? ? '' : ' -p ' + @packages[x].preun._meta_)
564         out += @packages[x].preun
565       end
566
567       unless @packages[x].post.nil?
568         out << ''
569         out << '%post' + (x.empty? ? '' : ' ' + x) + (@packages[x].post._meta_.nil? ? '' : ' -p ' + @packages[x].post._meta_)
570         out += @packages[x].post
571       end
572
573       unless @packages[x].postun.nil?
574         out << ''
575         out << '%postun' + (x.empty? ? '' : ' ' + x) + (@packages[x].postun._meta_.nil? ? '' : ' -p ' + @packages[x].postun._meta_)
576         out += @packages[x].postun
577       end
578
579       unless @packages[x].posttrans.nil?
580         out << ''
581         out << '%posttrans' + (x.empty? ? '' : ' ' + x) + (@packages[x].posttrans._meta_.nil? ? '' : ' -p ' + @packages[x].posttrans._meta_)
582         out += @packages[x].posttrans
583       end
584
585       unless @packages[x].verifyscript.nil?
586         out << ''
587         out << '%verifyscript' + (x.empty? ? '' : ' ' + x) + (@packages[x].verifyscript._meta_.nil? ? '' : ' -p ' + @packages[x].verifyscript._meta_)
588         out += @packages[x].verifyscript
589       end
590
591        @packages[x].triggerin.keys.sort.each{ |y|
592         out << ''
593         out << '%triggerin' + (x.empty? ? '' : ' ' + x) + (@packages[x].triggerin[y]._meta_.nil? ? '' : ' -p ' + @packages[x].triggerin[y]._meta_) + ' -- ' + y
594         out += @packages[x].triggerin[y]
595       }
596
597       @packages[x].triggerun.keys.sort.each{ |y|
598         out << ''
599         out << '%triggerun' + (x.empty? ? '' : ' ' + x) + (@packages[x].triggerun[y]._meta_.nil? ? '' : ' -p ' + @packages[x].triggerun[y]._meta_) + ' -- ' + y
600         out += @packages[x].triggerun[y]
601       }
602
603       @packages[x].triggerpostin.keys.sort.each{ |y|
604         out << ''
605         out << '%triggerpostin' + (x.empty? ? '' : ' ' + x) + (@packages[x].triggerpostin[y]._meta_.nil? ? '' : ' -p ' + @packages[x].triggerpostin[y]._meta_) + ' -- ' + y
606         out += @packages[x].triggerpostin[y]
607       }
608
609       @packages[x].triggerpostun.keys.sort.each{ |y|
610         out << ''
611         out << '%triggerpostun' + (x.empty? ? '' : ' ' + x) + (@packages[x].triggerpostun[y]._meta_.nil? ? '' : ' -p ' + @packages[x].triggerpostun[y]._meta_) + ' -- ' + y
612         out += @packages[x].triggerpostun[y]
613       }
614
615    }
616
617     @packages.keys.sort.each{ |x|
618       out << ''
619       out << '%files' + (x.empty? ? '' : ' ' + x) + @packages[x].files_include.inject('') { |sum, y| sum + ' -f ' + y }
620       out += @packages[x].files
621     }
622
623     unless @changelog.nil?
624       out << ''
625       out << '%changelog'
626       out += @changelog
627     end
628
629     out.join("\n")
630   end
631
632 end
633
634
635 abort 'Usage: spec-cleaner filename.spec' if ARGV.length < 1
636
637 spec = Spec.new
638
639 lines = IO.readlines(ARGV[0])
640 spec.parse(lines)
641
642 spec.process
643
644 puts spec.construct