Skip to content

Commit f91c259

Browse files
GANReviewTool: Convert the rest to TemplateFinder (#278)
* GANReviewTool: use TemplateFinder for GARCloserWikicodeGenerator * fix CI * fix CI * diagnose CI error * diagnose CI error * fix CI * add docblock * add tests --------- Co-authored-by: NovemLinguae <novemlinguae@gmail.com> Co-authored-by: NovemLinguae <79697282+NovemLinguae@users.noreply.github.com>
1 parent c5dc342 commit f91c259

File tree

5 files changed

+80
-98
lines changed

5 files changed

+80
-98
lines changed

GANReviewTool/modules/GARCloserWikicodeGenerator.js

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,27 @@ __TOC__`;
9797

9898
processDelistForArticle( wikicode ) {
9999
const gaTemplateNames = [ 'ga icon', 'ga article', 'good article' ];
100+
const templateFinder = new TemplateFinder( wikicode );
100101
for ( const templateName of gaTemplateNames ) {
102+
const template = templateFinder.firstTemplate( templateName );
103+
if ( !template ) {
104+
continue;
105+
}
106+
const { previousSibling, nextSibling } = template;
107+
template.remove();
101108
// handle lots of line breaks: \n\n{{templateName}}\n\n -> \n\n
102-
let regex = new RegExp( '\\n\\n\\{\\{' + templateName + '\\}\\}\\n\\n', 'i' );
103-
wikicode = wikicode.replace( regex, '\n\n' );
104-
105-
// handle normal: {{templateName}}\n -> '', {{templateName}} -> ''
106-
regex = new RegExp( '\\{\\{' + templateName + '\\}\\}\\n?', 'i' );
107-
wikicode = wikicode.replace( regex, '' );
109+
if (
110+
previousSibling && previousSibling.type === 'text' && previousSibling.data.endsWith( '\n\n' ) &&
111+
nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n\n' )
112+
) {
113+
nextSibling.deleteData( 0, 2 );
114+
115+
// handle normal: {{templateName}}\n -> '', {{templateName}} -> ''
116+
} else if ( nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n' ) ) {
117+
nextSibling.deleteData( 0, 1 );
118+
}
108119
}
109-
return wikicode;
120+
return templateFinder.getWikitext();
110121
}
111122

112123
processDelistForGAList( wikicode, articleToRemove ) {
@@ -288,8 +299,14 @@ __TOC__`;
288299
'weapons and buildings': 'Wikipedia:Good articles/Warfare',
289300
weapons: 'Wikipedia:Good articles/Warfare'
290301
};
291-
let topic = wikicode.match( /(?:\{\{Article ?history|\{\{GA\s*(?=\|)).*?\|\s*(?:sub)?topic\s*=\s*([^|}\n]+)/is )[ 1 ];
292-
topic = topic.toLowerCase().trim();
302+
const templateFinder = new TemplateFinder( wikicode );
303+
const templates = templateFinder.getTemplates( [ 'Article history', 'Articlehistory', 'GA' ] );
304+
const template = templates.find( ( t ) => t.getArgs( 'topic' ).size || t.getArgs( 'subtopic' ).size );
305+
let topic = template.getValue( 'topic' );
306+
if ( topic === undefined ) {
307+
topic = template.getValue( 'subtopic' );
308+
}
309+
topic = topic.toLowerCase();
293310
const gaListTitle = dictionary[ topic ];
294311
// throw the error a little later rather than now. that way it doesn't interrupt modifying the article talk page.
295312
return gaListTitle;
@@ -374,16 +391,9 @@ __TOC__`;
374391
}
375392

376393
removeTemplate( templateName, wikicode ) {
377-
const regex = new RegExp( `\\{\\{${ this.regExEscape( templateName ) }[^\\}]*\\}\\}\\n?`, 'i' );
378-
return wikicode.replace( regex, '' );
379-
}
380-
381-
regexGetFirstMatchString( regex, haystack ) {
382-
const matches = haystack.match( regex );
383-
if ( matches !== null && matches[ 1 ] !== undefined ) {
384-
return matches[ 1 ];
385-
}
386-
return null;
394+
const templateFinder = new TemplateFinder( wikicode );
395+
templateFinder.deleteTemplate( templateName );
396+
return templateFinder.getWikitext();
387397
}
388398

389399
/**
@@ -392,15 +402,15 @@ __TOC__`;
392402
convertGATemplateToArticleHistoryIfPresent( talkPageTitle, wikicode ) {
393403
const templateFinder = new TemplateFinder( wikicode );
394404
const hasArticleHistory = templateFinder.hasTemplate( 'Article ?history' );
395-
const gaTemplateWikicode = this.regexGetFirstMatchString( /(\{\{GA[^}]*\}\})/i, wikicode );
396-
if ( !hasArticleHistory && gaTemplateWikicode ) {
405+
const gaTemplate = templateFinder.firstTemplate( 'GA' );
406+
if ( !hasArticleHistory && gaTemplate ) {
397407
// delete {{ga}} template
398408
templateFinder.deleteTemplate( 'GA' );
399409
wikicode = templateFinder.getWikitext().trim();
400410

401411
// parse its parameters
402412
// example: |21:00, 12 March 2017 (UTC)|topic=Sports and recreation|page=1|oldid=769997774
403-
const parameters = this.getParametersFromTemplateWikicode( gaTemplateWikicode );
413+
const parameters = this.getParametersFromTemplateWikicode( gaTemplate );
404414

405415
// if no page specified, assume page is 1. so then the good article review link will be parsed as /GA1
406416
const noPageSpecified = parameters.page === undefined;
@@ -547,28 +557,18 @@ __TOC__`;
547557
/**
548558
* @return {Object} Parameters, with keys being equivalent to the template parameter names. Unnamed parameters will be 1, 2, 3, etc.
549559
*/
550-
getParametersFromTemplateWikicode( wikicodeOfSingleTemplate ) {
551-
wikicodeOfSingleTemplate = wikicodeOfSingleTemplate.slice( 2, -2 ); // remove {{ and }}
552-
// TODO: explode without exploding | inside of inner templates
553-
const strings = wikicodeOfSingleTemplate.split( '|' );
560+
getParametersFromTemplateWikicode( template ) {
561+
if ( typeof template === 'string' ) {
562+
const templateFinder = new TemplateFinder( template );
563+
template = templateFinder.firstTemplate();
564+
}
565+
if ( template.type !== 'template' ) {
566+
throw new Error( `InvalidArgumentException: ${ template.type }
567+
${ template }` );
568+
}
554569
const parameters = {};
555-
let unnamedParameterCount = 1;
556-
let i = 0;
557-
for ( const string of strings ) {
558-
i++;
559-
if ( i == 1 ) {
560-
continue; // skip the template name, this is not a parameter
561-
}
562-
const hasEquals = string.indexOf( '=' );
563-
if ( hasEquals === -1 ) {
564-
parameters[ unnamedParameterCount ] = string;
565-
unnamedParameterCount++;
566-
} else {
567-
const matches = string.match( /^([^=]*)=(.*)/s ); // isolate param name and param value by looking for first equals sign
568-
const paramName = matches[ 1 ].trim().toLowerCase();
569-
const paramValue = matches[ 2 ].trim();
570-
parameters[ paramName ] = paramValue;
571-
}
570+
for ( const parameter of template.getAllArgs() ) {
571+
parameters[ parameter.name.toLowerCase() ] = parameter.getValue();
572572
}
573573
return parameters;
574574
}
Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { TemplateFinder } from './TemplateFinder.js';
2+
13
export class MassGARWikicodeGenerator {
24
hasGoodArticleTemplate( mainArticleWikicode ) {
35
const gaTemplateNames = [ 'ga icon', 'ga article', 'good article' ];
@@ -12,12 +14,14 @@ export class MassGARWikicodeGenerator {
1214
}
1315

1416
// Check for {{Article history|currentstatus=GA}}
15-
// TODO: currently just checks for |currentstatus=GA anywhere on the page. Could improve this algorithm if there end up being false positives.
16-
const matches = talkPageWikicode.match( /\|\s*currentstatus\s*=\s*GA\b/i );
17-
if ( matches ) {
18-
return true;
17+
const templateFinder = new TemplateFinder( talkPageWikicode );
18+
const aliases = [ 'Articlehistory', 'Article milestones', 'Articlemilestones', 'Article History', 'ArticleHistory' ];
19+
const articleHistoryTemplate = templateFinder.firstTemplate( aliases );
20+
if ( !articleHistoryTemplate ) {
21+
return false;
1922
}
20-
return false;
23+
const value = articleHistoryTemplate.getValue( 'currentstatus' );
24+
return Boolean( value ) && value.toUpperCase() === 'GA';
2125
}
2226

2327
hasOpenGAR( talkPageWikicode ) {
@@ -30,21 +34,7 @@ export class MassGARWikicodeGenerator {
3034
* @param {Array} listOfTemplates Case insensitive.
3135
*/
3236
_wikicodeHasTemplate( wikicode, listOfTemplates ) {
33-
const stringForRegEx = listOfTemplates
34-
.map( ( v ) => this._regExEscape( v ) )
35-
.join( '|' );
36-
const regex = new RegExp( `{{(?:${ stringForRegEx })\\b`, 'i' );
37-
const matches = wikicode.match( regex );
38-
if ( matches ) {
39-
return true;
40-
}
41-
return false;
42-
}
43-
44-
/**
45-
* @copyright coolaj86, CC BY-SA 4.0 https://stackoverflow.com/a/6969486/3480193
46-
*/
47-
_regExEscape( string ) {
48-
return string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // $& means the whole matched string
37+
const templateFinder = new TemplateFinder( wikicode );
38+
return templateFinder.hasTemplate( listOfTemplates );
4939
}
5040
}

GANReviewTool/modules/TemplateFinder.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,18 @@ export class TemplateFinder {
6969
}
7070
}
7171

72-
getTemplates( templateNameCaseInsensitive ) {
73-
const templateName = `template:${ templateNameCaseInsensitive.toLowerCase().replace( / /g, '_' ) }`;
72+
/**
73+
* @param {string|Array} templateNamesCaseInsensitive
74+
*/
75+
getTemplates( templateNamesCaseInsensitive ) {
76+
if ( typeof templateNamesCaseInsensitive === 'string' ) {
77+
templateNamesCaseInsensitive = [ templateNamesCaseInsensitive ];
78+
}
79+
const templateNames = templateNamesCaseInsensitive.map(
80+
( t ) => `template:${ t.toLowerCase().replace( / /g, '_' ) }`
81+
);
7482
return this.wikiPage.querySelectorAll( 'template' )
75-
.filter( ( { name } ) => name.toLowerCase() === templateName );
83+
.filter( ( { name } ) => templateNames.includes( name.toLowerCase() ) );
7684
}
7785

7886
deleteTemplate( templateNameRegExOrArrayCaseInsensitive ) {

GANReviewTool/tests/GARCloserWikicodeGenerator.test.js

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ describe( 'getGAListTitleFromTalkPageWikicode(wikicode)', () => {
746746
} );
747747

748748
it( 'Should be case insensitive', () => {
749-
const wikicode = '{{aRTiClE HiStOrY|ToPiC=SpOrTs}}';
749+
const wikicode = '{{aRTiClE HiStOrY|topic=SpOrTs}}';
750750
const output = 'Wikipedia:Good articles/Sports and recreation';
751751
expect( wg.getGAListTitleFromTalkPageWikicode( wikicode ) ).toBe( output );
752752
} );
@@ -1459,36 +1459,6 @@ describe( 'deleteMiddleOfString(string, deleteStartPosition, deleteEndPosition)'
14591459
} );
14601460
} );
14611461

1462-
describe( 'regexGetFirstMatchString(regex, haystack)', () => {
1463-
test( 'match', () => {
1464-
const regex = /hello ([^ ]+)/;
1465-
const haystack = 'hello test goodbye';
1466-
const result = wg.regexGetFirstMatchString( regex, haystack );
1467-
expect( result ).toBe( 'test' );
1468-
} );
1469-
1470-
test( 'no match', () => {
1471-
const regex = /hello (bob)/;
1472-
const haystack = 'hello test goodbye';
1473-
const result = wg.regexGetFirstMatchString( regex, haystack );
1474-
expect( result ).toBe( null );
1475-
} );
1476-
1477-
test( 'no capture group, no match', () => {
1478-
const regex = /hello bob/;
1479-
const haystack = 'hello test goodbye';
1480-
const result = wg.regexGetFirstMatchString( regex, haystack );
1481-
expect( result ).toBe( null );
1482-
} );
1483-
1484-
test( 'no capture group, yes match', () => {
1485-
const regex = /hello bob/;
1486-
const haystack = 'hello bob';
1487-
const result = wg.regexGetFirstMatchString( regex, haystack );
1488-
expect( result ).toBe( null );
1489-
} );
1490-
} );
1491-
14921462
describe( 'convertGATemplateToArticleHistoryIfPresent(talkPageTitle, wikicode)', () => {
14931463
it( 'should default to page=1 when no page parameter', () => {
14941464
const talkPageTitle = 'Talk:Test';
@@ -1895,4 +1865,11 @@ describe( 'removeTemplate( templateName, wikicode )', () => {
18951865
const expected = `Test `;
18961866
expect( wg.removeTemplate( templateName, wikicode ) ).toBe( expected );
18971867
} );
1868+
1869+
it( '1 template with params + nested template', () => {
1870+
const templateName = 'GA';
1871+
const wikicode = `Test {{GA|date={{formatdate|2025-12-20}}}}`;
1872+
const expected = `Test `;
1873+
expect( wg.removeTemplate( templateName, wikicode ) ).toBe( expected );
1874+
} );
18981875
} );

GANReviewTool/tests/MassGARWikicodeGenerator.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,13 @@ describe( '_wikicodeHasTemplate( wikicode, listOfTemplates )', () => {
276276
expect( wg._wikicodeHasTemplate( wikicode, listOfTemplates ) ).toBe( expected );
277277
} );
278278

279+
it( '1 template in haystack, 0 templates to search for', () => {
280+
const wikicode = `Test {{GA}}`;
281+
const listOfTemplates = [];
282+
const expected = false;
283+
expect( wg._wikicodeHasTemplate( wikicode, listOfTemplates ) ).toBe( expected );
284+
} );
285+
279286
it( '1 template in haystack, 1 template to search for', () => {
280287
const wikicode = `Test {{GA}}`;
281288
const listOfTemplates = [ 'GA' ];

0 commit comments

Comments
 (0)