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