Skip to content

Commit e8efbbb

Browse files
committed
Introduce createFilenameFromString function
1 parent 2c35a97 commit e8efbbb

5 files changed

Lines changed: 134 additions & 56 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
type FilenameOptions = {
2+
removeDots?: boolean;
3+
};
4+
5+
/**
6+
* This will create a filename from an arbitrary string by removing
7+
* all characters which are not allowed in filenames and making them
8+
* more filesystem-friendly be replacing undesirable characters with
9+
* hyphens. The result will always be lowercase ASCII.
10+
*
11+
* @param str The string to create a filename from
12+
* @param removeDots Whether to remove dots from the filename [default: false]
13+
* @returns The filename
14+
*/
15+
export function createFilenameFromString(
16+
str: string,
17+
{ removeDots }: FilenameOptions = {},
18+
) {
19+
let fileName = str;
20+
21+
// Lowercase everything
22+
fileName = fileName.toLowerCase();
23+
24+
// Replace all spaces, underscores, slashes, and backslashes with hyphens
25+
fileName = fileName.replaceAll(/[\s_/\\]+/g, "-");
26+
27+
// Replace all characters which are not allowed by empty strings
28+
fileName = fileName.replaceAll(/[^a-z0-9.-]/g, "");
29+
30+
// Remove any leading or trailing hyphens or dots
31+
fileName = fileName.replaceAll(/^[.-]+|[.-]+$/g, "");
32+
33+
// Remove any duplicate hyphens
34+
fileName = fileName.replaceAll(/-{2,}/g, "-");
35+
// Remove any duplicate dots
36+
fileName = fileName.replaceAll(/\.{2,}/g, ".");
37+
38+
if (removeDots) {
39+
fileName = fileName.replaceAll(/\./g, "-");
40+
}
41+
42+
return fileName;
43+
}

extensions/ql-vscode/src/databases/database-fetcher.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { getLanguageDisplayName } from "../common/query-language";
4040
import type { DatabaseOrigin } from "./local-databases/database-origin";
4141
import { createTimeoutSignal } from "../common/fetch-stream";
4242
import type { App } from "../common/app";
43+
import { createFilenameFromString } from "../common/filenames";
4344

4445
/**
4546
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
@@ -421,22 +422,7 @@ async function getStorageFolder(
421422
let lastName: string;
422423

423424
if (nameOverrride) {
424-
// Lowercase everything
425-
let name = nameOverrride.toLowerCase();
426-
427-
// Replace all spaces, dots, underscores, and forward slashes with hyphens
428-
name = name.replaceAll(/[\s._/]+/g, "-");
429-
430-
// Replace all characters which are not allowed by empty strings
431-
name = name.replaceAll(/[^a-z0-9-]/g, "");
432-
433-
// Remove any leading or trailing hyphens
434-
name = name.replaceAll(/^-|-$/g, "");
435-
436-
// Remove any duplicate hyphens
437-
name = name.replaceAll(/-{2,}/g, "-");
438-
439-
lastName = name;
425+
lastName = createFilenameFromString(nameOverrride);
440426
} else {
441427
// we need to generate a folder name for the unzipped archive,
442428
// this needs to be human readable since we may use this name as the initial

extensions/ql-vscode/src/model-editor/extension-pack-name.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createFilenameFromString } from "../common/filenames";
2+
13
const packNamePartRegex = /[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
24
const packNameRegex = new RegExp(
35
`^(?<scope>${packNamePartRegex.source})/(?<name>${packNamePartRegex.source})$`,
@@ -23,7 +25,11 @@ export function autoNameExtensionPack(
2325
}
2426

2527
const parts = packName.split("/");
26-
const sanitizedParts = parts.map((part) => sanitizeExtensionPackName(part));
28+
const sanitizedParts = parts.map((part) =>
29+
createFilenameFromString(part, {
30+
removeDots: true,
31+
}),
32+
);
2733

2834
// If the scope is empty (e.g. if the given name is "-/b"), then we need to still set a scope
2935
if (sanitizedParts[0].length === 0) {
@@ -37,25 +43,6 @@ export function autoNameExtensionPack(
3743
};
3844
}
3945

40-
function sanitizeExtensionPackName(name: string) {
41-
// Lowercase everything
42-
name = name.toLowerCase();
43-
44-
// Replace all spaces, dots, and underscores with hyphens
45-
name = name.replaceAll(/[\s._]+/g, "-");
46-
47-
// Replace all characters which are not allowed by empty strings
48-
name = name.replaceAll(/[^a-z0-9-]/g, "");
49-
50-
// Remove any leading or trailing hyphens
51-
name = name.replaceAll(/^-|-$/g, "");
52-
53-
// Remove any duplicate hyphens
54-
name = name.replaceAll(/-{2,}/g, "-");
55-
56-
return name;
57-
}
58-
5946
export function parsePackName(packName: string): ExtensionPackName | undefined {
6047
const matches = packNameRegex.exec(packName);
6148
if (!matches?.groups) {

extensions/ql-vscode/src/model-editor/yaml.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
ModelExtension,
2121
ModelExtensionFile,
2222
} from "./model-extension-file";
23+
import { createFilenameFromString } from "../common/filenames";
2324
import type { QueryLanguage } from "../common/query-language";
2425

2526
import modelExtensionFileSchema from "./model-extension-file.schema.json";
@@ -275,26 +276,7 @@ export function createFilenameForLibrary(
275276
prefix = "models/",
276277
suffix = ".model",
277278
) {
278-
let libraryName = library;
279-
280-
// Lowercase everything
281-
libraryName = libraryName.toLowerCase();
282-
283-
// Replace all spaces and underscores with hyphens
284-
libraryName = libraryName.replaceAll(/[\s_]+/g, "-");
285-
286-
// Replace all characters which are not allowed by empty strings
287-
libraryName = libraryName.replaceAll(/[^a-z0-9.-]/g, "");
288-
289-
// Remove any leading or trailing hyphens or dots
290-
libraryName = libraryName.replaceAll(/^[.-]+|[.-]+$/g, "");
291-
292-
// Remove any duplicate hyphens
293-
libraryName = libraryName.replaceAll(/-{2,}/g, "-");
294-
// Remove any duplicate dots
295-
libraryName = libraryName.replaceAll(/\.{2,}/g, ".");
296-
297-
return `${prefix}${libraryName}${suffix}.yml`;
279+
return `${prefix}${createFilenameFromString(library)}${suffix}.yml`;
298280
}
299281

300282
export function createFilenameForPackage(
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { createFilenameFromString } from "../../../src/common/filenames";
2+
3+
describe("createFilenameFromString", () => {
4+
const testCases: Array<{
5+
input: string;
6+
filename: string;
7+
filenameWithoutDots?: string;
8+
}> = [
9+
{
10+
input: "sql2o",
11+
filename: "sql2o",
12+
},
13+
{
14+
input: "spring-boot",
15+
filename: "spring-boot",
16+
},
17+
{
18+
input: "spring--boot",
19+
filename: "spring-boot",
20+
},
21+
{
22+
input: "rt",
23+
filename: "rt",
24+
},
25+
{
26+
input: "System.Runtime",
27+
filename: "system.runtime",
28+
filenameWithoutDots: "system-runtime",
29+
},
30+
{
31+
input: "System..Runtime",
32+
filename: "system.runtime",
33+
filenameWithoutDots: "system-runtime",
34+
},
35+
{
36+
input: "google/brotli",
37+
filename: "google-brotli",
38+
},
39+
{
40+
input: "github/vscode-codeql",
41+
filename: "github-vscode-codeql",
42+
},
43+
{
44+
input: "github/vscode---codeql--",
45+
filename: "github-vscode-codeql",
46+
},
47+
{
48+
input: "github...vscode--c..odeql",
49+
filename: "github.vscode-c.odeql",
50+
filenameWithoutDots: "github-vscode-c-odeql",
51+
},
52+
{
53+
input: "github\\vscode-codeql",
54+
filename: "github-vscode-codeql",
55+
},
56+
{
57+
input: "uNetworking/uWebSockets.js",
58+
filename: "unetworking-uwebsockets.js",
59+
filenameWithoutDots: "unetworking-uwebsockets-js",
60+
},
61+
];
62+
63+
test.each(testCases)(
64+
"returns $filename if string is $input",
65+
({ input, filename }) => {
66+
expect(createFilenameFromString(input)).toEqual(filename);
67+
},
68+
);
69+
70+
test.each(testCases)(
71+
"returns $filename if string is $input and dots are not allowed",
72+
({ input, filename, filenameWithoutDots }) => {
73+
expect(
74+
createFilenameFromString(input, {
75+
removeDots: true,
76+
}),
77+
).toEqual(filenameWithoutDots ?? filename);
78+
},
79+
);
80+
});

0 commit comments

Comments
 (0)