@@ -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,78 @@ 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, {String ? commentChar}) {
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 ();
42+ String ? merge;
43+ String ? header;
44+ final rawLines = _trimOffNewlines (raw).split (RegExp (r'\r?\n' ));
45+ final lines = _truncateToScissor (rawLines).where (_gpgFilter).toList ();
5446 if (commentChar != null ) {
5547 lines.removeWhere ((line) => line.startsWith (commentChar));
5648 }
57- final header = lines.removeAt (0 );
58- final headerMatch = RegExp (headerPattern).matchAsPrefix (header);
49+ merge = lines.removeAt (0 );
50+ final mergeMatch = _kMergePattern.firstMatch (merge);
51+ if (mergeMatch != null ) {
52+ merge = mergeMatch.group (0 );
53+ if (lines.isNotEmpty) {
54+ header = lines.removeAt (0 );
55+ while (header! .trim ().isEmpty && lines.isNotEmpty) {
56+ header = lines.removeAt (0 );
57+ }
58+ }
59+ header ?? = '' ;
60+ } else {
61+ header = merge;
62+ merge = null ;
63+ }
64+ final headerMatch = _kHeaderPattern.firstMatch (header);
5965 final headerParts = < String , String ? > {};
6066 if (headerMatch != null ) {
61- for (var i = 0 ; i < headerCorrespondence.length; i ++ ) {
62- headerParts[headerCorrespondence[i]] = headerMatch.group (i + 1 );
67+ for (var name in _kHeaderCorrespondence ) {
68+ headerParts[name] = headerMatch.namedGroup (name );
6369 }
6470 }
65- final referencesPattern = getReferenceRegex (referenceActions );
66- final referencePartsPattern = getReferencePartsRegex (issuePrefixes , false );
67- references.addAll (getReferences (header,
71+ final referencesPattern = _getReferenceRegex (_kReferenceActions );
72+ final referencePartsPattern = _getReferencePartsRegex (_kIssuePrefixes , false );
73+ references.addAll (_getReferences (header,
6874 referencesPattern: referencesPattern,
6975 referencePartsPattern: referencePartsPattern));
7076
7177 bool continueNote = false ;
7278 bool isBody = true ;
73- final notesPattern = getNotesRegex (noteKeywords );
79+ final notesPattern = _getNotesRegex (_kNoteKeywords );
7480
7581 /// body or footer
7682 for (var line in lines) {
7783 bool referenceMatched = false ;
78- final notesMatch = notesPattern.matchAsPrefix (line);
84+ final notesMatch = notesPattern.firstMatch (line);
7985 if (notesMatch != null ) {
8086 continueNote = true ;
8187 isBody = false ;
82- footer = append (footer, line);
83- notes.add (CommitNote (
84- title: notesMatch.group (1 )! , text: notesMatch.group (2 )! . trim () ));
85- break ;
88+ footer = _append (footer, line);
89+ notes.add (
90+ CommitNote ( title: notesMatch.group (1 )! , text: notesMatch.group (2 )! ));
91+ continue ;
8692 }
8793
88- final lineReferences = getReferences (
94+ final lineReferences = _getReferences (
8995 line,
9096 referencesPattern: referencesPattern,
9197 referencePartsPattern: referencePartsPattern,
@@ -95,58 +101,72 @@ Commit parse(String raw,
95101 isBody = false ;
96102 referenceMatched = true ;
97103 continueNote = false ;
104+ references.addAll (lineReferences);
98105 }
99106
100- references.addAll (lineReferences);
101-
102107 if (referenceMatched) {
103- footer = append (footer, line);
104- break ;
108+ footer = _append (footer, line);
109+ continue ;
105110 }
106111
107112 if (continueNote) {
108- notes.last.text = append (notes.last.text, line). trim ( );
109- footer = append (footer, line);
110- break ;
113+ notes.last.text = _append (notes.last.text, line);
114+ footer = _append (footer, line);
115+ continue ;
111116 }
112117 if (isBody) {
113- body = append (body, line);
118+ body = _append (body, line);
114119 } else {
115- footer = append (footer, line);
120+ footer = _append (footer, line);
116121 }
117122 }
118123
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 )! );
124+ Match ? mentionsMatch = _kMentionsPattern.firstMatch (raw);
125+ while (mentionsMatch != null ) {
126+ mentions.add (mentionsMatch.group (1 )! );
127+ mentionsMatch = _kMentionsPattern.matchAsPrefix (raw, mentionsMatch.end);
125128 }
126129
127130 // does this commit revert any other commit?
128- final revertMatch = raw. matchAsPrefix (revertPattern );
131+ final revertMatch = _kRevertPattern. firstMatch (raw );
129132 if (revertMatch != null ) {
130133 revert = {};
131- for (var i = 0 ; i < revertCorrespondence .length; i++ ) {
132- revert[revertCorrespondence [i]] = revertMatch.group (i + 1 );
134+ for (var i = 0 ; i < _kRevertCorrespondence .length; i++ ) {
135+ revert[_kRevertCorrespondence [i]] = revertMatch.group (i + 1 );
133136 }
134137 }
135138
139+ for (var note in notes) {
140+ note.text = _trimOffNewlines (note.text);
141+ }
136142 return Commit (
143+ revert: revert,
144+ merge: merge,
137145 header: header,
138146 type: headerParts['type' ],
139147 scope: headerParts['scope' ],
140148 subject: headerParts['subject' ],
141- body: body? . trim () ,
142- footer: footer? . trim () ,
149+ body: body != null ? _trimOffNewlines (body) : null ,
150+ footer: footer != null ? _trimOffNewlines (footer) : null ,
143151 notes: notes,
144152 references: references,
145153 mentions: mentions,
146- revert: revert,
147154 );
148155}
149156
157+ String _trimOffNewlines (String input) {
158+ final result = RegExp (r'[^\r\n]' ).firstMatch (input);
159+ if (result == null ) {
160+ return '' ;
161+ }
162+ final firstIndex = result.start;
163+ var lastIndex = input.length - 1 ;
164+ while (input[lastIndex] == '\r ' || input[lastIndex] == '\n ' ) {
165+ lastIndex-- ;
166+ }
167+ return input.substring (firstIndex, lastIndex + 1 );
168+ }
169+
150170bool _gpgFilter (String line) {
151171 return ! RegExp (r'^\s*gpg:' ).hasMatch (line);
152172}
@@ -155,7 +175,7 @@ final _kMatchAll = RegExp(r'()(.+)', caseSensitive: false);
155175
156176const _kScissor = '# ------------------------ >8 ------------------------' ;
157177
158- List <String > truncateToScissor (List <String > lines) {
178+ List <String > _truncateToScissor (List <String > lines) {
159179 final scissorIndex = lines.indexOf (_kScissor);
160180
161181 if (scissorIndex == - 1 ) {
@@ -165,48 +185,46 @@ List<String> truncateToScissor(List<String> lines) {
165185 return lines.sublist (0 , scissorIndex);
166186}
167187
168- List <CommitReference > getReferences (
188+ List <CommitReference > _getReferences (
169189 String input, {
170- required Pattern referencesPattern,
171- required Pattern referencePartsPattern,
190+ required RegExp referencesPattern,
191+ required RegExp referencePartsPattern,
172192}) {
173193 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 ) {
194+ final reApplicable =
195+ referencesPattern.hasMatch (input) ? referencesPattern : _kMatchAll;
196+ Match ? referenceSentences = reApplicable.firstMatch (input);
197+ while (referenceSentences != null ) {
198+ final action = referenceSentences.group (1 );
199+ final sentence = referenceSentences.group (2 );
200+ Match ? referenceMatch = referencePartsPattern.firstMatch (sentence! );
201+ while (referenceMatch != null ) {
188202 String ? owner;
189- String ? repository = referenceMatch! .group (1 );
203+ String ? repository = referenceMatch.group (1 );
190204 final ownerRepo = repository? .split ('/' ) ?? [];
191205
192206 if (ownerRepo.length > 1 ) {
193207 owner = ownerRepo.removeAt (0 );
194208 repository = ownerRepo.join ('/' );
195209 }
196210 references.add (CommitReference (
197- raw: referenceMatch.group (0 )! ,
198211 action: action,
199212 owner: owner,
200213 repository: repository,
201214 issue: referenceMatch.group (3 ),
215+ raw: referenceMatch.group (0 )! ,
202216 prefix: referenceMatch.group (2 )! ,
203217 ));
218+ referenceMatch =
219+ referencePartsPattern.matchAsPrefix (sentence, referenceMatch.end);
204220 }
221+ referenceSentences =
222+ reApplicable.matchAsPrefix (input, referenceSentences.end);
205223 }
206224 return references;
207225}
208226
209- Pattern getReferenceRegex (Iterable <String > referenceActions) {
227+ RegExp _getReferenceRegex (Iterable <String > referenceActions) {
210228 if (referenceActions.isEmpty) {
211229 // matches everything
212230 return RegExp (r'()(.+)' , caseSensitive: false ); //gi
@@ -217,7 +235,7 @@ Pattern getReferenceRegex(Iterable<String> referenceActions) {
217235 caseSensitive: false );
218236}
219237
220- Pattern getReferencePartsRegex (
238+ RegExp _getReferencePartsRegex (
221239 List <String > issuePrefixes, bool issuePrefixesCaseSensitive) {
222240 if (issuePrefixes.isEmpty) {
223241 return RegExp (r'(?!.*)' );
@@ -227,17 +245,19 @@ Pattern getReferencePartsRegex(
227245 caseSensitive: issuePrefixesCaseSensitive);
228246}
229247
230- Pattern getNotesRegex (List <String > noteKeywords) {
248+ RegExp _getNotesRegex (List <String > noteKeywords) {
231249 if (noteKeywords.isEmpty) {
232250 return RegExp (r'(?!.*)' );
233251 }
234252 final noteKeywordsSelection = noteKeywords.join ('|' );
235- return RegExp ('^[\\ s|*]*($noteKeywordsSelection )[:\\ s]+(.*)' ,
236- caseSensitive: false );
253+ return RegExp (
254+ '^[\\ s|*]*($noteKeywordsSelection )[:\\ s]+(.*)' ,
255+ caseSensitive: false ,
256+ );
237257}
238258
239- String append (String ? src, String line) {
240- if (src != null ) {
259+ String _append (String ? src, String line) {
260+ if (src != null && src.isNotEmpty ) {
241261 return '$src \n $line ' ;
242262 } else {
243263 return line;
0 commit comments