Skip to content

Commit

Permalink
msggen: introduce chain of responsibility pattern to make msggen exte…
Browse files Browse the repository at this point in the history
…nsible

Changelog-Added: msggen: introduce chain of responsibility pattern to make msggen extensible

Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
  • Loading branch information
vincenzopalazzo committed Apr 26, 2022
1 parent 0ec947f commit 6340bc7
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 141 deletions.
153 changes: 18 additions & 135 deletions contrib/msggen/msggen/__main__.py
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -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)


Expand Down
3 changes: 3 additions & 0 deletions contrib/msggen/msggen/gen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .generator import IGenerator, GeneratorChain # noqa
from .grpc import GrpcGenerator, GrpcConverterGenerator, GrpcUnconverterGenerator, GrpcServerGenerator # noqa
from .rust import RustGenerator # noqa
36 changes: 36 additions & 0 deletions contrib/msggen/msggen/gen/generator.py
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -51,7 +52,7 @@
}


class GrpcGenerator:
class GrpcGenerator(IGenerator):
"""A generator that generates protobuf files.
"""

Expand Down Expand Up @@ -235,7 +236,7 @@ def generate(self, service: Service) -> None:
self.generate_message(message)


class GrpcConverterGenerator:
class GrpcConverterGenerator(IGenerator):
def __init__(self, dest: TextIO):
self.dest = dest
self.logger = logging.getLogger("msggen.grpc.GrpcConversionGenerator")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
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__)

Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions contrib/msggen/msggen/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .utils import load_jsonrpc_method, load_jsonrpc_service, repo_root # noqa
Loading

0 comments on commit 6340bc7

Please sign in to comment.