@@ -4,10 +4,11 @@ import 'types/commit.dart';
44/// Parse Commit Message String to Convensional Commit
55///
66
7- const _kDefaultHeaderPattern = r'^(\w*)(?:\((.*)\))?: (.*)$' ;
8- const _kDefaultHeaderCorrespondence = ['type' , 'scope' , 'subject' ];
7+ final _kHeaderPattern =
8+ RegExp (r'^(?<type>\w*?)(\((?<scope>.*)\))?!?: (?<subject>.+)$' );
9+ const _kHeaderCorrespondence = ['type' , 'scope' , 'subject' ];
910
10- const _kDefaultReferenceActions = [
11+ const _kReferenceActions = [
1112 'close' ,
1213 'closes' ,
1314 'closed' ,
@@ -19,73 +20,75 @@ const _kDefaultReferenceActions = [
1920 'resolved'
2021];
2122
22- const _kDefaultIssuePrefixes = ['#' ];
23- const _kDefaultNoteKeywords = ['BREAKING CHANGE' , 'BREAKING-CHANGE' ];
24-
25- const _kDefaultFieldPattern = r'^-(.*?)-$' ;
26-
27- const _kDefaultRevertPattern =
28- r'^(?:Revert|revert:)\s"?([\s\S]+?)"?\s*This reverts commit (\w*)\.' ;
29- const _kDefaultRevertCorrespondence = ['header' , 'hash' ];
30-
31- Commit parse (String raw,
32- {String headerPattern = _kDefaultHeaderPattern,
33- List <String > headerCorrespondence = _kDefaultHeaderCorrespondence,
34- List <String > referenceActions = _kDefaultReferenceActions,
35- List <String > issuePrefixes = _kDefaultIssuePrefixes,
36- List <String > noteKeywords = _kDefaultNoteKeywords,
37- String fieldPattern = _kDefaultFieldPattern,
38- String revertPattern = _kDefaultRevertPattern,
39- List <String > revertCorrespondence = _kDefaultRevertCorrespondence,
40- String ? commentChar}) {
41- final message = raw.trim ();
42- if (message.isEmpty) {
43- throw ArgumentError .value (raw, 'raw message' , 'must have content.' );
23+ const _kIssuePrefixes = ['#' ];
24+ const _kNoteKeywords = ['BREAKING CHANGE' , 'BREAKING-CHANGE' ];
25+ final _kMergePattern = RegExp (r'^(Merge|merge)\s(.*)$' );
26+ final _kRevertPattern = RegExp (
27+ r'^(?:Revert|revert:)\s"?(?<header>[\s\S]+?)"?\s*This reverts commit (?<hash>\w*)\.' );
28+ const _kRevertCorrespondence = ['header' , 'hash' ];
29+
30+ final _kMentionsPattern = RegExp (r'@([\w-]+)' );
31+
32+ Commit parse (String raw) {
33+ if (raw.trim ().isEmpty) {
34+ throw ArgumentError .value (raw, null , 'message raw must have content.' );
4435 }
4536 String ? body;
4637 String ? footer;
4738 List <String > mentions = [];
4839 List <CommitNote > notes = [];
4940 List <CommitReference > references = [];
5041 Map <String , String ?>? revert;
51- final lines = truncateToScissor (message.split (RegExp (r'\r?\n' )))
52- .where (_gpgFilter)
53- .toList ();
54- if (commentChar != null ) {
55- lines.removeWhere ((line) => line.startsWith (commentChar));
42+ String ? merge;
43+ String ? header;
44+ final rawLines = _trimOffNewlines (raw).split (RegExp (r'\r?\n' ));
45+ final lines = _truncateToScissor (rawLines).where (_gpgFilter).toList ();
46+ merge = lines.removeAt (0 );
47+ final mergeMatch = _kMergePattern.firstMatch (merge);
48+ if (mergeMatch != null ) {
49+ merge = mergeMatch.group (0 );
50+ if (lines.isNotEmpty) {
51+ header = lines.removeAt (0 );
52+ while (header! .trim ().isEmpty && lines.isNotEmpty) {
53+ header = lines.removeAt (0 );
54+ }
55+ }
56+ header ?? = '' ;
57+ } else {
58+ header = merge;
59+ merge = null ;
5660 }
57- final header = lines.removeAt (0 );
58- final headerMatch = RegExp (headerPattern).matchAsPrefix (header);
61+ final headerMatch = _kHeaderPattern.firstMatch (header);
5962 final headerParts = < String , String ? > {};
6063 if (headerMatch != null ) {
61- for (var i = 0 ; i < headerCorrespondence.length; i ++ ) {
62- headerParts[headerCorrespondence[i]] = headerMatch.group (i + 1 );
64+ for (var name in _kHeaderCorrespondence ) {
65+ headerParts[name] = headerMatch.namedGroup (name );
6366 }
6467 }
65- final referencesPattern = getReferenceRegex (referenceActions );
66- final referencePartsPattern = getReferencePartsRegex (issuePrefixes , false );
67- references.addAll (getReferences (header,
68+ final referencesPattern = _getReferenceRegex (_kReferenceActions );
69+ final referencePartsPattern = _getReferencePartsRegex (_kIssuePrefixes , false );
70+ references.addAll (_getReferences (header,
6871 referencesPattern: referencesPattern,
6972 referencePartsPattern: referencePartsPattern));
7073
7174 bool continueNote = false ;
7275 bool isBody = true ;
73- final notesPattern = getNotesRegex (noteKeywords );
76+ final notesPattern = _getNotesRegex (_kNoteKeywords );
7477
7578 /// body or footer
7679 for (var line in lines) {
7780 bool referenceMatched = false ;
78- final notesMatch = notesPattern.matchAsPrefix (line);
81+ final notesMatch = notesPattern.firstMatch (line);
7982 if (notesMatch != null ) {
8083 continueNote = true ;
8184 isBody = false ;
82- footer = append (footer, line);
83- notes.add (CommitNote (
84- title: notesMatch.group (1 )! , text: notesMatch.group (2 )! . trim () ));
85- break ;
85+ footer = _append (footer, line);
86+ notes.add (
87+ CommitNote ( title: notesMatch.group (1 )! , text: notesMatch.group (2 )! ));
88+ continue ;
8689 }
8790
88- final lineReferences = getReferences (
91+ final lineReferences = _getReferences (
8992 line,
9093 referencesPattern: referencesPattern,
9194 referencePartsPattern: referencePartsPattern,
@@ -95,58 +98,72 @@ Commit parse(String raw,
9598 isBody = false ;
9699 referenceMatched = true ;
97100 continueNote = false ;
101+ references.addAll (lineReferences);
98102 }
99103
100- references.addAll (lineReferences);
101-
102104 if (referenceMatched) {
103- footer = append (footer, line);
104- break ;
105+ footer = _append (footer, line);
106+ continue ;
105107 }
106108
107109 if (continueNote) {
108- notes.last.text = append (notes.last.text, line). trim ( );
109- footer = append (footer, line);
110- break ;
110+ notes.last.text = _append (notes.last.text, line);
111+ footer = _append (footer, line);
112+ continue ;
111113 }
112114 if (isBody) {
113- body = append (body, line);
115+ body = _append (body, line);
114116 } else {
115- footer = append (footer, line);
117+ footer = _append (footer, line);
116118 }
117119 }
118120
119- Match ? mentionsMatch;
120- final mentionsPattern = RegExp (r'@([\w-]+)' );
121- while ((mentionsMatch =
122- mentionsPattern.matchAsPrefix (raw, mentionsMatch? .end ?? 0 )) !=
123- null ) {
124- mentions.add (mentionsMatch! .group (1 )! );
121+ Match ? mentionsMatch = _kMentionsPattern.firstMatch (raw);
122+ while (mentionsMatch != null ) {
123+ mentions.add (mentionsMatch.group (1 )! );
124+ mentionsMatch = _kMentionsPattern.matchAsPrefix (raw, mentionsMatch.end);
125125 }
126126
127127 // does this commit revert any other commit?
128- final revertMatch = raw. matchAsPrefix (revertPattern );
128+ final revertMatch = _kRevertPattern. firstMatch (raw );
129129 if (revertMatch != null ) {
130130 revert = {};
131- for (var i = 0 ; i < revertCorrespondence .length; i++ ) {
132- revert[revertCorrespondence [i]] = revertMatch.group (i + 1 );
131+ for (var i = 0 ; i < _kRevertCorrespondence .length; i++ ) {
132+ revert[_kRevertCorrespondence [i]] = revertMatch.group (i + 1 );
133133 }
134134 }
135135
136+ for (var note in notes) {
137+ note.text = _trimOffNewlines (note.text);
138+ }
136139 return Commit (
140+ revert: revert,
141+ merge: merge,
137142 header: header,
138143 type: headerParts['type' ],
139144 scope: headerParts['scope' ],
140145 subject: headerParts['subject' ],
141- body: body? . trim () ,
142- footer: footer? . trim () ,
146+ body: body != null ? _trimOffNewlines (body) : null ,
147+ footer: footer != null ? _trimOffNewlines (footer) : null ,
143148 notes: notes,
144149 references: references,
145150 mentions: mentions,
146- revert: revert,
147151 );
148152}
149153
154+ String _trimOffNewlines (String input) {
155+ final result = RegExp (r'[^\r\n]' ).firstMatch (input);
156+ if (result == null ) {
157+ return '' ;
158+ }
159+ final firstIndex = result.start;
160+ var lastIndex = input.length - 1 ;
161+ while (input[lastIndex] == '\r ' || input[lastIndex] == '\n ' ) {
162+ lastIndex-- ;
163+ }
164+ return input.substring (firstIndex, lastIndex + 1 );
165+ }
166+
150167bool _gpgFilter (String line) {
151168 return ! RegExp (r'^\s*gpg:' ).hasMatch (line);
152169}
@@ -155,7 +172,7 @@ final _kMatchAll = RegExp(r'()(.+)', caseSensitive: false);
155172
156173const _kScissor = '# ------------------------ >8 ------------------------' ;
157174
158- List <String > truncateToScissor (List <String > lines) {
175+ List <String > _truncateToScissor (List <String > lines) {
159176 final scissorIndex = lines.indexOf (_kScissor);
160177
161178 if (scissorIndex == - 1 ) {
@@ -165,48 +182,46 @@ List<String> truncateToScissor(List<String> lines) {
165182 return lines.sublist (0 , scissorIndex);
166183}
167184
168- List <CommitReference > getReferences (
185+ List <CommitReference > _getReferences (
169186 String input, {
170- required Pattern referencesPattern,
171- required Pattern referencePartsPattern,
187+ required RegExp referencesPattern,
188+ required RegExp referencePartsPattern,
172189}) {
173190 final references = < CommitReference > [];
174- Match ? referenceSentences;
175- Match ? referenceMatch;
176-
177- final reApplicable = referencesPattern.allMatches (input).isNotEmpty
178- ? referencesPattern
179- : _kMatchAll;
180- while ((referenceSentences =
181- reApplicable.matchAsPrefix (input, referenceSentences? .end ?? 0 )) !=
182- null ) {
183- final action = referenceSentences! .group (1 )! ;
184- final sentence = referenceSentences.group (2 )! ;
185- while ((referenceMatch = referencePartsPattern.matchAsPrefix (
186- sentence, referenceMatch? .end ?? 0 )) !=
187- null ) {
191+ final reApplicable =
192+ referencesPattern.hasMatch (input) ? referencesPattern : _kMatchAll;
193+ Match ? referenceSentences = reApplicable.firstMatch (input);
194+ while (referenceSentences != null ) {
195+ final action = referenceSentences.group (1 );
196+ final sentence = referenceSentences.group (2 );
197+ Match ? referenceMatch = referencePartsPattern.firstMatch (sentence! );
198+ while (referenceMatch != null ) {
188199 String ? owner;
189- String ? repository = referenceMatch! .group (1 );
200+ String ? repository = referenceMatch.group (1 );
190201 final ownerRepo = repository? .split ('/' ) ?? [];
191202
192203 if (ownerRepo.length > 1 ) {
193204 owner = ownerRepo.removeAt (0 );
194205 repository = ownerRepo.join ('/' );
195206 }
196207 references.add (CommitReference (
197- raw: referenceMatch.group (0 )! ,
198208 action: action,
199209 owner: owner,
200210 repository: repository,
201211 issue: referenceMatch.group (3 ),
212+ raw: referenceMatch.group (0 )! ,
202213 prefix: referenceMatch.group (2 )! ,
203214 ));
215+ referenceMatch =
216+ referencePartsPattern.matchAsPrefix (sentence, referenceMatch.end);
204217 }
218+ referenceSentences =
219+ reApplicable.matchAsPrefix (input, referenceSentences.end);
205220 }
206221 return references;
207222}
208223
209- Pattern getReferenceRegex (Iterable <String > referenceActions) {
224+ RegExp _getReferenceRegex (Iterable <String > referenceActions) {
210225 if (referenceActions.isEmpty) {
211226 // matches everything
212227 return RegExp (r'()(.+)' , caseSensitive: false ); //gi
@@ -217,7 +232,7 @@ Pattern getReferenceRegex(Iterable<String> referenceActions) {
217232 caseSensitive: false );
218233}
219234
220- Pattern getReferencePartsRegex (
235+ RegExp _getReferencePartsRegex (
221236 List <String > issuePrefixes, bool issuePrefixesCaseSensitive) {
222237 if (issuePrefixes.isEmpty) {
223238 return RegExp (r'(?!.*)' );
@@ -227,17 +242,19 @@ Pattern getReferencePartsRegex(
227242 caseSensitive: issuePrefixesCaseSensitive);
228243}
229244
230- Pattern getNotesRegex (List <String > noteKeywords) {
245+ RegExp _getNotesRegex (List <String > noteKeywords) {
231246 if (noteKeywords.isEmpty) {
232247 return RegExp (r'(?!.*)' );
233248 }
234249 final noteKeywordsSelection = noteKeywords.join ('|' );
235- return RegExp ('^[\\ s|*]*($noteKeywordsSelection )[:\\ s]+(.*)' ,
236- caseSensitive: false );
250+ return RegExp (
251+ '^[\\ s|*]*($noteKeywordsSelection )[:\\ s]+(.*)' ,
252+ caseSensitive: false ,
253+ );
237254}
238255
239- String append (String ? src, String line) {
240- if (src != null ) {
256+ String _append (String ? src, String line) {
257+ if (src != null && src.isNotEmpty ) {
241258 return '$src \n $line ' ;
242259 } else {
243260 return line;
0 commit comments