1
/*******************************************************************************
2
* Copyright (c) 2007, 2009 Red Hat, Inc.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* Red Hat - initial API and implementation
11
*******************************************************************************/
13
package org.eclipse.linuxtools.rpm.ui.editor.parser;
15
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.BUILD_SECTION;
16
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.CHANGELOG_SECTION;
17
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.CLEAN_SECTION;
18
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.DESCRIPTION_SECTION;
19
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.FILES_SECTION;
20
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.INSTALL_SECTION;
21
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PACKAGE_SECTION;
22
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.POSTTRANS_SECTION;
23
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.POSTUN_SECTION;
24
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.POST_SECTION;
25
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PREP_SECTION;
26
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PRETRANS_SECTION;
27
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PREUN_SECTION;
28
import static org.eclipse.linuxtools.internal.rpm.ui.editor.RpmSections.PRE_SECTION;
30
import java.io.IOException;
31
import java.io.LineNumberReader;
32
import java.io.StringReader;
33
import java.util.Arrays;
34
import java.util.Iterator;
35
import java.util.List;
37
import org.eclipse.core.resources.IMarker;
38
import org.eclipse.jface.preference.IPreferenceStore;
39
import org.eclipse.jface.text.Document;
40
import org.eclipse.jface.text.IDocument;
41
import org.eclipse.linuxtools.internal.rpm.ui.editor.Activator;
42
import org.eclipse.linuxtools.internal.rpm.ui.editor.ISpecfileSpecialSymbols;
43
import org.eclipse.linuxtools.internal.rpm.ui.editor.RpmTags;
44
import org.eclipse.linuxtools.internal.rpm.ui.editor.SpecfileLog;
45
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.Messages;
46
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileParseException;
47
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfilePatchMacro;
48
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileSource;
49
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileSource.SourceType;
50
import org.eclipse.linuxtools.internal.rpm.ui.editor.parser.SpecfileTag;
51
import org.eclipse.linuxtools.internal.rpm.ui.editor.preferences.PreferenceConstants;
52
import org.eclipse.linuxtools.rpm.ui.editor.markers.SpecfileErrorHandler;
53
import org.eclipse.linuxtools.rpm.ui.editor.markers.SpecfileTaskHandler;
55
public class SpecfileParser {
57
private static final String DEFINE_SEPARATOR = ":"; //$NON-NLS-1$
59
private static final String SPACE_REGEX = "\\s+"; //$NON-NLS-1$
62
* These are SRPM-wide sections, and they also cannot have any flags like -n
63
* or -f. Hence they are called simple. This is probably a misleading name
64
* and it should be renamed to reflect that they are SRPM-wide sections.
66
public static final String[] simpleSections = { PREP_SECTION, BUILD_SECTION,
67
INSTALL_SECTION, CLEAN_SECTION, CHANGELOG_SECTION };
70
* These are sections that apply to a particular sub-package (i.e. binary
71
* RPM), including the main package. These can also have flags like -f or -n
72
* appended to them, hence they are called complex. This should probably be
73
* renamed to reflect that they are in fact per-RPM sections.
75
private static String[] complexSections = { PRETRANS_SECTION, PRE_SECTION,
76
PREUN_SECTION, POST_SECTION, POSTUN_SECTION, POSTTRANS_SECTION,
77
FILES_SECTION, PACKAGE_SECTION, DESCRIPTION_SECTION };
79
private static String[] simpleDefinitions = { RpmTags.EPOCH, RpmTags.NAME,
80
RpmTags.VERSION, RpmTags.RELEASE, RpmTags.URL, RpmTags.BUILD_ARCH };
82
private static String[] directValuesDefinitions = { RpmTags.LICENSE,
84
// Note that the ordering here should match that in
85
// SpecfileSource#SOURCETYPE
86
private static String[] complexDefinitions = { "Source", "Patch" }; //$NON-NLS-1$ //$NON-NLS-2$
88
private static String[] packageLevelDefinitions = { RpmTags.SUMMARY,
89
RpmTags.GROUP, RpmTags.OBSOLETES, RpmTags.PROVIDES,
90
RpmTags.REQUIRES, RpmTags.REQUIRES_PRE, RpmTags.REQUIRES_POST,
91
RpmTags.REQUIRES_POSTUN };
93
private SpecfileErrorHandler errorHandler;
94
private SpecfileTaskHandler taskHandler;
95
private IPreferenceStore store;
96
private SpecfileSection lastSection;
97
private SpecfilePackage activePackage;
99
public SpecfileParser() {
100
store = Activator.getDefault().getPreferenceStore();
103
public Specfile parse(IDocument specfileDocument) {
105
// remove all existing markers, if a SpecfileErrorHandler is
107
if (errorHandler != null)
108
errorHandler.removeExistingMarkers();
109
if (taskHandler != null) {
110
taskHandler.removeExistingMarkers();
112
LineNumberReader reader = new LineNumberReader(new StringReader(
113
specfileDocument.get()));
114
String line = ""; //$NON-NLS-1$
115
int lineStartPosition = 0;
116
Specfile specfile = new Specfile();
117
specfile.setDocument(specfileDocument);
119
while ((line = reader.readLine()) != null) {
120
if (taskHandler != null) {
121
generateTaskMarker(reader.getLineNumber() - 1, line);
123
// IDocument.getLine(#) is 0-indexed whereas
124
// reader.getLineNumber appears to be 1-indexed
125
SpecfileElement element = parseLine(line, specfile, reader
126
.getLineNumber() - 1);
127
if (element != null) {
128
element.setLineNumber(reader.getLineNumber() - 1);
129
element.setLineStartPosition(lineStartPosition);
130
element.setLineEndPosition(lineStartPosition
132
if (element.getClass() == SpecfileTag.class) {
133
SpecfileTag tag = (SpecfileTag) element;
134
specfile.addDefine(tag);
135
} else if ((element.getClass() == SpecfilePatchMacro.class)) {
136
SpecfilePatchMacro thisPatchMacro = (SpecfilePatchMacro) element;
137
if (thisPatchMacro != null) {
138
thisPatchMacro.setSpecfile(specfile);
140
SpecfileSource thisPatch = specfile
141
.getPatch(thisPatchMacro.getPatchNumber());
142
if (thisPatch != null) {
143
thisPatch.addLineUsed(reader.getLineNumber() - 1);
144
thisPatch.setSpecfile(specfile);
146
} else if ((element.getClass() == SpecfileDefine.class)) {
147
specfile.addDefine((SpecfileDefine) element);
148
} else if ((element.getClass() == SpecfileSource.class)) {
149
SpecfileSource source = (SpecfileSource) element;
151
source.setLineNumber(reader.getLineNumber() - 1);
152
if (source.getSourceType() == SpecfileSource.SourceType.SOURCE) {
153
specfile.addSource(source);
155
specfile.addPatch(source);
159
// The +1 is for the line delimiter. FIXME: will we end up off
160
// by one on the last line?
161
lineStartPosition += line.length() + 1;
163
} catch (IOException e) {
165
SpecfileLog.logError(e);
170
private void generateTaskMarker(int lineNumber, String line) {
171
String[] taskTags = store.getString(PreferenceConstants.P_TASK_TAGS)
172
.split(";"); //$NON-NLS-1$
173
int commentCharIndex = line
174
.indexOf(ISpecfileSpecialSymbols.COMMENT_START);
175
if (commentCharIndex > -1) {
176
for (String item : taskTags) {
177
int taskIndex = line.indexOf(item);
178
if (taskIndex > commentCharIndex) {
179
taskHandler.handleTask(lineNumber, line, item);
185
public Specfile parse(String specfileContent) {
186
return parse(new Document(specfileContent));
189
public SpecfileElement parseLine(String lineText, Specfile specfile,
192
if (lineText.startsWith("%")) //$NON-NLS-1$
193
return parseMacro(lineText, specfile, lineNumber);
195
for (String simpleDefinition : simpleDefinitions) {
196
if (lineText.startsWith(simpleDefinition + DEFINE_SEPARATOR)) {
197
return parseSimpleDefinition(lineText, specfile, lineNumber,
201
for (String directValuesDefinition : directValuesDefinitions) {
202
if (lineText.startsWith(directValuesDefinition + DEFINE_SEPARATOR)) {
203
return parseDirectDefinition(lineText, specfile, lineNumber);
206
for (String directValuesDefinition : packageLevelDefinitions) {
207
if (lineText.startsWith(directValuesDefinition + DEFINE_SEPARATOR)) {
208
SpecfileElement definition = parseDirectDefinition(lineText,
209
specfile, lineNumber);
210
if (directValuesDefinition.equals(RpmTags.REQUIRES)) {
211
if (activePackage != null) {
212
activePackage.addRequire((SpecfileTag) definition);
214
specfile.addRequire((SpecfileTag) definition);
221
if (lineText.startsWith(complexDefinitions[0])
222
&& lineText.contains(DEFINE_SEPARATOR)) {
223
return parseComplexDefinition(lineText, lineNumber,
225
} else if (lineText.startsWith(complexDefinitions[1])
226
&& lineText.contains(DEFINE_SEPARATOR)) {
227
return parseComplexDefinition(lineText, lineNumber,
229
} else if (lineText.startsWith("BuildRequires")) { //$NON-NLS-1$
230
return parseBuildRequire(lineText, lineNumber, specfile);
236
private SpecfileElement parseBuildRequire(String lineText, int lineNumber,
238
String value = lineText.substring(lineText.indexOf(':') + 1,
239
lineText.length()).trim();
240
SpecfileDefine buildRequire = new SpecfileDefine(
241
"BuildRequires", value, specfile, null); //$NON-NLS-1$
242
buildRequire.setLineNumber(lineNumber);
243
specfile.addBuildRequire(buildRequire);
247
private SpecfileSection parseSection(String lineText, Specfile specfile,
249
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
250
SpecfileSection toReturn = null;
251
boolean isSimpleSection = false;
252
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
253
String token = iter.next();
256
// Simple Section Headers
257
for (String simpleSection : simpleSections) {
258
if (token.equals(simpleSection)) {
259
toReturn = new SpecfileSection(token.substring(1), specfile);
260
specfile.addSection(toReturn);
261
isSimpleSection = true;
266
// Complex Section Headers
267
for (String complexSection : complexSections) {
268
if (token.equals(complexSection)) {
269
String name = token.substring(1);
270
if (!name.equals("package")) { //$NON-NLS-1$
271
toReturn = new SpecfileSection(name, specfile);
272
specfile.addComplexSection(toReturn);
274
while (iter.hasNext()) {
275
String nextToken = iter.next();
276
if (nextToken.equals("-n")) { //$NON-NLS-1$
277
if (!iter.hasNext()) {
279
.handleError(new SpecfileParseException(
281
.getString("SpecfileParser.1") //$NON-NLS-1$
284
.getString("SpecfileParser.2"), //$NON-NLS-1$
285
lineNumber, 0, lineText
287
IMarker.SEVERITY_ERROR));
291
nextToken = iter.next();
292
if (nextToken.startsWith("-")) { //$NON-NLS-1$
294
.handleError(new SpecfileParseException(
296
.getString("SpecfileParser.3") //$NON-NLS-1$
299
.getString("SpecfileParser.4"), //$NON-NLS-1$
300
lineNumber, 0, lineText
302
IMarker.SEVERITY_ERROR));
305
} else if (nextToken.equals("-p")) { //$NON-NLS-1$
306
// FIXME: rest of line is the actual section
308
} else if (nextToken.equals("-f")) { //$NON-NLS-1$
313
if (toReturn == null) {
314
SpecfilePackage tmpPackage = specfile
315
.getPackage(nextToken);
317
if (tmpPackage == null) {
318
tmpPackage = new SpecfilePackage(nextToken,
320
specfile.addPackage(tmpPackage);
322
activePackage = tmpPackage;
326
// this is another section
327
SpecfilePackage enclosingPackage = specfile
328
.getPackage(nextToken);
329
if (enclosingPackage == null) {
330
enclosingPackage = new SpecfilePackage(nextToken,
332
specfile.addPackage(enclosingPackage);
334
toReturn.setPackage(enclosingPackage);
335
enclosingPackage.addSection(toReturn);
341
// if this package is part of the top level package, add it to
343
if (toReturn != null && toReturn.getPackage() == null) {
344
SpecfilePackage topPackage = specfile
345
.getPackage(specfile.getName());
346
if (topPackage == null) {
347
topPackage = new SpecfilePackage(specfile.getName(), specfile);
348
specfile.addPackage(topPackage);
350
if (!isSimpleSection) {
351
topPackage.addSection(toReturn);
354
if (lastSection != null) {
355
lastSection.setSectionEndLine(lineNumber);
360
private SpecfileElement parseMacro(String lineText, Specfile specfile,
362
// FIXME: handle other macros
364
if (lineText.startsWith("%define") || lineText.startsWith("%global")) { //$NON-NLS-1$ //$NON-NLS-2$
365
return parseDefine(lineText, specfile, lineNumber);
366
} else if (lineText.startsWith("%patch")) { //$NON-NLS-1$
367
return parsePatch(lineText, lineNumber);
370
String[] sections = new String[simpleSections.length
371
+ complexSections.length];
372
System.arraycopy(simpleSections, 0, sections, 0, simpleSections.length);
373
System.arraycopy(complexSections, 0, sections, simpleSections.length,
374
complexSections.length);
375
for (String section : sections) {
376
if (lineText.startsWith(section)) {
377
lastSection = parseSection(lineText, specfile, lineNumber);
378
if (lastSection != null)
379
lastSection.setSectionEndLine(lineNumber + 1);
383
// FIXME: add handling of lines containing %{SOURCENNN}
387
private SpecfileElement parsePatch(String lineText, int lineNumber) {
389
SpecfilePatchMacro toReturn = null;
391
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
393
for (String token : tokens) {
396
if (token.startsWith("%patch")) { //$NON-NLS-1$
398
if (token.length() > 6) {
399
patchNumber = Integer.parseInt(token.substring(6));
401
toReturn = new SpecfilePatchMacro(patchNumber);
403
} catch (NumberFormatException e) {
404
errorHandler.handleError(new SpecfileParseException(
405
Messages.getString("SpecfileParser.5"), //$NON-NLS-1$
406
lineNumber, 0, lineText.length(),
407
IMarker.SEVERITY_ERROR));
415
private SpecfileDefine parseDefine(String lineText, Specfile specfile,
417
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
418
SpecfileDefine toReturn = null;
419
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
420
// Eat the actual "%define" or "%global" token
422
while (iter.hasNext()) {
423
String defineName = iter.next();
424
// FIXME: is this true? investigate in rpmbuild source
425
// Definitions must being with a letter
426
if (!Character.isLetter(defineName.charAt(0))
427
&& (defineName.charAt(0) != '_')) {
428
errorHandler.handleError(new SpecfileParseException(
429
Messages.getString("SpecfileParser.6"), //$NON-NLS-1$
430
lineNumber, 0, lineText.length(),
431
IMarker.SEVERITY_ERROR));
434
if (iter.hasNext()) {
435
String defineStringValue = iter.next();
436
// Defines that are more than one token
437
if (iter.hasNext()) {
438
defineStringValue = lineText.substring(lineText
439
.indexOf(defineStringValue));
440
// Eat up the rest of the tokens
441
while (iter.hasNext())
444
int defineIntValue = -1;
446
defineIntValue = Integer
447
.parseInt(defineStringValue);
448
} catch (NumberFormatException e) {
449
toReturn = new SpecfileDefine(defineName,
450
defineStringValue, specfile, null);
452
if (toReturn == null)
453
toReturn = new SpecfileDefine(defineName,
454
defineIntValue, specfile, null);
456
errorHandler.handleError(new SpecfileParseException(defineName+
457
Messages.getString("SpecfileParser.14"), //$NON-NLS-1$
458
lineNumber, 0, lineText.length(),
459
IMarker.SEVERITY_ERROR));
467
private SpecfileElement parseComplexDefinition(String lineText,
468
int lineNumber, SourceType sourceType) {
469
SpecfileSource toReturn = null;
470
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
472
boolean firstToken = true;
474
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
475
String token = iter.next();
476
if (token != null && token.length() > 0) {
478
if (token.endsWith(DEFINE_SEPARATOR)) {
479
token = token.substring(0, token.length() - 1);
481
// FIXME: come up with a better error message here
482
// FIXME: what about descriptions that begin a line with
483
// the word "Source" or "Patch"?
484
errorHandler.handleError(new SpecfileParseException(
485
Messages.getString("SpecfileParser.8"), //$NON-NLS-1$
486
lineNumber, 0, lineText.length(),
487
IMarker.SEVERITY_WARNING));
490
if (sourceType == SourceType.PATCH) {
491
if (token.length() > 5) {
492
number = Integer.parseInt(token.substring(5));
493
if (!("patch" + number).equalsIgnoreCase(token)) { //$NON-NLS-1$
495
.handleError(new SpecfileParseException(
497
.getString("SpecfileParser.10"), //$NON-NLS-1$
498
lineNumber, 0, lineText
500
IMarker.SEVERITY_ERROR));
507
if (token.length() > 6) {
508
number = Integer.parseInt(token.substring(6));
509
if (!("source" + number).equalsIgnoreCase(token)) { //$NON-NLS-1$
511
.handleError(new SpecfileParseException(
513
.getString("SpecfileParser.11"), //$NON-NLS-1$
514
lineNumber, 0, lineText
516
IMarker.SEVERITY_ERROR));
523
toReturn = new SpecfileSource(number, ""); //$NON-NLS-1$
524
toReturn.setSourceType(sourceType);
527
// toReturn should never be null but check just in case
528
if (toReturn != null)
529
toReturn.setFileName(token);
530
if (iter.hasNext()) {
531
errorHandler.handleError(new SpecfileParseException(
532
Messages.getString("SpecfileParser.12"), //$NON-NLS-1$
533
lineNumber, 0, lineText.length(),
534
IMarker.SEVERITY_ERROR));
543
private SpecfileElement parseSimpleDefinition(String lineText,
544
Specfile specfile, int lineNumber, boolean warnMultipleValues) {
545
List<String> tokens = Arrays.asList(lineText.split(SPACE_REGEX));
546
SpecfileTag toReturn = null;
548
for (Iterator<String> iter = tokens.iterator(); iter.hasNext();) {
549
String token = iter.next();
551
if (token.length() <= 0) {
555
if (iter.hasNext()) {
556
String possValue = iter.next();
557
if (possValue.startsWith("%") && iter.hasNext()) { //$NON-NLS-1$
558
possValue += ' ' + iter.next();
560
toReturn = new SpecfileTag(token.substring(0,
561
token.length() - 1).toLowerCase(), possValue, specfile,
563
if (iter.hasNext() && !warnMultipleValues) {
564
errorHandler.handleError(new SpecfileParseException(
565
token.substring(0, token.length() - 1)
566
+ Messages.getString("SpecfileParser.13"), //$NON-NLS-1$
567
lineNumber, 0, lineText.length(),
568
IMarker.SEVERITY_ERROR));
572
errorHandler.handleError(new SpecfileParseException(token
573
.substring(0, token.length() - 1)
574
+ Messages.getString("SpecfileParser.14"), lineNumber, //$NON-NLS-1$
575
0, lineText.length(), IMarker.SEVERITY_ERROR));
579
if ((toReturn != null) && (toReturn.getStringValue() != null)) {
581
int intValue = Integer.parseInt(toReturn.getStringValue());
582
toReturn.setValue(intValue);
583
} catch (NumberFormatException e) {
584
if (toReturn.getName().equalsIgnoreCase(RpmTags.EPOCH)) {
586
.handleError(new SpecfileParseException(
587
Messages.getString("SpecfileParser.16"), lineNumber, //$NON-NLS-1$
588
0, lineText.length(),
589
IMarker.SEVERITY_ERROR));
597
private SpecfileElement parseDirectDefinition(String lineText,
598
Specfile specfile, int lineNumber) {
599
String[] parts = lineText.split(DEFINE_SEPARATOR, 2);
600
SpecfileTag directDefinition;
601
if (parts.length == 2) {
602
directDefinition = new SpecfileTag(parts[0], parts[1].trim(),
603
specfile, activePackage);
604
directDefinition.setLineNumber(lineNumber);
606
errorHandler.handleError(new SpecfileParseException(parts[0]
607
+ Messages.getString("SpecfileParser.14"), lineNumber, //$NON-NLS-1$
608
0, lineText.length(), IMarker.SEVERITY_ERROR));
609
directDefinition = null;
611
return directDefinition;
614
public void setErrorHandler(SpecfileErrorHandler specfileErrorHandler) {
615
errorHandler = specfileErrorHandler;
618
public void setTaskHandler(SpecfileTaskHandler specfileTaskHandler) {
619
taskHandler = specfileTaskHandler;