fbuild 1.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.

Potentially problematic release.


This version of fbuild might be problematic. Click here for more details.

Files changed (93) hide show
  1. fbuild/__init__.py +0 -0
  2. fbuild/assets/example.txt +1 -0
  3. fbuild/build/__init__.py +117 -0
  4. fbuild/build/archive_creator.py +186 -0
  5. fbuild/build/binary_generator.py +444 -0
  6. fbuild/build/build_component_factory.py +131 -0
  7. fbuild/build/build_state.py +325 -0
  8. fbuild/build/build_utils.py +98 -0
  9. fbuild/build/compilation_executor.py +422 -0
  10. fbuild/build/compiler.py +165 -0
  11. fbuild/build/compiler_avr.py +574 -0
  12. fbuild/build/configurable_compiler.py +612 -0
  13. fbuild/build/configurable_linker.py +637 -0
  14. fbuild/build/flag_builder.py +186 -0
  15. fbuild/build/library_dependency_processor.py +185 -0
  16. fbuild/build/linker.py +708 -0
  17. fbuild/build/orchestrator.py +67 -0
  18. fbuild/build/orchestrator_avr.py +656 -0
  19. fbuild/build/orchestrator_esp32.py +797 -0
  20. fbuild/build/orchestrator_teensy.py +543 -0
  21. fbuild/build/source_compilation_orchestrator.py +220 -0
  22. fbuild/build/source_scanner.py +516 -0
  23. fbuild/cli.py +566 -0
  24. fbuild/cli_utils.py +312 -0
  25. fbuild/config/__init__.py +16 -0
  26. fbuild/config/board_config.py +457 -0
  27. fbuild/config/board_loader.py +92 -0
  28. fbuild/config/ini_parser.py +209 -0
  29. fbuild/config/mcu_specs.py +88 -0
  30. fbuild/daemon/__init__.py +34 -0
  31. fbuild/daemon/client.py +929 -0
  32. fbuild/daemon/compilation_queue.py +293 -0
  33. fbuild/daemon/daemon.py +474 -0
  34. fbuild/daemon/daemon_context.py +196 -0
  35. fbuild/daemon/error_collector.py +263 -0
  36. fbuild/daemon/file_cache.py +332 -0
  37. fbuild/daemon/lock_manager.py +270 -0
  38. fbuild/daemon/logging_utils.py +149 -0
  39. fbuild/daemon/messages.py +301 -0
  40. fbuild/daemon/operation_registry.py +288 -0
  41. fbuild/daemon/process_tracker.py +366 -0
  42. fbuild/daemon/processors/__init__.py +12 -0
  43. fbuild/daemon/processors/build_processor.py +157 -0
  44. fbuild/daemon/processors/deploy_processor.py +327 -0
  45. fbuild/daemon/processors/monitor_processor.py +146 -0
  46. fbuild/daemon/request_processor.py +401 -0
  47. fbuild/daemon/status_manager.py +216 -0
  48. fbuild/daemon/subprocess_manager.py +316 -0
  49. fbuild/deploy/__init__.py +17 -0
  50. fbuild/deploy/deployer.py +67 -0
  51. fbuild/deploy/deployer_esp32.py +314 -0
  52. fbuild/deploy/monitor.py +495 -0
  53. fbuild/interrupt_utils.py +34 -0
  54. fbuild/packages/__init__.py +53 -0
  55. fbuild/packages/archive_utils.py +1098 -0
  56. fbuild/packages/arduino_core.py +412 -0
  57. fbuild/packages/cache.py +249 -0
  58. fbuild/packages/downloader.py +366 -0
  59. fbuild/packages/framework_esp32.py +538 -0
  60. fbuild/packages/framework_teensy.py +346 -0
  61. fbuild/packages/github_utils.py +96 -0
  62. fbuild/packages/header_trampoline_cache.py +394 -0
  63. fbuild/packages/library_compiler.py +203 -0
  64. fbuild/packages/library_manager.py +549 -0
  65. fbuild/packages/library_manager_esp32.py +413 -0
  66. fbuild/packages/package.py +163 -0
  67. fbuild/packages/platform_esp32.py +383 -0
  68. fbuild/packages/platform_teensy.py +312 -0
  69. fbuild/packages/platform_utils.py +131 -0
  70. fbuild/packages/platformio_registry.py +325 -0
  71. fbuild/packages/sdk_utils.py +231 -0
  72. fbuild/packages/toolchain.py +436 -0
  73. fbuild/packages/toolchain_binaries.py +196 -0
  74. fbuild/packages/toolchain_esp32.py +484 -0
  75. fbuild/packages/toolchain_metadata.py +185 -0
  76. fbuild/packages/toolchain_teensy.py +404 -0
  77. fbuild/platform_configs/esp32.json +150 -0
  78. fbuild/platform_configs/esp32c2.json +144 -0
  79. fbuild/platform_configs/esp32c3.json +143 -0
  80. fbuild/platform_configs/esp32c5.json +151 -0
  81. fbuild/platform_configs/esp32c6.json +151 -0
  82. fbuild/platform_configs/esp32p4.json +149 -0
  83. fbuild/platform_configs/esp32s3.json +151 -0
  84. fbuild/platform_configs/imxrt1062.json +56 -0
  85. fbuild-1.1.0.dist-info/METADATA +447 -0
  86. fbuild-1.1.0.dist-info/RECORD +93 -0
  87. fbuild-1.1.0.dist-info/WHEEL +5 -0
  88. fbuild-1.1.0.dist-info/entry_points.txt +5 -0
  89. fbuild-1.1.0.dist-info/licenses/LICENSE +21 -0
  90. fbuild-1.1.0.dist-info/top_level.txt +2 -0
  91. fbuild_lint/__init__.py +0 -0
  92. fbuild_lint/ruff_plugins/__init__.py +0 -0
  93. fbuild_lint/ruff_plugins/keyboard_interrupt_checker.py +158 -0
@@ -0,0 +1,93 @@
1
+ fbuild/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ fbuild/cli.py,sha256=U74ITXQv2fCTU8FcARWEbuQ00DhOVxOgcuZNadJXKEg,17728
3
+ fbuild/cli_utils.py,sha256=yi2UJidfsBWmcBwv6IS_FcXA5810jnmDN23lVJi7OvY,9929
4
+ fbuild/interrupt_utils.py,sha256=JuEAgImyF17ZP03ijCTRCXcxcTPt6eLZUmzqueyOEOQ,1026
5
+ fbuild/assets/example.txt,sha256=lTBovRjiz0_TgtAtbA1C5hNi2ffbqnNPqkKg6UiKCT8,54
6
+ fbuild/build/__init__.py,sha256=NS6A-b-yPCVcjbE0Elkx3_Qf-_O6sdk_aXumSheYjOA,2871
7
+ fbuild/build/archive_creator.py,sha256=L5ZMLK44jZFhROOsyQdk-8nBKbDqDDRAxlM9gbplK7Y,6919
8
+ fbuild/build/binary_generator.py,sha256=wDGtcPuQlKfMuxjJZdZqRdqQCbg9cAao4zaXKi3qArg,16042
9
+ fbuild/build/build_component_factory.py,sha256=bUfGNOVrO823TdGpEjNPpPrsM3ZXvvgwGVODLUbCZ28,4237
10
+ fbuild/build/build_state.py,sha256=MtnCof6nOtKyAqW8OFWuMK7GXOIwmG1TeBJtXiRq18Y,11168
11
+ fbuild/build/build_utils.py,sha256=RJH76Lcintpl_7QkPG70cl6JPFJ-mOWnyskj73YC0Fc,2958
12
+ fbuild/build/compilation_executor.py,sha256=pzLeavweXkvaliLQEpwxjYvw1WMZcRzxdOJIX0kfezs,15748
13
+ fbuild/build/compiler.py,sha256=YMUUonS6eGbOPgf0Vo9BaG7ms3Kts2PalgCUhoIjCbY,4181
14
+ fbuild/build/compiler_avr.py,sha256=9KG2IfWEK9VGHbsWe2Y36rcFAkq1dMXo5PX5TPKI3D4,17604
15
+ fbuild/build/configurable_compiler.py,sha256=qeGEft8a3mVs5Cl5gmwhBfh8hQPfIu9qQnfWvbG2udA,21531
16
+ fbuild/build/configurable_linker.py,sha256=tK6jHEojVOz7zqiBOa-jQZCC730LgjeRZrGtLiWxwxM,24141
17
+ fbuild/build/flag_builder.py,sha256=-_oqrYem--DW2EL0UhRzhrxpKr4r3hiamvwFEqLra-0,6070
18
+ fbuild/build/library_dependency_processor.py,sha256=EoyOZJX4lVYCasXL6S5g3q86jlMlR9zyf-xfCu5vUyE,5860
19
+ fbuild/build/linker.py,sha256=4ooKcSqQDn96KLxHAiWNbR0vuhDnDOXFFEKqp5x9UJE,23831
20
+ fbuild/build/orchestrator.py,sha256=KvKfyJw-SZR_X7c-iE-hLj4KvFkO-VXkwDG0VeNyoms,1792
21
+ fbuild/build/orchestrator_avr.py,sha256=IQrzzrFn0aUnJ2xQmUi5dJdhq0HEMrviZIHdZvlII6s,24006
22
+ fbuild/build/orchestrator_esp32.py,sha256=AbL6jwkUEYCMB84tZGgSEzRUkyOKkGm8aGERmbXrKGk,27580
23
+ fbuild/build/orchestrator_teensy.py,sha256=q3G50svOvAufJHFaqboCnVd9qMN-O3V6v1H42ReTMzQ,18451
24
+ fbuild/build/source_compilation_orchestrator.py,sha256=MPg21vjcHa2NVvTNta08ZsXDIhEKfeoKrRCo4SUBiVw,7009
25
+ fbuild/build/source_scanner.py,sha256=sypaXJ7epC6APRosKG8xtTXGV-ngK2_3PdLCg-pnS4c,17422
26
+ fbuild/config/__init__.py,sha256=3QSJUBVhJabUl2g8dglAp58U8nH5fh2gHZWIoIxIl8g,403
27
+ fbuild/config/board_config.py,sha256=rDSTmUDKlxpPUMbxuN5oPuYK5o9XFNibh_48Mobkkx4,14090
28
+ fbuild/config/board_loader.py,sha256=X64LJSISfP2pDx9FrNG5tkXqQ2wDfnp5WxHmpkfLXIk,3528
29
+ fbuild/config/ini_parser.py,sha256=Ab783_LzZkGA3HFYM5vb0DEvdW2dvfDIYUUhuc_6qmo,6608
30
+ fbuild/config/mcu_specs.py,sha256=GZYl_86ykdYvuj5oLUoD5zN7hOUnhh7dK4X3q5AFk-E,2145
31
+ fbuild/daemon/__init__.py,sha256=pEwvWKbyueRxb51q4PskH7Li0Qg_CqYA4krYvPkJOlQ,722
32
+ fbuild/daemon/client.py,sha256=vB5i51vujSPYzXaowDy95ehQqC7z-TRMpukilB2InuE,30014
33
+ fbuild/daemon/compilation_queue.py,sha256=l7LUBy81l_OhxocA0TDJVPT4FyU0dkbLwEEvFRGIaIo,10405
34
+ fbuild/daemon/daemon.py,sha256=KDKXuwRtQVGbjBpmJeWM-qDh2kUV2ujSXBharoOPc4c,17290
35
+ fbuild/daemon/daemon_context.py,sha256=yFfGKc4o2SSiTbeYp-BWiRjSx-UBsc5NpqPetDZ5tdI,7131
36
+ fbuild/daemon/error_collector.py,sha256=pKO1PYw7zsMJgZLS_lXOY4mYTfywR92tlzTpwngLIG4,9223
37
+ fbuild/daemon/file_cache.py,sha256=PSzrgf8aR5OHtv74Yif2oxiSdJGoktW4Rv7256ZEQ2Q,11116
38
+ fbuild/daemon/lock_manager.py,sha256=YekUO-7YhwG_A8r2Q87FpvTWoGL-1x1js_XUEjAfzXs,10727
39
+ fbuild/daemon/logging_utils.py,sha256=0lshNSYoqFxgm8vJdfF3bv_YGw3xvQ7uKHxy3jq0lZ4,5233
40
+ fbuild/daemon/messages.py,sha256=zrGGNp8kQhPRdAhc9nm0zvqNiLUJcAKgERm55jcySJQ,11093
41
+ fbuild/daemon/operation_registry.py,sha256=pg_HCrG4vriebeFNB4Nq5WP_3hdO7-nweVMLQJu-dlw,11617
42
+ fbuild/daemon/process_tracker.py,sha256=cm0sngT6AP7spNCEmE2NJmcZcnYcYEhBds6yLsE8Ze0,12536
43
+ fbuild/daemon/request_processor.py,sha256=7FV_9SMeJS2l0PXJP3lb6SZdcVNl8PLt82wIlEGuYKQ,14916
44
+ fbuild/daemon/status_manager.py,sha256=OMlvMjTFOBuvSImYlu6e_s3ceYKqJ5o1lLiApr6yMCk,7883
45
+ fbuild/daemon/subprocess_manager.py,sha256=9LMQgK5ndSC-iCuuvmYmDpH6uE8LvKMPKGCHm4-2JVI,12557
46
+ fbuild/daemon/processors/__init__.py,sha256=716AnzwanhBrzGiTRlhAidZaIFpal7fVo3PDcBpxIU8,518
47
+ fbuild/daemon/processors/build_processor.py,sha256=PsdS-94auA35-6iRhxyV64-MQKBtpsUJjStGmmJ1QdE,6301
48
+ fbuild/daemon/processors/deploy_processor.py,sha256=nl5ntc1D-3Uu7Cfj-YnTBWlyMIHJD6yk-5Xz_3tNhLk,12728
49
+ fbuild/daemon/processors/monitor_processor.py,sha256=ywg96mUmfDHvr5tdSnR6cxtclXjaBMbKViHeHpZP8I4,5547
50
+ fbuild/deploy/__init__.py,sha256=sXJx5ebFGaBx0b9wdafqQjvFcsW9aI7zaWsR6zkl2oY,401
51
+ fbuild/deploy/deployer.py,sha256=WiC4hrtt-NzUAXzXd1CaSieqBOHX7nqCZrwtjp1NeDc,1578
52
+ fbuild/deploy/deployer_esp32.py,sha256=lnZQrEDn2y8QToLReqyGclAKcp0rrTNyVp62W2mgAqs,11327
53
+ fbuild/deploy/monitor.py,sha256=ZyWXG_mszohzBB0yADe_H-pTti6zDjihY3dOBeXBzfc,19233
54
+ fbuild/packages/__init__.py,sha256=JbdXK0Ssk5K91XA_ERFLEVdepmXzYRlCtnESaRTEjhg,1724
55
+ fbuild/packages/archive_utils.py,sha256=jjrfx6IZllIQ6mCshO_QA7oiLkVYtlHHn-S35rS4aBU,51925
56
+ fbuild/packages/arduino_core.py,sha256=Ym_EFfb__Tp4HAexzbkdCuqqhd9Uvw7pa3eWelbIyCg,13235
57
+ fbuild/packages/cache.py,sha256=S_NvKI2hZAN1qN5YJ1gwG_f8YUWJwGf1yRoxxuusyaM,8238
58
+ fbuild/packages/downloader.py,sha256=YEIW2TDj9zvOfBH3b22lrKjPq6umdU90x7VOBAXcW0w,12048
59
+ fbuild/packages/framework_esp32.py,sha256=2uS94oWbE2_vssTebMKz_9g-ZOHCFsYgo5PvfR4IpgQ,19612
60
+ fbuild/packages/framework_teensy.py,sha256=3jbcaN91t5vE4A1Pd9nTdx89dwcg2NahCkiwrY3EMwA,11352
61
+ fbuild/packages/github_utils.py,sha256=iLwLpWaXPhaadJwScE_sWcmlzA5r-e5TuZY7-Yet4nY,2857
62
+ fbuild/packages/header_trampoline_cache.py,sha256=fupEB5mE6_dytWswx3-LqogVnw7wq_YRWCfHsy-14XY,14305
63
+ fbuild/packages/library_compiler.py,sha256=Giavoet3ijrNyshZQGw0mHFr11JSiPDn8aQ3dd_jng4,7244
64
+ fbuild/packages/library_manager.py,sha256=7QIYu8WYUf4g-GGzwnubjcqKiS92GOjkL_X9Jvx4Mtw,17704
65
+ fbuild/packages/library_manager_esp32.py,sha256=2GB1Mr4u0vUgcP4txT_L7XotWwDaYiHz9G-4VWQZrLc,13775
66
+ fbuild/packages/package.py,sha256=scgvVHnaHm0gBV55DPbn0a8AcHLiBLKgBGFU--R6a6M,3894
67
+ fbuild/packages/platform_esp32.py,sha256=F1rbxnYwaxftHoSrarxj0p73XTrdL2IninsDWshS3zE,13820
68
+ fbuild/packages/platform_teensy.py,sha256=wUTT1Zr4Hg-FI1JtbKkxUkPKSQHhG4k7YtrdvLsNrZU,9730
69
+ fbuild/packages/platform_utils.py,sha256=nE2nXDI6LDFTDqdndV2e9lY103m72H5nT7nKerQM3JM,3978
70
+ fbuild/packages/platformio_registry.py,sha256=OGfI88SysnnJhif2ntSmNgtoCId-quR8iZR1JPWuVJ4,10389
71
+ fbuild/packages/sdk_utils.py,sha256=XbPFT2dOJ8z4rU6gIVjt0wwkPEvaSPFu__R9UTV0-fc,9042
72
+ fbuild/packages/toolchain.py,sha256=HxC8HbufztiA-glrEqbc029AZer3WE2jerBIgVX0ZfE,14274
73
+ fbuild/packages/toolchain_binaries.py,sha256=qJXgrSTN9CxGwFSGdtjs_2-BnxjP68OltAPmRTO-69Y,6917
74
+ fbuild/packages/toolchain_esp32.py,sha256=QQAfQCKSaAzQMeWdSBWE3_4HX3MGwT4bJNxdo2dLoiM,16734
75
+ fbuild/packages/toolchain_metadata.py,sha256=ws8bt5O_Ay8PwZDiN15BTaoKh2U77IlmhRyvA706h3s,6367
76
+ fbuild/packages/toolchain_teensy.py,sha256=5bM49z4nbgAplZxmk8nVcOB-p7yGMLOWgzAnkChrKus,13584
77
+ fbuild/platform_configs/esp32.json,sha256=bynMS4DnDfG9Ka_wZQO1io-QygtJwMohmEaK_jlrDBA,4532
78
+ fbuild/platform_configs/esp32c2.json,sha256=phr00CLtw7bCD0cW7iHTX2ShtfVVrWibBx7cAvcF3BY,4438
79
+ fbuild/platform_configs/esp32c3.json,sha256=GgLERfXXbvZGnauiCnaYhgugKv1B7DADWiI3i0z0WZk,4427
80
+ fbuild/platform_configs/esp32c5.json,sha256=07wPF69U8MfOLup8H5CcguyCBq0kwBeU81Ka9BDFSRw,4691
81
+ fbuild/platform_configs/esp32c6.json,sha256=zRYpaOJ414ja5wrFiNgUb8kvZxyQBtvmJRkg9mmOrAc,4691
82
+ fbuild/platform_configs/esp32p4.json,sha256=OniO6KxL8iK_7qS3jW18Y8iJSt693a06vNyDEfcYSk0,4602
83
+ fbuild/platform_configs/esp32s3.json,sha256=iKtW28vzYPbDXGhcb78gm9tqYQTBvxPFI5kTRMaUmd8,4611
84
+ fbuild/platform_configs/imxrt1062.json,sha256=_eW8sDCnx4T8Y5uEtQruKquIvo01U6WcrEzPpH8b-94,1047
85
+ fbuild-1.1.0.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
86
+ fbuild_lint/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
+ fbuild_lint/ruff_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
+ fbuild_lint/ruff_plugins/keyboard_interrupt_checker.py,sha256=zFxPRGx6M9tNEOl8o7eKEEPrU1ZytbqmwNiVyrhWgRk,6555
89
+ fbuild-1.1.0.dist-info/METADATA,sha256=G4jKhomRlSpafY3R09Y6tlZC0cM5WQ2BcO-xAcNIdF0,13203
90
+ fbuild-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
91
+ fbuild-1.1.0.dist-info/entry_points.txt,sha256=FoKJ3GeqwGCKFr7Zanl1f67qRYS_EQpA5s2GadeYwQU,146
92
+ fbuild-1.1.0.dist-info/top_level.txt,sha256=SP5cQb_9JGxIZk2wo2pH6RlSxePBRTEBPVvPti2ckhQ,19
93
+ fbuild-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ fbuild = fbuild.cli:main
3
+
4
+ [flake8.extension]
5
+ KBI = fbuild_lint.ruff_plugins.keyboard_interrupt_checker:KeyboardInterruptChecker
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 zackees
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ fbuild
2
+ fbuild_lint
File without changes
File without changes
@@ -0,0 +1,158 @@
1
+ """Flake8 plugin to check for proper KeyboardInterrupt handling.
2
+
3
+ This plugin ensures that try-except blocks that catch broad exceptions
4
+ (like Exception or BaseException) also properly handle KeyboardInterrupt.
5
+
6
+ Error Codes:
7
+ KBI001: Try-except catches Exception/BaseException without KeyboardInterrupt handler
8
+ KBI002: KeyboardInterrupt handler must call _thread.interrupt_main() or handle_keyboard_interrupt_properly()
9
+ """
10
+
11
+ import ast
12
+ from typing import Any, Generator, Tuple, Type
13
+
14
+
15
+ class KeyboardInterruptChecker:
16
+ """Flake8 plugin to check for proper KeyboardInterrupt handling."""
17
+
18
+ name = "keyboard-interrupt-checker"
19
+ version = "1.0.0"
20
+
21
+ def __init__(self, tree: ast.AST) -> None:
22
+ """Initialize the checker with an AST tree.
23
+
24
+ Args:
25
+ tree: The AST tree to check
26
+ """
27
+ self._tree = tree
28
+
29
+ def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:
30
+ """Run the checker on the AST tree.
31
+
32
+ Yields:
33
+ Tuple of (line_number, column, message, type)
34
+ """
35
+ visitor = TryExceptVisitor()
36
+ visitor.visit(self._tree)
37
+
38
+ for line, col, msg in visitor.errors:
39
+ yield (line, col, msg, type(self))
40
+
41
+
42
+ class TryExceptVisitor(ast.NodeVisitor):
43
+ """AST visitor to check try-except blocks for KeyboardInterrupt handling."""
44
+
45
+ def __init__(self) -> None:
46
+ """Initialize the visitor."""
47
+ self.errors: list[Tuple[int, int, str]] = []
48
+
49
+ def visit_Try(self, node: ast.Try) -> None:
50
+ """Visit a Try node and check exception handlers.
51
+
52
+ Args:
53
+ node: The Try node to check
54
+ """
55
+ # Check if any handler catches Exception or BaseException
56
+ catches_broad_exception = False
57
+ has_keyboard_interrupt_handler = False
58
+ keyboard_interrupt_handlers = []
59
+
60
+ for handler in node.handlers:
61
+ if handler.type is None:
62
+ # bare except: catches everything
63
+ catches_broad_exception = True
64
+ elif isinstance(handler.type, ast.Name):
65
+ if handler.type.id in ("Exception", "BaseException"):
66
+ catches_broad_exception = True
67
+ elif handler.type.id == "KeyboardInterrupt":
68
+ has_keyboard_interrupt_handler = True
69
+ keyboard_interrupt_handlers.append(handler)
70
+ elif isinstance(handler.type, ast.Tuple):
71
+ # Check if tuple contains Exception or BaseException
72
+ for exc_type in handler.type.elts:
73
+ if isinstance(exc_type, ast.Name):
74
+ if exc_type.id in ("Exception", "BaseException"):
75
+ catches_broad_exception = True
76
+ elif exc_type.id == "KeyboardInterrupt":
77
+ has_keyboard_interrupt_handler = True
78
+ keyboard_interrupt_handlers.append(handler)
79
+
80
+ # If we catch broad exceptions without KeyboardInterrupt handler, that's an error
81
+ if catches_broad_exception and not has_keyboard_interrupt_handler:
82
+ self.errors.append(
83
+ (
84
+ node.lineno,
85
+ node.col_offset,
86
+ (
87
+ "KBI001 Try-except catches Exception/BaseException without KeyboardInterrupt handler. "
88
+ "Add: except KeyboardInterrupt as ke: handle_keyboard_interrupt_properly(ke)"
89
+ ),
90
+ )
91
+ )
92
+
93
+ # Check all KeyboardInterrupt handlers to ensure they call _thread.interrupt_main()
94
+ for keyboard_interrupt_handler in keyboard_interrupt_handlers:
95
+ if not self._handler_calls_interrupt_main(keyboard_interrupt_handler):
96
+ self.errors.append(
97
+ (
98
+ keyboard_interrupt_handler.lineno,
99
+ keyboard_interrupt_handler.col_offset,
100
+ (
101
+ "KBI002 KeyboardInterrupt handler must call _thread.interrupt_main() "
102
+ "or use handle_keyboard_interrupt_properly(). "
103
+ "Add: import _thread; _thread.interrupt_main()"
104
+ ),
105
+ )
106
+ )
107
+
108
+ # Continue visiting child nodes
109
+ self.generic_visit(node)
110
+
111
+ def _handler_calls_interrupt_main(self, handler: ast.ExceptHandler) -> bool:
112
+ """Check if a KeyboardInterrupt handler properly calls _thread.interrupt_main().
113
+
114
+ Args:
115
+ handler: The exception handler to check
116
+
117
+ Returns:
118
+ True if the handler calls _thread.interrupt_main(), handle_keyboard_interrupt_properly(),
119
+ sys.exit(), ErrorFormatter.handle_keyboard_interrupt(), or re-raises the exception
120
+ """
121
+ # Check for re-raise (bare raise statement)
122
+ for node in ast.walk(handler):
123
+ if isinstance(node, ast.Raise):
124
+ # Bare raise (re-raise) is allowed
125
+ if node.exc is None:
126
+ return True
127
+
128
+ # Check for calls to _thread.interrupt_main(), handle_keyboard_interrupt_properly(), or sys.exit()
129
+ for node in ast.walk(handler):
130
+ if isinstance(node, ast.Call):
131
+ # Check for _thread.interrupt_main()
132
+ if isinstance(node.func, ast.Attribute):
133
+ if (
134
+ isinstance(node.func.value, ast.Name)
135
+ and node.func.value.id == "_thread"
136
+ and node.func.attr == "interrupt_main"
137
+ ):
138
+ return True
139
+ # Check for sys.exit()
140
+ if (
141
+ isinstance(node.func.value, ast.Name)
142
+ and node.func.value.id == "sys"
143
+ and node.func.attr == "exit"
144
+ ):
145
+ return True
146
+ # Check for ErrorFormatter.handle_keyboard_interrupt() or similar
147
+ if node.func.attr == "handle_keyboard_interrupt":
148
+ return True
149
+
150
+ # Check for handle_keyboard_interrupt_properly()
151
+ if isinstance(node.func, ast.Name):
152
+ if node.func.id == "handle_keyboard_interrupt_properly":
153
+ return True
154
+ # Check for handle_keyboard_interrupt()
155
+ if node.func.id == "handle_keyboard_interrupt":
156
+ return True
157
+
158
+ return False