blob: 30afa1e086782d8df7bf8a5e2b73ba096a052609 [file] [log] [blame]
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use size file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://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.
import {
Args,
ArgsTree,
ArgsTreeArray,
ArgsTreeMap,
isArgTreeArray,
isArgTreeMap,
} from '../common/arg_types';
// Converts a flats sequence of key-value pairs into a JSON-like nested
// structure. Dots in keys are used to create a nested dictionary, indices in
// brackets used to create nested array. For example, consider the following
// sequence of key-value pairs:
//
// simple_key = simple_value
// thing.key = value
// thing.point[0].x = 10
// thing.point[0].y = 20
// thing.point[1].x = 0
// thing.point[1].y = -10
//
// It's going to be converted to a following object:
//
// {
// "simple_key": "simple_value",
// "thing": {
// "key": "value",
// "point": [
// { "x": "10", "y": "20" },
// { "x": "0", "y": "-10" }
// ]
// }
// }
export function parseArgs(args: Args): ArgsTree|undefined {
const result: ArgsTreeMap = {};
for (const [key, value] of args) {
if (typeof value === 'string') {
fillObject(result, key.split('.'), value);
}
}
return result;
}
function getOrCreateMap(
object: ArgsTreeMap|ArgsTreeArray, key: string|number): ArgsTreeMap {
let value: ArgsTree;
if (isArgTreeMap(object) && typeof key === 'string') {
value = object[key];
} else if (isArgTreeArray(object) && typeof key === 'number') {
value = object[key];
} else {
throw new Error('incompatible parameters to getOrCreateSubmap');
}
if (value !== undefined) {
if (isArgTreeMap(value)) {
return value;
} else {
// There is a value, but it's not a map - something wrong with the key set
throw new Error('inconsistent keys');
}
}
value = {};
if (isArgTreeMap(object) && typeof key === 'string') {
object[key] = value;
} else if (isArgTreeArray(object) && typeof key === 'number') {
object[key] = value;
}
return value;
}
function getOrCreateArray(object: ArgsTreeMap, key: string): ArgsTree[] {
let value = object[key];
if (value !== undefined) {
if (isArgTreeArray(value)) {
return value;
} else {
// There is a value, but it's not an array - something wrong with the key
// set
throw new Error('inconsistent keys');
}
}
value = [];
object[key] = value;
return value;
}
function fillObject(object: ArgsTreeMap, path: string[], value: string) {
let current = object;
for (let i = 0; i < path.length - 1; i++) {
const [part, index] = parsePathSegment(path[i]);
if (index === undefined) {
current = getOrCreateMap(current, part);
} else {
const array = getOrCreateArray(current, part);
current = getOrCreateMap(array, index);
}
}
const [part, index] = parsePathSegment(path[path.length - 1]);
if (index === undefined) {
current[part] = value;
} else {
const array = getOrCreateArray(current, part);
array[index] = value;
}
}
// Segment is either a simple key (e.g. "foo") or a key with an index (e.g.
// "bar[42]"). This function returns a pair of key and index (if present).
function parsePathSegment(segment: string): [string, number?] {
if (!segment.endsWith(']')) {
return [segment, undefined];
}
const indexStart = segment.indexOf('[');
const indexString = segment.substring(indexStart + 1, segment.length - 1);
return [segment.substring(0, indexStart), Math.floor(Number(indexString))];
}