Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 32 additions & 67 deletions GANReviewTool/modules/GANReviewWikicodeGenerator.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable indent */
import { TemplateFinder } from './TemplateFinder.js';

export class GANReviewWikicodeGenerator {
Expand Down Expand Up @@ -101,20 +100,27 @@ export class GANReviewWikicodeGenerator {

changeGANomineeTemplateStatus( talkWikicode, newStatus ) {
// already has correct status
const regex = new RegExp( `({{GA nominee[^\\}]*\\|\\s*status\\s*=\\s*${ newStatus })`, 'i' );
const alreadyHasCorrectStatus = talkWikicode.match( regex );
const templateFinder = new TemplateFinder( talkWikicode );
const templates = templateFinder.getTemplates( 'GA nominee' );
const templatesWithStatus = templates.filter( ( template ) => template.getArgs( 'status' ).size );
const alreadyHasCorrectStatus = templatesWithStatus.some( ( template ) => {
const value = template.getValue( 'status' );
return value && value.toLowerCase() === newStatus.toLowerCase();
} );
if ( alreadyHasCorrectStatus ) {
return talkWikicode;
}

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

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

getLogMessageToAppend( username, action, reviewTitle, reviewRevisionID, talkRevisionID, gaRevisionID, error ) {
Expand Down Expand Up @@ -185,12 +191,9 @@ export class GANReviewWikicodeGenerator {
| status =
| result = ${ result }
}}`;
const hasH2 = wikicode.match( /^==[^=]+==$/m );
if ( hasH2 ) {
wikicode = wikicode.replace( /^(.*?==[^=]+==\n)(.*)$/s, '$1' + prependText + '\n$2' );
} else {
wikicode = prependText + '\n' + wikicode;
}
const templateFinder = new TemplateFinder( wikicode );
templateFinder.placeATOP( prependText, [ 2 ] );
wikicode = templateFinder.getWikitext();

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

getTemplateParameter( wikicode, templateName, parameterName ) {
templateName = this.regExEscape( templateName );
parameterName = this.regExEscape( parameterName );
const regex = new RegExp( `\\{\\{${ templateName }[^\\}]+\\|\\s*${ parameterName }\\s*=\\s*([^\\}\\|]+)\\s*[^\\}]*\\}\\}`, 'i' );
const parameterValue = wikicode.match( regex );
if ( Array.isArray( parameterValue ) && parameterValue[ 1 ] !== undefined ) {
return parameterValue[ 1 ].trim();
} else {
return null;
const templateFinder = new TemplateFinder( wikicode );
const templates = templateFinder.getTemplates( templateName );
parameterName = parameterName.toLowerCase();
for ( const template of templates ) {
const parameter = template.getAllArgs().find( ( { name } ) => name.toLowerCase() === parameterName );
if ( parameter ) {
return parameter.getValue();
}
}
return null;
}

/**
Expand All @@ -228,7 +232,9 @@ export class GANReviewWikicodeGenerator {
}

deleteGANomineeTemplate( talkWikicode ) {
return talkWikicode.replace( /\{\{GA nominee[^}]+\}\}\n?/i, '' );
const templateFinder = new TemplateFinder( talkWikicode );
templateFinder.deleteTemplate( 'GA nominee' );
return templateFinder.getWikitext();
}

addGATemplate( talkWikicode, topic, gaPageNumber, oldid ) {
Expand Down Expand Up @@ -272,51 +278,9 @@ export class GANReviewWikicodeGenerator {
* @param {string} codeToAdd
*/
addWikicodeAfterTemplates( wikicode, templates, codeToAdd ) {
/* 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.

// Note: the MOS:TALKORDER $templates variable is fed to us as a parameter
let whitespace = ["\t", "\n", " "];
let lastTemplateNameBuffer = '';
let currentTemplateNameBuffer = '';
let templateNestingCount = 0;
for ( i = 0; i < wikicode.length; i++ ) {
let toCheck = wikicode.slice(i);
if ( toCheck.startsWith('{{') {
templateNestingCount++;
} else if ( toCheck.startsWith('}}') ) {
templateNestingCount--;
}
// TODO: need to build the templateNameBuffer. need to look for termination characters | or }
*/

let insertPosition = 0;
for ( const template of templates ) {
// TODO: handle nested templates
const regex = new RegExp( `{{${ this.regExEscape( template ) }[^\\}]*}}\\n`, 'ig' );
const endOfTemplatePosition = this.getEndOfStringPositionOfLastMatch( wikicode, regex );
if ( endOfTemplatePosition > insertPosition ) {
insertPosition = endOfTemplatePosition;
}
}
return this.insertStringIntoStringAtPosition( wikicode, codeToAdd, insertPosition );
}

/**
* @param {string} haystack
* @param {RegExp} regex /g flag must be set
* @return {number} endOfStringPosition Returns zero if not found
*/
getEndOfStringPositionOfLastMatch( haystack, regex ) {
const matches = [ ...haystack.matchAll( regex ) ];
const hasMatches = matches.length;
if ( hasMatches ) {
const lastMatch = matches[ matches.length - 1 ];
const lastMatchStartPosition = lastMatch.index;
const lastMatchStringLength = lastMatch[ 0 ].length;
const lastMatchEndPosition = lastMatchStartPosition + lastMatchStringLength;
return lastMatchEndPosition;
}
return 0;
const templateFinder = new TemplateFinder( wikicode );
templateFinder.addWikicodeAfterTemplates( templates, codeToAdd );
return templateFinder.getWikitext();
}

changeWikiProjectArticleClassToGA( talkWikicode ) {
Expand Down Expand Up @@ -581,6 +545,7 @@ export class GANReviewWikicodeGenerator {
}

hasArticleHistoryTemplate( wikicode ) {
return Boolean( wikicode.match( /\{\{Article ?history/i ) );
const templateFinder = new TemplateFinder( wikicode );
return templateFinder.hasTemplate( 'Article ?history' );
}
}
16 changes: 7 additions & 9 deletions GANReviewTool/modules/GARCloserWikicodeGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,9 @@ __TOC__`;
const resultText = result ? `\n| result = ${ result }\n` : '';
const prependText =
`{{atop${ colorCode }${ resultText }}}`;
const hasH2OrH3 = wikicode.match( /^===?[^=]+===?$/m );
if ( hasH2OrH3 ) {
wikicode = wikicode.replace( /^(.*?===?[^=]+===?\n)\n*(.*)$/s, '$1' + prependText + '\n$2' );
} else {
wikicode = prependText + '\n' + wikicode;
}
const templateFinder = new TemplateFinder( wikicode );
templateFinder.placeATOP( prependText, [ 2, 3 ] );
wikicode = templateFinder.getWikitext();

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

// parse its parameters
// example: |21:00, 12 March 2017 (UTC)|topic=Sports and recreation|page=1|oldid=769997774
Expand Down
51 changes: 51 additions & 0 deletions GANReviewTool/modules/TemplateFinder.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,55 @@ export class TemplateFinder {
}
}
}

placeATOP( prependText, levels ) {
const heading = this.wikiPage.querySelectorAll( 'heading' )
.find( ( { level } ) => levels.includes( level ) );
if ( heading ) {
heading.after( `\n${ prependText }` );
const { lastChild } = heading.lastChild;
if ( lastChild && lastChild.type === 'text' ) {
lastChild.replaceData( lastChild.data.replace( /\n+$/, '' ) );
}
} else {
this.wikiPage.insertAt( prependText + '\n', 0 );
}
}

getTemplates( templateNameCaseInsensitive ) {
const templateName = `template:${ templateNameCaseInsensitive.toLowerCase().replace( / /g, '_' ) }`;
return this.wikiPage.querySelectorAll( 'template' )
.filter( ( { name } ) => name.toLowerCase() === templateName );
}

deleteTemplate( templateNameRegExOrArrayCaseInsensitive ) {
const template = this.firstTemplate( templateNameRegExOrArrayCaseInsensitive );
if ( template ) {
const { nextSibling } = template;
if ( nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n' ) ) {
nextSibling.deleteData( 0, 1 );
}
template.remove();
}
}

addWikicodeAfterTemplates( templates, codeToAdd ) {
const templateNameArray = templates.map( ( name ) => name.toLowerCase().replace( /\s/g, '_' ) );
const filter = ( { name } ) => templateNameArray.includes( TemplateFinder.removePrefix( name ).toLowerCase() );
const tokens = this.wikiPage.querySelectorAll( 'template' ).filter( filter );
if ( tokens.length === 0 ) {
this.wikiPage.insertAt( codeToAdd, 0 );
} else {
const last = tokens[ tokens.length - 1 ];
const { nextSibling } = last;
if ( nextSibling && nextSibling.type === 'text' && nextSibling.data.startsWith( '\n' ) ) {
nextSibling.deleteData( 0, 1 );
}
last.after( `\n${ codeToAdd }` );
}
}

hasTemplate( templateNameRegExOrArrayCaseInsensitive ) {
return Boolean( this.firstTemplate( templateNameRegExOrArrayCaseInsensitive ) );
}
}
Loading