openscad-parser 2.3.4__tar.gz → 2.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscad_parser
3
- Version: 2.3.4
3
+ Version: 2.4.0
4
4
  Summary: A PEG parser to read OpenSCAD language source code, with optional AST tree generation.
5
5
  Keywords: openscad,openscad parser,parser
6
6
  Author: Revar Desmera
@@ -14,6 +14,9 @@ Classifier: Operating System :: MacOS :: MacOS X
14
14
  Classifier: Operating System :: Microsoft :: Windows
15
15
  Classifier: Operating System :: POSIX
16
16
  Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
17
20
  Classifier: Topic :: Artistic Software
18
21
  Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
19
22
  Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
@@ -62,6 +65,8 @@ Features
62
65
  - AST tree can contain comment nodes (single-line and multi-line)
63
66
  - AST tree uses dataclasses and can be pickled/unpickled for caching/serialization
64
67
  - JSON and YAML serialization/deserialization of AST trees
68
+ - Pretty-printer that converts an AST back to formatted OpenSCAD source (``to_openscad()``)
69
+ - Command-line interface (``openscad-parser``) for JSON/YAML/formatted output
65
70
 
66
71
  Installation
67
72
  ------------
@@ -552,6 +557,13 @@ Main Functions
552
557
  - ``scope.lookup_module(name)`` — search this scope and its parents
553
558
  - ``scope.parent`` — the enclosing scope (``None`` for root)
554
559
 
560
+ ``to_openscad(nodes: list[ASTNode], indent_width: int = 4)``
561
+ Convert a list of AST nodes to formatted OpenSCAD source code.
562
+
563
+ :param nodes: Top-level AST nodes as returned by the ``getAST*`` functions.
564
+ :param indent_width: Spaces per indentation level (default: 4).
565
+ :returns: Formatted OpenSCAD source as a string.
566
+
555
567
  Serialization Functions
556
568
  ~~~~~~~~~~~~~~~~~~~~~~~
557
569
 
@@ -759,6 +771,100 @@ They are also available from ``openscad_parser.ast.serialization``::
759
771
  ast_from_yaml,
760
772
  )
761
773
 
774
+ Pretty-Printing
775
+ ---------------
776
+
777
+ The ``to_openscad()`` function converts an AST back to formatted OpenSCAD source code::
778
+
779
+ from openscad_parser.ast import getASTfromString, to_openscad
780
+
781
+ code = "module box(w,h){cube([w,h,1]);}"
782
+ ast = getASTfromString(code)
783
+
784
+ formatted = to_openscad(ast)
785
+ # module box(w, h) {
786
+ # cube([w, h, 1.0]);
787
+ # }
788
+ print(formatted)
789
+
790
+ The pretty-printer normalises whitespace and indentation while preserving the logical
791
+ structure of the code. It supports all AST node types including modules, functions,
792
+ control structures, modifiers, list comprehensions, and comments.
793
+
794
+ ``to_openscad(nodes, indent_width=4)``
795
+ Convert a list of AST nodes to formatted OpenSCAD source.
796
+
797
+ :param nodes: Top-level AST nodes (as returned by ``getAST*`` functions).
798
+ :param indent_width: Spaces per indentation level (default: 4).
799
+ :returns: Formatted OpenSCAD source code as a string.
800
+
801
+ - Blank lines are inserted before and after module/function declarations.
802
+ - Single-child module instantiations are formatted inline; multiple children use a block.
803
+ - Comments are preserved when the AST was parsed with ``include_comments=True``.
804
+
805
+ Controlling indentation::
806
+
807
+ from openscad_parser.ast import getASTfromString, to_openscad
808
+
809
+ ast = getASTfromString("module m() { cube(1); }")
810
+ print(to_openscad(ast, indent_width=2))
811
+ # module m() {
812
+ # cube(1.0);
813
+ # }
814
+
815
+ Command-Line Interface
816
+ -----------------------
817
+
818
+ The ``openscad-parser`` CLI is installed alongside the package::
819
+
820
+ pip install openscad-parser
821
+
822
+ Usage::
823
+
824
+ openscad-parser [OPTIONS] [FILE]
825
+
826
+ Read from a file or ``-`` for stdin. Default output is JSON.
827
+
828
+ **Options:**
829
+
830
+ ``--json``
831
+ Output AST as JSON (default).
832
+
833
+ ``--yaml``
834
+ Output AST as YAML (requires ``pip install openscad_parser[yaml]``).
835
+
836
+ ``--format``
837
+ Output reformatted OpenSCAD source code.
838
+
839
+ ``--indent N``
840
+ Indentation width in spaces (default: 4). Applies to ``--format`` and ``--json``.
841
+
842
+ ``--include-comments``
843
+ Include comment nodes in the output.
844
+
845
+ ``--no-includes``
846
+ Do not expand ``include <...>`` statements; keep ``IncludeStatement`` nodes instead.
847
+
848
+ **Examples:**
849
+
850
+ Dump AST as JSON::
851
+
852
+ openscad-parser model.scad
853
+ openscad-parser - < model.scad # stdin
854
+
855
+ Reformat OpenSCAD source::
856
+
857
+ openscad-parser --format model.scad
858
+ openscad-parser --format --indent 2 model.scad
859
+
860
+ Output YAML::
861
+
862
+ openscad-parser --yaml model.scad
863
+
864
+ Include comments in the AST::
865
+
866
+ openscad-parser --include-comments --json model.scad
867
+
762
868
  Error Handling
763
869
  --------------
764
870
 
@@ -22,6 +22,8 @@ Features
22
22
  - AST tree can contain comment nodes (single-line and multi-line)
23
23
  - AST tree uses dataclasses and can be pickled/unpickled for caching/serialization
24
24
  - JSON and YAML serialization/deserialization of AST trees
25
+ - Pretty-printer that converts an AST back to formatted OpenSCAD source (``to_openscad()``)
26
+ - Command-line interface (``openscad-parser``) for JSON/YAML/formatted output
25
27
 
26
28
  Installation
27
29
  ------------
@@ -512,6 +514,13 @@ Main Functions
512
514
  - ``scope.lookup_module(name)`` — search this scope and its parents
513
515
  - ``scope.parent`` — the enclosing scope (``None`` for root)
514
516
 
517
+ ``to_openscad(nodes: list[ASTNode], indent_width: int = 4)``
518
+ Convert a list of AST nodes to formatted OpenSCAD source code.
519
+
520
+ :param nodes: Top-level AST nodes as returned by the ``getAST*`` functions.
521
+ :param indent_width: Spaces per indentation level (default: 4).
522
+ :returns: Formatted OpenSCAD source as a string.
523
+
515
524
  Serialization Functions
516
525
  ~~~~~~~~~~~~~~~~~~~~~~~
517
526
 
@@ -719,6 +728,100 @@ They are also available from ``openscad_parser.ast.serialization``::
719
728
  ast_from_yaml,
720
729
  )
721
730
 
731
+ Pretty-Printing
732
+ ---------------
733
+
734
+ The ``to_openscad()`` function converts an AST back to formatted OpenSCAD source code::
735
+
736
+ from openscad_parser.ast import getASTfromString, to_openscad
737
+
738
+ code = "module box(w,h){cube([w,h,1]);}"
739
+ ast = getASTfromString(code)
740
+
741
+ formatted = to_openscad(ast)
742
+ # module box(w, h) {
743
+ # cube([w, h, 1.0]);
744
+ # }
745
+ print(formatted)
746
+
747
+ The pretty-printer normalises whitespace and indentation while preserving the logical
748
+ structure of the code. It supports all AST node types including modules, functions,
749
+ control structures, modifiers, list comprehensions, and comments.
750
+
751
+ ``to_openscad(nodes, indent_width=4)``
752
+ Convert a list of AST nodes to formatted OpenSCAD source.
753
+
754
+ :param nodes: Top-level AST nodes (as returned by ``getAST*`` functions).
755
+ :param indent_width: Spaces per indentation level (default: 4).
756
+ :returns: Formatted OpenSCAD source code as a string.
757
+
758
+ - Blank lines are inserted before and after module/function declarations.
759
+ - Single-child module instantiations are formatted inline; multiple children use a block.
760
+ - Comments are preserved when the AST was parsed with ``include_comments=True``.
761
+
762
+ Controlling indentation::
763
+
764
+ from openscad_parser.ast import getASTfromString, to_openscad
765
+
766
+ ast = getASTfromString("module m() { cube(1); }")
767
+ print(to_openscad(ast, indent_width=2))
768
+ # module m() {
769
+ # cube(1.0);
770
+ # }
771
+
772
+ Command-Line Interface
773
+ -----------------------
774
+
775
+ The ``openscad-parser`` CLI is installed alongside the package::
776
+
777
+ pip install openscad-parser
778
+
779
+ Usage::
780
+
781
+ openscad-parser [OPTIONS] [FILE]
782
+
783
+ Read from a file or ``-`` for stdin. Default output is JSON.
784
+
785
+ **Options:**
786
+
787
+ ``--json``
788
+ Output AST as JSON (default).
789
+
790
+ ``--yaml``
791
+ Output AST as YAML (requires ``pip install openscad_parser[yaml]``).
792
+
793
+ ``--format``
794
+ Output reformatted OpenSCAD source code.
795
+
796
+ ``--indent N``
797
+ Indentation width in spaces (default: 4). Applies to ``--format`` and ``--json``.
798
+
799
+ ``--include-comments``
800
+ Include comment nodes in the output.
801
+
802
+ ``--no-includes``
803
+ Do not expand ``include <...>`` statements; keep ``IncludeStatement`` nodes instead.
804
+
805
+ **Examples:**
806
+
807
+ Dump AST as JSON::
808
+
809
+ openscad-parser model.scad
810
+ openscad-parser - < model.scad # stdin
811
+
812
+ Reformat OpenSCAD source::
813
+
814
+ openscad-parser --format model.scad
815
+ openscad-parser --format --indent 2 model.scad
816
+
817
+ Output YAML::
818
+
819
+ openscad-parser --yaml model.scad
820
+
821
+ Include comments in the AST::
822
+
823
+ openscad-parser --include-comments --json model.scad
824
+
722
825
  Error Handling
723
826
  --------------
724
827
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "openscad_parser"
7
- version = "2.3.4"
7
+ version = "2.4.0"
8
8
  description = "A PEG parser to read OpenSCAD language source code, with optional AST tree generation."
9
9
  readme = "README.rst"
10
10
  authors = [
@@ -24,6 +24,9 @@ classifiers = [
24
24
  "Operating System :: Microsoft :: Windows",
25
25
  "Operating System :: POSIX",
26
26
  "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
27
30
  "Topic :: Artistic Software",
28
31
  "Topic :: Multimedia :: Graphics :: 3D Modeling",
29
32
  "Topic :: Multimedia :: Graphics :: 3D Rendering",
@@ -35,6 +38,9 @@ dependencies = [
35
38
  "arpeggio>=2.0.3",
36
39
  ]
37
40
 
41
+ [project.scripts]
42
+ openscad-parser = "openscad_parser.cli:main"
43
+
38
44
  [project.optional-dependencies]
39
45
  dev = [
40
46
  "pytest>=7.0.0",
@@ -87,6 +87,9 @@ from .builder import ASTBuilderVisitor, Position
87
87
  # Import scope classes
88
88
  from .scope import Scope, build_scopes
89
89
 
90
+ # Import pretty-printer
91
+ from .pretty_print import to_openscad
92
+
90
93
  # Import serialization functions
91
94
  from .serialization import (
92
95
  ast_to_dict,
@@ -333,7 +333,7 @@ class RangeLiteral(Primary):
333
333
  step: Expression
334
334
 
335
335
  def __str__(self):
336
- return f"{self.start}:{self.end}:{self.step}"
336
+ return f"[{self.start}:{self.end}:{self.step}]"
337
337
 
338
338
  def build_scope(self, parent_scope: "Scope") -> None:
339
339
  self.scope = parent_scope
@@ -1208,7 +1208,7 @@ class ListCompLet(VectorElement):
1208
1208
  body: Expression
1209
1209
 
1210
1210
  def __str__(self):
1211
- return f"let ({self.assignments}) {self.body}"
1211
+ return f"let ({', '.join(str(a) for a in self.assignments)}) {self.body}"
1212
1212
 
1213
1213
  def build_scope(self, parent_scope: "Scope") -> None:
1214
1214
  self.scope = parent_scope
@@ -1267,7 +1267,7 @@ class ListCompFor(VectorElement):
1267
1267
  body: VectorElement
1268
1268
 
1269
1269
  def __str__(self):
1270
- return f"for ({self.assignments}) {self.body}"
1270
+ return f"for ({', '.join(str(a) for a in self.assignments)}) {self.body}"
1271
1271
 
1272
1272
  def build_scope(self, parent_scope: "Scope") -> None:
1273
1273
  self.scope = parent_scope
@@ -1308,7 +1308,7 @@ class ListCompCFor(VectorElement):
1308
1308
  body: VectorElement
1309
1309
 
1310
1310
  def __str__(self):
1311
- return f"for ({self.initial}; {self.condition}; {self.increment}) {self.body}"
1311
+ return f"for ({', '.join(str(a) for a in self.initial)}; {self.condition}; {', '.join(str(a) for a in self.increment)}) {self.body}"
1312
1312
 
1313
1313
  def build_scope(self, parent_scope: "Scope") -> None:
1314
1314
  self.scope = parent_scope
@@ -0,0 +1,169 @@
1
+ """Pretty-printer: convert an OpenSCAD AST back to formatted source code."""
2
+ from __future__ import annotations
3
+ from .nodes import (
4
+ ASTNode, Assignment, FunctionDeclaration, ModuleDeclaration,
5
+ UseStatement, IncludeStatement,
6
+ ModuleInstantiation,
7
+ ModularCall, ModularFor, ModularCFor,
8
+ ModularIntersectionFor, ModularIntersectionCFor,
9
+ ModularLet, ModularEcho, ModularAssert,
10
+ ModularIf, ModularIfElse,
11
+ ModularModifierShowOnly, ModularModifierHighlight,
12
+ ModularModifierBackground, ModularModifierDisable,
13
+ CommentLine, CommentSpan,
14
+ )
15
+
16
+
17
+ def to_openscad(nodes: list[ASTNode], indent_width: int = 4) -> str:
18
+ """Convert a list of AST nodes to formatted OpenSCAD source code.
19
+
20
+ Args:
21
+ nodes: The AST nodes to format (top-level statements).
22
+ indent_width: Number of spaces per indentation level (default: 4).
23
+
24
+ Returns:
25
+ Formatted OpenSCAD source code as a string.
26
+ """
27
+ parts = []
28
+ prev_complex = False
29
+ for node in nodes:
30
+ is_complex = isinstance(node, (ModuleDeclaration, FunctionDeclaration))
31
+ if parts and (is_complex or prev_complex):
32
+ parts.append("")
33
+ parts.append(_fmt_node(node, 0, indent_width))
34
+ prev_complex = is_complex
35
+ return "\n".join(parts)
36
+
37
+
38
+ # --- helpers ---
39
+
40
+ def _as_list(val) -> list:
41
+ if isinstance(val, list):
42
+ return val
43
+ if val is None:
44
+ return []
45
+ return [val]
46
+
47
+
48
+ def _join_str(items) -> str:
49
+ return ", ".join(str(i) for i in items)
50
+
51
+
52
+ def _fmt_node(node: ASTNode, indent: int, w: int) -> str:
53
+ """Format any top-level or block-body node."""
54
+ pad = " " * indent
55
+
56
+ if isinstance(node, CommentLine):
57
+ return f"{pad}//{node.text}"
58
+ if isinstance(node, CommentSpan):
59
+ return f"{pad}/*{node.text}*/"
60
+ if isinstance(node, UseStatement):
61
+ return f"{pad}use <{node.filepath.val}>"
62
+ if isinstance(node, IncludeStatement):
63
+ return f"{pad}include <{node.filepath.val}>"
64
+ if isinstance(node, Assignment):
65
+ return f"{pad}{node.name} = {node.expr};"
66
+ if isinstance(node, FunctionDeclaration):
67
+ params = _join_str(node.parameters)
68
+ return f"{pad}function {node.name}({params}) = {node.expr};"
69
+ if isinstance(node, ModuleDeclaration):
70
+ params = _join_str(node.parameters)
71
+ block = _fmt_block(node.children, indent, w)
72
+ return f"{pad}module {node.name}({params}) {block}"
73
+ if isinstance(node, ModuleInstantiation):
74
+ return _fmt_inst(node, indent, w)
75
+ return f"{pad}{node}"
76
+
77
+
78
+ def _fmt_block(nodes: list, indent: int, w: int) -> str:
79
+ """Format a list of nodes as a braced block."""
80
+ pad = " " * indent
81
+ if not nodes:
82
+ return "{}"
83
+ inner = "\n".join(_fmt_node(n, indent + w, w) for n in nodes)
84
+ return "{\n" + inner + "\n" + pad + "}"
85
+
86
+
87
+ def _fmt_child(body, indent: int, w: int) -> str:
88
+ """Format the child body of a module instantiation.
89
+
90
+ Returns the tail string appended after the header:
91
+ - ``";"`` when there are no children
92
+ - ``"\\n child;"`` for a single inline child
93
+ - ``" {\\n ...\\n}"`` for a block of multiple children
94
+ """
95
+ nodes = _as_list(body)
96
+ pad = " " * indent
97
+
98
+ if not nodes:
99
+ return ";"
100
+ if len(nodes) == 1:
101
+ return "\n" + _fmt_inst(nodes[0], indent + w, w)
102
+ inner = "\n".join(_fmt_inst(n, indent + w, w) for n in nodes)
103
+ return " {\n" + inner + "\n" + pad + "}"
104
+
105
+
106
+ def _fmt_inst(node: ModuleInstantiation, indent: int, w: int, prefix: str = "") -> str:
107
+ """Format a ModuleInstantiation node.
108
+
109
+ ``prefix`` accumulates modifier characters (``!``, ``#``, ``%``, ``*``)
110
+ so nested modifiers produce e.g. ``!#cube(10);``.
111
+ """
112
+ pad = " " * indent
113
+
114
+ # Modifiers: push prefix down to the wrapped node
115
+ if isinstance(node, ModularModifierShowOnly):
116
+ return _fmt_inst(node.child, indent, w, "!" + prefix)
117
+ if isinstance(node, ModularModifierHighlight):
118
+ return _fmt_inst(node.child, indent, w, "#" + prefix)
119
+ if isinstance(node, ModularModifierBackground):
120
+ return _fmt_inst(node.child, indent, w, "%" + prefix)
121
+ if isinstance(node, ModularModifierDisable):
122
+ return _fmt_inst(node.child, indent, w, "*" + prefix)
123
+
124
+ if isinstance(node, ModularCall):
125
+ args = _join_str(node.arguments)
126
+ return f"{pad}{prefix}{node.name}({args})" + _fmt_child(node.children, indent, w)
127
+
128
+ if isinstance(node, ModularFor):
129
+ assigns = _join_str(_as_list(node.assignments))
130
+ return f"{pad}{prefix}for ({assigns})" + _fmt_child(node.body, indent, w)
131
+
132
+ if isinstance(node, ModularCFor):
133
+ init = _join_str(_as_list(node.initial))
134
+ inc = _join_str(_as_list(node.increment))
135
+ return f"{pad}{prefix}for ({init}; {node.condition}; {inc})" + _fmt_child(node.body, indent, w)
136
+
137
+ if isinstance(node, ModularIntersectionFor):
138
+ assigns = _join_str(_as_list(node.assignments))
139
+ return f"{pad}{prefix}intersection_for ({assigns})" + _fmt_child(node.body, indent, w)
140
+
141
+ if isinstance(node, ModularIntersectionCFor):
142
+ init = _join_str(_as_list(node.initial))
143
+ inc = _join_str(_as_list(node.increment))
144
+ return f"{pad}{prefix}intersection_for ({init}; {node.condition}; {inc})" + _fmt_child(node.body, indent, w)
145
+
146
+ if isinstance(node, ModularLet):
147
+ assigns = _join_str(_as_list(node.assignments))
148
+ return f"{pad}{prefix}let ({assigns})" + _fmt_child(node.children, indent, w)
149
+
150
+ if isinstance(node, ModularEcho):
151
+ args = _join_str(node.arguments)
152
+ return f"{pad}{prefix}echo({args})" + _fmt_child(node.children, indent, w)
153
+
154
+ if isinstance(node, ModularAssert):
155
+ args = _join_str(node.arguments)
156
+ return f"{pad}{prefix}assert({args})" + _fmt_child(node.children, indent, w)
157
+
158
+ if isinstance(node, ModularIf):
159
+ header = f"{pad}{prefix}if ({node.condition})"
160
+ return header + _fmt_child(node.true_branch, indent, w)
161
+
162
+ if isinstance(node, ModularIfElse):
163
+ header = f"{pad}{prefix}if ({node.condition})"
164
+ true_tail = _fmt_child(node.true_branch, indent, w)
165
+ false_tail = _fmt_child(node.false_branch, indent, w)
166
+ connector = " else" if true_tail.startswith(" {") else f"\n{pad}else"
167
+ return header + true_tail + connector + false_tail
168
+
169
+ return f"{pad}{prefix}{node};"
@@ -0,0 +1,84 @@
1
+ """Command-line interface for openscad_parser."""
2
+ import sys
3
+ import argparse
4
+ from openscad_parser.ast import getASTfromString, getASTfromFile, ast_to_json
5
+ from openscad_parser.ast.pretty_print import to_openscad
6
+
7
+
8
+ def main():
9
+ ap = argparse.ArgumentParser(
10
+ prog="openscad-parser",
11
+ description=(
12
+ "Parse an OpenSCAD file and dump its AST as JSON (default), "
13
+ "YAML, or reformatted OpenSCAD source."
14
+ ),
15
+ )
16
+ ap.add_argument(
17
+ "file",
18
+ nargs="?",
19
+ metavar="FILE",
20
+ help="OpenSCAD source file to parse. Omit or use '-' to read from stdin.",
21
+ )
22
+ output = ap.add_mutually_exclusive_group()
23
+ output.add_argument(
24
+ "--json", action="store_true", default=True,
25
+ help="Output AST as JSON (default).",
26
+ )
27
+ output.add_argument(
28
+ "--yaml", action="store_true",
29
+ help="Output AST as YAML (requires PyYAML).",
30
+ )
31
+ output.add_argument(
32
+ "--format", action="store_true",
33
+ help="Output reformatted OpenSCAD source code.",
34
+ )
35
+ ap.add_argument(
36
+ "--include-comments", action="store_true",
37
+ help="Include comment nodes in the AST.",
38
+ )
39
+ ap.add_argument(
40
+ "--no-includes", action="store_true",
41
+ help="Do not expand include <...> statements (keeps IncludeStatement nodes).",
42
+ )
43
+ ap.add_argument(
44
+ "--indent", type=int, default=4, metavar="N",
45
+ help="Indentation width in spaces (default: 4). Applies to --format and --json.",
46
+ )
47
+ args = ap.parse_args()
48
+
49
+ # --yaml and --format clear the --json default
50
+ if args.yaml or args.format:
51
+ args.json = False
52
+
53
+ try:
54
+ if args.file is None or args.file == "-":
55
+ code = sys.stdin.read()
56
+ ast = getASTfromString(code, include_comments=args.include_comments)
57
+ else:
58
+ ast = getASTfromFile(
59
+ args.file,
60
+ include_comments=args.include_comments,
61
+ process_includes=not args.no_includes,
62
+ )
63
+ except FileNotFoundError as e:
64
+ print(f"openscad-parser: {e}", file=sys.stderr)
65
+ sys.exit(1)
66
+
67
+ if ast is None:
68
+ sys.exit(1)
69
+
70
+ if args.format:
71
+ print(to_openscad(ast, indent_width=args.indent))
72
+ elif args.yaml:
73
+ try:
74
+ from openscad_parser.ast import ast_to_yaml
75
+ except ImportError:
76
+ print("openscad-parser: --yaml requires PyYAML (pip install openscad_parser[yaml])", file=sys.stderr)
77
+ sys.exit(1)
78
+ print(ast_to_yaml(ast))
79
+ else:
80
+ print(ast_to_json(ast, indent=args.indent))
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main()