mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-04 03:03:51 +01:00
msggen: introduce chain of responsibility pattern to make msggen extensible
Changelog-Added: msggen: introduce chain of responsibility pattern to make msggen extensible Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
This commit is contained in:
parent
80db867a30
commit
4e902fbd88
7 changed files with 195 additions and 141 deletions
|
@ -1,153 +1,31 @@
|
|||
from msggen.model import Method, CompositeField, Service
|
||||
from msggen.grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator
|
||||
from msggen.rust import RustGenerator
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import json
|
||||
from msggen.gen.grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator
|
||||
from msggen.gen.rust import RustGenerator
|
||||
from msggen.gen.generator import GeneratorChain
|
||||
from msggen.utils import repo_root, load_jsonrpc_service
|
||||
|
||||
|
||||
# Sometimes we want to rename a method, due to a name clash
|
||||
method_name_override = {
|
||||
"Connect": "ConnectPeer",
|
||||
}
|
||||
|
||||
|
||||
def repo_root():
|
||||
path = subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
|
||||
return Path(path.strip().decode('UTF-8'))
|
||||
|
||||
|
||||
def load_jsonrpc_method(name):
|
||||
"""Load a method based on the file naming conventions for the JSON-RPC.
|
||||
"""
|
||||
base_path = (repo_root() / "doc" / "schemas").resolve()
|
||||
req_file = base_path / f"{name.lower()}.request.json"
|
||||
resp_file = base_path / f"{name.lower()}.schema.json"
|
||||
request = CompositeField.from_js(json.load(open(req_file)), path=name)
|
||||
response = CompositeField.from_js(json.load(open(resp_file)), path=name)
|
||||
|
||||
# Normalize the method request and response typename so they no
|
||||
# longer conflict.
|
||||
request.typename += "Request"
|
||||
response.typename += "Response"
|
||||
|
||||
return Method(
|
||||
name=method_name_override.get(name, name),
|
||||
request=request,
|
||||
response=response,
|
||||
)
|
||||
|
||||
|
||||
def load_jsonrpc_service():
|
||||
method_names = [
|
||||
"Getinfo",
|
||||
"ListPeers",
|
||||
"ListFunds",
|
||||
"SendPay",
|
||||
"ListChannels",
|
||||
"AddGossip",
|
||||
"AutoCleanInvoice",
|
||||
"CheckMessage",
|
||||
"Close",
|
||||
"Connect",
|
||||
"CreateInvoice",
|
||||
"Datastore",
|
||||
"CreateOnion",
|
||||
"DelDatastore",
|
||||
"DelExpiredInvoice",
|
||||
"DelInvoice",
|
||||
"Invoice",
|
||||
"ListDatastore",
|
||||
"ListInvoices",
|
||||
"SendOnion",
|
||||
"ListSendPays",
|
||||
"ListTransactions",
|
||||
"Pay",
|
||||
"ListNodes",
|
||||
"WaitAnyInvoice",
|
||||
"WaitInvoice",
|
||||
"WaitSendPay",
|
||||
"NewAddr",
|
||||
"Withdraw",
|
||||
"KeySend",
|
||||
"FundPsbt",
|
||||
"SendPsbt",
|
||||
"SignPsbt",
|
||||
"UtxoPsbt",
|
||||
"TxDiscard",
|
||||
"TxPrepare",
|
||||
"TxSend",
|
||||
# "decodepay",
|
||||
# "decode",
|
||||
# "delpay",
|
||||
# "disableoffer",
|
||||
"Disconnect",
|
||||
"Feerates",
|
||||
# "fetchinvoice",
|
||||
# "fundchannel_cancel",
|
||||
# "fundchannel_complete",
|
||||
# "fundchannel",
|
||||
# "fundchannel_start",
|
||||
# "funderupdate",
|
||||
# "getlog",
|
||||
"GetRoute",
|
||||
# "getsharedsecret",
|
||||
"ListForwards",
|
||||
# "listoffers",
|
||||
"ListPays",
|
||||
# "multifundchannel",
|
||||
# "multiwithdraw",
|
||||
# "offerout",
|
||||
# "offer",
|
||||
# "openchannel_abort",
|
||||
# "openchannel_bump",
|
||||
# "openchannel_init",
|
||||
# "openchannel_signed",
|
||||
# "openchannel_update",
|
||||
# "parsefeerate",
|
||||
"Ping",
|
||||
# "plugin",
|
||||
# "reserveinputs",
|
||||
# "sendcustommsg",
|
||||
# "sendinvoice",
|
||||
# "sendonionmessage",
|
||||
# "setchannelfee",
|
||||
"SignMessage",
|
||||
# "unreserveinputs",
|
||||
# "waitblockheight",
|
||||
# "ListConfigs",
|
||||
# "check", # No point in mapping this one
|
||||
# "Stop", # Breaks a core assumption (root is an object) can't map unless we change this
|
||||
# "notifications", # No point in mapping this
|
||||
# "help",
|
||||
]
|
||||
methods = [load_jsonrpc_method(name) for name in method_names]
|
||||
service = Service(name="Node", methods=methods)
|
||||
service.includes = ['primitives.proto'] # Make sure we have the primitives included.
|
||||
return service
|
||||
|
||||
|
||||
def gengrpc(service, meta):
|
||||
def gengrpc(generator_chain: GeneratorChain, meta):
|
||||
"""Load all mapped RPC methods, wrap them in a Service, and split them into messages.
|
||||
"""
|
||||
fname = repo_root() / "cln-grpc" / "proto" / "node.proto"
|
||||
dest = open(fname, "w")
|
||||
GrpcGenerator(dest, meta).generate(service)
|
||||
generator_chain.add_generator(GrpcGenerator(dest, meta))
|
||||
|
||||
fname = repo_root() / "cln-grpc" / "src" / "convert.rs"
|
||||
dest = open(fname, "w")
|
||||
GrpcConverterGenerator(dest).generate(service)
|
||||
GrpcUnconverterGenerator(dest).generate(service)
|
||||
generator_chain.add_generator(GrpcConverterGenerator(dest))
|
||||
generator_chain.add_generator(GrpcUnconverterGenerator(dest))
|
||||
|
||||
fname = repo_root() / "cln-grpc" / "src" / "server.rs"
|
||||
dest = open(fname, "w")
|
||||
GrpcServerGenerator(dest).generate(service)
|
||||
generator_chain.add_generator(GrpcServerGenerator(dest))
|
||||
|
||||
|
||||
def genrustjsonrpc(service):
|
||||
def genrustjsonrpc(generator_chain: GeneratorChain):
|
||||
fname = repo_root() / "cln-rpc" / "src" / "model.rs"
|
||||
dest = open(fname, "w")
|
||||
RustGenerator(dest).generate(service)
|
||||
generator_chain.add_generator(RustGenerator(dest))
|
||||
|
||||
|
||||
def load_msggen_meta():
|
||||
|
@ -163,8 +41,13 @@ def write_msggen_meta(meta):
|
|||
def run():
|
||||
service = load_jsonrpc_service()
|
||||
meta = load_msggen_meta()
|
||||
gengrpc(service, meta)
|
||||
genrustjsonrpc(service)
|
||||
generator_chain = GeneratorChain()
|
||||
|
||||
gengrpc(generator_chain, meta)
|
||||
genrustjsonrpc(generator_chain)
|
||||
|
||||
generator_chain.generate(service)
|
||||
|
||||
write_msggen_meta(meta)
|
||||
|
||||
|
||||
|
|
3
contrib/msggen/msggen/gen/__init__.py
Normal file
3
contrib/msggen/msggen/gen/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from .generator import IGenerator, GeneratorChain # noqa
|
||||
from .grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator # noqa
|
||||
from .rust import RustGenerator # noqa
|
36
contrib/msggen/msggen/gen/generator.py
Normal file
36
contrib/msggen/msggen/gen/generator.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Generator interface!
|
||||
|
||||
author: https://github.com/vincenzopalazzo
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from msggen.model import Service
|
||||
|
||||
|
||||
class IGenerator(ABC):
|
||||
"""
|
||||
Change of responsibility handler that need to be
|
||||
implemented by all the generators.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def generate(self, service: Service):
|
||||
pass
|
||||
|
||||
|
||||
class GeneratorChain:
|
||||
"""
|
||||
Chain responsibility patter implementation to generalize
|
||||
the generation method.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.generators = []
|
||||
|
||||
def add_generator(self, generator: IGenerator) -> None:
|
||||
self.generators.append(generator)
|
||||
|
||||
def generate(self, service: Service) -> None:
|
||||
for _, generator in enumerate(self.generators):
|
||||
generator.generate(service)
|
|
@ -1,5 +1,6 @@
|
|||
# A grpc model
|
||||
from .model import ArrayField, Field, CompositeField, EnumField, PrimitiveField, Service
|
||||
from msggen.model import ArrayField, Field, CompositeField, EnumField, PrimitiveField, Service
|
||||
from msggen.gen import IGenerator
|
||||
from typing import TextIO, List, Dict, Any
|
||||
from textwrap import indent, dedent
|
||||
import re
|
||||
|
@ -51,7 +52,7 @@ method_name_overrides = {
|
|||
}
|
||||
|
||||
|
||||
class GrpcGenerator:
|
||||
class GrpcGenerator(IGenerator):
|
||||
"""A generator that generates protobuf files.
|
||||
"""
|
||||
|
||||
|
@ -235,7 +236,7 @@ class GrpcGenerator:
|
|||
self.generate_message(message)
|
||||
|
||||
|
||||
class GrpcConverterGenerator:
|
||||
class GrpcConverterGenerator(IGenerator):
|
||||
def __init__(self, dest: TextIO):
|
||||
self.dest = dest
|
||||
self.logger = logging.getLogger("msggen.grpc.GrpcConversionGenerator")
|
|
@ -5,8 +5,9 @@ import logging
|
|||
import sys
|
||||
import re
|
||||
|
||||
from .model import (ArrayField, CompositeField, EnumField,
|
||||
PrimitiveField, Service)
|
||||
from msggen.model import (ArrayField, CompositeField, EnumField,
|
||||
PrimitiveField, Service)
|
||||
from msggen.gen.generator import IGenerator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -196,7 +197,7 @@ def gen_composite(c) -> Tuple[str, str]:
|
|||
return ("", r)
|
||||
|
||||
|
||||
class RustGenerator:
|
||||
class RustGenerator(IGenerator):
|
||||
def __init__(self, dest: TextIO):
|
||||
self.dest = dest
|
||||
|
1
contrib/msggen/msggen/utils/__init__.py
Normal file
1
contrib/msggen/msggen/utils/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .utils import load_jsonrpc_method, load_jsonrpc_service, repo_root # noqa
|
129
contrib/msggen/msggen/utils/utils.py
Normal file
129
contrib/msggen/msggen/utils/utils.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
import subprocess
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from msggen.model import Method, CompositeField, Service
|
||||
|
||||
# Sometimes we want to rename a method, due to a name clash
|
||||
# FIXME: need to be generalized?
|
||||
method_name_override = {
|
||||
"Connect": "ConnectPeer",
|
||||
}
|
||||
|
||||
|
||||
def repo_root():
|
||||
path = subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
|
||||
return Path(path.strip().decode('UTF-8'))
|
||||
|
||||
|
||||
def load_jsonrpc_method(name, schema_dir: str = None):
|
||||
"""Load a method based on the file naming conventions for the JSON-RPC.
|
||||
"""
|
||||
if schema_dir is None:
|
||||
base_path = (repo_root() / "doc" / "schemas").resolve()
|
||||
else:
|
||||
base_path = schema_dir
|
||||
req_file = base_path / f"{name.lower()}.request.json"
|
||||
resp_file = base_path / f"{name.lower()}.schema.json"
|
||||
request = CompositeField.from_js(json.load(open(req_file)), path=name)
|
||||
response = CompositeField.from_js(json.load(open(resp_file)), path=name)
|
||||
|
||||
# Normalize the method request and response typename so they no
|
||||
# longer conflict.
|
||||
request.typename += "Request"
|
||||
response.typename += "Response"
|
||||
|
||||
return Method(
|
||||
name=method_name_override.get(name, name),
|
||||
request=request,
|
||||
response=response,
|
||||
)
|
||||
|
||||
|
||||
def load_jsonrpc_service(schema_dir: str = None):
|
||||
method_names = [
|
||||
"Getinfo",
|
||||
"ListPeers",
|
||||
"ListFunds",
|
||||
"SendPay",
|
||||
"ListChannels",
|
||||
"AddGossip",
|
||||
"AutoCleanInvoice",
|
||||
"CheckMessage",
|
||||
"Close",
|
||||
"Connect",
|
||||
"CreateInvoice",
|
||||
"Datastore",
|
||||
"CreateOnion",
|
||||
"DelDatastore",
|
||||
"DelExpiredInvoice",
|
||||
"DelInvoice",
|
||||
"Invoice",
|
||||
"ListDatastore",
|
||||
"ListInvoices",
|
||||
"SendOnion",
|
||||
"ListSendPays",
|
||||
"ListTransactions",
|
||||
"Pay",
|
||||
"ListNodes",
|
||||
"WaitAnyInvoice",
|
||||
"WaitInvoice",
|
||||
"WaitSendPay",
|
||||
"NewAddr",
|
||||
"Withdraw",
|
||||
"KeySend",
|
||||
"FundPsbt",
|
||||
"SendPsbt",
|
||||
"SignPsbt",
|
||||
"UtxoPsbt",
|
||||
"TxDiscard",
|
||||
"TxPrepare",
|
||||
"TxSend",
|
||||
# "decodepay",
|
||||
# "decode",
|
||||
# "delpay",
|
||||
# "disableoffer",
|
||||
"Disconnect",
|
||||
"Feerates",
|
||||
# "fetchinvoice",
|
||||
# "fundchannel_cancel",
|
||||
# "fundchannel_complete",
|
||||
# "fundchannel",
|
||||
# "fundchannel_start",
|
||||
# "funderupdate",
|
||||
# "getlog",
|
||||
"GetRoute",
|
||||
# "getsharedsecret",
|
||||
"ListForwards",
|
||||
# "listoffers",
|
||||
"ListPays",
|
||||
# "multifundchannel",
|
||||
# "multiwithdraw",
|
||||
# "offerout",
|
||||
# "offer",
|
||||
# "openchannel_abort",
|
||||
# "openchannel_bump",
|
||||
# "openchannel_init",
|
||||
# "openchannel_signed",
|
||||
# "openchannel_update",
|
||||
# "parsefeerate",
|
||||
"Ping",
|
||||
# "plugin",
|
||||
# "reserveinputs",
|
||||
# "sendcustommsg",
|
||||
# "sendinvoice",
|
||||
# "sendonionmessage",
|
||||
# "setchannelfee",
|
||||
"SignMessage",
|
||||
# "unreserveinputs",
|
||||
# "waitblockheight",
|
||||
# "ListConfigs",
|
||||
# "check", # No point in mapping this one
|
||||
# "Stop", # Breaks a core assumption (root is an object) can't map unless we change this
|
||||
# "notifications", # No point in mapping this
|
||||
# "help",
|
||||
]
|
||||
methods = [load_jsonrpc_method(name, schema_dir=schema_dir) for name in method_names]
|
||||
service = Service(name="Node", methods=methods)
|
||||
service.includes = ['primitives.proto'] # Make sure we have the primitives included.
|
||||
return service
|
Loading…
Add table
Reference in a new issue