Code to generate structs for JSON marshalling

This commit is contained in:
Kovid Goyal 2022-08-17 21:57:02 +05:30
parent 0aa1bacbe7
commit 47feb73cdf
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
38 changed files with 89 additions and 39 deletions

View File

@ -95,6 +95,40 @@ class Option:
return ans return ans
json_field_types: Dict[str, str] = {
'bool': 'bool', 'str': 'string', 'list.str': '[]string', 'dict.str': 'map[string]string', 'float': 'float64', 'int': 'int',
'scroll_amount': '[2]interface{}', 'spacing': 'interface{}', 'colors': 'interface{}',
}
def go_field_type(json_field_type: str) -> str:
q = json_field_types.get(json_field_type)
if q:
return q
if json_field_type.startswith('choices.'):
return 'string'
if '.' in json_field_type:
p, r = json_field_type.split('.', 1)
p = {'list': '[]', 'dict': 'map[string]'}[p]
return p + go_field_type(r)
raise TypeError(f'Unknown JSON field type: {json_field_type}')
class JSONField:
def __init__(self, line: str) -> None:
field_def = line.split(':', 1)[0]
self.required = False
self.field, self.field_type = field_def.split('/', 1)
if self.field.endswith('+'):
self.required = True
self.field = self.field[:-1]
self.struct_field_name = self.field[0].upper() + self.field[1:]
def go_declaration(self) -> str:
return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field},omitempty"`'
def render_alias_map(alias_map: Dict[str, Tuple[str, ...]]) -> str: def render_alias_map(alias_map: Dict[str, Tuple[str, ...]]) -> str:
if not alias_map: if not alias_map:
return '' return ''
@ -125,6 +159,13 @@ def build_go_code(name: str, cmd: RemoteCommand, seq: OptionSpecSeq, template: s
continue continue
od.append(f'{o.go_var_name} {o.go_type}') od.append(f'{o.go_var_name} {o.go_type}')
ov.append(o.set_flag_value()) ov.append(o.set_flag_value())
jd: List[str] = []
for line in cmd.protocol_spec.splitlines():
line = line.strip()
if ':' not in line:
continue
f = JSONField(line)
jd.append(f.go_declaration())
ans = replace( ans = replace(
template, template,
@ -136,6 +177,7 @@ def build_go_code(name: str, cmd: RemoteCommand, seq: OptionSpecSeq, template: s
ALIAS_NORMALIZE_CODE=render_alias_map(alias_map), ALIAS_NORMALIZE_CODE=render_alias_map(alias_map),
OPTIONS_DECLARATION_CODE='\n'.join(od), OPTIONS_DECLARATION_CODE='\n'.join(od),
SET_OPTION_VALUES_CODE='\n'.join(ov), SET_OPTION_VALUES_CODE='\n'.join(ov),
JSON_DECLARATION_CODE='\n'.join(jd),
) )
return ans return ans

View File

@ -0,0 +1 @@

View File

@ -175,6 +175,7 @@ class RemoteCommand:
defaults: Optional[Dict[str, Any]] = None defaults: Optional[Dict[str, Any]] = None
is_asynchronous: bool = False is_asynchronous: bool = False
options_class: Type[RCOptions] = RCOptions options_class: Type[RCOptions] = RCOptions
protocol_spec: str = ''
def __init__(self) -> None: def __init__(self) -> None:
self.desc = self.desc or self.short_desc self.desc = self.desc or self.short_desc

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class CloseTab(RemoteCommand): class CloseTab(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which tab to close match/str: Which tab to close
no_response/bool: Boolean indicating whether to wait for a response no_response/bool: Boolean indicating whether to wait for a response
self/bool: Boolean indicating whether to close the tab of the window the command is run in self/bool: Boolean indicating whether to close the tab of the window the command is run in

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class CloseWindow(RemoteCommand): class CloseWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which window to close match/str: Which window to close
no_response/bool: Boolean indicating whether to wait for a response no_response/bool: Boolean indicating whether to wait for a response
self/bool: Boolean indicating whether to close the window the command is run in self/bool: Boolean indicating whether to close the window the command is run in

View File

@ -16,7 +16,7 @@ if TYPE_CHECKING:
class CreateMarker(RemoteCommand): class CreateMarker(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which window to create the marker in match/str: Which window to create the marker in
self/bool: Boolean indicating whether to create marker in the window the command is run in self/bool: Boolean indicating whether to create marker in the window the command is run in
marker_spec/list.str: A list or arguments that define the marker specification, for example: ['text', '1', 'ERROR'] marker_spec/list.str: A list or arguments that define the marker specification, for example: ['text', '1', 'ERROR']

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class DetachTab(RemoteCommand): class DetachTab(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which tab to detach match/str: Which tab to detach
target_tab/str: Which tab to move the detached tab to the OS window it is run in target_tab/str: Which tab to move the detached tab to the OS window it is run in
self/bool: Boolean indicating whether to detach the tab the command is run in self/bool: Boolean indicating whether to detach the tab the command is run in

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class DetachWindow(RemoteCommand): class DetachWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which window to detach match/str: Which window to detach
target_tab/str: Which tab to move the detached window to target_tab/str: Which tab to move the detached window to
self/bool: Boolean indicating whether to detach the window the command is run in self/bool: Boolean indicating whether to detach the window the command is run in

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class DisableLigatures(RemoteCommand): class DisableLigatures(RemoteCommand):
''' protocol_spec = __doc__ = '''
strategy+/choices.never.always.cursor: One of :code:`never`, :code:`always` or :code:`cursor` strategy+/choices.never.always.cursor: One of :code:`never`, :code:`always` or :code:`cursor`
match_window/str: Window to change opacity in match_window/str: Window to change opacity in
match_tab/str: Tab to change opacity in match_tab/str: Tab to change opacity in

View File

@ -11,7 +11,7 @@ from .base import (
class Env(RemoteCommand): class Env(RemoteCommand):
''' protocol_spec = __doc__ = '''
env+/dict.str: Dictionary of environment variables to values. Empty values cause the variable to be removed. env+/dict.str: Dictionary of environment variables to values. Empty values cause the variable to be removed.
''' '''

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class FocusTab(RemoteCommand): class FocusTab(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: The tab to focus match/str: The tab to focus
no_response/bool: Boolean indicating whether to wait for a response no_response/bool: Boolean indicating whether to wait for a response
''' '''

View File

@ -16,7 +16,7 @@ if TYPE_CHECKING:
class FocusWindow(RemoteCommand): class FocusWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: The window to focus match/str: The window to focus
no_response/bool: Boolean indicating whether to wait for a response no_response/bool: Boolean indicating whether to wait for a response
''' '''

View File

@ -18,7 +18,7 @@ if TYPE_CHECKING:
class GetColors(RemoteCommand): class GetColors(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: The window to get the colors for match/str: The window to get the colors for
configured/bool: Boolean indicating whether to get configured or current colors configured/bool: Boolean indicating whether to get configured or current colors
''' '''

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class GetText(RemoteCommand): class GetText(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: The window to get text from match/str: The window to get text from
extent/choices.screen.first_cmd_output_on_screen.last_cmd_output.last_visited_cmd_output.all.selection: \ extent/choices.screen.first_cmd_output_on_screen.last_cmd_output.last_visited_cmd_output.all.selection: \
One of :code:`screen`, :code:`first_cmd_output_on_screen`, :code:`last_cmd_output`, \ One of :code:`screen`, :code:`first_cmd_output_on_screen`, :code:`last_cmd_output`, \

View File

@ -19,7 +19,7 @@ def layout_names() -> Iterable[str]:
class GotoLayout(RemoteCommand): class GotoLayout(RemoteCommand):
''' protocol_spec = __doc__ = '''
layout+/str: The new layout name layout+/str: The new layout name
match/str: Which tab to change the layout of match/str: Which tab to change the layout of
''' '''

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class Kitten(RemoteCommand): class Kitten(RemoteCommand):
''' protocol_spec = __doc__ = '''
kitten+/str: The name of the kitten to run kitten+/str: The name of the kitten to run
args/list.str: Arguments to pass to the kitten as a list args/list.str: Arguments to pass to the kitten as a list
match/str: The window to run the kitten over match/str: The window to run the kitten over

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class LastUsedLayout(RemoteCommand): class LastUsedLayout(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which tab to change the layout of match/str: Which tab to change the layout of
all/bool: Boolean to match all tabs all/bool: Boolean to match all tabs
no_response/bool: Boolean indicating whether to wait for a response no_response/bool: Boolean indicating whether to wait for a response

View File

@ -21,7 +21,7 @@ if TYPE_CHECKING:
class Launch(RemoteCommand): class Launch(RemoteCommand):
''' protocol_spec = __doc__ = '''
args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell
match/str: The tab to open the new window in match/str: The tab to open the new window in
window_title/str: Title for the new window window_title/str: Title for the new window
@ -36,7 +36,7 @@ class Launch(RemoteCommand):
hold/bool: Boolean indicating whether to keep window open after cmd exits hold/bool: Boolean indicating whether to keep window open after cmd exits
location/choices.first.after.before.neighbor.last.vsplit.hsplit.split.default: Where in the tab to open the new window location/choices.first.after.before.neighbor.last.vsplit.hsplit.split.default: Where in the tab to open the new window
allow_remote_control/bool: Boolean indicating whether to allow remote control from the new window allow_remote_control/bool: Boolean indicating whether to allow remote control from the new window
remote_control_password/list/str: A list of remote control passwords remote_control_password/list.str: A list of remote control passwords
stdin_source/choices.none.@selection.@screen.@screen_scrollback.@alternate.@alternate_scrollback.\ stdin_source/choices.none.@selection.@screen.@screen_scrollback.@alternate.@alternate_scrollback.\
@first_cmd_output_on_screen.@last_cmd_output.@last_visited_cmd_output: Where to get stdin for the process from @first_cmd_output_on_screen.@last_cmd_output.@last_visited_cmd_output: Where to get stdin for the process from
stdin_add_formatting/bool: Boolean indicating whether to add formatting codes to stdin stdin_add_formatting/bool: Boolean indicating whether to add formatting codes to stdin

View File

@ -16,7 +16,7 @@ if TYPE_CHECKING:
class LS(RemoteCommand): class LS(RemoteCommand):
''' protocol_spec = __doc__ = '''
all_env_vars/bool: Whether to send all environment variables for every window rather than just differing ones all_env_vars/bool: Whether to send all environment variables for every window rather than just differing ones
''' '''

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class NewWindow(RemoteCommand): class NewWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell args+/list.str: The command line to run in the new window, as a list, use an empty list to run the default shell
match/str: The tab to open the new window in match/str: The tab to open the new window in
title/str: Title for the new window title/str: Title for the new window

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class RemoveMarker(RemoteCommand): class RemoveMarker(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which window to remove the marker from match/str: Which window to remove the marker from
self/bool: Boolean indicating whether to detach the window the command is run in self/bool: Boolean indicating whether to detach the window the command is run in
''' '''

View File

@ -13,7 +13,7 @@ if TYPE_CHECKING:
class ResizeOSWindow(RemoteCommand): class ResizeOSWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which window to resize match/str: Which window to resize
self/bool: Boolean indicating whether to close the window the command is run in self/bool: Boolean indicating whether to close the window the command is run in
incremental/bool: Boolean indicating whether to adjust the size incrementally incremental/bool: Boolean indicating whether to adjust the size incrementally

View File

@ -13,7 +13,7 @@ if TYPE_CHECKING:
class ResizeWindow(RemoteCommand): class ResizeWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: Which window to resize match/str: Which window to resize
self/bool: Boolean indicating whether to resize the window the command is run in self/bool: Boolean indicating whether to resize the window the command is run in
increment/int: Integer specifying the resize increment increment/int: Integer specifying the resize increment

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class ScrollWindow(RemoteCommand): class ScrollWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
amount+/list.scroll_amount: The amount to scroll, a two item list with the first item being \ amount+/list.scroll_amount: The amount to scroll, a two item list with the first item being \
either a number or the keywords, start and end. \ either a number or the keywords, start and end. \
And the second item being either 'p' for pages or 'l' for lines or 'u' And the second item being either 'p' for pages or 'l' for lines or 'u'

View File

@ -17,7 +17,7 @@ if TYPE_CHECKING:
class SelectWindow(RemoteCommand): class SelectWindow(RemoteCommand):
''' protocol_spec = __doc__ = '''
match/str: The tab to open the new window in match/str: The tab to open the new window in
self/bool: Boolean, if True use tab the command was run in self/bool: Boolean, if True use tab the command was run in
title/str: A title for this selection title/str: A title for this selection

View File

@ -63,13 +63,13 @@ class FocusChangedSession(SessionAction):
class SendText(RemoteCommand): class SendText(RemoteCommand):
''' protocol_spec = __doc__ = '''
data+/send_text: The data being sent. Can be either: text: followed by text or base64: followed by standard base64 encoded bytes data+/str: The data being sent. Can be either: text: followed by text or base64: followed by standard base64 encoded bytes
match/str: A string indicating the window to send text to match/str: A string indicating the window to send text to
match_tab/str: A string indicating the tab to send text to match_tab/str: A string indicating the tab to send text to
all/bool: A boolean indicating all windows should be matched. all/bool: A boolean indicating all windows should be matched.
exclude_active/bool: A boolean that prevents sending text to the active window exclude_active/bool: A boolean that prevents sending text to the active window
session_id/internal: A string that identifies a "broadcast session" session_id/str: A string that identifies a "broadcast session"
''' '''
short_desc = 'Send arbitrary text to specified windows' short_desc = 'Send arbitrary text to specified windows'
desc = ( desc = (

View File

@ -23,8 +23,8 @@ layout_choices = 'tiled,scaled,mirror-tiled,clamped,configured'
class SetBackgroundImage(RemoteCommand): class SetBackgroundImage(RemoteCommand):
f''' protocol_spec = __doc__ = '''
data+/image_data: Chunk of at most 512 bytes of PNG data, base64 encoded. Must send an empty chunk to indicate end of image. \ data+/str: Chunk of at most 512 bytes of PNG data, base64 encoded. Must send an empty chunk to indicate end of image. \
Or the special value - to indicate image must be removed. Or the special value - to indicate image must be removed.
match/str: Window to change opacity in match/str: Window to change opacity in
layout/choices.{layout_choices.replace(",", ".")}: The image layout layout/choices.{layout_choices.replace(",", ".")}: The image layout

View File

@ -16,7 +16,7 @@ if TYPE_CHECKING:
class SetBackgroundOpacity(RemoteCommand): class SetBackgroundOpacity(RemoteCommand):
''' protocol_spec = __doc__ = '''
opacity+/float: A number between 0.1 and 1 opacity+/float: A number between 0.1 and 1
match_window/str: Window to change opacity in match_window/str: Window to change opacity in
match_tab/str: Tab to change opacity in match_tab/str: Tab to change opacity in

View File

@ -54,7 +54,7 @@ def parse_colors(args: Iterable[str]) -> Dict[str, Optional[int]]:
class SetColors(RemoteCommand): class SetColors(RemoteCommand):
''' protocol_spec = __doc__ = '''
colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers or null for nullable colors colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers or null for nullable colors
match_window/str: Window to change colors in match_window/str: Window to change colors in
match_tab/str: Tab to change colors in match_tab/str: Tab to change colors in

View File

@ -22,7 +22,7 @@ def layout_names() -> Iterable[str]:
class SetEnabledLayouts(RemoteCommand): class SetEnabledLayouts(RemoteCommand):
''' protocol_spec = __doc__ = '''
layouts+/list.str: The list of layout names layouts+/list.str: The list of layout names
match/str: Which tab to change the layout of match/str: Which tab to change the layout of
configured/bool: Boolean indicating whether to change the configured value configured/bool: Boolean indicating whether to change the configured value

View File

@ -13,7 +13,7 @@ if TYPE_CHECKING:
class SetFontSize(RemoteCommand): class SetFontSize(RemoteCommand):
''' protocol_spec = __doc__ = '''
size+/float: The new font size in pts (a positive number) size+/float: The new font size in pts (a positive number)
all/bool: Boolean whether to change font size in the current window or all windows all/bool: Boolean whether to change font size in the current window or all windows
increment_op/choices.+.-: The string ``+`` or ``-`` to interpret size as an increment increment_op/choices.+.-: The string ``+`` or ``-`` to interpret size as an increment

View File

@ -67,7 +67,7 @@ def parse_spacing_settings(args: Iterable[str]) -> Dict[str, Optional[float]]:
class SetSpacing(RemoteCommand): class SetSpacing(RemoteCommand):
''' protocol_spec = __doc__ = '''
settings+/dict.spacing: An object mapping margins/paddings using canonical form {'margin-top': 50, 'padding-left': null} etc settings+/dict.spacing: An object mapping margins/paddings using canonical form {'margin-top': 50, 'padding-left': null} etc
match_window/str: Window to change paddings and margins in match_window/str: Window to change paddings and margins in
match_tab/str: Tab to change paddings and margins in match_tab/str: Tab to change paddings and margins in

View File

@ -37,7 +37,7 @@ def parse_colors(args: ArgsType) -> Dict[str, Optional[int]]:
class SetTabColor(RemoteCommand): class SetTabColor(RemoteCommand):
''' protocol_spec = __doc__ = '''
colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers. A color value of null indicates it should be unset. colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers. A color value of null indicates it should be unset.
match/str: Which tab to change the color of match/str: Which tab to change the color of
self/bool: Boolean indicating whether to use the tab of the window the command is run in self/bool: Boolean indicating whether to use the tab of the window the command is run in

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class SetTabTitle(RemoteCommand): class SetTabTitle(RemoteCommand):
''' protocol_spec = __doc__ = '''
title+/str: The new title title+/str: The new title
match/str: Which tab to change the title of match/str: Which tab to change the title of
''' '''

View File

@ -20,8 +20,8 @@ if TYPE_CHECKING:
class SetWindowLogo(RemoteCommand): class SetWindowLogo(RemoteCommand):
''' protocol_spec = __doc__ = '''
data+/image_data: Chunk of at most 512 bytes of PNG data, base64 encoded. Must send an empty chunk to indicate end of image. \ data+/str: Chunk of at most 512 bytes of PNG data, base64 encoded. Must send an empty chunk to indicate end of image. \
Or the special value :code:`-` to indicate image must be removed. Or the special value :code:`-` to indicate image must be removed.
position/str: The logo position as a string, empty string means default position/str: The logo position as a string, empty string means default
alpha/float: The logo alpha between :code:`0` and :code:`1`. :code:`-1` means use default alpha/float: The logo alpha between :code:`0` and :code:`1`. :code:`-1` means use default

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class SetWindowTitle(RemoteCommand): class SetWindowTitle(RemoteCommand):
''' protocol_spec = __doc__ = '''
title/str: The new title title/str: The new title
match/str: Which windows to change the title in match/str: Which windows to change the title in
temporary/bool: Boolean indicating if the change is temporary or permanent temporary/bool: Boolean indicating if the change is temporary or permanent

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class SignalChild(RemoteCommand): class SignalChild(RemoteCommand):
''' protocol_spec = __doc__ = '''
signals/list.str: The signals, a list of names, such as :code:`SIGTERM`, :code:`SIGKILL`, :code:`SIGUSR1`, etc. signals/list.str: The signals, a list of names, such as :code:`SIGTERM`, :code:`SIGKILL`, :code:`SIGUSR1`, etc.
match/str: Which windows to send the signals to match/str: Which windows to send the signals to
no_response/bool: Boolean indicating whether to wait for a response no_response/bool: Boolean indicating whether to wait for a response

View File

@ -18,6 +18,12 @@ type options_CMD_NAME_type struct {
var options_CMD_NAME options_CMD_NAME_type var options_CMD_NAME options_CMD_NAME_type
type CMD_NAME_json_type struct {
JSON_DECLARATION_CODE
}
var CMD_NAME_json CMD_NAME_json_type
func run_CMD_NAME(cmd *cobra.Command, args []string) (err error) { func run_CMD_NAME(cmd *cobra.Command, args []string) (err error) {
SET_OPTION_VALUES_CODE SET_OPTION_VALUES_CODE