echotools 2.3.22__tar.gz → 2.3.23__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.
- {echotools-2.3.22 → echotools-2.3.23}/PKG-INFO +6 -1
- {echotools-2.3.22 → echotools-2.3.23}/pyproject.toml +5 -1
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/compat.py +1 -0
- echotools-2.3.23/src/echotools/media/console/__init__.py +38 -0
- echotools-2.3.23/src/echotools/media/console/charmap.py +148 -0
- echotools-2.3.23/src/echotools/media/console/spinner.py +342 -0
- echotools-2.3.23/src/echotools/media/console/ui.py +4243 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools.egg-info/PKG-INFO +6 -1
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools.egg-info/SOURCES.txt +5 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools.egg-info/requires.txt +6 -0
- echotools-2.3.23/src/tests/test_console.py +28 -0
- {echotools-2.3.22 → echotools-2.3.23}/README.md +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/docs/README.md +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/setup.cfg +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/cache/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/cache/list_cache.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/cache/memory_cache.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/config/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/config/base.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/config/center.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/config/center_notify.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/config/center_template.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/config/loader.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/config/merge.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/errors/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/errors/base.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/errors/classify.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/errors/common.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/errors/http.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/ids/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/ids/generator.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/io/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/io/io_utils.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/io/printstream.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/logger/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/logger/manager.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/retry/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/base/retry/retry.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/dispatch/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/dispatch/candidate.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/dispatch/dispatcher.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/dispatch/proxy_selector.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/dispatch/race.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/dispatch/selector.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/dispatch/usage.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/parsers/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/parsers/stream.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/parsers/xml_params.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/parsers/xml_parser.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/prompt/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/prompt/history.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/prompt/history_format.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/prompt/inject.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/prompt/prompt_helpers.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/prompt/templates.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml_invoke.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml_patterns.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml_thinking.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml_thinking_history.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml_thinking_parse.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml_tools.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/protocols/entml_values.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/registry.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/shared/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/shared/coercion.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/shared/loop_detect.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/shared/normalization.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/shared/schema_render.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/fncall/shared/xml_helpers.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/keys.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/lifecycle/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/lifecycle/manager.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/lifecycle/updater.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/lifecycle/updater_pull.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/process/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/process/port.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/protocol/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/protocol/base.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/conpty.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/local/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/local/core.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/local/proc.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/local/unix.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/local/win.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/sanitize.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/session/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/session/base.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/session/client.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/ssh.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/exec/terminal/tmux.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/events/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/events/bus.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/events/event.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/spinner/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/spinner/spinner.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/spinner/spinner_color.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/tracing/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/tracing/context.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/tracing/span.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/tracing/tracer.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/translate.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/watcher/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/watcher/file_watcher.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/application.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/broker.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/app.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/components/filezone.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/components/motionkit.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/components/sendbutton.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/components/sortablelist.css +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/components/sortablelist.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/components/textinput.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/components/voiceinput.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/fonts/inter-500.woff2 +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/fonts/inter-600.woff2 +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/fonts/inter400.woff2 +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/fonts/tailwind.css +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/inputbox.css +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/inputbox.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/style.css +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/input_box/voicegif.js +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/middleware.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/stats.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/media/web/utils.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/files/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/files/file_util.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/network/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/network/http_utils.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/plugin/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/plugin/base.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/plugin/discovery.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/plugin/registry.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/plugin/registry_ops.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/proxy/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/proxy/manager.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/runtime/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/runtime/collector.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/scheduler/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/scheduler/scheduler.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/sdk/__init__.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/plat/sdk/facade.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools/version.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools.egg-info/dependency_links.txt +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/echotools.egg-info/top_level.txt +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/bench_import.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/conftest.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_cache_retry.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_completion.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_config.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_config_extended.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_coverage_core.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_dispatch.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_dispatch_extended.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_dispatch_race.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_entml_protocol.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_entml_thinking_history.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_entml_thinking_parse.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_entml_tools.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_errors.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_errors_common.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_events.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_events_event.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_facade.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_fncall.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_ids.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_imports.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_io.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_keys.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_lifecycle.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_logger.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_merge.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_plugin.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_printstream.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_protocols.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_proxy.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_proxy_selector.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_runtime.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_scheduler.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_tracing.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_usage.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_web.py +0 -0
- {echotools-2.3.22 → echotools-2.3.23}/src/tests/test_web_broker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: echotools
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.23
|
|
4
4
|
Summary: 通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链
|
|
5
5
|
Author: nichengfuben
|
|
6
6
|
License: MIT
|
|
@@ -33,6 +33,9 @@ Provides-Extra: ssh
|
|
|
33
33
|
Requires-Dist: paramiko>=3.0.0; extra == "ssh"
|
|
34
34
|
Provides-Extra: terminal
|
|
35
35
|
Requires-Dist: pywinpty>=2.0.0; sys_platform == "win32" and extra == "terminal"
|
|
36
|
+
Provides-Extra: console
|
|
37
|
+
Requires-Dist: rich>=13.0; extra == "console"
|
|
38
|
+
Requires-Dist: wcwidth>=0.2; extra == "console"
|
|
36
39
|
Provides-Extra: all
|
|
37
40
|
Requires-Dist: tomlkit>=0.11.0; extra == "all"
|
|
38
41
|
Requires-Dist: tomli>=2.0.0; python_version < "3.11" and extra == "all"
|
|
@@ -41,6 +44,8 @@ Requires-Dist: aiohttp>=3.8.0; extra == "all"
|
|
|
41
44
|
Requires-Dist: aiohttp-socks>=0.8.0; extra == "all"
|
|
42
45
|
Requires-Dist: paramiko>=3.0.0; extra == "all"
|
|
43
46
|
Requires-Dist: pywinpty>=2.0.0; sys_platform == "win32" and extra == "all"
|
|
47
|
+
Requires-Dist: rich>=13.0; extra == "all"
|
|
48
|
+
Requires-Dist: wcwidth>=0.2; extra == "all"
|
|
44
49
|
Provides-Extra: dev
|
|
45
50
|
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
46
51
|
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "echotools"
|
|
7
|
-
version = "2.3.
|
|
7
|
+
version = "2.3.23"
|
|
8
8
|
description = "通用基础设施 SDK:配置、日志、事件、调度、插件、协议、调用链"
|
|
9
9
|
readme = "docs/README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -33,6 +33,7 @@ http = ["aiohttp>=3.8.0"]
|
|
|
33
33
|
socks = ["aiohttp-socks>=0.8.0"]
|
|
34
34
|
ssh = ["paramiko>=3.0.0"]
|
|
35
35
|
terminal = ["pywinpty>=2.0.0; sys_platform == 'win32'"]
|
|
36
|
+
console = ["rich>=13.0", "wcwidth>=0.2"]
|
|
36
37
|
all = [
|
|
37
38
|
"tomlkit>=0.11.0",
|
|
38
39
|
"tomli>=2.0.0; python_version < '3.11'",
|
|
@@ -41,6 +42,8 @@ all = [
|
|
|
41
42
|
"aiohttp-socks>=0.8.0",
|
|
42
43
|
"paramiko>=3.0.0",
|
|
43
44
|
"pywinpty>=2.0.0; sys_platform == 'win32'",
|
|
45
|
+
"rich>=13.0",
|
|
46
|
+
"wcwidth>=0.2",
|
|
44
47
|
]
|
|
45
48
|
dev = [
|
|
46
49
|
"pytest>=7.4.0",
|
|
@@ -113,6 +116,7 @@ omit = [
|
|
|
113
116
|
"*/compat.py",
|
|
114
117
|
"*/terminal/*",
|
|
115
118
|
"*/spinner/*",
|
|
119
|
+
"*/console/*",
|
|
116
120
|
"*/media/web/input_box/*",
|
|
117
121
|
"*/lifecycle/updater.py",
|
|
118
122
|
"*/watcher/*",
|
|
@@ -29,6 +29,7 @@ _MODULE_ALIASES: Dict[str, str] = {
|
|
|
29
29
|
"echotools.web": "echotools.media.web",
|
|
30
30
|
"echotools.events": "echotools.media.events",
|
|
31
31
|
"echotools.spinner": "echotools.media.spinner",
|
|
32
|
+
"echotools.console": "echotools.media.console",
|
|
32
33
|
"echotools.tracing": "echotools.media.tracing",
|
|
33
34
|
"echotools.translate": "echotools.media.translate",
|
|
34
35
|
"echotools.watcher": "echotools.media.watcher",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""echotools.media.console:终端 ConsoleUI 框架。
|
|
4
|
+
|
|
5
|
+
渐变文本、圆角边框、ASCII 艺术字、表格/面板、异步输入与 Spinner。
|
|
6
|
+
依赖:rich、wcwidth(``pip install echotools[console]``)。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from echotools.media.console.charmap import char_map
|
|
10
|
+
from echotools.media.console.spinner import Clock, Spinner
|
|
11
|
+
from echotools.media.console.ui import (
|
|
12
|
+
Alignment,
|
|
13
|
+
BorderStyle,
|
|
14
|
+
ConsoleUI,
|
|
15
|
+
FileLogWriter,
|
|
16
|
+
FontStyle,
|
|
17
|
+
GradientTheme,
|
|
18
|
+
TextUtils,
|
|
19
|
+
create_ui,
|
|
20
|
+
_get_backend,
|
|
21
|
+
_normalize_key_event,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Alignment",
|
|
26
|
+
"BorderStyle",
|
|
27
|
+
"Clock",
|
|
28
|
+
"ConsoleUI",
|
|
29
|
+
"FileLogWriter",
|
|
30
|
+
"FontStyle",
|
|
31
|
+
"GradientTheme",
|
|
32
|
+
"Spinner",
|
|
33
|
+
"TextUtils",
|
|
34
|
+
"char_map",
|
|
35
|
+
"create_ui",
|
|
36
|
+
"_get_backend",
|
|
37
|
+
"_normalize_key_event",
|
|
38
|
+
]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
char_map = {
|
|
2
|
+
'!': [' ██╗ ', ' ██║ ', ' ██║ ', ' ╚═╝ ', ' ██╗ ', ' ╚═╝ '],
|
|
3
|
+
'"': [' ██╗ ██╗', ' ██║ ██║', ' ╚═╝ ╚═╝', ' ', ' ', ' '],
|
|
4
|
+
'#': [' ██╗ ██╗ ', '████████╗', '╚██╔═██╔╝', '████████╗', '╚██╔═██╔╝', ' ╚═╝ ╚═╝ '],
|
|
5
|
+
'$': [' ▄███╗ ', '██╔═██╗ ', '███████║', '╚═█████║', ' ╚═══██║', ' ▀███╔╝ '],
|
|
6
|
+
'%': ['██╗ ██╗ ', '╚═╝ ██╔╝ ', ' ██╔╝ ', ' ██╔╝ ', ' ██╔╝ ██╗', '██╔╝ ╚═╝'],
|
|
7
|
+
"'": [' ██╗ ', ' ██║ ', ' ╚═╝ ', ' ', ' ', ' '],
|
|
8
|
+
'(': [' ██╗ ', '██╔╝ ', '██║ ', '██║ ', '╚██╗ ', ' ╚═╝ '],
|
|
9
|
+
')': ['██╗ ', '╚██╗ ', ' ██║ ', ' ██║ ', '██╔╝ ', '╚═╝ '],
|
|
10
|
+
'+': [' ', ' ██╗ ', '████████╗', '╚══██╔══╝', ' ██║ ', ' ╚═╝ '],
|
|
11
|
+
',': [' ', ' ', ' ', ' ', ' ██╗ ', '██╔╝ '],
|
|
12
|
+
'.': [' ', ' ', ' ', ' ', ' ██╗ ', ' ╚═╝ '],
|
|
13
|
+
'/': [' ██╗', ' ██╔╝', ' ██╔╝ ', ' ██╔╝ ', ' ██╔╝ ', '██╔╝ '],
|
|
14
|
+
'0': [' ██████╗ ', '██╔═████╗', '██║██╔██║', '████╔╝██║', '╚██████╔╝', ' ╚═════╝ '],
|
|
15
|
+
'1': [' ██╗ ', '███║ ', '╚██║ ', ' ██║ ', ' ██║ ', ' ╚═╝ '],
|
|
16
|
+
'2': ['██████╗ ', '╚════██╗', ' █████╔╝', '██╔═══╝ ', '███████╗', '╚══════╝'],
|
|
17
|
+
'3': ['██████╗ ', '╚════██╗', ' █████╔╝', ' ╚═══██╗', '██████╔╝', '╚═════╝ '],
|
|
18
|
+
'4': ['██╗ ██╗', '██║ ██║', '███████║', '╚════██║', ' ██║', ' ╚═╝'],
|
|
19
|
+
'5': ['███████╗', '██╔════╝', '███████╗', '╚════██║', '███████║', '╚══════╝'],
|
|
20
|
+
'6': [' ██████╗ ', '██╔════╝ ', '███████╗ ', '██╔═══██╗', '╚██████╔╝', ' ╚═════╝ '],
|
|
21
|
+
'7': ['███████╗', '╚════██║', ' ██╔╝', ' ██╔╝ ', ' ██║ ', ' ╚═╝ '],
|
|
22
|
+
'8': [' █████╗ ', '██╔══██╗', '╚█████╔╝', '██╔══██╗', '╚█████╔╝', ' ╚════╝ '],
|
|
23
|
+
'9': [' █████╗ ', '██╔══██╗', '╚██████║', ' ╚═══██║', ' █████╔╝', ' ╚════╝ '],
|
|
24
|
+
':': [' ', ' ██╗ ', ' ╚═╝ ', ' ', ' ██╗ ', ' ╚═╝ '],
|
|
25
|
+
';': [' ', ' ██╗ ', ' ╚═╝ ', ' ', ' ██╗ ', '██╔╝ '],
|
|
26
|
+
'=': [' ', '█████████╗', '╚════════╝', '█████████╗', '╚════════╝', ' '],
|
|
27
|
+
'?': [' █████╗ ', '██╔══██╗', ' ███╔╝', ' ██╔╝ ', ' ╚═╝ ', ' ██╗ '],
|
|
28
|
+
'@': [' ██████╗ ', '██╔██╗██╗ ', '██║██║██║ ', '██║╚█████║', '╚██████╔╝ ', ' ╚═════╝ '],
|
|
29
|
+
'A': [' █████╗ ', '██╔══██╗', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
|
|
30
|
+
'B': ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██████╔╝', '╚═════╝ '],
|
|
31
|
+
'C': [' ██████╗', '██╔════╝', '██║ ', '██║ ', '╚██████╗', ' ╚═════╝'],
|
|
32
|
+
'D': ['██████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '██████╔╝', '╚═════╝ '],
|
|
33
|
+
'E': ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
|
|
34
|
+
'F': ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '██║ ', '╚═╝ '],
|
|
35
|
+
'G': [' ██████╗ ', '██╔════╝ ', '██║ ███╗', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
|
|
36
|
+
'H': ['██╗ ██╗', '██║ ██║', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
|
|
37
|
+
'I': ['██╗ ', '██║ ', '██║ ', '██║ ', '██║ ', '╚═╝ '],
|
|
38
|
+
'J': [' ██╗', ' ██║', ' ██║', '██ ██║', '╚█████╔╝', ' ╚════╝ '],
|
|
39
|
+
'K': ['██╗ ██╗', '██║ ██╔╝', '█████╔╝ ', '██╔═██╗ ', '██║ ██╗', '╚═╝ ╚═╝'],
|
|
40
|
+
'L': ['██╗ ', '██║ ', '██║ ', '██║ ', '███████╗', '╚══════╝'],
|
|
41
|
+
'M': ['███╗ ███╗', '████╗ ████║', '██╔████╔██║', '██║╚██╔╝██║', '██║ ╚═╝ ██║', '╚═╝ ╚═╝'],
|
|
42
|
+
'N': ['███╗ ██╗', '████╗ ██║', '██╔██╗ ██║', '██║╚██╗██║', '██║ ╚████║', '╚═╝ ╚═══╝'],
|
|
43
|
+
'O': [' ██████╗ ', '██╔═══██╗', '██║ ██║', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
|
|
44
|
+
'P': ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔═══╝ ', '██║ ', '╚═╝ '],
|
|
45
|
+
'Q': [' ██████╗ ', '██╔═══██╗', '██║ ██║', '██║▄▄ ██║', '╚██████╔╝', ' ╚══▀▀═╝ '],
|
|
46
|
+
'R': ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
|
|
47
|
+
'S': ['███████╗', '██╔════╝', '███████╗', '╚════██║', '███████║', '╚══════╝'],
|
|
48
|
+
'T': ['████████╗', '╚══██╔══╝', ' ██║ ', ' ██║ ', ' ██║ ', ' ╚═╝ '],
|
|
49
|
+
'U': ['██╗ ██╗', '██║ ██║', '██║ ██║', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
|
|
50
|
+
'V': ['██╗ ██╗', '██║ ██║', '██║ ██║', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚═══╝ '],
|
|
51
|
+
'W': ['██╗ ██╗', '██║ ██║', '██║ █╗ ██║', '██║███╗██║', '╚███╔███╔╝', ' ╚══╝╚══╝ '],
|
|
52
|
+
'X': ['██╗ ██╗', '╚██╗██╔╝', ' ╚███╔╝ ', ' ██╔██╗ ', '██╔╝ ██╗', '╚═╝ ╚═╝'],
|
|
53
|
+
'Y': ['██╗ ██╗', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚██╔╝ ', ' ██║ ', ' ╚═╝ '],
|
|
54
|
+
'Z': ['███████╗', '╚══███╔╝', ' ███╔╝ ', ' ███╔╝ ', '███████╗', '╚══════╝'],
|
|
55
|
+
'_': [' ', ' ', ' ', ' ', ' ', '████████'],
|
|
56
|
+
'a': [' ', ' ', ' ██████╗ ', '██╔══██║ ', '╚███████╗', ' ╚══════╝'],
|
|
57
|
+
'b': ['██╗ ', '██║ ', '██████╗ ', '██╔══██╗', '██████╔╝', '╚═════╝ '],
|
|
58
|
+
'c': [' ', ' ', ' ██████╗', '██╔════╝', '╚██████╗', ' ╚═════╝'],
|
|
59
|
+
'd': [' ██╗', ' ██║', ' ██████║', '██╔══██║', '╚██████║', ' ╚═════╝'],
|
|
60
|
+
'e': [' ', ' ', ' █████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
|
|
61
|
+
'f': [' ████╗ ', '██╔══╝ ', '█████╗ ', '██╔══╝ ', '██║ ', '╚═╝ '],
|
|
62
|
+
'g': [' ', ' ██████╗', '██╔══██║', '╚██████║', ' ╚═══██║', '██████╔╝'],
|
|
63
|
+
'h': ['██╗ ', '██║ ', '██████╗ ', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
|
|
64
|
+
'i': ['██╗ ', '╚═╝ ', '██╗ ', '██║ ', '██║ ', '╚═╝ '],
|
|
65
|
+
'j': [' ██╗ ', ' ╚═╝ ', ' ██╗ ', ' ██║ ', ' ██║ ', '████╔╝ '],
|
|
66
|
+
'k': ['██╗ ', '██║ ', '██║ ██╗ ', '████╔╝ ', '██╔═██╗ ', '╚═╝ ╚═╝ '],
|
|
67
|
+
'l': ['██╗ ', '██║ ', '██║ ', '██║ ', '██║ ', '╚═╝ '],
|
|
68
|
+
'm': [' ', ' ', '██████████╗', '██╔═██╔═██║', '██║ ██║ ██║', '╚═╝ ╚═╝ ╚═╝'],
|
|
69
|
+
'n': [' ', ' ', '██████╗ ', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
|
|
70
|
+
'o': [' ', ' ', ' ██████╗ ', '██╔═══██║', '╚██████╔╝', ' ╚═════╝ '],
|
|
71
|
+
'p': [' ', '██████╗ ', '██╔══██╗', '██████╔╝', '██╔═══╝ ', '╚═╝ '],
|
|
72
|
+
'q': [' ', ' ██████╗', '██╔══██║', '╚██████║', ' ╚═══██║', ' ╚═╝'],
|
|
73
|
+
'r': [' ', ' ', '██████╗ ', '██╔══██╗', '██║ ╚═╝', '╚═╝ '],
|
|
74
|
+
's': [' ', ' ████╗ ', '████ ╝ ', '╚══█ ╗ ', '█████║ ', '╚════╝ '],
|
|
75
|
+
't': [' ██╗ ', ' ██║ ', '███████╗ ', '╚══██╔═╝ ', ' ████║ ', ' ╚═══╝ '],
|
|
76
|
+
'u': [' ', ' ', '██╗ ██╗', '██║ ██║', '╚██████║', ' ╚═════╝'],
|
|
77
|
+
'v': [' ', ' ', '██╗ ██╗', '╚██╗██╔╝', ' ╚███╔╝ ', ' ╚══╝ '],
|
|
78
|
+
'w': [' ', ' ', '██╗ ██╗ ██╗', '╚██╗████╗██╔╝', ' ╚██╔══██╔╝ ', ' ╚═╝ ╚═╝ '],
|
|
79
|
+
'x': [' ', ' ', '██╗ ██╗', '╚████╔╝ ', '██╔═██╗ ', '╚═╝ ╚═╝ '],
|
|
80
|
+
'y': [' ', '██╗ ██╗', '╚██╗██╔╝', ' ╚███╔╝ ', '███╔═╝ ', '╚═╝ '],
|
|
81
|
+
'z': [' ', ' ', '███████╗', '╚══███╔╝', '███████║', '╚══════╝'],
|
|
82
|
+
'ı': ['██╗ ', '██║ ', '██║ ', '██║ ', '██║ ', '╚═╝ '],
|
|
83
|
+
'ſ': ['███████╗', '██╔════╝', '███████╗', '╚════██║', '███████║', '╚══════╝'],
|
|
84
|
+
' ': [' ', ' ', ' ', ' ', ' ', ' '],
|
|
85
|
+
'-': [' ', ' ', '███████╗', '╚══════╝', ' ', ' '],
|
|
86
|
+
'[': ['███╗ ', '██╔╝ ', '██║ ', '██║ ', '███╗ ', '╚══╝ '],
|
|
87
|
+
']': ['███╗ ', '╚██║ ', ' ██║ ', ' ██║ ', '███║ ', '╚══╝ '],
|
|
88
|
+
'{': [' ██╗ ', '██╔╝ ', '█╔╝ ', '╚██╗ ', ' ╚██╗ ', ' ╚═╝ '],
|
|
89
|
+
'}': ['██╗ ', '╚██╗ ', ' ╚█╗ ', '██╔╝ ', '██╔╝ ', '╚═╝ '],
|
|
90
|
+
'\\': ['██╗ ', '╚██╗ ', ' ╚██╗ ', ' ╚██╗ ', ' ╚██╗ ', ' ╚██╗'],
|
|
91
|
+
'|': ['██╗ ', '██║ ', '██║ ', '██║ ', '██║ ', '╚═╝ '],
|
|
92
|
+
'^': [' ██╗ ', '████╗ ', '╚═██║ ', ' ╚═╝ ', ' ', ' '],
|
|
93
|
+
'&': [' █████╗ ', '██╔══██╗', '╚█████╔╝', '██╔═██╗ ', '╚██████║', ' ╚═════╝'],
|
|
94
|
+
'*': [' ', ' ▄ █ ▄ ', '██████╗ ', ' ▀ █ ▀ ', ' ', ' '],
|
|
95
|
+
'<': [' ██╗ ', ' ██╔╝ ', ' ██╔╝ ', ' ╚██╗ ', ' ╚██╗ ', ' ╚═╝ '],
|
|
96
|
+
'>': ['██╗ ', '╚██╗ ', ' ╚██╗ ', ' ██╔╝ ', '██╔╝ ', '╚═╝ '],
|
|
97
|
+
'~': [' ', '███╗ ██╗ ', '╚██████╔╝', ' ╚═══██║ ', ' ', ' '],
|
|
98
|
+
'`': ['██╗ ', '╚█╗ ', ' ╚╝ ', ' ', ' ', ' '],
|
|
99
|
+
'±': [' ', ' ██╗ ', '████████╗', '╚══██╔══╝', '████████╗', '╚═══════╝'],
|
|
100
|
+
'×': [' ', '██╗ ██╗ ', '╚████╔██╗', '██╔███╔╝ ', '██╔╝╚██╗ ', '╚═╝ ╚═╝ '],
|
|
101
|
+
'÷': [' ', ' ██╗ ', '████████╗', '╚══██╔══╝', ' ██║ ', ' ╚═╝ '],
|
|
102
|
+
'°': ['██████╗ ', '██╔══██║', '╚██████║', ' ╚═════╝', ' ', ' '],
|
|
103
|
+
'€': [' █████╗ ', '██╔═══╝ ', '█████╗ ', '██╔══╝ ', '╚██████╗', ' ╚═════╝'],
|
|
104
|
+
'£': [' █████╗ ', '██╔══██╗', '█████╔═╝', '██╔══╝ ', '███████╗', '╚══════╝'],
|
|
105
|
+
'¥': ['██╗ ██╗', '╚██╗ ██╔╝', '████████╗', '╚══██╔══╝', ' ██║ ', ' ╚═╝ '],
|
|
106
|
+
'¢': [' ██╗ ', ' ██████╗ ', '██╔════╝ ', '██║ ', '╚██████╗ ', ' ╚══╝ '],
|
|
107
|
+
'§': [' █████╗ ', '██╔═══╝ ', '███████╗', '╚════██║', '███████║', '╚══════╝'],
|
|
108
|
+
'©': [' ██████╗ ', '██╔═══██╗ ', '██║ ▄▄██║ ', '██║ ╚═╝██║', '╚██████╔╝ ', ' ╚═════╝ '],
|
|
109
|
+
'®': [' ██████╗ ', '██╔══██╗ ', '██████╔╔╝', '██╔══██╗ ', '██║ ██║ ', '╚═╝ ╚═╝ '],
|
|
110
|
+
'™': ['████████╗███╗████ ', '╚══██╔══╝████║█ ', ' ██║ ██╔═█═█║█', ' ██║ ██║ █ █║█', ' ██║ ██║ █ ║ ', ' ╚═╝ ╚═╝ █ ╝█ '],
|
|
111
|
+
'¿': [' ██╗ ', ' ██╔╝ ', ' ███╔╝ ', '██╔══██╗ ', '╚█████╔╝ ', ' ╚════╝ '],
|
|
112
|
+
'¡': [' ██╗ ', ' ╚═╝ ', ' ██╗ ', ' ██║ ', ' ██║ ', ' ╚═╝ '],
|
|
113
|
+
'→': [' ', ' ██╗ ', ' ████╗', '██████╔═╝', '╚═════╝ ', ' '],
|
|
114
|
+
'←': [' ', ' ██╗ ', '████╗ ', '╚═██████╗', ' ', ' '],
|
|
115
|
+
'↑': [' ██╗ ', ' ███╗ ', '████████║', '╚═══██║ ', ' ██║ ', ' ╚═╝ '],
|
|
116
|
+
'↓': [' ██╗ ', ' ██║ ', ' ██║ ', '██████║ ', '╚═███╔╝ ', ' ╚═╝ '],
|
|
117
|
+
'☺': [' ██████╗ ', '██╔██╗██╗ ', '██║ ╚═██║', '██║ ╔══██║', '╚██████╔╝ ', ' ╚═════╝ '],
|
|
118
|
+
'☹': [' ██████╗ ', '██╔██╗██╗ ', '██║ ██║', '██║ ╔══██║', '╚██████╔╝ ', ' ╚═════╝ '],
|
|
119
|
+
'♥': [' ', ' ██╗ ██╗ ', '███████╗ ', '╚██████╔╝', ' ╚████╔╝ ', ' ╚══╝ '],
|
|
120
|
+
'♦': [' ██╗ ', ' ████╗ ', ' ██████╗ ', '╚██████╔╝', ' ╚██╔╝ ', ' ╚═╝ '],
|
|
121
|
+
'♠': [' ██╗ ', ' ████╗ ', ' ██████╗ ', '╚██████╔╝', ' ██║ ', ' ████╗ '],
|
|
122
|
+
'♣': [' ████╗ ', ' ██████╗ ', '╚██████╔╝', ' ██║ ', ' ████╗ ', ' ╚════╝ '],
|
|
123
|
+
'α': [' ', ' █ ', ' ██████╗ ', '██╔══██╗ ', '╚██████║ ', ' ╚════█═╝'],
|
|
124
|
+
'β': ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██████╔╝', '█═════╝ '],
|
|
125
|
+
'γ': ['█ █ ', ' █ █ ', '███████╗ ', '╚══██╔══╝ ', '███╔╝ ', '╚══╝ '],
|
|
126
|
+
'δ': [' ████╗ ', '██╔══██╗ ', '███║ ██║ ', '██║ ██║ ', '╚██████╔╝', ' ╚═════╝ '],
|
|
127
|
+
'π': [' ', '█████████╗ ', '╚══██╔═█═╝ ', ' ██║ █ ', ' ██║ ██ ', ' ╚═╝ ╚═╝ '],
|
|
128
|
+
'Σ': ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
|
|
129
|
+
'Ω': [' ██████╗ ', '██╔═══██╗', '██║ ██║', '╚██╗ ██╔╝', ' ╚═══╝ ', ' '],
|
|
130
|
+
'∞': [' ', '███╗ ███╗ ', '██████████║', '╚██╔═██╔══╝', ' ╚═╝ ╚═╝ ', ' '],
|
|
131
|
+
'∑': ['███████╗', '██╔════╝', ' █████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
|
|
132
|
+
'∆': [' ██╗ ', ' ████╗ ', ' ██╔═██╗ ', '██╔╝ ██╗', '████████╗', '╚═══════╝'],
|
|
133
|
+
'√': [' ██╗', ' ██╔╝', ' ██╔╝ ', '██ ██╔╝ ', '████╔╝ ', '╚══╝ '],
|
|
134
|
+
'≤': [' ██╗ ', ' ██╔╝ ', ' ██╔╝ ', '████╗ ', '╚██████╗', ' ╚═════╝'],
|
|
135
|
+
'≥': ['██╗ ', '╚██╗ ', ' ╚██╗ ', ' ██████╗', '██████╔╝', '╚═════╝ '],
|
|
136
|
+
'≠': [' ██╗ ', '███████╗ ', '╚══██╔══╝', '███████╗ ', '██╔════╝ ', '╚═╝ '],
|
|
137
|
+
'≈': [' ', '███╗ ██╗ ', '╚██████╔╝', '███╗ ██╗ ', '╚██████╔╝', ' ╚═════╝ '],
|
|
138
|
+
'∴': [' ', ' ██╗ ', '████████╗', '╚══██╔══╝', '███╗██╗ ', '╚══╝╚═╝ '],
|
|
139
|
+
'∵': ['███╗██╗ ', '╚══╝╚═╝ ', '████████╗', '╚══██╔══╝', ' ██╗ ', ' ╚═╝ '],
|
|
140
|
+
'∈': [' ', '██████╗ ', '██╔═══██╗', '██║ ████║', '╚██████╔╝', ' ╚═════╝ '],
|
|
141
|
+
'∉': [' ██╗ ', '██████╔╝ ', '██╔═█████╗ ', '██║██╔══██║', '╚██████╔╝ ', ' ╚═════╝ '],
|
|
142
|
+
'⊂': [' ', ' ██████╗ ', '██╔════╝ ', '██║ ', '╚██████╗ ', ' ╚═════╝ '],
|
|
143
|
+
'⊃': [' ', ' ██████╗ ', ' ╚═════██║', ' ██║', ' ██████╔╝ ', ' ╚═════╝ '],
|
|
144
|
+
'∪': [' ', '██╗ ██╗', '██║ ██║', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
|
|
145
|
+
'∩': [' ', ' ██████╗ ', '██╔════██╗', '██║ ██║', '██║ ██║', '╚═╝ ╚═╝'],
|
|
146
|
+
'\n': [' ', ' ', ' ', ' ', ' ', ' '],
|
|
147
|
+
'\t': [' ', ' ', ' ', ' ', ' ', ' '],
|
|
148
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Spinner 动画引擎 - 绿→蓝单向渐变版
|
|
3
|
+
|
|
4
|
+
关键修复:
|
|
5
|
+
- 用 ANSI 隐藏/显示光标 + \\r 覆盖同一行,确保动画在原地更新不换行
|
|
6
|
+
- stop() 后恢复光标并清除 spinner 行
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
import math
|
|
14
|
+
import threading
|
|
15
|
+
from typing import List, Tuple, Dict, Callable, Optional
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from wcwidth import wcswidth as _wcswidth
|
|
19
|
+
def _display_width(s: str) -> int:
|
|
20
|
+
w = _wcswidth(s)
|
|
21
|
+
return w if w >= 0 else len(s)
|
|
22
|
+
except ImportError:
|
|
23
|
+
def _display_width(s: str) -> int:
|
|
24
|
+
return len(s)
|
|
25
|
+
|
|
26
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
+
# 全局速度控制
|
|
28
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
SPEED_MULTIPLIER = 1.5
|
|
30
|
+
FRAME_INTERVAL_MS = 16
|
|
31
|
+
|
|
32
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
33
|
+
# ANSI 序列
|
|
34
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
35
|
+
HIDE_CURSOR = "\033[?25l"
|
|
36
|
+
SHOW_CURSOR = "\033[?25h"
|
|
37
|
+
CLEAR_LINE = "\033[2K"
|
|
38
|
+
RESET_COLOR = "\033[0m"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _enable_win_ansi():
|
|
42
|
+
"""Windows 下启用 ANSI 转义支持。"""
|
|
43
|
+
if platform.system() != "Windows":
|
|
44
|
+
return
|
|
45
|
+
try:
|
|
46
|
+
import ctypes
|
|
47
|
+
kernel32 = ctypes.windll.kernel32
|
|
48
|
+
handle = kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE
|
|
49
|
+
mode = ctypes.c_ulong()
|
|
50
|
+
kernel32.GetConsoleMode(handle, ctypes.byref(mode))
|
|
51
|
+
# ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
|
52
|
+
kernel32.SetConsoleMode(handle, mode.value | 0x0004)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
# 备用:触发 os.system("") 也能激活
|
|
56
|
+
os.system("")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_enable_win_ansi()
|
|
60
|
+
|
|
61
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
62
|
+
# 字符集
|
|
63
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
64
|
+
|
|
65
|
+
def get_default_characters() -> List[str]:
|
|
66
|
+
term = os.environ.get('TERM', '')
|
|
67
|
+
if term == 'xterm-ghostty':
|
|
68
|
+
return ['·', '✢', '✳', '✶', '✻', '*']
|
|
69
|
+
if platform.system() == 'Darwin':
|
|
70
|
+
return ['·', '✢', '✳', '✶', '✻', '✽']
|
|
71
|
+
else:
|
|
72
|
+
return ['·', '✢', '*', '✶', '✻', '✽']
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def to_rgb_color_str(color: Tuple[int, int, int]) -> str:
|
|
76
|
+
return f"\033[38;2;{color[0]};{color[1]};{color[2]}m"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _lerp(a: int, b: int, t: float) -> int:
|
|
80
|
+
return round(a + (b - a) * t)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
84
|
+
# 主题:绿 → 蓝 单向渐变
|
|
85
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
86
|
+
|
|
87
|
+
GREEN_START: Tuple[int, int, int] = (0, 230, 160)
|
|
88
|
+
BLUE_END: Tuple[int, int, int] = (0, 160, 255)
|
|
89
|
+
|
|
90
|
+
TRANSITION_DURATION_MS = 1800 / SPEED_MULTIPLIER
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _theme_green_to_blue(elapsed_ms: float) -> Tuple[int, int, int]:
|
|
94
|
+
t = min(max(elapsed_ms / TRANSITION_DURATION_MS, 0.0), 1.0)
|
|
95
|
+
return (
|
|
96
|
+
_lerp(GREEN_START[0], BLUE_END[0], t),
|
|
97
|
+
_lerp(GREEN_START[1], BLUE_END[1], t),
|
|
98
|
+
_lerp(GREEN_START[2], BLUE_END[2], t),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
THEMES: Dict[str, Callable[[float], Tuple[int, int, int]]] = {
|
|
103
|
+
"green_blue": _theme_green_to_blue,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
108
|
+
# 时钟系统
|
|
109
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Clock:
|
|
113
|
+
def __init__(self, tick_interval_ms: int = FRAME_INTERVAL_MS):
|
|
114
|
+
self._subscribers: Dict[Callable[[], None], bool] = {}
|
|
115
|
+
self._current_tick_interval_ms = tick_interval_ms / SPEED_MULTIPLIER
|
|
116
|
+
self._start_time: float = 0.0
|
|
117
|
+
self._tick_time: float = 0.0
|
|
118
|
+
self._running = False
|
|
119
|
+
self._lock = threading.Lock()
|
|
120
|
+
self._thread: Optional[threading.Thread] = None
|
|
121
|
+
self._stop_event = threading.Event()
|
|
122
|
+
|
|
123
|
+
def _run_thread(self) -> None:
|
|
124
|
+
while not self._stop_event.is_set():
|
|
125
|
+
time.sleep(self._current_tick_interval_ms / 1000.0)
|
|
126
|
+
if self._running and not self._stop_event.is_set():
|
|
127
|
+
self._tick()
|
|
128
|
+
|
|
129
|
+
def _tick(self) -> None:
|
|
130
|
+
with self._lock:
|
|
131
|
+
if not self._running:
|
|
132
|
+
return
|
|
133
|
+
now_ms = time.perf_counter() * 1000
|
|
134
|
+
self._tick_time = now_ms - self._start_time
|
|
135
|
+
subscribers = list(self._subscribers.keys())
|
|
136
|
+
for on_change in subscribers:
|
|
137
|
+
try:
|
|
138
|
+
on_change()
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
def _update_interval(self) -> None:
|
|
143
|
+
any_keep_alive = False
|
|
144
|
+
with self._lock:
|
|
145
|
+
any_keep_alive = any(self._subscribers.values())
|
|
146
|
+
if any_keep_alive:
|
|
147
|
+
with self._lock:
|
|
148
|
+
if self._start_time == 0.0:
|
|
149
|
+
self._start_time = time.perf_counter() * 1000
|
|
150
|
+
if not self._running:
|
|
151
|
+
self._running = True
|
|
152
|
+
self._stop_event.clear()
|
|
153
|
+
self._thread = threading.Thread(target=self._run_thread, daemon=True)
|
|
154
|
+
self._thread.start()
|
|
155
|
+
else:
|
|
156
|
+
with self._lock:
|
|
157
|
+
if self._running:
|
|
158
|
+
self._running = False
|
|
159
|
+
self._stop_event.set()
|
|
160
|
+
|
|
161
|
+
def subscribe(self, on_change: Callable[[], None], keep_alive: bool = False) -> Callable[[], None]:
|
|
162
|
+
with self._lock:
|
|
163
|
+
self._subscribers[on_change] = keep_alive
|
|
164
|
+
self._update_interval()
|
|
165
|
+
|
|
166
|
+
def unsubscribe():
|
|
167
|
+
with self._lock:
|
|
168
|
+
if on_change in self._subscribers:
|
|
169
|
+
del self._subscribers[on_change]
|
|
170
|
+
self._update_interval()
|
|
171
|
+
return unsubscribe
|
|
172
|
+
|
|
173
|
+
def now(self) -> float:
|
|
174
|
+
current_time = time.perf_counter() * 1000
|
|
175
|
+
with self._lock:
|
|
176
|
+
if self._start_time == 0.0:
|
|
177
|
+
self._start_time = current_time
|
|
178
|
+
if self._running and self._tick_time > 0:
|
|
179
|
+
return self._tick_time
|
|
180
|
+
return current_time - self._start_time
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
184
|
+
# 动画帧状态
|
|
185
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class AnimationFrameState:
|
|
189
|
+
def __init__(self, clock: Clock, interval_ms: float = 120):
|
|
190
|
+
self._clock = clock
|
|
191
|
+
self._interval_ms = interval_ms / SPEED_MULTIPLIER
|
|
192
|
+
self._time: float = 0.0
|
|
193
|
+
self._active = False
|
|
194
|
+
self._unsubscribe: Optional[Callable[[], None]] = None
|
|
195
|
+
self._last_update: float = 0.0
|
|
196
|
+
self._event = threading.Event()
|
|
197
|
+
|
|
198
|
+
def _on_change(self) -> None:
|
|
199
|
+
if not self._active:
|
|
200
|
+
return
|
|
201
|
+
now = self._clock.now()
|
|
202
|
+
if now - self._last_update >= self._interval_ms:
|
|
203
|
+
self._last_update = now
|
|
204
|
+
self._time = now
|
|
205
|
+
self._event.set()
|
|
206
|
+
|
|
207
|
+
def start(self) -> None:
|
|
208
|
+
if self._active:
|
|
209
|
+
return
|
|
210
|
+
self._active = True
|
|
211
|
+
self._time = self._clock.now()
|
|
212
|
+
self._last_update = self._time
|
|
213
|
+
self._unsubscribe = self._clock.subscribe(self._on_change, keep_alive=True)
|
|
214
|
+
|
|
215
|
+
def stop(self) -> None:
|
|
216
|
+
self._active = False
|
|
217
|
+
if self._unsubscribe is not None:
|
|
218
|
+
self._unsubscribe()
|
|
219
|
+
self._unsubscribe = None
|
|
220
|
+
self._event.set()
|
|
221
|
+
|
|
222
|
+
def get_time(self) -> float:
|
|
223
|
+
return self._time
|
|
224
|
+
|
|
225
|
+
def wait_for_update(self, timeout: float = 0.001) -> bool:
|
|
226
|
+
return self._event.wait(timeout)
|
|
227
|
+
|
|
228
|
+
def clear_event(self) -> None:
|
|
229
|
+
self._event.clear()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
233
|
+
# Spinner 主类
|
|
234
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
235
|
+
|
|
236
|
+
DEFAULT_CHARACTERS = get_default_characters()
|
|
237
|
+
SPINNER_FRAMES = DEFAULT_CHARACTERS + list(reversed(DEFAULT_CHARACTERS))
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class Spinner:
|
|
241
|
+
"""
|
|
242
|
+
彩色终端加载动画。
|
|
243
|
+
|
|
244
|
+
核心行为:
|
|
245
|
+
- start() 时隐藏光标,颜色从绿色开始
|
|
246
|
+
- 后台线程用 \\r 回到行首覆盖输出,保证动画在同一行原地更新
|
|
247
|
+
- stop() 时清除 spinner 行、恢复光标
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def __init__(
|
|
251
|
+
self,
|
|
252
|
+
clock: Clock,
|
|
253
|
+
reduced_motion: bool = False,
|
|
254
|
+
columns: int = 80,
|
|
255
|
+
message: str = "Working",
|
|
256
|
+
theme: str = "green_blue",
|
|
257
|
+
):
|
|
258
|
+
self._clock = clock
|
|
259
|
+
self._reduced_motion = reduced_motion
|
|
260
|
+
self._columns = columns
|
|
261
|
+
self._message = message
|
|
262
|
+
self._message_lock = threading.Lock()
|
|
263
|
+
self._anim_frame = AnimationFrameState(clock, interval_ms=120)
|
|
264
|
+
self._running = False
|
|
265
|
+
self._frame_index = 0
|
|
266
|
+
self._time_ms: float = 0.0
|
|
267
|
+
self._start_offset_ms: float = 0.0
|
|
268
|
+
|
|
269
|
+
# stdout 写锁:防止后台渲染线程和主线程(stop)竞争 stdout
|
|
270
|
+
self._write_lock = threading.Lock()
|
|
271
|
+
|
|
272
|
+
self.set_theme(theme)
|
|
273
|
+
|
|
274
|
+
def set_theme(self, theme: str) -> None:
|
|
275
|
+
if theme not in THEMES:
|
|
276
|
+
theme = "green_blue"
|
|
277
|
+
self._theme = theme
|
|
278
|
+
self._theme_fn = THEMES[theme]
|
|
279
|
+
|
|
280
|
+
def update_message(self, message: str) -> None:
|
|
281
|
+
with self._message_lock:
|
|
282
|
+
self._message = message
|
|
283
|
+
|
|
284
|
+
def start(self) -> None:
|
|
285
|
+
if self._running:
|
|
286
|
+
return
|
|
287
|
+
self._running = True
|
|
288
|
+
self._start_offset_ms = self._clock.now()
|
|
289
|
+
# 隐藏光标,避免光标在 spinner 字符旁闪烁
|
|
290
|
+
with self._write_lock:
|
|
291
|
+
sys.stdout.write(HIDE_CURSOR)
|
|
292
|
+
sys.stdout.flush()
|
|
293
|
+
self._anim_frame.start()
|
|
294
|
+
threading.Thread(target=self._run_loop, daemon=True).start()
|
|
295
|
+
|
|
296
|
+
def stop(self) -> None:
|
|
297
|
+
was_running = self._running
|
|
298
|
+
self._running = False
|
|
299
|
+
self._anim_frame.stop()
|
|
300
|
+
time.sleep(0.02)
|
|
301
|
+
|
|
302
|
+
with self._write_lock:
|
|
303
|
+
if was_running:
|
|
304
|
+
sys.stdout.write(f"\r{CLEAR_LINE}{SHOW_CURSOR}")
|
|
305
|
+
sys.stdout.flush()
|
|
306
|
+
|
|
307
|
+
def _run_loop(self) -> None:
|
|
308
|
+
while self._running:
|
|
309
|
+
updated = self._anim_frame.wait_for_update(timeout=0.050)
|
|
310
|
+
if updated:
|
|
311
|
+
self._anim_frame.clear_event()
|
|
312
|
+
if not self._running:
|
|
313
|
+
break
|
|
314
|
+
self._time_ms = self._anim_frame.get_time()
|
|
315
|
+
self._render_frame()
|
|
316
|
+
|
|
317
|
+
def _render_frame(self) -> None:
|
|
318
|
+
if not self._running:
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
elapsed = max(0.0, self._time_ms - self._start_offset_ms)
|
|
322
|
+
|
|
323
|
+
frame_interval = 120 / SPEED_MULTIPLIER
|
|
324
|
+
if not self._reduced_motion:
|
|
325
|
+
self._frame_index = int(math.floor(elapsed / frame_interval)) % len(SPINNER_FRAMES)
|
|
326
|
+
else:
|
|
327
|
+
self._frame_index = 0
|
|
328
|
+
char = SPINNER_FRAMES[self._frame_index]
|
|
329
|
+
color = self._theme_fn(elapsed)
|
|
330
|
+
|
|
331
|
+
color_code = to_rgb_color_str(color)
|
|
332
|
+
with self._message_lock:
|
|
333
|
+
message = self._message
|
|
334
|
+
visible_len = _display_width(char) + 1 + _display_width(message)
|
|
335
|
+
padding = max(0, self._columns - visible_len)
|
|
336
|
+
# 关键:\r 回到行首,CLEAR_LINE 清除残留,不写 \n
|
|
337
|
+
line = f"\r{CLEAR_LINE}{color_code}{char}{RESET_COLOR} {color_code}{message}{RESET_COLOR}{' ' * padding}"
|
|
338
|
+
|
|
339
|
+
with self._write_lock:
|
|
340
|
+
if self._running: # 二次检查,防止 stop 已经调了
|
|
341
|
+
sys.stdout.write(line)
|
|
342
|
+
sys.stdout.flush()
|