pyfcstm 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfcstm/__init__.py +0 -0
- pyfcstm/__main__.py +4 -0
- pyfcstm/config/__init__.py +0 -0
- pyfcstm/config/meta.py +20 -0
- pyfcstm/dsl/__init__.py +6 -0
- pyfcstm/dsl/error.py +226 -0
- pyfcstm/dsl/grammar/Grammar.g4 +190 -0
- pyfcstm/dsl/grammar/Grammar.interp +168 -0
- pyfcstm/dsl/grammar/Grammar.tokens +118 -0
- pyfcstm/dsl/grammar/GrammarLexer.interp +214 -0
- pyfcstm/dsl/grammar/GrammarLexer.py +523 -0
- pyfcstm/dsl/grammar/GrammarLexer.tokens +118 -0
- pyfcstm/dsl/grammar/GrammarListener.py +521 -0
- pyfcstm/dsl/grammar/GrammarParser.py +4373 -0
- pyfcstm/dsl/grammar/__init__.py +3 -0
- pyfcstm/dsl/listener.py +440 -0
- pyfcstm/dsl/node.py +1581 -0
- pyfcstm/dsl/parse.py +155 -0
- pyfcstm/entry/__init__.py +1 -0
- pyfcstm/entry/base.py +126 -0
- pyfcstm/entry/cli.py +12 -0
- pyfcstm/entry/dispatch.py +46 -0
- pyfcstm/entry/generate.py +83 -0
- pyfcstm/entry/plantuml.py +67 -0
- pyfcstm/model/__init__.py +3 -0
- pyfcstm/model/base.py +51 -0
- pyfcstm/model/expr.py +764 -0
- pyfcstm/model/model.py +1392 -0
- pyfcstm/render/__init__.py +3 -0
- pyfcstm/render/env.py +36 -0
- pyfcstm/render/expr.py +180 -0
- pyfcstm/render/func.py +77 -0
- pyfcstm/render/render.py +279 -0
- pyfcstm/utils/__init__.py +6 -0
- pyfcstm/utils/binary.py +38 -0
- pyfcstm/utils/doc.py +64 -0
- pyfcstm/utils/jinja2.py +121 -0
- pyfcstm/utils/json.py +125 -0
- pyfcstm/utils/text.py +91 -0
- pyfcstm/utils/validate.py +102 -0
- pyfcstm-0.0.1.dist-info/LICENSE +165 -0
- pyfcstm-0.0.1.dist-info/METADATA +205 -0
- pyfcstm-0.0.1.dist-info/RECORD +46 -0
- pyfcstm-0.0.1.dist-info/WHEEL +5 -0
- pyfcstm-0.0.1.dist-info/entry_points.txt +2 -0
- pyfcstm-0.0.1.dist-info/top_level.txt +1 -0
pyfcstm/dsl/parse.py
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
"""
|
2
|
+
Grammar parsing module for processing and interpreting grammar-based input text.
|
3
|
+
|
4
|
+
This module provides functions to parse input text according to grammar rules defined
|
5
|
+
in the GrammarParser. It uses ANTLR4 for lexical analysis and parsing, and provides
|
6
|
+
specialized functions for parsing different grammar elements like conditions, preambles,
|
7
|
+
and operations.
|
8
|
+
"""
|
9
|
+
|
10
|
+
from antlr4 import CommonTokenStream, InputStream, ParseTreeWalker
|
11
|
+
|
12
|
+
from .error import CollectingErrorListener
|
13
|
+
from .grammar import GrammarParser, GrammarLexer
|
14
|
+
from .listener import GrammarParseListener
|
15
|
+
|
16
|
+
|
17
|
+
def _parse_as_element(input_text, fn_element, force_finished: bool = True):
|
18
|
+
"""
|
19
|
+
Parse input text using the specified grammar element function.
|
20
|
+
|
21
|
+
This internal function handles the common parsing logic for all grammar elements,
|
22
|
+
including error handling and parse tree walking.
|
23
|
+
|
24
|
+
:param input_text: The text to parse
|
25
|
+
:type input_text: str
|
26
|
+
|
27
|
+
:param fn_element: The parser function to use for parsing
|
28
|
+
:type fn_element: callable
|
29
|
+
|
30
|
+
:param force_finished: Whether to check if parsing consumed all input
|
31
|
+
:type force_finished: bool
|
32
|
+
|
33
|
+
:return: The parsed node representation of the input
|
34
|
+
:rtype: object
|
35
|
+
|
36
|
+
:raises: Various parsing errors if the input doesn't match the grammar
|
37
|
+
"""
|
38
|
+
error_listener = CollectingErrorListener()
|
39
|
+
|
40
|
+
input_stream = InputStream(input_text)
|
41
|
+
lexer = GrammarLexer(input_stream)
|
42
|
+
lexer.removeErrorListeners()
|
43
|
+
lexer.addErrorListener(error_listener)
|
44
|
+
|
45
|
+
stream = CommonTokenStream(lexer)
|
46
|
+
parser = GrammarParser(stream)
|
47
|
+
parser.removeErrorListeners()
|
48
|
+
parser.addErrorListener(error_listener)
|
49
|
+
|
50
|
+
parse_tree = fn_element(parser)
|
51
|
+
if force_finished:
|
52
|
+
error_listener.check_unfinished_parsing_error(stream)
|
53
|
+
error_listener.check_errors()
|
54
|
+
|
55
|
+
listener = GrammarParseListener()
|
56
|
+
walker = ParseTreeWalker()
|
57
|
+
walker.walk(listener, parse_tree)
|
58
|
+
return listener.nodes[parse_tree]
|
59
|
+
|
60
|
+
|
61
|
+
def parse_with_grammar_entry(input_text: str, entry_name: str, force_finished: bool = True):
|
62
|
+
"""
|
63
|
+
Parse input text using a specified grammar entry point.
|
64
|
+
|
65
|
+
This function allows parsing with any entry point in the grammar by name.
|
66
|
+
|
67
|
+
:param input_text: The text to parse
|
68
|
+
:type input_text: str
|
69
|
+
|
70
|
+
:param entry_name: The name of the grammar rule to use as entry point
|
71
|
+
:type entry_name: str
|
72
|
+
|
73
|
+
:param force_finished: Whether to check if parsing consumed all input
|
74
|
+
:type force_finished: bool
|
75
|
+
|
76
|
+
:return: The parsed node representation of the input
|
77
|
+
:rtype: object
|
78
|
+
|
79
|
+
:raises: Various parsing errors if the input doesn't match the grammar
|
80
|
+
|
81
|
+
Example::
|
82
|
+
|
83
|
+
>>> result = parse_with_grammar_entry("x > 5", "condition")
|
84
|
+
"""
|
85
|
+
return _parse_as_element(
|
86
|
+
input_text=input_text,
|
87
|
+
fn_element=getattr(GrammarParser, entry_name),
|
88
|
+
force_finished=force_finished
|
89
|
+
)
|
90
|
+
|
91
|
+
|
92
|
+
def parse_condition(input_text: str):
|
93
|
+
"""
|
94
|
+
Parse input text as a condition expression.
|
95
|
+
|
96
|
+
This function specifically parses conditional expressions according to
|
97
|
+
the grammar's condition rule.
|
98
|
+
|
99
|
+
:param input_text: The condition text to parse
|
100
|
+
:type input_text: str
|
101
|
+
|
102
|
+
:return: The parsed condition node
|
103
|
+
:rtype: object
|
104
|
+
|
105
|
+
:raises: Various parsing errors if the input doesn't match the condition grammar
|
106
|
+
|
107
|
+
Example::
|
108
|
+
|
109
|
+
>>> condition_node = parse_condition("x > 5 && y < 10")
|
110
|
+
"""
|
111
|
+
return parse_with_grammar_entry(input_text, 'condition')
|
112
|
+
|
113
|
+
|
114
|
+
def parse_preamble(input_text: str):
|
115
|
+
"""
|
116
|
+
Parse input text as a preamble program.
|
117
|
+
|
118
|
+
This function specifically parses preamble programs according to
|
119
|
+
the grammar's preamble_program rule.
|
120
|
+
|
121
|
+
:param input_text: The preamble program text to parse
|
122
|
+
:type input_text: str
|
123
|
+
|
124
|
+
:return: The parsed preamble program node
|
125
|
+
:rtype: object
|
126
|
+
|
127
|
+
:raises: Various parsing errors if the input doesn't match the preamble grammar
|
128
|
+
|
129
|
+
Example::
|
130
|
+
|
131
|
+
>>> preamble_node = parse_preamble("x = 10;")
|
132
|
+
"""
|
133
|
+
return parse_with_grammar_entry(input_text, 'preamble_program')
|
134
|
+
|
135
|
+
|
136
|
+
def parse_operation(input_text: str):
|
137
|
+
"""
|
138
|
+
Parse input text as an operation program.
|
139
|
+
|
140
|
+
This function specifically parses operation programs according to
|
141
|
+
the grammar's operation_program rule.
|
142
|
+
|
143
|
+
:param input_text: The operation program text to parse
|
144
|
+
:type input_text: str
|
145
|
+
|
146
|
+
:return: The parsed operation program node
|
147
|
+
:rtype: object
|
148
|
+
|
149
|
+
:raises: Various parsing errors if the input doesn't match the operation grammar
|
150
|
+
|
151
|
+
Example::
|
152
|
+
|
153
|
+
>>> operation_node = parse_operation("x := 10;")
|
154
|
+
"""
|
155
|
+
return parse_with_grammar_entry(input_text, 'operation_program')
|
@@ -0,0 +1 @@
|
|
1
|
+
from .cli import cli as pyfcstmcli
|
pyfcstm/entry/base.py
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
import builtins
|
2
|
+
import itertools
|
3
|
+
import os
|
4
|
+
import sys
|
5
|
+
import traceback
|
6
|
+
from functools import wraps, partial
|
7
|
+
from typing import Optional, IO, Callable
|
8
|
+
|
9
|
+
import click
|
10
|
+
from click.exceptions import ClickException
|
11
|
+
|
12
|
+
CONTEXT_SETTINGS = dict(
|
13
|
+
help_option_names=['-h', '--help']
|
14
|
+
)
|
15
|
+
|
16
|
+
|
17
|
+
class ClickWarningException(ClickException):
|
18
|
+
"""
|
19
|
+
Custom exception class for displaying warnings in yellow color.
|
20
|
+
|
21
|
+
:param message: The error message.
|
22
|
+
:type message: str
|
23
|
+
"""
|
24
|
+
|
25
|
+
def show(self, file: Optional[IO] = None) -> None:
|
26
|
+
"""
|
27
|
+
Display the warning message in yellow.
|
28
|
+
|
29
|
+
:param file: File to write the output to.
|
30
|
+
:type file: Optional[IO]
|
31
|
+
"""
|
32
|
+
click.secho(self.format_message(), fg='yellow', file=sys.stderr)
|
33
|
+
|
34
|
+
|
35
|
+
class ClickErrorException(ClickException):
|
36
|
+
"""
|
37
|
+
Custom exception class for displaying errors in red color.
|
38
|
+
|
39
|
+
:param message: The error message.
|
40
|
+
:type message: str
|
41
|
+
"""
|
42
|
+
|
43
|
+
def show(self, file: Optional[IO] = None) -> None:
|
44
|
+
"""
|
45
|
+
Display the error message in red.
|
46
|
+
|
47
|
+
:param file: File to write the output to.
|
48
|
+
:type file: Optional[IO]
|
49
|
+
"""
|
50
|
+
click.secho(self.format_message(), fg='red', file=sys.stderr)
|
51
|
+
|
52
|
+
|
53
|
+
def print_exception(err: BaseException, print: Optional[Callable] = None):
|
54
|
+
"""
|
55
|
+
Print exception information, including traceback.
|
56
|
+
|
57
|
+
:param err: The exception object.
|
58
|
+
:type err: BaseException
|
59
|
+
:param print: Custom print function. If not provided, uses built-in print.
|
60
|
+
:type print: Optional[Callable]
|
61
|
+
"""
|
62
|
+
print = print or builtins.print
|
63
|
+
|
64
|
+
lines = list(itertools.chain(*map(
|
65
|
+
lambda x: x.splitlines(keepends=False),
|
66
|
+
traceback.format_tb(err.__traceback__)
|
67
|
+
)))
|
68
|
+
|
69
|
+
if lines:
|
70
|
+
print('Traceback (most recent call last):')
|
71
|
+
print(os.linesep.join(lines))
|
72
|
+
|
73
|
+
if len(err.args) == 0:
|
74
|
+
print(f'{type(err).__name__}')
|
75
|
+
elif len(err.args) == 1:
|
76
|
+
print(f'{type(err).__name__}: {err.args[0]}')
|
77
|
+
else:
|
78
|
+
print(f'{type(err).__name__}: {err.args}')
|
79
|
+
|
80
|
+
|
81
|
+
class KeyboardInterrupted(ClickWarningException):
|
82
|
+
"""
|
83
|
+
Exception class for handling keyboard interruptions.
|
84
|
+
|
85
|
+
:param msg: Custom message to display.
|
86
|
+
:type msg: Optional[str]
|
87
|
+
"""
|
88
|
+
exit_code = 0x7
|
89
|
+
|
90
|
+
def __init__(self, msg=None):
|
91
|
+
"""
|
92
|
+
Initialize the exception.
|
93
|
+
|
94
|
+
:param msg: Custom message to display.
|
95
|
+
:type msg: Optional[str]
|
96
|
+
"""
|
97
|
+
ClickWarningException.__init__(self, msg or 'Interrupted.')
|
98
|
+
|
99
|
+
|
100
|
+
def command_wrap():
|
101
|
+
"""
|
102
|
+
Decorator for wrapping Click commands.
|
103
|
+
|
104
|
+
This decorator catches exceptions and provides consistent error handling.
|
105
|
+
|
106
|
+
:return: Decorator function.
|
107
|
+
:rtype: Callable
|
108
|
+
"""
|
109
|
+
|
110
|
+
def _decorator(func):
|
111
|
+
@wraps(func)
|
112
|
+
def _new_func(*args, **kwargs):
|
113
|
+
try:
|
114
|
+
return func(*args, **kwargs)
|
115
|
+
except ClickException:
|
116
|
+
raise
|
117
|
+
except KeyboardInterrupt:
|
118
|
+
raise KeyboardInterrupted
|
119
|
+
except BaseException as err:
|
120
|
+
click.secho('Unexpected error found when running pyfcstm!', fg='red', file=sys.stderr)
|
121
|
+
print_exception(err, partial(click.secho, fg='red', file=sys.stderr))
|
122
|
+
click.get_current_context().exit(1)
|
123
|
+
|
124
|
+
return _new_func
|
125
|
+
|
126
|
+
return _decorator
|
pyfcstm/entry/cli.py
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
from .dispatch import pyfcstmcli
|
2
|
+
from .generate import _add_generate_subcommand
|
3
|
+
from .plantuml import _add_plantuml_subcommand
|
4
|
+
|
5
|
+
_DECORATORS = [
|
6
|
+
_add_generate_subcommand,
|
7
|
+
_add_plantuml_subcommand,
|
8
|
+
]
|
9
|
+
|
10
|
+
cli = pyfcstmcli
|
11
|
+
for deco in _DECORATORS:
|
12
|
+
cli = deco(cli)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import click
|
2
|
+
from click.core import Context, Option
|
3
|
+
|
4
|
+
from .base import CONTEXT_SETTINGS
|
5
|
+
from ..config.meta import __TITLE__, __VERSION__, __AUTHOR__, __AUTHOR_EMAIL__, __DESCRIPTION__
|
6
|
+
|
7
|
+
_raw_authors = [item.strip() for item in __AUTHOR__.split(',') if item.strip()]
|
8
|
+
_raw_emails = [item.strip() for item in __AUTHOR_EMAIL__.split(',')]
|
9
|
+
if len(_raw_emails) < len(_raw_authors): # pragma: no cover
|
10
|
+
_raw_emails += [None] * (len(_raw_authors) - len(_raw_emails))
|
11
|
+
elif len(_raw_emails) > len(_raw_authors): # pragma: no cover
|
12
|
+
_raw_emails[len(_raw_authors) - 1] = tuple(_raw_emails[len(_raw_authors) - 1:])
|
13
|
+
del _raw_emails[len(_raw_authors):]
|
14
|
+
|
15
|
+
_author_tuples = [
|
16
|
+
(author, tuple([item for item in (email if isinstance(email, tuple) else ((email,) if email else ())) if item]))
|
17
|
+
for author, email in zip(_raw_authors, _raw_emails)
|
18
|
+
]
|
19
|
+
_authors = [
|
20
|
+
author if not emails else '{author} ({emails})'.format(author=author, emails=', '.join(emails))
|
21
|
+
for author, emails in _author_tuples
|
22
|
+
]
|
23
|
+
|
24
|
+
|
25
|
+
# noinspection PyUnusedLocal
|
26
|
+
def print_version(ctx: Context, param: Option, value: bool) -> None:
|
27
|
+
"""
|
28
|
+
Print version information of cli
|
29
|
+
:param ctx: click context
|
30
|
+
:param param: current parameter's metadata
|
31
|
+
:param value: value of current parameter
|
32
|
+
"""
|
33
|
+
if not value or ctx.resilient_parsing:
|
34
|
+
return # pragma: no cover
|
35
|
+
click.echo('{title}, version {version}.'.format(title=__TITLE__.capitalize(), version=__VERSION__))
|
36
|
+
if _authors:
|
37
|
+
click.echo('Developed by {authors}.'.format(authors=', '.join(_authors)))
|
38
|
+
ctx.exit()
|
39
|
+
|
40
|
+
|
41
|
+
@click.group(context_settings=CONTEXT_SETTINGS, help=__DESCRIPTION__)
|
42
|
+
@click.option('-v', '--version', is_flag=True,
|
43
|
+
callback=print_version, expose_value=False, is_eager=True,
|
44
|
+
help="Show pyfcstm's version information.")
|
45
|
+
def pyfcstmcli():
|
46
|
+
pass # pragma: no cover
|
@@ -0,0 +1,83 @@
|
|
1
|
+
"""
|
2
|
+
State Machine Code Generation Module
|
3
|
+
|
4
|
+
This module provides command line interface functionality for generating code from a state machine DSL.
|
5
|
+
It includes functionality to parse DSL code, convert it to a state machine model, and render the model
|
6
|
+
using templates to generate output code.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import pathlib
|
10
|
+
|
11
|
+
import click
|
12
|
+
|
13
|
+
from .base import CONTEXT_SETTINGS
|
14
|
+
from ..dsl import parse_with_grammar_entry
|
15
|
+
from ..model import parse_dsl_node_to_state_machine
|
16
|
+
from ..render import StateMachineCodeRenderer
|
17
|
+
|
18
|
+
|
19
|
+
def _add_generate_subcommand(cli: click.Group) -> click.Group:
|
20
|
+
"""
|
21
|
+
Add the 'generate' subcommand to the CLI group.
|
22
|
+
|
23
|
+
This function adds a 'generate' subcommand to the provided CLI group that allows users to
|
24
|
+
generate code from a state machine DSL file using specified templates.
|
25
|
+
|
26
|
+
:param cli: The click Group object to which the subcommand will be added
|
27
|
+
:type cli: click.Group
|
28
|
+
|
29
|
+
:return: The modified CLI group with the 'generate' subcommand added
|
30
|
+
:rtype: click.Group
|
31
|
+
|
32
|
+
Example::
|
33
|
+
|
34
|
+
>>> app = click.Group()
|
35
|
+
>>> app = _add_generate_subcommand(app)
|
36
|
+
"""
|
37
|
+
|
38
|
+
@cli.command('generate', help='Generate code with template of a given state machine DSL code.',
|
39
|
+
context_settings=CONTEXT_SETTINGS)
|
40
|
+
@click.option('-i', '--input-code', 'input_code_file', type=str, required=True,
|
41
|
+
help='Input code file of state machine DSL.')
|
42
|
+
@click.option('-t', '--template-dir', 'template_dir', type=str, required=True,
|
43
|
+
help='Template directory of the code generation.')
|
44
|
+
@click.option('-o', '--output-dir', 'output_dir', type=str, required=True,
|
45
|
+
help='Output directory of the code generation.')
|
46
|
+
@click.option('--clear', '--clear-directory', 'clear_directory', type=bool, is_flag=True,
|
47
|
+
help='Clear the destination directory of the output directory.')
|
48
|
+
def generate(input_code_file, template_dir, output_dir, clear_directory):
|
49
|
+
"""
|
50
|
+
Generate code from a state machine DSL file using templates.
|
51
|
+
|
52
|
+
This function reads a state machine DSL file, parses it into an AST node,
|
53
|
+
converts the AST node to a state machine model, and renders the model using
|
54
|
+
templates to generate output code.
|
55
|
+
|
56
|
+
:param input_code_file: Path to the input DSL code file
|
57
|
+
:type input_code_file: str
|
58
|
+
|
59
|
+
:param template_dir: Path to the directory containing templates
|
60
|
+
:type template_dir: str
|
61
|
+
|
62
|
+
:param output_dir: Path to the directory where generated code will be written
|
63
|
+
:type output_dir: str
|
64
|
+
|
65
|
+
:param clear_directory: Whether to clear the output directory before generating code
|
66
|
+
:type clear_directory: bool
|
67
|
+
|
68
|
+
:return: None
|
69
|
+
"""
|
70
|
+
code = pathlib.Path(input_code_file).read_text()
|
71
|
+
ast_node = parse_with_grammar_entry(code, entry_name='state_machine_dsl')
|
72
|
+
model = parse_dsl_node_to_state_machine(ast_node)
|
73
|
+
|
74
|
+
renderer = StateMachineCodeRenderer(
|
75
|
+
template_dir=template_dir,
|
76
|
+
)
|
77
|
+
renderer.render(
|
78
|
+
model,
|
79
|
+
output_dir=output_dir,
|
80
|
+
clear_previous_directory=clear_directory
|
81
|
+
)
|
82
|
+
|
83
|
+
return cli
|
@@ -0,0 +1,67 @@
|
|
1
|
+
"""
|
2
|
+
State Machine DSL to PlantUML Converter Module
|
3
|
+
|
4
|
+
This module provides functionality to convert state machine DSL code into PlantUML format.
|
5
|
+
It includes a command-line interface for easy conversion of state machine DSL files
|
6
|
+
to PlantUML code, which can be used to generate visual state machine diagrams.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import pathlib
|
10
|
+
|
11
|
+
import click
|
12
|
+
|
13
|
+
from .base import CONTEXT_SETTINGS
|
14
|
+
from ..dsl import parse_with_grammar_entry
|
15
|
+
from ..model import parse_dsl_node_to_state_machine
|
16
|
+
|
17
|
+
|
18
|
+
def _add_plantuml_subcommand(cli: click.Group) -> click.Group:
|
19
|
+
"""
|
20
|
+
Add the 'plantuml' subcommand to the provided CLI group.
|
21
|
+
|
22
|
+
This function adds a subcommand that converts state machine DSL code to PlantUML format.
|
23
|
+
The subcommand reads DSL code from a file, parses it into a state machine model,
|
24
|
+
and outputs the corresponding PlantUML code.
|
25
|
+
|
26
|
+
:param cli: The click Group to which the subcommand should be added
|
27
|
+
:type cli: click.Group
|
28
|
+
|
29
|
+
:return: The modified CLI group with the plantuml subcommand added
|
30
|
+
:rtype: click.Group
|
31
|
+
|
32
|
+
Example::
|
33
|
+
|
34
|
+
>>> from click import Group
|
35
|
+
>>> cli = Group()
|
36
|
+
>>> cli = _add_plantuml_subcommand(cli)
|
37
|
+
"""
|
38
|
+
|
39
|
+
@cli.command('plantuml', help='Create Plantuml code of a given state machine DSL code.',
|
40
|
+
context_settings=CONTEXT_SETTINGS)
|
41
|
+
@click.option('-i', '--input-code', 'input_code_file', type=str, required=True,
|
42
|
+
help='Input code file of state machine DSL.')
|
43
|
+
@click.option('-o', '--output', 'output_file', type=str, default=None,
|
44
|
+
help='Output directory of the code generation, output to stdout when not assigned.')
|
45
|
+
def plantuml(input_code_file, output_file):
|
46
|
+
"""
|
47
|
+
Convert state machine DSL code to PlantUML format.
|
48
|
+
|
49
|
+
This command reads state machine DSL code from the specified input file,
|
50
|
+
parses it into a state machine model, and outputs the corresponding PlantUML code
|
51
|
+
either to a file or to stdout.
|
52
|
+
|
53
|
+
:param input_code_file: Path to the file containing state machine DSL code
|
54
|
+
:type input_code_file: str
|
55
|
+
:param output_file: Path to the output file for PlantUML code (stdout if None)
|
56
|
+
:type output_file: str or None
|
57
|
+
"""
|
58
|
+
code = pathlib.Path(input_code_file).read_text()
|
59
|
+
ast_node = parse_with_grammar_entry(code, entry_name='state_machine_dsl')
|
60
|
+
model = parse_dsl_node_to_state_machine(ast_node)
|
61
|
+
if output_file is not None:
|
62
|
+
with open(output_file, 'w') as f:
|
63
|
+
f.write(model.to_plantuml())
|
64
|
+
else:
|
65
|
+
click.echo(model.to_plantuml())
|
66
|
+
|
67
|
+
return cli
|
pyfcstm/model/base.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
"""
|
2
|
+
Module for defining exportable interfaces for AST and PlantUML formats.
|
3
|
+
|
4
|
+
This module provides abstract base classes that define interfaces for objects
|
5
|
+
that can be exported to Abstract Syntax Tree (AST) nodes or PlantUML diagram
|
6
|
+
representations.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from ..dsl import node as dsl_nodes
|
10
|
+
|
11
|
+
|
12
|
+
class AstExportable:
|
13
|
+
"""
|
14
|
+
Abstract base class for objects that can be exported to AST nodes.
|
15
|
+
|
16
|
+
Classes that inherit from this interface should implement the to_ast_node
|
17
|
+
method to convert themselves into appropriate AST node representations.
|
18
|
+
|
19
|
+
:raises NotImplementedError: If the subclass does not implement the to_ast_node method.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def to_ast_node(self) -> dsl_nodes.ASTNode:
|
23
|
+
"""
|
24
|
+
Convert the object to an AST node representation.
|
25
|
+
|
26
|
+
:return: An AST node representing this object
|
27
|
+
:rtype: dsl_nodes.ASTNode
|
28
|
+
:raises NotImplementedError: This is an abstract method that must be implemented by subclasses
|
29
|
+
"""
|
30
|
+
raise NotImplementedError # pragma: no cover
|
31
|
+
|
32
|
+
|
33
|
+
class PlantUMLExportable:
|
34
|
+
"""
|
35
|
+
Abstract base class for objects that can be exported to PlantUML format.
|
36
|
+
|
37
|
+
Classes that inherit from this interface should implement the to_plantuml
|
38
|
+
method to convert themselves into PlantUML diagram syntax.
|
39
|
+
|
40
|
+
:raises NotImplementedError: If the subclass does not implement the to_plantuml method.
|
41
|
+
"""
|
42
|
+
|
43
|
+
def to_plantuml(self) -> str:
|
44
|
+
"""
|
45
|
+
Convert the object to a PlantUML diagram representation.
|
46
|
+
|
47
|
+
:return: A string containing PlantUML syntax representing this object
|
48
|
+
:rtype: str
|
49
|
+
:raises NotImplementedError: This is an abstract method that must be implemented by subclasses
|
50
|
+
"""
|
51
|
+
raise NotImplementedError # pragma: no cover
|