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
bootstack/cli/dev.py ADDED
@@ -0,0 +1,153 @@
1
+ """bootstack dev command - Run an application with hot reload.
2
+
3
+ PROVISIONAL — the dev workflow ships in 0.1.0 but is carved out of the API
4
+ freeze and may change in a minor release.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ import threading
14
+ from pathlib import Path
15
+
16
+ from bootstack.cli.config import TtkbConfig, find_config
17
+
18
+
19
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
20
+ """Add the 'dev' subcommand parser."""
21
+ parser = subparsers.add_parser(
22
+ "dev",
23
+ help="Run the application with hot reload",
24
+ description=(
25
+ "Run the application with live reload. Saving a source file updates "
26
+ "the running app. A module-level `with bs.App()` reloads in place "
27
+ "(state preserved); anything else falls back to a process restart."
28
+ ),
29
+ )
30
+ parser.add_argument(
31
+ "path",
32
+ nargs="?",
33
+ default=None,
34
+ help="Path to entry point or directory with bootstack.toml (default: current directory)",
35
+ )
36
+ parser.add_argument(
37
+ "--restart",
38
+ action="store_true",
39
+ help="Always reload by restarting the process, even for a module-level app",
40
+ )
41
+ parser.set_defaults(func=run_dev)
42
+
43
+
44
+ def run_dev(args: argparse.Namespace) -> None:
45
+ """Execute the dev command."""
46
+ resolved = _resolve_entry(args.path)
47
+ if resolved is None:
48
+ return
49
+ entry_point, project_root = resolved
50
+
51
+ if not entry_point.exists():
52
+ print(f"Error: Entry point '{entry_point}' does not exist.")
53
+ return
54
+
55
+ try:
56
+ display_path = entry_point.relative_to(project_root)
57
+ except ValueError:
58
+ display_path = entry_point
59
+ print(f"Starting {display_path} with hot reload...")
60
+ print("(Save a file to reload. Press Ctrl+C to stop.)")
61
+ print()
62
+
63
+ env = dict(os.environ)
64
+ env["BOOTSTACK_DEV"] = "1"
65
+ env["BOOTSTACK_DEV_MODE"] = "restart" if args.restart else "inprocess"
66
+
67
+ src_dir = project_root / "src"
68
+ if src_dir.exists():
69
+ python_path = env.get("PYTHONPATH", "")
70
+ env["PYTHONPATH"] = (
71
+ f"{src_dir}{os.pathsep}{python_path}" if python_path else str(src_dir)
72
+ )
73
+
74
+ # Supervise the app here rather than letting it os.execv itself on restart:
75
+ # on Windows execv does not replace the process in place — it spawns a new
76
+ # PID and terminates the caller, which would drop this supervisor after the
77
+ # first reload and orphan the app. The child re-launches by exiting with
78
+ # DEV_RESTART_EXIT_CODE; looping subprocess.run is portable and re-establishes
79
+ # cwd + env on every relaunch.
80
+ from bootstack.dev._env import DEV_RESTART_EXIT_CODE
81
+
82
+ try:
83
+ while True:
84
+ result = subprocess.run(
85
+ [sys.executable, str(entry_point.resolve())],
86
+ cwd=str(project_root),
87
+ env=env,
88
+ )
89
+ if result.returncode == DEV_RESTART_EXIT_CODE:
90
+ continue # a valid edit asked for a clean restart
91
+ if result.returncode == 0:
92
+ sys.exit(0) # the app window was closed normally
93
+ # The app crashed (e.g. an edit raised at import). Don't end the
94
+ # session — stay alive and relaunch when the file is fixed and
95
+ # saved. This is the restart-mode counterpart to the in-process
96
+ # error banner's fix-and-save recovery; the child's own watcher
97
+ # died with it, so the supervisor watches in the meantime.
98
+ print(
99
+ f"\n[bootstack dev] app exited with code {result.returncode} - "
100
+ "waiting for a change to retry (Ctrl+C to stop)...",
101
+ flush=True,
102
+ )
103
+ _wait_for_change(str(project_root))
104
+ except KeyboardInterrupt:
105
+ print("\nStopped.")
106
+ sys.exit(0)
107
+
108
+
109
+ def _wait_for_change(root: str) -> None:
110
+ """Block until a watched source file under `root` changes (or Ctrl+C).
111
+
112
+ Used by the restart supervisor to stay alive after the app crashes: rather
113
+ than ending the dev session, it waits for the next save and lets the loop
114
+ relaunch.
115
+ """
116
+ from bootstack.dev._watcher import PollWatcher
117
+
118
+ changed = threading.Event()
119
+ watcher = PollWatcher(root, lambda _paths: changed.set())
120
+ watcher.start()
121
+ try:
122
+ # Short timed waits keep Ctrl+C responsive on Windows, where a bare
123
+ # Event.wait() with no timeout can swallow the interrupt.
124
+ while not changed.wait(0.3):
125
+ pass
126
+ finally:
127
+ watcher.stop()
128
+
129
+
130
+ def _resolve_entry(path: str | None) -> tuple[Path, Path] | None:
131
+ """Resolve (entry_point, project_root) from a path or bootstack.toml."""
132
+ if path:
133
+ p = Path(path)
134
+ if p.is_file() and p.suffix == ".py":
135
+ return p, p.parent
136
+ if p.is_dir():
137
+ config_path = p / "bootstack.toml"
138
+ if not config_path.exists():
139
+ print(f"Error: No bootstack.toml found in '{p}'")
140
+ return None
141
+ config = TtkbConfig.load(config_path)
142
+ return p / config.app.entry, p
143
+ print(f"Error: '{path}' is not a valid file or directory")
144
+ return None
145
+
146
+ config_path = find_config()
147
+ if config_path is None:
148
+ print("Error: No bootstack.toml found in current directory or parents.")
149
+ print("Pass a path: bootstack dev app.py")
150
+ return None
151
+ config = TtkbConfig.load(config_path)
152
+ project_root = config_path.parent
153
+ return project_root / config.app.entry, project_root
@@ -0,0 +1,195 @@
1
+ """bootstack doctor command - Validate project structure and environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import platform
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from bootstack.cli.config import TtkbConfig, find_config
11
+
12
+
13
+ _OK = " [OK] "
14
+ _WARN = " [WARN] "
15
+ _FAIL = " [FAIL] "
16
+
17
+
18
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
19
+ """Add the 'doctor' subcommand parser."""
20
+ parser = subparsers.add_parser(
21
+ "doctor",
22
+ help="Diagnose project and environment health",
23
+ description=(
24
+ "Validate bootstack.toml, check the project layout, and report "
25
+ "environment versions relevant to building and running."
26
+ ),
27
+ )
28
+ parser.set_defaults(func=run_doctor)
29
+
30
+
31
+ def run_doctor(args: argparse.Namespace) -> None:
32
+ """Run the diagnostic checks."""
33
+ failures = 0
34
+ warnings = 0
35
+
36
+ # ---- Environment -------------------------------------------------------
37
+ print("Environment:")
38
+ print(f"{_OK}Python {platform.python_version()} ({sys.executable})")
39
+
40
+ tk_version, tk_error = _probe_tk()
41
+ if tk_version:
42
+ print(f"{_OK}Tcl/Tk {tk_version}")
43
+ else:
44
+ print(f"{_FAIL}Tcl/Tk: {tk_error}")
45
+ failures += 1
46
+
47
+ ttkb_version = _probe_bootstack()
48
+ print(f"{_OK}bootstack {ttkb_version}")
49
+
50
+ print()
51
+
52
+ # ---- Project -----------------------------------------------------------
53
+ config_path = find_config()
54
+ if config_path is None:
55
+ print("Project:")
56
+ print(f"{_WARN}No bootstack.toml found in current directory or parents.")
57
+ print(" (Run 'bootstack start <name>' to create a project, or 'cd' into one.)")
58
+ return
59
+
60
+ project_root = config_path.parent
61
+ print(f"Project: {project_root}")
62
+
63
+ try:
64
+ config = TtkbConfig.load(config_path)
65
+ except Exception as e:
66
+ print(f"{_FAIL}bootstack.toml failed to parse: {e}")
67
+ sys.exit(1)
68
+ print(f"{_OK}bootstack.toml parses ([app] name={config.app.name!r}, template={config.app.template!r})")
69
+
70
+ # Entry point exists
71
+ entry_path = project_root / config.app.entry
72
+ if entry_path.exists():
73
+ print(f"{_OK}entry point: {config.app.entry}")
74
+ else:
75
+ print(f"{_FAIL}entry point missing: {config.app.entry}")
76
+ failures += 1
77
+
78
+ # Layout matches template
79
+ if entry_path.parts and "src" in entry_path.parts:
80
+ idx = list(entry_path.parts).index("src")
81
+ if idx + 1 < len(entry_path.parts):
82
+ module_dir = project_root.joinpath(*entry_path.parts[: idx + 2])
83
+ failures += _check_template_layout(config.app.template, module_dir)
84
+ else:
85
+ # Non-src layout: skip the views/pages check
86
+ pass
87
+
88
+ # Packaging — always reported. If [build] exists we run the full check;
89
+ # otherwise we still flag a missing PyInstaller as a heads-up so users
90
+ # discover it before they reach 'bootstack build'.
91
+ if config.build is not None:
92
+ warnings += _check_build(config, project_root)
93
+ else:
94
+ warnings += _check_packaging_readiness()
95
+
96
+ # ---- Summary -----------------------------------------------------------
97
+ print()
98
+ if failures:
99
+ print(f"{failures} failure(s), {warnings} warning(s).")
100
+ sys.exit(1)
101
+ if warnings:
102
+ print(f"All checks passed with {warnings} warning(s).")
103
+ else:
104
+ print("All checks passed.")
105
+
106
+
107
+ def _check_template_layout(template: str, module_dir: Path) -> int:
108
+ """Verify the project's component directory matches its template type.
109
+
110
+ Returns the number of failures recorded.
111
+ """
112
+ expected = "pages" if template == "appshell" else "views"
113
+ other = "views" if template == "appshell" else "pages"
114
+
115
+ expected_dir = module_dir / expected
116
+ other_dir = module_dir / other
117
+
118
+ failures = 0
119
+ if expected_dir.exists():
120
+ print(f"{_OK}layout: found {expected}/ (matches template={template!r})")
121
+ else:
122
+ print(f"{_FAIL}layout: expected {expected_dir.relative_to(module_dir.parent.parent)}/ for template={template!r}")
123
+ failures += 1
124
+
125
+ if other_dir.exists():
126
+ print(f"{_WARN}layout: unexpected {other}/ directory for template={template!r}")
127
+ return failures
128
+
129
+
130
+ def _check_packaging_readiness() -> int:
131
+ """Report packaging-tool availability for un-promoted projects.
132
+
133
+ Never fails — just warns if PyInstaller is missing so users know
134
+ they have an install ahead of them before 'bootstack build' will work.
135
+ Returns the number of warnings.
136
+ """
137
+ print()
138
+ print("Packaging (project not yet promoted):")
139
+ try:
140
+ import PyInstaller # noqa: F401
141
+ print(f"{_OK}PyInstaller is available (run 'bootstack promote --pyinstaller' to enable builds)")
142
+ return 0
143
+ except ImportError:
144
+ print(f"{_WARN}PyInstaller is not installed")
145
+ print(" Install it before 'bootstack build': pip install pyinstaller")
146
+ return 1
147
+
148
+
149
+ def _check_build(config: TtkbConfig, project_root: Path) -> int:
150
+ """Verify build-time prerequisites. Returns the number of warnings."""
151
+ warnings = 0
152
+ backend = config.build.backend if config.build else None
153
+ print()
154
+ print(f"Build (backend={backend!r}):")
155
+
156
+ if backend == "pyinstaller":
157
+ spec = project_root / "build" / "pyinstaller" / "app.spec"
158
+ if spec.exists():
159
+ print(f"{_OK}spec file: {spec.relative_to(project_root)}")
160
+ else:
161
+ print(f"{_WARN}spec file missing — run 'bootstack promote --pyinstaller --force'")
162
+ warnings += 1
163
+ try:
164
+ import PyInstaller # noqa: F401
165
+ print(f"{_OK}PyInstaller is importable")
166
+ except ImportError:
167
+ print(f"{_WARN}PyInstaller is not installed (pip install pyinstaller)")
168
+ warnings += 1
169
+ else:
170
+ print(f"{_WARN}unknown build backend: {backend!r}")
171
+ warnings += 1
172
+
173
+ return warnings
174
+
175
+
176
+ def _probe_tk() -> tuple[str | None, str | None]:
177
+ """Return (version, None) on success, (None, error_message) on failure.
178
+
179
+ Uses `_tkinter` module constants directly so the probe does not have
180
+ to instantiate a Tk interpreter (bootstack monkey-patches
181
+ `Tk.__init__` to require an App, which would trip the probe).
182
+ """
183
+ try:
184
+ import _tkinter
185
+ return f"{_tkinter.TCL_VERSION} / Tk {_tkinter.TK_VERSION}", None
186
+ except Exception as e:
187
+ return None, str(e)
188
+
189
+
190
+ def _probe_bootstack() -> str:
191
+ try:
192
+ import bootstack
193
+ return getattr(bootstack, "__version__", "(unknown)")
194
+ except Exception:
195
+ return "(import failed)"
bootstack/cli/icons.py ADDED
@@ -0,0 +1,98 @@
1
+ """Icons command — launches the Bootstrap Icons browser."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+
6
+
7
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
8
+ parser = subparsers.add_parser(
9
+ "icons",
10
+ help="Browse Bootstrap Icons available in bootstack",
11
+ description=(
12
+ "Launch the Bootstrap Icons browser.\n\n"
13
+ "Displays all Bootstrap Icons bundled with bootstack. "
14
+ "Click any icon to copy its name to the clipboard — the "
15
+ "copied name works directly as icon= on any bootstack widget."
16
+ ),
17
+ )
18
+ parser.set_defaults(func=lambda args: run_icons())
19
+
20
+
21
+ _DEFAULT_STATUS = (
22
+ 'Click an icon to copy its name · use as icon="name" on any widget'
23
+ )
24
+
25
+
26
+ def run_icons() -> None:
27
+ import bootstack as bs
28
+ from bootstack.clipboard import set_clipboard
29
+ from bootstack.data import MemoryDataSource, col
30
+ from bootstack.images import get_icon, list_icons
31
+
32
+ names = list_icons()
33
+
34
+ with bs.App(title="Bootstrap Icons", size=(990, 720), gap=0) as app:
35
+ # Each record carries a theme-following Image. Gallery recycles its tiles,
36
+ # so the thumbnails render lazily as they scroll into view and recolor
37
+ # with the theme on their own — no manual batching or repaint needed.
38
+ records = [
39
+ {"id": i, "name": name, "icon": get_icon(name, size=32)}
40
+ for i, name in enumerate(names)
41
+ ]
42
+ source = MemoryDataSource()
43
+ source.load(records)
44
+
45
+ count = bs.Signal("")
46
+ status = bs.Signal(_DEFAULT_STATUS)
47
+
48
+ def update_count() -> None:
49
+ count.set(f"{source.count:,} of {len(names):,} icons")
50
+
51
+ with bs.Row(padding=(12, 8), gap=10, horizontal="stretch", vertical_items="center"):
52
+ search = bs.TextField(placeholder="Search icons", grow=True)
53
+ search.insert_addon("label", "before", icon="search")
54
+ bs.Label(textsignal=count, font="caption", accent="muted")
55
+
56
+ bs.Divider()
57
+
58
+ gallery = bs.Gallery(
59
+ data_source=source,
60
+ image_field="icon",
61
+ caption_field="name",
62
+ columns="auto",
63
+ tile_size=(72, 40),
64
+ fit="none", # show the 32px glyph at native size — crisp, no upscale
65
+ gap=6,
66
+ selection_mode="single",
67
+ grow=True,
68
+ horizontal="stretch",
69
+ )
70
+
71
+ bs.Divider()
72
+
73
+ with bs.Row(padding=(12, 4), horizontal="stretch", surface="chrome"):
74
+ bs.Label(textsignal=status, font="caption", accent="muted")
75
+
76
+ def on_search(event) -> None:
77
+ query = event.text.strip().lower()
78
+ source.where(col("name").contains(query) if query else None)
79
+ gallery.reload()
80
+ update_count()
81
+
82
+ # Live, as-you-type filtering — debounced so a fast typist isn't reloaded
83
+ # on every keystroke.
84
+ search.on_input().debounce(120).listen(on_search)
85
+
86
+ def on_pick(record: dict) -> None:
87
+ name = record["name"]
88
+ set_clipboard(name)
89
+ status.set(f'Copied "{name}" — ready to paste as icon="{name}"')
90
+
91
+ gallery.on_item_click(on_pick)
92
+
93
+ update_count()
94
+
95
+ app.run()
96
+
97
+ if __name__ == '__main__':
98
+ run_icons()
@@ -0,0 +1,120 @@
1
+ """bootstack promote command - Upgrade project to packaging-ready."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ from bootstack.cli.config import (
9
+ BUILD_CONFIG_TEMPLATE,
10
+ TtkbConfig,
11
+ find_config,
12
+ )
13
+ from bootstack.cli.pyinstaller import generate_spec
14
+
15
+
16
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
17
+ """Add the 'promote' subcommand parser."""
18
+ parser = subparsers.add_parser(
19
+ "promote",
20
+ help="Upgrade project to packaging-ready",
21
+ description="Add build configuration and generate build files for distribution.",
22
+ )
23
+ parser.add_argument(
24
+ "--pyinstaller",
25
+ action="store_true",
26
+ help="Enable PyInstaller build support",
27
+ )
28
+ parser.add_argument(
29
+ "--force",
30
+ action="store_true",
31
+ help="Overwrite existing build configuration",
32
+ )
33
+ parser.set_defaults(func=run_promote)
34
+
35
+
36
+ def run_promote(args: argparse.Namespace) -> None:
37
+ """Execute the promote command."""
38
+ if not args.pyinstaller:
39
+ print("Error: Please specify a build backend.")
40
+ print(" bootstack promote --pyinstaller")
41
+ return
42
+
43
+ # Find project root
44
+ config_path = find_config()
45
+ if config_path is None:
46
+ print("Error: No bootstack.toml found in current directory or parents.")
47
+ print("Run 'bootstack start <appname>' to create a new project first.")
48
+ return
49
+
50
+ project_root = config_path.parent
51
+ config = TtkbConfig.load(config_path)
52
+
53
+ # Check if already promoted
54
+ if config.build is not None and not args.force:
55
+ print("Project already has build configuration.")
56
+ print("Use --force to overwrite.")
57
+ return
58
+
59
+ print(f"Promoting project '{config.app.name}' for PyInstaller...")
60
+
61
+ # Update bootstack.toml with build section
62
+ _add_build_section(config_path)
63
+
64
+ # Create build directory
65
+ build_dir = project_root / "build" / "pyinstaller"
66
+ build_dir.mkdir(parents=True, exist_ok=True)
67
+
68
+ # Reload config to get build settings
69
+ config = TtkbConfig.load(config_path)
70
+
71
+ # Generate .spec file
72
+ spec_path = build_dir / "app.spec"
73
+ generate_spec(config, project_root, spec_path)
74
+
75
+ print()
76
+ print("Project promoted successfully!")
77
+ print()
78
+ print("Generated files:")
79
+ print(f" - {spec_path.relative_to(project_root)}")
80
+ print()
81
+ print("Updated:")
82
+ print(f" - bootstack.toml (added [build] section)")
83
+ print()
84
+
85
+ # Warn early if PyInstaller isn't on this interpreter — `bootstack build`
86
+ # needs it but the promote step itself only writes config.
87
+ try:
88
+ import PyInstaller # noqa: F401
89
+ except ImportError:
90
+ print("Note: PyInstaller is not installed in this environment.")
91
+ print(" Install it before running 'bootstack build':")
92
+ print(" pip install pyinstaller")
93
+ print()
94
+
95
+ print("Next steps:")
96
+ print(" 1. (Optional) Edit bootstack.toml [build] section")
97
+ print(" 2. Run 'bootstack build' to create executable")
98
+
99
+
100
+ def _add_build_section(config_path: Path) -> None:
101
+ """Add [build] section to existing bootstack.toml."""
102
+ content = config_path.read_text(encoding="utf-8")
103
+
104
+ # Check if [build] section already exists
105
+ if "[build]" in content:
106
+ # Remove existing build section and everything after
107
+ lines = content.split("\n")
108
+ new_lines = []
109
+ skip = False
110
+ for line in lines:
111
+ if line.strip().startswith("[build"):
112
+ skip = True
113
+ if not skip:
114
+ new_lines.append(line)
115
+ content = "\n".join(new_lines).rstrip() + "\n"
116
+
117
+ # Append build section
118
+ content += BUILD_CONFIG_TEMPLATE
119
+
120
+ config_path.write_text(content, encoding="utf-8")