Files
ols-ucentral-schema/generate-example.uc
Jo-Philipp Wich b5531dd0a0 generate-reader, generate-example: introduce new uc-portrange format
The `uc-portrange` string format will match single ports and ranges
in `minport-maxport` notation.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
2022-04-20 12:44:17 +02:00

524 lines
12 KiB
Ucode
Executable File

#!/usr/bin/env -S ucode -R
let fs = require("fs");
let math = require("math");
// words sourced from https://github.com/imsky/wordlists/
let verbs = [
'access', 'address', 'bookmark', 'browse', 'build', 'chat', 'click', 'close', 'code', 'compile', 'connect',
'debug', 'deploy', 'develop', 'download', 'downsize', 'drag', 'evangelize', 'execute', 'flub', 'generate',
'insource', 'install', 'link', 'load', 'log', 'make', 'marinate', 'message', 'noodle', 'onboard', 'open',
'paper', 'patch', 'ping', 'populate', 'post', 'productize', 'receive', 'release', 'resonate', 'run', 'scroll',
'search', 'send', 'share', 'step', 'sunset', 'surface', 'task', 'transition', 'triangulate', 'type', 'unpack',
'upload', 'vector', 'view', 'visit'
];
let nouns = [
'admin', 'agreeance', 'alignment', 'application', 'architecture', 'argument', 'availability', 'backburner',
'bandwidth', 'baseline', 'benefit', 'block', 'boondoggle', 'brass', 'buy-in', 'capital', 'chain', 'channel',
'code', 'comment', 'community', 'content', 'convergence', 'coopetition', 'cowboy', 'creative', 'data', 'deck',
'deliverable', 'delta', 'dialogue', 'disconnect', 'dog', 'empowerment', 'experience', 'expertise', 'float',
'flunky', 'function', 'functionality', 'gatekeeper', 'gofer', 'goldbricker', 'hardball', 'idea', 'ideation',
'imperative', 'improvement', 'infomediary', 'information', 'infrastructure', 'initiative', 'innovation',
'integer', 'interface', 'issue', 'item', 'keyword', 'kicker', 'kudos', 'language', 'leadership', 'learning',
'line', 'linkage', 'literal', 'market', 'material', 'method', 'methodology', 'metrics', 'mindshare', 'model',
'name', 'network', 'niche', 'number', 'object', 'opportunity', 'ownership', 'paradigm', 'parameter',
'partnership', 'pivot', 'platform', 'portal', 'potentiality', 'practice', 'procedure', 'process', 'product',
'pushback', 'relationship', 'report', 'resource', 'result', 'runway', 'scenario', 'schema', 'scope', 'scrub',
'service', 'shrink', 'sidebar', 'silo', 'skill', 'skillset', 'snippet', 'solution', 'source', 'space',
'strategy', 'string', 'synergy', 'syntax', 'system', 'talent', 'technology', 'type', 'upside', 'value',
'vector', 'verbiage', 'vision'
];
let adjectives = [
'absolute', 'achromatic', 'acoustic', 'adiabatic', 'alternating', 'atomic', 'binding', 'brownian', 'buoyant',
'central', 'chief', 'chromatic', 'closed', 'coherent', 'corporate', 'critical', 'customer', 'dense', 'direct',
'direct', 'district', 'dynamic', 'electric', 'electrical', 'endothermic', 'exothermic', 'forward', 'free',
'fundamental', 'future', 'global', 'gravitational', 'human', 'internal', 'internal', 'international',
'isobaric', 'isochoric', 'isothermal', 'kinetic', 'latent', 'lead', 'legacy', 'magnetic', 'mechanical',
'national', 'natural', 'nuclear', 'open', 'optical', 'potential', 'primary', 'principal', 'progressive',
'quantum', 'radiant', 'radioactive', 'rectilinear', 'regional', 'relative', 'resolving', 'resonnt',
'resultant', 'rigid', 'senior', 'volumetric'
];
function is_array(val)
{
return (type(val) == "array" && length(val) > 0);
}
function random_string(len)
{
let chars = [];
for (let i = 0; i < len; i++) {
let b = math.rand() % 52;
push(chars, (b > 26 ? 65 : 97) + (b % 26));
}
return chr(...chars);
}
function random_item(array)
{
if (type(array) != "array")
return null;
return array[math.rand() % length(array)];
}
function random_phrase(min, max) {
let str, len;
let prefix = '';
while (true) {
let verb = random_item(verbs),
adjective, noun;
while (true) {
adjective = random_item(adjectives);
if (adjective != verb)
break;
}
while (true) {
noun = random_item(nouns);
if (noun != verb && noun != adjective)
break;
}
str = prefix + verb + '-' + adjective + '-' + noun;
len = length(str);
if (len < min) {
prefix = str + '-';
continue;
}
if (len > max)
str = substr(str, 0, max);
break;
}
return str;
}
function random_ip4addr() {
return sprintf(random_item([
'10.%d.%d.%d',
'172.16.%d.%d',
'172.17.%d.%d',
'172.18.%d.%d',
'172.19.%d.%d',
'172.20.%d.%d',
'172.21.%d.%d',
'172.22.%d.%d',
'172.23.%d.%d',
'172.24.%d.%d',
'172.25.%d.%d',
'172.26.%d.%d',
'172.27.%d.%d',
'172.28.%d.%d',
'172.29.%d.%d',
'172.30.%d.%d',
'172.31.%d.%d',
'192.168.%d.%d'
]), math.rand() % 256, math.rand() % 256, math.rand() % 256);
}
function random_ip6addr() {
return sprintf('2001:db8:%x:%x:%x:%x:%x:%x',
math.rand() % 0x10000, math.rand() % 0x10000, math.rand() % 0x10000,
math.rand() % 0x10000, math.rand() % 0x10000, math.rand() % 0x10000);
}
function random_hostname() {
return random_phrase(0, Infinity) + '.example.org';
}
function random_email() {
return random_phrase(0, Infinity) + '@example.org';
}
function random_uri() {
return 'https://example.org/' + random_phrase(0, Infinity);
}
function random_value(kind, minLength, maxLength) {
switch (kind) {
case 'ipv4':
return random_ip4addr();
case 'ipv6':
return random_ip6addr();
case 'email':
case 'idn-email':
return random_email();
case 'hostname':
case 'idn-hostname':
case 'uc-fqdn':
return random_hostname();
case 'uri':
case 'uri-reference':
case 'iri':
case 'iri-reference':
return random_uri();
case 'uc-ip':
switch (math.rand() % 2) {
case 0: return random_ip4addr();
case 1: return random_ip6addr();
}
break;
case 'uc-host':
switch (math.rand() % 3) {
case 0: return random_hostname();
case 1: return random_ip4addr();
case 2: return random_ip6addr();
}
break;
case 'uc-mac':
return sprintf('02:%02x:%02x:%02x:%02x:%02x',
math.rand() % 256, math.rand() % 256,
math.rand() % 256, math.rand() % 256,
math.rand() % 256);
case 'uc-cidr4':
return sprintf('%s/%d',
random_ip4addr(), math.rand() % 33);
case 'uc-cidr6':
return sprintf('%s/%d',
random_ip6addr(), math.rand() % 129);
case 'uc-cidr':
switch (math.rand() % 2) {
case 0: return sprintf('%s/%d', random_ip4addr(), math.rand() % 33);
case 1: return sprintf('%s/%d', random_ip6addr(), math.rand() % 129);
}
break;
case 'uc-base64':
return b64enc(random_phrase(50, 100));
case 'uc-portrange':
switch (math.rand() % 2) {
case 0: return sprintf('%d', math.rand() % 65536);
case 1: return join('-', sort([ math.rand() % 65536, math.rand() % 65536 ]));
}
break;
default:
if (minLength <= 15 && maxLength >= 20)
return random_phrase(minLength, maxLength);
else
return random_string(minLength + (math.rand() % ((maxLength - minLength) + 1)));
}
}
let GeneratorProto = {
type_keywords: {
number: [
'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum',
'multipleOf'
],
string: [
'pattern', 'format', 'minLength', 'maxLength'
],
array: [
'minItems', 'maxItems', 'items'
],
object: [
'additionalProperties', 'minProperties', 'maxProperties',
'properties', 'propertyNames'
]
},
is_ref: function(value)
{
return (
type(value) == "object" &&
exists(value, "$ref") &&
//length(value) == 1 &&
type(value["$ref"]) == "string"
) ? value["$ref"] : null;
},
is_number: function(value)
{
if (type(value) == "int" || type(value) == "double")
return value;
return NaN;
},
lookup_ptr: function(ptr)
{
let m = match(ptr, /^([^#]*)#(\/.*)$/);
if (!m)
return null;
// Can't resolve cross-document references yet
if (m[1] != "")
return null;
let ref = null;
for (let idx, part in split(m[2], "/")) {
part = replace(part, /~[01]/g, (m) => (m == "~0" ? "~" : "/" ));
if (idx == 0)
ref = this.schema;
else if (type(ref) == "object" || type(ref) == "array")
ref = ref[part];
else
return null;
}
return ref;
},
infer_type: function(valueSpec)
{
if (exists(valueSpec, 'type')) {
if (type(valueSpec.type) == 'array')
return random_item(valueSpec.type);
return valueSpec.type;
}
for (let type, keywords in this.type_keywords) {
for (let keyword in keywords)
if (exists(valueSpec, keyword))
return type;
}
return null;
},
emit_generic: function(objectSpec)
{
if (is_array(objectSpec.enum))
return random_item(objectSpec.enum);
if (exists(objectSpec, "const"))
return objectSpec.const;
return NaN;
},
emit_object: function(objectSpec)
{
let object = {};
if (type(objectSpec.properties) == "object") {
for (let propertyName, propertySpec in objectSpec.properties) {
let value = this.emit_spec(propertySpec);
object[propertyName] = value;
}
}
return object;
},
emit_array: function(arraySpec)
{
let array = [];
if (type(arraySpec.items) == "object") {
let len = 3;
if (type(arraySpec.minItems) == "int" && arraySpec.minItems > len)
len = arraySpec.minItems;
if (type(arraySpec.maxItems) == "int" && arraySpec.maxItems < len)
len = arraySpec.maxItems;
if (is_array(arraySpec.items.enum) && length(arraySpec.items.enum) < len)
len = length(arraySpec.items.enum);
let examples = [ ...(is_array(arraySpec.items.examples) ? arraySpec.items.examples : []) ];
for (let i = 0; i < len; i++) {
let item;
if (length(examples)) {
item = random_item(examples);
examples = filter(examples, (i) => i != item);
}
else {
item = this.emit_spec(arraySpec.items, true);
}
push(array, item);
}
}
return array;
},
emit_string: function(stringSpec, noExample)
{
let rv = this.emit_generic(stringSpec);
if (rv == rv)
return rv;
if (!noExample && is_array(stringSpec.examples))
return random_item(stringSpec.examples);
else if (exists(stringSpec, "default"))
return stringSpec.default;
let minLength = 0;
let maxLength = 256;
if (type(stringSpec.minLength) == "int")
minLength = stringSpec.minLength;
if (type(stringSpec.maxLength) == "int")
maxLength = stringSpec.maxLength;
return random_value(stringSpec.format, minLength, maxLength);
},
emit_number: function(numberSpec)
{
let rv = this.emit_generic(numberSpec);
if (rv == rv)
return rv;
let multiplicator = 1;
if (type(numberSpec.default) == "double" ||
length(filter(numberSpec.examples, i => type(i) == "double")))
multiplicator = 0.1;
if (this.is_number(numberSpec.multipleOf) > 0)
multiplicator = numberSpec.multipleOf;
let min = this.is_number(numberSpec.minimum);
let max = this.is_number(numberSpec.maximum);
let exMin = this.is_number(numberSpec.exclusiveMinimum);
let exMax = this.is_number(numberSpec.exclusiveMaximum);
if (is_array(numberSpec.examples))
return random_item(numberSpec.examples);
while (true) {
let n = multiplicator * math.rand();
if (max == max)
n = n % max;
else if (exMax == exMax)
n = n % exMax;
if (min == min && n < min)
continue;
if (exMin == exMin && n <= min)
continue;
return n;
}
},
emit_boolean: function(boolSpec)
{
let rv = this.emit_generic(boolSpec);
if (rv == rv)
return rv;
if (is_array(boolSpec.examples))
return random_item(boolSpec.examples);
else if (exists(boolSpec, "default"))
return (boolSpec.default == true);
return ((math.rand() % 2) == 0);
},
emit_spec: function(valueSpec, noExample)
{
let ref = this.is_ref(valueSpec);
if (ref) {
let refValueSpec = this.lookup_ptr(ref);
assert(type(refValueSpec) == "object",
"$ref value '" + ref + "' points to non-object value");
valueSpec = { ...refValueSpec, ...valueSpec };
}
let alternatives = valueSpec.anyOf || valueSpec.allOf || valueSpec.oneOf;
if (type(alternatives) == 'array' && length(alternatives) > 0)
return this.emit_spec(alternatives[math.rand() % length(alternatives)], noExample);
switch (this.infer_type(valueSpec)) {
case "object":
return this.emit_object(valueSpec);
case "array":
return this.emit_array(valueSpec);
case "string":
return this.emit_string(valueSpec, noExample);
case "integer":
case "number":
return this.emit_number(valueSpec);
case "boolean":
return this.emit_boolean(valueSpec);
default:
warn("Unknown type " + valueSpec.type + " (" + valueSpec + ")\n");
}
},
read_schema: function(path)
{
let fd = fs.open(path, "r");
if (!fd)
return {};
let schema = json(fd.read("all"));
fd.close();
return schema;
},
generate: function()
{
this.schema = this.read_schema(this.path);
return this.emit_spec(this.schema);
}
};
function createGenerator(path) {
return proto({ path }, GeneratorProto);
}
let generator = createGenerator("./ucentral.schema.pretty.json");
let document = generator.generate();
printf("%.J\n", document);