2 # vim: set ts=4 sw=4 et: coding=UTF-8
5 # Copyright (c) 2009, Novell, Inc.
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions are met:
11 # * Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 # * Neither the name of the <ORGANIZATION> nor the names of its contributors
17 # may be used to endorse or promote products derived from this software
18 # without specific prior written permission.
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 # POSSIBILITY OF SUCH DAMAGE.
33 # (Licensed under the simplified BSD license)
36 # Vincent Untz <vuntz@novell.com>
37 # Pavol Rusnak <prusnak@opensuse.org>
48 #######################################################################
52 re_comment = re.compile('^$|^\s*#')
53 re_define = re.compile('^\s*%define', re.IGNORECASE)
55 re_bindir = re.compile('%{_prefix}/bin([/\s$])')
56 re_sbindir = re.compile('%{_prefix}/sbin([/\s$])')
57 re_includedir = re.compile('%{_prefix}/include([/\s$])')
58 re_datadir = re.compile('%{_prefix}/share([/\s$])')
59 re_mandir = re.compile('%{_datadir}/man([/\s$])')
60 re_infodir = re.compile('%{_datadir}/info([/\s$])')
63 def strip_useless_spaces(s):
64 return ' '.join(s.split())
67 def replace_known_dirs(s):
68 s = s.replace('%_prefix', '%{_prefix}')
69 s = s.replace('%_usr', '%{_prefix}')
70 s = s.replace('%{_usr}', '%{_prefix}')
71 s = s.replace('%_bindir', '%{_bindir}')
72 s = s.replace('%_sbindir', '%{_sbindir}')
73 s = s.replace('%_includedir', '%{_includedir}')
74 s = s.replace('%_datadir', '%{_datadir}')
75 s = s.replace('%_mandir', '%{_mandir}')
76 s = s.replace('%_infodir', '%{_infodir}')
77 s = s.replace('%_libdir', '%{_libdir}')
78 s = s.replace('%_libexecdir', '%{_libexecdir}')
79 s = s.replace('%_lib', '%{_lib}')
80 s = s.replace('%{_prefix}/%{_lib}', '%{_libdir}')
81 s = s.replace('%_sysconfdir', '%{_sysconfdir}')
82 s = s.replace('%_localstatedir', '%{_localstatedir}')
83 s = s.replace('%_var', '%{_localstatedir}')
84 s = s.replace('%{_var}', '%{_localstatedir}')
85 s = s.replace('%_initddir', '%{_initddir}')
86 # old typo in rpm macro
87 s = s.replace('%_initrddir', '%{_initddir}')
88 s = s.replace('%{_initrddir}', '%{_initddir}')
90 s = re_bindir.sub(r'%{_bindir}\1', s)
91 s = re_sbindir.sub(r'%{_sbindir}\1', s)
92 s = re_includedir.sub(r'%{_includedir}\1', s)
93 s = re_datadir.sub(r'%{_datadir}\1', s)
94 s = re_mandir.sub(r'%{_mandir}\1', s)
95 s = re_infodir.sub(r'%{_infodir}\1', s)
100 def replace_buildroot(s):
101 s = s.replace('${RPM_BUILD_ROOT}', '%{buildroot}')
102 s = s.replace('$RPM_BUILD_ROOT', '%{buildroot}')
103 s = s.replace('%buildroot', '%{buildroot}')
104 s = s.replace('%{buildroot}/etc/init.d/', '%{buildroot}%{_initddir}/')
105 s = s.replace('%{buildroot}/etc/', '%{buildroot}%{_sysconfdir}/')
106 s = s.replace('%{buildroot}/usr/', '%{buildroot}%{_prefix}/')
107 s = s.replace('%{buildroot}/var/', '%{buildroot}%{_localstatedir}/')
108 s = s.replace('"%{buildroot}"', '%{buildroot}')
112 def replace_optflags(s):
113 s = s.replace('${RPM_OPT_FLAGS}', '%{optflags}')
114 s = s.replace('$RPM_OPT_FLAGS', '%{optflags}')
115 s = s.replace('%optflags', '%{optflags}')
119 def replace_remove_la(s):
120 cmp_line = strip_useless_spaces(s)
121 if cmp_line in [ 'find %{buildroot} -type f -name "*.la" -exec %{__rm} -fv {} +', 'find %{buildroot} -type f -name "*.la" -delete' ]:
122 s = 'find %{buildroot} -type f -name "*.la" -delete -print'
126 def replace_utils(s):
127 # take care of all utilities macros that bloat spec file
128 s = s.replace('%__id_u', 'id -u')
129 s = s.replace('%{__id_u}', 'id -u')
130 s = s.replace('%__ln_s', 'ln -s')
131 s = s.replace('%{__ln_s}', 'ln -s')
132 s = s.replace('%__lzma', 'xz --format-lzma')
133 s = s.replace('%{__lzma}', 'xz --format-lzma')
134 s = s.replace('%__mkdir_p', 'mkdir -p')
135 s = s.replace('%{__mkdir_p}', 'mkdir -p')
136 s = s.replace('%__awk','gawk')
137 s = s.replace('%{__awk}','gawk')
138 s = s.replace('%__cc','gcc')
139 s = s.replace('%{__cc}','gcc')
140 s = s.replace('%__cpp','gcc -E')
141 s = s.replace('%{__cpp}','gcc -E')
142 s = s.replace('%__cxx','g++')
143 s = s.replace('%{__cxx}','g++')
144 s = s.replace('%__remsh','rsh')
145 s = s.replace('%{__remsh}','rsh')
147 for i in [ 'aclocal', 'ar', 'as', 'autoconf', 'autoheader', 'automake', 'bzip2', 'cat', 'chgrp', 'chmod', 'chown', 'cp', 'cpio', 'file', 'gpg', 'grep', 'gzip', 'id', 'install', 'ld', 'libtoolize', 'make', 'mkdir', 'mv', 'nm', 'objcopy', 'objdump', 'patch', 'perl', 'python', 'ranlib', 'restorecon', 'rm', 'rsh', 'sed', 'semodule', 'ssh', 'strip', 'tar', 'unzip', 'xz', ]:
148 s = s.replace('%__'+i, i)
149 s = s.replace('%{__'+i+'}', i)
154 def replace_buildservice(s):
155 for i in ['centos', 'debian', 'fedora', 'mandriva', 'meego', 'rhel', 'sles', 'suse', 'ubuntu']:
156 s = s.replace('%' + i + '_version','0%{?' + i + '_version}')
157 s = s.replace('%{' + i + '_version}','0%{?' + i + '_version}')
162 s = replace_buildroot(s)
163 s = replace_optflags(s)
164 s = replace_known_dirs(s)
165 s = replace_remove_la(s)
167 s = replace_buildservice(s)
171 #######################################################################
174 class RpmException(Exception):
178 #######################################################################
181 class RpmSection(object):
183 Basic cleanup: we remove trailing spaces.
188 self.previous_line = None
192 line = replace_all(line)
193 self.lines.append(line)
194 self.previous_line = line
196 def output(self, fout):
197 for line in self.lines:
198 fout.write(line + '\n')
201 #######################################################################
204 class RpmCopyright(RpmSection):
206 Adds default copyright notice if needed.
207 Remove initial empty lines.
208 Remove norootforbuild.
212 def _add_default_copyright(self):
213 self.lines.append(time.strftime('''#
214 # spec file for package
216 # Copyright (c) %Y SUSE LINUX Products GmbH, Nuernberg, Germany.
218 # All modifications and additions to the file contributed by third parties
219 # remain the property of their copyright owners, unless otherwise agreed
220 # upon. The license for this file, and modifications and additions to the
221 # file, is the same license as for the pristine package itself (unless the
222 # license for the pristine package is not an Open Source License, in which
223 # case the license is the MIT License). An "Open Source License" is a
224 # license that conforms to the Open Source Definition (Version 1.9)
225 # published by the Open Source Initiative.
227 # Please submit bugfixes or comments via http://bugs.opensuse.org/
234 if not self.lines and not line:
237 if line == '# norootforbuild':
240 RpmSection.add(self, line)
243 def output(self, fout):
245 self._add_default_copyright()
246 RpmSection.output(self, fout)
249 #######################################################################
252 class RpmPreamble(RpmSection):
254 Only keep one empty line for many consecutive ones.
257 Use one line per BuildRequires/Requires/etc.
258 Use %{version} instead of %{version}-%{release} for BuildRequires/etc.
260 Standardize BuildRoot.
262 This one is a bit tricky since we reorder things. We have a notion of
263 paragraphs, categories, and groups.
265 A paragraph is a list of non-empty lines. Conditional directives like
266 %if/%else/%endif also mark paragraphs. It contains categories.
267 A category is a list of lines on the same topic. It contains a list of
269 A group is a list of lines where the first few ones are either %define
270 or comment lines, and the last one is a normal line.
272 This means that the %define and comments will stay attached to one
273 line, even if we reorder the lines.
276 re_if = re.compile('^\s*(?:%if\s|%ifarch\s|%ifnarch\s|%else\s*$|%endif\s*$)', re.IGNORECASE)
278 re_name = re.compile('^Name:\s*(\S*)', re.IGNORECASE)
279 re_version = re.compile('^Version:\s*(\S*)', re.IGNORECASE)
280 re_release = re.compile('^Release:\s*(\S*)', re.IGNORECASE)
281 re_license = re.compile('^License:\s*(.*)', re.IGNORECASE)
282 re_summary = re.compile('^Summary:\s*(.*)', re.IGNORECASE)
283 re_url = re.compile('^Url:\s*(\S*)', re.IGNORECASE)
284 re_group = re.compile('^Group:\s*(.*)', re.IGNORECASE)
285 re_source = re.compile('^Source(\d*):\s*(\S*)', re.IGNORECASE)
286 re_patch = re.compile('^((?:#[#\s]*)?)Patch(\d*):\s*(\S*)', re.IGNORECASE)
287 re_buildrequires = re.compile('^BuildRequires:\s*(.*)', re.IGNORECASE)
288 re_prereq = re.compile('^PreReq:\s*(.*)', re.IGNORECASE)
289 re_requires = re.compile('^Requires:\s*(.*)', re.IGNORECASE)
290 re_recommends = re.compile('^Recommends:\s*(.*)', re.IGNORECASE)
291 re_suggests = re.compile('^Suggests:\s*(.*)', re.IGNORECASE)
292 re_supplements = re.compile('^Supplements:\s*(.*)', re.IGNORECASE)
293 re_provides = re.compile('^Provides:\s*(.*)', re.IGNORECASE)
294 re_obsoletes = re.compile('^Obsoletes:\s*(.*)', re.IGNORECASE)
295 re_buildroot = re.compile('^\s*BuildRoot:', re.IGNORECASE)
296 re_buildarch = re.compile('^\s*BuildArch:\s*(.*)', re.IGNORECASE)
298 re_requires_token = re.compile('(\s*(\S+(?:\s*(?:[<>]=?|=)\s*[^\s,]+)?),?)')
302 'version': re_version,
303 'release': re_release,
304 'license': re_license,
305 'summary': re_summary,
308 # for source, we have a special match to keep the source number
309 # for patch, we have a special match to keep the patch number
310 'buildrequires': re_buildrequires,
312 'requires': re_requires,
313 'recommends': re_recommends,
314 'suggests': re_suggests,
315 'supplements': re_supplements,
316 # for provides/obsoletes, we have a special case because we group them
317 # for build root, we have a special match because we force its value
318 'buildarch': re_buildarch
323 'version': 'Version',
324 'release': 'Release',
325 'license': 'License',
326 'summary': 'Summary',
331 'buildrequires': 'BuildRequires',
333 'requires': 'Requires',
334 'recommends': 'Recommends',
335 'suggests': 'Suggests',
336 'supplements': 'Supplements',
337 # Provides/Obsoletes cannot be part of this since we want to keep them
338 # mixed, so we'll have to specify the key when needed
339 'buildroot': 'BuildRoot',
340 'buildarch': 'BuildArch'
343 category_to_fixer = {
347 'LGPL v2.0 only': 'LGPLv2.0',
348 'LGPL v2.0 or later': 'LGPLv2.0+',
349 'LGPL v2.1 only': 'LGPLv2.1',
350 'LGPL v2.1 or later': 'LGPLv2.1+',
351 'LGPL v3 only': 'LGPLv3',
352 'LGPL v3 or later': 'LGPLv3+',
353 'GPL v2 only': 'GPLv2',
354 'GPL v2 or later': 'GPLv2+',
355 'GPL v3 only': 'GPLv3',
356 'GPL v3 or later': 'GPLv3+'
359 categories_order = [ 'name', 'version', 'release', 'license', 'summary', 'url', 'group', 'source', 'patch', 'buildrequires', 'prereq', 'requires', 'recommends', 'suggests', 'supplements', 'provides_obsoletes', 'buildroot', 'buildarch', 'misc' ]
361 categories_with_sorted_package_tokens = [ 'buildrequires', 'prereq', 'requires', 'recommends', 'suggests', 'supplements' ]
362 categories_with_package_tokens = categories_with_sorted_package_tokens[:]
363 categories_with_package_tokens.append('provides_obsoletes')
365 re_autoreqprov = re.compile('^\s*AutoReqProv:\s*on\s*$', re.IGNORECASE)
369 RpmSection.__init__(self)
370 self._start_paragraph()
373 def _start_paragraph(self):
375 for i in self.categories_order:
376 self.paragraph[i] = []
377 self.current_group = []
380 def _add_group(self, group):
384 RpmSection.add(self, group)
386 for subgroup in group:
387 self._add_group(subgroup)
389 raise RpmException('Unknown type of group in preamble: %s' % t)
392 def _end_paragraph(self):
393 def sort_helper_key(a):
400 raise RpmException('Unknown type during sort: %s' % t)
402 for i in self.categories_order:
403 if i in self.categories_with_sorted_package_tokens:
404 self.paragraph[i].sort(key=sort_helper_key)
405 for group in self.paragraph[i]:
406 self._add_group(group)
407 if self.current_group:
408 # the current group was not added to any category. It's just some
409 # random stuff that should be at the end anyway.
410 self._add_group(self.current_group)
412 self._start_paragraph()
415 def _fix_license(self, value):
416 licenses = value.split(';')
417 for (index, license) in enumerate(licenses):
418 license = strip_useless_spaces(license)
419 if self.license_fixes.has_key(license):
420 license = self.license_fixes[license]
421 licenses[index] = license
423 return [ ' ; '.join(licenses) ]
425 category_to_fixer['license'] = _fix_license
428 def _fix_list_of_packages(self, value):
429 if self.re_requires_token.match(value):
430 tokens = [ item[1] for item in self.re_requires_token.findall(value) ]
431 for (index, token) in enumerate(tokens):
432 token = token.replace('%{version}-%{release}', '%{version}')
433 tokens[index] = token
440 for i in categories_with_package_tokens:
441 category_to_fixer[i] = _fix_list_of_packages
444 def _add_line_value_to(self, category, value, key = None):
446 Change a key-value line, to make sure we have the right spacing.
448 Note: since we don't have a key <-> category matching, we need to
449 redo one. (Eg: Provides and Obsoletes are in the same category)
451 keylen = len('BuildRequires: ')
455 elif self.category_to_key.has_key(category):
456 key = self.category_to_key[category]
458 raise RpmException('Unhandled category in preamble: %s' % category)
461 while len(key) < keylen:
464 if self.category_to_fixer.has_key(category):
465 values = self.category_to_fixer[category](self, value)
471 self._add_line_to(category, line)
474 def _add_line_to(self, category, line):
475 if self.current_group:
476 self.current_group.append(line)
477 self.paragraph[category].append(self.current_group)
478 self.current_group = []
480 self.paragraph[category].append(line)
482 self.previous_line = line
487 if not self.previous_line or len(self.previous_line) == 0:
490 # we put the empty line in the current group (so we don't list it),
491 # and write the paragraph
492 self.current_group.append(line)
493 self._end_paragraph()
494 self.previous_line = line
497 elif self.re_if.match(line):
498 # %if/%else/%endif marks the end of the previous paragraph
499 # We append the line at the end of the previous paragraph, though,
500 # since it will stay at the end there. If putting it at the
501 # beginning of the next paragraph, it will likely move (with the
503 self.current_group.append(line)
504 self._end_paragraph()
505 self.previous_line = line
508 elif re_comment.match(line) or re_define.match(line):
509 self.current_group.append(line)
510 self.previous_line = line
513 elif self.re_autoreqprov.match(line):
516 elif self.re_source.match(line):
517 match = self.re_source.match(line)
518 self._add_line_value_to('source', match.group(2), key = 'Source%s' % match.group(1))
521 elif self.re_patch.match(line):
522 # FIXME: this is not perfect, but it's good enough for most cases
523 if not self.previous_line or not re_comment.match(self.previous_line):
524 self.current_group.append('# PATCH-MISSING-TAG -- See http://en.opensuse.org/Packaging/Patches')
526 match = self.re_patch.match(line)
527 self._add_line_value_to('source', match.group(3), key = '%sPatch%s' % (match.group(1), match.group(2)))
530 elif self.re_provides.match(line):
531 match = self.re_provides.match(line)
532 self._add_line_value_to('provides_obsoletes', match.group(1), key = 'Provides')
535 elif self.re_obsoletes.match(line):
536 match = self.re_obsoletes.match(line)
537 self._add_line_value_to('provides_obsoletes', match.group(1), key = 'Obsoletes')
540 elif self.re_buildroot.match(line):
541 if len(self.paragraph['buildroot']) == 0:
542 self._add_line_value_to('buildroot', '%{_tmppath}/%{name}-%{version}-build')
546 for (category, regexp) in self.category_to_re.iteritems():
547 match = regexp.match(line)
549 self._add_line_value_to(category, match.group(1))
552 self._add_line_to('misc', line)
555 def output(self, fout):
556 self._end_paragraph()
557 RpmSection.output(self, fout)
560 #######################################################################
563 class RpmPackage(RpmPreamble):
565 We handle this the same was as the preamble.
569 # The first line (%package) should always be added and is different
570 # from the lines we handle in RpmPreamble.
571 if self.previous_line is None:
572 RpmSection.add(self, line)
575 RpmPreamble.add(self, line)
578 #######################################################################
581 class RpmDescription(RpmSection):
583 Only keep one empty line for many consecutive ones.
584 Remove Authors from description.
588 RpmSection.__init__(self)
589 self.removing_authors = False
590 # Tracks the use of a macro. When this happens and we're still in a
591 # description, we actually don't know where we are so we just put all
592 # the following lines blindly, without trying to fix anything.
593 self.unknown_line = False
596 lstrip = line.lstrip()
597 if self.previous_line != None and len(lstrip) > 0 and lstrip[0] == '%':
598 self.unknown_line = True
600 if self.removing_authors and not self.unknown_line:
604 if not self.previous_line or len(self.previous_line) == 0:
607 if line == 'Authors:':
608 self.removing_authors = True
611 RpmSection.add(self, line)
614 #######################################################################
617 class RpmPrep(RpmSection):
619 Try to simplify to %setup -q when possible.
623 if line.startswith('%setup'):
624 cmp_line = line.replace(' -q', '')
625 cmp_line = cmp_line.replace(' -n %{name}-%{version}', '')
626 cmp_line = strip_useless_spaces(cmp_line)
627 if cmp_line == '%setup':
630 RpmSection.add(self, line)
633 #######################################################################
636 class RpmBuild(RpmSection):
638 Replace %{?jobs:-j%jobs} (suse-ism) with %{?_smp_mflags}
642 if not re_comment.match(line):
643 line = line.replace('%{?jobs:-j%jobs}' , '%{?_smp_mflags}')
644 line = line.replace('%{?jobs: -j%jobs}', '%{?_smp_mflags}')
646 RpmSection.add(self, line)
649 #######################################################################
652 class RpmInstall(RpmSection):
654 Remove commands that wipe out the build root.
655 Use %make_install macro.
658 re_autoreqprov = re.compile('^\s*AutoReqProv:\s*on\s*$', re.IGNORECASE)
661 # remove double spaces when comparing the line
662 cmp_line = strip_useless_spaces(line)
663 cmp_line = replace_buildroot(cmp_line)
665 if cmp_line.find('DESTDIR=%{buildroot}') != -1:
666 buf = cmp_line.replace('DESTDIR=%{buildroot}', '')
667 buf = strip_useless_spaces(buf)
668 if buf == 'make install':
669 line = '%make_install'
670 elif cmp_line == 'rm -rf %{buildroot}':
673 if self.re_autoreqprov.match(line):
676 RpmSection.add(self, line)
679 #######################################################################
682 class RpmClean(RpmSection):
683 # if the section contains just rm -rf %{buildroot} then remove the whole section (including %clean)
687 #######################################################################
690 class RpmScriptlets(RpmSection):
692 Do %post -p /sbin/ldconfig when possible.
696 RpmSection.__init__(self)
701 if len(self.lines) == 0:
703 if line.find(' -p ') == -1 and line.find(' -f ') == -1:
704 self.cache.append(line)
707 if line in ['', '/sbin/ldconfig' ]:
708 self.cache.append(line)
711 for cached in self.cache:
712 RpmSection.add(self, cached)
715 RpmSection.add(self, line)
718 def output(self, fout):
720 RpmSection.add(self, self.cache[0] + ' -p /sbin/ldconfig')
721 RpmSection.add(self, '')
723 RpmSection.output(self, fout)
726 #######################################################################
729 class RpmFiles(RpmSection):
731 Replace additional /usr, /etc and /var because we're sure we can use
734 Replace '%dir %{_includedir}/mux' and '%{_includedir}/mux/*' with
735 '%{_includedir}/mux/'
738 re_etcdir = re.compile('(^|\s)/etc/')
739 re_usrdir = re.compile('(^|\s)/usr/')
740 re_vardir = re.compile('(^|\s)/var/')
742 re_dir = re.compile('^\s*%dir\s*(\S+)\s*')
745 RpmSection.__init__(self)
746 self.dir_on_previous_line = None
750 line = self.re_etcdir.sub(r'\1%{_sysconfdir}/', line)
751 line = self.re_usrdir.sub(r'\1%{_prefix}/', line)
752 line = self.re_vardir.sub(r'\1%{_localstatedir}/', line)
754 if self.dir_on_previous_line:
755 if line == self.dir_on_previous_line + '/*':
756 RpmSection.add(self, self.dir_on_previous_line + '/')
757 self.dir_on_previous_line = None
760 RpmSection.add(self, '%dir ' + self.dir_on_previous_line)
761 self.dir_on_previous_line = None
763 match = self.re_dir.match(line)
765 self.dir_on_previous_line = match.group(1)
768 RpmSection.add(self, line)
771 #######################################################################
774 class RpmChangelog(RpmSection):
776 Remove changelog entries.
780 # only add the first line (%changelog)
781 if len(self.lines) == 0:
782 RpmSection.add(self, line)
785 #######################################################################
788 class RpmSpecCleaner:
793 current_section = None
796 re_spec_package = re.compile('^%package\s*', re.IGNORECASE)
797 re_spec_description = re.compile('^%description\s*', re.IGNORECASE)
798 re_spec_prep = re.compile('^%prep\s*$', re.IGNORECASE)
799 re_spec_build = re.compile('^%build\s*$', re.IGNORECASE)
800 re_spec_install = re.compile('^%install\s*$', re.IGNORECASE)
801 re_spec_clean = re.compile('^%clean\s*$', re.IGNORECASE)
802 re_spec_scriptlets = re.compile('(?:^%pretrans\s*)|(?:^%pre\s*)|(?:^%post\s*)|(?:^%preun\s*)|(?:^%postun\s*)|(?:^%posttrans\s*)', re.IGNORECASE)
803 re_spec_files = re.compile('^%files\s*', re.IGNORECASE)
804 re_spec_changelog = re.compile('^%changelog\s*$', re.IGNORECASE)
808 (re_spec_package, RpmPackage),
809 (re_spec_description, RpmDescription),
810 (re_spec_prep, RpmPrep),
811 (re_spec_build, RpmBuild),
812 (re_spec_install, RpmInstall),
813 (re_spec_clean, RpmClean),
814 (re_spec_scriptlets, RpmScriptlets),
815 (re_spec_files, RpmFiles),
816 (re_spec_changelog, RpmChangelog)
820 def __init__(self, specfile, output, inline, force):
821 if not specfile.endswith('.spec'):
822 raise RpmException('%s does not appear to be a spec file.' % specfile)
824 if not os.path.exists(specfile):
825 raise RpmException('%s does not exist.' % specfile)
827 self.specfile = specfile
831 self.fin = open(self.specfile)
834 if not force and os.path.exists(self.output):
835 raise RpmException('%s already exists.' % self.output)
836 self.fout = open(self.output, 'w')
838 io = cStringIO.StringIO()
840 bytes = self.fin.read(500 * 1024)
848 self.fout = open(self.specfile, 'w')
850 self.fout = sys.stdout
854 if not self.specfile or not self.fin:
855 raise RpmException('No spec file.')
857 def _line_for_new_section(self, line):
858 if isinstance(self.current_section, RpmCopyright):
859 if not re_comment.match(line):
862 for (regexp, newclass) in self.section_starts:
863 if regexp.match(line):
869 self.current_section = RpmCopyright()
872 line = self.fin.readline()
875 # Remove \n to make it easier to parse things
878 new_class = _line_for_new_section(self, line)
880 self.current_section.output(self.fout)
881 self.current_section = new_class()
883 self.current_section.add(line)
885 self.current_section.output(self.fout)
897 #######################################################################
901 parser = optparse.OptionParser(epilog='This script cleans spec file according to some arbitrary style guide. The results it produces should always be checked by someone since it is not and will never be perfect.')
903 parser.add_option("-i", "--inline", action="store_true", dest="inline",
904 default=False, help="edit the file inline")
905 parser.add_option("-o", "--output", dest="output",
907 parser.add_option("-f", "--force", action="store_true", dest="force",
908 default=False, help="overwrite output file if already existing")
909 parser.add_option("-v", "--version", action="store_true", dest="version",
910 default=False, help="display version (" + VERSION + ")")
912 (options, args) = parser.parse_args()
915 print 'spec-cleaner ' + VERSION
919 print >> sys.stderr, '\nUsage:\n\tspec-cleaner file.spec\n'
922 spec = os.path.expanduser(args[0])
924 options.output = os.path.expanduser(options.output)
926 if options.output == spec:
928 options.inline = True
930 if options.output and options.inline:
931 print >> sys.stderr, 'Conflicting options: --inline and --output.'
935 cleaner = RpmSpecCleaner(spec, options.output, options.inline, options.force)
937 except RpmException, e:
938 print >> sys.stderr, '%s' % e
943 if __name__ == '__main__':
947 except KeyboardInterrupt: