diff --git a/doc/Makefile b/doc/Makefile index 51c8d2c2b794..3a26050d30b5 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -4,134 +4,6 @@ doc-wrongdir: $(MAKE) -C .. doc-all -# ORIGINAL -# MANPAGES := doc/lightning-cli.1 \ -# doc/lightningd.8 \ -# doc/lightningd-config.5 \ -# doc/lightningd-rpc.7 \ -# doc/lightning-addgossip.7 \ -# doc/lightning-autoclean-once.7 \ -# doc/lightning-autoclean-status.7 \ -# doc/lightning-batching.7 \ -# doc/lightning-bkpr-channelsapy.7 \ -# doc/lightning-bkpr-dumpincomecsv.7 \ -# doc/lightning-bkpr-inspect.7 \ -# doc/lightning-bkpr-listaccountevents.7 \ -# doc/lightning-bkpr-listbalances.7 \ -# doc/lightning-bkpr-listincome.7 \ -# doc/lightning-blacklistrune.7 \ -# doc/lightning-check.7 \ -# 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-listrunes.7 \ -# doc/lightning-commando-rune.7 \ -# doc/lightning-createonion.7 \ -# doc/lightning-createinvoice.7 \ -# doc/lightning-createrune.7 \ -# doc/lightning-datastore.7 \ -# doc/lightning-datastoreusage.7 \ -# doc/lightning-decodepay.7 \ -# doc/lightning-decode.7 \ -# doc/lightning-deldatastore.7 \ -# doc/lightning-delexpiredinvoice.7 \ -# doc/lightning-delforward.7 \ -# doc/lightning-delinvoice.7 \ -# doc/lightning-delpay.7 \ -# doc/lightning-disableinvoicerequest.7 \ -# doc/lightning-disableoffer.7 \ -# doc/lightning-disconnect.7 \ -# doc/lightning-emergencyrecover.7 \ -# doc/lightning-feerates.7 \ -# doc/lightning-fetchinvoice.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-getroute.7 \ -# doc/lightning-hsmtool.8 \ -# doc/lightning-invoice.7 \ -# doc/lightning-invoicerequest.7 \ -# doc/lightning-keysend.7 \ -# doc/lightning-listchannels.7 \ -# doc/lightning-listclosedchannels.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-listoffers.7 \ -# doc/lightning-listpays.7 \ -# doc/lightning-listpeers.7 \ -# doc/lightning-listpeerchannels.7 \ -# doc/lightning-showrunes.7 \ -# doc/lightning-listsendpays.7 \ -# doc/lightning-makesecret.7 \ -# doc/lightning-multifundchannel.7 \ -# doc/lightning-multiwithdraw.7 \ -# doc/lightning-newaddr.7 \ -# doc/lightning-notifications.7 \ -# doc/lightning-offer.7 \ -# doc/lightning-openchannel_abort.7 \ -# doc/lightning-openchannel_bump.7 \ -# 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-plugin.7 \ -# doc/lightning-preapproveinvoice.7 \ -# doc/lightning-preapprovekeysend.7 \ -# doc/lightning-recover.7 \ -# doc/lightning-recoverchannel.7 \ -# doc/lightning-renepay.7 \ -# doc/lightning-renepaystatus.7 \ -# doc/lightning-reserveinputs.7 \ -# doc/lightning-sendinvoice.7 \ -# doc/lightning-sendonion.7 \ -# doc/lightning-sendonionmessage.7 \ -# doc/lightning-sendpay.7 \ -# doc/lightning-setchannel.7 \ -# doc/lightning-setconfig.7 \ -# doc/lightning-setpsbtversion.7 \ -# doc/lightning-sendcustommsg.7 \ -# doc/lightning-signinvoice.7 \ -# doc/lightning-signmessage.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-txdiscard.7 \ -# doc/lightning-txsend.7 \ -# doc/lightning-unreserveinputs.7 \ -# doc/lightning-utxopsbt.7 \ -# doc/lightning-wait.7 \ -# doc/lightning-waitinvoice.7 \ -# doc/lightning-waitanyinvoice.7 \ -# doc/lightning-waitblockheight.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/reckless.7 - -# REMOVED 4 temporarily: doc/lightning-datastoreusage.7 doc/lightning-listdatastore.7 doc/lightning-pay.7 doc/lightning-stop.7 MANPAGES := doc/lightning-cli.1 \ doc/lightningd.8 \ doc/lightningd-config.5 \ @@ -160,6 +32,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-createinvoice.7 \ doc/lightning-createrune.7 \ doc/lightning-datastore.7 \ + doc/lightning-datastoreusage.7 \ doc/lightning-decodepay.7 \ doc/lightning-decode.7 \ doc/lightning-deldatastore.7 \ @@ -187,6 +60,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-keysend.7 \ doc/lightning-listchannels.7 \ doc/lightning-listclosedchannels.7 \ + doc/lightning-listdatastore.7 \ doc/lightning-listforwards.7 \ doc/lightning-listfunds.7 \ doc/lightning-listhtlcs.7 \ @@ -209,6 +83,7 @@ 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-plugin.7 \ doc/lightning-preapproveinvoice.7 \ @@ -244,6 +119,7 @@ MANPAGES := doc/lightning-cli.1 \ 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 \ diff --git a/tools/fromschema.py b/tools/fromschema.py index ada8254fdaa4..e12dac306ce9 100755 --- a/tools/fromschema.py +++ b/tools/fromschema.py @@ -24,7 +24,7 @@ def json_value(obj): return "*true*" return "*false*" if type(obj) is str: - return "'" + esc_underscores(obj) + "'" + return '"' + esc_underscores(obj) + '"' if obj is None: return "*null*" assert False @@ -123,7 +123,7 @@ def output_member(propname, properties, is_optional, indent, print_type=True, pr """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! @@ -153,17 +153,16 @@ 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 list(items.keys()) == ["type"]: + 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 + "-") output_type(items, False) - if "description" in items and items["description"] != "": - output(" {}\n".format(esc_underscores(items["description"]))) - else: - output("\n") + 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: @@ -187,71 +186,20 @@ def has_members(sub): def output_members(sub, indent=""): """Generate lines for these properties""" warnings = [] - if "properties" not in sub: - if "additionalProperties" in sub: - return - elif "oneOf" in sub: - for oneOfItem in sub["oneOf"]: - if "type" in oneOfItem and oneOfItem["type"] == "object": - output_member(None, oneOfItem, False, indent, prefix="-") - 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") - else: - # If we have multiple ifs, we have to wrap them in allOf. - if "allOf" in sub: - ifclauses = sub["allOf"] - elif "if" in sub: - ifclauses = [sub] - else: - ifclauses = [] - - # We partially handle if, assuming it depends on particular values of prior properties. - for ifclause in ifclauses: - conditions = [] - - # "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 - 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" - if len(whichvalues) == 1: - cond += " {}".format(json_value(whichvalues[0])) - else: - 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" - - if has_members(ifclause["then"]): - # Prefix with blank line. - outputs(["\n", sentence]) - - output_members(ifclause["then"], indent + " ") - else: - # Remove deprecated: True and stub properties, collect warnings - # (Stubs required to keep additionalProperties: false happy) - - # FIXME: It fails for schemas which have only an array type with - # no properties, ex: - # "abcd": { - # "type": "array", - # "items": { - # "type": "whatever", - # "description": "efgh" - # } - # } - # Checkout the schema of `staticbackup`. + # Remove deprecated: True and stub properties, collect warnings + # (Stubs required to keep additionalProperties: false happy) + + # FIXME: It fails for schemas which have only an array type with + # no properties, ex: + # "abcd": { + # "type": "array", + # "items": { + # "type": "whatever", + # "description": "efgh" + # } + # } + # Checkout the schema of `staticbackup`. + 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] @@ -271,10 +219,59 @@ def output_members(sub, indent=""): 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") - for w in warnings: - output_member(w, sub["properties"][w], False, indent + " ", print_type=False) + if warnings != []: + output(indent + "- the following warnings are possible:\n") + for w in warnings: + output_member(w, sub["properties"][w], False, indent + " ", print_type=False) + + 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: + ifclauses = sub["allOf"] + elif "if" in sub: + ifclauses = [sub] + else: + ifclauses = [] + + # We partially handle if, assuming it depends on particular values of prior properties. + for ifclause in ifclauses: + conditions = [] + + # "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 + 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" + if len(whichvalues) == 1: + cond += " {}".format(json_value(whichvalues[0])) + else: + 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" + + if has_members(ifclause["then"]): + # Prefix with blank line. + outputs(["\n", sentence]) + output_members(ifclause["then"], indent + " ") + output("\n") def output_params(schema): @@ -293,37 +290,55 @@ def output_params(schema): def generate_from_request(schema): props = schema["request"]["properties"] toplevels = list(props.keys()) + indent="" output_title("SYNOPSIS") output_params(schema) - output_title("PARAMETERS") - if toplevels == []: - sub = schema["request"] - 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) + if len(schema["request"]["properties"]) > 0: + output_title("METHOD PARAMETERS") + 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 schema["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) def generate_from_response(schema): """This is not general, but works for us""" output_title("RETURN VALUE") - if schema["type"] != "object": - # "stop" returns a single string! - output_member(None, schema, False, "", prefix="On success, returns a single element") + + 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["response"]["properties"] + props = response["properties"] # We handle warnings on top-level objects with a separate section, # so collect them now and remove them @@ -350,7 +365,7 @@ def generate_from_response(schema): sub = props[toplevels[0]]["items"] else: output("On success, an object is returned, containing:\n\n") - sub = schema["response"] + sub = response output_members(sub) @@ -359,6 +374,11 @@ def generate_from_response(schema): for w, desc in warnings: output("- {}: {}".format(fmt_propname(w), desc)) + if "post_return_value_notes" in response: + if warnings: + output("\n\n") + outputs(response["post_return_value_notes"], "\n") + def generate_header(schema): output_title("".join(["lightning-", schema["rpc"], " -- ", schema["title"]]), "=", 0, 1) diff --git a/tools/merge.py b/tools/merge.py index 84b4c3076532..d88b36780032 100644 --- a/tools/merge.py +++ b/tools/merge.py @@ -52,7 +52,8 @@ if "deprecated" in request_json: merged_json["deprecated"] = request_json["deprecated"] request_json.pop("deprecated", None) - # md_file_contents = md_file.readlines() + if base_name == "stop": + response_json["type"] = "string" md_file_contents = [line.strip("\n") for line in md_file.readlines()] for i in range(0, len(md_file_contents)): line = md_file_contents[i] @@ -63,12 +64,29 @@ merged_json["title"] = title.strip("\n") else: title_line = md_file_contents[i - 1].strip("\n") - if line.startswith("----") and not (title_line.startswith("SYNOPSIS") or title_line.startswith("RETURN VALUE")): + if line.startswith("----") and not (title_line.startswith("SYNOPSIS")): for j in range(i+2, len(md_file_contents)): if md_file_contents[j].startswith("----"): title_line_end = j - 2 break - if title_line.startswith("DESCRIPTION") or title_line.startswith("EXAMPLE JSON REQUEST"): + if title_line.startswith("RETURN VALUE"): + for j in range(i+2, len(md_file_contents)): + if md_file_contents[j].startswith("[comment]: # (GENERATE-FROM-SCHEMA-START)"): + pre_notes_end = j - 1 + break + pre_return_value_notes = md_file_contents[i+2:pre_notes_end] + for k in range(j, len(md_file_contents)): + if md_file_contents[k].startswith("[comment]: # (GENERATE-FROM-SCHEMA-END)"): + post_notes_start = k + 2 + if md_file_contents[k].startswith("----"): + post_notes_end = k - 2 + break + post_return_value_notes = md_file_contents[post_notes_start:post_notes_end] + if len(pre_return_value_notes) > 0: + response_json["pre_return_value_notes"] = pre_return_value_notes + if len(post_return_value_notes) > 0: + response_json["post_return_value_notes"] = post_return_value_notes + elif title_line.startswith("DESCRIPTION") or title_line.startswith("EXAMPLE JSON REQUEST"): request_json[title_line.lower().replace(" ", "_")] = md_file_contents[i+2:title_line_end] merged_json["request"] = request_json merged_json["response"] = response_json