Skip to content

Commit b63a368

Browse files
GANReviewTool: Switch to TemplateFinder (#277)
* GANReviewTool: Switch to TemplateFinder close #209, #214 * fix typo * add unit tests * update unit tests * match formatting of other tests * fix linter errors * fix linter errors --------- Co-authored-by: NovemLinguae <novemlinguae@gmail.com>
1 parent 5d3218c commit b63a368

File tree

6 files changed

+509
-106
lines changed

6 files changed

+509
-106
lines changed

GANReviewTool/modules/GANReviewWikicodeGenerator.js

Lines changed: 32 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable indent */
21
import { TemplateFinder } from './TemplateFinder.js';
32

43
export class GANReviewWikicodeGenerator {
@@ -101,20 +100,27 @@ export class GANReviewWikicodeGenerator {
101100

102101
changeGANomineeTemplateStatus( talkWikicode, newStatus ) {
103102
// already has correct status
104-
const regex = new RegExp( `({{GA nominee[^\\}]*\\|\\s*status\\s*=\\s*${ newStatus })`, 'i' );
105-
const alreadyHasCorrectStatus = talkWikicode.match( regex );
103+
const templateFinder = new TemplateFinder( talkWikicode );
104+
const templates = templateFinder.getTemplates( 'GA nominee' );
105+
const templatesWithStatus = templates.filter( ( template ) => template.getArgs( 'status' ).size );
106+
const alreadyHasCorrectStatus = templatesWithStatus.some( ( template ) => {
107+
const value = template.getValue( 'status' );
108+
return value && value.toLowerCase() === newStatus.toLowerCase();
109+
} );
106110
if ( alreadyHasCorrectStatus ) {
107111
return talkWikicode;
108112
}
109113

110114
// has a status, but needs to be changed
111-
const hasStatus = talkWikicode.match( /({{GA nominee[^}]*\|\s*status\s*=\s*)[^}|]*/i );
115+
const hasStatus = templatesWithStatus.length > 0;
112116
if ( hasStatus ) {
113-
return talkWikicode.replace( /({{GA nominee[^}]*\|\s*status\s*=\s*)[^}|]*/i, `$1${ newStatus }` );
117+
templatesWithStatus[ 0 ].setValue( 'status', newStatus );
118+
return templateFinder.getWikitext();
114119
}
115120

116121
// if no old status, insert new status
117-
return talkWikicode.replace( /({{GA nominee[^}]*)(}})/i, `$1|status=${ newStatus }$2` );
122+
templates[ 0 ].setValue( 'status', newStatus );
123+
return templateFinder.getWikitext();
118124
}
119125

120126
getLogMessageToAppend( username, action, reviewTitle, reviewRevisionID, talkRevisionID, gaRevisionID, error ) {
@@ -185,12 +191,9 @@ export class GANReviewWikicodeGenerator {
185191
| status =
186192
| result = ${ result }
187193
}}`;
188-
const hasH2 = wikicode.match( /^==[^=]+==$/m );
189-
if ( hasH2 ) {
190-
wikicode = wikicode.replace( /^(.*?==[^=]+==\n)(.*)$/s, '$1' + prependText + '\n$2' );
191-
} else {
192-
wikicode = prependText + '\n' + wikicode;
193-
}
194+
const templateFinder = new TemplateFinder( wikicode );
195+
templateFinder.placeATOP( prependText, [ 2 ] );
196+
wikicode = templateFinder.getWikitext();
194197

195198
// place bottom piece at end
196199
const appendText = '{{abot}}';
@@ -209,15 +212,16 @@ export class GANReviewWikicodeGenerator {
209212
}
210213

211214
getTemplateParameter( wikicode, templateName, parameterName ) {
212-
templateName = this.regExEscape( templateName );
213-
parameterName = this.regExEscape( parameterName );
214-
const regex = new RegExp( `\\{\\{${ templateName }[^\\}]+\\|\\s*${ parameterName }\\s*=\\s*([^\\}\\|]+)\\s*[^\\}]*\\}\\}`, 'i' );
215-
const parameterValue = wikicode.match( regex );
216-
if ( Array.isArray( parameterValue ) && parameterValue[ 1 ] !== undefined ) {
217-
return parameterValue[ 1 ].trim();
218-
} else {
219-
return null;
215+
const templateFinder = new TemplateFinder( wikicode );
216+
const templates = templateFinder.getTemplates( templateName );
217+
parameterName = parameterName.toLowerCase();
218+
for ( const template of templates ) {
219+
const parameter = template.getAllArgs().find( ( { name } ) => name.toLowerCase() === parameterName );
220+
if ( parameter ) {
221+
return parameter.getValue();
222+
}
220223
}
224+
return null;
221225
}
222226

223227
/**
@@ -228,7 +232,9 @@ export class GANReviewWikicodeGenerator {
228232
}
229233

230234
deleteGANomineeTemplate( talkWikicode ) {
231-
return talkWikicode.replace( /\{\{GA nominee[^}]+\}\}\n?/i, '' );
235+
const templateFinder = new TemplateFinder( talkWikicode );
236+
templateFinder.deleteTemplate( 'GA nominee' );
237+
return templateFinder.getWikitext();
232238
}
233239

234240
addGATemplate( talkWikicode, topic, gaPageNumber, oldid ) {
@@ -272,51 +278,9 @@ export class GANReviewWikicodeGenerator {
272278
* @param {string} codeToAdd
273279
*/
274280
addWikicodeAfterTemplates( wikicode, templates, codeToAdd ) {
275-
/* Started to write a lexer that would solve the edge case of putting the {{GA}} template too low when the [[MOS:TALKORDER]] is incorrect. It's a lot of work though. Pausing for now.
276-
277-
// Note: the MOS:TALKORDER $templates variable is fed to us as a parameter
278-
let whitespace = ["\t", "\n", " "];
279-
let lastTemplateNameBuffer = '';
280-
let currentTemplateNameBuffer = '';
281-
let templateNestingCount = 0;
282-
for ( i = 0; i < wikicode.length; i++ ) {
283-
let toCheck = wikicode.slice(i);
284-
if ( toCheck.startsWith('{{') {
285-
templateNestingCount++;
286-
} else if ( toCheck.startsWith('}}') ) {
287-
templateNestingCount--;
288-
}
289-
// TODO: need to build the templateNameBuffer. need to look for termination characters | or }
290-
*/
291-
292-
let insertPosition = 0;
293-
for ( const template of templates ) {
294-
// TODO: handle nested templates
295-
const regex = new RegExp( `{{${ this.regExEscape( template ) }[^\\}]*}}\\n`, 'ig' );
296-
const endOfTemplatePosition = this.getEndOfStringPositionOfLastMatch( wikicode, regex );
297-
if ( endOfTemplatePosition > insertPosition ) {
298-
insertPosition = endOfTemplatePosition;
299-
}
300-
}
301-
return this.insertStringIntoStringAtPosition( wikicode, codeToAdd, insertPosition );
302-
}
303-
304-
/**
305-
* @param {string} haystack
306-
* @param {RegExp} regex /g flag must be set
307-
* @return {number} endOfStringPosition Returns zero if not found
308-
*/
309-
getEndOfStringPositionOfLastMatch( haystack, regex ) {
310-
const matches = [ ...haystack.matchAll( regex ) ];
311-
const hasMatches = matches.length;
312-
if ( hasMatches ) {
313-
const lastMatch = matches[ matches.length - 1 ];
314-
const lastMatchStartPosition = lastMatch.index;
315-
const lastMatchStringLength = lastMatch[ 0 ].length;
316-
const lastMatchEndPosition = lastMatchStartPosition + lastMatchStringLength;
317-
return lastMatchEndPosition;
318-
}
319-
return 0;
281+
const templateFinder = new TemplateFinder( wikicode );
282+
templateFinder.addWikicodeAfterTemplates( templates, codeToAdd );
283+
return templateFinder.getWikitext();
320284
}
321285

322286
changeWikiProjectArticleClassToGA( talkWikicode ) {
@@ -581,6 +545,7 @@ export class GANReviewWikicodeGenerator {
581545
}
582546

583547
hasArticleHistoryTemplate( wikicode ) {
584-
return Boolean( wikicode.match( /\{\{Article ?history/i ) );
548+
const templateFinder = new TemplateFinder( wikicode );
549+
return templateFinder.hasTemplate( 'Article ?history' );
585550
}
586551
}

GANReviewTool/modules/GARCloserWikicodeGenerator.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -354,12 +354,9 @@ __TOC__`;
354354
const resultText = result ? `\n| result = ${ result }\n` : '';
355355
const prependText =
356356
`{{atop${ colorCode }${ resultText }}}`;
357-
const hasH2OrH3 = wikicode.match( /^===?[^=]+===?$/m );
358-
if ( hasH2OrH3 ) {
359-
wikicode = wikicode.replace( /^(.*?===?[^=]+===?\n)\n*(.*)$/s, '$1' + prependText + '\n$2' );
360-
} else {
361-
wikicode = prependText + '\n' + wikicode;
362-
}
357+
const templateFinder = new TemplateFinder( wikicode );
358+
templateFinder.placeATOP( prependText, [ 2, 3 ] );
359+
wikicode = templateFinder.getWikitext();
363360

364361
// place bottom piece at end
365362
const appendText = '{{abot}}';
@@ -393,12 +390,13 @@ __TOC__`;
393390
* There's a {{GA}} template that some people use instead of {{Article history}}. If this is present, replace it with {{Article history}}.
394391
*/
395392
convertGATemplateToArticleHistoryIfPresent( talkPageTitle, wikicode ) {
396-
const hasArticleHistory = Boolean( wikicode.match( /\{\{Article ?history([^}]*)\}\}/gi ) );
393+
const templateFinder = new TemplateFinder( wikicode );
394+
const hasArticleHistory = templateFinder.hasTemplate( 'Article ?history' );
397395
const gaTemplateWikicode = this.regexGetFirstMatchString( /(\{\{GA[^}]*\}\})/i, wikicode );
398396
if ( !hasArticleHistory && gaTemplateWikicode ) {
399397
// delete {{ga}} template
400-
wikicode = wikicode.replace( /\{\{GA[^}]*\}\}\n?/i, '' );
401-
wikicode = wikicode.trim();
398+
templateFinder.deleteTemplate( 'GA' );
399+
wikicode = templateFinder.getWikitext().trim();
402400

403401
// parse its parameters
404402
// example: |21:00, 12 March 2017 (UTC)|topic=Sports and recreation|page=1|oldid=769997774

GANReviewTool/modules/TemplateFinder.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,55 @@ export class TemplateFinder {
5454
}
5555
}
5656
}
57+
58+
placeATOP( prependText, levels ) {
59+
const heading = this.wikiPage.querySelectorAll( 'heading' )
60+
.find( ( { level } ) => levels.includes( level ) );
61+
if ( heading ) {
62+
heading.after( `\n${ prependText }` );
63+
const { lastChild } = heading.lastChild;
64+
if ( lastChild && lastChild.type === 'text' ) {
65+
lastChild.replaceData( lastChild.data.replace( /\n+$/, '' ) );
66+
}
67+
} else {
68+
this.wikiPage.insertAt( prependText + '\n', 0 );
69+
}
70+
}
71+
72+
getTemplates( templateNameCaseInsensitive ) {
73+
const templateName = `template:${ templateNameCaseInsensitive.toLowerCase().replace( / /g, '_' ) }`;
74+
return this.wikiPage.querySelectorAll( 'template' )
75+
.filter( ( { name } ) => name.toLowerCase() === templateName );
76+
}
77+
78+
deleteTemplate( templateNameRegExOrArrayCaseInsensitive ) {
79+
const template = this.firstTemplate( templateNameRegExOrArrayCaseInsensitive );
80+
if ( template ) {
81+
const { nextSibling } = template;
82+
if ( nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n' ) ) {
83+
nextSibling.deleteData( 0, 1 );
84+
}
85+
template.remove();
86+
}
87+
}
88+
89+
addWikicodeAfterTemplates( templates, codeToAdd ) {
90+
const templateNameArray = templates.map( ( name ) => name.toLowerCase().replace( /\s/g, '_' ) );
91+
const filter = ( { name } ) => templateNameArray.includes( TemplateFinder.removePrefix( name ).toLowerCase() );
92+
const tokens = this.wikiPage.querySelectorAll( 'template' ).filter( filter );
93+
if ( tokens.length === 0 ) {
94+
this.wikiPage.insertAt( codeToAdd, 0 );
95+
} else {
96+
const last = tokens[ tokens.length - 1 ];
97+
const { nextSibling } = last;
98+
if ( nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n' ) ) {
99+
nextSibling.deleteData( 0, 1 );
100+
}
101+
last.after( `\n${ codeToAdd }` );
102+
}
103+
}
104+
105+
hasTemplate( templateNameRegExOrArrayCaseInsensitive ) {
106+
return Boolean( this.firstTemplate( templateNameRegExOrArrayCaseInsensitive ) );
107+
}
57108
}

0 commit comments

Comments
 (0)