pythonnative 0.30.0__tar.gz → 0.32.0__tar.gz
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.
- {pythonnative-0.30.0/src/pythonnative.egg-info → pythonnative-0.32.0}/PKG-INFO +1 -1
- {pythonnative-0.30.0 → pythonnative-0.32.0}/pyproject.toml +1 -1
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/__init__.py +1 -1
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/cli/pn.py +95 -10
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/platform_metrics.py +8 -5
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/config.py +47 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/devices.py +14 -1
- {pythonnative-0.30.0 → pythonnative-0.32.0/src/pythonnative.egg-info}/PKG-INFO +1 -1
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_cli.py +300 -19
- {pythonnative-0.30.0 → pythonnative-0.32.0}/LICENSE +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/README.md +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/setup.cfg +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/_ios_log.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/alerts.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/animated.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/appearance.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/cli/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/diagnostics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/element.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/events.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/gestures.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/hot_reload.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/images.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/layout.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/mutations.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/app_state.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/battery.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/biometrics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/camera.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/clipboard.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/file_system.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/haptics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/linking.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/location.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/net_info.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/notifications.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/permissions.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/secure_store.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_modules/share.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/android.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/base.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/desktop.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/native_views/ios.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/navigation.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/net.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/platform.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/preview.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/android.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/builder.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/doctor.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/icons.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/ios.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/permissions.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/project/runtime_assets.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/reconciler.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/runtime.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/screen.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/sdk/__init__.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/sdk/_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/storage.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/style.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/suspense.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNFrameLayout.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradlew +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/BridgingHeader.h +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/utils.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative/virtual_rows.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/SOURCES.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/requires.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/src/pythonnative.egg-info/top_level.txt +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_alert.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_animated.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_animated_graph.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_appearance.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_async_hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_core_semantics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_desktop_backend.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_element.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_events.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_extended_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_gesture_composition.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_gestures.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_hot_reload.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_images.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_incremental_render.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_interaction_props.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_ios_log.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_layout.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_metric_hooks.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_mutations.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_native_lists.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_native_modules.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_native_views.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_navigation.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_net.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_new_components.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_platform.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_platform_metrics.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_reconciler.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_ref.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_runtime.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_screen.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_sdk.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_smoke.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_storage.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_style.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_suspense.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_transitions.py +0 -0
- {pythonnative-0.30.0 → pythonnative-0.32.0}/tests/test_utils.py +0 -0
|
@@ -9,7 +9,7 @@ The console script `pn` (declared in `pyproject.toml`) dispatches to:
|
|
|
9
9
|
- `pn preview [component]`: render the app in a desktop (Tkinter) window
|
|
10
10
|
with Fast Refresh, the fast inner dev loop, no device required.
|
|
11
11
|
- `pn devices [platform]`: list connected devices, emulators, and
|
|
12
|
-
simulators
|
|
12
|
+
simulators, as a table or as JSON with `--json`.
|
|
13
13
|
- `pn run android|ios [--device D]`: stage + build + install + launch on
|
|
14
14
|
a device, emulator, or simulator, with optional on-device hot reload.
|
|
15
15
|
- `pn logs android|ios`: stream logs from the running app without
|
|
@@ -38,7 +38,7 @@ import sys
|
|
|
38
38
|
import time
|
|
39
39
|
from importlib.metadata import version as pkg_version
|
|
40
40
|
from pathlib import Path
|
|
41
|
-
from typing import Any, Dict, List, Optional
|
|
41
|
+
from typing import Any, Dict, List, Optional, TextIO
|
|
42
42
|
|
|
43
43
|
from ..project import builder as builder_mod
|
|
44
44
|
from ..project import devices as devices_mod
|
|
@@ -98,6 +98,35 @@ def App():
|
|
|
98
98
|
_GITIGNORE = "# PythonNative\n__pycache__/\n*.pyc\n.venv/\nbuild/\n.DS_Store\n"
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
_NAME_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
|
|
102
|
+
"""Legal ``pn init`` project names, in the spirit of ``flutter create`` / ``cargo new``."""
|
|
103
|
+
|
|
104
|
+
_FALLBACK_NAME = "my_app"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _sanitize_name(name: str) -> str:
|
|
108
|
+
"""Return a legal project name derived from ``name``.
|
|
109
|
+
|
|
110
|
+
Lowercases, collapses each run of illegal characters to one
|
|
111
|
+
underscore, trims leading and trailing ``_`` and ``-``, and prefixes
|
|
112
|
+
a name that doesn't start with a letter. The result always matches
|
|
113
|
+
``_NAME_RE``, falling back to ``_FALLBACK_NAME`` when nothing usable
|
|
114
|
+
survives.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: The rejected name, which may be empty.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A name suitable for suggesting back to the user.
|
|
121
|
+
"""
|
|
122
|
+
slug = re.sub(r"[^a-z0-9_-]+", "_", name.lower()).strip("_-")
|
|
123
|
+
if not slug:
|
|
124
|
+
return _FALLBACK_NAME
|
|
125
|
+
if not slug[0].isascii() or not slug[0].isalpha():
|
|
126
|
+
slug = f"app_{slug}"
|
|
127
|
+
return slug
|
|
128
|
+
|
|
129
|
+
|
|
101
130
|
def _app_id_from_name(name: str) -> str:
|
|
102
131
|
slug = re.sub(r"[^a-z0-9_]", "", name.lower())
|
|
103
132
|
if not slug or not slug[0].isalpha():
|
|
@@ -113,9 +142,17 @@ def init_project(args: argparse.Namespace) -> None:
|
|
|
113
142
|
it. Either way it writes ``app/main.py``, ``pythonnative.toml``, and
|
|
114
143
|
``.gitignore``.
|
|
115
144
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
145
|
+
A name you pass has to match ``^[a-z][a-z0-9_-]*$``: lowercase letters,
|
|
146
|
+
digits, ``-``, and ``_``, starting with a letter. Anything else is
|
|
147
|
+
refused with a legal suggestion. That keeps the directory name and the
|
|
148
|
+
``name`` field in the generated config identical, in the same spirit as
|
|
149
|
+
``flutter create`` and ``cargo new``. The name taken from the current
|
|
150
|
+
directory when you pass none is used as-is, so an existing directory
|
|
151
|
+
with any name still works.
|
|
152
|
+
|
|
153
|
+
The name also has to be a single directory name, so the project always
|
|
154
|
+
lands inside the current directory. Anything that reads as a path, such
|
|
155
|
+
as ``a/b``, ``..``, or ``/tmp/app``, is refused, and so is a name that
|
|
119
156
|
resolves somewhere else, such as a symlink to another directory.
|
|
120
157
|
|
|
121
158
|
It won't scaffold into a target directory that already holds files, and it
|
|
@@ -137,6 +174,19 @@ def init_project(args: argparse.Namespace) -> None:
|
|
|
137
174
|
print(f"Refusing to treat a path as a project name: {name!r}. Use a single directory name like my_app.")
|
|
138
175
|
sys.exit(1)
|
|
139
176
|
|
|
177
|
+
# Charset check, still lexical, so it stays ahead of ``Path.cwd()`` below.
|
|
178
|
+
# ``is not None`` rather than truthiness: "" is invalid under the pattern,
|
|
179
|
+
# and falling through to the no-name path would silently scaffold here.
|
|
180
|
+
# ``fullmatch``, not ``match``: ``$`` also matches before a trailing
|
|
181
|
+
# newline, so ``match`` would accept "app\n" and create a directory
|
|
182
|
+
# whose name contains one.
|
|
183
|
+
if name is not None and not _NAME_RE.fullmatch(name):
|
|
184
|
+
print(
|
|
185
|
+
f"Invalid project name: {name!r}. Use lowercase letters, digits, '-', and '_', "
|
|
186
|
+
f"starting with a letter. Try: {_sanitize_name(name)}"
|
|
187
|
+
)
|
|
188
|
+
sys.exit(1)
|
|
189
|
+
|
|
140
190
|
cwd = Path.cwd()
|
|
141
191
|
target = cwd / name if name else cwd
|
|
142
192
|
project_name: str = name or cwd.name
|
|
@@ -298,18 +348,46 @@ def _preview_entry(project_dir: Path) -> str:
|
|
|
298
348
|
# ======================================================================
|
|
299
349
|
|
|
300
350
|
|
|
351
|
+
def _print_no_devices_hints(stream: TextIO) -> None:
|
|
352
|
+
"""Print the "no devices" guidance to ``stream``.
|
|
353
|
+
|
|
354
|
+
Shared by both output modes so the wording can't drift between the
|
|
355
|
+
table (which sends it to stdout) and ``--json`` (stderr).
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
stream: Where to write, ``sys.stdout`` or ``sys.stderr``.
|
|
359
|
+
"""
|
|
360
|
+
print("No devices found.", file=stream)
|
|
361
|
+
print("Android: start an emulator or connect a device with USB debugging enabled.", file=stream)
|
|
362
|
+
print("iOS: open Xcode once to install Simulators, or plug in a device.", file=stream)
|
|
363
|
+
|
|
364
|
+
|
|
301
365
|
def devices_command(args: argparse.Namespace) -> None:
|
|
302
366
|
"""List connected devices, emulators, and simulators.
|
|
303
367
|
|
|
368
|
+
Prints an aligned table and exits 1 when nothing is connected.
|
|
369
|
+
|
|
370
|
+
With ``--json``, stdout carries a JSON array and nothing else, one
|
|
371
|
+
object per device (see ``Device.to_dict``), so it stays parseable.
|
|
372
|
+
The "no devices" hints go to stderr instead, an empty result prints
|
|
373
|
+
``[]``, and the exit status is 0 either way, since "no devices" is a
|
|
374
|
+
valid answer for a script rather than a failure.
|
|
375
|
+
|
|
304
376
|
Args:
|
|
305
|
-
args: Parsed namespace with optional ``platform``.
|
|
377
|
+
args: Parsed namespace with optional ``platform`` and ``json``.
|
|
306
378
|
"""
|
|
307
379
|
platform: Optional[str] = getattr(args, "platform", None)
|
|
380
|
+
as_json: bool = getattr(args, "json", False)
|
|
308
381
|
devices = devices_mod.list_devices(platform)
|
|
382
|
+
|
|
383
|
+
if as_json:
|
|
384
|
+
if not devices:
|
|
385
|
+
_print_no_devices_hints(sys.stderr)
|
|
386
|
+
print(json.dumps([device.to_dict() for device in devices], indent=2))
|
|
387
|
+
return
|
|
388
|
+
|
|
309
389
|
if not devices:
|
|
310
|
-
|
|
311
|
-
print("Android: start an emulator or connect a device with USB debugging enabled.")
|
|
312
|
-
print("iOS: open Xcode once to install Simulators, or plug in a device.")
|
|
390
|
+
_print_no_devices_hints(sys.stdout)
|
|
313
391
|
sys.exit(1)
|
|
314
392
|
print(f" {'IDENTIFIER':<40} {'KIND':<10} {'STATE':<10} NAME")
|
|
315
393
|
for device in devices:
|
|
@@ -919,7 +997,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
919
997
|
subparsers = parser.add_subparsers()
|
|
920
998
|
|
|
921
999
|
parser_init = subparsers.add_parser("init", help="Scaffold a new project")
|
|
922
|
-
parser_init.add_argument(
|
|
1000
|
+
parser_init.add_argument(
|
|
1001
|
+
"name",
|
|
1002
|
+
nargs="?",
|
|
1003
|
+
help="Project name, matching ^[a-z][a-z0-9_-]*$; creates ./<name>/ (default: current directory)",
|
|
1004
|
+
)
|
|
923
1005
|
parser_init.add_argument("--force", action="store_true", help="Overwrite existing files or a non-empty directory")
|
|
924
1006
|
parser_init.set_defaults(func=init_project)
|
|
925
1007
|
|
|
@@ -943,6 +1025,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
943
1025
|
|
|
944
1026
|
parser_devices = subparsers.add_parser("devices", help="List devices, emulators, and simulators")
|
|
945
1027
|
parser_devices.add_argument("platform", nargs="?", choices=["android", "ios"], help="Restrict to a platform")
|
|
1028
|
+
parser_devices.add_argument(
|
|
1029
|
+
"--json", action="store_true", help="Print a JSON array to stdout for scripting (hints go to stderr)"
|
|
1030
|
+
)
|
|
946
1031
|
parser_devices.set_defaults(func=devices_command)
|
|
947
1032
|
|
|
948
1033
|
parser_run = subparsers.add_parser("run", help="Build, install, and launch on a device/simulator")
|
|
@@ -226,12 +226,15 @@ def reset_keyboard_height() -> None:
|
|
|
226
226
|
# that natural height. Forcing our own height threw off the pill
|
|
227
227
|
# geometry, so the Android handler defers entirely to the system.
|
|
228
228
|
|
|
229
|
-
#: UIKit HIG tab-bar content height in points. The total bar reaches
|
|
230
|
-
#: ``IOS_TAB_BAR_BASE_HEIGHT_PT + safe_area_insets.bottom`` so the
|
|
231
|
-
#: pill background can extend over the home indicator. Apple's HIG
|
|
232
|
-
#: places the tab bar flush with the screen edge and lets UIKit
|
|
233
|
-
#: render its own internal padding for the home indicator.
|
|
234
229
|
IOS_TAB_BAR_BASE_HEIGHT_PT: float = 49.0
|
|
230
|
+
"""UIKit HIG tab-bar content height in points.
|
|
231
|
+
|
|
232
|
+
The total bar reaches ``IOS_TAB_BAR_BASE_HEIGHT_PT +
|
|
233
|
+
safe_area_insets.bottom`` so the pill background can extend over the
|
|
234
|
+
home indicator. Apple's HIG places the tab bar flush with the screen
|
|
235
|
+
edge and lets UIKit render its own internal padding for the home
|
|
236
|
+
indicator.
|
|
237
|
+
"""
|
|
235
238
|
|
|
236
239
|
|
|
237
240
|
def ios_tab_bar_height() -> float:
|
|
@@ -586,9 +586,52 @@ def entrypoint_to_module(entry_point: str) -> str:
|
|
|
586
586
|
return normalized or "app.main"
|
|
587
587
|
|
|
588
588
|
|
|
589
|
+
# TOML v1.0.0 basic strings must escape the quotation mark, the backslash,
|
|
590
|
+
# and every control character except tab: U+0000-U+0008, U+000A-U+001F, and
|
|
591
|
+
# U+007F. Tab is legal raw, and U+000B has no compact escape, so anything
|
|
592
|
+
# without one falls through to \uXXXX.
|
|
593
|
+
_TOML_COMPACT_ESCAPES = {
|
|
594
|
+
"\\": "\\\\",
|
|
595
|
+
'"': '\\"',
|
|
596
|
+
"\b": "\\b",
|
|
597
|
+
"\f": "\\f",
|
|
598
|
+
"\n": "\\n",
|
|
599
|
+
"\r": "\\r",
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def _toml_escape(value: str) -> str:
|
|
604
|
+
"""Escape ``value`` for use inside a TOML basic string.
|
|
605
|
+
|
|
606
|
+
Applied at the render boundary rather than relying on the caller,
|
|
607
|
+
since this module is public API and reachable without ``pn init``'s
|
|
608
|
+
name validation in front of it.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
value: The raw string to embed between double quotes.
|
|
612
|
+
|
|
613
|
+
Returns:
|
|
614
|
+
The escaped text, without the surrounding quotes.
|
|
615
|
+
"""
|
|
616
|
+
out = []
|
|
617
|
+
for char in value:
|
|
618
|
+
escaped = _TOML_COMPACT_ESCAPES.get(char)
|
|
619
|
+
if escaped is not None:
|
|
620
|
+
out.append(escaped)
|
|
621
|
+
elif char != "\t" and (char < "\x20" or char == "\x7f"):
|
|
622
|
+
out.append(f"\\u{ord(char):04X}")
|
|
623
|
+
else:
|
|
624
|
+
out.append(char)
|
|
625
|
+
return "".join(out)
|
|
626
|
+
|
|
627
|
+
|
|
589
628
|
def render_default_toml(*, name: str, app_id: str, python_version: str = "3.11") -> str:
|
|
590
629
|
"""Render a starter ``pythonnative.toml`` for ``pn init``.
|
|
591
630
|
|
|
631
|
+
Every interpolated value is escaped for a TOML basic string, so a
|
|
632
|
+
name containing a quote, a backslash, or a control character still
|
|
633
|
+
produces a parseable file.
|
|
634
|
+
|
|
592
635
|
Args:
|
|
593
636
|
name: Project name.
|
|
594
637
|
app_id: Reverse-DNS app identifier.
|
|
@@ -599,6 +642,10 @@ def render_default_toml(*, name: str, app_id: str, python_version: str = "3.11")
|
|
|
599
642
|
for the optional tables.
|
|
600
643
|
"""
|
|
601
644
|
display = name.replace("_", " ").replace("-", " ").strip().title() or name
|
|
645
|
+
name = _toml_escape(name)
|
|
646
|
+
display = _toml_escape(display)
|
|
647
|
+
app_id = _toml_escape(app_id)
|
|
648
|
+
python_version = _toml_escape(python_version)
|
|
602
649
|
return f"""# PythonNative project configuration.
|
|
603
650
|
# Docs: https://pythonnative.com/guides/configuration/
|
|
604
651
|
|
|
@@ -18,7 +18,7 @@ import json
|
|
|
18
18
|
import re
|
|
19
19
|
import subprocess
|
|
20
20
|
import tempfile
|
|
21
|
-
from dataclasses import dataclass
|
|
21
|
+
from dataclasses import asdict, dataclass
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Any, Dict, List, Optional
|
|
24
24
|
|
|
@@ -64,6 +64,19 @@ class Device:
|
|
|
64
64
|
return self.state in ("booted", "shutdown")
|
|
65
65
|
return self.state in ("booted", "connected")
|
|
66
66
|
|
|
67
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
68
|
+
"""Return a JSON-serializable view of this target.
|
|
69
|
+
|
|
70
|
+
Derived from the dataclass fields so a field added later is
|
|
71
|
+
carried into the payload automatically, plus the computed
|
|
72
|
+
``is_ready`` that consumers would otherwise have to re-derive
|
|
73
|
+
from ``kind`` and ``state``.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
The declared fields in declaration order, then ``is_ready``.
|
|
77
|
+
"""
|
|
78
|
+
return {**asdict(self), "is_ready": self.is_ready}
|
|
79
|
+
|
|
67
80
|
def format(self) -> str:
|
|
68
81
|
"""Return one aligned listing row for the CLI."""
|
|
69
82
|
os_part = f" ({self.os_version})" if self.os_version else ""
|