bootstack 0.1.0__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.
Files changed (471) hide show
  1. bootstack/__init__.py +157 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/_core/__init__.py +21 -0
  4. bootstack/_core/capabilities/__init__.py +45 -0
  5. bootstack/_core/capabilities/after.py +103 -0
  6. bootstack/_core/capabilities/bind.py +192 -0
  7. bootstack/_core/capabilities/bindtags.py +112 -0
  8. bootstack/_core/capabilities/busy.py +72 -0
  9. bootstack/_core/capabilities/clipboard.py +89 -0
  10. bootstack/_core/capabilities/focus.py +118 -0
  11. bootstack/_core/capabilities/grab.py +65 -0
  12. bootstack/_core/capabilities/grid.py +211 -0
  13. bootstack/_core/capabilities/localization.py +231 -0
  14. bootstack/_core/capabilities/pack.py +140 -0
  15. bootstack/_core/capabilities/place.py +113 -0
  16. bootstack/_core/capabilities/selection.py +136 -0
  17. bootstack/_core/capabilities/signals.py +244 -0
  18. bootstack/_core/capabilities/winfo.py +315 -0
  19. bootstack/_core/colorutils.py +234 -0
  20. bootstack/_core/exceptions.py +25 -0
  21. bootstack/_core/images.py +463 -0
  22. bootstack/_core/mixins/__init__.py +1 -0
  23. bootstack/_core/mixins/ttk_state.py +35 -0
  24. bootstack/_core/mixins/widget.py +132 -0
  25. bootstack/_core/paths.py +49 -0
  26. bootstack/_core/publisher.py +149 -0
  27. bootstack/_core/variables.py +62 -0
  28. bootstack/_runtime/__init__.py +3 -0
  29. bootstack/_runtime/app.py +930 -0
  30. bootstack/_runtime/base_window.py +945 -0
  31. bootstack/_runtime/events.py +399 -0
  32. bootstack/_runtime/shortcuts.py +496 -0
  33. bootstack/_runtime/tk_patch.py +43 -0
  34. bootstack/_runtime/toplevel.py +276 -0
  35. bootstack/_runtime/utility.py +457 -0
  36. bootstack/_runtime/visual_focus.py +240 -0
  37. bootstack/_runtime/window_utilities.py +1123 -0
  38. bootstack/assets/__init__.py +21 -0
  39. bootstack/assets/bootstack.ico +0 -0
  40. bootstack/assets/bootstack.png +0 -0
  41. bootstack/assets/elements/__init__.py +0 -0
  42. bootstack/assets/elements/badge-pill.png +0 -0
  43. bootstack/assets/elements/badge-square.png +0 -0
  44. bootstack/assets/elements/button-compact.png +0 -0
  45. bootstack/assets/elements/button-default.png +0 -0
  46. bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
  47. bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
  48. bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
  49. bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
  50. bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
  51. bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
  52. bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
  53. bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
  54. bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
  55. bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
  56. bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
  57. bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
  58. bootstack/assets/elements/card.png +0 -0
  59. bootstack/assets/elements/checkbox-checked.png +0 -0
  60. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  61. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  62. bootstack/assets/elements/field.png +0 -0
  63. bootstack/assets/elements/input-addon-compact.png +0 -0
  64. bootstack/assets/elements/input-addon-default.png +0 -0
  65. bootstack/assets/elements/input-compact.png +0 -0
  66. bootstack/assets/elements/input-default.png +0 -0
  67. bootstack/assets/elements/list-item-separated.png +0 -0
  68. bootstack/assets/elements/list-item.png +0 -0
  69. bootstack/assets/elements/listrow-compact.png +0 -0
  70. bootstack/assets/elements/listrow-default.png +0 -0
  71. bootstack/assets/elements/manifest.toml +361 -0
  72. bootstack/assets/elements/menu-item.png +0 -0
  73. bootstack/assets/elements/navitem-compact.png +0 -0
  74. bootstack/assets/elements/navitem-default.png +0 -0
  75. bootstack/assets/elements/progressbar-h-compact.png +0 -0
  76. bootstack/assets/elements/progressbar-h-default.png +0 -0
  77. bootstack/assets/elements/progressbar-v-compact.png +0 -0
  78. bootstack/assets/elements/progressbar-v-default.png +0 -0
  79. bootstack/assets/elements/radiobutton.png +0 -0
  80. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  81. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  82. bootstack/assets/elements/slider-handle.png +0 -0
  83. bootstack/assets/elements/slider-track-h.png +0 -0
  84. bootstack/assets/elements/slider-track-v.png +0 -0
  85. bootstack/assets/elements/switch-off.png +0 -0
  86. bootstack/assets/elements/switch-on.png +0 -0
  87. bootstack/assets/elements/tab-h.png +0 -0
  88. bootstack/assets/elements/tab-v.png +0 -0
  89. bootstack/assets/icons/bootstrap.ttf +0 -0
  90. bootstack/assets/icons/glyphmap.json +2080 -0
  91. bootstack/assets/icons/icon_metrics.json +12470 -0
  92. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  93. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
  94. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
  95. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
  96. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  97. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
  98. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  99. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
  100. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  101. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
  102. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  103. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
  104. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  105. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
  106. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  107. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
  108. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  109. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
  110. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  111. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
  112. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  113. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
  114. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  115. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
  116. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  117. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
  118. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  119. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
  120. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  121. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
  122. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  123. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
  124. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  125. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
  126. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  127. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
  128. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  129. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
  130. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  131. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
  132. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  133. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
  134. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  135. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
  136. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  137. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
  138. bootstack/cli/__init__.py +133 -0
  139. bootstack/cli/__main__.py +6 -0
  140. bootstack/cli/add.py +395 -0
  141. bootstack/cli/appicon.py +285 -0
  142. bootstack/cli/build.py +115 -0
  143. bootstack/cli/config.py +313 -0
  144. bootstack/cli/demo.py +564 -0
  145. bootstack/cli/dev.py +153 -0
  146. bootstack/cli/doctor.py +195 -0
  147. bootstack/cli/icons.py +98 -0
  148. bootstack/cli/promote.py +120 -0
  149. bootstack/cli/pyinstaller.py +268 -0
  150. bootstack/cli/run.py +95 -0
  151. bootstack/cli/start.py +117 -0
  152. bootstack/cli/templates/__init__.py +931 -0
  153. bootstack/clipboard.py +48 -0
  154. bootstack/constants.py +318 -0
  155. bootstack/data/README.md +615 -0
  156. bootstack/data/__init__.py +78 -0
  157. bootstack/data/_observable.py +276 -0
  158. bootstack/data/base.py +780 -0
  159. bootstack/data/file_source.py +367 -0
  160. bootstack/data/memory_source.py +450 -0
  161. bootstack/data/query.py +367 -0
  162. bootstack/data/readers.py +289 -0
  163. bootstack/data/sqlite_source.py +869 -0
  164. bootstack/data/types.py +354 -0
  165. bootstack/data/writers.py +232 -0
  166. bootstack/dev/__init__.py +36 -0
  167. bootstack/dev/_capture.py +141 -0
  168. bootstack/dev/_env.py +43 -0
  169. bootstack/dev/_registry.py +140 -0
  170. bootstack/dev/_reloader.py +351 -0
  171. bootstack/dev/_reset.py +50 -0
  172. bootstack/dev/_watcher.py +91 -0
  173. bootstack/dialogs/__init__.py +948 -0
  174. bootstack/dialogs/_impl/__init__.py +47 -0
  175. bootstack/dialogs/_impl/colorchooser.py +588 -0
  176. bootstack/dialogs/_impl/datedialog.py +450 -0
  177. bootstack/dialogs/_impl/dialog.py +594 -0
  178. bootstack/dialogs/_impl/filterdialog.py +358 -0
  179. bootstack/dialogs/_impl/fontdialog.py +364 -0
  180. bootstack/dialogs/_impl/formdialog.py +564 -0
  181. bootstack/dialogs/_impl/message.py +486 -0
  182. bootstack/dialogs/_impl/query.py +570 -0
  183. bootstack/errors.py +67 -0
  184. bootstack/events/__init__.py +111 -0
  185. bootstack/events/_event.py +135 -0
  186. bootstack/events/_payloads.py +539 -0
  187. bootstack/events/_subscription.py +38 -0
  188. bootstack/i18n/README.md +77 -0
  189. bootstack/i18n/__init__.py +23 -0
  190. bootstack/i18n/catalog.py +121 -0
  191. bootstack/i18n/intl_format.py +584 -0
  192. bootstack/i18n/msgcat.py +425 -0
  193. bootstack/i18n/specs.py +156 -0
  194. bootstack/images.py +563 -0
  195. bootstack/py.typed +1 -0
  196. bootstack/scheduling/__init__.py +11 -0
  197. bootstack/scheduling/_schedule.py +218 -0
  198. bootstack/shortcuts.py +21 -0
  199. bootstack/signals/README.md +98 -0
  200. bootstack/signals/__init__.py +10 -0
  201. bootstack/signals/integration.py +100 -0
  202. bootstack/signals/signal.py +353 -0
  203. bootstack/signals/types.py +6 -0
  204. bootstack/store.py +286 -0
  205. bootstack/streams/__init__.py +12 -0
  206. bootstack/streams/_stream.py +321 -0
  207. bootstack/style/__init__.py +38 -0
  208. bootstack/style/builders/__init__.py +51 -0
  209. bootstack/style/builders/badge.py +46 -0
  210. bootstack/style/builders/button.py +339 -0
  211. bootstack/style/builders/buttongroup.py +311 -0
  212. bootstack/style/builders/calendar.py +271 -0
  213. bootstack/style/builders/checkbutton.py +110 -0
  214. bootstack/style/builders/combobox.py +113 -0
  215. bootstack/style/builders/contextmenu.py +268 -0
  216. bootstack/style/builders/entry.py +82 -0
  217. bootstack/style/builders/expander.py +148 -0
  218. bootstack/style/builders/field.py +335 -0
  219. bootstack/style/builders/frame.py +50 -0
  220. bootstack/style/builders/label.py +28 -0
  221. bootstack/style/builders/labelframe.py +34 -0
  222. bootstack/style/builders/listview.py +369 -0
  223. bootstack/style/builders/menubar.py +91 -0
  224. bootstack/style/builders/menubutton.py +359 -0
  225. bootstack/style/builders/panedwindow.py +25 -0
  226. bootstack/style/builders/progressbar.py +67 -0
  227. bootstack/style/builders/radiobutton.py +99 -0
  228. bootstack/style/builders/scale.py +64 -0
  229. bootstack/style/builders/scrollbar.py +225 -0
  230. bootstack/style/builders/separator.py +49 -0
  231. bootstack/style/builders/sidenav.py +643 -0
  232. bootstack/style/builders/sizegrip.py +15 -0
  233. bootstack/style/builders/spinbox.py +119 -0
  234. bootstack/style/builders/switch.py +70 -0
  235. bootstack/style/builders/tabitem.py +204 -0
  236. bootstack/style/builders/togglegroup.py +294 -0
  237. bootstack/style/builders/toolbutton.py +275 -0
  238. bootstack/style/builders/tooltip.py +26 -0
  239. bootstack/style/builders/treeview.py +193 -0
  240. bootstack/style/builders/utils.py +455 -0
  241. bootstack/style/builders_tk/__init__.py +16 -0
  242. bootstack/style/builders_tk/defaults.py +229 -0
  243. bootstack/style/element.py +173 -0
  244. bootstack/style/fonts.py +123 -0
  245. bootstack/style/style.py +609 -0
  246. bootstack/style/style_builder_base.py +716 -0
  247. bootstack/style/style_builder_mixed.py +93 -0
  248. bootstack/style/style_builder_tk.py +109 -0
  249. bootstack/style/style_builder_ttk.py +353 -0
  250. bootstack/style/style_resolver.py +447 -0
  251. bootstack/style/theme.py +245 -0
  252. bootstack/style/theme_provider.py +471 -0
  253. bootstack/style/themes/__init__.py +128 -0
  254. bootstack/style/tk_patch.py +5 -0
  255. bootstack/style/token_maps.py +41 -0
  256. bootstack/style/types.py +32 -0
  257. bootstack/style/typography.py +523 -0
  258. bootstack/style/utility.py +746 -0
  259. bootstack/types.py +39 -0
  260. bootstack/validation/__init__.py +6 -0
  261. bootstack/validation/types.py +15 -0
  262. bootstack/validation/validation_result.py +17 -0
  263. bootstack/validation/validation_rules.py +205 -0
  264. bootstack/widgets/__init__.py +74 -0
  265. bootstack/widgets/_core/__init__.py +31 -0
  266. bootstack/widgets/_core/app_config.py +266 -0
  267. bootstack/widgets/_core/base.py +461 -0
  268. bootstack/widgets/_core/container.py +334 -0
  269. bootstack/widgets/_core/context.py +35 -0
  270. bootstack/widgets/_core/events.py +130 -0
  271. bootstack/widgets/_core/field_mixin.py +353 -0
  272. bootstack/widgets/_core/icon_image_props.py +72 -0
  273. bootstack/widgets/_core/image_binding.py +93 -0
  274. bootstack/widgets/_core/navmodel.py +345 -0
  275. bootstack/widgets/_core/options.py +210 -0
  276. bootstack/widgets/_core/selection_group.py +130 -0
  277. bootstack/widgets/_core/window_controls.py +96 -0
  278. bootstack/widgets/_core/window_menu.py +246 -0
  279. bootstack/widgets/_impl/__init__.py +1 -0
  280. bootstack/widgets/_impl/_internal/__init__.py +0 -0
  281. bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
  282. bootstack/widgets/_impl/_parts/__init__.py +11 -0
  283. bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
  284. bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
  285. bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
  286. bootstack/widgets/_impl/composites/__init__.py +33 -0
  287. bootstack/widgets/_impl/composites/_dateutils.py +33 -0
  288. bootstack/widgets/_impl/composites/_image_fit.py +105 -0
  289. bootstack/widgets/_impl/composites/accordion.py +381 -0
  290. bootstack/widgets/_impl/composites/avatar.py +191 -0
  291. bootstack/widgets/_impl/composites/buttongroup.py +371 -0
  292. bootstack/widgets/_impl/composites/calendar.py +972 -0
  293. bootstack/widgets/_impl/composites/carousel.py +567 -0
  294. bootstack/widgets/_impl/composites/chart.py +882 -0
  295. bootstack/widgets/_impl/composites/compositeframe.py +298 -0
  296. bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
  297. bootstack/widgets/_impl/composites/dateentry.py +404 -0
  298. bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
  299. bootstack/widgets/_impl/composites/expander.py +515 -0
  300. bootstack/widgets/_impl/composites/field.py +670 -0
  301. bootstack/widgets/_impl/composites/form.py +1066 -0
  302. bootstack/widgets/_impl/composites/gallery.py +551 -0
  303. bootstack/widgets/_impl/composites/list/__init__.py +15 -0
  304. bootstack/widgets/_impl/composites/list/listitem.py +802 -0
  305. bootstack/widgets/_impl/composites/list/listview.py +1433 -0
  306. bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
  307. bootstack/widgets/_impl/composites/menu/model.py +358 -0
  308. bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
  309. bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
  310. bootstack/widgets/_impl/composites/meter.py +860 -0
  311. bootstack/widgets/_impl/composites/numericentry.py +201 -0
  312. bootstack/widgets/_impl/composites/pagestack.py +395 -0
  313. bootstack/widgets/_impl/composites/passwordentry.py +142 -0
  314. bootstack/widgets/_impl/composites/pathentry.py +168 -0
  315. bootstack/widgets/_impl/composites/picture.py +289 -0
  316. bootstack/widgets/_impl/composites/radiogroup.py +511 -0
  317. bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
  318. bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
  319. bootstack/widgets/_impl/composites/scrollview.py +764 -0
  320. bootstack/widgets/_impl/composites/selectbox.py +1026 -0
  321. bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
  322. bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
  323. bootstack/widgets/_impl/composites/shell/layout.py +335 -0
  324. bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
  325. bootstack/widgets/_impl/composites/shell/providers.py +558 -0
  326. bootstack/widgets/_impl/composites/shell/rail.py +117 -0
  327. bootstack/widgets/_impl/composites/shell/shell.py +581 -0
  328. bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
  329. bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
  330. bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
  331. bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
  332. bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
  333. bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
  334. bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
  335. bootstack/widgets/_impl/composites/slider/slider.py +851 -0
  336. bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
  337. bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
  338. bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
  339. bootstack/widgets/_impl/composites/tableview/types.py +169 -0
  340. bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
  341. bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
  342. bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
  343. bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
  344. bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
  345. bootstack/widgets/_impl/composites/textarea/change.py +46 -0
  346. bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
  347. bootstack/widgets/_impl/composites/textarea/core.py +495 -0
  348. bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
  349. bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
  350. bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
  351. bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
  352. bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
  353. bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
  354. bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
  355. bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
  356. bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
  357. bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
  358. bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
  359. bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
  360. bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
  361. bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
  362. bootstack/widgets/_impl/composites/textentry.py +57 -0
  363. bootstack/widgets/_impl/composites/timeentry.py +176 -0
  364. bootstack/widgets/_impl/composites/toast.py +390 -0
  365. bootstack/widgets/_impl/composites/toast_stack.py +156 -0
  366. bootstack/widgets/_impl/composites/togglegroup.py +418 -0
  367. bootstack/widgets/_impl/composites/toolbar.py +608 -0
  368. bootstack/widgets/_impl/composites/tooltip.py +491 -0
  369. bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
  370. bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
  371. bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
  372. bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
  373. bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
  374. bootstack/widgets/_impl/mixins/__init__.py +24 -0
  375. bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
  376. bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
  377. bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
  378. bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
  379. bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
  380. bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
  381. bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
  382. bootstack/widgets/_impl/primitives/__init__.py +49 -0
  383. bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
  384. bootstack/widgets/_impl/primitives/badge.py +45 -0
  385. bootstack/widgets/_impl/primitives/button.py +76 -0
  386. bootstack/widgets/_impl/primitives/card.py +45 -0
  387. bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
  388. bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
  389. bootstack/widgets/_impl/primitives/combobox.py +156 -0
  390. bootstack/widgets/_impl/primitives/entry.py +87 -0
  391. bootstack/widgets/_impl/primitives/flexframe.py +448 -0
  392. bootstack/widgets/_impl/primitives/frame.py +185 -0
  393. bootstack/widgets/_impl/primitives/gridframe.py +546 -0
  394. bootstack/widgets/_impl/primitives/label.py +84 -0
  395. bootstack/widgets/_impl/primitives/labelframe.py +54 -0
  396. bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
  397. bootstack/widgets/_impl/primitives/packframe.py +227 -0
  398. bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
  399. bootstack/widgets/_impl/primitives/progressbar.py +83 -0
  400. bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
  401. bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
  402. bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
  403. bootstack/widgets/_impl/primitives/separator.py +43 -0
  404. bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
  405. bootstack/widgets/_impl/primitives/spinbox.py +95 -0
  406. bootstack/widgets/_impl/primitives/switch.py +44 -0
  407. bootstack/widgets/_impl/primitives/treeview.py +69 -0
  408. bootstack/widgets/app.py +371 -0
  409. bootstack/widgets/appshell.py +1179 -0
  410. bootstack/widgets/avatar.py +140 -0
  411. bootstack/widgets/boolean_controls.py +455 -0
  412. bootstack/widgets/button.py +224 -0
  413. bootstack/widgets/buttongroup.py +239 -0
  414. bootstack/widgets/calendar.py +195 -0
  415. bootstack/widgets/card.py +159 -0
  416. bootstack/widgets/carousel.py +241 -0
  417. bootstack/widgets/chart.py +302 -0
  418. bootstack/widgets/codeeditor.py +675 -0
  419. bootstack/widgets/contextmenu.py +371 -0
  420. bootstack/widgets/datatable.py +688 -0
  421. bootstack/widgets/datefield.py +395 -0
  422. bootstack/widgets/divider.py +60 -0
  423. bootstack/widgets/expander.py +579 -0
  424. bootstack/widgets/form.py +200 -0
  425. bootstack/widgets/gallery.py +250 -0
  426. bootstack/widgets/gauge.py +168 -0
  427. bootstack/widgets/grid.py +121 -0
  428. bootstack/widgets/groupbox.py +162 -0
  429. bootstack/widgets/label.py +229 -0
  430. bootstack/widgets/listview.py +354 -0
  431. bootstack/widgets/menubutton.py +419 -0
  432. bootstack/widgets/numberfield.py +431 -0
  433. bootstack/widgets/pagestack.py +345 -0
  434. bootstack/widgets/passwordfield.py +383 -0
  435. bootstack/widgets/pathfield.py +454 -0
  436. bootstack/widgets/picture.py +251 -0
  437. bootstack/widgets/progressbar.py +106 -0
  438. bootstack/widgets/radio_variants.py +271 -0
  439. bootstack/widgets/radiogroup.py +228 -0
  440. bootstack/widgets/scrollbar.py +64 -0
  441. bootstack/widgets/scrollview.py +186 -0
  442. bootstack/widgets/select.py +302 -0
  443. bootstack/widgets/selectbutton.py +182 -0
  444. bootstack/widgets/sidebar_toggle.py +130 -0
  445. bootstack/widgets/sizegrip.py +42 -0
  446. bootstack/widgets/slider.py +408 -0
  447. bootstack/widgets/spinbox.py +147 -0
  448. bootstack/widgets/spinnerfield.py +413 -0
  449. bootstack/widgets/splash.py +367 -0
  450. bootstack/widgets/splitview.py +482 -0
  451. bootstack/widgets/stacks.py +249 -0
  452. bootstack/widgets/statusbar.py +189 -0
  453. bootstack/widgets/tabs.py +425 -0
  454. bootstack/widgets/textarea.py +459 -0
  455. bootstack/widgets/textfield.py +410 -0
  456. bootstack/widgets/theme_toggle.py +92 -0
  457. bootstack/widgets/timefield.py +340 -0
  458. bootstack/widgets/toast.py +314 -0
  459. bootstack/widgets/togglegroup.py +231 -0
  460. bootstack/widgets/toolbar.py +347 -0
  461. bootstack/widgets/tooltip.py +94 -0
  462. bootstack/widgets/tree.py +648 -0
  463. bootstack/widgets/types.py +305 -0
  464. bootstack/widgets/window.py +307 -0
  465. bootstack-0.1.0.dist-info/METADATA +301 -0
  466. bootstack-0.1.0.dist-info/RECORD +471 -0
  467. bootstack-0.1.0.dist-info/WHEEL +5 -0
  468. bootstack-0.1.0.dist-info/entry_points.txt +2 -0
  469. bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  470. bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
  471. bootstack-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,133 @@
1
+ """CLI entry point for bootstack.
2
+
3
+ The bootstack CLI provides commands for:
4
+ - Creating new projects (start)
5
+ - Running applications (run)
6
+ - Building for distribution (build)
7
+ - Adding components (add)
8
+
9
+ Usage:
10
+ bootstack start <appname> Create a new project
11
+ bootstack run [path] Run the application
12
+ bootstack dev [path] Run with hot reload (live edit-and-save)
13
+ bootstack promote --pyinstaller Enable PyInstaller support
14
+ bootstack build Build for distribution
15
+ bootstack add page <ClassName> Add a new page (AppShell)
16
+ bootstack add view <ClassName> Add a new view
17
+ bootstack add dialog <ClassName> Add a new dialog
18
+ bootstack add i18n Add i18n support
19
+ bootstack doctor Diagnose project and environment health
20
+ bootstack gallery Launch the widget gallery
21
+ bootstack icons Browse Bootstrap Icons
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import argparse
27
+ import sys
28
+ from typing import Sequence
29
+
30
+ from bootstack.cli import add, appicon, build, dev, doctor, icons, promote, run, start
31
+ from bootstack.cli.demo import run_demo
32
+
33
+
34
+ def main(argv: Sequence[str] | None = None) -> None:
35
+ """Dispatch CLI commands registered in bootstack."""
36
+ parser = argparse.ArgumentParser(
37
+ prog="bootstack",
38
+ description="bootstack command line interface",
39
+ formatter_class=argparse.RawDescriptionHelpFormatter,
40
+ epilog="""\
41
+ Examples:
42
+ bootstack start MyApp Create a new project
43
+ bootstack start MyApp --template appshell Create an AppShell project
44
+ bootstack start MyApp --theme superhero Use a specific theme
45
+ bootstack run Run the application
46
+ bootstack dev app.py Run with hot reload
47
+ bootstack promote --pyinstaller Enable PyInstaller support
48
+ bootstack build Build for distribution
49
+ bootstack add view SettingsView Add a new view
50
+ bootstack doctor Diagnose project and environment health
51
+ bootstack gallery Launch the widget gallery
52
+ bootstack icons Browse Bootstrap Icons
53
+
54
+ For more information on a command:
55
+ bootstack <command> --help
56
+ """,
57
+ )
58
+
59
+ parser.add_argument(
60
+ "--version",
61
+ action="version",
62
+ version=_get_version(),
63
+ )
64
+ parser.add_argument(
65
+ "-v",
66
+ "--verbose",
67
+ action="store_true",
68
+ help="Print full tracebacks on error",
69
+ )
70
+
71
+ subparsers = parser.add_subparsers(
72
+ dest="command",
73
+ title="commands",
74
+ metavar="<command>",
75
+ )
76
+
77
+ # Register commands
78
+ start.add_parser(subparsers)
79
+ run.add_parser(subparsers)
80
+ dev.add_parser(subparsers)
81
+ promote.add_parser(subparsers)
82
+ build.add_parser(subparsers)
83
+ add.add_parser(subparsers)
84
+ doctor.add_parser(subparsers)
85
+ icons.add_parser(subparsers)
86
+ appicon.add_parser(subparsers)
87
+
88
+ # Gallery command
89
+ gallery_parser = subparsers.add_parser(
90
+ "gallery",
91
+ help="Launch the widget gallery",
92
+ )
93
+ gallery_parser.set_defaults(func=lambda args: run_demo())
94
+
95
+ # demo is kept as a backwards-compatible alias
96
+ demo_parser = subparsers.add_parser(
97
+ "demo",
98
+ help=argparse.SUPPRESS,
99
+ )
100
+ demo_parser.set_defaults(func=lambda args: run_demo())
101
+
102
+ # Parse arguments
103
+ args = parser.parse_args(argv)
104
+ func = getattr(args, "func", None)
105
+
106
+ if func is None:
107
+ parser.print_help()
108
+ sys.exit(0)
109
+
110
+ # Execute command
111
+ try:
112
+ func(args)
113
+ except KeyboardInterrupt:
114
+ print("\nInterrupted.")
115
+ sys.exit(1)
116
+ except Exception as e:
117
+ if getattr(args, "verbose", False):
118
+ import traceback
119
+ traceback.print_exc()
120
+ else:
121
+ print(f"Error: {e}")
122
+ print("(Run with --verbose for the full traceback.)")
123
+ sys.exit(1)
124
+
125
+
126
+ def _get_version() -> str:
127
+ """Get the bootstack version string."""
128
+ try:
129
+ import bootstack
130
+
131
+ return f"bootstack {bootstack.__version__}"
132
+ except Exception:
133
+ return "bootstack (unknown version)"
@@ -0,0 +1,6 @@
1
+ """Allow running bootstack CLI as python -m bootstack.cli."""
2
+
3
+ from bootstack.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
bootstack/cli/add.py ADDED
@@ -0,0 +1,395 @@
1
+ """bootstack add command - Add views, dialogs, and i18n to a project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ from bootstack.cli.config import TtkbConfig, find_config
9
+ from bootstack.cli.templates import (
10
+ _build_func_name,
11
+ create_dialog,
12
+ create_page,
13
+ create_view,
14
+ )
15
+
16
+
17
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
18
+ """Add the 'add' subcommand parser."""
19
+ parser = subparsers.add_parser(
20
+ "add",
21
+ help="Add components to the project",
22
+ description="Add views, pages, dialogs, or i18n support to the project.",
23
+ )
24
+ add_subparsers = parser.add_subparsers(dest="component")
25
+
26
+ # bootstack add page <ClassName>
27
+ page_parser = add_subparsers.add_parser(
28
+ "page",
29
+ help="Add a new page (for AppShell projects)",
30
+ )
31
+ page_parser.add_argument(
32
+ "class_name",
33
+ help="Page class name (CamelCase, e.g., 'DashboardPage')",
34
+ )
35
+ page_parser.add_argument(
36
+ "--dir",
37
+ type=Path,
38
+ default=None,
39
+ help="Target directory (default: src/<app>/pages/)",
40
+ )
41
+ page_parser.add_argument(
42
+ "--scrollable",
43
+ action="store_true",
44
+ help="Make the page scrollable (wraps content in a ScrollView)",
45
+ )
46
+ page_parser.set_defaults(func=run_add_page)
47
+
48
+ # bootstack add view <ClassName>
49
+ view_parser = add_subparsers.add_parser(
50
+ "view",
51
+ help="Add a new view",
52
+ )
53
+ view_parser.add_argument(
54
+ "class_name",
55
+ help="View class name (CamelCase, e.g., 'SettingsView')",
56
+ )
57
+ view_parser.add_argument(
58
+ "--container",
59
+ choices=["grid", "pack"],
60
+ default=None,
61
+ help="Container type (default: from bootstack.toml or 'grid')",
62
+ )
63
+ view_parser.add_argument(
64
+ "--dir",
65
+ type=Path,
66
+ default=None,
67
+ help="Target directory (default: src/<app>/views/)",
68
+ )
69
+ view_parser.set_defaults(func=run_add_view)
70
+
71
+ # bootstack add dialog <ClassName>
72
+ dialog_parser = add_subparsers.add_parser(
73
+ "dialog",
74
+ help="Add a new dialog",
75
+ )
76
+ dialog_parser.add_argument(
77
+ "class_name",
78
+ help="Dialog class name (CamelCase, e.g., 'ConfirmDialog')",
79
+ )
80
+ dialog_parser.add_argument(
81
+ "--dir",
82
+ type=Path,
83
+ default=None,
84
+ help="Target directory (default: src/<app>/dialogs/)",
85
+ )
86
+ dialog_parser.set_defaults(func=run_add_dialog)
87
+
88
+ # bootstack add i18n
89
+ i18n_parser = add_subparsers.add_parser(
90
+ "i18n",
91
+ help="Add internationalization support",
92
+ )
93
+ i18n_parser.add_argument(
94
+ "--languages",
95
+ nargs="+",
96
+ default=["es"],
97
+ help="Locale codes to scaffold (default: es)",
98
+ )
99
+ i18n_parser.add_argument(
100
+ "--po",
101
+ action="store_true",
102
+ help="Scaffold .po catalog files in assets/locales/ instead of a Python "
103
+ "translations module",
104
+ )
105
+ i18n_parser.set_defaults(func=run_add_i18n)
106
+
107
+ parser.set_defaults(func=lambda args: parser.print_help())
108
+
109
+
110
+ def run_add_view(args: argparse.Namespace) -> None:
111
+ """Add a new view to the project."""
112
+ class_name = args.class_name
113
+
114
+ # Validate class name
115
+ if not class_name[0].isupper():
116
+ print("Error: Class name should be CamelCase (e.g., 'SettingsView')")
117
+ return
118
+
119
+ # Find project configuration
120
+ config_path = find_config()
121
+ if config_path is None:
122
+ print("Error: No bootstack.toml found. Are you in a bootstack project?")
123
+ return
124
+
125
+ project_root = config_path.parent
126
+ config = TtkbConfig.load(config_path)
127
+
128
+ # Reject in AppShell projects - views belong to the basic template
129
+ if config.app.template == "appshell":
130
+ print("Error: 'bootstack add view' is for basic-template projects.")
131
+ print("This project uses the 'appshell' template. Use 'bootstack add page' instead.")
132
+ return
133
+
134
+ # Determine container type
135
+ container = args.container
136
+ if container is None:
137
+ container = config.layout.default_container
138
+
139
+ # Determine target directory
140
+ if args.dir:
141
+ target_dir = args.dir
142
+ else:
143
+ # Parse entry point to find source directory
144
+ entry_path = Path(config.app.entry)
145
+ if entry_path.parts[0] == "src" and len(entry_path.parts) >= 2:
146
+ module_name = entry_path.parts[1]
147
+ target_dir = project_root / "src" / module_name / "views"
148
+ else:
149
+ target_dir = project_root / "views"
150
+
151
+ target_dir.mkdir(parents=True, exist_ok=True)
152
+
153
+ # Ensure __init__.py exists
154
+ init_file = target_dir / "__init__.py"
155
+ if not init_file.exists():
156
+ init_file.write_text('"""Views package."""\n', encoding="utf-8")
157
+
158
+ # Create view
159
+ file_path = create_view(class_name, target_dir, container)
160
+
161
+ print(f"Created view: {file_path.relative_to(project_root)}")
162
+
163
+
164
+ def run_add_page(args: argparse.Namespace) -> None:
165
+ """Add a new page to an AppShell project."""
166
+ class_name = args.class_name
167
+
168
+ # Validate class name
169
+ if not class_name[0].isupper():
170
+ print("Error: Class name should be CamelCase (e.g., 'DashboardPage')")
171
+ return
172
+
173
+ # Find project configuration
174
+ config_path = find_config()
175
+ if config_path is None:
176
+ print("Error: No bootstack.toml found. Are you in a bootstack project?")
177
+ return
178
+
179
+ project_root = config_path.parent
180
+ config = TtkbConfig.load(config_path)
181
+
182
+ # Check that this is an AppShell project
183
+ if config.app.template != "appshell":
184
+ print("Error: 'bootstack add page' is for AppShell projects.")
185
+ print("This project uses the 'basic' template. Use 'bootstack add view' instead.")
186
+ return
187
+
188
+ # Determine target directory and module name for the import hint
189
+ entry_path = Path(config.app.entry)
190
+ if entry_path.parts[0] == "src" and len(entry_path.parts) >= 2:
191
+ module_name = entry_path.parts[1]
192
+ default_target = project_root / "src" / module_name / "pages"
193
+ else:
194
+ module_name = None
195
+ default_target = project_root / "pages"
196
+
197
+ target_dir = args.dir if args.dir else default_target
198
+
199
+ target_dir.mkdir(parents=True, exist_ok=True)
200
+
201
+ # Ensure __init__.py exists
202
+ init_file = target_dir / "__init__.py"
203
+ if not init_file.exists():
204
+ init_file.write_text('"""Pages package."""\n', encoding="utf-8")
205
+
206
+ # Create page
207
+ scrollable = args.scrollable
208
+ file_path = create_page(class_name, target_dir, scrollable=scrollable)
209
+
210
+ func_name = _build_func_name(class_name)
211
+ print(f"Created page: {file_path.relative_to(project_root)}")
212
+ print()
213
+ print("This created the file only - it is NOT yet shown in the sidebar.")
214
+ print("To register it with the AppShell, paste these lines into main.py")
215
+ print("inside your shell.page_nav() block:")
216
+ print()
217
+ import_module = f"{module_name}.pages" if module_name else "<module>.pages"
218
+ print(f" from {import_module}.{file_path.stem} import {func_name}")
219
+ scrollable_arg = ", scrollable=True" if scrollable else ""
220
+ print(f' with nav.add_page("<id>", text="<Label>", icon="<icon>",')
221
+ print(f' padding=20, gap=12, horizontal_items="stretch"{scrollable_arg}):')
222
+ print(f" {func_name}()")
223
+
224
+
225
+ def run_add_dialog(args: argparse.Namespace) -> None:
226
+ """Add a new dialog to the project."""
227
+ class_name = args.class_name
228
+
229
+ # Validate class name
230
+ if not class_name[0].isupper():
231
+ print("Error: Class name should be CamelCase (e.g., 'ConfirmDialog')")
232
+ return
233
+
234
+ # Find project configuration
235
+ config_path = find_config()
236
+ if config_path is None:
237
+ print("Error: No bootstack.toml found. Are you in a bootstack project?")
238
+ return
239
+
240
+ project_root = config_path.parent
241
+ config = TtkbConfig.load(config_path)
242
+
243
+ # Determine target directory
244
+ if args.dir:
245
+ target_dir = args.dir
246
+ else:
247
+ # Parse entry point to find source directory
248
+ entry_path = Path(config.app.entry)
249
+ if entry_path.parts[0] == "src" and len(entry_path.parts) >= 2:
250
+ module_name = entry_path.parts[1]
251
+ target_dir = project_root / "src" / module_name / "dialogs"
252
+ else:
253
+ target_dir = project_root / "dialogs"
254
+
255
+ target_dir.mkdir(parents=True, exist_ok=True)
256
+
257
+ # Ensure __init__.py exists
258
+ init_file = target_dir / "__init__.py"
259
+ if not init_file.exists():
260
+ init_file.write_text('"""Dialogs package."""\n', encoding="utf-8")
261
+
262
+ # Create dialog
263
+ file_path = create_dialog(class_name, target_dir)
264
+
265
+ print(f"Created dialog: {file_path.relative_to(project_root)}")
266
+
267
+
268
+ def run_add_i18n(args: argparse.Namespace) -> None:
269
+ """Add internationalization support to the project.
270
+
271
+ By default this scaffolds a Python translations module (the simplest path -
272
+ no tooling, bundled with your code). With `--po` it scaffolds `.po`
273
+ catalog files in `assets/` for a file-based workflow.
274
+ """
275
+ languages = args.languages
276
+
277
+ config_path = find_config()
278
+ if config_path is None:
279
+ print("Error: No bootstack.toml found. Are you in a bootstack project?")
280
+ return
281
+ project_root = config_path.parent
282
+
283
+ if args.po:
284
+ _scaffold_po_catalogs(project_root, languages)
285
+ else:
286
+ config = TtkbConfig.load(config_path)
287
+ entry_path = Path(config.app.entry)
288
+ module_name = entry_path.parts[1] if len(entry_path.parts) > 1 else None
289
+ _scaffold_i18n_module(project_root, module_name, languages)
290
+
291
+
292
+ def _scaffold_i18n_module(project_root, module_name, languages) -> None:
293
+ """Scaffold a Python translations module using add_translations()."""
294
+ if not module_name:
295
+ print("Error: could not determine the project module from bootstack.toml.")
296
+ return
297
+
298
+ target = project_root / "src" / module_name / "i18n.py"
299
+ if target.exists():
300
+ print(f"{target.relative_to(project_root)} already exists - not overwritten.")
301
+ return
302
+
303
+ target.write_text(_get_i18n_module_template(languages), encoding="utf-8")
304
+ print(f"Created: {target.relative_to(project_root)}")
305
+ print()
306
+ print("Wire it into main.py - call it before creating the app:")
307
+ print()
308
+ print(f" from {module_name}.i18n import install_translations")
309
+ print(" install_translations()")
310
+ print(" with bs.AppShell(...): # or bs.App(...)")
311
+ print(" ...")
312
+ print()
313
+ print("Then add your strings to i18n.py. Widget text auto-localizes - once a")
314
+ print('translation is registered, bs.Label("Save") shows it for the active locale.')
315
+
316
+
317
+ def _scaffold_po_catalogs(project_root, languages) -> None:
318
+ """Scaffold .po catalog files in assets/locales/ for a file-based workflow."""
319
+ locales_dir = project_root / "assets" / "locales"
320
+ locales_dir.mkdir(parents=True, exist_ok=True)
321
+
322
+ for lang in languages:
323
+ po_file = locales_dir / f"{lang}.po"
324
+ if not po_file.exists():
325
+ po_file.write_text(_get_po_template(lang), encoding="utf-8")
326
+ print(f"Created: {po_file.relative_to(project_root)}")
327
+
328
+ print()
329
+ print("These .po files live under assets/, so the build bundles them already")
330
+ print("(assets/** is in [build.datas]). Fill in the translations, then load them")
331
+ print("at startup - before the app:")
332
+ print()
333
+ print(" from bootstack.i18n import load_po")
334
+ for lang in languages:
335
+ print(f' load_po("assets/locales/{lang}.po")')
336
+ print()
337
+ print('Widget text then auto-localizes - bs.Label("Save") shows the translation.')
338
+
339
+
340
+ def _get_i18n_module_template(languages) -> str:
341
+ """Python translations-module template using add_translations()."""
342
+ blocks = []
343
+ for lang in languages:
344
+ blocks.append(
345
+ f' add_translations("{lang}", {{\n'
346
+ f' # "Save": "...",\n'
347
+ f' # "Cancel": "...",\n'
348
+ f' }})'
349
+ )
350
+ body = "\n".join(blocks) if blocks else " pass"
351
+ return (
352
+ '"""Application translations.\n'
353
+ "\n"
354
+ "Call install_translations() at startup, before creating the app, so the\n"
355
+ "strings are registered when widgets resolve their text. Widget text\n"
356
+ "auto-localizes: a plain string is translated when a translation exists.\n"
357
+ '"""\n'
358
+ "\n"
359
+ "from bootstack.i18n import add_translations\n"
360
+ "\n"
361
+ "\n"
362
+ "def install_translations() -> None:\n"
363
+ ' """Register the application\'s translations."""\n'
364
+ f"{body}\n"
365
+ )
366
+
367
+
368
+ def _get_po_template(lang: str) -> str:
369
+ """Get a .po file template."""
370
+ from datetime import datetime, timezone
371
+
372
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M%z")
373
+
374
+ return f'''\
375
+ # {lang.upper()} translations for the application.
376
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
377
+ # This file is distributed under the same license as the PACKAGE package.
378
+ #
379
+ msgid ""
380
+ msgstr ""
381
+ "Project-Id-Version: 1.0\\n"
382
+ "Report-Msgid-Bugs-To: \\n"
383
+ "POT-Creation-Date: {now}\\n"
384
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
385
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
386
+ "Language-Team: {lang.upper()} <LL@li.org>\\n"
387
+ "Language: {lang}\\n"
388
+ "MIME-Version: 1.0\\n"
389
+ "Content-Type: text/plain; charset=UTF-8\\n"
390
+ "Content-Transfer-Encoding: 8bit\\n"
391
+
392
+ # Example translation
393
+ # msgid "Hello"
394
+ # msgstr "Hello"
395
+ '''