tina4-python 3.13.102__tar.gz → 3.13.103__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.
- {tina4_python-3.13.102 → tina4_python-3.13.103}/PKG-INFO +1 -1
- {tina4_python-3.13.102 → tina4_python-3.13.103}/pyproject.toml +1 -1
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/dev_admin/metrics.py +1 -1
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/frond/engine.py +167 -234
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/js/tina4-dev-admin.min.js +2 -2
- {tina4_python-3.13.102 → tina4_python-3.13.103}/.gitignore +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/README.md +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/ai/client.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/port_takeover.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/database_url.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/sql_translator.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/env.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/mqtt/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue/amqp_url.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/memcached_handler.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -12,7 +12,7 @@ class MetricsEngineError(RuntimeError):
|
|
|
12
12
|
|
|
13
13
|
_TIMEOUT_SECONDS = 60
|
|
14
14
|
_SUMMARY_KEYS = ("files_analyzed", "total_functions", "avg_complexity", "avg_maintainability")
|
|
15
|
-
_FILE_KEYS = ("path", "loc", "avg_complexity", "maintainability", "
|
|
15
|
+
_FILE_KEYS = ("path", "loc", "avg_complexity", "maintainability", "has_referencing_test")
|
|
16
16
|
_FUNCTION_KEYS = ("name", "file", "line", "complexity", "loc")
|
|
17
17
|
_INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli"
|
|
18
18
|
_last_scan_root = ""
|
|
@@ -643,86 +643,64 @@ def _expr_descriptor(expr: str, has_filters: bool):
|
|
|
643
643
|
operator" — fall through to the function-call / ``_resolve`` path, which stays
|
|
644
644
|
inline in :func:`_eval_expr` because its callable check is context-dependent.
|
|
645
645
|
"""
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
elif ch == "," and in_q is None and depth == 0:
|
|
670
|
-
items.append(current.strip())
|
|
671
|
-
current = ""
|
|
672
|
-
else:
|
|
673
|
-
current += ch
|
|
674
|
-
if current.strip():
|
|
646
|
+
for detector in _EXPRESSION_DETECTORS:
|
|
647
|
+
descriptor = detector(expr, has_filters)
|
|
648
|
+
if descriptor is not None:
|
|
649
|
+
return descriptor
|
|
650
|
+
return ("tail",)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _split_literal_items(inner: str, opening: str, closing: str) -> list[str]:
|
|
654
|
+
"""Split collection members without splitting nested collections."""
|
|
655
|
+
items = []
|
|
656
|
+
current = ""
|
|
657
|
+
in_q = None
|
|
658
|
+
depth = 0
|
|
659
|
+
for ch in inner:
|
|
660
|
+
if ch in ('"', "'") and in_q is None:
|
|
661
|
+
in_q = ch
|
|
662
|
+
elif ch == in_q:
|
|
663
|
+
in_q = None
|
|
664
|
+
elif ch in opening and in_q is None:
|
|
665
|
+
depth += 1
|
|
666
|
+
elif ch in closing and in_q is None:
|
|
667
|
+
depth -= 1
|
|
668
|
+
elif ch == "," and in_q is None and depth == 0:
|
|
675
669
|
items.append(current.strip())
|
|
676
|
-
|
|
670
|
+
current = ""
|
|
671
|
+
continue
|
|
672
|
+
current += ch
|
|
673
|
+
if current.strip():
|
|
674
|
+
items.append(current.strip())
|
|
675
|
+
return items
|
|
676
|
+
|
|
677
677
|
|
|
678
|
-
|
|
678
|
+
def _describe_collection(expr: str, _has_filters: bool):
|
|
679
|
+
"""Describe array and dictionary literals."""
|
|
680
|
+
if expr.startswith("[") and expr.endswith("]"):
|
|
681
|
+
inner = expr[1:-1].strip()
|
|
682
|
+
return ("array", _split_literal_items(inner, "[", "]") if inner else [])
|
|
679
683
|
if expr.startswith("{") and expr.endswith("}"):
|
|
680
684
|
inner = expr[1:-1].strip()
|
|
681
685
|
if not inner:
|
|
682
686
|
return ("dict", [])
|
|
683
|
-
|
|
684
|
-
pairs = []
|
|
685
|
-
current = ""
|
|
686
|
-
in_q = None
|
|
687
|
-
depth = 0
|
|
688
|
-
for ch in inner:
|
|
689
|
-
if ch in ('"', "'") and in_q is None:
|
|
690
|
-
in_q = ch
|
|
691
|
-
current += ch
|
|
692
|
-
elif ch == in_q:
|
|
693
|
-
in_q = None
|
|
694
|
-
current += ch
|
|
695
|
-
elif ch in ("{", "[") and in_q is None:
|
|
696
|
-
depth += 1
|
|
697
|
-
current += ch
|
|
698
|
-
elif ch in ("}", "]") and in_q is None:
|
|
699
|
-
depth -= 1
|
|
700
|
-
current += ch
|
|
701
|
-
elif ch == "," and in_q is None and depth == 0:
|
|
702
|
-
pairs.append(current.strip())
|
|
703
|
-
current = ""
|
|
704
|
-
else:
|
|
705
|
-
current += ch
|
|
706
|
-
if current.strip():
|
|
707
|
-
pairs.append(current.strip())
|
|
687
|
+
pairs = _split_literal_items(inner, "{[", "}]")
|
|
708
688
|
kv = []
|
|
709
689
|
for pair in pairs:
|
|
710
690
|
colon_pos = _find_outside_quotes(pair, ":")
|
|
711
691
|
if colon_pos > 0:
|
|
712
692
|
kv.append((pair[:colon_pos].strip(), pair[colon_pos + 1:].strip()))
|
|
713
693
|
return ("dict", kv)
|
|
694
|
+
return None
|
|
695
|
+
|
|
714
696
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
# expressions like 'yes' if x else 'no').
|
|
697
|
+
def _describe_primary(expr: str, _has_filters: bool):
|
|
698
|
+
"""Describe literals and a fully parenthesized sub-expression."""
|
|
718
699
|
if len(expr) >= 2:
|
|
719
700
|
q = expr[0]
|
|
720
701
|
if q in ('"', "'") and expr.endswith(q) and q not in expr[1:-1]:
|
|
721
702
|
return ("str", expr[1:-1])
|
|
722
|
-
|
|
723
|
-
# Parenthesized sub-expression: (expr) — strip parens and evaluate inner
|
|
724
703
|
if expr.startswith("(") and expr.endswith(")"):
|
|
725
|
-
# Verify matching parens (not just any parens)
|
|
726
704
|
depth = 0
|
|
727
705
|
matched = True
|
|
728
706
|
for i, ch in enumerate(expr):
|
|
@@ -735,8 +713,11 @@ def _expr_descriptor(expr: str, has_filters: bool):
|
|
|
735
713
|
break
|
|
736
714
|
if matched:
|
|
737
715
|
return ("paren", expr[1:-1])
|
|
716
|
+
return None
|
|
738
717
|
|
|
739
|
-
|
|
718
|
+
|
|
719
|
+
def _describe_conditional(expr: str, _has_filters: bool):
|
|
720
|
+
"""Describe ternary, inline-if, and null-coalescing expressions."""
|
|
740
721
|
q_pos = _find_outside_quotes(expr, "?")
|
|
741
722
|
if q_pos > 0:
|
|
742
723
|
rest = expr[q_pos + 1:]
|
|
@@ -744,66 +725,152 @@ def _expr_descriptor(expr: str, has_filters: bool):
|
|
|
744
725
|
if c_pos >= 0:
|
|
745
726
|
return ("ternary", expr[:q_pos].strip(),
|
|
746
727
|
rest[:c_pos].strip(), rest[c_pos + 1:].strip())
|
|
747
|
-
|
|
748
|
-
# Jinja2-style inline if: value if condition else other_value — quote-aware
|
|
749
728
|
if_pos = _find_outside_quotes(expr, " if ")
|
|
750
729
|
if if_pos >= 0:
|
|
751
730
|
else_pos = _find_outside_quotes(expr, " else ")
|
|
752
731
|
if else_pos > if_pos:
|
|
753
732
|
return ("inline_if", expr[:if_pos].strip(),
|
|
754
733
|
expr[if_pos + 4:else_pos].strip(), expr[else_pos + 6:].strip())
|
|
755
|
-
|
|
756
|
-
# Null coalescing: value ?? "default"
|
|
757
734
|
nc_pos = _find_outside_quotes(expr, "??")
|
|
758
735
|
if nc_pos >= 0:
|
|
759
736
|
return ("coalesce", expr[:nc_pos].strip(), expr[nc_pos + 2:].strip())
|
|
737
|
+
return None
|
|
760
738
|
|
|
761
|
-
|
|
739
|
+
|
|
740
|
+
def _describe_binary(expr: str, _has_filters: bool):
|
|
741
|
+
"""Describe concat, comparison, logical, and arithmetic expressions."""
|
|
762
742
|
tilde_pos = _find_outside_quotes(expr, "~")
|
|
763
743
|
if tilde_pos >= 0:
|
|
764
744
|
return ("concat", _split_outside_quotes(expr, "~"))
|
|
765
|
-
|
|
766
|
-
# Comparison / logical operators -> _eval_comparison, the SAME evaluator
|
|
767
|
-
# {% if %} uses, so a condition means the same thing in a condition and in
|
|
768
|
-
# an output expression.
|
|
769
|
-
#
|
|
770
|
-
# A LEADING unary ``not`` needs its own check: every operator in the tuple
|
|
771
|
-
# below is matched WITH surrounding spaces, so ``not x`` (nothing to its
|
|
772
|
-
# left) matched none of them, fell through to the ``_resolve`` tail, and was
|
|
773
|
-
# looked up as a variable literally named "not x" -- found nothing, rendered
|
|
774
|
-
# EMPTY. ``{% if not x %}`` and ``x and not y`` always worked; only the
|
|
775
|
-
# standalone ``{{ not x }}`` was dropped, and before booleans rendered
|
|
776
|
-
# lowercase a dropped expression and ``False -> ''`` looked identical, which
|
|
777
|
-
# is why it survived. Fixed in 3.13.87 alongside the boolean contract.
|
|
778
745
|
if expr.startswith("not "):
|
|
779
746
|
return ("comparison",)
|
|
780
747
|
for op in (" not in ", " in ", " is not ", " is ", "!=", "==", ">=", "<=", ">", "<", " and ", " or ", " not "):
|
|
781
748
|
if _find_outside_quotes(expr, op) >= 0:
|
|
782
749
|
return ("comparison",)
|
|
783
|
-
|
|
784
|
-
# Arithmetic operators: +, -, *, /, //, %, ** (lowest to highest precedence)
|
|
785
|
-
# Check for +/- first (lower precedence), then *//, then %, then **
|
|
786
750
|
for op in (" + ", " - ", " * ", " // ", " / ", " % ", " ** "):
|
|
787
751
|
pos = _find_outside_quotes(expr, op)
|
|
788
752
|
if pos >= 0:
|
|
789
753
|
return ("arith", op.strip(),
|
|
790
754
|
expr[:pos].strip(), expr[pos + len(op):].strip())
|
|
755
|
+
return None
|
|
791
756
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
# nesting depth (issue #171). Only fires when the engine threaded its bound
|
|
796
|
-
# applier in; the many internal callers pass None and skip it. A pipe inside
|
|
797
|
-
# quotes/parens is not top-level (``_parse_filter_chain`` respects both), so
|
|
798
|
-
# ``{{ (x|f) ~ y }}`` and ``{{ items|join(' ~ ') }}`` resolve correctly.
|
|
757
|
+
|
|
758
|
+
def _describe_filter(expr: str, has_filters: bool):
|
|
759
|
+
"""Describe a top-level Twig filter chain when filter support is active."""
|
|
799
760
|
if has_filters:
|
|
800
761
|
var_name, pipe_filters = _parse_filter_chain(expr)
|
|
801
762
|
if pipe_filters:
|
|
802
763
|
return ("pipe", var_name, pipe_filters)
|
|
764
|
+
return None
|
|
803
765
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
766
|
+
|
|
767
|
+
_EXPRESSION_DETECTORS = (
|
|
768
|
+
_describe_collection,
|
|
769
|
+
_describe_primary,
|
|
770
|
+
_describe_conditional,
|
|
771
|
+
_describe_binary,
|
|
772
|
+
_describe_filter,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def _eval_array(_expr, desc, context, apply_filters):
|
|
777
|
+
return [_eval_expr(item, context, apply_filters) for item in desc[1]]
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def _eval_dict(_expr, desc, context, apply_filters):
|
|
781
|
+
return {
|
|
782
|
+
_eval_expr(key, context, apply_filters): _eval_expr(value, context, apply_filters)
|
|
783
|
+
for key, value in desc[1]
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def _eval_primary(_expr, desc, context, apply_filters):
|
|
788
|
+
if desc[0] == "str":
|
|
789
|
+
return desc[1]
|
|
790
|
+
return _eval_expr(desc[1], context, apply_filters)
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def _eval_conditional(_expr, desc, context, apply_filters):
|
|
794
|
+
_, condition, when_true, when_false = desc
|
|
795
|
+
branch = when_true if _eval_expr(condition, context, apply_filters) else when_false
|
|
796
|
+
return _eval_expr(branch, context, apply_filters)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def _eval_inline_if(_expr, desc, context, apply_filters):
|
|
800
|
+
_, when_true, condition, when_false = desc
|
|
801
|
+
branch = when_true if _eval_expr(condition, context, apply_filters) else when_false
|
|
802
|
+
return _eval_expr(branch, context, apply_filters)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _eval_coalesce(_expr, desc, context, apply_filters):
|
|
806
|
+
value = _eval_expr(desc[1], context, apply_filters)
|
|
807
|
+
return _eval_expr(desc[2], context, apply_filters) if value is None else value
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def _eval_concat(_expr, desc, context, apply_filters):
|
|
811
|
+
return "".join(str(_eval_expr(part, context, apply_filters) or "") for part in desc[1])
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def _eval_arithmetic(_expr, desc, context, apply_filters):
|
|
815
|
+
operator, left, right = desc[1:]
|
|
816
|
+
try:
|
|
817
|
+
left_value = _eval_expr(left, context, apply_filters)
|
|
818
|
+
right_value = _eval_expr(right, context, apply_filters)
|
|
819
|
+
left_num = float(left_value) if left_value is not None else 0
|
|
820
|
+
right_num = float(right_value) if right_value is not None else 0
|
|
821
|
+
if left_num == int(left_num) and right_num == int(right_num) and operator != "/":
|
|
822
|
+
left_num, right_num = int(left_num), int(right_num)
|
|
823
|
+
operations = {
|
|
824
|
+
"+": lambda: left_num + right_num,
|
|
825
|
+
"-": lambda: left_num - right_num,
|
|
826
|
+
"*": lambda: left_num * right_num,
|
|
827
|
+
"//": lambda: left_num // right_num if right_num != 0 else 0,
|
|
828
|
+
"/": lambda: left_num / right_num if right_num != 0 else 0,
|
|
829
|
+
"%": lambda: left_num % right_num if right_num != 0 else 0,
|
|
830
|
+
"**": lambda: left_num ** right_num,
|
|
831
|
+
}
|
|
832
|
+
return operations[operator]()
|
|
833
|
+
except (ValueError, TypeError):
|
|
834
|
+
return None
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def _eval_pipe(_expr, desc, context, apply_filters):
|
|
838
|
+
base = _eval_expr(desc[1], context, apply_filters)
|
|
839
|
+
return apply_filters(base, desc[2], context)
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def _eval_function_call(expr, context, apply_filters):
|
|
843
|
+
match = _FUNC_CALL_RE.match(expr)
|
|
844
|
+
if not match:
|
|
845
|
+
return False, None
|
|
846
|
+
name, raw_args = match.group(1), match.group(2) or ""
|
|
847
|
+
if "." in name:
|
|
848
|
+
owner_name, member = name.rsplit(".", 1)
|
|
849
|
+
owner = _resolve(owner_name, context)
|
|
850
|
+
if isinstance(owner, dict):
|
|
851
|
+
function = owner.get(member)
|
|
852
|
+
else:
|
|
853
|
+
function = getattr(owner, member, None) if owner is not None else None
|
|
854
|
+
else:
|
|
855
|
+
function = context.get(name) or _resolve(name, context)
|
|
856
|
+
if not callable(function):
|
|
857
|
+
return False, None
|
|
858
|
+
args = [_eval_expr(arg, context, apply_filters) for arg in _split_args(raw_args)] if raw_args.strip() else []
|
|
859
|
+
return True, function(*args)
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
_EXPRESSION_EVALUATORS = {
|
|
863
|
+
"array": _eval_array,
|
|
864
|
+
"dict": _eval_dict,
|
|
865
|
+
"str": _eval_primary,
|
|
866
|
+
"paren": _eval_primary,
|
|
867
|
+
"ternary": _eval_conditional,
|
|
868
|
+
"inline_if": _eval_inline_if,
|
|
869
|
+
"coalesce": _eval_coalesce,
|
|
870
|
+
"concat": _eval_concat,
|
|
871
|
+
"arith": _eval_arithmetic,
|
|
872
|
+
"pipe": _eval_pipe,
|
|
873
|
+
}
|
|
807
874
|
|
|
808
875
|
|
|
809
876
|
def _eval_expr(expr: str, context: dict, apply_filters=None):
|
|
@@ -821,150 +888,16 @@ def _eval_expr(expr: str, context: dict, apply_filters=None):
|
|
|
821
888
|
"""
|
|
822
889
|
expr = expr.strip()
|
|
823
890
|
|
|
824
|
-
# Structural analysis (which top-level operator this expression splits on
|
|
825
|
-
# and where) is invariant across renders, so it is computed once and cached
|
|
826
|
-
# by _expr_descriptor keyed on (expr, has_filters). Only the value
|
|
827
|
-
# resolution below runs every call. The dispatch order below is IDENTICAL to
|
|
828
|
-
# the linear branch order it replaced — same precedence, same behaviour.
|
|
829
891
|
desc = _expr_descriptor(expr, apply_filters is not None)
|
|
830
892
|
kind = desc[0]
|
|
831
|
-
|
|
832
|
-
# Array literal: ["a", "b", "c"] or [1, 2, 3]
|
|
833
|
-
if kind == "array":
|
|
834
|
-
return [_eval_expr(item, context, apply_filters) for item in desc[1]]
|
|
835
|
-
|
|
836
|
-
# Dict literal: {"key": "value", ...}
|
|
837
|
-
if kind == "dict":
|
|
838
|
-
result = {}
|
|
839
|
-
for key_expr, val_expr in desc[1]:
|
|
840
|
-
key = _eval_expr(key_expr, context, apply_filters)
|
|
841
|
-
val = _eval_expr(val_expr, context, apply_filters)
|
|
842
|
-
result[key] = val
|
|
843
|
-
return result
|
|
844
|
-
|
|
845
|
-
# String literal
|
|
846
|
-
if kind == "str":
|
|
847
|
-
return desc[1]
|
|
848
|
-
|
|
849
|
-
# Parenthesized sub-expression: (expr) — evaluate inner
|
|
850
|
-
if kind == "paren":
|
|
851
|
-
return _eval_expr(desc[1], context, apply_filters)
|
|
852
|
-
|
|
853
|
-
# Ternary: condition ? "yes" : "no"
|
|
854
|
-
if kind == "ternary":
|
|
855
|
-
_, cond_part, true_part, false_part = desc
|
|
856
|
-
cond = _eval_expr(cond_part, context, apply_filters)
|
|
857
|
-
if cond:
|
|
858
|
-
return _eval_expr(true_part, context, apply_filters)
|
|
859
|
-
return _eval_expr(false_part, context, apply_filters)
|
|
860
|
-
|
|
861
|
-
# Jinja2-style inline if: value if condition else other_value
|
|
862
|
-
if kind == "inline_if":
|
|
863
|
-
_, value_part, cond_part, else_part = desc
|
|
864
|
-
cond = _eval_expr(cond_part, context, apply_filters)
|
|
865
|
-
if cond:
|
|
866
|
-
return _eval_expr(value_part, context, apply_filters)
|
|
867
|
-
return _eval_expr(else_part, context, apply_filters)
|
|
868
|
-
|
|
869
|
-
# Null coalescing: value ?? "default"
|
|
870
|
-
if kind == "coalesce":
|
|
871
|
-
_, left, right = desc
|
|
872
|
-
val = _eval_expr(left, context, apply_filters)
|
|
873
|
-
if val is None:
|
|
874
|
-
return _eval_expr(right, context, apply_filters)
|
|
875
|
-
return val
|
|
876
|
-
|
|
877
|
-
# String concatenation with ~
|
|
878
|
-
if kind == "concat":
|
|
879
|
-
return "".join(str(_eval_expr(p, context, apply_filters) or "") for p in desc[1])
|
|
880
|
-
|
|
881
|
-
# Comparison operators for if conditions
|
|
882
893
|
if kind == "comparison":
|
|
883
894
|
return _eval_comparison(expr, context)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
try:
|
|
891
|
-
l_num = float(l_val) if l_val is not None else 0
|
|
892
|
-
r_num = float(r_val) if r_val is not None else 0
|
|
893
|
-
# Preserve int type when both operands are int-like
|
|
894
|
-
if l_num == int(l_num) and r_num == int(r_num) and op_s not in ("/",):
|
|
895
|
-
l_num, r_num = int(l_num), int(r_num)
|
|
896
|
-
if op_s == "+":
|
|
897
|
-
return l_num + r_num
|
|
898
|
-
elif op_s == "-":
|
|
899
|
-
return l_num - r_num
|
|
900
|
-
elif op_s == "*":
|
|
901
|
-
return l_num * r_num
|
|
902
|
-
elif op_s == "//":
|
|
903
|
-
return l_num // r_num if r_num != 0 else 0
|
|
904
|
-
elif op_s == "/":
|
|
905
|
-
return l_num / r_num if r_num != 0 else 0
|
|
906
|
-
elif op_s == "%":
|
|
907
|
-
return l_num % r_num if r_num != 0 else 0
|
|
908
|
-
elif op_s == "**":
|
|
909
|
-
return l_num ** r_num
|
|
910
|
-
except (ValueError, TypeError):
|
|
911
|
-
return None
|
|
912
|
-
|
|
913
|
-
# Filter pipe (Twig ``|``): the highest-precedence binary operator, resolved
|
|
914
|
-
# only when the engine threaded its bound applier in (has_filters). See
|
|
915
|
-
# _expr_descriptor / issue #171 for the precedence rationale.
|
|
916
|
-
if kind == "pipe":
|
|
917
|
-
_, var_name, pipe_filters = desc
|
|
918
|
-
base = _eval_expr(var_name, context, apply_filters)
|
|
919
|
-
return apply_filters(base, pipe_filters, context)
|
|
920
|
-
|
|
921
|
-
# kind == "tail": no top-level operator — fall through to the
|
|
922
|
-
# function-call / _resolve path below.
|
|
923
|
-
|
|
924
|
-
# Function call: name("arg1", "arg2") or obj.method("arg1")
|
|
925
|
-
fn_match = _FUNC_CALL_RE.match(expr)
|
|
926
|
-
if fn_match:
|
|
927
|
-
fn_name = fn_match.group(1)
|
|
928
|
-
raw_args = fn_match.group(2) or ""
|
|
929
|
-
# For dotted names like obj.method, resolve the object then get the method
|
|
930
|
-
if "." in fn_name:
|
|
931
|
-
parts = fn_name.rsplit(".", 1)
|
|
932
|
-
obj = _resolve(parts[0], context)
|
|
933
|
-
if obj is None:
|
|
934
|
-
fn = None
|
|
935
|
-
elif isinstance(obj, dict):
|
|
936
|
-
fn = obj.get(parts[1])
|
|
937
|
-
elif hasattr(obj, parts[1]):
|
|
938
|
-
fn = getattr(obj, parts[1])
|
|
939
|
-
else:
|
|
940
|
-
fn = None
|
|
941
|
-
else:
|
|
942
|
-
fn = context.get(fn_name) or _resolve(fn_name, context)
|
|
943
|
-
if callable(fn):
|
|
944
|
-
if raw_args.strip():
|
|
945
|
-
# Split args manually, evaluate each as expression
|
|
946
|
-
parts = []
|
|
947
|
-
current = ""
|
|
948
|
-
in_q = None
|
|
949
|
-
for ch in raw_args:
|
|
950
|
-
if ch in ('"', "'") and not in_q:
|
|
951
|
-
in_q = ch
|
|
952
|
-
current += ch
|
|
953
|
-
elif ch == in_q:
|
|
954
|
-
in_q = None
|
|
955
|
-
current += ch
|
|
956
|
-
elif ch == "," and not in_q:
|
|
957
|
-
parts.append(current.strip())
|
|
958
|
-
current = ""
|
|
959
|
-
else:
|
|
960
|
-
current += ch
|
|
961
|
-
if current.strip():
|
|
962
|
-
parts.append(current.strip())
|
|
963
|
-
eval_args = [_eval_expr(a, context, apply_filters) for a in parts]
|
|
964
|
-
else:
|
|
965
|
-
eval_args = []
|
|
966
|
-
return fn(*eval_args)
|
|
967
|
-
|
|
895
|
+
evaluator = _EXPRESSION_EVALUATORS.get(kind)
|
|
896
|
+
if evaluator is not None:
|
|
897
|
+
return evaluator(expr, desc, context, apply_filters)
|
|
898
|
+
called, value = _eval_function_call(expr, context, apply_filters)
|
|
899
|
+
if called:
|
|
900
|
+
return value
|
|
968
901
|
return _resolve(expr, context)
|
|
969
902
|
|
|
970
903
|
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/js/tina4-dev-admin.min.js
RENAMED
|
@@ -234,11 +234,11 @@ ${t.traceback||""}`;window.__switchTab("chat"),setTimeout(()=>{window.__prefillC
|
|
|
234
234
|
</tr>`).join("")}
|
|
235
235
|
</tbody>
|
|
236
236
|
</table>
|
|
237
|
-
`)}function Xy(i,e){const t=[],n={},r={};i.forEach((s,o)=>{n[Rd(s)]=o,r[s]=o});for(const[s,o]of Object.entries(e)){const a=r[s];if(a!==void 0)for(const l of o){const O=r[l];if(O!==void 0){O!==a&&t.push([a,O]);continue}const c=l.replace(/^\.\//,"").replace(/^\.\.\//,"").split(/[./]/);let d;for(let h=c.length-1;h>=0;h--){const f=c[h].toLowerCase();if(f&&f!=="js"&&f!=="py"&&f!=="rb"&&f!=="ts"&&f!=="index"&&(d=n[f],d!==void 0))break}d===void 0&&(d=n[Rd(l)]),d!==void 0&&a!==d&&t.push([a,d])}}return t}function Rd(i){const e=i.replace(/\\/g,"/").split("/").pop()||"",t=e.lastIndexOf(".");return(t>0?e.substring(0,t):e).toLowerCase()}function _y(i,e,t,n){var gi,$n,ps;const r=e.offsetWidth||900,s=Math.max(450,Math.min(650,r*.45)),o=Math.max(...i.map(G=>G.loc))||1,a=Math.max(...i.map(G=>G.dep_count||0))||1,l=Math.max(...i.map(G=>G.coupling_afferent||0))||1,O=i.some(G=>(G.coupling_afferent||0)>0),c=14,d=Math.min(70,r/10);function h(G){const I=Math.min((G.avg_complexity||0)/10,1),D=G.
|
|
237
|
+
`)}function Xy(i,e){const t=[],n={},r={};i.forEach((s,o)=>{n[Rd(s)]=o,r[s]=o});for(const[s,o]of Object.entries(e)){const a=r[s];if(a!==void 0)for(const l of o){const O=r[l];if(O!==void 0){O!==a&&t.push([a,O]);continue}const c=l.replace(/^\.\//,"").replace(/^\.\.\//,"").split(/[./]/);let d;for(let h=c.length-1;h>=0;h--){const f=c[h].toLowerCase();if(f&&f!=="js"&&f!=="py"&&f!=="rb"&&f!=="ts"&&f!=="index"&&(d=n[f],d!==void 0))break}d===void 0&&(d=n[Rd(l)]),d!==void 0&&a!==d&&t.push([a,d])}}return t}function Rd(i){const e=i.replace(/\\/g,"/").split("/").pop()||"",t=e.lastIndexOf(".");return(t>0?e.substring(0,t):e).toLowerCase()}function _y(i,e,t,n){var gi,$n,ps;const r=e.offsetWidth||900,s=Math.max(450,Math.min(650,r*.45)),o=Math.max(...i.map(G=>G.loc))||1,a=Math.max(...i.map(G=>G.dep_count||0))||1,l=Math.max(...i.map(G=>G.coupling_afferent||0))||1,O=i.some(G=>(G.coupling_afferent||0)>0),c=14,d=Math.min(70,r/10);function h(G){const I=Math.min((G.avg_complexity||0)/10,1),D=G.has_referencing_test?0:1,de=Math.min((G.dep_count||0)/5,1),j=I*.4+D*.4+de*.2,W=Math.max(0,Math.min(1,j)),H=Math.round(120*(1-W)),te=Math.round(70+W*30),pe=Math.round(42+18*(1-W));return`hsl(${H},${te}%,${pe}%)`}function f(G){return G.loc/o*.4+(G.avg_complexity||0)/10*.4+(G.dep_count||0)/a*.2}const p=[...i].sort((G,I)=>f(G)-f(I)),m=r/2,$=s/2,g=[];let y=0,S=0;for(const G of p){const I=c+Math.sqrt(f(G))*(d-c),D=h(G);let de=!1;for(let j=0;j<800;j++){const W=m+S*Math.cos(y),H=$+S*Math.sin(y);let te=!1;for(const pe of g){const he=W-pe.x,ke=H-pe.y;if(Math.sqrt(he*he+ke*ke)<I+pe.r+2){te=!0;break}}if(!te&&W>I+2&&W<r-I-2&&H>I+25&&H<s-I-2){g.push({x:W,y:H,vx:0,vy:0,r:I,color:D,f:G}),de=!0;break}y+=.2,S+=.04}de||g.push({x:m+(Math.random()-.5)*r*.3,y:$+(Math.random()-.5)*s*.3,vx:0,vy:0,r:I,color:D,f:G})}const x=Xy(g.map(G=>G.f.path),t),w=document.createElement("canvas");w.width=r,w.height=s,w.style.cssText="display:block;border:1px solid var(--border);border-radius:8px;cursor:pointer;background:#0f172a";const R=n==="framework"?'<span style="color:#cba6f7;font-weight:600">(Framework)</span> Add code to src/ to see your project':"";e.innerHTML=`<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.5rem"><h3 style="margin:0;font-size:0.85rem">Code Landscape ${R}</h3><span style="font-size:0.65rem;color:var(--muted)">Drag bubbles | Dbl-click to drill down</span></div><div style="position:relative" id="metrics-canvas-wrap"></div><div style="margin-top:0.4rem;font-size:0.62rem;color:var(--muted);display:flex;gap:0.9rem;flex-wrap:wrap">${O?'<span>Centre = most depended-on</span><span><span style="color:hsl(210,85%,60%)">●</span> stable ring</span><span><span style="color:hsl(30,85%,60%)">●</span> unstable ring</span>':""}<span id="metrics-hover-info" class="text-mono"></span></div>`,document.getElementById("metrics-canvas-wrap").appendChild(w);const _=document.getElementById("metrics-hover-info"),A=document.createElement("div");A.style.cssText="position:absolute;top:8px;left:8px;z-index:2;display:flex;gap:4px;flex-direction:column",A.innerHTML=`
|
|
238
238
|
<button class="btn btn-sm" id="metrics-zoom-in" style="width:28px;height:28px;padding:0;font-size:14px;font-weight:700;line-height:1">+</button>
|
|
239
239
|
<button class="btn btn-sm" id="metrics-zoom-out" style="width:28px;height:28px;padding:0;font-size:14px;font-weight:700;line-height:1">−</button>
|
|
240
240
|
<button class="btn btn-sm" id="metrics-zoom-fit" style="width:28px;height:28px;padding:0;font-size:10px;font-weight:700;line-height:1">Fit</button>
|
|
241
|
-
`,document.getElementById("metrics-canvas-wrap").appendChild(A),(gi=document.getElementById("metrics-zoom-in"))==null||gi.addEventListener("click",()=>{le=Math.min(5,le*1.3)}),($n=document.getElementById("metrics-zoom-out"))==null||$n.addEventListener("click",()=>{le=Math.max(.3,le*.7)}),(ps=document.getElementById("metrics-zoom-fit"))==null||ps.addEventListener("click",()=>{le=1,U=0,oe=0});const P=w.getContext("2d");let v=-1,k=-1,X=0,z=0,U=0,oe=0,le=1,rt=!1,mn=0,Gt=0,us=0,ft=0;function De(){for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j],H=m-W.x,te=$-W.y,pe=.3+W.r/d*.7,he=O?.3+(W.f.coupling_afferent||0)/l*.7:pe,ke=.008*pe*he;W.vx+=H*ke,W.vy+=te*ke}for(const[j,W]of x){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20,Bt=(ke-Dt)*.002,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx+=ms,H.vy+=$s),W!==k&&(te.vx-=ms,te.vy-=$s)}for(let j=0;j<g.length;j++)for(let W=j+1;W<g.length;W++){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20;if(ke<Dt){const Bt=40*(Dt-ke)/Dt,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx-=ms,H.vy-=$s),W!==k&&(te.vx+=ms,te.vy+=$s)}}for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j];W.vx*=.65,W.vy*=.65;const H=2;W.vx=Math.max(-H,Math.min(H,W.vx)),W.vy=Math.max(-H,Math.min(H,W.vy)),W.x+=W.vx,W.y+=W.vy,W.x=Math.max(W.r+2,Math.min(r-W.r-2,W.x)),W.y=Math.max(W.r+25,Math.min(s-W.r-2,W.y))}}function st(){var G;De(),P.clearRect(0,0,r,s),P.save(),P.translate(U,oe),P.scale(le,le),P.strokeStyle="rgba(255,255,255,0.03)",P.lineWidth=1/le;for(let I=0;I<r/le;I+=50)P.beginPath(),P.moveTo(I,0),P.lineTo(I,s/le),P.stroke();for(let I=0;I<s/le;I+=50)P.beginPath(),P.moveTo(0,I),P.lineTo(r/le,I),P.stroke();for(const[I,D]of x){const de=g[I],j=g[D],W=j.x-de.x,H=j.y-de.y,te=Math.sqrt(W*W+H*H)||1,pe=v===I||v===D;P.beginPath(),P.moveTo(de.x+W/te*de.r,de.y+H/te*de.r);const he=j.x-W/te*j.r,ke=j.y-H/te*j.r;P.lineTo(he,ke),P.strokeStyle=pe?"rgba(139,180,250,0.9)":"rgba(255,255,255,0.15)",P.lineWidth=pe?3:1,P.stroke();const Dt=pe?12:6,Bt=Math.atan2(H,W);P.beginPath(),P.moveTo(he,ke),P.lineTo(he-Dt*Math.cos(Bt-.4),ke-Dt*Math.sin(Bt-.4)),P.lineTo(he-Dt*Math.cos(Bt+.4),ke-Dt*Math.sin(Bt+.4)),P.closePath(),P.fillStyle=P.strokeStyle,P.fill()}for(let I=0;I<g.length;I++){const D=g[I],de=I===v,j=de?D.r+4:D.r;de&&(P.beginPath(),P.arc(D.x,D.y,j+8,0,Math.PI*2),P.fillStyle="rgba(255,255,255,0.08)",P.fill()),P.beginPath(),P.arc(D.x,D.y,j,0,Math.PI*2),P.fillStyle=D.color,P.globalAlpha=de?1:.85,P.fill(),P.globalAlpha=1,P.strokeStyle=de?"rgba(255,255,255,0.6)":"rgba(255,255,255,0.25)",P.lineWidth=de?2.5:1.5,P.stroke();const W=D.f.instability;O&&typeof W=="number"&&(D.f.coupling_afferent||0)+(D.f.coupling_efferent||0)>0&&(P.beginPath(),P.arc(D.x,D.y,j+3,0,Math.PI*2),P.strokeStyle=`hsl(${Math.round(210-180*W)},85%,60%)`,P.lineWidth=1+W*2.5,P.stroke());const H=((G=D.f.path.split("/").pop())==null?void 0:G.replace(/\.\w+$/,""))||"?";if(j>16){const he=Math.max(8,Math.min(13,j*.38));P.fillStyle="#fff",P.font=`600 ${he}px monospace`,P.textAlign="center",P.fillText(H,D.x,D.y-2),P.fillStyle="rgba(255,255,255,0.65)",P.font=`${he-1}px monospace`,P.fillText(`${D.f.loc} LOC`,D.x,D.y+he)}const te=Math.max(9,j*.3),pe=te*.7;if(j>14&&D.f.dep_count>0){const he=D.y-j+pe+3;P.beginPath(),P.arc(D.x,he,pe,0,Math.PI*2),P.fillStyle="#ea580c",P.fill(),P.fillStyle="#fff",P.font=`bold ${te}px sans-serif`,P.textAlign="center",P.fillText("D",D.x,he+te*.35)}if(j>14&&D.f.
|
|
241
|
+
`,document.getElementById("metrics-canvas-wrap").appendChild(A),(gi=document.getElementById("metrics-zoom-in"))==null||gi.addEventListener("click",()=>{le=Math.min(5,le*1.3)}),($n=document.getElementById("metrics-zoom-out"))==null||$n.addEventListener("click",()=>{le=Math.max(.3,le*.7)}),(ps=document.getElementById("metrics-zoom-fit"))==null||ps.addEventListener("click",()=>{le=1,U=0,oe=0});const P=w.getContext("2d");let v=-1,k=-1,X=0,z=0,U=0,oe=0,le=1,rt=!1,mn=0,Gt=0,us=0,ft=0;function De(){for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j],H=m-W.x,te=$-W.y,pe=.3+W.r/d*.7,he=O?.3+(W.f.coupling_afferent||0)/l*.7:pe,ke=.008*pe*he;W.vx+=H*ke,W.vy+=te*ke}for(const[j,W]of x){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20,Bt=(ke-Dt)*.002,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx+=ms,H.vy+=$s),W!==k&&(te.vx-=ms,te.vy-=$s)}for(let j=0;j<g.length;j++)for(let W=j+1;W<g.length;W++){const H=g[j],te=g[W],pe=te.x-H.x,he=te.y-H.y,ke=Math.sqrt(pe*pe+he*he)||1,Dt=H.r+te.r+20;if(ke<Dt){const Bt=40*(Dt-ke)/Dt,ms=pe/ke*Bt,$s=he/ke*Bt;j!==k&&(H.vx-=ms,H.vy-=$s),W!==k&&(te.vx+=ms,te.vy+=$s)}}for(let j=0;j<g.length;j++){if(j===k)continue;const W=g[j];W.vx*=.65,W.vy*=.65;const H=2;W.vx=Math.max(-H,Math.min(H,W.vx)),W.vy=Math.max(-H,Math.min(H,W.vy)),W.x+=W.vx,W.y+=W.vy,W.x=Math.max(W.r+2,Math.min(r-W.r-2,W.x)),W.y=Math.max(W.r+25,Math.min(s-W.r-2,W.y))}}function st(){var G;De(),P.clearRect(0,0,r,s),P.save(),P.translate(U,oe),P.scale(le,le),P.strokeStyle="rgba(255,255,255,0.03)",P.lineWidth=1/le;for(let I=0;I<r/le;I+=50)P.beginPath(),P.moveTo(I,0),P.lineTo(I,s/le),P.stroke();for(let I=0;I<s/le;I+=50)P.beginPath(),P.moveTo(0,I),P.lineTo(r/le,I),P.stroke();for(const[I,D]of x){const de=g[I],j=g[D],W=j.x-de.x,H=j.y-de.y,te=Math.sqrt(W*W+H*H)||1,pe=v===I||v===D;P.beginPath(),P.moveTo(de.x+W/te*de.r,de.y+H/te*de.r);const he=j.x-W/te*j.r,ke=j.y-H/te*j.r;P.lineTo(he,ke),P.strokeStyle=pe?"rgba(139,180,250,0.9)":"rgba(255,255,255,0.15)",P.lineWidth=pe?3:1,P.stroke();const Dt=pe?12:6,Bt=Math.atan2(H,W);P.beginPath(),P.moveTo(he,ke),P.lineTo(he-Dt*Math.cos(Bt-.4),ke-Dt*Math.sin(Bt-.4)),P.lineTo(he-Dt*Math.cos(Bt+.4),ke-Dt*Math.sin(Bt+.4)),P.closePath(),P.fillStyle=P.strokeStyle,P.fill()}for(let I=0;I<g.length;I++){const D=g[I],de=I===v,j=de?D.r+4:D.r;de&&(P.beginPath(),P.arc(D.x,D.y,j+8,0,Math.PI*2),P.fillStyle="rgba(255,255,255,0.08)",P.fill()),P.beginPath(),P.arc(D.x,D.y,j,0,Math.PI*2),P.fillStyle=D.color,P.globalAlpha=de?1:.85,P.fill(),P.globalAlpha=1,P.strokeStyle=de?"rgba(255,255,255,0.6)":"rgba(255,255,255,0.25)",P.lineWidth=de?2.5:1.5,P.stroke();const W=D.f.instability;O&&typeof W=="number"&&(D.f.coupling_afferent||0)+(D.f.coupling_efferent||0)>0&&(P.beginPath(),P.arc(D.x,D.y,j+3,0,Math.PI*2),P.strokeStyle=`hsl(${Math.round(210-180*W)},85%,60%)`,P.lineWidth=1+W*2.5,P.stroke());const H=((G=D.f.path.split("/").pop())==null?void 0:G.replace(/\.\w+$/,""))||"?";if(j>16){const he=Math.max(8,Math.min(13,j*.38));P.fillStyle="#fff",P.font=`600 ${he}px monospace`,P.textAlign="center",P.fillText(H,D.x,D.y-2),P.fillStyle="rgba(255,255,255,0.65)",P.font=`${he-1}px monospace`,P.fillText(`${D.f.loc} LOC`,D.x,D.y+he)}const te=Math.max(9,j*.3),pe=te*.7;if(j>14&&D.f.dep_count>0){const he=D.y-j+pe+3;P.beginPath(),P.arc(D.x,he,pe,0,Math.PI*2),P.fillStyle="#ea580c",P.fill(),P.fillStyle="#fff",P.font=`bold ${te}px sans-serif`,P.textAlign="center",P.fillText("D",D.x,he+te*.35)}if(j>14&&D.f.has_referencing_test){const he=D.y+j-pe-3;P.beginPath(),P.arc(D.x,he,pe,0,Math.PI*2),P.fillStyle="#16a34a",P.fill(),P.fillStyle="#fff",P.font=`bold ${te}px sans-serif`,P.textAlign="center",P.fillText("T",D.x,he+te*.35)}}if(P.restore(),v>=0){const I=g[v].f,D=I.coupling_afferent??0,de=I.coupling_efferent??0,j=typeof I.instability=="number"?I.instability.toFixed(2):"-";_.textContent=`${I.path} ca ${D} / ce ${de} / I ${j} / MI ${I.maintainability}`}else _.textContent&&(_.textContent="");requestAnimationFrame(st)}w.addEventListener("mousemove",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;if(rt){U=us+(G.clientX-mn),oe=ft+(G.clientY-Gt);return}if(k>=0){ut=!0,g[k].x=D+X,g[k].y=de+z,g[k].vx=0,g[k].vy=0;return}v=-1;for(let j=g.length-1;j>=0;j--){const W=g[j],H=D-W.x,te=de-W.y;if(Math.sqrt(H*H+te*te)<W.r+4){v=j;break}}w.style.cursor=v>=0?"grab":"default"}),w.addEventListener("mousedown",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;if(G.button===2){rt=!0,mn=G.clientX,Gt=G.clientY,us=U,ft=oe,w.style.cursor="move";return}v>=0&&(k=v,X=g[k].x-D,z=g[k].y-de,ut=!1,w.style.cursor="grabbing")});let ut=!1;w.addEventListener("mouseup",G=>{if(rt){rt=!1,w.style.cursor="default";return}if(k>=0){ut||Xa(g[k].f.path),w.style.cursor="grab",k=-1,ut=!1;return}}),w.addEventListener("mouseleave",()=>{v=-1,k=-1,rt=!1}),w.addEventListener("dblclick",G=>{const I=w.getBoundingClientRect(),D=(G.clientX-I.left-U)/le,de=(G.clientY-I.top-oe)/le;for(let j=g.length-1;j>=0;j--){const W=g[j],H=D-W.x,te=de-W.y;if(Math.sqrt(H*H+te*te)<W.r+4){Xa(W.f.path);break}}}),w.addEventListener("contextmenu",G=>G.preventDefault()),requestAnimationFrame(st)}async function Xa(i){const e=document.getElementById("metrics-detail");if(!e)return;e.innerHTML='<p class="text-muted">Loading file analysis...</p>';const t=await L("/metrics/file?path="+encodeURIComponent(i));if(t.error){e.innerHTML=`<p style="color:var(--danger)">${Q(t.error)}</p>`;return}const n=t.functions||[],r=Math.max(1,...n.map(s=>s.complexity));e.innerHTML=`
|
|
242
242
|
<div style="background:var(--surface);border:1px solid var(--border);border-radius:0.5rem;padding:1rem">
|
|
243
243
|
<div class="flex items-center" style="justify-content:space-between;margin-bottom:0.75rem">
|
|
244
244
|
<h3 style="font-size:0.9rem">${Q(t.path)}</h3>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/gallery/error-overlay/meta.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue_backends/kafka_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue_backends/mongo_backend.py
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/realtime/models/ChannelMember.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/memcached_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.102 → tina4_python-3.13.103}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|