From 0d2d22167d88345ed257d703cbaddfe02ef7be68 Mon Sep 17 00:00:00 2001
From: IKEDA Sho <suicaicoca@gmail.com>
Date: Tue, 12 Jul 2022 17:29:27 +0900
Subject: [PATCH] feat(versioning): add perl versioning (#16518)

---
 lib/modules/versioning/api.ts             |  2 +
 lib/modules/versioning/perl/index.spec.ts | 71 +++++++++++++++++++++++
 lib/modules/versioning/perl/index.ts      | 60 +++++++++++++++++++
 lib/modules/versioning/perl/readme.md     |  3 +
 4 files changed, 136 insertions(+)
 create mode 100644 lib/modules/versioning/perl/index.spec.ts
 create mode 100644 lib/modules/versioning/perl/index.ts
 create mode 100644 lib/modules/versioning/perl/readme.md

diff --git a/lib/modules/versioning/api.ts b/lib/modules/versioning/api.ts
index 83fb40fa00..6b70d0f9dd 100644
--- a/lib/modules/versioning/api.ts
+++ b/lib/modules/versioning/api.ts
@@ -17,6 +17,7 @@ import * as node from './node';
 import * as npm from './npm';
 import * as nuget from './nuget';
 import * as pep440 from './pep440';
+import * as perl from './perl';
 import * as poetry from './poetry';
 import redhat from './redhat';
 import * as regex from './regex';
@@ -50,6 +51,7 @@ api.set('node', node.api);
 api.set('npm', npm.api);
 api.set('nuget', nuget.api);
 api.set('pep440', pep440.api);
+api.set(perl.id, perl.api);
 api.set('poetry', poetry.api);
 api.set('redhat', redhat);
 api.set('regex', regex.api);
diff --git a/lib/modules/versioning/perl/index.spec.ts b/lib/modules/versioning/perl/index.spec.ts
new file mode 100644
index 0000000000..87577df997
--- /dev/null
+++ b/lib/modules/versioning/perl/index.spec.ts
@@ -0,0 +1,71 @@
+import perl from '.';
+
+describe('modules/versioning/perl/index', () => {
+  test.each`
+    input           | expected
+    ${'1'}          | ${true}
+    ${'1.2'}        | ${true}
+    ${'1.02'}       | ${true}
+    ${'1.002'}      | ${true}
+    ${'1.0023'}     | ${true}
+    ${'1.00203'}    | ${true}
+    ${'1.002003'}   | ${true}
+    ${'1.002_003'}  | ${true}
+    ${'1._002003'}  | ${false}
+    ${'1.002003_'}  | ${false}
+    ${'1.00_20_03'} | ${false}
+    ${'v1'}         | ${true}
+    ${'v1.200'}     | ${true}
+    ${'v1.20.0'}    | ${true}
+    ${'v1.2.3'}     | ${true}
+    ${'1.2.3'}      | ${true}
+    ${'v1.2_3'}     | ${true}
+    ${'v1._23'}     | ${false}
+    ${'v1.23_'}     | ${false}
+    ${'v1.2_3_4'}   | ${false}
+  `('isValid("$input") === $expected', ({ input, expected }) => {
+    const res = !!perl.isValid(input);
+    expect(res).toBe(expected);
+  });
+
+  test.each`
+    input         | expected
+    ${'1'}        | ${true}
+    ${'1.234'}    | ${true}
+    ${'1.2_34'}   | ${false}
+    ${'v1'}       | ${true}
+    ${'v1.2'}     | ${true}
+    ${'v1.2.3'}   | ${true}
+    ${'v1.2.3_4'} | ${false}
+  `('isStable("$input") === $expected', ({ input, expected }) => {
+    expect(perl.isStable(input)).toBe(expected);
+  });
+
+  test.each`
+    a             | b              | expected
+    ${'1.2'}      | ${'v1.200.0'}  | ${true}
+    ${'1.02'}     | ${'v1.20.0'}   | ${true}
+    ${'1.002'}    | ${'v1.2.0'}    | ${true}
+    ${'1.0023'}   | ${'v1.2.300'}  | ${true}
+    ${'1.00203'}  | ${'v1.2.30'}   | ${true}
+    ${'1.002003'} | ${'v1.2.3'}    | ${true}
+    ${'1.02_03'}  | ${'1.020_3'}   | ${true}
+    ${'1.02_03'}  | ${'v1.20_300'} | ${true}
+  `('equals($a, $b) === $expected', ({ a, b, expected }) => {
+    expect(perl.equals(a, b)).toBe(expected);
+  });
+
+  test.each`
+    a            | b            | expected
+    ${'2.4.2'}   | ${'2.4.1'}   | ${true}
+    ${'0.1301'}  | ${'0.13_01'} | ${true}
+    ${'0.13_01'} | ${'0.1301'}  | ${false}
+    ${'1.900'}   | ${'2.000'}   | ${false}
+    ${'1.900'}   | ${'1.901'}   | ${false}
+    ${'1.2.0.1'} | ${'1.2.0'}   | ${true}
+    ${'1.2.0'}   | ${'1.2.0.1'} | ${false}
+    ${undefined} | ${'1.2.0'}   | ${true}
+  `('isGreaterThan($a, $b) === $expected', ({ a, b, expected }) => {
+    expect(perl.isGreaterThan(a, b)).toBe(expected);
+  });
+});
diff --git a/lib/modules/versioning/perl/index.ts b/lib/modules/versioning/perl/index.ts
new file mode 100644
index 0000000000..1acb75945b
--- /dev/null
+++ b/lib/modules/versioning/perl/index.ts
@@ -0,0 +1,60 @@
+import { regEx } from '../../../util/regex';
+import { GenericVersion, GenericVersioningApi } from '../generic';
+import type { VersioningApi } from '../types';
+
+export const id = 'perl';
+export const displayName = 'Perl';
+export const urls = ['https://metacpan.org/pod/version'];
+export const supportsRanges = false;
+
+// https://metacpan.org/pod/version#Decimal-Versions
+const decimalVersionPattern = regEx(/^(\d+)\.(\d+(?:_\d+)?)$/);
+// https://metacpan.org/pod/version#Dotted-Decimal-Versions
+const dottedDecimalVersionPattern = regEx(/^v?(\d+(?:\.\d+)*(?:_\d+)?)$/);
+
+class PerlVersioningApi extends GenericVersioningApi {
+  protected _parse(version: string): GenericVersion | null {
+    return (
+      this._parseDecimalVersion(version) ??
+      this._parseDottedDecimalVersion(version)
+    );
+  }
+
+  private _parseDecimalVersion(version: string): GenericVersion | null {
+    const matches = decimalVersionPattern.exec(version);
+    if (!matches) {
+      return null;
+    }
+    const [, intPart, decimalPart] = matches;
+    const prerelease = decimalPart.includes('_') ? 'alpha' : '';
+
+    const decimalComponents =
+      decimalPart
+        .replace(/_/g, '')
+        .match(/.{1,3}/g)
+        ?.map((value) => {
+          let component = value;
+          while (component.length < 3) {
+            component += '0';
+          }
+          return Number.parseInt(component, 10);
+        }) ?? /* istanbul ignore next */ [];
+    const release = [Number.parseInt(intPart, 10), ...decimalComponents];
+    return { release, prerelease };
+  }
+
+  private _parseDottedDecimalVersion(version: string): GenericVersion | null {
+    const matches = dottedDecimalVersionPattern.exec(version);
+    if (!matches) {
+      return null;
+    }
+    const [, versionValue] = matches;
+    const prerelease = versionValue.includes('_') ? 'alpha' : '';
+    const release = versionValue.split(/[._]/).map(Number);
+    return { release, prerelease };
+  }
+}
+
+export const api: VersioningApi = new PerlVersioningApi();
+
+export default api;
diff --git a/lib/modules/versioning/perl/readme.md b/lib/modules/versioning/perl/readme.md
new file mode 100644
index 0000000000..0cb3c741f2
--- /dev/null
+++ b/lib/modules/versioning/perl/readme.md
@@ -0,0 +1,3 @@
+Perl versioning is based on Perl's [`version` module](https://metacpan.org/pod/version).
+
+This could be used in combination with the `regex` manager and the `repology` datasource.
-- 
GitLab