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

Feature: cache array support #239

15 changes: 15 additions & 0 deletions docs/features/PROJECT-CACHE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ echo "myKey=myValue >> VELOCITAS_CACHE"
print("myKey=myValue >> VELOCITAS_CACHE")
```

#### String array support
The cache write supports string array values as well. The elements need to be double quoted and comma separated.

Examples:

**Bash**
```bash
echo 'myKey=["myValue", "myValue2"] >> VELOCITAS_CACHE'
```

**Python 3**
```python
print('myKey=["myValue", "myValue2"] >> VELOCITAS_CACHE')
```

### Reading values

To read values from the cache, programs may read the `VELOCITAS_CACHE_DATA` environment variable. It contains a the entire cache data as JSON-string.
Expand Down
19 changes: 4 additions & 15 deletions src/modules/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,15 @@ import { join, resolve } from 'node:path';
import { ExecSpec, ProgramSpec } from './component';
import { ProjectCache } from './project-cache';
import { ProjectConfig } from './project-config';

const CACHE_OUTPUT_REGEX: RegExp = /(\w+)\s*=\s*(\'.*?\'|\".*?\"|\w+)\s+\>\>\s+VELOCITAS_CACHE/;
import { stdOutParser } from './stdout-parser';

const lineCapturer = (projectCache: ProjectCache, writeStdout: boolean, data: string) => {
if (writeStdout) {
process.stdout.write(data);
}
for (let line of data.toString().split('\n')) {
let lineTrimmed = (line as string).trim();

if (lineTrimmed.length === 0) {
continue;
}
const result = CACHE_OUTPUT_REGEX.exec(lineTrimmed);
if (result && result.length > 0) {
const key = result[1];
const value = result[2].replaceAll("'", '').replaceAll('"', '');
projectCache.set(key, value);
}
}
data.toString()
.split('\n')
.forEach((value) => stdOutParser(projectCache, value));
};

export function setSpawnImplementation(func: (command: string, args: string | string[], options: any) => IPty) {
Expand Down
42 changes: 42 additions & 0 deletions src/modules/stdout-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2024 Contributors to the Eclipse Foundation
//
// This program and the accompanying materials are made available under the
// terms of the Apache License, Version 2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// SPDX-License-Identifier: Apache-2.0

import { info } from 'console';
import { ProjectCache } from './project-cache';

const CACHE_OUTPUT_REGEX: RegExp =
/(\w+)\s*=\s*(\[((\'(\/*\w+)*\',\s*|(\"(\/*\w+)*\",\s*)|(\/*\w+)*,\s*|\'(\/*\w+)*\'(?=\])|\"(\/*\w+)*\"(?=\])|(\/*\w+)*(?=\]))*\])|(\'.*?\'|\".*?\"|\w+))\s+\>\>\s+VELOCITAS_CACHE/;

export function stdOutParser(projectCache: ProjectCache, line: string) {
let lineTrimmed = (line as string).trim();

if (lineTrimmed.length === 0) {
return;
}
const match = CACHE_OUTPUT_REGEX.exec(line);
if (match && match.length > 0) {
const [_ignored, key, value] = match;

Check warning on line 29 in src/modules/stdout-parser.ts

View workflow job for this annotation

GitHub Actions / cli-tests (ubuntu-22.04, lts/Iron)

Variable name `_ignored` must match one of the following formats: camelCase, UPPER_CASE

Check warning on line 29 in src/modules/stdout-parser.ts

View workflow job for this annotation

GitHub Actions / cli-tests (ubuntu-22.04, 21.7.1)

Variable name `_ignored` must match one of the following formats: camelCase, UPPER_CASE
lukasmittag marked this conversation as resolved.
Show resolved Hide resolved
const cleanedValue = value.replace(/['"]/g, '');
info(cleanedValue);
info(key);
lukasmittag marked this conversation as resolved.
Show resolved Hide resolved
if (cleanedValue.startsWith('[') && cleanedValue.endsWith(']')) {
const arrayPart = cleanedValue.substring(1, cleanedValue.length - 1);
const array = arrayPart.split(',');
const trimmedArray = array.map((str) => str.trim());
projectCache.set(key, trimmedArray);
} else {
projectCache.set(key, cleanedValue);
}
}
}
19 changes: 11 additions & 8 deletions src/modules/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ export class VariableCollection {

for (const [key, value] of this._variables.entries()) {
const transformedKey = key.replaceAll('.', '_');

const transformedValue = Array.isArray(value) ? JSON.stringify(value) : value;
Object.assign(envVars, {
[transformedKey]: value,
[transformedKey]: transformedValue,
});
}

Expand Down Expand Up @@ -196,17 +196,20 @@ function verifyGivenVariables(

for (const componentExposedVariable of variableDefinitions) {
const configuredValue = configuredVars.get(componentExposedVariable.name);
const errorMsg = `'${componentExposedVariable.name}' has wrong type! Expected ${
componentExposedVariable.type
} but got ${typeof configuredValue}`;
if (!configuredValue) {
if (componentExposedVariable.default === undefined) {
missingVars.push(componentExposedVariable);
}
} else {
if (typeof configuredValue !== componentExposedVariable.type) {
wronglyTypedVars.push(
`'${componentExposedVariable.name}' has wrong type! Expected ${
componentExposedVariable.type
} but got ${typeof configuredValue}`,
);
if (componentExposedVariable.type === 'array') {
if (!Array.isArray(configuredValue)) {
wronglyTypedVars.push(errorMsg);
}
} else if (typeof configuredValue !== componentExposedVariable.type) {
wronglyTypedVars.push(errorMsg);
}
configuredVars.delete(componentExposedVariable.name);
}
Expand Down
7 changes: 6 additions & 1 deletion test/system-test/exec.stest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ describe('CLI command', () => {

it('should be able to let programs get cache values', () => {
const result = spawnSync(VELOCITAS_PROCESS, ['exec', 'test-component', 'get-cache'], { encoding: DEFAULT_BUFFER_ENCODING });

expect(result.stdout).to.contain('my_cache_key');
expect(result.stdout).to.contain('my_cache_value');
expect(result.stdout).to.contain('foo');
Expand All @@ -81,6 +80,12 @@ describe('CLI command', () => {
expect(result.stdout).to.contain('random');
expect(result.stdout).to.not.contain('var');
expect(result.stdout).to.not.contain('asdc');
expect(result.stdout).to.contain('arr1');
expect(result.stdout).to.contain("['/path/test1', '/path/test2']");
expect(result.stdout).to.contain('arr2');
expect(result.stdout).to.contain("['/path/test1', '/path/test2']");
expect(result.stdout).to.contain('arr3');
expect(result.stdout).to.contain("['/path/test1', '/path/test2']");
});

it('should be able to run programs which read from stdin', () => {
Expand Down
69 changes: 69 additions & 0 deletions test/unit/stdout-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Contributors to the Eclipse Foundation
//
// This program and the accompanying materials are made available under the
// terms of the Apache License, Version 2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// SPDX-License-Identifier: Apache-2.0

import { expect } from 'chai';
import { ProjectCache } from '../../src/modules/project-cache';
import { stdOutParser } from '../../src/modules/stdout-parser';
import { CliFileSystem, MockFileSystem, MockFileSystemObj } from '../../src/utils/fs-bridge';
import { getCacheData } from '../helpers/cache';

describe('stdOutParser - module', () => {
var matches = new Map<string, any>([
lukasmittag marked this conversation as resolved.
Show resolved Hide resolved
['test_1 = "/this/is/path/one" >> VELOCITAS_CACHE', '/this/is/path/one'],
["test_2 = '/this/is/path/one' >> VELOCITAS_CACHE", '/this/is/path/one'],
['test_3 = test3 >> VELOCITAS_CACHE', 'test3'],
['test_4 = ["/this/is/path/one", "/this/is/path/two"] >> VELOCITAS_CACHE', ['/this/is/path/one', '/this/is/path/two']],
['test_5 = ["/this/is/path/one","/this/is/path/two"] >> VELOCITAS_CACHE', ['/this/is/path/one', '/this/is/path/two']],
['test_6 = ["/this/is/path/one", "/this/is/path/two"] >> VELOCITAS_CACHE', ['/this/is/path/one', '/this/is/path/two']],
['test_7 =["/this/is/path/one", "/this/is/path/two"] >> VELOCITAS_CACHE', ['/this/is/path/one', '/this/is/path/two']],
['test_8=["/this/is/path/one", "/this/is/path/two"] >> VELOCITAS_CACHE', ['/this/is/path/one', '/this/is/path/two']],
["test_9 = ['/this/is/path/one', /this/is/path/two] >> VELOCITAS_CACHE", ['/this/is/path/one', '/this/is/path/two']],
['test_10 = [/this/is/path/one, "/this/is/path/two"] >> VELOCITAS_CACHE', ['/this/is/path/one', '/this/is/path/two']],
['test_11 = [/this/is/path/one, /this/is/path/two] >> VELOCITAS_CACHE', ['/this/is/path/one', '/this/is/path/two']],
]);

before(() => {
const projectCacheDir = ProjectCache.getCacheDir();
const mockFilesystem: MockFileSystemObj = {
[`${projectCacheDir}/cache.json`]: '{}',
};
CliFileSystem.setImpl(new MockFileSystem(mockFilesystem));
});
for (let [element, result] of matches) {
it('should match and set project cache correct', () => {
let id = element.split('=')[0].trim();
const projectCache = ProjectCache.read();
stdOutParser(projectCache, element);
expect(JSON.stringify(projectCache.get(id))).to.equal(JSON.stringify(result));
});
}

var noMatches = new Map<string, any>([
['test_1 = "/this/is/path/one >> VELOCITAS_CACHE', {}],
["test_2 = '/this/is/path/one >> VELOCITAS_CACHE", {}],
['test_3 = /this/is/path/one"this/test >> VELOCITAS_CACHE', {}],
['test_4 = ["/this/is/path/one", /this/is/path/two"]', {}],
['test_5 = ["/this/is/path/one""/this/is/path/two"] >> VELOCITAS_CACHE', {}],
['"test_6" = ["/this/is/path/one", "/this/is/path/two"] >> VELOCITAS_CACHE', {}],
['test_7 = [/this/is/path/one""/this/is/path/two"] >> VELOCITAS_CACHE', {}],
["test_8 = ['/this/is/path/one''/this/is/path/two'] >> VELOCITAS_CACHE", {}],
]);
noMatches.forEach((result, element) => {
it('should not match for wrong inputs', async () => {
const projectCache = ProjectCache.read();
stdOutParser(projectCache, element);
expect(JSON.stringify(getCacheData())).to.equal(JSON.stringify(result));
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@
print("foo = bar >> VELOCITAS_CACHE")
print("x=y >> VELOCITAS_CACHE")
print("0123='random' >> VELOCITAS_CACHE")
print('arr1=["/path/test1", "/path/test2"] >> VELOCITAS_CACHE')
print('arr2=["/path/test1" ,"/path/test2"] >> VELOCITAS_CACHE')
lukasmittag marked this conversation as resolved.
Show resolved Hide resolved
print('arr3=["/path/test1","/path/test2"] >> VELOCITAS_CACHE')
print("var=\"asdc' >> VELOCITAS_CACHE")
Loading