spec-cleaner 1.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spec_cleaner/__init__.py +170 -0
- spec_cleaner/__main__.py +11 -0
- spec_cleaner/dependency_parser.py +273 -0
- spec_cleaner/fileutils.py +64 -0
- spec_cleaner/rpmbuild.py +56 -0
- spec_cleaner/rpmcheck.py +77 -0
- spec_cleaner/rpmcleaner.py +427 -0
- spec_cleaner/rpmcopyright.py +117 -0
- spec_cleaner/rpmdescription.py +38 -0
- spec_cleaner/rpmexception.py +26 -0
- spec_cleaner/rpmfiles.py +116 -0
- spec_cleaner/rpmhelpers.py +286 -0
- spec_cleaner/rpminstall.py +64 -0
- spec_cleaner/rpmpackage.py +34 -0
- spec_cleaner/rpmpreamble.py +716 -0
- spec_cleaner/rpmpreambleelements.py +367 -0
- spec_cleaner/rpmprep.py +83 -0
- spec_cleaner/rpmprune.py +26 -0
- spec_cleaner/rpmregexp.py +237 -0
- spec_cleaner/rpmrequirestoken.py +107 -0
- spec_cleaner/rpmscriplets.py +55 -0
- spec_cleaner/rpmsection.py +393 -0
- spec_cleaner-1.2.3.data/data/lib/obs/service/clean_spec_file +54 -0
- spec_cleaner-1.2.3.data/data/lib/obs/service/clean_spec_file.service +8 -0
- spec_cleaner-1.2.3.data/data/lib/obs/service/format_spec_file +54 -0
- spec_cleaner-1.2.3.data/data/lib/obs/service/format_spec_file.service +8 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/allowed_groups.txt +245 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/cmake_conversions.txt +394 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/excludes-bracketing.txt +185 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/licenses-suse.toml +1674 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/licenses.toml +1648 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/licenses_changes.txt +1643 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/licenses_exceptions.txt +567 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/perl_conversions.txt +1381 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/pkgconfig_conversions.txt +2021 -0
- spec_cleaner-1.2.3.data/data/share/spec-cleaner/tex_conversions.txt +2800 -0
- spec_cleaner-1.2.3.dist-info/AUTHORS +8 -0
- spec_cleaner-1.2.3.dist-info/COPYING +26 -0
- spec_cleaner-1.2.3.dist-info/METADATA +27 -0
- spec_cleaner-1.2.3.dist-info/RECORD +43 -0
- spec_cleaner-1.2.3.dist-info/WHEEL +5 -0
- spec_cleaner-1.2.3.dist-info/entry_points.txt +2 -0
- spec_cleaner-1.2.3.dist-info/top_level.txt +1 -0
spec_cleaner/__init__.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# vim: set ts=4 sw=4 et: coding=UTF-8
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2015, SUSE LINUX Products GmbH, Nuernberg, Germany
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
# See COPYING for details.
|
|
6
|
+
|
|
7
|
+
"""Spec-cleaner formatting module."""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
from .rpmcleaner import RpmSpecCleaner
|
|
16
|
+
from .rpmexception import RpmExceptionError, RpmWrongArgsError
|
|
17
|
+
|
|
18
|
+
__version__ = '1.2.3'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def process_args(argv: List[str]) -> Dict[str, Any]:
|
|
22
|
+
"""
|
|
23
|
+
Parse and process commandline arguments.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
argv: A list of passed arguments.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A dict mapping arguments to the corresponding values.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
RpmWrongArgsError: If the specfile doesn't exist or
|
|
33
|
+
if the output file already exists but '--force' option (overwrite the output) wasn't used.
|
|
34
|
+
"""
|
|
35
|
+
parser = argparse.ArgumentParser(
|
|
36
|
+
prog='spec-cleaner',
|
|
37
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
38
|
+
description='Cleans the given spec file according to style guide and returns the result. If "#nospeccleaner"'
|
|
39
|
+
'tag is used in the spec file than the spec file will not be cleaned.',
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Make the -d, -i, and -o exclusive as we can do only one of those
|
|
43
|
+
output_group = parser.add_mutually_exclusive_group()
|
|
44
|
+
|
|
45
|
+
parser.add_argument('specfile', metavar='SPEC', type=str, help='spec file to beautify')
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
'-c',
|
|
48
|
+
'--cmake',
|
|
49
|
+
action='store_true',
|
|
50
|
+
help='convert dependencies to their cmake() counterparts, requires bit more of cleanup in spec afterwards.',
|
|
51
|
+
)
|
|
52
|
+
output_group.add_argument(
|
|
53
|
+
'-d',
|
|
54
|
+
'--diff',
|
|
55
|
+
action='store_true',
|
|
56
|
+
help='run the diff program to show differences between new and original specfile.',
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
'--diff-prog', default='vimdiff', help='specify the diff binary to call with diff option.',
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
'-f', '--force', action='store_true', help='overwrite the output file if already exist.',
|
|
63
|
+
)
|
|
64
|
+
output_group.add_argument(
|
|
65
|
+
'-i',
|
|
66
|
+
'--inline',
|
|
67
|
+
action='store_true',
|
|
68
|
+
help='inline the changes directly to the parsed file.',
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
'-m',
|
|
72
|
+
'--minimal',
|
|
73
|
+
action='store_true',
|
|
74
|
+
help='run in minimal mode that does not do anything intrusive (ie. just sets the Copyright)',
|
|
75
|
+
)
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
'--no-curlification',
|
|
78
|
+
action='store_true',
|
|
79
|
+
help='do not convert variables bracketing (%%{macro}) and keep it as it was on the input',
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
'--no-copyright',
|
|
83
|
+
action='store_true',
|
|
84
|
+
help='do not include official SUSE copyright hear and just keep what is present',
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
'--remove-groups', action='store_true', help='remove groups from the specfile.'
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
'--copyright-year',
|
|
91
|
+
metavar='YYYY',
|
|
92
|
+
type=int,
|
|
93
|
+
default=datetime.now().year,
|
|
94
|
+
help='year to insert into the copyright header when re-generating it',
|
|
95
|
+
)
|
|
96
|
+
output_group.add_argument(
|
|
97
|
+
'-o', '--output', default='', help='specify the output file for the cleaned spec content.',
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
'-p',
|
|
101
|
+
'--pkgconfig',
|
|
102
|
+
action='store_true',
|
|
103
|
+
help='convert dependencies to their pkgconfig() counterparts, requires bit more of cleanup in spec afterwards.',
|
|
104
|
+
)
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
'--perl',
|
|
107
|
+
action='store_true',
|
|
108
|
+
help='convert dependencies to their perl() counterparts, highly expansive use carefully',
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
'-t',
|
|
112
|
+
'--tex',
|
|
113
|
+
action='store_true',
|
|
114
|
+
help='convert dependencies to their tex() counterparts, requires verification of the output afterwards.',
|
|
115
|
+
)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
'-v',
|
|
118
|
+
'--version',
|
|
119
|
+
action='version',
|
|
120
|
+
version=__version__,
|
|
121
|
+
help='show package version and exit',
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
'-k', '--keep-space', action='store_true', help='keep empty lines in preamble intact.',
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# print help if there is no argument
|
|
128
|
+
if len(argv) < 1:
|
|
129
|
+
parser.print_help()
|
|
130
|
+
sys.exit(0)
|
|
131
|
+
|
|
132
|
+
options = parser.parse_args(args=argv)
|
|
133
|
+
|
|
134
|
+
# the spec must exist for us to do anything
|
|
135
|
+
if not os.path.exists(options.specfile):
|
|
136
|
+
raise RpmWrongArgsError('{0} does not exist.'.format(options.specfile))
|
|
137
|
+
|
|
138
|
+
# the path for output must exist and the file must not be there unless
|
|
139
|
+
# force is specified
|
|
140
|
+
if options.output:
|
|
141
|
+
options.output = os.path.expanduser(options.output)
|
|
142
|
+
if not options.force and os.path.exists(options.output):
|
|
143
|
+
raise RpmWrongArgsError('{0} already exists.'.format(options.output))
|
|
144
|
+
|
|
145
|
+
# convert options to dict
|
|
146
|
+
options_dict = vars(options)
|
|
147
|
+
return options_dict
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def main() -> int:
|
|
151
|
+
"""
|
|
152
|
+
Run main function.
|
|
153
|
+
|
|
154
|
+
It calls argument parsing ensures their sanity and then creates
|
|
155
|
+
RpmSpecCleaner object that works with passed spec file.
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
options = process_args(sys.argv[1:])
|
|
159
|
+
except RpmWrongArgsError as exception:
|
|
160
|
+
sys.stderr.write('ERROR: {0}\n'.format(exception))
|
|
161
|
+
return 1
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
cleaner = RpmSpecCleaner(options)
|
|
165
|
+
cleaner.run()
|
|
166
|
+
except RpmExceptionError as exception:
|
|
167
|
+
sys.stderr.write('ERROR: {0}\n'.format(exception))
|
|
168
|
+
return 1
|
|
169
|
+
|
|
170
|
+
return 0
|
spec_cleaner/__main__.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from .rpmexception import NoMatchExceptionError
|
|
5
|
+
from .rpmrequirestoken import RpmRequiresToken
|
|
6
|
+
|
|
7
|
+
chunk_types = ('text', 'space', 'macro', 'operator', 'version')
|
|
8
|
+
|
|
9
|
+
state_types = ('start', 'name', 'operator', 'version')
|
|
10
|
+
|
|
11
|
+
re_brackets = {}
|
|
12
|
+
re_brackets['('] = re.compile(
|
|
13
|
+
r'(' + r'\(' + r'|' + r'\)' + r'|' + r'\\(' + r'|' + r'\\)' + r'|' + r'[^\()]+' + r')'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
re_brackets['{'] = re.compile(
|
|
17
|
+
r'(' + r'\{' + r'|' + r'\}' + r'|' + r'\\{' + r'|' + r'\\}' + r'|' + r'[^\{}]+' + r')'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
re_name = re.compile(r'[-A-Za-z0-9_~(){}@:;.+/*\[\]]+')
|
|
21
|
+
re_version = re.compile(r'[-A-Za-z0-9_~():.+]+')
|
|
22
|
+
re_spaces = re.compile(r'(\s*,\s*|\s+)')
|
|
23
|
+
re_macro_unbraced = re.compile('%[A-Za-z0-9_]{3,}')
|
|
24
|
+
re_version_operator = re.compile('(>=|<=|=>|=<|>|<|=)')
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger('DepParser')
|
|
27
|
+
# Switch to logging.DEBUG if needed
|
|
28
|
+
logger.setLevel(logging.ERROR)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def find_end_of_bracketed_macro(string, regex, opening, closing):
|
|
32
|
+
"""Find the end of bracketed macros."""
|
|
33
|
+
# ommit the initial bracket, or %bracket
|
|
34
|
+
if string.startswith('%'):
|
|
35
|
+
macro = string[0:2]
|
|
36
|
+
string = string[2:]
|
|
37
|
+
else:
|
|
38
|
+
macro = string[0:1]
|
|
39
|
+
string = string[1:]
|
|
40
|
+
opened = 1
|
|
41
|
+
while opened and string:
|
|
42
|
+
try:
|
|
43
|
+
bite, string = consume_chars(regex, string)
|
|
44
|
+
except NoMatchExceptionError:
|
|
45
|
+
raise Exception('unexpected parser error when looking for end of macro')
|
|
46
|
+
|
|
47
|
+
if bite == opening:
|
|
48
|
+
opened += 1
|
|
49
|
+
elif bite == closing:
|
|
50
|
+
opened -= 1
|
|
51
|
+
macro += bite
|
|
52
|
+
|
|
53
|
+
if opened:
|
|
54
|
+
raise Exception('Unexpectedly met end of string when looking for end of macro')
|
|
55
|
+
return macro, string
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def consume_chars(regex, string):
|
|
59
|
+
"""Consume regex matches of all characters of a string."""
|
|
60
|
+
match = regex.match(string)
|
|
61
|
+
if match:
|
|
62
|
+
end = match.end()
|
|
63
|
+
return string[0:end], string[end:]
|
|
64
|
+
else:
|
|
65
|
+
raise NoMatchExceptionError(
|
|
66
|
+
'Expected match failed (string: "%s", regex: "%s" )' % (string, regex.pattern)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def read_boolean(string):
|
|
71
|
+
"""Read boolean macros."""
|
|
72
|
+
return find_end_of_bracketed_macro(string, re_brackets['('], '(', ')')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def matching_bracket(bracket):
|
|
76
|
+
"""Find the appropriate matching brackets."""
|
|
77
|
+
if bracket == '{':
|
|
78
|
+
return '}'
|
|
79
|
+
elif bracket == '(':
|
|
80
|
+
return ')'
|
|
81
|
+
raise Exception(
|
|
82
|
+
'Undefined bracket matching - add defintion of "%s" to ' 'matching_bracket()' % bracket
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def read_macro(string):
|
|
87
|
+
"""Read macro in brackets."""
|
|
88
|
+
opening = string[1]
|
|
89
|
+
closing = matching_bracket(opening)
|
|
90
|
+
return find_end_of_bracketed_macro(string, re_brackets[opening], opening, closing)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def read_next_chunk(string):
|
|
94
|
+
"""Read string chunks after operators."""
|
|
95
|
+
chunk = ''
|
|
96
|
+
chunk_type = ''
|
|
97
|
+
|
|
98
|
+
if not string:
|
|
99
|
+
return '', '', 'text'
|
|
100
|
+
|
|
101
|
+
if string[0:2] in ('>=', '<=', '=>', '=<'):
|
|
102
|
+
chunk = string[0:2]
|
|
103
|
+
chunk_type = 'operator'
|
|
104
|
+
rest = string[2:]
|
|
105
|
+
|
|
106
|
+
elif string[0:1] in ('<', '>', '='):
|
|
107
|
+
chunk = string[0:1]
|
|
108
|
+
chunk_type = 'operator'
|
|
109
|
+
rest = string[1:]
|
|
110
|
+
|
|
111
|
+
elif string[0].isspace() or string[0] == ',':
|
|
112
|
+
chunk = ''
|
|
113
|
+
chunk_type = 'space'
|
|
114
|
+
rest = consume_chars(re_spaces, string)[1]
|
|
115
|
+
|
|
116
|
+
elif string[0:2] == '%%':
|
|
117
|
+
chunk = '%%'
|
|
118
|
+
chunk_type = 'text'
|
|
119
|
+
rest = string[2:]
|
|
120
|
+
|
|
121
|
+
elif string[0:2] in ('%{', '%('):
|
|
122
|
+
chunk, rest = read_macro(string)
|
|
123
|
+
chunk_type = 'macro'
|
|
124
|
+
|
|
125
|
+
elif string[0] == '%':
|
|
126
|
+
chunk, rest = consume_chars(re_macro_unbraced, string)
|
|
127
|
+
chunk_type = 'macro'
|
|
128
|
+
|
|
129
|
+
elif string[0] == '(':
|
|
130
|
+
chunk, rest = read_boolean(string)
|
|
131
|
+
chunk_type = 'macro'
|
|
132
|
+
|
|
133
|
+
else:
|
|
134
|
+
chunk, rest = consume_chars(re_name, string)
|
|
135
|
+
chunk_type = 'text'
|
|
136
|
+
|
|
137
|
+
return (rest, chunk, chunk_type)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class DepParserError(Exception):
|
|
141
|
+
"""Exception definition for dependency parsing."""
|
|
142
|
+
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class DependencyParser:
|
|
147
|
+
"""Dependency parsing class."""
|
|
148
|
+
|
|
149
|
+
def __init__(self, line):
|
|
150
|
+
"""Initialize class."""
|
|
151
|
+
# adding comma will cause flush in the end of line
|
|
152
|
+
self.string = line + ', '
|
|
153
|
+
self.parsed = []
|
|
154
|
+
self.token = []
|
|
155
|
+
self.state = 'start'
|
|
156
|
+
self.space = False
|
|
157
|
+
self.token_name = ''
|
|
158
|
+
self.token_operator = None
|
|
159
|
+
self.token_version = None
|
|
160
|
+
self.go_on = True
|
|
161
|
+
while self.go_on:
|
|
162
|
+
self.string, self.next, self.next_type = read_next_chunk(self.string)
|
|
163
|
+
logger.debug(
|
|
164
|
+
"""========
|
|
165
|
+
chunk: '%s'
|
|
166
|
+
chunk_type: '%s'
|
|
167
|
+
rest: '%s'
|
|
168
|
+
token: '%s'
|
|
169
|
+
parsed: '%s'""",
|
|
170
|
+
self.next,
|
|
171
|
+
self.next_type,
|
|
172
|
+
self.string,
|
|
173
|
+
self.token,
|
|
174
|
+
self.parsed,
|
|
175
|
+
)
|
|
176
|
+
self.state_change()
|
|
177
|
+
|
|
178
|
+
def flat_out(self):
|
|
179
|
+
"""Flatten versioned definition to a single string."""
|
|
180
|
+
result = []
|
|
181
|
+
for name, operator, ver in self.parsed:
|
|
182
|
+
result.append(RpmRequiresToken(name, operator, ver))
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
def flush(self):
|
|
186
|
+
"""Clean token variables of the class."""
|
|
187
|
+
self.parsed.append((self.token_name, self.token_operator, self.token_version))
|
|
188
|
+
# cleanup state
|
|
189
|
+
self.token = []
|
|
190
|
+
self.token_name = ''
|
|
191
|
+
self.token_operator = None
|
|
192
|
+
self.token_version = None
|
|
193
|
+
if not self.string:
|
|
194
|
+
self.go_on = False
|
|
195
|
+
|
|
196
|
+
def reconstitute_token(self):
|
|
197
|
+
"""Erase token."""
|
|
198
|
+
r = ''.join(self.token)
|
|
199
|
+
logger.debug("reconstituting '%s'", r)
|
|
200
|
+
self.token = []
|
|
201
|
+
return r
|
|
202
|
+
|
|
203
|
+
def name_state_change(self):
|
|
204
|
+
"""Check name of next token."""
|
|
205
|
+
if self.next_type in ['text', 'macro']:
|
|
206
|
+
if self.space:
|
|
207
|
+
logger.debug('text after space --> flush')
|
|
208
|
+
self.token_name = self.reconstitute_token()
|
|
209
|
+
self.space = False
|
|
210
|
+
self.flush()
|
|
211
|
+
elif self.next_type == 'space':
|
|
212
|
+
self.space = True
|
|
213
|
+
elif self.next_type == 'operator':
|
|
214
|
+
self.token_name = self.reconstitute_token()
|
|
215
|
+
self.state = 'operator'
|
|
216
|
+
self.space = False
|
|
217
|
+
|
|
218
|
+
self.token.append(self.next)
|
|
219
|
+
|
|
220
|
+
def operator_state_change(self):
|
|
221
|
+
"""Check next operator token."""
|
|
222
|
+
if self.next_type in ['text', 'macro']:
|
|
223
|
+
self.state = 'version'
|
|
224
|
+
self.token_operator = self.reconstitute_token()
|
|
225
|
+
self.space = False
|
|
226
|
+
elif self.next_type == 'space':
|
|
227
|
+
self.space = True
|
|
228
|
+
elif self.next_type == 'operator':
|
|
229
|
+
if self.space:
|
|
230
|
+
raise DepParserError('found operator after operator')
|
|
231
|
+
|
|
232
|
+
self.token.append(self.next)
|
|
233
|
+
|
|
234
|
+
def version_state_change(self):
|
|
235
|
+
"""Check valid token after version."""
|
|
236
|
+
if self.next_type == 'text':
|
|
237
|
+
pass
|
|
238
|
+
elif self.next_type == 'space':
|
|
239
|
+
self.token_version = self.reconstitute_token()
|
|
240
|
+
self.flush()
|
|
241
|
+
self.state = 'name'
|
|
242
|
+
self.space = False
|
|
243
|
+
elif self.next_type == 'macro':
|
|
244
|
+
pass
|
|
245
|
+
elif self.next_type == 'operator':
|
|
246
|
+
raise DepParserError('found operator after version')
|
|
247
|
+
|
|
248
|
+
self.token.append(self.next)
|
|
249
|
+
|
|
250
|
+
def start_state_change(self):
|
|
251
|
+
"""Check next token valid for starting definition."""
|
|
252
|
+
if self.next_type == 'text':
|
|
253
|
+
self.state = 'name'
|
|
254
|
+
elif self.next_type == 'space':
|
|
255
|
+
pass
|
|
256
|
+
elif self.next_type == 'macro':
|
|
257
|
+
self.state = 'name'
|
|
258
|
+
elif self.next_type == 'operator':
|
|
259
|
+
raise DepParserError('found operator when name expected')
|
|
260
|
+
|
|
261
|
+
self.token.append(self.next)
|
|
262
|
+
|
|
263
|
+
def state_change(self):
|
|
264
|
+
"""Select which state change to check."""
|
|
265
|
+
if self.state == 'name':
|
|
266
|
+
self.name_state_change()
|
|
267
|
+
elif self.state == 'operator':
|
|
268
|
+
self.operator_state_change()
|
|
269
|
+
elif self.state == 'version':
|
|
270
|
+
self.version_state_change()
|
|
271
|
+
elif self.state == 'start':
|
|
272
|
+
self.start_state_change()
|
|
273
|
+
logger.debug('new state: %s', self.state)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# vim: set ts=4 sw=4 et: coding=UTF-8
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import sysconfig
|
|
6
|
+
from io import StringIO
|
|
7
|
+
from typing import IO
|
|
8
|
+
|
|
9
|
+
from .rpmexception import RpmExceptionError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def open_datafile(name: str) -> IO[str]:
|
|
13
|
+
"""
|
|
14
|
+
Open data files.
|
|
15
|
+
|
|
16
|
+
Used all around so kept glob here for importing.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
name: A string representing the name of the datafile to open.
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
RpmExceptionError if the file is not found in predefined datadirs.
|
|
23
|
+
"""
|
|
24
|
+
homedir = os.getenv('HOME', '~') + '/.local/'
|
|
25
|
+
|
|
26
|
+
possible_paths = (
|
|
27
|
+
'{0}/../data/{1}'.format(os.path.dirname(os.path.realpath(__file__)), name),
|
|
28
|
+
'{0}/share/spec-cleaner/{1}'.format(homedir, name),
|
|
29
|
+
'{0}/share/spec-cleaner/{1}'.format(sysconfig.get_path('data'), name),
|
|
30
|
+
'{0}/share/spec-cleaner/{1}'.format(sys.prefix, name),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
for path in possible_paths:
|
|
34
|
+
try:
|
|
35
|
+
_file = open(path, mode='r')
|
|
36
|
+
except OSError:
|
|
37
|
+
pass
|
|
38
|
+
else:
|
|
39
|
+
return _file
|
|
40
|
+
# file not found
|
|
41
|
+
raise RpmExceptionError("File '{}' not found in datadirs".format(name))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def open_stringio_spec(name: str) -> IO[str]:
|
|
45
|
+
"""
|
|
46
|
+
Open regular files with exception handling.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
name: A string with the file name.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A file object.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
RpmExceptionError if the file is not readable.
|
|
56
|
+
"""
|
|
57
|
+
data = StringIO()
|
|
58
|
+
try:
|
|
59
|
+
with open(name, mode='r') as f:
|
|
60
|
+
data.write(f.read())
|
|
61
|
+
data.seek(0, 0)
|
|
62
|
+
except (IOError, UnicodeDecodeError) as error:
|
|
63
|
+
raise RpmExceptionError(str(error))
|
|
64
|
+
return data
|
spec_cleaner/rpmbuild.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# vim: set ts=4 sw=4 et: coding=UTF-8
|
|
2
|
+
|
|
3
|
+
# We basically extend rpmcheck
|
|
4
|
+
from .rpmcheck import RpmCheck
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RpmBuild(RpmCheck):
|
|
8
|
+
"""A class providing methods for %build section cleaning."""
|
|
9
|
+
|
|
10
|
+
def add(self, line: str) -> None:
|
|
11
|
+
# we do not want to run suseupdateconfig, deprecated
|
|
12
|
+
if self.reg.re_suseupdateconfig.search(line):
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
# if user uses cmake/configure directly just recommend him using the macros
|
|
16
|
+
if not self.minimal:
|
|
17
|
+
self._comment_macro_calls(line)
|
|
18
|
+
|
|
19
|
+
RpmCheck.add(self, line)
|
|
20
|
+
|
|
21
|
+
def _comment_macro_calls(self, line: str) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Add a comment if the package uses direct call of build tools instead of macro.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
line: A string representing a line to process.
|
|
27
|
+
"""
|
|
28
|
+
# if the line itself is comment then we do not add any fixme on top of them
|
|
29
|
+
if self.reg.re_comment.match(line):
|
|
30
|
+
return
|
|
31
|
+
# otherwise process the fixes
|
|
32
|
+
if self.reg.re_configure.match(line):
|
|
33
|
+
msg = '# FIXME: you should use the %%configure macro'
|
|
34
|
+
elif self.reg.re_cmake.match(line):
|
|
35
|
+
msg = '# FIXME: you should use the %%cmake macros'
|
|
36
|
+
elif self.reg.re_meson.match(line):
|
|
37
|
+
msg = '# FIXME: you should use the %%meson macros'
|
|
38
|
+
elif self.reg.re_qmake5.match(line):
|
|
39
|
+
msg = '# FIXME: you should use the %%qmake5 macro'
|
|
40
|
+
else:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
position = len(self.lines)
|
|
44
|
+
if self.previous_line and self.previous_line.endswith('\\'):
|
|
45
|
+
# Backtrack until we are on top of multiline command
|
|
46
|
+
for i in reversed(self.lines):
|
|
47
|
+
if i.endswith('\\'):
|
|
48
|
+
position -= 1
|
|
49
|
+
else:
|
|
50
|
+
# we stop backtracking on the first
|
|
51
|
+
# non-backslashed line
|
|
52
|
+
break
|
|
53
|
+
# Check if on the position above is no comment
|
|
54
|
+
# if not add our message
|
|
55
|
+
if not self.lines[position - 1].startswith('#'):
|
|
56
|
+
self.lines.insert(position, msg)
|
spec_cleaner/rpmcheck.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# vim: set ts=4 sw=4 et: coding=UTF-8
|
|
2
|
+
|
|
3
|
+
from .rpmsection import Section
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RpmCheck(Section):
|
|
7
|
+
"""
|
|
8
|
+
A class providing methods for %check section cleaning.
|
|
9
|
+
|
|
10
|
+
Replace various troublemakers in check phase.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def add(self, line: str) -> None:
|
|
14
|
+
line = self._complete_cleanup(line)
|
|
15
|
+
|
|
16
|
+
# smp_mflags for jobs macro replacement
|
|
17
|
+
line = self.reg.re_jobs.sub('%{?_smp_mflags}', line)
|
|
18
|
+
|
|
19
|
+
if not self.minimal:
|
|
20
|
+
line = self._replace_pytest(line)
|
|
21
|
+
line = self._replace_unittest(line)
|
|
22
|
+
line = self._replace_make(line)
|
|
23
|
+
|
|
24
|
+
Section.add(self, line)
|
|
25
|
+
|
|
26
|
+
def _replace_pytest(self, line: str) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Replace various pytest calls with %pytest or %pytest_arch macros.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
line: A string representing a line to process.
|
|
32
|
+
|
|
33
|
+
Return:
|
|
34
|
+
The processed line.
|
|
35
|
+
"""
|
|
36
|
+
line = self.reg.re_pytest.sub('%pytest', line)
|
|
37
|
+
line = self.reg.re_pytest_arch.sub('%pytest_arch', line)
|
|
38
|
+
return line
|
|
39
|
+
|
|
40
|
+
def _replace_unittest(self, line: str) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Replace various pytest calls with %pyunittest or %pyunittest_arch macros.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
line: A string representing a line to process.
|
|
46
|
+
|
|
47
|
+
Return:
|
|
48
|
+
The processed line.
|
|
49
|
+
"""
|
|
50
|
+
line = self.reg.re_pyunittest.sub('%pyunittest', line)
|
|
51
|
+
line = self.reg.re_pyunittest_arch.sub('%pyunittest_arch', line)
|
|
52
|
+
return line
|
|
53
|
+
|
|
54
|
+
def _replace_make(self, line: str) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Replace 'make' call with '%make_build' macro.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
line: A string representing a line to process.
|
|
60
|
+
|
|
61
|
+
Return:
|
|
62
|
+
The processed line.
|
|
63
|
+
"""
|
|
64
|
+
# if it is comment line then do nothing
|
|
65
|
+
if line.lstrip().startswith('#'):
|
|
66
|
+
return line
|
|
67
|
+
# find all make calls and replace them with %make_build
|
|
68
|
+
if self.reg.re_make.match(line):
|
|
69
|
+
line = self.reg.re_make.sub(r'\1%make_build\2', line)
|
|
70
|
+
# remove from line all the options that are part of %make_build macro
|
|
71
|
+
if self.reg.re_make_build.match(line):
|
|
72
|
+
# remove smp flags
|
|
73
|
+
line = line.replace(' %{?_smp_mflags}', '')
|
|
74
|
+
# remove verbosity
|
|
75
|
+
line = line.replace(' V=1', '')
|
|
76
|
+
line = line.replace(' VERBOSE=1', '')
|
|
77
|
+
return line
|