pythonnative 0.29.0__tar.gz → 0.31.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.29.0/src/pythonnative.egg-info → pythonnative-0.31.0}/PKG-INFO +1 -1
- {pythonnative-0.29.0 → pythonnative-0.31.0}/pyproject.toml +1 -1
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/__init__.py +1 -1
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/cli/pn.py +96 -19
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/devices.py +14 -1
- {pythonnative-0.29.0 → pythonnative-0.31.0/src/pythonnative.egg-info}/PKG-INFO +1 -1
- pythonnative-0.31.0/tests/test_cli.py +608 -0
- pythonnative-0.29.0/tests/test_cli.py +0 -280
- {pythonnative-0.29.0 → pythonnative-0.31.0}/LICENSE +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/README.md +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/setup.cfg +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/_ios_log.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/alerts.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/animated.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/appearance.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/cli/__init__.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/components.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/diagnostics.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/element.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/events.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/gestures.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/hooks.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/hot_reload.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/images.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/layout.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/mutations.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/__init__.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/app_state.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/battery.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/biometrics.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/camera.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/clipboard.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/file_system.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/haptics.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/linking.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/location.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/net_info.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/notifications.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/permissions.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/secure_store.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_modules/share.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_views/__init__.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_views/android.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_views/base.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_views/desktop.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/native_views/ios.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/navigation.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/net.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/platform.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/platform_metrics.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/preview.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/__init__.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/android.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/builder.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/config.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/doctor.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/icons.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/ios.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/permissions.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/project/runtime_assets.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/reconciler.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/runtime.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/screen.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/sdk/__init__.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/sdk/_components.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/storage.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/style.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/suspense.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/build.gradle +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/proguard-rules.pro +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/androidTest/java/com/pythonnative/android_template/ExampleInstrumentedTest.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNAccessibilityDelegate.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNBorderDrawable.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNFrameLayout.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNVirtualListView.java +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable/ic_launcher_background.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/layout/activity_main.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/navigation/nav_graph.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values/colors.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values/strings.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values/themes.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/values-night/themes.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/backup_rules.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/main/res/xml/data_extraction_rules.xml +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/app/src/test/java/com/pythonnative/android_template/ExampleUnitTest.kt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/build.gradle +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.jar +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradle.properties +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradlew +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/gradlew.bat +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/android_template/settings.gradle +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/AppDelegate.swift +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Base.lproj/LaunchScreen.storyboard +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/BridgingHeader.h +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/Info.plist +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template/ViewController.swift +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/utils.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative/virtual_rows.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/SOURCES.txt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/dependency_links.txt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/entry_points.txt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/requires.txt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/src/pythonnative.egg-info/top_level.txt +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_alert.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_animated.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_animated_graph.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_appearance.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_async_hooks.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_components.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_core_semantics.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_desktop_backend.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_element.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_events.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_extended_components.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_gesture_composition.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_gestures.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_hooks.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_hot_reload.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_images.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_incremental_render.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_interaction_props.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_ios_log.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_layout.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_metric_hooks.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_mutations.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_native_lists.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_native_modules.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_native_views.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_navigation.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_net.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_new_components.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_platform.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_platform_metrics.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_reconciler.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_ref.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_runtime.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_screen.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_sdk.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_smoke.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_storage.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_style.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_suspense.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_transitions.py +0 -0
- {pythonnative-0.29.0 → pythonnative-0.31.0}/tests/test_utils.py +0 -0
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
The console script `pn` (declared in `pyproject.toml`) dispatches to:
|
|
4
4
|
|
|
5
|
-
- `pn init [name]`: scaffold a new project (``pythonnative.toml`` +
|
|
5
|
+
- `pn init [name]`: scaffold a new project (``pythonnative.toml`` +
|
|
6
|
+
``app/``) into ``./name/``, or into the current directory when no name
|
|
7
|
+
is given.
|
|
6
8
|
- `pn doctor [platform]`: diagnose the local toolchain and config.
|
|
7
9
|
- `pn preview [component]`: render the app in a desktop (Tkinter) window
|
|
8
10
|
with Fast Refresh, the fast inner dev loop, no device required.
|
|
9
11
|
- `pn devices [platform]`: list connected devices, emulators, and
|
|
10
|
-
simulators
|
|
12
|
+
simulators, as a table or as JSON with `--json`.
|
|
11
13
|
- `pn run android|ios [--device D]`: stage + build + install + launch on
|
|
12
14
|
a device, emulator, or simulator, with optional on-device hot reload.
|
|
13
15
|
- `pn logs android|ios`: stream logs from the running app without
|
|
@@ -36,7 +38,7 @@ import sys
|
|
|
36
38
|
import time
|
|
37
39
|
from importlib.metadata import version as pkg_version
|
|
38
40
|
from pathlib import Path
|
|
39
|
-
from typing import Any, Dict, List, Optional
|
|
41
|
+
from typing import Any, Dict, List, Optional, TextIO
|
|
40
42
|
|
|
41
43
|
from ..project import builder as builder_mod
|
|
42
44
|
from ..project import devices as devices_mod
|
|
@@ -104,21 +106,62 @@ def _app_id_from_name(name: str) -> str:
|
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
def init_project(args: argparse.Namespace) -> None:
|
|
107
|
-
"""Scaffold a new PythonNative project
|
|
109
|
+
"""Scaffold a new PythonNative project.
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
Given a name, this creates ``./<name>/`` and scaffolds into it. Without
|
|
112
|
+
one, it scaffolds into the current directory and names the project after
|
|
113
|
+
it. Either way it writes ``app/main.py``, ``pythonnative.toml``, and
|
|
114
|
+
``.gitignore``.
|
|
115
|
+
|
|
116
|
+
The name has to be a single directory name, so the project always lands
|
|
117
|
+
inside the current directory. Anything that reads as a path, such as
|
|
118
|
+
``a/b``, ``..``, or ``/tmp/app``, is refused, and so is a name that
|
|
119
|
+
resolves somewhere else, such as a symlink to another directory.
|
|
120
|
+
|
|
121
|
+
It won't scaffold into a target directory that already holds files, and it
|
|
122
|
+
won't overwrite any of the three paths above; pass ``--force`` to override
|
|
123
|
+
both. An existing but empty target directory is fine. A plain file at
|
|
124
|
+
``./<name>`` is always refused, since ``--force`` can't turn it into a
|
|
125
|
+
directory, and ``--force`` lifts neither of the rules above.
|
|
111
126
|
|
|
112
127
|
Args:
|
|
113
128
|
args: Parsed namespace with ``name`` (optional) and ``force``.
|
|
114
129
|
"""
|
|
115
|
-
|
|
116
|
-
project_name: str = getattr(args, "name", None) or cwd.name
|
|
130
|
+
name: Optional[str] = getattr(args, "name", None)
|
|
117
131
|
force: bool = getattr(args, "force", False)
|
|
118
132
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
# Lexical check, before anything reads the filesystem. ``Path("..").name``
|
|
134
|
+
# is "..", so ".." needs naming explicitly; the rest (absolute, nested,
|
|
135
|
+
# trailing separator, ".") fall out of the name check.
|
|
136
|
+
if name and (name in (os.curdir, os.pardir) or Path(name).name != name):
|
|
137
|
+
print(f"Refusing to treat a path as a project name: {name!r}. Use a single directory name like my_app.")
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
140
|
+
cwd = Path.cwd()
|
|
141
|
+
target = cwd / name if name else cwd
|
|
142
|
+
project_name: str = name or cwd.name
|
|
143
|
+
|
|
144
|
+
# A lexically clean name can still resolve elsewhere, and ``exists()`` and
|
|
145
|
+
# ``is_dir()`` below follow symlinks. Check containment rather than just
|
|
146
|
+
# ``is_symlink()`` so the whole class is closed, not one spelling of it.
|
|
147
|
+
if name and (target.is_symlink() or target.resolve().parent != cwd.resolve()):
|
|
148
|
+
print(
|
|
149
|
+
f"Refusing to scaffold through a link or outside the current directory: {name}. "
|
|
150
|
+
"Use a plain directory name."
|
|
151
|
+
)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
app_dir = target / "app"
|
|
155
|
+
config_path = target / CONFIG_FILENAME
|
|
156
|
+
gitignore_path = target / ".gitignore"
|
|
157
|
+
|
|
158
|
+
if name and target.exists():
|
|
159
|
+
if not target.is_dir():
|
|
160
|
+
print(f"Refusing to overwrite existing file: {name}. Remove it or choose a different name.")
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
if any(target.iterdir()) and not force:
|
|
163
|
+
print(f"Refusing to overwrite existing non-empty directory: {name}/. Use --force to overwrite.")
|
|
164
|
+
sys.exit(1)
|
|
122
165
|
|
|
123
166
|
if not force:
|
|
124
167
|
existing = [
|
|
@@ -142,8 +185,11 @@ def init_project(args: argparse.Namespace) -> None:
|
|
|
142
185
|
if force or not gitignore_path.exists():
|
|
143
186
|
gitignore_path.write_text(_GITIGNORE, encoding="utf-8")
|
|
144
187
|
|
|
145
|
-
print(f"Initialized PythonNative project in {
|
|
146
|
-
|
|
188
|
+
print(f"Initialized PythonNative project in {target}.")
|
|
189
|
+
next_steps = "pn preview (desktop) | pn run android | pn run ios"
|
|
190
|
+
if name:
|
|
191
|
+
next_steps = f"cd {name} | {next_steps}"
|
|
192
|
+
print(f"Next: {next_steps}")
|
|
147
193
|
|
|
148
194
|
|
|
149
195
|
# ======================================================================
|
|
@@ -252,18 +298,46 @@ def _preview_entry(project_dir: Path) -> str:
|
|
|
252
298
|
# ======================================================================
|
|
253
299
|
|
|
254
300
|
|
|
301
|
+
def _print_no_devices_hints(stream: TextIO) -> None:
|
|
302
|
+
"""Print the "no devices" guidance to ``stream``.
|
|
303
|
+
|
|
304
|
+
Shared by both output modes so the wording can't drift between the
|
|
305
|
+
table (which sends it to stdout) and ``--json`` (stderr).
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
stream: Where to write, ``sys.stdout`` or ``sys.stderr``.
|
|
309
|
+
"""
|
|
310
|
+
print("No devices found.", file=stream)
|
|
311
|
+
print("Android: start an emulator or connect a device with USB debugging enabled.", file=stream)
|
|
312
|
+
print("iOS: open Xcode once to install Simulators, or plug in a device.", file=stream)
|
|
313
|
+
|
|
314
|
+
|
|
255
315
|
def devices_command(args: argparse.Namespace) -> None:
|
|
256
316
|
"""List connected devices, emulators, and simulators.
|
|
257
317
|
|
|
318
|
+
Prints an aligned table and exits 1 when nothing is connected.
|
|
319
|
+
|
|
320
|
+
With ``--json``, stdout carries a JSON array and nothing else, one
|
|
321
|
+
object per device (see ``Device.to_dict``), so it stays parseable.
|
|
322
|
+
The "no devices" hints go to stderr instead, an empty result prints
|
|
323
|
+
``[]``, and the exit status is 0 either way, since "no devices" is a
|
|
324
|
+
valid answer for a script rather than a failure.
|
|
325
|
+
|
|
258
326
|
Args:
|
|
259
|
-
args: Parsed namespace with optional ``platform``.
|
|
327
|
+
args: Parsed namespace with optional ``platform`` and ``json``.
|
|
260
328
|
"""
|
|
261
329
|
platform: Optional[str] = getattr(args, "platform", None)
|
|
330
|
+
as_json: bool = getattr(args, "json", False)
|
|
262
331
|
devices = devices_mod.list_devices(platform)
|
|
332
|
+
|
|
333
|
+
if as_json:
|
|
334
|
+
if not devices:
|
|
335
|
+
_print_no_devices_hints(sys.stderr)
|
|
336
|
+
print(json.dumps([device.to_dict() for device in devices], indent=2))
|
|
337
|
+
return
|
|
338
|
+
|
|
263
339
|
if not devices:
|
|
264
|
-
|
|
265
|
-
print("Android: start an emulator or connect a device with USB debugging enabled.")
|
|
266
|
-
print("iOS: open Xcode once to install Simulators, or plug in a device.")
|
|
340
|
+
_print_no_devices_hints(sys.stdout)
|
|
267
341
|
sys.exit(1)
|
|
268
342
|
print(f" {'IDENTIFIER':<40} {'KIND':<10} {'STATE':<10} NAME")
|
|
269
343
|
for device in devices:
|
|
@@ -873,8 +947,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
873
947
|
subparsers = parser.add_subparsers()
|
|
874
948
|
|
|
875
949
|
parser_init = subparsers.add_parser("init", help="Scaffold a new project")
|
|
876
|
-
parser_init.add_argument("name", nargs="?", help="Project name (
|
|
877
|
-
parser_init.add_argument("--force", action="store_true", help="Overwrite existing files
|
|
950
|
+
parser_init.add_argument("name", nargs="?", help="Project name; creates ./<name>/ (default: current directory)")
|
|
951
|
+
parser_init.add_argument("--force", action="store_true", help="Overwrite existing files or a non-empty directory")
|
|
878
952
|
parser_init.set_defaults(func=init_project)
|
|
879
953
|
|
|
880
954
|
parser_doctor = subparsers.add_parser("doctor", help="Diagnose the local toolchain and config")
|
|
@@ -897,6 +971,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
897
971
|
|
|
898
972
|
parser_devices = subparsers.add_parser("devices", help="List devices, emulators, and simulators")
|
|
899
973
|
parser_devices.add_argument("platform", nargs="?", choices=["android", "ios"], help="Restrict to a platform")
|
|
974
|
+
parser_devices.add_argument(
|
|
975
|
+
"--json", action="store_true", help="Print a JSON array to stdout for scripting (hints go to stderr)"
|
|
976
|
+
)
|
|
900
977
|
parser_devices.set_defaults(func=devices_command)
|
|
901
978
|
|
|
902
979
|
parser_run = subparsers.add_parser("run", help="Build, install, and launch on a device/simulator")
|
|
@@ -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 ""
|