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.
- bootstack/__init__.py +157 -0
- bootstack/__main__.py +5 -0
- bootstack/_core/__init__.py +21 -0
- bootstack/_core/capabilities/__init__.py +45 -0
- bootstack/_core/capabilities/after.py +103 -0
- bootstack/_core/capabilities/bind.py +192 -0
- bootstack/_core/capabilities/bindtags.py +112 -0
- bootstack/_core/capabilities/busy.py +72 -0
- bootstack/_core/capabilities/clipboard.py +89 -0
- bootstack/_core/capabilities/focus.py +118 -0
- bootstack/_core/capabilities/grab.py +65 -0
- bootstack/_core/capabilities/grid.py +211 -0
- bootstack/_core/capabilities/localization.py +231 -0
- bootstack/_core/capabilities/pack.py +140 -0
- bootstack/_core/capabilities/place.py +113 -0
- bootstack/_core/capabilities/selection.py +136 -0
- bootstack/_core/capabilities/signals.py +244 -0
- bootstack/_core/capabilities/winfo.py +315 -0
- bootstack/_core/colorutils.py +234 -0
- bootstack/_core/exceptions.py +25 -0
- bootstack/_core/images.py +463 -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/paths.py +49 -0
- bootstack/_core/publisher.py +149 -0
- bootstack/_core/variables.py +62 -0
- bootstack/_runtime/__init__.py +3 -0
- bootstack/_runtime/app.py +930 -0
- bootstack/_runtime/base_window.py +945 -0
- bootstack/_runtime/events.py +399 -0
- bootstack/_runtime/shortcuts.py +496 -0
- bootstack/_runtime/tk_patch.py +43 -0
- bootstack/_runtime/toplevel.py +276 -0
- bootstack/_runtime/utility.py +457 -0
- bootstack/_runtime/visual_focus.py +240 -0
- bootstack/_runtime/window_utilities.py +1123 -0
- bootstack/assets/__init__.py +21 -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/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- bootstack/assets/elements/card.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-addon-compact.png +0 -0
- bootstack/assets/elements/input-addon-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/listrow-compact.png +0 -0
- bootstack/assets/elements/listrow-default.png +0 -0
- bootstack/assets/elements/manifest.toml +361 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/navitem-compact.png +0 -0
- bootstack/assets/elements/navitem-default.png +0 -0
- bootstack/assets/elements/progressbar-h-compact.png +0 -0
- bootstack/assets/elements/progressbar-h-default.png +0 -0
- bootstack/assets/elements/progressbar-v-compact.png +0 -0
- bootstack/assets/elements/progressbar-v-default.png +0 -0
- bootstack/assets/elements/radiobutton.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-h.png +0 -0
- bootstack/assets/elements/slider-track-v.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tab-h.png +0 -0
- bootstack/assets/elements/tab-v.png +0 -0
- bootstack/assets/icons/bootstrap.ttf +0 -0
- bootstack/assets/icons/glyphmap.json +2080 -0
- bootstack/assets/icons/icon_metrics.json +12470 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
- bootstack/cli/__init__.py +133 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +395 -0
- bootstack/cli/appicon.py +285 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +313 -0
- bootstack/cli/demo.py +564 -0
- bootstack/cli/dev.py +153 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/icons.py +98 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +268 -0
- bootstack/cli/run.py +95 -0
- bootstack/cli/start.py +117 -0
- bootstack/cli/templates/__init__.py +931 -0
- bootstack/clipboard.py +48 -0
- bootstack/constants.py +318 -0
- bootstack/data/README.md +615 -0
- bootstack/data/__init__.py +78 -0
- bootstack/data/_observable.py +276 -0
- bootstack/data/base.py +780 -0
- bootstack/data/file_source.py +367 -0
- bootstack/data/memory_source.py +450 -0
- bootstack/data/query.py +367 -0
- bootstack/data/readers.py +289 -0
- bootstack/data/sqlite_source.py +869 -0
- bootstack/data/types.py +354 -0
- bootstack/data/writers.py +232 -0
- bootstack/dev/__init__.py +36 -0
- bootstack/dev/_capture.py +141 -0
- bootstack/dev/_env.py +43 -0
- bootstack/dev/_registry.py +140 -0
- bootstack/dev/_reloader.py +351 -0
- bootstack/dev/_reset.py +50 -0
- bootstack/dev/_watcher.py +91 -0
- bootstack/dialogs/__init__.py +948 -0
- bootstack/dialogs/_impl/__init__.py +47 -0
- bootstack/dialogs/_impl/colorchooser.py +588 -0
- bootstack/dialogs/_impl/datedialog.py +450 -0
- bootstack/dialogs/_impl/dialog.py +594 -0
- bootstack/dialogs/_impl/filterdialog.py +358 -0
- bootstack/dialogs/_impl/fontdialog.py +364 -0
- bootstack/dialogs/_impl/formdialog.py +564 -0
- bootstack/dialogs/_impl/message.py +486 -0
- bootstack/dialogs/_impl/query.py +570 -0
- bootstack/errors.py +67 -0
- bootstack/events/__init__.py +111 -0
- bootstack/events/_event.py +135 -0
- bootstack/events/_payloads.py +539 -0
- bootstack/events/_subscription.py +38 -0
- bootstack/i18n/README.md +77 -0
- bootstack/i18n/__init__.py +23 -0
- bootstack/i18n/catalog.py +121 -0
- bootstack/i18n/intl_format.py +584 -0
- bootstack/i18n/msgcat.py +425 -0
- bootstack/i18n/specs.py +156 -0
- bootstack/images.py +563 -0
- bootstack/py.typed +1 -0
- bootstack/scheduling/__init__.py +11 -0
- bootstack/scheduling/_schedule.py +218 -0
- bootstack/shortcuts.py +21 -0
- bootstack/signals/README.md +98 -0
- bootstack/signals/__init__.py +10 -0
- bootstack/signals/integration.py +100 -0
- bootstack/signals/signal.py +353 -0
- bootstack/signals/types.py +6 -0
- bootstack/store.py +286 -0
- bootstack/streams/__init__.py +12 -0
- bootstack/streams/_stream.py +321 -0
- bootstack/style/__init__.py +38 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +46 -0
- bootstack/style/builders/button.py +339 -0
- bootstack/style/builders/buttongroup.py +311 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +110 -0
- bootstack/style/builders/combobox.py +113 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +82 -0
- bootstack/style/builders/expander.py +148 -0
- bootstack/style/builders/field.py +335 -0
- bootstack/style/builders/frame.py +50 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +34 -0
- bootstack/style/builders/listview.py +369 -0
- bootstack/style/builders/menubar.py +91 -0
- bootstack/style/builders/menubutton.py +359 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +67 -0
- bootstack/style/builders/radiobutton.py +99 -0
- bootstack/style/builders/scale.py +64 -0
- bootstack/style/builders/scrollbar.py +225 -0
- bootstack/style/builders/separator.py +49 -0
- bootstack/style/builders/sidenav.py +643 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +70 -0
- bootstack/style/builders/tabitem.py +204 -0
- bootstack/style/builders/togglegroup.py +294 -0
- bootstack/style/builders/toolbutton.py +275 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +193 -0
- bootstack/style/builders/utils.py +455 -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/fonts.py +123 -0
- bootstack/style/style.py +609 -0
- bootstack/style/style_builder_base.py +716 -0
- bootstack/style/style_builder_mixed.py +93 -0
- bootstack/style/style_builder_tk.py +109 -0
- bootstack/style/style_builder_ttk.py +353 -0
- bootstack/style/style_resolver.py +447 -0
- bootstack/style/theme.py +245 -0
- bootstack/style/theme_provider.py +471 -0
- bootstack/style/themes/__init__.py +128 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +41 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +523 -0
- bootstack/style/utility.py +746 -0
- bootstack/types.py +39 -0
- bootstack/validation/__init__.py +6 -0
- bootstack/validation/types.py +15 -0
- bootstack/validation/validation_result.py +17 -0
- bootstack/validation/validation_rules.py +205 -0
- bootstack/widgets/__init__.py +74 -0
- bootstack/widgets/_core/__init__.py +31 -0
- bootstack/widgets/_core/app_config.py +266 -0
- bootstack/widgets/_core/base.py +461 -0
- bootstack/widgets/_core/container.py +334 -0
- bootstack/widgets/_core/context.py +35 -0
- bootstack/widgets/_core/events.py +130 -0
- bootstack/widgets/_core/field_mixin.py +353 -0
- bootstack/widgets/_core/icon_image_props.py +72 -0
- bootstack/widgets/_core/image_binding.py +93 -0
- bootstack/widgets/_core/navmodel.py +345 -0
- bootstack/widgets/_core/options.py +210 -0
- bootstack/widgets/_core/selection_group.py +130 -0
- bootstack/widgets/_core/window_controls.py +96 -0
- bootstack/widgets/_core/window_menu.py +246 -0
- bootstack/widgets/_impl/__init__.py +1 -0
- bootstack/widgets/_impl/_internal/__init__.py +0 -0
- bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
- bootstack/widgets/_impl/_parts/__init__.py +11 -0
- bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
- bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
- bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
- bootstack/widgets/_impl/composites/__init__.py +33 -0
- bootstack/widgets/_impl/composites/_dateutils.py +33 -0
- bootstack/widgets/_impl/composites/_image_fit.py +105 -0
- bootstack/widgets/_impl/composites/accordion.py +381 -0
- bootstack/widgets/_impl/composites/avatar.py +191 -0
- bootstack/widgets/_impl/composites/buttongroup.py +371 -0
- bootstack/widgets/_impl/composites/calendar.py +972 -0
- bootstack/widgets/_impl/composites/carousel.py +567 -0
- bootstack/widgets/_impl/composites/chart.py +882 -0
- bootstack/widgets/_impl/composites/compositeframe.py +298 -0
- bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
- bootstack/widgets/_impl/composites/dateentry.py +404 -0
- bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
- bootstack/widgets/_impl/composites/expander.py +515 -0
- bootstack/widgets/_impl/composites/field.py +670 -0
- bootstack/widgets/_impl/composites/form.py +1066 -0
- bootstack/widgets/_impl/composites/gallery.py +551 -0
- bootstack/widgets/_impl/composites/list/__init__.py +15 -0
- bootstack/widgets/_impl/composites/list/listitem.py +802 -0
- bootstack/widgets/_impl/composites/list/listview.py +1433 -0
- bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
- bootstack/widgets/_impl/composites/menu/model.py +358 -0
- bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
- bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
- bootstack/widgets/_impl/composites/meter.py +860 -0
- bootstack/widgets/_impl/composites/numericentry.py +201 -0
- bootstack/widgets/_impl/composites/pagestack.py +395 -0
- bootstack/widgets/_impl/composites/passwordentry.py +142 -0
- bootstack/widgets/_impl/composites/pathentry.py +168 -0
- bootstack/widgets/_impl/composites/picture.py +289 -0
- bootstack/widgets/_impl/composites/radiogroup.py +511 -0
- bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
- bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/_impl/composites/scrollview.py +764 -0
- bootstack/widgets/_impl/composites/selectbox.py +1026 -0
- bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
- bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
- bootstack/widgets/_impl/composites/shell/layout.py +335 -0
- bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
- bootstack/widgets/_impl/composites/shell/providers.py +558 -0
- bootstack/widgets/_impl/composites/shell/rail.py +117 -0
- bootstack/widgets/_impl/composites/shell/shell.py +581 -0
- bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
- bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
- bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
- bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
- bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
- bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
- bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
- bootstack/widgets/_impl/composites/slider/slider.py +851 -0
- bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
- bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
- bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
- bootstack/widgets/_impl/composites/tableview/types.py +169 -0
- bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
- bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
- bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
- bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
- bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
- bootstack/widgets/_impl/composites/textarea/change.py +46 -0
- bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
- bootstack/widgets/_impl/composites/textarea/core.py +495 -0
- bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
- bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
- bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
- bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
- bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
- bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
- bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
- bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
- bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
- bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
- bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
- bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
- bootstack/widgets/_impl/composites/textentry.py +57 -0
- bootstack/widgets/_impl/composites/timeentry.py +176 -0
- bootstack/widgets/_impl/composites/toast.py +390 -0
- bootstack/widgets/_impl/composites/toast_stack.py +156 -0
- bootstack/widgets/_impl/composites/togglegroup.py +418 -0
- bootstack/widgets/_impl/composites/toolbar.py +608 -0
- bootstack/widgets/_impl/composites/tooltip.py +491 -0
- bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
- bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
- bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
- bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
- bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
- bootstack/widgets/_impl/mixins/__init__.py +24 -0
- bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
- bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
- bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
- bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
- bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
- bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
- bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
- bootstack/widgets/_impl/primitives/__init__.py +49 -0
- bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
- bootstack/widgets/_impl/primitives/badge.py +45 -0
- bootstack/widgets/_impl/primitives/button.py +76 -0
- bootstack/widgets/_impl/primitives/card.py +45 -0
- bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
- bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
- bootstack/widgets/_impl/primitives/combobox.py +156 -0
- bootstack/widgets/_impl/primitives/entry.py +87 -0
- bootstack/widgets/_impl/primitives/flexframe.py +448 -0
- bootstack/widgets/_impl/primitives/frame.py +185 -0
- bootstack/widgets/_impl/primitives/gridframe.py +546 -0
- bootstack/widgets/_impl/primitives/label.py +84 -0
- bootstack/widgets/_impl/primitives/labelframe.py +54 -0
- bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
- bootstack/widgets/_impl/primitives/packframe.py +227 -0
- bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
- bootstack/widgets/_impl/primitives/progressbar.py +83 -0
- bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
- bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
- bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
- bootstack/widgets/_impl/primitives/separator.py +43 -0
- bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
- bootstack/widgets/_impl/primitives/spinbox.py +95 -0
- bootstack/widgets/_impl/primitives/switch.py +44 -0
- bootstack/widgets/_impl/primitives/treeview.py +69 -0
- bootstack/widgets/app.py +371 -0
- bootstack/widgets/appshell.py +1179 -0
- bootstack/widgets/avatar.py +140 -0
- bootstack/widgets/boolean_controls.py +455 -0
- bootstack/widgets/button.py +224 -0
- bootstack/widgets/buttongroup.py +239 -0
- bootstack/widgets/calendar.py +195 -0
- bootstack/widgets/card.py +159 -0
- bootstack/widgets/carousel.py +241 -0
- bootstack/widgets/chart.py +302 -0
- bootstack/widgets/codeeditor.py +675 -0
- bootstack/widgets/contextmenu.py +371 -0
- bootstack/widgets/datatable.py +688 -0
- bootstack/widgets/datefield.py +395 -0
- bootstack/widgets/divider.py +60 -0
- bootstack/widgets/expander.py +579 -0
- bootstack/widgets/form.py +200 -0
- bootstack/widgets/gallery.py +250 -0
- bootstack/widgets/gauge.py +168 -0
- bootstack/widgets/grid.py +121 -0
- bootstack/widgets/groupbox.py +162 -0
- bootstack/widgets/label.py +229 -0
- bootstack/widgets/listview.py +354 -0
- bootstack/widgets/menubutton.py +419 -0
- bootstack/widgets/numberfield.py +431 -0
- bootstack/widgets/pagestack.py +345 -0
- bootstack/widgets/passwordfield.py +383 -0
- bootstack/widgets/pathfield.py +454 -0
- bootstack/widgets/picture.py +251 -0
- bootstack/widgets/progressbar.py +106 -0
- bootstack/widgets/radio_variants.py +271 -0
- bootstack/widgets/radiogroup.py +228 -0
- bootstack/widgets/scrollbar.py +64 -0
- bootstack/widgets/scrollview.py +186 -0
- bootstack/widgets/select.py +302 -0
- bootstack/widgets/selectbutton.py +182 -0
- bootstack/widgets/sidebar_toggle.py +130 -0
- bootstack/widgets/sizegrip.py +42 -0
- bootstack/widgets/slider.py +408 -0
- bootstack/widgets/spinbox.py +147 -0
- bootstack/widgets/spinnerfield.py +413 -0
- bootstack/widgets/splash.py +367 -0
- bootstack/widgets/splitview.py +482 -0
- bootstack/widgets/stacks.py +249 -0
- bootstack/widgets/statusbar.py +189 -0
- bootstack/widgets/tabs.py +425 -0
- bootstack/widgets/textarea.py +459 -0
- bootstack/widgets/textfield.py +410 -0
- bootstack/widgets/theme_toggle.py +92 -0
- bootstack/widgets/timefield.py +340 -0
- bootstack/widgets/toast.py +314 -0
- bootstack/widgets/togglegroup.py +231 -0
- bootstack/widgets/toolbar.py +347 -0
- bootstack/widgets/tooltip.py +94 -0
- bootstack/widgets/tree.py +648 -0
- bootstack/widgets/types.py +305 -0
- bootstack/widgets/window.py +307 -0
- bootstack-0.1.0.dist-info/METADATA +301 -0
- bootstack-0.1.0.dist-info/RECORD +471 -0
- bootstack-0.1.0.dist-info/WHEEL +5 -0
- bootstack-0.1.0.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
"""Project templates for bootstack start command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
ContainerType = Literal["grid", "pack"]
|
|
9
|
+
TemplateType = Literal["basic", "appshell"]
|
|
10
|
+
NavStyle = Literal["single", "grouped", "master-detail", "workspaces"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# =============================================================================
|
|
14
|
+
# Main entry point template
|
|
15
|
+
# =============================================================================
|
|
16
|
+
|
|
17
|
+
MAIN_PY_TEMPLATE = '''\
|
|
18
|
+
"""
|
|
19
|
+
{app_name} - A bootstack application.
|
|
20
|
+
|
|
21
|
+
Run with: python -m {module_name}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import bootstack as bs
|
|
25
|
+
|
|
26
|
+
from {module_name}.views.main_view import build_main
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main() -> None:
|
|
30
|
+
"""Application entry point."""
|
|
31
|
+
with bs.App(
|
|
32
|
+
title="{app_name}",
|
|
33
|
+
theme="{theme}",
|
|
34
|
+
size=(800, 600),
|
|
35
|
+
padding=20,
|
|
36
|
+
gap=10,
|
|
37
|
+
) as app:
|
|
38
|
+
# `build_main` is a builder function: it paints its widgets into the
|
|
39
|
+
# active container (here, the App body). See the "Composing with
|
|
40
|
+
# Builders" guide.
|
|
41
|
+
build_main()
|
|
42
|
+
|
|
43
|
+
app.run()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
|
48
|
+
'''
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# =============================================================================
|
|
52
|
+
# View templates (GridFrame vs PackFrame)
|
|
53
|
+
# =============================================================================
|
|
54
|
+
|
|
55
|
+
MAIN_VIEW_GRID_TEMPLATE = '''\
|
|
56
|
+
"""Main application view."""
|
|
57
|
+
|
|
58
|
+
import bootstack as bs
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build_main() -> None:
|
|
62
|
+
"""Build the main view (a two-column grid form)."""
|
|
63
|
+
# The App body is a column, so open a Grid for the label/field columns.
|
|
64
|
+
# `horizontal="stretch"` lets the Grid fill the width so the field column grows.
|
|
65
|
+
with bs.Grid(columns=["auto", 1], gap=10, horizontal="stretch"):
|
|
66
|
+
bs.Label("Welcome to {app_name}", font="heading-lg", columnspan=2)
|
|
67
|
+
bs.Label("Name:")
|
|
68
|
+
name_field = bs.TextField()
|
|
69
|
+
bs.Label("Email:")
|
|
70
|
+
email_field = bs.TextField()
|
|
71
|
+
|
|
72
|
+
def on_submit() -> None:
|
|
73
|
+
print(f"Name: {{name_field.value}}, Email: {{email_field.value}}")
|
|
74
|
+
|
|
75
|
+
bs.Button("Get Started", accent="primary", on_click=on_submit, columnspan=2)
|
|
76
|
+
'''
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
MAIN_VIEW_PACK_TEMPLATE = '''\
|
|
80
|
+
"""Main application view."""
|
|
81
|
+
|
|
82
|
+
import bootstack as bs
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_main() -> None:
|
|
86
|
+
"""Build the main view (a vertical stack of fields)."""
|
|
87
|
+
# Paints directly into the active container (the padded App body).
|
|
88
|
+
bs.Label("Welcome to {app_name}", font="heading-lg")
|
|
89
|
+
name_field = bs.TextField(label="Name:", horizontal="stretch")
|
|
90
|
+
email_field = bs.TextField(label="Email:", horizontal="stretch")
|
|
91
|
+
|
|
92
|
+
def on_submit() -> None:
|
|
93
|
+
print(f"Name: {{name_field.value}}, Email: {{email_field.value}}")
|
|
94
|
+
|
|
95
|
+
bs.Button("Get Started", accent="primary", on_click=on_submit)
|
|
96
|
+
'''
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# =============================================================================
|
|
100
|
+
# View/Dialog add templates
|
|
101
|
+
# =============================================================================
|
|
102
|
+
|
|
103
|
+
VIEW_GRID_TEMPLATE = '''\
|
|
104
|
+
"""{title} view."""
|
|
105
|
+
|
|
106
|
+
import bootstack as bs
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def {func_name}() -> None:
|
|
110
|
+
"""Build the {title} view (a two-column grid)."""
|
|
111
|
+
with bs.Grid(columns=["auto", 1], gap=10, horizontal="stretch"):
|
|
112
|
+
bs.Label("{title}", font="heading-lg", columnspan=2)
|
|
113
|
+
# Add your widgets here
|
|
114
|
+
'''
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
VIEW_PACK_TEMPLATE = '''\
|
|
118
|
+
"""{title} view."""
|
|
119
|
+
|
|
120
|
+
import bootstack as bs
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def {func_name}() -> None:
|
|
124
|
+
"""Build the {title} view."""
|
|
125
|
+
# Paints into the active container; the caller supplies padding/gap.
|
|
126
|
+
bs.Label("{title}", font="heading-lg")
|
|
127
|
+
# Add your widgets here
|
|
128
|
+
'''
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
DIALOG_TEMPLATE = '''\
|
|
132
|
+
"""
|
|
133
|
+
{class_name} dialog.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
import bootstack as bs
|
|
137
|
+
from bootstack.dialogs import Dialog, DialogButton
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class {class_name}:
|
|
141
|
+
"""A custom {class_name} dialog.
|
|
142
|
+
|
|
143
|
+
Usage::
|
|
144
|
+
|
|
145
|
+
result = {class_name}(parent=app).show()
|
|
146
|
+
if result:
|
|
147
|
+
...
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(self, parent=None):
|
|
151
|
+
self._dialog = Dialog(
|
|
152
|
+
title="{class_name}",
|
|
153
|
+
content_builder=self._build,
|
|
154
|
+
buttons=[
|
|
155
|
+
DialogButton("Cancel", role="cancel"),
|
|
156
|
+
DialogButton("OK", role="primary", result=True, default=True),
|
|
157
|
+
],
|
|
158
|
+
min_size=(360, 200),
|
|
159
|
+
parent=parent,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def _build(self, frame) -> None:
|
|
163
|
+
"""Fill the dialog body."""
|
|
164
|
+
with bs.Column(padding=20, gap=8, parent=frame):
|
|
165
|
+
bs.Label("Dialog content goes here.")
|
|
166
|
+
|
|
167
|
+
def show(self):
|
|
168
|
+
"""Show the dialog modally and return its result (None if cancelled)."""
|
|
169
|
+
self._dialog.show()
|
|
170
|
+
return self._dialog.result
|
|
171
|
+
'''
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# =============================================================================
|
|
175
|
+
# Package init template
|
|
176
|
+
# =============================================================================
|
|
177
|
+
|
|
178
|
+
INIT_PY_TEMPLATE = '''\
|
|
179
|
+
"""{app_name} package."""
|
|
180
|
+
|
|
181
|
+
__version__ = "0.1.0"
|
|
182
|
+
'''
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# =============================================================================
|
|
186
|
+
# README template
|
|
187
|
+
# =============================================================================
|
|
188
|
+
|
|
189
|
+
README_TEMPLATE = '''\
|
|
190
|
+
# {app_name}
|
|
191
|
+
|
|
192
|
+
A bootstack application.
|
|
193
|
+
|
|
194
|
+
## Getting Started
|
|
195
|
+
|
|
196
|
+
### Development
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Run the application
|
|
200
|
+
python -m {module_name}
|
|
201
|
+
|
|
202
|
+
# Or use the CLI
|
|
203
|
+
bootstack run
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Building for Distribution
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Promote to packaging-ready (adds PyInstaller support)
|
|
210
|
+
bootstack promote --pyinstaller
|
|
211
|
+
|
|
212
|
+
# Build the executable
|
|
213
|
+
bootstack build
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Project Structure
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
{project_dir}/
|
|
220
|
+
├── src/{module_name}/
|
|
221
|
+
│ ├── __init__.py
|
|
222
|
+
│ ├── main.py
|
|
223
|
+
│ └── views/
|
|
224
|
+
│ └── main_view.py
|
|
225
|
+
├── assets/
|
|
226
|
+
├── bootstack.toml
|
|
227
|
+
└── README.md
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Configuration
|
|
231
|
+
|
|
232
|
+
Application settings are defined in `bootstack.toml`:
|
|
233
|
+
|
|
234
|
+
- `[app]` - Application metadata
|
|
235
|
+
- `[layout]` - Default layout preferences
|
|
236
|
+
- `[build]` - Build/packaging configuration (after `bootstack promote`)
|
|
237
|
+
'''
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# =============================================================================
|
|
241
|
+
# AppShell templates
|
|
242
|
+
# =============================================================================
|
|
243
|
+
|
|
244
|
+
def _appshell_chrome(app_name: str) -> str:
|
|
245
|
+
"""The shared toolbar (menus + commands) and status bar baseline for AppShell
|
|
246
|
+
scaffolds. Delete what you do not need."""
|
|
247
|
+
return f'''\
|
|
248
|
+
# --- Toolbar (menus + commands) -------------------------------------
|
|
249
|
+
with shell.add_toolbar() as bar:
|
|
250
|
+
with bar.add_menu("File") as file_menu:
|
|
251
|
+
file_menu.add_action("Quit", shortcut="Mod+Q", on_click=shell.close)
|
|
252
|
+
with bar.add_menu("Help") as help_menu:
|
|
253
|
+
help_menu.add_action(
|
|
254
|
+
"About",
|
|
255
|
+
on_click=lambda: bs.alert("{app_name}", title="About"),
|
|
256
|
+
)
|
|
257
|
+
bar.add_spacer()
|
|
258
|
+
bar.add_theme_toggle()
|
|
259
|
+
|
|
260
|
+
# --- Status bar -----------------------------------------------------
|
|
261
|
+
shell.statusbar.add_text("Ready")
|
|
262
|
+
shell.statusbar.add_text("v0.1.0", side="right")
|
|
263
|
+
'''
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
APPSHELL_MAIN_SINGLE_TEMPLATE = '''\
|
|
267
|
+
"""
|
|
268
|
+
{app_name} - A bootstack application.
|
|
269
|
+
|
|
270
|
+
Run with: python -m {module_name}
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
import bootstack as bs
|
|
274
|
+
|
|
275
|
+
from {module_name}.pages.home_page import build_home
|
|
276
|
+
from {module_name}.pages.settings_page import build_settings
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def main() -> None:
|
|
280
|
+
"""Application entry point."""
|
|
281
|
+
with bs.AppShell(
|
|
282
|
+
title="{app_name}",
|
|
283
|
+
theme="{theme}",
|
|
284
|
+
size=(1000, 650),
|
|
285
|
+
show_statusbar=True,
|
|
286
|
+
) as shell:
|
|
287
|
+
{chrome}
|
|
288
|
+
# --- Navigation: a flat sidebar of pages ----------------------------
|
|
289
|
+
# Each page IS a layout container, so configure padding/gap/alignment on
|
|
290
|
+
# add_page() and let the page's builder paint straight into it.
|
|
291
|
+
with shell.page_nav() as nav:
|
|
292
|
+
with nav.add_page("home", text="Home", icon="house",
|
|
293
|
+
padding=20, gap=12, horizontal_items="stretch"):
|
|
294
|
+
build_home()
|
|
295
|
+
|
|
296
|
+
with nav.add_page("settings", text="Settings", icon="gear",
|
|
297
|
+
pin_to_footer=True, padding=20, gap=12,
|
|
298
|
+
horizontal_items="stretch"):
|
|
299
|
+
build_settings()
|
|
300
|
+
|
|
301
|
+
shell.navigate("home")
|
|
302
|
+
|
|
303
|
+
shell.run()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
if __name__ == "__main__":
|
|
307
|
+
main()
|
|
308
|
+
'''
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
APPSHELL_MAIN_GROUPED_TEMPLATE = '''\
|
|
312
|
+
"""
|
|
313
|
+
{app_name} - A bootstack application.
|
|
314
|
+
|
|
315
|
+
Run with: python -m {module_name}
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
import bootstack as bs
|
|
319
|
+
|
|
320
|
+
from {module_name}.pages.home_page import build_home
|
|
321
|
+
from {module_name}.pages.reports_page import build_reports
|
|
322
|
+
from {module_name}.pages.profile_page import build_profile
|
|
323
|
+
from {module_name}.pages.settings_page import build_settings
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def main() -> None:
|
|
327
|
+
"""Application entry point."""
|
|
328
|
+
with bs.AppShell(
|
|
329
|
+
title="{app_name}",
|
|
330
|
+
theme="{theme}",
|
|
331
|
+
size=(1000, 650),
|
|
332
|
+
show_statusbar=True,
|
|
333
|
+
) as shell:
|
|
334
|
+
{chrome}
|
|
335
|
+
# --- Navigation: pages grouped under section headers ----------------
|
|
336
|
+
# Each page IS a layout container, so configure padding/gap/alignment on
|
|
337
|
+
# add_page() and let the page's builder paint straight into it.
|
|
338
|
+
with shell.page_nav() as nav:
|
|
339
|
+
nav.add_header("Workspace")
|
|
340
|
+
with nav.add_page("home", text="Home", icon="house",
|
|
341
|
+
padding=20, gap=12, horizontal_items="stretch"):
|
|
342
|
+
build_home()
|
|
343
|
+
with nav.add_page("reports", text="Reports", icon="bar-chart",
|
|
344
|
+
padding=20, gap=12, horizontal_items="stretch"):
|
|
345
|
+
build_reports()
|
|
346
|
+
|
|
347
|
+
nav.add_header("Account")
|
|
348
|
+
with nav.add_page("profile", text="Profile", icon="person",
|
|
349
|
+
padding=20, gap=12, horizontal_items="stretch"):
|
|
350
|
+
build_profile()
|
|
351
|
+
|
|
352
|
+
with nav.add_page("settings", text="Settings", icon="gear",
|
|
353
|
+
pin_to_footer=True, padding=20, gap=12,
|
|
354
|
+
horizontal_items="stretch"):
|
|
355
|
+
build_settings()
|
|
356
|
+
|
|
357
|
+
shell.navigate("home")
|
|
358
|
+
|
|
359
|
+
shell.run()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
363
|
+
main()
|
|
364
|
+
'''
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
APPSHELL_MAIN_MASTER_DETAIL_TEMPLATE = '''\
|
|
368
|
+
"""
|
|
369
|
+
{app_name} - A bootstack application.
|
|
370
|
+
|
|
371
|
+
Run with: python -m {module_name}
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
import bootstack as bs
|
|
375
|
+
from bootstack.data import MemoryDataSource
|
|
376
|
+
|
|
377
|
+
from {module_name}.pages.detail_view import build_detail
|
|
378
|
+
|
|
379
|
+
# Sample records drive the sidebar list. Swap in your own data source.
|
|
380
|
+
RECORDS = MemoryDataSource().load([
|
|
381
|
+
{{"id": 1, "title": "Dana Reyes", "text": "Q3 roadmap review", "icon": "person"}},
|
|
382
|
+
{{"id": 2, "title": "Sam Okonkwo", "text": "Budget approval", "icon": "person"}},
|
|
383
|
+
{{"id": 3, "title": "Priya Nair", "text": "New hire onboarding", "icon": "person"}},
|
|
384
|
+
])
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def main() -> None:
|
|
388
|
+
"""Application entry point."""
|
|
389
|
+
with bs.AppShell(
|
|
390
|
+
title="{app_name}",
|
|
391
|
+
theme="{theme}",
|
|
392
|
+
size=(1000, 650),
|
|
393
|
+
show_statusbar=True,
|
|
394
|
+
) as shell:
|
|
395
|
+
{chrome}
|
|
396
|
+
# --- Navigation: a record list drives a detail view -----------------
|
|
397
|
+
# `build_detail` is a builder: it paints the selected record into the
|
|
398
|
+
# detail region each time the selection changes.
|
|
399
|
+
shell.list_nav(RECORDS)
|
|
400
|
+
|
|
401
|
+
@shell.detail
|
|
402
|
+
def show(record):
|
|
403
|
+
build_detail(record)
|
|
404
|
+
|
|
405
|
+
shell.run()
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
if __name__ == "__main__":
|
|
409
|
+
main()
|
|
410
|
+
'''
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
APPSHELL_MAIN_WORKSPACES_TEMPLATE = '''\
|
|
414
|
+
"""
|
|
415
|
+
{app_name} - A bootstack application.
|
|
416
|
+
|
|
417
|
+
Run with: python -m {module_name}
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
import bootstack as bs
|
|
421
|
+
|
|
422
|
+
from {module_name}.pages.home_page import build_home
|
|
423
|
+
from {module_name}.pages.reports_page import build_reports
|
|
424
|
+
from {module_name}.pages.settings_page import build_settings
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def main() -> None:
|
|
428
|
+
"""Application entry point."""
|
|
429
|
+
with bs.Workbench(
|
|
430
|
+
title="{app_name}",
|
|
431
|
+
theme="{theme}",
|
|
432
|
+
size=(1000, 650),
|
|
433
|
+
show_statusbar=True,
|
|
434
|
+
rail_labels=True,
|
|
435
|
+
) as shell:
|
|
436
|
+
{chrome}
|
|
437
|
+
# --- Navigation: a labeled rail of workspaces -----------------------
|
|
438
|
+
# Each page IS a layout container, so configure padding/gap/alignment on
|
|
439
|
+
# add_page() and let the page's builder paint straight into it.
|
|
440
|
+
with shell.add_workspace("home", text="Home", icon="house") as ws:
|
|
441
|
+
with ws.page_nav() as nav:
|
|
442
|
+
with nav.add_page("overview", text="Overview", icon="speedometer2",
|
|
443
|
+
padding=20, gap=12, horizontal_items="stretch"):
|
|
444
|
+
build_home()
|
|
445
|
+
|
|
446
|
+
with shell.add_workspace("reports", text="Reports", icon="bar-chart") as ws:
|
|
447
|
+
with ws.page_nav() as nav:
|
|
448
|
+
with nav.add_page("monthly", text="Monthly", icon="calendar3",
|
|
449
|
+
padding=20, gap=12, horizontal_items="stretch"):
|
|
450
|
+
build_reports()
|
|
451
|
+
|
|
452
|
+
with shell.add_workspace("settings", text="Settings", icon="gear", pin_to_footer=True) as ws:
|
|
453
|
+
with ws.page_nav() as nav:
|
|
454
|
+
with nav.add_page("general", text="General", icon="sliders",
|
|
455
|
+
padding=20, gap=12, horizontal_items="stretch"):
|
|
456
|
+
build_settings()
|
|
457
|
+
|
|
458
|
+
shell.run()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
if __name__ == "__main__":
|
|
462
|
+
main()
|
|
463
|
+
'''
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
APPSHELL_HOME_PAGE_TEMPLATE = '''\
|
|
467
|
+
"""Home page."""
|
|
468
|
+
|
|
469
|
+
import bootstack as bs
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def build_home():
|
|
473
|
+
"""Build the Home page.
|
|
474
|
+
|
|
475
|
+
A builder function: it paints into the active container. The page region
|
|
476
|
+
(configured on `nav.add_page(...)` in main.py) supplies the padding and gap,
|
|
477
|
+
so there is no extra layout wrapper here.
|
|
478
|
+
"""
|
|
479
|
+
bs.Label("Welcome to {app_name}", font="heading-xl")
|
|
480
|
+
bs.Label(
|
|
481
|
+
"This is your home page. Edit this file to get started.",
|
|
482
|
+
wrap_width=500,
|
|
483
|
+
)
|
|
484
|
+
with bs.GroupBox("Getting Started", grow=True, horizontal="stretch"):
|
|
485
|
+
bs.Label(
|
|
486
|
+
"Add your widgets here.\\n\\n"
|
|
487
|
+
"To add another page:\\n"
|
|
488
|
+
" 1. Run \\'bootstack add page <Name>\\' to generate the file.\\n"
|
|
489
|
+
" 2. In main.py, add a \\'with nav.add_page(...):\\' block inside\\n"
|
|
490
|
+
" your \\'shell.page_nav()\\' and call the page's build_* function in it."
|
|
491
|
+
)
|
|
492
|
+
'''
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
APPSHELL_SETTINGS_PAGE_TEMPLATE = '''\
|
|
496
|
+
"""Settings page."""
|
|
497
|
+
|
|
498
|
+
import bootstack as bs
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def build_settings():
|
|
502
|
+
"""Build the Settings page."""
|
|
503
|
+
bs.Label("Settings", font="heading-xl")
|
|
504
|
+
bs.Label("Configure your application preferences.", wrap_width=500)
|
|
505
|
+
with bs.GroupBox("Preferences", grow=True, horizontal="stretch"):
|
|
506
|
+
with bs.Row(gap=8):
|
|
507
|
+
bs.Label("Theme:")
|
|
508
|
+
bs.ThemeToggle()
|
|
509
|
+
'''
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
APPSHELL_PAGE_TEMPLATE = '''\
|
|
513
|
+
"""{page_title} page."""
|
|
514
|
+
|
|
515
|
+
import bootstack as bs
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def {func_name}():
|
|
519
|
+
"""Build the {page_title} page."""
|
|
520
|
+
bs.Label("{page_title}", font="heading-lg")
|
|
521
|
+
with bs.GroupBox("Content", grow=True, horizontal="stretch"):
|
|
522
|
+
pass # Add your widgets here
|
|
523
|
+
'''
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
APPSHELL_CONTENT_PAGE_TEMPLATE = '''\
|
|
527
|
+
"""{page_title} page."""
|
|
528
|
+
|
|
529
|
+
import bootstack as bs
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def {func_name}():
|
|
533
|
+
"""Build the {page_title} page."""
|
|
534
|
+
bs.Label("{page_title}", font="heading-lg")
|
|
535
|
+
with bs.GroupBox("Content", grow=True, horizontal="stretch"):
|
|
536
|
+
bs.Label("Add your widgets here.")
|
|
537
|
+
'''
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
APPSHELL_DETAIL_VIEW_TEMPLATE = '''\
|
|
541
|
+
"""Detail view for the record selected in the sidebar list."""
|
|
542
|
+
|
|
543
|
+
import bootstack as bs
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def build_detail(record):
|
|
547
|
+
"""Build the detail view for the selected record.
|
|
548
|
+
|
|
549
|
+
The detail region is a plain fill area, so this builder opens its own
|
|
550
|
+
padded `Column` to frame the content.
|
|
551
|
+
"""
|
|
552
|
+
with bs.Column(padding=20, gap=12, horizontal_items="stretch"):
|
|
553
|
+
bs.Label(record["title"], font="heading-lg")
|
|
554
|
+
bs.Label(record.get("text", ""), accent="secondary")
|
|
555
|
+
bs.Divider(horizontal="stretch")
|
|
556
|
+
bs.Label("Detail content for this record goes here.")
|
|
557
|
+
'''
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
APPSHELL_README_TEMPLATE = '''\
|
|
561
|
+
# {app_name}
|
|
562
|
+
|
|
563
|
+
A bootstack application using AppShell navigation.
|
|
564
|
+
|
|
565
|
+
## Getting Started
|
|
566
|
+
|
|
567
|
+
### Development
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
# Run the application
|
|
571
|
+
python -m {module_name}
|
|
572
|
+
|
|
573
|
+
# Or use the CLI
|
|
574
|
+
bootstack run
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Adding Pages
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
# Scaffold a new page
|
|
581
|
+
bootstack add page DashboardPage
|
|
582
|
+
|
|
583
|
+
# Then wire it up in main.py, inside your shell.page_nav() block:
|
|
584
|
+
# from {module_name}.pages.dashboard_page import build_dashboard
|
|
585
|
+
# with nav.add_page("dashboard", text="Dashboard", icon="speedometer2",
|
|
586
|
+
# padding=20, gap=12, horizontal_items="stretch"):
|
|
587
|
+
# build_dashboard()
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Building for Distribution
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
# Promote to packaging-ready (adds PyInstaller support)
|
|
594
|
+
bootstack promote --pyinstaller
|
|
595
|
+
|
|
596
|
+
# Build the executable
|
|
597
|
+
bootstack build
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## Project Structure
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
{project_dir}/
|
|
604
|
+
\u251c\u2500\u2500 src/{module_name}/
|
|
605
|
+
\u2502 \u251c\u2500\u2500 __init__.py
|
|
606
|
+
\u2502 \u251c\u2500\u2500 main.py
|
|
607
|
+
\u2502 \u2514\u2500\u2500 pages/
|
|
608
|
+
\u2502 \u251c\u2500\u2500 __init__.py
|
|
609
|
+
\u2502 \u251c\u2500\u2500 home_page.py
|
|
610
|
+
\u2502 \u2514\u2500\u2500 settings_page.py
|
|
611
|
+
\u251c\u2500\u2500 assets/
|
|
612
|
+
\u251c\u2500\u2500 bootstack.toml
|
|
613
|
+
\u2514\u2500\u2500 README.md
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## Configuration
|
|
617
|
+
|
|
618
|
+
Application settings are defined in `bootstack.toml`:
|
|
619
|
+
|
|
620
|
+
- `[app]` - Application metadata
|
|
621
|
+
- `[layout]` - Default layout preferences
|
|
622
|
+
- `[build]` - Build/packaging configuration (after `bootstack promote`)
|
|
623
|
+
'''
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def create_page(
|
|
627
|
+
class_name: str,
|
|
628
|
+
target_dir: Path,
|
|
629
|
+
scrollable: bool = False,
|
|
630
|
+
) -> Path:
|
|
631
|
+
"""Create a new AppShell page file.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
class_name: Page class name (CamelCase).
|
|
635
|
+
target_dir: Directory to create the page in.
|
|
636
|
+
scrollable: If True, the page template notes that
|
|
637
|
+
`scrollable=True` should be passed to `add_page()`.
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
Path to the created file.
|
|
641
|
+
"""
|
|
642
|
+
file_name = _camel_to_snake(class_name) + ".py"
|
|
643
|
+
|
|
644
|
+
content = APPSHELL_PAGE_TEMPLATE.format(
|
|
645
|
+
func_name=_build_func_name(class_name),
|
|
646
|
+
page_title=_readable_title(class_name),
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
file_path = target_dir / file_name
|
|
650
|
+
file_path.write_text(content, encoding="utf-8")
|
|
651
|
+
|
|
652
|
+
return file_path
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def create_project(
|
|
656
|
+
name: str,
|
|
657
|
+
target_dir: Path,
|
|
658
|
+
container: ContainerType = "grid",
|
|
659
|
+
theme: str = "bootstrap-light",
|
|
660
|
+
template: TemplateType = "basic",
|
|
661
|
+
simple: bool = False,
|
|
662
|
+
nav: NavStyle = "single",
|
|
663
|
+
) -> None:
|
|
664
|
+
"""Create a new bootstack project.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
name: Application name.
|
|
668
|
+
target_dir: Target directory for the project.
|
|
669
|
+
container: Default container type ('grid' or 'pack').
|
|
670
|
+
theme: Theme name for the application.
|
|
671
|
+
template: Project template ('basic' or 'appshell').
|
|
672
|
+
simple: If True, create minimal project without build config.
|
|
673
|
+
nav: Navigation style for the appshell template ('single', 'grouped',
|
|
674
|
+
'master-detail', or 'workspaces'). Ignored for the basic template.
|
|
675
|
+
"""
|
|
676
|
+
if template == "appshell":
|
|
677
|
+
_create_appshell_project(name, target_dir, theme, simple, nav)
|
|
678
|
+
else:
|
|
679
|
+
_create_basic_project(name, target_dir, container, theme, simple)
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _create_basic_project(
|
|
683
|
+
name: str,
|
|
684
|
+
target_dir: Path,
|
|
685
|
+
container: ContainerType,
|
|
686
|
+
theme: str,
|
|
687
|
+
simple: bool,
|
|
688
|
+
) -> None:
|
|
689
|
+
"""Create a basic single-view project."""
|
|
690
|
+
from bootstack.cli.config import write_config
|
|
691
|
+
|
|
692
|
+
# Normalize names
|
|
693
|
+
name_lower = name.lower().replace(" ", "_").replace("-", "_")
|
|
694
|
+
module_name = name_lower
|
|
695
|
+
|
|
696
|
+
# Create directory structure
|
|
697
|
+
src_dir = target_dir / "src" / module_name
|
|
698
|
+
views_dir = src_dir / "views"
|
|
699
|
+
assets_dir = target_dir / "assets"
|
|
700
|
+
|
|
701
|
+
src_dir.mkdir(parents=True, exist_ok=True)
|
|
702
|
+
views_dir.mkdir(parents=True, exist_ok=True)
|
|
703
|
+
if not simple:
|
|
704
|
+
assets_dir.mkdir(parents=True, exist_ok=True)
|
|
705
|
+
|
|
706
|
+
# Write main.py
|
|
707
|
+
main_content = MAIN_PY_TEMPLATE.format(
|
|
708
|
+
app_name=name,
|
|
709
|
+
module_name=module_name,
|
|
710
|
+
theme=theme,
|
|
711
|
+
)
|
|
712
|
+
(src_dir / "main.py").write_text(main_content, encoding="utf-8")
|
|
713
|
+
|
|
714
|
+
# Write __init__.py
|
|
715
|
+
init_content = INIT_PY_TEMPLATE.format(app_name=name)
|
|
716
|
+
(src_dir / "__init__.py").write_text(init_content, encoding="utf-8")
|
|
717
|
+
|
|
718
|
+
# Write views/__init__.py
|
|
719
|
+
(views_dir / "__init__.py").write_text(
|
|
720
|
+
'"""Views package."""\n', encoding="utf-8"
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
# Write main_view.py
|
|
724
|
+
if container == "grid":
|
|
725
|
+
view_template = MAIN_VIEW_GRID_TEMPLATE
|
|
726
|
+
else:
|
|
727
|
+
view_template = MAIN_VIEW_PACK_TEMPLATE
|
|
728
|
+
|
|
729
|
+
view_content = view_template.format(app_name=name)
|
|
730
|
+
(views_dir / "main_view.py").write_text(view_content, encoding="utf-8")
|
|
731
|
+
|
|
732
|
+
# Write bootstack.toml
|
|
733
|
+
write_config(
|
|
734
|
+
path=target_dir / "bootstack.toml",
|
|
735
|
+
name=name,
|
|
736
|
+
entry=f"src/{module_name}/main.py",
|
|
737
|
+
theme=theme,
|
|
738
|
+
template="basic",
|
|
739
|
+
include_build=False,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# Write README.md
|
|
743
|
+
if not simple:
|
|
744
|
+
readme_content = README_TEMPLATE.format(
|
|
745
|
+
app_name=name,
|
|
746
|
+
module_name=module_name,
|
|
747
|
+
project_dir=target_dir.name,
|
|
748
|
+
)
|
|
749
|
+
(target_dir / "README.md").write_text(readme_content, encoding="utf-8")
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
_APPSHELL_MAIN_TEMPLATES = {
|
|
753
|
+
"single": APPSHELL_MAIN_SINGLE_TEMPLATE,
|
|
754
|
+
"grouped": APPSHELL_MAIN_GROUPED_TEMPLATE,
|
|
755
|
+
"master-detail": APPSHELL_MAIN_MASTER_DETAIL_TEMPLATE,
|
|
756
|
+
"workspaces": APPSHELL_MAIN_WORKSPACES_TEMPLATE,
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _create_appshell_project(
|
|
761
|
+
name: str,
|
|
762
|
+
target_dir: Path,
|
|
763
|
+
theme: str,
|
|
764
|
+
simple: bool,
|
|
765
|
+
nav: NavStyle = "single",
|
|
766
|
+
) -> None:
|
|
767
|
+
"""Create an AppShell project with the chosen navigation style."""
|
|
768
|
+
from bootstack.cli.config import write_config
|
|
769
|
+
|
|
770
|
+
# Normalize names
|
|
771
|
+
name_lower = name.lower().replace(" ", "_").replace("-", "_")
|
|
772
|
+
module_name = name_lower
|
|
773
|
+
|
|
774
|
+
# Create directory structure
|
|
775
|
+
src_dir = target_dir / "src" / module_name
|
|
776
|
+
pages_dir = src_dir / "pages"
|
|
777
|
+
assets_dir = target_dir / "assets"
|
|
778
|
+
|
|
779
|
+
src_dir.mkdir(parents=True, exist_ok=True)
|
|
780
|
+
pages_dir.mkdir(parents=True, exist_ok=True)
|
|
781
|
+
if not simple:
|
|
782
|
+
assets_dir.mkdir(parents=True, exist_ok=True)
|
|
783
|
+
|
|
784
|
+
# Write main.py for the chosen navigation style
|
|
785
|
+
main_template = _APPSHELL_MAIN_TEMPLATES.get(nav, APPSHELL_MAIN_SINGLE_TEMPLATE)
|
|
786
|
+
main_content = main_template.format(
|
|
787
|
+
app_name=name,
|
|
788
|
+
module_name=module_name,
|
|
789
|
+
theme=theme,
|
|
790
|
+
chrome=_appshell_chrome(name),
|
|
791
|
+
)
|
|
792
|
+
(src_dir / "main.py").write_text(main_content, encoding="utf-8")
|
|
793
|
+
|
|
794
|
+
# Write __init__.py
|
|
795
|
+
init_content = INIT_PY_TEMPLATE.format(app_name=name)
|
|
796
|
+
(src_dir / "__init__.py").write_text(init_content, encoding="utf-8")
|
|
797
|
+
|
|
798
|
+
# Write pages/__init__.py
|
|
799
|
+
(pages_dir / "__init__.py").write_text(
|
|
800
|
+
'"""Pages package."""\n', encoding="utf-8"
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
def _write_content_page(class_name: str, title: str) -> None:
|
|
804
|
+
content = APPSHELL_CONTENT_PAGE_TEMPLATE.format(
|
|
805
|
+
func_name=_build_func_name(class_name), page_title=title
|
|
806
|
+
)
|
|
807
|
+
(pages_dir / f"{_camel_to_snake(class_name)}.py").write_text(
|
|
808
|
+
content, encoding="utf-8"
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
# Write the page files the chosen nav style imports
|
|
812
|
+
if nav == "master-detail":
|
|
813
|
+
(pages_dir / "detail_view.py").write_text(
|
|
814
|
+
APPSHELL_DETAIL_VIEW_TEMPLATE, encoding="utf-8"
|
|
815
|
+
)
|
|
816
|
+
else:
|
|
817
|
+
(pages_dir / "home_page.py").write_text(
|
|
818
|
+
APPSHELL_HOME_PAGE_TEMPLATE.format(app_name=name), encoding="utf-8"
|
|
819
|
+
)
|
|
820
|
+
(pages_dir / "settings_page.py").write_text(
|
|
821
|
+
APPSHELL_SETTINGS_PAGE_TEMPLATE, encoding="utf-8"
|
|
822
|
+
)
|
|
823
|
+
if nav in ("grouped", "workspaces"):
|
|
824
|
+
_write_content_page("ReportsPage", "Reports")
|
|
825
|
+
if nav == "grouped":
|
|
826
|
+
_write_content_page("ProfilePage", "Profile")
|
|
827
|
+
|
|
828
|
+
# Write bootstack.toml
|
|
829
|
+
write_config(
|
|
830
|
+
path=target_dir / "bootstack.toml",
|
|
831
|
+
name=name,
|
|
832
|
+
entry=f"src/{module_name}/main.py",
|
|
833
|
+
theme=theme,
|
|
834
|
+
template="appshell",
|
|
835
|
+
include_build=False,
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
# Write README.md
|
|
839
|
+
if not simple:
|
|
840
|
+
readme_content = APPSHELL_README_TEMPLATE.format(
|
|
841
|
+
app_name=name,
|
|
842
|
+
module_name=module_name,
|
|
843
|
+
project_dir=target_dir.name,
|
|
844
|
+
)
|
|
845
|
+
(target_dir / "README.md").write_text(readme_content, encoding="utf-8")
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
def create_view(
|
|
849
|
+
class_name: str,
|
|
850
|
+
target_dir: Path,
|
|
851
|
+
container: ContainerType = "grid",
|
|
852
|
+
) -> Path:
|
|
853
|
+
"""Create a new view file.
|
|
854
|
+
|
|
855
|
+
Args:
|
|
856
|
+
class_name: View class name (CamelCase).
|
|
857
|
+
target_dir: Directory to create the view in.
|
|
858
|
+
container: Container type ('grid' or 'pack').
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
Path to the created file.
|
|
862
|
+
"""
|
|
863
|
+
# Convert CamelCase to snake_case
|
|
864
|
+
file_name = _camel_to_snake(class_name) + ".py"
|
|
865
|
+
|
|
866
|
+
if container == "grid":
|
|
867
|
+
template = VIEW_GRID_TEMPLATE
|
|
868
|
+
else:
|
|
869
|
+
template = VIEW_PACK_TEMPLATE
|
|
870
|
+
|
|
871
|
+
content = template.format(
|
|
872
|
+
func_name=_build_func_name(class_name),
|
|
873
|
+
title=_readable_title(class_name),
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
file_path = target_dir / file_name
|
|
877
|
+
file_path.write_text(content, encoding="utf-8")
|
|
878
|
+
|
|
879
|
+
return file_path
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def create_dialog(
|
|
883
|
+
class_name: str,
|
|
884
|
+
target_dir: Path,
|
|
885
|
+
) -> Path:
|
|
886
|
+
"""Create a new dialog file.
|
|
887
|
+
|
|
888
|
+
Args:
|
|
889
|
+
class_name: Dialog class name (CamelCase).
|
|
890
|
+
target_dir: Directory to create the dialog in.
|
|
891
|
+
|
|
892
|
+
Returns:
|
|
893
|
+
Path to the created file.
|
|
894
|
+
"""
|
|
895
|
+
file_name = _camel_to_snake(class_name) + ".py"
|
|
896
|
+
content = DIALOG_TEMPLATE.format(class_name=class_name)
|
|
897
|
+
|
|
898
|
+
file_path = target_dir / file_name
|
|
899
|
+
file_path.write_text(content, encoding="utf-8")
|
|
900
|
+
|
|
901
|
+
return file_path
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def _camel_to_snake(name: str) -> str:
|
|
905
|
+
"""Convert CamelCase to snake_case."""
|
|
906
|
+
import re
|
|
907
|
+
|
|
908
|
+
# Insert underscore before uppercase letters and lowercase
|
|
909
|
+
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
910
|
+
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def _strip_kind_suffix(name: str) -> str:
|
|
914
|
+
"""Drop a trailing `Page`/`View` from a CamelCase name (keep if it's all there is)."""
|
|
915
|
+
for suffix in ("Page", "View"):
|
|
916
|
+
if name.endswith(suffix) and len(name) > len(suffix):
|
|
917
|
+
return name[: -len(suffix)]
|
|
918
|
+
return name
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def _build_func_name(class_name: str) -> str:
|
|
922
|
+
"""Derive a builder function name from a name (e.g. `DashboardPage` -> `build_dashboard`)."""
|
|
923
|
+
return "build_" + _camel_to_snake(_strip_kind_suffix(class_name))
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def _readable_title(class_name: str) -> str:
|
|
927
|
+
"""Derive a readable title from a name (e.g. `DashboardPage` -> `Dashboard`)."""
|
|
928
|
+
import re
|
|
929
|
+
|
|
930
|
+
base = _strip_kind_suffix(class_name)
|
|
931
|
+
return re.sub(r"([a-z])([A-Z])", r"\1 \2", base)
|