Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(regex): cleanup up index.ts and move matchStringStrategy to enum #12525

Merged
merged 8 commits into from
Dec 10, 2021
168 changes: 2 additions & 166 deletions lib/manager/regex/index.ts
Original file line number Diff line number Diff line change
@@ -1,180 +1,16 @@
import { URL } from 'url';
import { logger } from '../../logger';
import { regEx } from '../../util/regex';
import * as template from '../../util/template';
import type {
CustomExtractConfig,
PackageDependency,
PackageFile,
Result,
} from '../types';
import type { ExtractionTemplate } from './types';
import { handleAny, handleCombination, handleRecursive } from './strategies';
import { validMatchFields } from './utils';

export const defaultConfig = {
pinDigests: false,
};

const validMatchFields = [
'depName',
'lookupName',
'currentValue',
'currentDigest',
'datasource',
'versioning',
'extractVersion',
'registryUrl',
'depType',
];

function regexMatchAll(regex: RegExp, content: string): RegExpMatchArray[] {
const matches: RegExpMatchArray[] = [];
let matchResult;
do {
matchResult = regex.exec(content);
if (matchResult) {
matches.push(matchResult);
}
} while (matchResult);
return matches;
}

function createDependency(
extractionTemplate: ExtractionTemplate,
config: CustomExtractConfig,
dep?: PackageDependency
): PackageDependency {
const dependency = dep || {};
const { groups, replaceString } = extractionTemplate;

function updateDependency(field: string, value: string): void {
switch (field) {
case 'registryUrl':
// check if URL is valid and pack inside an array
try {
const url = new URL(value).toString();
dependency.registryUrls = [url];
} catch (err) {
logger.warn({ value }, 'Invalid regex manager registryUrl');
}
break;
default:
dependency[field] = value;
break;
}
}

for (const field of validMatchFields) {
const fieldTemplate = `${field}Template`;
if (config[fieldTemplate]) {
try {
const compiled = template.compile(config[fieldTemplate], groups, false);
updateDependency(field, compiled);
} catch (err) {
logger.warn(
{ template: config[fieldTemplate] },
'Error compiling template for custom manager'
);
return null;
}
} else if (groups[field]) {
updateDependency(field, groups[field]);
}
}
dependency.replaceString = replaceString;
return dependency;
}

function handleAny(
content: string,
packageFile: string,
config: CustomExtractConfig
): PackageDependency[] {
return config.matchStrings
.map((matchString) => regEx(matchString, 'g'))
.flatMap((regex) => regexMatchAll(regex, content)) // match all regex to content, get all matches, reduce to single array
.map((matchResult) =>
createDependency(
{ groups: matchResult.groups, replaceString: matchResult[0] },
config
)
);
}

function mergeGroups(
mergedGroup: Record<string, string>,
secondGroup: Record<string, string>
): Record<string, string> {
return { ...mergedGroup, ...secondGroup };
}

export function mergeExtractionTemplate(
base: ExtractionTemplate,
addition: ExtractionTemplate
): ExtractionTemplate {
return {
groups: mergeGroups(base.groups, addition.groups),
replaceString: addition.replaceString ?? base.replaceString,
};
}

function handleCombination(
content: string,
packageFile: string,
config: CustomExtractConfig
): PackageDependency[] {
const matches = config.matchStrings
.map((matchString) => regEx(matchString, 'g'))
.flatMap((regex) => regexMatchAll(regex, content)); // match all regex to content, get all matches, reduce to single array

if (!matches.length) {
return [];
}

const extraction = matches
.map((match) => ({
groups: match.groups,
replaceString: match?.groups?.currentValue ? match[0] : undefined,
}))
.reduce((base, addition) => mergeExtractionTemplate(base, addition));
return [createDependency(extraction, config)];
}

function handleRecursive(
content: string,
packageFile: string,
config: CustomExtractConfig,
index = 0,
combinedGroups: Record<string, string> = {}
): PackageDependency[] {
const regexes = config.matchStrings.map((matchString) =>
regEx(matchString, 'g')
);
// abort if we have no matchString anymore
if (regexes[index] == null) {
return [];
}
return regexMatchAll(regexes[index], content).flatMap((match) => {
// if we have a depName and a currentValue which have the minimal viable definition
if (match?.groups?.depName && match?.groups?.currentValue) {
return createDependency(
{
groups: mergeGroups(combinedGroups, match.groups),
replaceString: match[0],
},
config
);
}

return handleRecursive(
match[0],
packageFile,
config,
index + 1,
mergeGroups(combinedGroups, match.groups || {})
);
});
}

export function extractPackageFile(
content: string,
packageFile: string,
Expand Down
82 changes: 82 additions & 0 deletions lib/manager/regex/strategies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { regEx } from '../../util/regex';
import type { CustomExtractConfig, PackageDependency } from '../types';
import {
createDependency,
mergeExtractionTemplate,
mergeGroups,
regexMatchAll,
} from './utils';

export function handleAny(
content: string,
packageFile: string,
config: CustomExtractConfig
): PackageDependency[] {
return config.matchStrings
.map((matchString) => regEx(matchString, 'g'))
.flatMap((regex) => regexMatchAll(regex, content)) // match all regex to content, get all matches, reduce to single array
.map((matchResult) =>
createDependency(
{ groups: matchResult.groups, replaceString: matchResult[0] },
config
)
);
}

export function handleCombination(
content: string,
packageFile: string,
config: CustomExtractConfig
): PackageDependency[] {
const matches = config.matchStrings
.map((matchString) => regEx(matchString, 'g'))
.flatMap((regex) => regexMatchAll(regex, content)); // match all regex to content, get all matches, reduce to single array

if (!matches.length) {
return [];
}

const extraction = matches
.map((match) => ({
groups: match.groups,
replaceString: match?.groups?.currentValue ? match[0] : undefined,
}))
.reduce((base, addition) => mergeExtractionTemplate(base, addition));
return [createDependency(extraction, config)];
}

export function handleRecursive(
content: string,
packageFile: string,
config: CustomExtractConfig,
index = 0,
combinedGroups: Record<string, string> = {}
): PackageDependency[] {
const regexes = config.matchStrings.map((matchString) =>
regEx(matchString, 'g')
);
// abort if we have no matchString anymore
if (regexes[index] == null) {
return [];
}
return regexMatchAll(regexes[index], content).flatMap((match) => {
// if we have a depName and a currentValue which have the minimal viable definition
if (match?.groups?.depName && match?.groups?.currentValue) {
return createDependency(
{
groups: mergeGroups(combinedGroups, match.groups),
replaceString: match[0],
},
config
);
}

return handleRecursive(
match[0],
packageFile,
config,
index + 1,
mergeGroups(combinedGroups, match.groups || {})
);
});
}
95 changes: 95 additions & 0 deletions lib/manager/regex/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { URL } from 'url';
import { logger } from '../../logger';
import * as template from '../../util/template';
import type { CustomExtractConfig, PackageDependency } from '../types';
import type { ExtractionTemplate } from './types';

export const validMatchFields = [
'depName',
'lookupName',
'currentValue',
'currentDigest',
'datasource',
'versioning',
'extractVersion',
'registryUrl',
'depType',
];

export function createDependency(
extractionTemplate: ExtractionTemplate,
config: CustomExtractConfig,
dep?: PackageDependency
): PackageDependency {
const dependency = dep || {};
const { groups, replaceString } = extractionTemplate;

function updateDependency(field: string, value: string): void {
switch (field) {
case 'registryUrl':
// check if URL is valid and pack inside an array
try {
const url = new URL(value).toString();
dependency.registryUrls = [url];
} catch (err) {
logger.warn({ value }, 'Invalid regex manager registryUrl');
}
break;
default:
dependency[field] = value;
break;
}
}

for (const field of validMatchFields) {
const fieldTemplate = `${field}Template`;
if (config[fieldTemplate]) {
try {
const compiled = template.compile(config[fieldTemplate], groups, false);
updateDependency(field, compiled);
} catch (err) {
logger.warn(
{ template: config[fieldTemplate] },
'Error compiling template for custom manager'
);
return null;
}
} else if (groups[field]) {
updateDependency(field, groups[field]);
}
}
dependency.replaceString = replaceString;
return dependency;
}

export function regexMatchAll(
regex: RegExp,
content: string
): RegExpMatchArray[] {
const matches: RegExpMatchArray[] = [];
let matchResult;
do {
matchResult = regex.exec(content);
if (matchResult) {
matches.push(matchResult);
}
} while (matchResult);
return matches;
}

export function mergeGroups(
mergedGroup: Record<string, string>,
secondGroup: Record<string, string>
): Record<string, string> {
return { ...mergedGroup, ...secondGroup };
}

export function mergeExtractionTemplate(
base: ExtractionTemplate,
addition: ExtractionTemplate
): ExtractionTemplate {
return {
groups: mergeGroups(base.groups, addition.groups),
replaceString: addition.replaceString ?? base.replaceString,
};
}