mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-10-31 18:38:06 +00:00
429 lines
14 KiB
Python
429 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2018-present, Facebook, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the BSD-style license found in the
|
|
# LICENSE file in the root directory of this source tree. An additional grant
|
|
# of patent rights can be found in the PATENTS file in the same directory.
|
|
#
|
|
|
|
import re
|
|
import json
|
|
import jsonschema as js
|
|
|
|
|
|
class SchemaUtils:
|
|
draftFiles = {'d0' : 'meta_sys_schema_d0.json',
|
|
'd4' : 'meta_sys_schema_d4.json',
|
|
'd6' : 'meta_sys_schema_d6.json'}
|
|
|
|
def __init__(self, md, json_fname, c_fname, draft):
|
|
"""
|
|
Load meta schema and configuration schema
|
|
"""
|
|
self.draft = draft
|
|
self.valid = False
|
|
self.json_fname = json_fname
|
|
self.c_fname = c_fname
|
|
self.sys_schema = ''
|
|
self.md = md
|
|
# Meta schema: this shouldn't change except when either a new JSON
|
|
# draft requires it, or when the hardware is redesigned
|
|
self.metaschema = json.load(open(SchemaUtils.draftFiles[draft], 'r'))
|
|
|
|
if md != 'g':
|
|
# JSON definition file exists and we want to produce the
|
|
# corresponding C-file
|
|
|
|
# Configuration schema: this can change any time device
|
|
# configuration changes
|
|
self.cfgschema = json.load(open(self.json_fname, 'r'))
|
|
else:
|
|
# Want to create JSON definition file from a C-file
|
|
# containing a schema declared as a structure
|
|
self.cfgschema = []
|
|
|
|
def generate(self):
|
|
new_schema = []
|
|
with open(self.c_fname, 'r') as f:
|
|
in_schema = False
|
|
# Find start of schema declaration and extract
|
|
for line in f:
|
|
# find start of schema declaration
|
|
if (not in_schema) and ('sys_schema' in line):
|
|
in_schema = True
|
|
# Skip first line
|
|
new_schema.append('[\n')
|
|
continue
|
|
if in_schema:
|
|
new_schema.append(line)
|
|
schema = ''.join(str(line) for line in new_schema)
|
|
|
|
# Remove all C language features
|
|
schema = SchemaUtils.stripC(schema)
|
|
# Find terminations to array declarations
|
|
schema = SchemaUtils.fixarray(schema)
|
|
# JSON line formatting
|
|
schema = SchemaUtils.parse(schema, 4)
|
|
# JSON punctuation
|
|
self.sys_schema = SchemaUtils.punct(schema)
|
|
|
|
def auto_file(self):
|
|
"""
|
|
Write C-file from validated schema
|
|
"""
|
|
# Remove all JSON format features
|
|
schema, decls = SchemaUtils.strip(self.sys_schema)
|
|
# Format values as required for each data type
|
|
schema = SchemaUtils.typeformat(schema)
|
|
# Write C-file
|
|
SchemaUtils.writeSchema(decls, schema, self.c_fname)
|
|
|
|
def dump(self):
|
|
"""
|
|
Save the validated schema
|
|
"""
|
|
if self.md != 'g':
|
|
# convert the data dictionary to a string
|
|
if self.valid:
|
|
self.sys_schema = json.dumps(self.cfgschema, indent=4)
|
|
#
|
|
v_fname = 'valid_schema.json'
|
|
msg = 'Saved validated schema to ' + v_fname
|
|
else:
|
|
v_fname = self.json_fname
|
|
msg = 'Saved auto generated schema to ' + v_fname
|
|
|
|
with open(v_fname, 'w') as f:
|
|
f.write(self.sys_schema)
|
|
f.close()
|
|
print(msg)
|
|
|
|
def validate(self):
|
|
"""
|
|
Check for errors in either instance or schema
|
|
"""
|
|
# Sample errors:
|
|
# self.metaschema['definitions']['Subcomponent']['required'] = []
|
|
# self.cfgschema[0]['name'] = []
|
|
try:
|
|
try:
|
|
if (self.draft == 'd4') or (self.draft == 'd0'):
|
|
js.Draft4Validator.check_schema(self.metaschema)
|
|
else:
|
|
# not supported until jsonschema 3.0
|
|
js.Draft6Validator.check_schema(self.metaschema)
|
|
print('Meta-schema OK')
|
|
except js.SchemaError as e:
|
|
print('Error in meta-schema:' + str(e))
|
|
except Exception():
|
|
raise
|
|
|
|
try:
|
|
js.validate(self.cfgschema, self.metaschema)
|
|
print('Configuration schema validated')
|
|
except js.ValidationError as e:
|
|
print('Error in schema configuration:' + str(e))
|
|
except Exception():
|
|
raise
|
|
except Exception() as e:
|
|
print(e)
|
|
self.valid = False
|
|
else:
|
|
self.valid = True
|
|
return self.valid
|
|
|
|
@staticmethod
|
|
def declares(text):
|
|
"""
|
|
Select fields for which either a declaration or a prototype is necessary
|
|
"""
|
|
setA = set()
|
|
setC = set()
|
|
setP = set()
|
|
for line in text.split('\n'):
|
|
words = line.split()
|
|
if 'driver_cfg' in line:
|
|
setA.add(words[-1])
|
|
if 'factory_config' in line:
|
|
setC.add(words[-1])
|
|
if 'null' not in line.lower():
|
|
if 'preInitFxn' in line:
|
|
setP.add(words[-1])
|
|
if 'postInitFxn' in line:
|
|
setP.add(words[-1])
|
|
if 'cb_cmd' in line:
|
|
setP.add(words[-1])
|
|
|
|
qual1 = 'SCHEMA_IMPORT DriverStruct '
|
|
lines1 = [qual1 + value + ';' for value in iter(setA)]
|
|
lines1.sort()
|
|
str1 = '\n'.join(str(line) for line in lines1)
|
|
|
|
qual2 = 'SCHEMA_IMPORT const DriverStruct '
|
|
lines2 = [qual2 + value + ';' for value in iter(setC)]
|
|
lines2.sort()
|
|
str2 = '\n'.join(str(line) for line in lines2)
|
|
|
|
qual3 = 'SCHEMA_IMPORT bool '
|
|
qual4 = '(void *, void *)'
|
|
lines3 = [qual3 + value + qual4 + ';' for value in iter(setP)]
|
|
lines3.sort(key=str.lower)
|
|
str3 = '\n'.join(str(line) for line in lines3)
|
|
|
|
decls = str1 + '\n\n' + str2 + '\n\n' + str3 + '\n\n'
|
|
return decls
|
|
|
|
@staticmethod
|
|
def strip(text):
|
|
"""
|
|
Remove JSON format features
|
|
"""
|
|
# Find and remove all double quotes
|
|
p = re.compile(r'\"')
|
|
result = p.sub('', text)
|
|
|
|
# Find and replace all colons
|
|
result = re.sub(r':', ' =', result)
|
|
|
|
# Find and replace all commas
|
|
result = re.sub(r',', '', result)
|
|
|
|
# Find values for which a declaration is required
|
|
decls = SchemaUtils.declares(result)
|
|
|
|
# Format structure elements
|
|
result = re.sub(r'name', '.name', result)
|
|
result = re.sub(r'components', '.components', result)
|
|
result = re.sub(r'\bdriver\b', '.driver', result)
|
|
result = re.sub(r'\bdriver_cfg\b', '.driver_cfg', result)
|
|
result = re.sub(r'factory_config', '.factory_config', result)
|
|
result = re.sub(r'commands', '.commands', result)
|
|
result = re.sub(r'ssHookSet', '.ssHookSet', result)
|
|
result = re.sub(r'postDisabled', '.postDisabled', result)
|
|
result = re.sub(r'cb_cmd', '.cb_cmd', result)
|
|
result = re.sub(r'preInitFxn', '.preInitFxn', result)
|
|
result = re.sub(r'postInitFxn', '.postInitFxn', result)
|
|
return result, decls
|
|
|
|
@staticmethod
|
|
def typeformat(text):
|
|
"""
|
|
Format each element type as required
|
|
"""
|
|
newschema = []
|
|
for line in text.split('\n'):
|
|
words = line.split()
|
|
if '.name' in line:
|
|
newline = line.replace(words[-1], '"' + words[-1] + '"')
|
|
elif '.driver' in line:
|
|
newline = line.replace(words[-1], '&' + words[-1])
|
|
elif '.factory_config' in line:
|
|
newline = line.replace(words[-1], '&' + words[-1])
|
|
elif '.components' in line:
|
|
newline = line.replace('[', '(Component[]) {')
|
|
elif '.commands' in line:
|
|
newline = line.replace('[', '(Command[]) {')
|
|
elif '.ssHookSet' in line:
|
|
newline = line.replace('{', '&(SSHookSet) {')
|
|
elif '.preInitFxn' in line and 'NULL' not in line:
|
|
newline = line.replace(words[-1], '(ssHook_Cb)' + words[-1])
|
|
elif '.postInitFxn' in line and 'NULL' not in line:
|
|
newline = line.replace(words[-1], '(ssHook_Cb)' + words[-1])
|
|
elif ']' in line:
|
|
n = line.find(']') + 4
|
|
newschema.append(' ' * n + '{}')
|
|
newline = line.replace(']', '}')
|
|
else:
|
|
newline = line
|
|
|
|
if '{' in newline:
|
|
newschema.append(newline)
|
|
else:
|
|
newschema.append(newline + ',')
|
|
|
|
# Set first line correctly
|
|
newschema[0] = 'const Component sys_schema[] = {'
|
|
# Set last line correctly
|
|
newschema[-1] = '};'
|
|
|
|
return '\n'.join(str(line) for line in newschema)
|
|
|
|
@staticmethod
|
|
def writeSchema(decls, schema, c_fname):
|
|
"""
|
|
Save schema to .c file
|
|
"""
|
|
f = open(c_fname, 'w')
|
|
cpw = []
|
|
cpw.append('/**')
|
|
cpw.append('* Copyright (c) 2018-present, Facebook, Inc.')
|
|
cpw.append('* All rights reserved.')
|
|
cpw.append('*')
|
|
cpw.append('* This source code is licensed under the BSD-style license')
|
|
cpw.append('* found in the LICENSE file in the root directory of this')
|
|
cpw.append('* source tree. An additional grant of patent rights can be')
|
|
cpw.append('* found in the PATENTS file in the same directory.')
|
|
cpw.append('*')
|
|
cpw.append('* WARNING: Do not modify this file by hand. It is auto')
|
|
cpw.append('* generated from the json schema definition.')
|
|
cpw.append('* Refer to sdtester.py')
|
|
cpw.append('*/\n')
|
|
cpw.append('#include "auto_schema.h"\n\n')
|
|
f.write('\n'.join(line for line in cpw))
|
|
f.write(decls)
|
|
f.write('\n')
|
|
f.write(schema)
|
|
f.write('\n')
|
|
f.close()
|
|
print('Saved schema to ' + c_fname)
|
|
|
|
@staticmethod
|
|
def stripC(text):
|
|
"""
|
|
Using regular expressions, strip schema struct from
|
|
C-languange features
|
|
"""
|
|
# Find and remove C-style comments, single line and multiline
|
|
p = re.compile(r'/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/')
|
|
result = p.sub('', text)
|
|
# Find and remove all dots
|
|
result = re.sub(r'\.', '', result)
|
|
# Find and remove all double quotes
|
|
result = re.sub(r'\"', '', result)
|
|
# Find and remove all commas
|
|
result = re.sub(r',', '', result)
|
|
# Find and replace all =
|
|
result = re.sub(r'=', ' : ', result)
|
|
# Find and remove all ampersands
|
|
result = re.sub(r'&', '', result)
|
|
# Find and remove all {}
|
|
result = re.sub(r'{}', '', result)
|
|
# Find and remove all semicolons
|
|
result = re.sub(r';', '', result)
|
|
# Find and remove all empty lines
|
|
result = re.sub(r'\n\s*\n', '\n', result)
|
|
# Find and remove all array declarations
|
|
p = re.compile(r'\(\w*\[\]\)\s?\{')
|
|
result = p.sub('[', result)
|
|
# Find and remove all type casts
|
|
p = re.compile(r'\(\w*\)')
|
|
result = p.sub('', result)
|
|
#
|
|
return result
|
|
|
|
@staticmethod
|
|
def fixarray(text):
|
|
"""
|
|
Use a stack to find where array declarations should end
|
|
"""
|
|
opers = []
|
|
schema = []
|
|
for line in text.split('\n'):
|
|
modline = line
|
|
if '{' in line:
|
|
opers.append('{')
|
|
if '[' in line:
|
|
opers.append('[')
|
|
if '}' in line:
|
|
if opers.pop() == '[':
|
|
modline = line.replace('}', ']')
|
|
schema.append(modline)
|
|
|
|
return '\n'.join(str(line) for line in schema)
|
|
|
|
@staticmethod
|
|
def parse(text, ident):
|
|
"""
|
|
Apply indentation levels by parsing through schema
|
|
"""
|
|
tab = ident * ' '
|
|
depth = 0
|
|
schema = []
|
|
for line in text.split('\n'):
|
|
indent, depth = SchemaUtils.stack(line, depth)
|
|
modline = SchemaUtils.fields(line)
|
|
newline = indent * tab + modline
|
|
schema.append(newline)
|
|
|
|
return '\n'.join(str(line) for line in schema)
|
|
|
|
@staticmethod
|
|
def punct(text):
|
|
"""
|
|
JSON punctuation
|
|
"""
|
|
schema = []
|
|
lines = text.split('\n')
|
|
n = len(lines) - 1
|
|
for i in range(n):
|
|
# No comma in starting bracket or brace
|
|
if ('{' in lines[i]) or ('[' in lines[i]):
|
|
modline = lines[i]
|
|
# Comma in objects, except last in a group
|
|
elif (':' in lines[i]) and ('}' not in lines[i + 1]):
|
|
modline = lines[i] + ','
|
|
# No comma in a last array member
|
|
elif ('}' in lines[i]) and \
|
|
(('}' in lines[i + 1]) or (']' in lines[i + 1])):
|
|
modline = lines[i]
|
|
# Comma after object member of an array
|
|
# Comma after array member of an object
|
|
elif (('}' in lines[i]) or (']' in lines[i])) and \
|
|
(('{' in lines[i + 1]) or ('[' in lines[i + 1]) or
|
|
(':' in lines[i + 1])):
|
|
modline = lines[i] + ','
|
|
else:
|
|
modline = lines[i]
|
|
schema.append(modline)
|
|
schema.append(lines[-1])
|
|
return '\n'.join(str(line) for line in schema)
|
|
|
|
@staticmethod
|
|
def stack(line, depth):
|
|
"""
|
|
Compute indentation level
|
|
"""
|
|
if ('{' in line) or ('[' in line):
|
|
indent = depth
|
|
depth += 1
|
|
elif ('}' in line) or (']' in line):
|
|
depth -= 1
|
|
indent = depth
|
|
else:
|
|
depth = depth
|
|
indent = depth
|
|
return indent, depth
|
|
|
|
@staticmethod
|
|
def fields(line):
|
|
"""
|
|
Apply JSON formatting to the keys and values
|
|
"""
|
|
modline = ''
|
|
for word in line.split():
|
|
modline = modline + SchemaUtils.compose(word)
|
|
return modline
|
|
|
|
@staticmethod
|
|
def compose(word):
|
|
"""
|
|
Correct use of quotes and spaces
|
|
"""
|
|
if SchemaUtils.wordOK(word):
|
|
newword = '"' + word + '"'
|
|
elif word == ':':
|
|
newword = ' : '
|
|
else:
|
|
newword = word
|
|
return newword
|
|
|
|
@staticmethod
|
|
def wordOK(word):
|
|
"""
|
|
Check for non-words
|
|
"""
|
|
return word != '{' and word != '}' and \
|
|
word != '[' and word != ']' and word != ':'
|