ComponentsMkcomponentPage.vala 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. using Spry;
  2. using Inversion;
  3. /**
  4. * ComponentsMkcomponentPage - Documentation for the spry-mkcomponent tool
  5. *
  6. * This page explains how to use spry-mkcomponent to inject HTML markup
  7. * into Vala component files.
  8. */
  9. public class ComponentsMkcomponentPage : PageComponent {
  10. public const string ROUTE = "/components/spry-mkcomponent";
  11. private ComponentFactory factory = inject<ComponentFactory>();
  12. public override string markup { get {
  13. return """
  14. <div sid="page" class="doc-content">
  15. <h1>spry-mkcomponent Tool</h1>
  16. <section class="doc-section">
  17. <h2>What is spry-mkcomponent?</h2>
  18. <p>
  19. <code>spry-mkcomponent</code> is a command-line tool that processes Vala component
  20. files by injecting HTML markup into the <code>markup</code> property getter. This
  21. allows you to maintain your HTML templates in separate files while keeping your
  22. Vala code clean and focused on logic.
  23. </p>
  24. <p>
  25. The tool enables a cleaner separation of concerns:
  26. </p>
  27. <ul>
  28. <li><strong>Component logic</strong> - Vala code handling actions, state, and behavior</li>
  29. <li><strong>HTML templates</strong> - Separate .html files with your markup</li>
  30. </ul>
  31. </section>
  32. <section class="doc-section">
  33. <h2>Command-Line Usage</h2>
  34. <spry-component name="CodeBlockComponent" sid="usage-code"/>
  35. <h3>Options</h3>
  36. <table class="doc-table">
  37. <thead>
  38. <tr>
  39. <th>Option</th>
  40. <th>Description</th>
  41. </tr>
  42. </thead>
  43. <tbody>
  44. <tr>
  45. <td><code>-o, --output FILE</code></td>
  46. <td>Output file path (default: input filename in build directory)</td>
  47. </tr>
  48. <tr>
  49. <td><code>-h, --html FILE</code></td>
  50. <td>HTML template file (default: <input_vala>.html)</td>
  51. </tr>
  52. <tr>
  53. <td><code>-v, --version</code></td>
  54. <td>Show version information</td>
  55. </tr>
  56. <tr>
  57. <td><code>--help</code></td>
  58. <td>Show help message</td>
  59. </tr>
  60. </tbody>
  61. </table>
  62. </section>
  63. <section class="doc-section">
  64. <h2>How It Works</h2>
  65. <p>
  66. The tool takes two input files and produces a single output file with the
  67. HTML template embedded in the markup property:
  68. </p>
  69. <spry-component name="CodeBlockComponent" sid="workflow-diagram"/>
  70. <div class="info-box">
  71. <p>
  72. <strong>๐Ÿ’ก Key Concept:</strong> The tool looks for an empty <code>markup</code>
  73. property getter and injects the HTML content. If the getter already has content,
  74. the file is passed through unchanged.
  75. </p>
  76. </div>
  77. </section>
  78. <section class="doc-section">
  79. <h2>Example Transformation</h2>
  80. <p>
  81. Here's a complete example showing how the tool transforms your component files:
  82. </p>
  83. <h3>Input Vala File (<code>UserContentComponent.vala</code>)</h3>
  84. <spry-component name="CodeBlockComponent" sid="input-vala"/>
  85. <h3>Input HTML File (<code>UserContentComponent.vala.html</code>)</h3>
  86. <spry-component name="CodeBlockComponent" sid="input-html"/>
  87. <h3>Output Vala File (<code>UserContentComponent.vala</code>)</h3>
  88. <spry-component name="CodeBlockComponent" sid="output-vala"/>
  89. <p>
  90. Notice how the empty <code>markup</code> getter now returns the HTML content
  91. as a verbatim string literal. The <code>sid</code> attributes in your HTML
  92. are preserved, allowing you to access elements via <code>this["sid"]</code>
  93. in your Vala code.
  94. </p>
  95. </section>
  96. <section class="doc-section">
  97. <h2>Integrating with Meson</h2>
  98. <p>
  99. The recommended way to use spry-mkcomponent is with meson's build system.
  100. You can use either <code>generator()</code> for processing multiple files
  101. or <code>custom_target()</code> for single files with custom options.
  102. </p>
  103. <h3>Using generator() for Multiple Files</h3>
  104. <spry-component name="CodeBlockComponent" sid="meson-generator"/>
  105. <p>
  106. The <code>generator()</code> function is ideal when you have multiple component
  107. files that all follow the same pattern (HTML file named
  108. <code><component>.vala.html</code>).
  109. </p>
  110. <h3>Using custom_target() for Single Files</h3>
  111. <spry-component name="CodeBlockComponent" sid="meson-custom-target"/>
  112. <p>
  113. Use <code>custom_target()</code> when you need more control over file paths
  114. or want to specify a custom HTML template location.
  115. </p>
  116. <h3>Complete meson.build Example</h3>
  117. <spry-component name="CodeBlockComponent" sid="meson-complete"/>
  118. <div class="info-box">
  119. <p>
  120. <strong>๐Ÿ’ก Tip:</strong> The <code>@INPUT@</code> and <code>@OUTPUT@</code>
  121. placeholders are automatically replaced by meson with the actual file paths.
  122. The <code>@BASENAME@</code> placeholder is replaced with the input filename
  123. without extension.
  124. </p>
  125. </div>
  126. </section>
  127. <section class="doc-section">
  128. <h2>HTML Template Guidelines</h2>
  129. <p>
  130. When creating HTML templates for use with spry-mkcomponent, follow these guidelines:
  131. </p>
  132. <ul>
  133. <li>
  134. <strong>Use <code>sid</code> attributes</strong> - Add <code>sid="name"</code>
  135. to elements you need to access from Vala code via <code>this["name"]</code>
  136. </li>
  137. <li>
  138. <strong>Keep templates alongside Vala files</strong> - Name them
  139. <code>ComponentName.vala.html</code> for automatic discovery
  140. </li>
  141. <li>
  142. <strong>Use HTMX attributes</strong> - Add <code>hx-*</code> attributes for
  143. interactivity without writing JavaScript
  144. </li>
  145. <li>
  146. <strong>Avoid complex logic</strong> - Keep templates focused on structure;
  147. use Vala for conditional rendering
  148. </li>
  149. </ul>
  150. <spry-component name="CodeBlockComponent" sid="html-guidelines"/>
  151. </section>
  152. <section class="doc-section">
  153. <h2>Workflow Integration</h2>
  154. <p>
  155. A typical development workflow with spry-mkcomponent looks like this:
  156. </p>
  157. <ol>
  158. <li><strong>Create</strong> your Vala component with an empty <code>markup</code> getter</li>
  159. <li><strong>Create</strong> an HTML template file with the same base name</li>
  160. <li><strong>Build</strong> your project - meson runs spry-mkcomponent automatically</li>
  161. <li><strong>Iterate</strong> - Edit either file; changes are picked up on rebuild</li>
  162. </ol>
  163. <p>
  164. During development, you can run the tool manually to preview the generated output:
  165. </p>
  166. <spry-component name="CodeBlockComponent" sid="manual-run"/>
  167. </section>
  168. <section class="doc-section">
  169. <h2>When to Use spry-mkcomponent</h2>
  170. <h3>โœ… Good Use Cases</h3>
  171. <ul>
  172. <li>Components with substantial HTML markup</li>
  173. <li>Teams where designers work on HTML separately from developers</li>
  174. <li>Reusing HTML templates across multiple components</li>
  175. <li>Keeping Vala files focused on logic</li>
  176. </ul>
  177. <h3>โŒ When to Skip It</h3>
  178. <ul>
  179. <li>Simple components with minimal markup</li>
  180. <li>Quick prototypes where separation isn't beneficial</li>
  181. <li>Components with dynamically generated markup</li>
  182. </ul>
  183. <div class="info-box">
  184. <p>
  185. <strong>๐Ÿ’ก Remember:</strong> You can always inline markup directly in your
  186. Vala files. spry-mkcomponent is opt-in - use it when the separation provides
  187. value for your project.
  188. </p>
  189. </div>
  190. </section>
  191. <section class="doc-section">
  192. <h2>Next Steps</h2>
  193. <div class="nav-cards">
  194. <a href="/components/template-syntax" class="nav-card">
  195. <h3>โ† Template Syntax</h3>
  196. <p>Learn about component template syntax</p>
  197. </a>
  198. <a href="/components/actions" class="nav-card">
  199. <h3>Actions โ†’</h3>
  200. <p>Handle user interactions in components</p>
  201. </a>
  202. </div>
  203. </section>
  204. </div>
  205. """;
  206. }}
  207. public override async void prepare() throws Error {
  208. var usage_code = get_component_child<CodeBlockComponent>("usage-code");
  209. usage_code.language = "Bash";
  210. usage_code.code = "spry-mkcomponent [OPTIONS] <INPUT_VALA_FILE>\n\n" +
  211. "# Examples:\n" +
  212. "spry-mkcomponent Components/UserComponent.vala\n" +
  213. "spry-mkcomponent -o build/UserComponent.vala Components/UserComponent.vala\n" +
  214. "spry-mkcomponent --html Templates/User.html Components/UserComponent.vala";
  215. var workflow_diagram = get_component_child<CodeBlockComponent>("workflow-diagram");
  216. workflow_diagram.language = "Text";
  217. workflow_diagram.code = "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" +
  218. "โ”‚ Component.vala โ”‚ โ”‚ Component.vala.html โ”‚\n" +
  219. "โ”‚ (empty markup getter) โ”‚ โ”‚ (HTML template) โ”‚\n" +
  220. "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n" +
  221. " โ”‚ โ”‚\n" +
  222. " โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n" +
  223. " โ”‚\n" +
  224. " โ–ผ\n" +
  225. " โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" +
  226. " โ”‚ spry-mkcomponent โ”‚\n" +
  227. " โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n" +
  228. " โ”‚\n" +
  229. " โ–ผ\n" +
  230. " โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" +
  231. " โ”‚ Component.vala โ”‚\n" +
  232. " โ”‚ (with generated getter) โ”‚\n" +
  233. " โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜";
  234. var input_vala = get_component_child<CodeBlockComponent>("input-vala");
  235. input_vala.language = "Vala";
  236. input_vala.code = "class UserContentComponent : Component {\n" +
  237. " public override string markup { get; }\n\n" +
  238. " public async override void handle_action(string action) throws Error {\n" +
  239. " this[\"message\"].text_content = \"Hello, World!\";\n" +
  240. " }\n" +
  241. "}";
  242. var input_html = get_component_child<CodeBlockComponent>("input-html");
  243. input_html.language = "HTML";
  244. input_html.code = "<p>You said: <strong sid=\"message\"></strong></p>";
  245. var output_vala = get_component_child<CodeBlockComponent>("output-vala");
  246. output_vala.language = "Vala";
  247. output_vala.code = "class UserContentComponent : Component {\n" +
  248. " public override string markup { get {\n" +
  249. " return \"\"\"<p>You said: <strong sid=\"message\"></strong></p>\"\"\";\n" +
  250. " }}\n\n" +
  251. " public async override void handle_action(string action) throws Error {\n" +
  252. " this[\"message\"].text_content = \"Hello, World!\";\n" +
  253. " }\n" +
  254. "}";
  255. var meson_generator = get_component_child<CodeBlockComponent>("meson-generator");
  256. meson_generator.language = "Meson";
  257. meson_generator.code = "# Find the tool\n" +
  258. "spry_mkcomponent = find_program('spry-mkcomponent')\n\n" +
  259. "# Define the generator once\n" +
  260. "spry_mkcomponent_gen = generator(spry_mkcomponent,\n" +
  261. " output: '@BASENAME@.vala',\n" +
  262. " arguments: ['@INPUT@', '@OUTPUT@']\n" +
  263. ")\n\n" +
  264. "# Process multiple component files\n" +
  265. "generated_components = spry_mkcomponent_gen.process(files(\n" +
  266. " 'Components/UserContentComponent.vala',\n" +
  267. " 'Components/LoginFormComponent.vala',\n" +
  268. " 'Components/NavSidebarComponent.vala',\n" +
  269. "))\n\n" +
  270. "# Use in executable\n" +
  271. "executable('myapp',\n" +
  272. " sources: [other_sources, generated_components],\n" +
  273. " dependencies: [spry_dep]\n" +
  274. ")";
  275. var meson_custom_target = get_component_child<CodeBlockComponent>("meson-custom-target");
  276. meson_custom_target.language = "Meson";
  277. meson_custom_target.code = "# Simple case - HTML file is ComponentName.vala.html\n" +
  278. "user_content_component = custom_target('user-content-component',\n" +
  279. " input: 'Components/UserContentComponent.vala',\n" +
  280. " output: 'UserContentComponent.vala',\n" +
  281. " command: [spry_mkcomponent, '@INPUT@', '@OUTPUT@']\n" +
  282. ")\n\n" +
  283. "# With explicit HTML file path\n" +
  284. "login_form_component = custom_target('login-form-component',\n" +
  285. " input: 'Components/LoginFormComponent.vala',\n" +
  286. " output: 'LoginFormComponent.vala',\n" +
  287. " command: [spry_mkcomponent, '@INPUT@', '@OUTPUT@',\n" +
  288. " '--html', 'Templates/LoginForm.html']\n" +
  289. ")";
  290. var meson_complete = get_component_child<CodeBlockComponent>("meson-complete");
  291. meson_complete.language = "Meson";
  292. meson_complete.code = "# Find the spry-mkcomponent tool\n" +
  293. "spry_mkcomponent = find_program('spry-mkcomponent')\n\n" +
  294. "# Define generator for components\n" +
  295. "component_gen = generator(spry_mkcomponent,\n" +
  296. " output: '@BASENAME@.vala',\n" +
  297. " arguments: ['@INPUT@', '@OUTPUT@']\n" +
  298. ")\n\n" +
  299. "# Process all component files with HTML templates\n" +
  300. "generated_components = component_gen.process(files(\n" +
  301. " 'Components/HeaderComponent.vala',\n" +
  302. " 'Components/FooterComponent.vala',\n" +
  303. " 'Components/SidebarComponent.vala',\n" +
  304. "))\n\n" +
  305. "# Other source files (no HTML templates)\n" +
  306. "other_sources = files(\n" +
  307. " 'Main.vala',\n" +
  308. " 'Components/SimpleComponent.vala', # Inline markup\n" +
  309. ")\n\n" +
  310. "# Build executable with generated components\n" +
  311. "executable('myapp',\n" +
  312. " sources: [other_sources, generated_components],\n" +
  313. " dependencies: [spry_dep],\n" +
  314. " vala_args: ['--vapidir', spry_vapi_dir]\n" +
  315. ")";
  316. var html_guidelines = get_component_child<CodeBlockComponent>("html-guidelines");
  317. html_guidelines.language = "HTML";
  318. html_guidelines.code = "<!-- Good: Use sid attributes for elements you need to access -->\n" +
  319. "<div class=\"user-card\">\n" +
  320. " <h2 sid=\"username\"></h2>\n" +
  321. " <p sid=\"email\"></p>\n" +
  322. " <button sid=\"delete-btn\" hx-post=\"/delete\" hx-target=\"closest .user-card\">\n" +
  323. " Delete\n" +
  324. " </button>\n" +
  325. "</div>\n\n" +
  326. "<!-- Good: Keep it simple and focused on structure -->\n" +
  327. "<form sid=\"login-form\" hx-post=\"/login\">\n" +
  328. " <input type=\"text\" name=\"username\" sid=\"username-input\"/>\n" +
  329. " <input type=\"password\" name=\"password\" sid=\"password-input\"/>\n" +
  330. " <button type=\"submit\">Login</button>\n" +
  331. "</form>";
  332. var manual_run = get_component_child<CodeBlockComponent>("manual-run");
  333. manual_run.language = "Bash";
  334. manual_run.code = "# Run manually to see generated output\n" +
  335. "spry-mkcomponent Components/MyComponent.vala -o /tmp/output.vala\n\n" +
  336. "# View the result\n" +
  337. "cat /tmp/output.vala\n\n" +
  338. "# Check if the tool would modify a file (compare with diff)\n" +
  339. "spry-mkcomponent Components/MyComponent.vala -o /tmp/test.vala && \\\n" +
  340. " diff Components/MyComponent.vala /tmp/test.vala";
  341. }
  342. }