11import argparse
2- from pathlib import Path
32import json
43import sys
54import time
5+ from pathlib import Path
6+
67import yaml
7- from watchdog .observers import Observer
88from watchdog .events import FileSystemEventHandler
9+ from watchdog .observers import Observer
910
10- from stencil .main import run , generate_tree
11+ from stencil .main import generate_tree , run
1112
1213CONFIG_FILES = ["stencil.yaml" , "stencil.json" ]
1314DEFAULT_YAML_PATH = Path .cwd () / "stencil.yaml"
1415
15- DEFAULT_YAML_CONTENT = """# Stencil Configuration File
16- # ... (omitted for brevity, content is correct)
16+
17+ DEFAULT_YAML_CONTENT = """ # Stencil Configuration File
18+ # --------------------------
19+ # This file is used to define the UI elements for your application.
20+ # You can generate different outputs (like HTML or a desktop app) from the same config.
21+
22+ # Optional configuration for the project
23+ config:
24+ # The backend determines the output format.
25+ # Supported backends: "html", "imgui"
26+ # Default is "html".
27+ backend: "html"
28+ version: "1.0.0"
29+ author: "Your Name"
30+
31+ # The 'app' section defines the sequence of UI elements to be rendered.
32+ app:
33+ # 'title': Sets the main title of the page or window.
34+ - title: "My Awesome App"
35+
36+ # 'text': A block of text. Can be multi-line using the '|' character.
37+ - text: |
38+ Welcome to Stencil!
39+ This is a simple example of a UI defined in YAML.
40+
41+ # 'button': A clickable button.
42+ # 'label' is the text on the button.
43+ # 'callback' is the function name that will be called when clicked.
44+ # Stencil generates a placeholder for this function.
45+ - button:
46+ label: "Click Me!"
47+ callback: "onButtonClick"
48+
49+ # 'separator': A horizontal line to divide sections.
50+ - separator
51+
52+ # 'input': A text input field.
53+ # 'label' is the text displayed next to the input.
54+ # 'placeholder' is the text that appears when the input is empty.
55+ - input:
56+ label: "Your Name"
57+ placeholder: "Enter your name..."
58+
59+ - button:
60+ label: "Submit"
61+ callback: "doSomething"
62+
63+ - text: "© 2025 Your Company"
1764"""
1865
66+
1967def find_config ():
2068 root = Path .cwd ()
2169 for f in CONFIG_FILES :
2270 if (root / f ).exists ():
2371 return root / f
2472 return None
2573
74+
2675def handle_init ():
2776 if DEFAULT_YAML_PATH .exists ():
2877 print (f"'{ DEFAULT_YAML_PATH .name } ' already exists in this directory." )
2978 return 0
30-
79+
3180 with open (DEFAULT_YAML_PATH , "w" ) as f :
3281 f .write (DEFAULT_YAML_CONTENT )
3382 print (f"Successfully created a default '{ DEFAULT_YAML_PATH .name } '." )
3483 return 0
3584
85+
3686def do_generate (args ):
3787 config_path = find_config ()
3888 if not config_path :
@@ -46,15 +96,16 @@ def do_generate(args):
4696 config_data = yaml .safe_load (f )
4797 else :
4898 config_data = json .load (f )
49-
99+
50100 tree = generate_tree (config_data )
51101 run (tree , config_data , args )
52102 except (ValueError , TypeError ) as e :
53103 print (f"Error processing config file '{ config_path .name } ': { e } " , file = sys .stderr )
54104 return 1
55-
105+
56106 return 0
57107
108+
58109class ConfigChangeHandler (FileSystemEventHandler ):
59110 def __init__ (self , args ):
60111 self .args = args
@@ -70,46 +121,45 @@ def on_modified(self, event):
70121 do_generate (self .args )
71122 self .last_run = time .time ()
72123
124+
73125def main ():
74- parser = argparse .ArgumentParser (
75- description = "A tool to generate UI from a simple config file." ,
76- prog = "stencil"
77- )
126+ parser = argparse .ArgumentParser (description = "A tool to generate UI from a simple config file." , prog = "stencil" )
78127 subparsers = parser .add_subparsers (dest = "command" , help = "Available commands" )
79128
80129 subparsers .add_parser ("init" , help = "Create a default stencil.yaml file." )
81130 gen_parser = subparsers .add_parser ("generate" , help = "Generate UI from config file (default action)." )
82131 gen_parser .add_argument ("-w" , "--watch" , action = "store_true" , help = "Watch config and regenerate automatically" )
83- gen_parser .add_argument ("-b" , "--backend" , type = str , default = None , help = "The backend to use (html, imgui), overrides config file" )
132+ gen_parser .add_argument (
133+ "-b" , "--backend" , type = str , default = None , help = "The backend to use (html, imgui), overrides config file"
134+ )
84135
85136 args , unknown = parser .parse_known_args ()
86-
137+
87138 if args .command is None :
88139 if unknown :
89- # If there are unknown args and no command, maybe the user tried 'stencil --watch'
90- if ' --watch' in unknown or '-w' in unknown :
91- # Re-parse as if 'generate' was passed
92- args = parser .parse_args ([' generate' ] + sys .argv [1 :])
93- else :
94- parser .print_help ()
95- return 1
140+ # If there are unknown args and no command, maybe the user tried 'stencil --watch'
141+ if " --watch" in unknown or "-w" in unknown :
142+ # Re-parse as if 'generate' was passed
143+ args = parser .parse_args ([" generate" ] + sys .argv [1 :])
144+ else :
145+ parser .print_help ()
146+ return 1
96147 else :
97- # Default to generate
98- args = parser .parse_args (['generate' ])
99-
148+ # Default to generate
149+ args = parser .parse_args (["generate" ])
100150
101151 if args .command == "init" :
102152 return handle_init ()
103153 elif args .command == "generate" :
104154 result = do_generate (args )
105155 if result != 0 :
106156 return result
107-
157+
108158 if args .watch :
109159 config_path = find_config ()
110160 if not config_path :
111161 return 1
112-
162+
113163 event_handler = ConfigChangeHandler (args )
114164 observer = Observer ()
115165 observer .schedule (event_handler , path = config_path .parent , recursive = False )
@@ -122,11 +172,12 @@ def main():
122172 observer .stop ()
123173 print ("\n Observer stopped." )
124174 observer .join ()
125-
175+
126176 return 0
127177 else :
128178 parser .print_help ()
129179 return 1
130180
181+
131182if __name__ == "__main__" :
132183 sys .exit (main ())
0 commit comments