bootstack 0.1.0a1__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.
- bootstack/__init__.py +249 -0
- bootstack/__main__.py +5 -0
- bootstack/api/__init__.py +127 -0
- bootstack/api/app.py +30 -0
- bootstack/api/constants.py +3 -0
- bootstack/api/data.py +23 -0
- bootstack/api/dialogs.py +44 -0
- bootstack/api/i18n.py +17 -0
- bootstack/api/localization.py +16 -0
- bootstack/api/menu.py +7 -0
- bootstack/api/style.py +23 -0
- bootstack/api/utils.py +24 -0
- bootstack/api/widgets.py +137 -0
- bootstack/assets/__init__.py +24 -0
- bootstack/assets/bootstack-transparent.png +0 -0
- bootstack/assets/bootstack.ico +0 -0
- bootstack/assets/bootstack.png +0 -0
- bootstack/assets/elements/__init__.py +0 -0
- bootstack/assets/elements/badge-pill.png +0 -0
- bootstack/assets/elements/badge-square.png +0 -0
- bootstack/assets/elements/border.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
- bootstack/assets/elements/checkbox-checked.png +0 -0
- bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- bootstack/assets/elements/checkbox-unchecked.png +0 -0
- bootstack/assets/elements/field.png +0 -0
- bootstack/assets/elements/input-after-compact.png +0 -0
- bootstack/assets/elements/input-after-default.png +0 -0
- bootstack/assets/elements/input-before-compact.png +0 -0
- bootstack/assets/elements/input-before-default.png +0 -0
- bootstack/assets/elements/input-compact.png +0 -0
- bootstack/assets/elements/input-default.png +0 -0
- bootstack/assets/elements/list-item-separated.png +0 -0
- bootstack/assets/elements/list-item.png +0 -0
- bootstack/assets/elements/manifest.toml +480 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/nav-button-compact.png +0 -0
- bootstack/assets/elements/nav-button-default.png +0 -0
- bootstack/assets/elements/nav-icon-button-compact.png +0 -0
- bootstack/assets/elements/nav-icon-button-default.png +0 -0
- bootstack/assets/elements/notebook-client-border.png +0 -0
- bootstack/assets/elements/notebook-tab-active.png +0 -0
- bootstack/assets/elements/notebook-tab-bar.png +0 -0
- bootstack/assets/elements/notebook-tab-normal.png +0 -0
- bootstack/assets/elements/notebook-tab-pill.png +0 -0
- bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/elements/progress-bar-solid.png +0 -0
- bootstack/assets/elements/progress-bar-thin.png +0 -0
- bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/elements/radio-selected.png +0 -0
- bootstack/assets/elements/radio-unselected.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle-focus.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-horizontal.png +0 -0
- bootstack/assets/elements/slider-track-vertical.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
- bootstack/assets/elements/tabs-bar-vertical.png +0 -0
- bootstack/assets/elements/tabs-pill.png +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/themes/__init__.py +0 -0
- bootstack/assets/themes/amber-dark.json +32 -0
- bootstack/assets/themes/amber-light.json +32 -0
- bootstack/assets/themes/aurora-dark.json +32 -0
- bootstack/assets/themes/aurora-light.json +32 -0
- bootstack/assets/themes/bootstrap-dark.json +32 -0
- bootstack/assets/themes/bootstrap-light.json +32 -0
- bootstack/assets/themes/classic-dark.json +32 -0
- bootstack/assets/themes/classic-light.json +32 -0
- bootstack/assets/themes/docs-dark.json +32 -0
- bootstack/assets/themes/docs-light.json +32 -0
- bootstack/assets/themes/forest-dark.json +32 -0
- bootstack/assets/themes/forest-light.json +32 -0
- bootstack/assets/themes/ocean-dark.json +32 -0
- bootstack/assets/themes/ocean-light.json +32 -0
- bootstack/assets/themes/rose-dark.json +32 -0
- bootstack/assets/themes/rose-light.json +32 -0
- bootstack/assets/widgets/__init__.py +0 -0
- bootstack/assets/widgets/badge-default.png +0 -0
- bootstack/assets/widgets/badge-pill.png +0 -0
- bootstack/assets/widgets/border.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
- bootstack/assets/widgets/button-group-vertical-after.png +0 -0
- bootstack/assets/widgets/button-group-vertical-before.png +0 -0
- bootstack/assets/widgets/button-group-vertical-center.png +0 -0
- bootstack/assets/widgets/button.png +0 -0
- bootstack/assets/widgets/checkbox-checked.png +0 -0
- bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
- bootstack/assets/widgets/checkbox-unchecked.png +0 -0
- bootstack/assets/widgets/field.png +0 -0
- bootstack/assets/widgets/icon-button.png +0 -0
- bootstack/assets/widgets/input-inner.png +0 -0
- bootstack/assets/widgets/input-prefix.png +0 -0
- bootstack/assets/widgets/input-suffix.png +0 -0
- bootstack/assets/widgets/input.png +0 -0
- bootstack/assets/widgets/list-item-focus.png +0 -0
- bootstack/assets/widgets/list-item-separated.png +0 -0
- bootstack/assets/widgets/menu-item-separated.png +0 -0
- bootstack/assets/widgets/notebook-client-border.png +0 -0
- bootstack/assets/widgets/notebook-pill-active.png +0 -0
- bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
- bootstack/assets/widgets/notebook-tab-active.png +0 -0
- bootstack/assets/widgets/notebook-tab-border.png +0 -0
- bootstack/assets/widgets/notebook-tab-normal.png +0 -0
- bootstack/assets/widgets/notebook-underline.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
- bootstack/assets/widgets/progress-trough-vertical.png +0 -0
- bootstack/assets/widgets/radio-selected.png +0 -0
- bootstack/assets/widgets/radio-unselected.png +0 -0
- bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
- bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
- bootstack/assets/widgets/separator-horizontal.png +0 -0
- bootstack/assets/widgets/separator-vertical.png +0 -0
- bootstack/assets/widgets/slider-handle-focus.png +0 -0
- bootstack/assets/widgets/slider-handle.png +0 -0
- bootstack/assets/widgets/slider-track-horizontal.png +0 -0
- bootstack/assets/widgets/slider-track-vertical.png +0 -0
- bootstack/assets/widgets/switch-off.png +0 -0
- bootstack/assets/widgets/switch-on.png +0 -0
- bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
- bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
- bootstack/assets/widgets/tabs-pill.png +0 -0
- bootstack/cli/__init__.py +124 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +439 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +287 -0
- bootstack/cli/demo.py +1267 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/list_cmd.py +71 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +246 -0
- bootstack/cli/run.py +99 -0
- bootstack/cli/start.py +105 -0
- bootstack/cli/templates/__init__.py +861 -0
- bootstack/constants.py +325 -0
- bootstack/core/__init__.py +34 -0
- bootstack/core/capabilities/__init__.py +45 -0
- bootstack/core/capabilities/after.py +103 -0
- bootstack/core/capabilities/bind.py +154 -0
- bootstack/core/capabilities/bindtags.py +112 -0
- bootstack/core/capabilities/busy.py +61 -0
- bootstack/core/capabilities/clipboard.py +88 -0
- bootstack/core/capabilities/focus.py +118 -0
- bootstack/core/capabilities/grab.py +65 -0
- bootstack/core/capabilities/grid.py +188 -0
- bootstack/core/capabilities/localization.py +231 -0
- bootstack/core/capabilities/pack.py +119 -0
- bootstack/core/capabilities/place.py +92 -0
- bootstack/core/capabilities/selection.py +136 -0
- bootstack/core/capabilities/signals.py +242 -0
- bootstack/core/capabilities/winfo.py +315 -0
- bootstack/core/colorutils.py +234 -0
- bootstack/core/exceptions.py +95 -0
- bootstack/core/images.py +283 -0
- bootstack/core/localization/README.md +90 -0
- bootstack/core/localization/__init__.py +13 -0
- bootstack/core/localization/intl_format.py +580 -0
- bootstack/core/localization/msgcat.py +425 -0
- bootstack/core/localization/specs.py +143 -0
- bootstack/core/mixins/__init__.py +1 -0
- bootstack/core/mixins/ttk_state.py +35 -0
- bootstack/core/mixins/widget.py +132 -0
- bootstack/core/publisher.py +147 -0
- bootstack/core/signals/README.md +112 -0
- bootstack/core/signals/__init__.py +8 -0
- bootstack/core/signals/integration.py +100 -0
- bootstack/core/signals/signal.py +317 -0
- bootstack/core/signals/types.py +4 -0
- bootstack/core/validation/__init__.py +5 -0
- bootstack/core/validation/types.py +13 -0
- bootstack/core/validation/validation_result.py +17 -0
- bootstack/core/validation/validation_rules.py +112 -0
- bootstack/core/variables.py +62 -0
- bootstack/datasource/README.md +607 -0
- bootstack/datasource/__init__.py +51 -0
- bootstack/datasource/base.py +474 -0
- bootstack/datasource/file_source.py +541 -0
- bootstack/datasource/memory_source.py +482 -0
- bootstack/datasource/sqlite_source.py +453 -0
- bootstack/datasource/types.py +259 -0
- bootstack/dialogs/__init__.py +56 -0
- bootstack/dialogs/colorchooser.py +674 -0
- bootstack/dialogs/colordropper.py +257 -0
- bootstack/dialogs/datedialog.py +404 -0
- bootstack/dialogs/dialog.py +514 -0
- bootstack/dialogs/filterdialog.py +358 -0
- bootstack/dialogs/fontdialog.py +339 -0
- bootstack/dialogs/formdialog.py +541 -0
- bootstack/dialogs/message.py +489 -0
- bootstack/dialogs/query.py +561 -0
- bootstack/py.typed +1 -0
- bootstack/runtime/__init__.py +3 -0
- bootstack/runtime/app.py +879 -0
- bootstack/runtime/base_window.py +786 -0
- bootstack/runtime/events.py +399 -0
- bootstack/runtime/menu.py +510 -0
- bootstack/runtime/shortcuts.py +423 -0
- bootstack/runtime/tk_patch.py +31 -0
- bootstack/runtime/toplevel.py +131 -0
- bootstack/runtime/utility.py +371 -0
- bootstack/runtime/visual_focus.py +228 -0
- bootstack/runtime/window_utilities.py +1043 -0
- bootstack/style/__init__.py +5498 -0
- bootstack/style/bootstyle.py +507 -0
- bootstack/style/bootstyle_builder_base.py +752 -0
- bootstack/style/bootstyle_builder_mixed.py +93 -0
- bootstack/style/bootstyle_builder_tk.py +109 -0
- bootstack/style/bootstyle_builder_ttk.py +354 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +44 -0
- bootstack/style/builders/button.py +453 -0
- bootstack/style/builders/buttongroup.py +344 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +95 -0
- bootstack/style/builders/combobox.py +112 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +83 -0
- bootstack/style/builders/expander.py +171 -0
- bootstack/style/builders/field.py +312 -0
- bootstack/style/builders/frame.py +27 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +41 -0
- bootstack/style/builders/listview.py +267 -0
- bootstack/style/builders/menubar.py +74 -0
- bootstack/style/builders/menubutton.py +408 -0
- bootstack/style/builders/notebook.py +316 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +71 -0
- bootstack/style/builders/radiobutton.py +68 -0
- bootstack/style/builders/scale.py +66 -0
- bootstack/style/builders/scrollbar.py +360 -0
- bootstack/style/builders/separator.py +45 -0
- bootstack/style/builders/sidenav.py +313 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +67 -0
- bootstack/style/builders/tabitem.py +205 -0
- bootstack/style/builders/toolbutton.py +260 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +269 -0
- bootstack/style/builders/utils.py +404 -0
- bootstack/style/builders_tk/__init__.py +16 -0
- bootstack/style/builders_tk/defaults.py +229 -0
- bootstack/style/element.py +173 -0
- bootstack/style/style.py +499 -0
- bootstack/style/theme_provider.py +449 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +42 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +527 -0
- bootstack/style/utility.py +696 -0
- bootstack/themes/__init__.py +12 -0
- bootstack/themes/standard.py +415 -0
- bootstack/themes/user.py +45 -0
- bootstack/widgets/__init__.py +53 -0
- bootstack/widgets/composites/__init__.py +38 -0
- bootstack/widgets/composites/accordion.py +385 -0
- bootstack/widgets/composites/appshell.py +445 -0
- bootstack/widgets/composites/buttongroup.py +391 -0
- bootstack/widgets/composites/calendar.py +914 -0
- bootstack/widgets/composites/compositeframe.py +282 -0
- bootstack/widgets/composites/contextmenu.py +1754 -0
- bootstack/widgets/composites/dateentry.py +261 -0
- bootstack/widgets/composites/dropdownbutton.py +190 -0
- bootstack/widgets/composites/expander.py +508 -0
- bootstack/widgets/composites/field.py +448 -0
- bootstack/widgets/composites/floodgauge.py +434 -0
- bootstack/widgets/composites/form.py +983 -0
- bootstack/widgets/composites/labeledscale.py +209 -0
- bootstack/widgets/composites/list/__init__.py +15 -0
- bootstack/widgets/composites/list/listitem.py +733 -0
- bootstack/widgets/composites/list/listview.py +1507 -0
- bootstack/widgets/composites/menubar.py +303 -0
- bootstack/widgets/composites/meter.py +882 -0
- bootstack/widgets/composites/numericentry.py +183 -0
- bootstack/widgets/composites/pagestack.py +330 -0
- bootstack/widgets/composites/passwordentry.py +149 -0
- bootstack/widgets/composites/pathentry.py +223 -0
- bootstack/widgets/composites/radiogroup.py +466 -0
- bootstack/widgets/composites/scrolledtext.py +388 -0
- bootstack/widgets/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/composites/scrollview.py +675 -0
- bootstack/widgets/composites/selectbox.py +544 -0
- bootstack/widgets/composites/sidenav/__init__.py +24 -0
- bootstack/widgets/composites/sidenav/group.py +485 -0
- bootstack/widgets/composites/sidenav/header.py +83 -0
- bootstack/widgets/composites/sidenav/item.py +413 -0
- bootstack/widgets/composites/sidenav/separator.py +51 -0
- bootstack/widgets/composites/sidenav/view.py +919 -0
- bootstack/widgets/composites/spinnerentry.py +232 -0
- bootstack/widgets/composites/tableview/__init__.py +5 -0
- bootstack/widgets/composites/tableview/tableview.py +2254 -0
- bootstack/widgets/composites/tableview/types.py +169 -0
- bootstack/widgets/composites/tabs/__init__.py +6 -0
- bootstack/widgets/composites/tabs/tabitem.py +372 -0
- bootstack/widgets/composites/tabs/tabs.py +478 -0
- bootstack/widgets/composites/tabs/tabview.py +352 -0
- bootstack/widgets/composites/textentry.py +90 -0
- bootstack/widgets/composites/timeentry.py +189 -0
- bootstack/widgets/composites/toast.py +364 -0
- bootstack/widgets/composites/togglegroup.py +382 -0
- bootstack/widgets/composites/toolbar.py +393 -0
- bootstack/widgets/composites/tooltip.py +404 -0
- bootstack/widgets/internal/__init__.py +0 -0
- bootstack/widgets/internal/wrapper_base.py +304 -0
- bootstack/widgets/mixins/__init__.py +25 -0
- bootstack/widgets/mixins/configure_mixin.py +186 -0
- bootstack/widgets/mixins/entry_mixin.py +70 -0
- bootstack/widgets/mixins/font_mixin.py +346 -0
- bootstack/widgets/mixins/icon_mixin.py +38 -0
- bootstack/widgets/mixins/localization_mixin.py +255 -0
- bootstack/widgets/mixins/signal_mixin.py +272 -0
- bootstack/widgets/mixins/validation_mixin.py +204 -0
- bootstack/widgets/parts/__init__.py +11 -0
- bootstack/widgets/parts/numberentry_part.py +345 -0
- bootstack/widgets/parts/spinnerentry_part.py +394 -0
- bootstack/widgets/parts/textentry_part.py +344 -0
- bootstack/widgets/primitives/__init__.py +55 -0
- bootstack/widgets/primitives/badge.py +44 -0
- bootstack/widgets/primitives/button.py +89 -0
- bootstack/widgets/primitives/card.py +66 -0
- bootstack/widgets/primitives/checkbutton.py +124 -0
- bootstack/widgets/primitives/checktoggle.py +53 -0
- bootstack/widgets/primitives/combobox.py +165 -0
- bootstack/widgets/primitives/entry.py +98 -0
- bootstack/widgets/primitives/frame.py +206 -0
- bootstack/widgets/primitives/gridframe.py +479 -0
- bootstack/widgets/primitives/label.py +95 -0
- bootstack/widgets/primitives/labelframe.py +63 -0
- bootstack/widgets/primitives/menubutton.py +118 -0
- bootstack/widgets/primitives/notebook.py +551 -0
- bootstack/widgets/primitives/optionmenu.py +248 -0
- bootstack/widgets/primitives/packframe.py +228 -0
- bootstack/widgets/primitives/panedwindow.py +58 -0
- bootstack/widgets/primitives/progressbar.py +95 -0
- bootstack/widgets/primitives/radiobutton.py +115 -0
- bootstack/widgets/primitives/radiotoggle.py +50 -0
- bootstack/widgets/primitives/scale.py +85 -0
- bootstack/widgets/primitives/scrollbar.py +56 -0
- bootstack/widgets/primitives/separator.py +56 -0
- bootstack/widgets/primitives/sizegrip.py +47 -0
- bootstack/widgets/primitives/spinbox.py +91 -0
- bootstack/widgets/primitives/switch.py +41 -0
- bootstack/widgets/primitives/treeview.py +77 -0
- bootstack/widgets/types.py +20 -0
- bootstack-0.1.0a1.dist-info/METADATA +196 -0
- bootstack-0.1.0a1.dist-info/RECORD +419 -0
- bootstack-0.1.0a1.dist-info/WHEEL +5 -0
- bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
- bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
bootstack/cli/doctor.py
ADDED
|
@@ -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)"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""bootstack list command - List available themes and other resources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
10
|
+
"""Add the 'list' subcommand parser."""
|
|
11
|
+
parser = subparsers.add_parser(
|
|
12
|
+
"list",
|
|
13
|
+
help="List available resources",
|
|
14
|
+
description="List themes and other available resources.",
|
|
15
|
+
)
|
|
16
|
+
list_subparsers = parser.add_subparsers(dest="resource")
|
|
17
|
+
|
|
18
|
+
# bootstack list themes
|
|
19
|
+
themes_parser = list_subparsers.add_parser(
|
|
20
|
+
"themes",
|
|
21
|
+
help="List available themes",
|
|
22
|
+
)
|
|
23
|
+
themes_parser.set_defaults(func=run_list_themes)
|
|
24
|
+
|
|
25
|
+
parser.set_defaults(func=lambda args: parser.print_help())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_list_themes(args: argparse.Namespace) -> None:
|
|
29
|
+
"""List available themes by reading theme JSON files from the package."""
|
|
30
|
+
from importlib import resources
|
|
31
|
+
|
|
32
|
+
themes: list[dict[str, str]] = []
|
|
33
|
+
|
|
34
|
+
for package in ("bootstack.assets.themes",):
|
|
35
|
+
try:
|
|
36
|
+
base = resources.files(package)
|
|
37
|
+
except (ModuleNotFoundError, FileNotFoundError):
|
|
38
|
+
continue
|
|
39
|
+
for item in base.iterdir():
|
|
40
|
+
if not item.name.endswith(".json"):
|
|
41
|
+
continue
|
|
42
|
+
try:
|
|
43
|
+
data = json.loads(item.read_text(encoding="utf-8"))
|
|
44
|
+
themes.append({
|
|
45
|
+
"name": data.get("name", item.name.removesuffix(".json")),
|
|
46
|
+
"display_name": data.get("display_name", ""),
|
|
47
|
+
"mode": data.get("mode", ""),
|
|
48
|
+
})
|
|
49
|
+
except (json.JSONDecodeError, OSError):
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
if not themes:
|
|
53
|
+
print("No themes found.")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
themes.sort(key=lambda t: (t["mode"], t["name"]))
|
|
57
|
+
|
|
58
|
+
# Print table
|
|
59
|
+
name_width = max(max(len(t["name"]) for t in themes), len("Name"))
|
|
60
|
+
display_width = max(
|
|
61
|
+
max(len(t["display_name"]) for t in themes), len("Display Name")
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
header = f" {'Name':<{name_width}} {'Display Name':<{display_width}} Mode"
|
|
65
|
+
print(header)
|
|
66
|
+
print(f" {'-' * name_width} {'-' * display_width} ----")
|
|
67
|
+
|
|
68
|
+
for t in themes:
|
|
69
|
+
print(f" {t['name']:<{name_width}} {t['display_name']:<{display_width}} {t['mode']}")
|
|
70
|
+
|
|
71
|
+
print(f"\n {len(themes)} themes available.")
|
bootstack/cli/promote.py
ADDED
|
@@ -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")
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""PyInstaller spec file generation for bootstack projects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from bootstack.cli.config import TtkbConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# PyInstaller spec template that reads from bootstack.toml
|
|
13
|
+
SPEC_TEMPLATE = '''\
|
|
14
|
+
# -*- mode: python ; coding: utf-8 -*-
|
|
15
|
+
"""
|
|
16
|
+
PyInstaller spec file for {app_name}.
|
|
17
|
+
|
|
18
|
+
This file is auto-generated by bootstack CLI.
|
|
19
|
+
Edit bootstack.toml to change build settings, then regenerate with:
|
|
20
|
+
bootstack promote --pyinstaller --force
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
# PyInstaller exec's this spec with SPECPATH (directory of the .spec file)
|
|
27
|
+
# in the namespace. __file__ is not defined inside a spec.
|
|
28
|
+
PROJECT_ROOT = Path(SPECPATH).parent.parent
|
|
29
|
+
CONFIG_PATH = PROJECT_ROOT / "bootstack.toml"
|
|
30
|
+
|
|
31
|
+
# Ensure we can import bootstack CLI tools
|
|
32
|
+
try:
|
|
33
|
+
from bootstack.cli.config import TtkbConfig
|
|
34
|
+
except ImportError:
|
|
35
|
+
sys.path.insert(0, str(PROJECT_ROOT / "src"))
|
|
36
|
+
from bootstack.cli.config import TtkbConfig
|
|
37
|
+
|
|
38
|
+
if not CONFIG_PATH.exists():
|
|
39
|
+
raise FileNotFoundError(f"bootstack.toml not found at {{CONFIG_PATH}}")
|
|
40
|
+
|
|
41
|
+
config = TtkbConfig.load(CONFIG_PATH)
|
|
42
|
+
|
|
43
|
+
# Extract settings
|
|
44
|
+
APP_NAME = config.app.name
|
|
45
|
+
ENTRY_POINT = PROJECT_ROOT / config.app.entry
|
|
46
|
+
WINDOWED = config.build.windowed if config.build else True
|
|
47
|
+
ONEFILE = config.build.onefile if config.build else False
|
|
48
|
+
|
|
49
|
+
# Icon handling
|
|
50
|
+
ICON_PATH = None
|
|
51
|
+
if config.build and config.build.icon.path:
|
|
52
|
+
icon_candidate = PROJECT_ROOT / config.build.icon.path
|
|
53
|
+
if icon_candidate.exists():
|
|
54
|
+
ICON_PATH = str(icon_candidate)
|
|
55
|
+
else:
|
|
56
|
+
# Fall back to the bundled bootstack launch icon (the same artwork
|
|
57
|
+
# the runtime sets via wm_iconphoto, just rendered as a multi-size
|
|
58
|
+
# .ico for Windows). Users can override by setting [build.icon] path
|
|
59
|
+
# in bootstack.toml.
|
|
60
|
+
try:
|
|
61
|
+
import bootstack
|
|
62
|
+
default_icon = Path(bootstack.__file__).parent / "assets" / "bootstack.ico"
|
|
63
|
+
if default_icon.exists():
|
|
64
|
+
ICON_PATH = str(default_icon)
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# =============================================================================
|
|
70
|
+
# Collect data files
|
|
71
|
+
# =============================================================================
|
|
72
|
+
|
|
73
|
+
from glob import glob
|
|
74
|
+
|
|
75
|
+
datas = []
|
|
76
|
+
|
|
77
|
+
# Always include bootstack.toml
|
|
78
|
+
datas.append((str(CONFIG_PATH), "."))
|
|
79
|
+
|
|
80
|
+
# Include patterns from config
|
|
81
|
+
if config.build and config.build.datas.include:
|
|
82
|
+
for pattern in config.build.datas.include:
|
|
83
|
+
if pattern == "bootstack.toml":
|
|
84
|
+
continue # Already added
|
|
85
|
+
matches = glob(str(PROJECT_ROOT / pattern), recursive=True)
|
|
86
|
+
for match in matches:
|
|
87
|
+
match_path = Path(match)
|
|
88
|
+
if match_path.is_file():
|
|
89
|
+
rel_path = match_path.relative_to(PROJECT_ROOT)
|
|
90
|
+
dest_dir = str(rel_path.parent) if rel_path.parent != Path(".") else "."
|
|
91
|
+
datas.append((str(match_path), dest_dir))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# =============================================================================
|
|
95
|
+
# Collect package data (themes, fonts, icon glyphmaps, locales, etc.)
|
|
96
|
+
# =============================================================================
|
|
97
|
+
|
|
98
|
+
# PyInstaller's collect_data_files walks a package and grabs every non-.py
|
|
99
|
+
# file, returning (src, dest) tuples in the form `datas` expects. We use it
|
|
100
|
+
# here for bootstack's own assets, plus a downstream workaround:
|
|
101
|
+
#
|
|
102
|
+
# WORKAROUND (until upstream fixes ship):
|
|
103
|
+
# ttkbootstrap_icons ships a complete _pyinstaller/ hook directory, but
|
|
104
|
+
# its pyproject.toml does not declare the `pyinstaller40` entry point, so
|
|
105
|
+
# PyInstaller never discovers those hooks. ttkbootstrap_icons_bs also has
|
|
106
|
+
# no hook of its own. Both manifest at runtime as:
|
|
107
|
+
# ModuleNotFoundError: No module named 'ttkbootstrap_icons_bs.assets'
|
|
108
|
+
# We bundle their data files manually here so frozen apps actually work.
|
|
109
|
+
# Tracked at: https://github.com/israel-dryer/bootstack-icons/issues
|
|
110
|
+
# Once upstream lands the entry point + a `hook-ttkbootstrap_icons_bs.py`,
|
|
111
|
+
# the two icon-package entries below can be dropped — PyInstaller will
|
|
112
|
+
# pick them up automatically.
|
|
113
|
+
from PyInstaller.utils.hooks import collect_data_files
|
|
114
|
+
|
|
115
|
+
for _pkg in ("bootstack", "ttkbootstrap_icons", "ttkbootstrap_icons_bs"):
|
|
116
|
+
try:
|
|
117
|
+
datas += collect_data_files(_pkg)
|
|
118
|
+
except Exception as _e: # pragma: no cover
|
|
119
|
+
print(f"Warning: Could not collect data files from {{_pkg}}: {{_e}}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# Hidden imports for bootstack
|
|
124
|
+
# =============================================================================
|
|
125
|
+
|
|
126
|
+
hiddenimports = [
|
|
127
|
+
"bootstack",
|
|
128
|
+
"bootstack.themes",
|
|
129
|
+
"bootstack.style",
|
|
130
|
+
"bootstack.widgets",
|
|
131
|
+
"ttkbootstrap_icons",
|
|
132
|
+
"ttkbootstrap_icons_bs",
|
|
133
|
+
"PIL",
|
|
134
|
+
"PIL._tkinter_finder",
|
|
135
|
+
"babel.numbers",
|
|
136
|
+
"babel.dates",
|
|
137
|
+
"dateutil",
|
|
138
|
+
"dateparser",
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
# babel.bin is a CLI script package whose __init__.py calls exit(1) when
|
|
142
|
+
# imported without args; PyInstaller's binary-dependency analyzer
|
|
143
|
+
# imports it during package walking and crashes. Excluding it is safe
|
|
144
|
+
# because nothing in a bootstack app uses babel's command-line tools.
|
|
145
|
+
excludes = [
|
|
146
|
+
"babel.bin",
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# =============================================================================
|
|
151
|
+
# Analysis
|
|
152
|
+
# =============================================================================
|
|
153
|
+
|
|
154
|
+
a = Analysis(
|
|
155
|
+
[str(ENTRY_POINT)],
|
|
156
|
+
pathex=[str(PROJECT_ROOT / "src")],
|
|
157
|
+
binaries=[],
|
|
158
|
+
datas=datas,
|
|
159
|
+
hiddenimports=hiddenimports,
|
|
160
|
+
hookspath=[],
|
|
161
|
+
hooksconfig={{}},
|
|
162
|
+
runtime_hooks=[],
|
|
163
|
+
excludes=excludes,
|
|
164
|
+
noarchive=False,
|
|
165
|
+
optimize=0,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# =============================================================================
|
|
170
|
+
# Build
|
|
171
|
+
# =============================================================================
|
|
172
|
+
|
|
173
|
+
pyz = PYZ(a.pure)
|
|
174
|
+
|
|
175
|
+
if ONEFILE:
|
|
176
|
+
exe = EXE(
|
|
177
|
+
pyz,
|
|
178
|
+
a.scripts,
|
|
179
|
+
a.binaries,
|
|
180
|
+
a.datas,
|
|
181
|
+
[],
|
|
182
|
+
name=APP_NAME,
|
|
183
|
+
debug=False,
|
|
184
|
+
bootloader_ignore_signals=False,
|
|
185
|
+
strip=False,
|
|
186
|
+
upx=True,
|
|
187
|
+
upx_exclude=[],
|
|
188
|
+
runtime_tmpdir=None,
|
|
189
|
+
console=not WINDOWED,
|
|
190
|
+
disable_windowed_traceback=False,
|
|
191
|
+
argv_emulation=False,
|
|
192
|
+
target_arch=None,
|
|
193
|
+
codesign_identity=None,
|
|
194
|
+
entitlements_file=None,
|
|
195
|
+
icon=ICON_PATH,
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
exe = EXE(
|
|
199
|
+
pyz,
|
|
200
|
+
a.scripts,
|
|
201
|
+
[],
|
|
202
|
+
exclude_binaries=True,
|
|
203
|
+
name=APP_NAME,
|
|
204
|
+
debug=False,
|
|
205
|
+
bootloader_ignore_signals=False,
|
|
206
|
+
strip=False,
|
|
207
|
+
upx=True,
|
|
208
|
+
console=not WINDOWED,
|
|
209
|
+
disable_windowed_traceback=False,
|
|
210
|
+
argv_emulation=False,
|
|
211
|
+
target_arch=None,
|
|
212
|
+
codesign_identity=None,
|
|
213
|
+
entitlements_file=None,
|
|
214
|
+
icon=ICON_PATH,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
coll = COLLECT(
|
|
218
|
+
exe,
|
|
219
|
+
a.binaries,
|
|
220
|
+
a.datas,
|
|
221
|
+
strip=False,
|
|
222
|
+
upx=True,
|
|
223
|
+
upx_exclude=[],
|
|
224
|
+
name=APP_NAME,
|
|
225
|
+
)
|
|
226
|
+
'''
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def generate_spec(
|
|
230
|
+
config: TtkbConfig,
|
|
231
|
+
project_root: Path,
|
|
232
|
+
output_path: Path,
|
|
233
|
+
) -> None:
|
|
234
|
+
"""Generate a PyInstaller spec file.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
config: The project configuration.
|
|
238
|
+
project_root: Root directory of the project.
|
|
239
|
+
output_path: Path to write the spec file.
|
|
240
|
+
"""
|
|
241
|
+
content = SPEC_TEMPLATE.format(
|
|
242
|
+
app_name=config.app.name,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
246
|
+
output_path.write_text(content, encoding="utf-8")
|