diff --git a/lib/manager/regex/index.ts b/lib/manager/regex/index.ts index a5a327051ae109..7557012ad67dc5 100644 --- a/lib/manager/regex/index.ts +++ b/lib/manager/regex/index.ts @@ -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, - secondGroup: Record -): Record { - 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 = {} -): PackageDependency[] { - const regexes = config.matchStrings.map((matchString) => - regEx(matchString, 'g') - ); - // abort if we have no matchString anymore - if (!regexes[index]) { - 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, diff --git a/lib/manager/regex/strategies.ts b/lib/manager/regex/strategies.ts new file mode 100644 index 00000000000000..0e1afab28f0e37 --- /dev/null +++ b/lib/manager/regex/strategies.ts @@ -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 = {} +): PackageDependency[] { + const regexes = config.matchStrings.map((matchString) => + regEx(matchString, 'g') + ); + // abort if we have no matchString anymore + if (!regexes[index]) { + 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 || {}) + ); + }); +} diff --git a/lib/manager/regex/utils.ts b/lib/manager/regex/utils.ts new file mode 100644 index 00000000000000..c5059f9882f7ac --- /dev/null +++ b/lib/manager/regex/utils.ts @@ -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, + secondGroup: Record +): Record { + return { ...mergedGroup, ...secondGroup }; +} + +export function mergeExtractionTemplate( + base: ExtractionTemplate, + addition: ExtractionTemplate +): ExtractionTemplate { + return { + groups: mergeGroups(base.groups, addition.groups), + replaceString: addition.replaceString ?? base.replaceString, + }; +}