Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
92 changes: 15 additions & 77 deletions GANReviewTool/modules/GANReviewWikicodeGenerator.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable indent */
import { TemplateFinder } from './TemplateFinder.js';

export class GANReviewWikicodeGenerator {
getPassWikicodeForGANPage( reviewWikicode ) {
Expand Down Expand Up @@ -255,11 +256,12 @@ export class GANReviewWikicodeGenerator {
}

getFirstTemplateNameFromWikicode( wikicode ) {
const match = wikicode.match( /(?<=\{\{)[^|}]+/ );
if ( !match ) {
const templateFinder = new TemplateFinder( wikicode );
const template = templateFinder.firstTemplate();
if ( !template ) {
throw new Error( 'getFirstTemplateNameFromWikicode: No template found in Wikicode.' );
}
return match[ 0 ];
return TemplateFinder.removePrefix( template.name );
}

/**
Expand Down Expand Up @@ -350,7 +352,7 @@ export class GANReviewWikicodeGenerator {
const topicString = `\n|topic = ${ topic }`;

// https://en.wikipedia.org/wiki/Template:Article_history#How_to_use_in_practice
const existingStatus = this.firstTemplateGetParameterValue( talkWikicode, 'Artricle history', 'currentstatus' );
const existingStatus = this.firstTemplateGetParameterValue( talkWikicode, 'Article ?history', 'currentstatus' );
talkWikicode = this.firstTemplateDeleteParameter( talkWikicode, 'Article ?history', 'currentstatus' );
const currentStatusString = this.getArticleHistoryNewStatus( existingStatus, listedOrFailed );

Expand Down Expand Up @@ -393,84 +395,20 @@ export class GANReviewWikicodeGenerator {
}

firstTemplateInsertCode( wikicode, templateNameRegExNoDelimiters, codeToInsert ) {
// TODO: handle nested templates
const regex = new RegExp( `(\\{\\{${ templateNameRegExNoDelimiters }[^\\}]*)(\\}\\})`, 'i' );
return wikicode.replace( regex, `$1\n${ codeToInsert }\n$2` );
}

firstTemplateGetParameterValue( wikicode, template, parameter ) {
// TODO: rewrite to be more robust. currently using a simple algorithm that is prone to failure
// new algorithm:
// find start of template. use regex /i (ignore case)
// iterate using loops until end of template found
// handle <nowiki>
// handle triple {{{
// handle nested

const regex = new RegExp( `\\|\\s*${ parameter }\\s*=\\s*([^\\n\\|\\}]*)\\s*`, '' );
const result = wikicode.match( regex );
if ( wikicode.match( regex ) === null ) {
return null;
}
return result[ 1 ];
const templateFinder = new TemplateFinder( wikicode );
templateFinder.firstTemplateInsertCode( templateNameRegExNoDelimiters, codeToInsert );
return templateFinder.getWikitext();
}

/**
* @param {RegExp} regex
*/
preg_position( regex, haystack ) {
const matches = [ ...haystack.matchAll( regex ) ];
const hasMatches = matches.length;
if ( hasMatches ) {
return matches[ 0 ].index;
}
return false;
}

findEndOfTemplate( wikicode, templateStartPosition ) {
// TODO: handle triple braces, handle <nowiki> tags
let nesting = 0;
let templateEndPosition = -1;
// +1 to skip the first {{, will throw off our nesting count
for ( let i = templateStartPosition + 1; i < wikicode.length; i++ ) {
const nextTwoChars = wikicode.slice( i, i + 2 );
if ( nextTwoChars === '{{' ) {
nesting++;
continue;
} else if ( nextTwoChars === '}}' ) {
if ( nesting > 0 ) {
nesting--;
continue;
} else {
templateEndPosition = i + 2;
break;
}
}
}
return templateEndPosition;
firstTemplateGetParameterValue( wikicode, templateRegEx, parameter ) {
const templateFinder = new TemplateFinder( wikicode );
return templateFinder.firstTemplateGetParameterValue( templateRegEx, parameter );
}

firstTemplateDeleteParameter( wikicode, templateRegEx, parameter ) {
// templateStartPosition
const regex = new RegExp( '{{' + templateRegEx, 'gi' );
const templateStartPosition = this.preg_position( regex, wikicode );

// templateEndPosition
const templateEndPosition = this.findEndOfTemplate( wikicode, templateStartPosition );

// slice
const firstPiece = wikicode.slice( 0, templateStartPosition );
let secondPiece = wikicode.slice( templateStartPosition, templateEndPosition );
const thirdPiece = wikicode.slice( templateEndPosition );

// replace only inside the slice
const regex2 = new RegExp( `\\|\\s*${ parameter }\\s*=\\s*([^\\n\\|\\}]*)\\s*`, '' );
secondPiece = secondPiece.replace( regex2, '' );

// glue back together
wikicode = firstPiece + secondPiece + thirdPiece;

return wikicode;
const templateFinder = new TemplateFinder( wikicode );
templateFinder.firstTemplateDeleteParameter( templateRegEx, parameter );
return templateFinder.getWikitext();
}

removeFormattingThatInterferesWithSort( str ) {
Expand Down
83 changes: 14 additions & 69 deletions GANReviewTool/modules/GARCloserWikicodeGenerator.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TemplateFinder } from './TemplateFinder.js';

export class GARCloserWikicodeGenerator {
processKeepForGARPage( garPageWikicode, message, isCommunityAssessment ) {
return this.processGARPage( garPageWikicode, message, isCommunityAssessment, 'Kept.', 'green' );
Expand Down Expand Up @@ -583,15 +585,15 @@ __TOC__`;
throw new Error( 'InvalidArgumentException' );
}

const topic = this.firstTemplateGetParameterValue( wikicode, 'Artricle history', 'topic' );
const topic = this.firstTemplateGetParameterValue( wikicode, 'Article ?history', 'topic' );
let topicString = '';
if ( !topic ) {
topicString = `\n|topic = ${ topic }`;
}

// https://en.wikipedia.org/wiki/Template:Article_history#How_to_use_in_practice
const existingStatus = this.firstTemplateGetParameterValue( wikicode, 'Artricle history', 'currentstatus' );
wikicode = this.firstTemplateDeleteParameter( wikicode, 'Article history', 'currentstatus' );
const existingStatus = this.firstTemplateGetParameterValue( wikicode, 'Article ?history', 'currentstatus' );
wikicode = this.firstTemplateDeleteParameter( wikicode, 'Article ?history', 'currentstatus' );
const currentStatusString = this.getArticleHistoryNewStatus( existingStatus, keepOrDelist );

const result = this.getKeepOrDelistPastTense( keepOrDelist );
Expand Down Expand Up @@ -634,21 +636,9 @@ __TOC__`;
}
}

firstTemplateGetParameterValue( wikicode, template, parameter ) {
// TODO: rewrite to be more robust. currently using a simple algorithm that is prone to failure
// new algorithm:
// find start of template. use regex /i (ignore case)
// iterate using loops until end of template found
// handle <nowiki>
// handle triple {{{
// handle nested

const regex = new RegExp( `\\|\\s*${ parameter }\\s*=\\s*([^\\n\\|\\}]*)\\s*`, '' );
const result = wikicode.match( regex );
if ( wikicode.match( regex ) === null ) {
return null;
}
return result[ 1 ];
firstTemplateGetParameterValue( wikicode, templateRegEx, parameter ) {
const templateFinder = new TemplateFinder( wikicode );
return templateFinder.firstTemplateGetParameterValue( templateRegEx, parameter );
}

getArticleHistoryNewStatus( existingStatus, keepOrDelist ) {
Expand All @@ -663,63 +653,18 @@ __TOC__`;
* @param {Array} templateNameArrayCaseInsensitive
*/
firstTemplateInsertCode( wikicode, templateNameArrayCaseInsensitive, codeToInsert ) {
for ( const templateName of templateNameArrayCaseInsensitive ) {
const strPosOfEndOfFirstTemplate = this.getStrPosOfEndOfFirstTemplateFound( wikicode, templateName );
if ( strPosOfEndOfFirstTemplate !== null ) {
const insertPosition = strPosOfEndOfFirstTemplate - 2; // 2 characters from the end, right before }}
const result = this.insertStringIntoStringAtPosition( wikicode, `\n${ codeToInsert }\n`, insertPosition );
return result;
}
}
}

/**
* CC BY-SA 4.0, jAndy, https://stackoverflow.com/a/4364902/3480193
*/
insertStringIntoStringAtPosition( bigString, insertString, position ) {
return [
bigString.slice( 0, position ),
insertString,
bigString.slice( position )
].join( '' );
}

/**
* Grabs string position of the END of first {{template}} contained in wikicode. Case insensitive. Returns null if no template found. Handles nested templates.
*
* @return {number|null}
*/
getStrPosOfEndOfFirstTemplateFound( wikicode, templateName ) {
const starting_position = wikicode.toLowerCase().indexOf( '{{' + templateName.toLowerCase() );
if ( starting_position === -1 ) {
return null;
}
let counter = 0;
const length = wikicode.length;
for ( let i = starting_position + 2; i < length; i++ ) {
const next_two = wikicode.substr( i, 2 );
if ( next_two == '{{' ) {
counter++;
continue;
} else if ( next_two == '}}' ) {
if ( counter == 0 ) {
return i + 2; // +2 to account for next_two being }} (2 characters)
} else {
counter--;
continue;
}
}
}
return null;
const templateFinder = new TemplateFinder( wikicode );
templateFinder.firstTemplateInsertCode( templateNameArrayCaseInsensitive, codeToInsert );
return templateFinder.getWikitext();
}

removeGAStatusFromWikiprojectBanners( wikicode ) {
return wikicode.replace( /(\|\s*class\s*=\s*)([^}|\s]*)/gi, '$1' );
}

firstTemplateDeleteParameter( wikicode, template, parameter ) {
// TODO: rewrite to be more robust. currently using a simple algorithm that is prone to failure
const regex = new RegExp( `\\|\\s*${ parameter }\\s*=\\s*([^\\n\\|\\}]*)\\s*`, '' );
return wikicode.replace( regex, '' );
const templateFinder = new TemplateFinder( wikicode );
templateFinder.firstTemplateDeleteParameter( template, parameter );
return templateFinder.getWikitext();
}
}
36 changes: 36 additions & 0 deletions GANReviewTool/modules/Parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Parser from 'wikiparser-template';

Parser.config = {
ext: [
'pre',
'nowiki',
'gallery',
'indicator',
'langconvert',
'graph',
'timeline',
'hiero',
'charinsert',
'ref',
'references',
'inputbox',
'imagemap',
'source',
'syntaxhighlight',
'poem',
'categorytree',
'section',
'score',
'templatestyles',
'templatedata',
'math',
'ce',
'chem',
'maplink',
'mapframe',
'page-collection',
'phonos'
]
};

export default Parser;
61 changes: 54 additions & 7 deletions GANReviewTool/modules/TemplateFinder.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,57 @@
// TODO: A couple of recent bugs will require a lexer or template parser type class to solve.
import Parser from './Parser.js';

class TemplateFinder {
// getTemplateList()
// appendParameter()
// addWikitextAfterTemplate()
// getWikitext()
export class TemplateFinder {
constructor( wikicode ) {
this.root = Parser.parse( wikicode, false, 2 );
}

// appends/adds need to shift all the position variables by the length of the append/add
}
static removePrefix( templateName ) {
return templateName.replace( /^Template:/, '' );
}

getWikitext() {
return String( this.root );
}

firstTemplate( templateNameRegExOrArrayCaseInsensitive ) {
let filter;
if ( !templateNameRegExOrArrayCaseInsensitive ) {
filter = () => true;
} else if ( Array.isArray( templateNameRegExOrArrayCaseInsensitive ) ) {
const templateNameArray = templateNameRegExOrArrayCaseInsensitive
.map( ( name ) => name.toLowerCase().replace( /\s/g, '_' ) );
filter = ( { name } ) => templateNameArray.includes( TemplateFinder.removePrefix( name ).toLowerCase() );
} else {
const regEx = new RegExp( `^Template:${ templateNameRegExOrArrayCaseInsensitive }$`, 'i' );
filter = ( { name } ) => regEx.test( name.replace( /_/g, ' ' ) );
}
return this.root.querySelectorAll( 'template' ).find( filter );
}

firstTemplateInsertCode( templateNameRegExOrArrayCaseInsensitive, codeToInsert ) {
const template = this.firstTemplate( templateNameRegExOrArrayCaseInsensitive );
if ( template ) {
template.append( `${ codeToInsert.replace( /^\|/, '' ) }\n` );
}
}

firstTemplateGetParameterValue( templateNameRegExOrArrayCaseInsensitive, parameter ) {
const template = this.firstTemplate( templateNameRegExOrArrayCaseInsensitive );
if ( !template ) {
return null;
}
return template.getValue( parameter ) ?? null;
}

firstTemplateDeleteParameter( templateNameRegExOrArrayCaseInsensitive, parameter ) {
const template = this.firstTemplate( templateNameRegExOrArrayCaseInsensitive );
if ( template ) {
for ( const token of template.getAllArgs() ) {
if ( token.name.toLowerCase() === parameter ) {
token.remove();
}
}
}
}
}
12 changes: 11 additions & 1 deletion GANReviewTool/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion GANReviewTool/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"dependencies": {
"jest": "^28.1.0",
"types-mediawiki": "github:wikimedia-gadgets/types-mediawiki"
"types-mediawiki": "github:wikimedia-gadgets/types-mediawiki",
"wikiparser-template": "^1.24.1"
},
"scripts": {
"test": "jest"
Expand Down
Loading