From 736ac59095be27d42d26074bf02163cbce9af02c Mon Sep 17 00:00:00 2001 From: ShahanaFarooqui Date: Mon, 15 Jan 2024 17:20:55 -0800 Subject: [PATCH] doc: Update scripts - Update `make doc-all` script - Update `fromschema.py` script to update `.md` files --- doc/Makefile | 87 ++++++------ tools/fromschema.py | 330 +++++++++++++++++++++++++++++++------------- 2 files changed, 282 insertions(+), 135 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index 9e6ce8cf4026..a919823aed37 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -4,11 +4,8 @@ doc-wrongdir: $(MAKE) -C .. doc-all -MANPAGES := doc/lightning-cli.1 \ - doc/lightningd.8 \ - doc/lightningd-config.5 \ - doc/lightningd-rpc.7 \ - doc/lightning-addgossip.7 \ +GENERATE_MARKDOWN := doc/lightning-addgossip.7 \ + doc/lightning-addpsbtoutput.7 \ doc/lightning-autoclean-once.7 \ doc/lightning-autoclean-status.7 \ doc/lightning-batching.7 \ @@ -23,20 +20,20 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-checkmessage.7 \ doc/lightning-checkrune.7 \ doc/lightning-close.7 \ - doc/lightning-connect.7 \ - doc/lightning-commando.7 \ doc/lightning-commando-blacklist.7 \ + doc/lightning-commando.7 \ doc/lightning-commando-listrunes.7 \ doc/lightning-commando-rune.7 \ - doc/lightning-createonion.7 \ + doc/lightning-connect.7 \ doc/lightning-createinvoice.7 \ + doc/lightning-createonion.7 \ doc/lightning-createrune.7 \ doc/lightning-datastore.7 \ doc/lightning-datastoreusage.7 \ - doc/lightning-decodepay.7 \ doc/lightning-decode.7 \ + doc/lightning-decodepay.7 \ doc/lightning-deldatastore.7 \ - doc/lightning-delexpiredinvoice.7 \ + doc/lightning-delexpiredinvoice.7 \ doc/lightning-delforward.7 \ doc/lightning-delinvoice.7 \ doc/lightning-delpay.7 \ @@ -46,32 +43,35 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-emergencyrecover.7 \ doc/lightning-feerates.7 \ doc/lightning-fetchinvoice.7 \ + doc/lightning-fundchannel_cancel.7 \ + doc/lightning-fundchannel_complete.7 \ doc/lightning-fundchannel.7 \ doc/lightning-fundchannel_start.7 \ - doc/lightning-fundchannel_complete.7 \ - doc/lightning-fundchannel_cancel.7 \ doc/lightning-funderupdate.7 \ - doc/lightning-addpsbtoutput.7 \ doc/lightning-fundpsbt.7 \ + doc/lightning-getinfo.7 \ + doc/lightning-getlog.7 \ doc/lightning-getroute.7 \ - doc/lightning-hsmtool.8 \ + doc/lightning-help.7 \ doc/lightning-invoice.7 \ doc/lightning-invoicerequest.7 \ doc/lightning-keysend.7 \ doc/lightning-listchannels.7 \ doc/lightning-listclosedchannels.7 \ + doc/lightning-listconfigs.7 \ doc/lightning-listdatastore.7 \ doc/lightning-listforwards.7 \ doc/lightning-listfunds.7 \ doc/lightning-listhtlcs.7 \ - doc/lightning-listinvoices.7 \ doc/lightning-listinvoicerequests.7 \ + doc/lightning-listinvoices.7 \ + doc/lightning-listnodes.7 \ doc/lightning-listoffers.7 \ doc/lightning-listpays.7 \ - doc/lightning-listpeers.7 \ doc/lightning-listpeerchannels.7 \ - doc/lightning-showrunes.7 \ + doc/lightning-listpeers.7 \ doc/lightning-listsendpays.7 \ + doc/lightning-listtransactions.7 \ doc/lightning-makesecret.7 \ doc/lightning-multifundchannel.7 \ doc/lightning-multiwithdraw.7 \ @@ -83,61 +83,67 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-openchannel_init.7 \ doc/lightning-openchannel_signed.7 \ doc/lightning-openchannel_update.7 \ - doc/lightning-pay.7 \ doc/lightning-parsefeerate.7 \ + doc/lightning-pay.7 \ + doc/lightning-ping.7 \ doc/lightning-plugin.7 \ doc/lightning-preapproveinvoice.7 \ doc/lightning-preapprovekeysend.7 \ - doc/lightning-recover.7 \ doc/lightning-recoverchannel.7 \ + doc/lightning-recover.7 \ doc/lightning-renepay.7 \ doc/lightning-renepaystatus.7 \ doc/lightning-reserveinputs.7 \ + doc/lightning-sendcustommsg.7 \ doc/lightning-sendinvoice.7 \ doc/lightning-sendonion.7 \ doc/lightning-sendonionmessage.7 \ doc/lightning-sendpay.7 \ + doc/lightning-sendpsbt.7 \ doc/lightning-setchannel.7 \ doc/lightning-setconfig.7 \ doc/lightning-setpsbtversion.7 \ - doc/lightning-sendcustommsg.7 \ + doc/lightning-showrunes.7 \ doc/lightning-signinvoice.7 \ doc/lightning-signmessage.7 \ + doc/lightning-signpsbt.7 \ doc/lightning-splice_init.7 \ - doc/lightning-splice_update.7 \ doc/lightning-splice_signed.7 \ - doc/lightning-staticbackup.7 \ - doc/lightning-txprepare.7 \ + doc/lightning-splice_update.7 \ + doc/lightning-staticbackup.7 \ + doc/lightning-stop.7 \ doc/lightning-txdiscard.7 \ + doc/lightning-txprepare.7 \ doc/lightning-txsend.7 \ doc/lightning-unreserveinputs.7 \ + doc/lightning-upgradewallet.7 \ doc/lightning-utxopsbt.7 \ - doc/lightning-wait.7 \ - doc/lightning-waitinvoice.7 \ doc/lightning-waitanyinvoice.7 \ doc/lightning-waitblockheight.7 \ + doc/lightning-waitinvoice.7 \ + doc/lightning-wait.7 \ doc/lightning-waitsendpay.7 \ - doc/lightning-withdraw.7 \ - doc/lightning-ping.7 \ - doc/lightning-stop.7 \ - doc/lightning-signpsbt.7 \ - doc/lightning-sendpsbt.7 \ - doc/lightning-getinfo.7 \ - doc/lightning-listtransactions.7 \ - doc/lightning-listnodes.7 \ - doc/lightning-listconfigs.7 \ - doc/lightning-help.7 \ - doc/lightning-getlog.7 \ + doc/lightning-withdraw.7 + +MANPAGES := $(GENERATE_MARKDOWN) \ + doc/lightning-cli.1 \ + doc/lightningd.8 \ + doc/lightningd-config.5 \ + doc/lightningd-rpc.7 \ + doc/lightning-hsmtool.8 \ doc/reckless.7 ifeq ($(HAVE_SQLITE3),1) +GENERATE_MARKDOWN += doc/lightning-listsqlschemas.7 MANPAGES += doc/lightning-listsqlschemas.7 \ doc/lightning-sql.7 endif +MARKDOWN_WITH_SCHEMA := $(GENERATE_MARKDOWN:=.md) + doc-all: $(MANPAGES) doc/index.rst -SCHEMAS := $(wildcard doc/schemas/*.json) +SCHEMAS := $(wildcard doc/schemas/lightning-*.json) check-fmt-schemas: $(SCHEMAS:%=check-fmt-schema/%) fmt-schemas: $(SCHEMAS:%=fmt-schema/%) @@ -149,15 +155,12 @@ fmt-schema/%: % check-doc: check-config-docs check-manpages check-fmt-schemas -# Some manpages use a schema, so need that added. -MARKDOWN_WITH_SCHEMA := $(shell grep -l GENERATE-FROM-SCHEMA $(MANPAGES:=.md)) - # These are hard to use in $(call) functions. LBRACKET=( RBRACKET=) -$(MARKDOWN_WITH_SCHEMA): doc/lightning-%.7.md: doc/schemas/%.schema.json tools/fromschema.py - @if $(call SHA256STAMP_CHANGED); then $(call VERBOSE, "fromschema $@", tools/fromschema.py --markdownfile=$@ doc/schemas/$*.schema.json > $@.tmp && grep -v SHA256STAMP: $@.tmp > $@ && rm -f $@.tmp && $(call SHA256STAMP,[comment]: # $(LBRACKET),$(RBRACKET))); else touch $@; fi +$(MARKDOWN_WITH_SCHEMA): doc/lightning-%.7.md: doc/schemas/lightning-%.json tools/fromschema.py + @if $(call SHA256STAMP_CHANGED); then $(call VERBOSE, "fromschema $@", tools/fromschema.py --markdownfile=$@ doc/schemas/lightning-$*.json > $@.tmp && grep -v SHA256STAMP: $@.tmp > $@ && rm -f $@.tmp && $(call SHA256STAMP,[comment]: # $(LBRACKET),$(RBRACKET))); else touch $@; fi # If we need to build lowdown, make tools/md2man.sh depend on it. # That way it's not used in SHA256STAMP (which only uses direct diff --git a/tools/fromschema.py b/tools/fromschema.py index e312ac617642..a66081830832 100755 --- a/tools/fromschema.py +++ b/tools/fromschema.py @@ -6,6 +6,15 @@ import json import re +BODY_KEY_SEQUENCE = ['reliability', 'usage', 'restriction_format', 'example_usage', 'example_json_request', 'notes', 'notifications', 'sharing_runes', 'riskfactor_effect_on_routing', 'recommended_riskfactor_values', 'optimality', 'randomization'] +FOOTER_KEY_SEQUENCE = ['example_json_response', 'errors', 'example_json_notifications', 'trivia', 'author', 'see_also', 'resources'] + + +def output_title(title, underline='-', num_leading_newlines=1, num_trailing_newlines=2): + """Add a title to the output""" + print('\n' * num_leading_newlines + title, end='\n') + print(underline * len(title) + '\n' * num_trailing_newlines, end='') + def esc_underscores(s): """Backslash-escape underscores outside of backtick-enclosed spans""" @@ -25,9 +34,9 @@ def json_value(obj): assert False -def outputs(lines): +def outputs(lines, separator=''): """Add these lines to the final output""" - print(''.join(lines), end='') + print(separator.join(lines), end='') def output(line): @@ -36,39 +45,63 @@ def output(line): def output_type(properties, is_optional): + """Add types for request and reponse parameters""" + if 'oneOf' in properties: + properties['type'] = 'one of' typename = esc_underscores(properties['type']) if typename == 'array': - typename += ' of {}s'.format(esc_underscores(properties['items']['type'])) + if 'items' in properties and 'type' in properties['items']: + typename += ' of {}s'.format(esc_underscores(properties['items']['type'])) if is_optional: - typename += ", optional" - output(" ({})".format(typename)) + typename += ', optional' + output(' ({})'.format(typename)) -def output_range(properties): +def output_range(properties, brackets=True): if 'maximum' and 'minimum' in properties: - output(" ({} to {} inclusive)".format(properties['minimum'], + output(' ({} to {} inclusive)'.format(properties['minimum'], properties['maximum'])) elif 'maximum' in properties: - output(" (max {})".format(properties['maximum'])) + output(' (max {})'.format(properties['maximum'])) elif 'minimum' in properties: - output(" (min {})".format(properties['minimum'])) + output(' (min {})'.format(properties['minimum'])) if 'maxLength' and 'minLength' in properties: if properties['minLength'] == properties['maxLength']: - output(' (always {} characters)'.format(properties['minLength'])) + if brackets is True: + output(' (always {} characters)'.format(properties['minLength'])) + else: + output('always {} characters'.format(properties['minLength'])) else: - output(' ({} to {} characters)'.format(properties['minLength'], - properties['maxLength'])) + if brackets is True: + output(' ({} to {} characters)'.format(properties['minLength'], properties['maxLength'])) + else: + output('{} to {} characters'.format(properties['minLength'], properties['maxLength'])) elif 'maxLength' in properties: - output(' (up to {} characters)'.format(properties['maxLength'])) + if brackets is True: + output(' (up to {} characters)'.format(properties['maxLength'])) + else: + output('up to {} characters'.format(properties['maxLength'])) elif 'minLength' in properties: - output(' (at least {} characters)'.format(properties['minLength'])) + if brackets is True: + output(' (at least {} characters)'.format(properties['minLength'])) + else: + output('at least {} characters'.format(properties['minLength'])) if 'enum' in properties: if len(properties['enum']) == 1: - output(" (always {})".format(json_value(properties['enum'][0]))) + if brackets is True: + output(' (always {})'.format(json_value(properties['enum'][0]))) + else: + output('always {}'.format(json_value(properties['enum'][0]))) else: - output(' (one of {})'.format(', '.join([json_value(p) for p in properties['enum']]))) + if brackets is True: + output(' (one of {})'.format(', '.join([json_value(p) for p in properties['enum']]))) + else: + output('one of {}'.format(', '.join([json_value(p) for p in properties['enum']]))) + + if list(properties.keys()) == ['type'] and properties['type'] == 'string': + output('(' + properties['type'] + ')') def fmt_propname(propname): @@ -76,6 +109,11 @@ def fmt_propname(propname): return '**{}**'.format(esc_underscores(propname)) +def fmt_paramname(paramname, is_optional=True): + """Pretty-print format a parameter name""" + return ' [*{}*]'.format(esc_underscores(paramname)) if is_optional else ' *{}*'.format(esc_underscores(paramname)) + + def deprecated_to_deleted(vername): """We promise a 6 month minumum deprecation period, and versions are every 3 months""" assert vername.startswith('v') @@ -93,9 +131,8 @@ def deprecated_to_deleted(vername): def output_member(propname, properties, is_optional, indent, print_type=True, prefix=None): """Generate description line(s) for this member""" - if prefix is None: - prefix = '- ' + fmt_propname(propname) + prefix = '- ' + fmt_propname(propname) if propname is not None else '- ' output(indent + prefix) # We make them explicitly note if they don't want a type! @@ -104,17 +141,26 @@ def output_member(propname, properties, is_optional, indent, print_type=True, pr if not is_untyped and print_type: output_type(properties, is_optional) - if 'description' in properties: - output(": {}".format(esc_underscores(properties['description']))) - output_range(properties) + if 'description' in properties: + if type(properties['description']) is list: + for i in range(0, len(properties['description'])): + output('{} {}{}'.format(':' if i == 0 else '', esc_underscores(properties['description'][i]), '' if i + 1 == len(properties['description']) else '\n')) + else: + output(': {}'.format(esc_underscores(properties['description']))) + if 'deprecated' in properties: - output(" **deprecated, removal in {}**".format(deprecated_to_deleted(properties['deprecated']))) + # Information on when the field was deprecated + output(' **deprecated, removal in {}**'.format(deprecated_to_deleted(properties['deprecated']))) if 'added' in properties: - output(" *(added {})*".format(properties['added'])) + # Version when the field was added + output(' *(added {})*'.format(properties['added'])) - if not is_untyped and properties['type'] == 'object': + if 'oneOf' in properties and isinstance(properties['oneOf'], list): + output(':\n') + output_members(properties, indent + ' ') + elif not is_untyped and properties['type'] == 'object': output(':\n') output_members(properties, indent + ' ') elif not is_untyped and properties['type'] == 'array': @@ -126,13 +172,22 @@ def output_member(propname, properties, is_optional, indent, print_type=True, pr def output_array(items, indent): """We've already said it's an array of {type}""" - if items['type'] == 'object': + if 'oneOf' in items and isinstance(items['oneOf'], list): + output_members(items, indent + ' ') + elif list(items.keys()) == ['type']: + output(indent + '- ' + items['type'] + '\n') + elif items['type'] == 'object': output_members(items, indent) elif items['type'] == 'array': - output(indent + '- {}:\n'.format(esc_underscores(items['description']))) - output_array(items['items'], indent + ' ') + output(indent + '-') + output_type(items, False) + output(' {}\n'.format(esc_underscores(items['description'])) if 'description' in items and items['description'] != '' else '\n') + if 'items' in items: + output_array(items['items'], indent + ' ') else: - output(indent + '- {}'.format(esc_underscores(items['description']))) + if 'description' in items and items['description'] != '': + output(indent + '-') + output(' {}'.format(esc_underscores(items['description']))) output_range(items) output('\n') @@ -151,7 +206,6 @@ def has_members(sub): def output_members(sub, indent=''): """Generate lines for these properties""" warnings = [] - # Remove deprecated: True and stub properties, collect warnings # (Stubs required to keep additionalProperties: false happy) @@ -165,32 +219,41 @@ def output_members(sub, indent=''): # } # } # Checkout the schema of `staticbackup`. - for p in list(sub['properties'].keys()): - if len(sub['properties'][p]) == 0 or sub['properties'][p].get('deprecated') is True: - del sub['properties'][p] - elif p.startswith('warning'): - warnings.append(p) - - # First list always-present properties - for p in sub['properties']: - if p.startswith('warning'): - continue - if 'required' in sub and p in sub['required']: - output_member(p, sub['properties'][p], False, indent) - - for p in sub['properties']: - if p.startswith('warning'): - continue - if 'required' not in sub or p not in sub['required']: - output_member(p, sub['properties'][p], True, indent) + if 'properties' in sub: + for p in list(sub['properties'].keys()): + if len(sub['properties'][p]) == 0 or sub['properties'][p].get('deprecated') is True: + del sub['properties'][p] + elif p.startswith('warning'): + warnings.append(p) + + # First list always-present properties + for p in sub['properties']: + if p.startswith('warning'): + continue + if 'required' in sub and p in sub['required']: + output_member(p, sub['properties'][p], False, indent) + + for p in sub['properties']: + if p.startswith('warning'): + continue + if 'required' not in sub or p not in sub['required']: + output_member(p, sub['properties'][p], True, indent) if warnings != []: - output(indent + "- the following warnings are possible:\n") + output(indent + '- the following warnings are possible:\n') for w in warnings: output_member(w, sub['properties'][w], False, indent + ' ', print_type=False) - # Not handled. - assert 'oneOf' not in sub + if 'oneOf' in sub: + for oneOfItem in sub['oneOf']: + if 'type' in oneOfItem and oneOfItem['type'] == 'object': + output_member(None, oneOfItem, False, indent, '-') + elif 'type' in oneOfItem and oneOfItem['type'] == 'array': + output_array(oneOfItem, indent) + elif 'type' in oneOfItem and oneOfItem['type'] == 'string': + output(indent + '- ') + output_range(oneOfItem, False) + output('\n') # If we have multiple ifs, we have to wrap them in allOf. if 'allOf' in sub: @@ -204,43 +267,110 @@ def output_members(sub, indent=''): for ifclause in ifclauses: conditions = [] - # "required" are fields that simply must be present + # 'required' are fields that simply must be present for r in ifclause['if'].get('required', []): conditions.append(fmt_propname(r) + ' is present') - # "properties" are enums of field values + # 'properties' are enums of field values for tag, vals in ifclause['if'].get('properties', {}).items(): # Don't have a description field here, it's not used. assert 'description' not in vals whichvalues = vals['enum'] - cond = fmt_propname(tag) + " is" + cond = fmt_propname(tag) + ' is' if len(whichvalues) == 1: - cond += " {}".format(json_value(whichvalues[0])) + cond += ' {}'.format(json_value(whichvalues[0])) else: - cond += " {} or {}".format(", ".join([json_value(v) for v in whichvalues[:-1]]), + cond += ' {} or {}'.format(', '.join([json_value(v) for v in whichvalues[:-1]]), json_value(whichvalues[-1])) conditions.append(cond) - sentence = indent + "If " + ", and ".join(conditions) + ":\n\n" + sentence = indent + 'If ' + ', and '.join(conditions) + ':\n' if has_members(ifclause['then']): # Prefix with blank line. outputs(['\n', sentence]) - output_members(ifclause['then'], indent + ' ') -def generate_from_schema(schema): +def generate_header(schema): + """Generate lines for rpc title and synopsis with request parameters""" + output_title(''.join(['lightning-', schema['rpc'], ' -- ', schema['title']]), '=', 0, 1) + output_title('SYNOPSIS') + # Add command level warning if exists + if 'warning' in schema: + output('**(WARNING: {})**\n\n'.format(schema['warning'])) + # generate the rpc command details with request parameters + request = schema['request'] + toplevels = list(request['properties'].keys()) + output('{}'.format(fmt_propname(schema['rpc']))) + for p in toplevels: + output('{}'.format(fmt_paramname(p, False if 'required' in request and p in request['required'] else True))) + output('\n') + + +def generate_description(schema): + """Generate rpc description with request parameter descriptions""" + request = schema['request'] + props = request['properties'] + toplevels = list(props.keys()) + indent = '' + output_title('DESCRIPTION') + # Add deprecated and removal information for the command + if 'deprecated' in schema: + output('Command **deprecated, removal in {}**.\n\n'.format(deprecated_to_deleted(schema['deprecated']))) + # Version when the command was added + if 'added' in schema: + output('Command *added* in {}.\n\n'.format(schema['added'])) + # Command's detailed description + outputs(schema['description'], '\n') + # Request parameter's detailed description + if len(props) > 0: + output('\n\n') + if toplevels == []: + sub = schema['request'] + elif len(toplevels) == 1 and 'oneOf' in props[toplevels[0]] and isinstance(props[toplevels[0]]['oneOf'], list): + output('{}'.format(fmt_propname(toplevels[0]))) + output_type(props[toplevels[0]], False if toplevels[0] in request['required'] else True) + output('\n') + indent = indent + ' ' + sub = props[toplevels[0]] + elif len(toplevels) == 1 and props[toplevels[0]]['type'] == 'object': + output('{}\n'.format(fmt_propname(toplevels[0]))) + assert 'description' not in toplevels[0] + sub = props[toplevels[0]] + elif len(toplevels) == 1 and props[toplevels[0]]['type'] == 'array' and props[toplevels[0]]['items']['type'] == 'object': + output('{}\n'.format(fmt_propname(toplevels[0]))) + assert 'description' not in toplevels[0] + sub = props[toplevels[0]]['items'] + else: + sub = schema['request'] + output_members(sub, indent) + else: + output('\n') + + +def generate_return_value(schema): """This is not general, but works for us""" - if schema['type'] != 'object': - # 'stop' returns a single string! - output_member(None, schema, False, '', prefix='On success, returns a single element') + output_title('RETURN VALUE') + + response = schema['response'] + + if 'pre_return_value_notes' in response: + outputs(response['pre_return_value_notes'], '\n') + output('\n\n') + + if 'properties' not in response and 'enum' in response: + # 'stop' returns a single enum string and post_return_value_notes! + output_member(None, response, False, '', prefix='On success, returns a single element') + output('\n') + if 'post_return_value_notes' in response: + outputs(response['post_return_value_notes'], '\n') return toplevels = [] warnings = [] - props = schema['properties'] + props = response['properties'] # We handle warnings on top-level objects with a separate section, # so collect them now and remove them @@ -256,63 +386,77 @@ def generate_from_schema(schema): output('On success, an empty object is returned.\n') sub = schema elif len(toplevels) == 1 and props[toplevels[0]]['type'] == 'object': - output('On success, an object containing {} is returned. It is an object containing:\n\n'.format(fmt_propname(toplevels[0]))) + output('On success, an object containing {} is returned. It is an object containing:\n\n'.format(fmt_propname(toplevels[0]))) # Don't have a description field here, it's not used. assert 'description' not in toplevels[0] sub = props[toplevels[0]] elif len(toplevels) == 1 and props[toplevels[0]]['type'] == 'array' and props[toplevels[0]]['items']['type'] == 'object': - output('On success, an object containing {} is returned. It is an array of objects, where each object contains:\n\n'.format(fmt_propname(toplevels[0]))) + output('On success, an object containing {} is returned. \nIt is an array of objects, where each object contains:\n\n'.format(fmt_propname(toplevels[0]))) # Don't have a description field here, it's not used. assert 'description' not in toplevels[0] sub = props[toplevels[0]]['items'] else: output('On success, an object is returned, containing:\n\n') - sub = schema + sub = response output_members(sub) if warnings: - outputs(['\n', 'The following warnings may also be returned:\n\n']) + output('\nThe following warnings may also be returned:\n\n') for w, desc in warnings: - output("- {}: {}\n".format(fmt_propname(w), desc)) + output('- {}: {}\n'.format(fmt_propname(w), ''.join(desc))) - # GH markdown rendering gets upset if there isn't a blank line - # between a list and the end comment. - output('\n') + if 'post_return_value_notes' in response: + output('\n') + outputs(response['post_return_value_notes'], '\n') + output('\n') -def main(schemafile, markdownfile): - start_marker = '[comment]: # (GENERATE-FROM-SCHEMA-START)\n' - end_marker = '[comment]: # (GENERATE-FROM-SCHEMA-END)\n' +def generate_body(schema): + """Output sections which should be printed after description and before return value""" + first_matching_key = True + for key in BODY_KEY_SEQUENCE: + if key not in schema: + continue + output_title(key.replace('_', ' ').upper(), '-', 1 if first_matching_key else 2) + first_matching_key = False + outputs(schema[key], '\n') + output('\n') - if markdownfile is None: - with open(schemafile, "r") as f: - schema = json.load(f) - generate_from_schema(schema) - return - with open(markdownfile, "r") as f: - md = f.readlines() +def generate_footer(schema): + """Output sections which should be printed after return value""" + first_matching_key = True + for key in FOOTER_KEY_SEQUENCE: + if key not in schema: + continue + output_title(key.replace('_', ' ').upper(), '-', 1 if first_matching_key else 2) + first_matching_key = False + outputs(schema[key], ', ' if key in ['see_also'] else '\n') + output('\n\n') - suppress_output = False - for line in md: - if line == end_marker: - suppress_output = False - if not suppress_output: - print(line, end='') +def main(schemafile, markdownfile): + with open(schemafile, 'r') as f: + schema = json.load(f) + # Outputs rpc title and synopsis with request parameters + generate_header(schema) + # Outputs command description with request parameter descriptions + generate_description(schema) + # Outputs other remaining sections before return value section + generate_body(schema) + # Outputs command response with response parameter descriptions + generate_return_value(schema) + # Outputs other remaining sections after return value section + generate_footer(schema) - if line == start_marker: - with open(schemafile, "r") as f: - schema = json.load(f) - generate_from_schema(schema) - suppress_output = True + if markdownfile is None: + return -if __name__ == "__main__": +if __name__ == '__main__': parser = ArgumentParser() parser.add_argument('schemafile', help='The schema file to use') parser.add_argument('--markdownfile', help='The markdown file to read') parsed_args = parser.parse_args() - main(parsed_args.schemafile, parsed_args.markdownfile)