Skip to content

Commit b5fa2d5

Browse files
committed
Add list-files: csv format
1 parent e2bed85 commit b5fa2d5

File tree

8 files changed

+127
-51
lines changed

8 files changed

+127
-51
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ For more information see [CHANGELOG](https://github.com/dorny/paths-filter/blob/
122122
123123
# Enables listing of files matching the filter:
124124
# 'none' - Disables listing of matching files (default).
125+
# 'csv' - Coma separated list of filenames.
126+
# If needed it uses double quotes to wrap filename with unsafe characters.
125127
# 'json' - Matching files paths are formatted as JSON array.
126128
# 'shell' - Space delimited list usable as command line argument list in Linux shell.
127129
# If needed it uses single or double quotes to wrap filename with unsafe characters.

__tests__/csv-escape.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {csvEscape} from '../src/list-format/csv-escape'
2+
3+
describe('csvEscape() backslash escapes every character except subset of definitely safe characters', () => {
4+
test('simple filename should not be modified', () => {
5+
expect(csvEscape('file.txt')).toBe('file.txt')
6+
})
7+
8+
test('directory separator should be preserved and not escaped', () => {
9+
expect(csvEscape('path/to/file.txt')).toBe('path/to/file.txt')
10+
})
11+
12+
test('filename with spaces should be quoted', () => {
13+
expect(csvEscape('file with space')).toBe('"file with space"')
14+
})
15+
16+
test('filename with "," should be quoted', () => {
17+
expect(csvEscape('file, with coma')).toBe('"file, with coma"')
18+
})
19+
20+
test('Double quote should be escaped by another double quote', () => {
21+
expect(csvEscape('file " with double quote')).toBe('"file "" with double quote"')
22+
})
23+
})

__tests__/shell-escape.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
import {escape, shellEscape} from '../src/shell-escape'
1+
import {backslashEscape, shellEscape} from '../src/list-format/shell-escape'
22

33
describe('escape() backslash escapes every character except subset of definitely safe characters', () => {
44
test('simple filename should not be modified', () => {
5-
expect(escape('file.txt')).toBe('file.txt')
5+
expect(backslashEscape('file.txt')).toBe('file.txt')
66
})
77

88
test('directory separator should be preserved and not escaped', () => {
9-
expect(escape('path/to/file.txt')).toBe('path/to/file.txt')
9+
expect(backslashEscape('path/to/file.txt')).toBe('path/to/file.txt')
1010
})
1111

1212
test('spaces should be escaped with backslash', () => {
13-
expect(escape('file with space')).toBe('file\\ with\\ space')
13+
expect(backslashEscape('file with space')).toBe('file\\ with\\ space')
1414
})
1515

1616
test('quotes should be escaped with backslash', () => {
17-
expect(escape('file\'with quote"')).toBe('file\\\'with\\ quote\\"')
17+
expect(backslashEscape('file\'with quote"')).toBe('file\\\'with\\ quote\\"')
1818
})
1919

2020
test('$variables should be escaped', () => {
21-
expect(escape('$var')).toBe('\\$var')
21+
expect(backslashEscape('$var')).toBe('\\$var')
2222
})
2323
})
2424

action.yml

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ inputs:
2222
description: |
2323
Enables listing of files matching the filter:
2424
'none' - Disables listing of matching files (default).
25+
'csv' - Coma separated list of filenames.
26+
If needed it uses double quotes to wrap filename with unsafe characters.
2527
'json' - Serialized as JSON array.
2628
'shell' - Space delimited list usable as command line argument list in linux shell.
2729
If needed it uses single or double quotes to wrap filename with unsafe characters.

dist/index.js

+70-40
Original file line numberDiff line numberDiff line change
@@ -4648,7 +4648,8 @@ const github = __importStar(__webpack_require__(469));
46484648
const filter_1 = __webpack_require__(235);
46494649
const file_1 = __webpack_require__(258);
46504650
const git = __importStar(__webpack_require__(136));
4651-
const shell_escape_1 = __webpack_require__(751);
4651+
const shell_escape_1 = __webpack_require__(206);
4652+
const csv_escape_1 = __webpack_require__(410);
46524653
async function run() {
46534654
try {
46544655
const workingDirectory = core.getInput('working-directory', { required: false });
@@ -4825,18 +4826,20 @@ function exportResults(results, format) {
48254826
function serializeExport(files, format) {
48264827
const fileNames = files.map(file => file.filename);
48274828
switch (format) {
4829+
case 'csv':
4830+
return fileNames.map(csv_escape_1.csvEscape).join(',');
48284831
case 'json':
48294832
return JSON.stringify(fileNames);
48304833
case 'escape':
4831-
return fileNames.map(shell_escape_1.escape).join(' ');
4834+
return fileNames.map(shell_escape_1.backslashEscape).join(' ');
48324835
case 'shell':
48334836
return fileNames.map(shell_escape_1.shellEscape).join(' ');
48344837
default:
48354838
return '';
48364839
}
48374840
}
48384841
function isExportFormat(value) {
4839-
return value === 'none' || value === 'shell' || value === 'json' || value === 'escape';
4842+
return ['none', 'csv', 'shell', 'json', 'escape'].includes(value);
48404843
}
48414844
run();
48424845

@@ -5028,6 +5031,43 @@ module.exports = {
50285031
};
50295032

50305033

5034+
/***/ }),
5035+
5036+
/***/ 206:
5037+
/***/ (function(__unusedmodule, exports) {
5038+
5039+
"use strict";
5040+
5041+
Object.defineProperty(exports, "__esModule", { value: true });
5042+
exports.shellEscape = exports.backslashEscape = void 0;
5043+
// Backslash escape every character except small subset of definitely safe characters
5044+
function backslashEscape(value) {
5045+
return value.replace(/([^a-zA-Z0-9,._+:@%/-])/gm, '\\$1');
5046+
}
5047+
exports.backslashEscape = backslashEscape;
5048+
// Returns filename escaped for usage as shell argument.
5049+
// Applies "human readable" approach with as few escaping applied as possible
5050+
function shellEscape(value) {
5051+
if (value === '')
5052+
return value;
5053+
// Only safe characters
5054+
if (/^[a-zA-Z0-9,._+:@%/-]+$/m.test(value)) {
5055+
return value;
5056+
}
5057+
if (value.includes("'")) {
5058+
// Only safe characters, single quotes and white-spaces
5059+
if (/^[a-zA-Z0-9,._+:@%/'\s-]+$/m.test(value)) {
5060+
return `"${value}"`;
5061+
}
5062+
// Split by single quote and apply escaping recursively
5063+
return value.split("'").map(shellEscape).join("\\'");
5064+
}
5065+
// Contains some unsafe characters but no single quote
5066+
return `'${value}'`;
5067+
}
5068+
exports.shellEscape = shellEscape;
5069+
5070+
50315071
/***/ }),
50325072

50335073
/***/ 211:
@@ -8813,6 +8853,33 @@ function Octokit(plugins, options) {
88138853
}
88148854

88158855

8856+
/***/ }),
8857+
8858+
/***/ 410:
8859+
/***/ (function(__unusedmodule, exports) {
8860+
8861+
"use strict";
8862+
8863+
Object.defineProperty(exports, "__esModule", { value: true });
8864+
exports.csvEscape = void 0;
8865+
// Returns filename escaped for CSV
8866+
// Wraps file name into "..." only when it contains some potentially unsafe character
8867+
function csvEscape(value) {
8868+
if (value === '')
8869+
return value;
8870+
// Only safe characters
8871+
if (/^[a-zA-Z0-9._+:@%/-]+$/m.test(value)) {
8872+
return value;
8873+
}
8874+
// https://tools.ietf.org/html/rfc4180
8875+
// If double-quotes are used to enclose fields, then a double-quote
8876+
// appearing inside a field must be escaped by preceding it with
8877+
// another double quote
8878+
return `"${value.replace(/"/g, '""')}"`;
8879+
}
8880+
exports.csvEscape = csvEscape;
8881+
8882+
88168883
/***/ }),
88178884

88188885
/***/ 413:
@@ -15225,43 +15292,6 @@ function sync (path, options) {
1522515292

1522615293
module.exports = require("fs");
1522715294

15228-
/***/ }),
15229-
15230-
/***/ 751:
15231-
/***/ (function(__unusedmodule, exports) {
15232-
15233-
"use strict";
15234-
15235-
Object.defineProperty(exports, "__esModule", { value: true });
15236-
exports.shellEscape = exports.escape = void 0;
15237-
// Backslash escape every character except small subset of definitely safe characters
15238-
function escape(value) {
15239-
return value.replace(/([^a-zA-Z0-9,._+:@%/-])/gm, '\\$1');
15240-
}
15241-
exports.escape = escape;
15242-
// Returns filename escaped for usage as shell argument.
15243-
// Applies "human readable" approach with as few escaping applied as possible
15244-
function shellEscape(value) {
15245-
if (value === '')
15246-
return value;
15247-
// Only safe characters
15248-
if (/^[a-zA-Z0-9,._+:@%/-]+$/m.test(value)) {
15249-
return value;
15250-
}
15251-
if (value.includes("'")) {
15252-
// Only safe characters, single quotes and white-spaces
15253-
if (/^[a-zA-Z0-9,._+:@%/'\s-]+$/m.test(value)) {
15254-
return `"${value}"`;
15255-
}
15256-
// Split by single quote and apply escaping recursively
15257-
return value.split("'").map(shellEscape).join("\\'");
15258-
}
15259-
// Contains some unsafe characters but no single quote
15260-
return `'${value}'`;
15261-
}
15262-
exports.shellEscape = shellEscape;
15263-
15264-
1526515295
/***/ }),
1526615296

1526715297
/***/ 753:

src/list-format/csv-escape.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Returns filename escaped for CSV
2+
// Wraps file name into "..." only when it contains some potentially unsafe character
3+
export function csvEscape(value: string): string {
4+
if (value === '') return value
5+
6+
// Only safe characters
7+
if (/^[a-zA-Z0-9._+:@%/-]+$/m.test(value)) {
8+
return value
9+
}
10+
11+
// https://tools.ietf.org/html/rfc4180
12+
// If double-quotes are used to enclose fields, then a double-quote
13+
// appearing inside a field must be escaped by preceding it with
14+
// another double quote
15+
return `"${value.replace(/"/g, '""')}"`
16+
}

src/shell-escape.ts src/list-format/shell-escape.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Backslash escape every character except small subset of definitely safe characters
2-
export function escape(value: string): string {
2+
export function backslashEscape(value: string): string {
33
return value.replace(/([^a-zA-Z0-9,._+:@%/-])/gm, '\\$1')
44
}
55

src/main.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import {Webhooks} from '@octokit/webhooks'
66
import {Filter, FilterResults} from './filter'
77
import {File, ChangeStatus} from './file'
88
import * as git from './git'
9-
import {escape, shellEscape} from './shell-escape'
9+
import {backslashEscape, shellEscape} from './list-format/shell-escape'
10+
import {csvEscape} from './list-format/csv-escape'
1011

11-
type ExportFormat = 'none' | 'json' | 'shell' | 'escape'
12+
type ExportFormat = 'none' | 'csv' | 'json' | 'shell' | 'escape'
1213

1314
async function run(): Promise<void> {
1415
try {
@@ -210,10 +211,12 @@ function exportResults(results: FilterResults, format: ExportFormat): void {
210211
function serializeExport(files: File[], format: ExportFormat): string {
211212
const fileNames = files.map(file => file.filename)
212213
switch (format) {
214+
case 'csv':
215+
return fileNames.map(csvEscape).join(',')
213216
case 'json':
214217
return JSON.stringify(fileNames)
215218
case 'escape':
216-
return fileNames.map(escape).join(' ')
219+
return fileNames.map(backslashEscape).join(' ')
217220
case 'shell':
218221
return fileNames.map(shellEscape).join(' ')
219222
default:
@@ -222,7 +225,7 @@ function serializeExport(files: File[], format: ExportFormat): string {
222225
}
223226

224227
function isExportFormat(value: string): value is ExportFormat {
225-
return value === 'none' || value === 'shell' || value === 'json' || value === 'escape'
228+
return ['none', 'csv', 'shell', 'json', 'escape'].includes(value)
226229
}
227230

228231
run()

0 commit comments

Comments
 (0)