mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
More docs-related commits will follow, but this needs to be merged in order to continue with other development. * Docs: Overhaul docs generator (beginning) * docs: Rename comments file * docs: Move comments gitignore * docs: Initial request documentation * docs: Improvements to comment processing * docs: More improvements * docs: Add enum functionality for protocol.json * WebSocketServer: Document enums * RequestHandler: Document RequestStatus enum * Base: Move ObsWebSocketRequestBatchExecutionType to its own file Moves it to its own file, renaming it to `RequestBatchExecutionType`. Changes the RPC to use integer values for selecting execution type instead of strings. * docs: Update introduction header Removes the enum section, and documents RequestBatchExecutionType. * WebSocketCloseCode: Shuffle a bit * Base: Use `field` instead of `key` or `parameter` in most places * RequestStatus: Mild shuffle It was really bothering me that OutputPaused and OutputNotPaused had to be separated, so we're breaking it while we're breaking other stuff. * docs: Delete old files They may be added back in some form, but for now I'm getting them out of the way. * docs: Add enum identifier value Forgot to add this before, oops * docs: Document more enums * docs: Add basic protocol.md generator * docs: More work on MD generator * docs: MD generator should be finished now * docs: More fixes * docs: More fixes * docs: More tweaks + add readme * docs: Update readme and add inputs docs * docs: More documentation
209 lines
8.4 KiB
Python
209 lines
8.4 KiB
Python
import logging
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [process_comments.py] [%(levelname)s] %(message)s")
|
|
import os
|
|
import sys
|
|
import json
|
|
|
|
# The comments parser will return a string type instead of an array if there is only one field
|
|
def field_to_array(field):
|
|
if type(field) == str:
|
|
return [field]
|
|
return field
|
|
|
|
# This raw JSON can be really damn unpredictable. Let's handle that
|
|
def field_to_string(field):
|
|
if type(field) == list:
|
|
return field_to_string(field[0])
|
|
elif type(field) == dict:
|
|
return field_to_string(field['description'])
|
|
return str(field)
|
|
|
|
# Make sure that everything we expect is there
|
|
def validate_fields(data, fields):
|
|
for field in fields:
|
|
if field not in data:
|
|
logging.warning('Missing required item: {}'.format(field))
|
|
return False
|
|
return True
|
|
|
|
# Get the individual components of a `requestField` or `responseField` or `dataField` entry
|
|
def get_components(data):
|
|
ret = []
|
|
components_raw = data.split('|')
|
|
for component in components_raw:
|
|
ret.append(component.strip())
|
|
return ret
|
|
|
|
# Convert all request fields from raw to final
|
|
def get_request_fields(fields):
|
|
fields = field_to_array(fields)
|
|
ret = []
|
|
for field in fields:
|
|
components = get_components(field)
|
|
field_out = {}
|
|
field_out['valueName'] = components[0].replace('?', '')
|
|
field_out['valueType'] = components[1]
|
|
field_out['valueDescription'] = components[2]
|
|
|
|
valueOptionalOffset = 3
|
|
# If value type is a number, restrictions are required. Else, should not be added.
|
|
if field_out['valueType'].lower() == 'number':
|
|
# In the case of a number, the optional component gets pushed back.
|
|
valueOptionalOffset += 1
|
|
field_out['valueRestrictions'] = components[3] if components[3].lower() != 'none' else None
|
|
else:
|
|
field_out['valueRestrictions'] = None
|
|
|
|
field_out['valueOptional'] = components[0].startswith('?')
|
|
if field_out['valueOptional']:
|
|
field_out['valueOptionalBehavior'] = components[valueOptionalOffset] if len(components) > valueOptionalOffset else 'Unknown'
|
|
else:
|
|
field_out['valueOptionalBehavior'] = None
|
|
ret.append(field_out)
|
|
return ret
|
|
|
|
# Convert all response (or event data) fields from raw to final
|
|
def get_response_fields(fields):
|
|
fields = field_to_array(fields)
|
|
ret = []
|
|
for field in fields:
|
|
components = get_components(field)
|
|
field_out = {}
|
|
field_out['valueName'] = components[0]
|
|
field_out['valueType'] = components[1]
|
|
field_out['valueDescription'] = components[2]
|
|
ret.append(field_out)
|
|
return ret
|
|
|
|
#######################################################################################################################
|
|
|
|
# Read versions json
|
|
try:
|
|
with open('../versions.json', 'r') as f:
|
|
versions = json.load(f)
|
|
except IOError:
|
|
logging.error('Failed to get global versions. Versions file not configured?')
|
|
os.exit(1)
|
|
|
|
# Read the raw comments output file
|
|
with open('../work/comments.json', 'r') as f:
|
|
comments_raw = json.load(f)
|
|
|
|
# Prepare output variables
|
|
enums = []
|
|
requests = []
|
|
events = []
|
|
|
|
enums_raw = {}
|
|
# Process the raw comments
|
|
for comment in comments_raw:
|
|
# Skip unrelated comments like #include
|
|
if 'api' not in comment:
|
|
continue
|
|
|
|
api = comment['api']
|
|
if api == 'enums':
|
|
if not validate_fields(comment, ['description', 'enumIdentifier', 'enumType', 'rpcVersion', 'initialVersion']):
|
|
logging.warning('Failed to process enum id comment due to missing field(s):\n{}'.format(comment))
|
|
continue
|
|
|
|
enumType = field_to_string(comment['enumType'])
|
|
|
|
enum = {}
|
|
# Recombines the header back into one string, allowing multi-line descriptions.
|
|
enum['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description'])
|
|
enum['enumIdentifier'] = field_to_string(comment['enumIdentifier'])
|
|
rpcVersionRaw = field_to_string(comment['rpcVersion'])
|
|
enum['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw)
|
|
enum['deprecated'] = False if rpcVersionRaw == '-1' else True
|
|
enum['initialVersion'] = field_to_string(comment['initialVersion'])
|
|
|
|
if 'enumValue' in comment:
|
|
enumValue = field_to_string(comment['enumValue'])
|
|
enum['enumValue'] = int(enumValue) if enumValue.isdigit() else enumValue
|
|
else:
|
|
enum['enumValue'] = None
|
|
|
|
if enumType not in enums_raw:
|
|
enums_raw[enumType] = {'enumIdentifiers': [enum]}
|
|
else:
|
|
enums_raw[enumType]['enumIdentifiers'].append(enum)
|
|
|
|
logging.info('Processed enum: {}::{}'.format(enumType, enum['enumIdentifier']))
|
|
elif api == 'requests':
|
|
if not validate_fields(comment, ['description', 'requestType', 'complexity', 'rpcVersion', 'initialVersion', 'category']):
|
|
logging.warning('Failed to process request comment due to missing field(s):\n{}'.format(comment))
|
|
continue
|
|
|
|
req = {}
|
|
req['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description'])
|
|
req['requestType'] = field_to_string(comment['requestType'])
|
|
req['complexity'] = int(field_to_string(comment['complexity']))
|
|
rpcVersionRaw = field_to_string(comment['rpcVersion'])
|
|
req['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw)
|
|
req['deprecated'] = False if rpcVersionRaw == '-1' else True
|
|
req['initialVersion'] = field_to_string(comment['initialVersion'])
|
|
req['category'] = field_to_string(comment['category'])
|
|
|
|
try:
|
|
if 'requestField' in comment:
|
|
req['requestFields'] = get_request_fields(comment['requestField'])
|
|
else:
|
|
req['requestFields'] = []
|
|
except:
|
|
logging.exception('Failed to process request `{}` request fields due to error:\n'.format(req['requestType']))
|
|
continue
|
|
|
|
try:
|
|
if 'responseField' in comment:
|
|
req['responseFields'] = get_response_fields(comment['responseField'])
|
|
else:
|
|
req['responseFields'] = []
|
|
except:
|
|
logging.exception('Failed to process request `{}` request fields due to error:\n'.format(req['requestType']))
|
|
continue
|
|
|
|
logging.info('Processed request: {}'.format(req['requestType']))
|
|
|
|
requests.append(req)
|
|
elif api == 'events':
|
|
if not validate_fields(comment, ['description', 'eventType', 'eventSubscription', 'complexity', 'rpcVersion', 'initialVersion', 'category']):
|
|
logging.warning('Failed to process event comment due to missing field(s):\n{}'.format(comment))
|
|
continue
|
|
|
|
eve = {}
|
|
eve['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description'])
|
|
eve['eventType'] = field_to_string(comment['eventType'])
|
|
eve['eventSubscription'] = field_to_string(comment['eventSubscription'])
|
|
eve['complexity'] = int(field_to_string(comment['complexity']))
|
|
rpcVersionRaw = field_to_string(comment['rpcVersion'])
|
|
eve['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw)
|
|
eve['deprecated'] = False if rpcVersionRaw == '-1' else True
|
|
eve['initialVersion'] = field_to_string(comment['initialVersion'])
|
|
eve['category'] = field_to_string(comment['category'])
|
|
|
|
try:
|
|
if 'dataField' in comment:
|
|
eve['dataFields'] = get_response_fields(comment['dataField'])
|
|
else:
|
|
eve['dataFields'] = []
|
|
except:
|
|
logging.exception('Failed to process event `{}` data fields due to error:\n'.format(req['eventType']))
|
|
continue
|
|
|
|
logging.info('Processed event: {}'.format(eve['eventType']))
|
|
|
|
events.append(eve)
|
|
else:
|
|
logging.warning('Comment with unknown api: {}'.format(api))
|
|
|
|
# Reconfigure enums to match the correct structure
|
|
for enumType in enums_raw.keys():
|
|
enum = enums_raw[enumType]
|
|
enums.append({'enumType': enumType, 'enumIdentifiers': enum['enumIdentifiers']})
|
|
|
|
finalObject = {'enums': enums, 'requests': requests, 'events': events}
|
|
|
|
with open('../generated/protocol.json', 'w') as f:
|
|
json.dump(finalObject, f, indent=2)
|