python-tty 0.1.2rc2__tar.gz → 0.1.5__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.
Files changed (82) hide show
  1. python_tty-0.1.5/.github/workflows/python-publish.yml +115 -0
  2. python_tty-0.1.5/.gitignore +317 -0
  3. {python_tty-0.1.2rc2/src/python_tty.egg-info → python_tty-0.1.5}/PKG-INFO +5 -1
  4. python_tty-0.1.5/demos/file_manager/__init__.py +12 -0
  5. python_tty-0.1.5/demos/file_manager/commands/__init__.py +188 -0
  6. python_tty-0.1.5/demos/file_manager/commands/root_commands.py +51 -0
  7. python_tty-0.1.5/demos/file_manager/consoles/__init__.py +75 -0
  8. python_tty-0.1.5/demos/file_manager/consoles/root.py +31 -0
  9. python_tty-0.1.5/demos/file_manager/core/__init__.py +0 -0
  10. python_tty-0.1.5/demos/file_manager/core/file_manager.py +92 -0
  11. python_tty-0.1.5/demos/file_manager/exceptions/__init__.py +0 -0
  12. python_tty-0.1.5/demos/file_manager/main.py +4 -0
  13. python_tty-0.1.5/demos/file_manager/setup.py +14 -0
  14. python_tty-0.1.5/demos/file_manager/utils/__init__.py +0 -0
  15. python_tty-0.1.5/demos/file_manager/utils/table.py +124 -0
  16. python_tty-0.1.5/docs/LOG.md +351 -0
  17. python_tty-0.1.5/docs/context.md +2995 -0
  18. python_tty-0.1.5/pyproject.toml +5 -0
  19. python_tty-0.1.2rc2/src/python_tty.egg-info/requires.txt → python_tty-0.1.5/requirements.txt +2 -0
  20. {python_tty-0.1.2rc2 → python_tty-0.1.5}/setup.py +6 -1
  21. python_tty-0.1.5/src/python_tty/__init__.py +25 -0
  22. python_tty-0.1.5/src/python_tty/audit/__init__.py +7 -0
  23. python_tty-0.1.5/src/python_tty/audit/sink.py +141 -0
  24. {python_tty-0.1.2rc2/src/python_tty/utils → python_tty-0.1.5/src/python_tty/audit}/ui_logger.py +3 -4
  25. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/examples/root_commands.py +2 -3
  26. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/mixins.py +3 -4
  27. python_tty-0.1.5/src/python_tty/config/__init__.py +16 -0
  28. python_tty-0.1.5/src/python_tty/config/config.py +92 -0
  29. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/console_factory.py +52 -3
  30. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/core.py +5 -5
  31. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/manager.py +5 -5
  32. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/registry.py +33 -0
  33. python_tty-0.1.5/src/python_tty/exceptions/console_exception.py +12 -0
  34. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/executor/executor.py +115 -16
  35. python_tty-0.1.5/src/python_tty/frontends/__init__.py +0 -0
  36. python_tty-0.1.5/src/python_tty/frontends/rpc/__init__.py +0 -0
  37. python_tty-0.1.5/src/python_tty/frontends/web/__init__.py +0 -0
  38. python_tty-0.1.5/src/python_tty/meta/__init__.py +96 -0
  39. python_tty-0.1.5/src/python_tty/runtime/__init__.py +29 -0
  40. python_tty-0.1.5/src/python_tty/runtime/events.py +147 -0
  41. python_tty-0.1.5/src/python_tty/runtime/provider.py +62 -0
  42. python_tty-0.1.2rc2/src/python_tty/ui/output.py → python_tty-0.1.5/src/python_tty/runtime/router.py +53 -11
  43. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/utils/__init__.py +1 -3
  44. {python_tty-0.1.2rc2 → python_tty-0.1.5/src/python_tty.egg-info}/PKG-INFO +5 -1
  45. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty.egg-info/SOURCES.txt +30 -4
  46. python_tty-0.1.5/src/python_tty.egg-info/requires.txt +6 -0
  47. python_tty-0.1.5/tests/__init__.py +0 -0
  48. python_tty-0.1.2rc2/pyproject.toml +0 -3
  49. python_tty-0.1.2rc2/src/python_tty/__init__.py +0 -15
  50. python_tty-0.1.2rc2/src/python_tty/config/__init__.py +0 -9
  51. python_tty-0.1.2rc2/src/python_tty/config/config.py +0 -35
  52. python_tty-0.1.2rc2/src/python_tty/ui/__init__.py +0 -13
  53. python_tty-0.1.2rc2/src/python_tty/ui/events.py +0 -55
  54. {python_tty-0.1.2rc2 → python_tty-0.1.5}/LICENSE +0 -0
  55. {python_tty-0.1.2rc2 → python_tty-0.1.5}/MANIFEST.in +0 -0
  56. {python_tty-0.1.2rc2 → python_tty-0.1.5}/NOTICE +0 -0
  57. {python_tty-0.1.2rc2 → python_tty-0.1.5}/README.md +0 -0
  58. {python_tty-0.1.2rc2 → python_tty-0.1.5}/README_zh.md +0 -0
  59. {python_tty-0.1.2rc2/src/python_tty/frontends → python_tty-0.1.5/demos}/__init__.py +0 -0
  60. {python_tty-0.1.2rc2/src/python_tty/meta → python_tty-0.1.5/demos/chat_room}/__init__.py +0 -0
  61. {python_tty-0.1.2rc2/src/python_tty → python_tty-0.1.5/demos/file_manager}/exceptions/console_exception.py +0 -0
  62. {python_tty-0.1.2rc2 → python_tty-0.1.5}/setup.cfg +0 -0
  63. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/__init__.py +0 -0
  64. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/core.py +0 -0
  65. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/decorators.py +0 -0
  66. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/examples/__init__.py +0 -0
  67. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/examples/sub_commands.py +0 -0
  68. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/general.py +0 -0
  69. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/commands/registry.py +0 -0
  70. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/__init__.py +0 -0
  71. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/decorators.py +0 -0
  72. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/examples/__init__.py +0 -0
  73. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/examples/root_console.py +0 -0
  74. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/examples/sub_console.py +0 -0
  75. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/consoles/loader.py +0 -0
  76. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/exceptions/__init__.py +0 -0
  77. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/executor/__init__.py +0 -0
  78. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/executor/models.py +0 -0
  79. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/utils/table.py +0 -0
  80. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty/utils/tokenize.py +0 -0
  81. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty.egg-info/dependency_links.txt +0 -0
  82. {python_tty-0.1.2rc2 → python_tty-0.1.5}/src/python_tty.egg-info/top_level.txt +0 -0
@@ -0,0 +1,115 @@
1
+ name: Build & Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ # workflow 级最小权限;发布 job 再单独提升 id-token
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ outputs:
16
+ is_prerelease: ${{ steps.tag.outputs.is_prerelease }}
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.11"
25
+
26
+ - name: Upgrade packaging tools
27
+ run: |
28
+ python -m pip install -U pip setuptools wheel
29
+
30
+ - id: tag
31
+ name: Classify tag
32
+ shell: bash
33
+ run: |
34
+ set -euo pipefail
35
+ TAG="${GITHUB_REF_NAME}"
36
+ # 仅基于 tag 名判断(不含 refs/tags 前缀)
37
+ # 允许:
38
+ # vX.Y.Z
39
+ # vX.Y.ZaN / vX.Y.ZbN / vX.Y.ZrcN
40
+ # vX.Y.Z.devN 或 vX.Y.ZdevN(两种都兼容)
41
+ if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+((a|b|rc)[0-9]+|(\.dev|dev)[0-9]+)$ ]]; then
42
+ echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
43
+ else
44
+ echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
45
+ fi
46
+
47
+ - name: Build dists
48
+ run: |
49
+ python -m pip install -U build
50
+ python -m build
51
+ - name: Verify version matches tag
52
+ shell: bash
53
+ run: |
54
+ set -euo pipefail
55
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
56
+
57
+ python -m pip install -U pip
58
+ python -m pip install dist/*.whl
59
+
60
+ PKG_VERSION="$(python -c "import importlib.metadata as m; print(m.version('python-tty'))")"
61
+
62
+ echo "tag: $TAG_VERSION"
63
+ echo "pkg: $PKG_VERSION"
64
+
65
+ if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
66
+ echo "ERROR: Tag version ($TAG_VERSION) != package version ($PKG_VERSION)"
67
+ exit 1
68
+ fi
69
+
70
+ - name: Upload dist artifact
71
+ uses: actions/upload-artifact@v4
72
+ with:
73
+ name: dist
74
+ path: dist/*
75
+
76
+ publish_testpypi:
77
+ needs: build
78
+ if: needs.build.outputs.is_prerelease == 'true'
79
+ runs-on: ubuntu-latest
80
+ environment:
81
+ name: testpypi
82
+ permissions:
83
+ id-token: write
84
+ contents: read
85
+ steps:
86
+ - uses: actions/download-artifact@v4
87
+ with:
88
+ name: dist
89
+ path: dist
90
+
91
+ - name: Publish to TestPyPI (Trusted Publishing)
92
+ uses: pypa/gh-action-pypi-publish@release/v1
93
+ with:
94
+ repository-url: https://test.pypi.org/legacy/
95
+ skip-existing: true
96
+ verbose: true
97
+
98
+ publish_pypi:
99
+ needs: build
100
+ if: needs.build.outputs.is_prerelease != 'true'
101
+ runs-on: ubuntu-latest
102
+ environment:
103
+ name: pypi
104
+ url: https://pypi.org/p/python-tty
105
+ permissions:
106
+ id-token: write
107
+ contents: read
108
+ steps:
109
+ - uses: actions/download-artifact@v4
110
+ with:
111
+ name: dist
112
+ path: dist
113
+
114
+ - name: Publish to PyPI (Trusted Publishing)
115
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,317 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+ virEnv/
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py.cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ # Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ # uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ # poetry.lock
110
+ # poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ # pdm.lock
117
+ # pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ # pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi
127
+
128
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
129
+ __pypackages__/
130
+
131
+ # Celery stuff
132
+ celerybeat-schedule
133
+ celerybeat.pid
134
+
135
+ # Redis
136
+ *.rdb
137
+ *.aof
138
+ *.pid
139
+
140
+ # RabbitMQ
141
+ mnesia/
142
+ rabbitmq/
143
+ rabbitmq-data/
144
+
145
+ # ActiveMQ
146
+ activemq-data/
147
+
148
+ # SageMath parsed files
149
+ *.sage.py
150
+
151
+ # Environments
152
+ .env
153
+ .envrc
154
+ .venv
155
+ env/
156
+ venv/
157
+ ENV/
158
+ env.bak/
159
+ venv.bak/
160
+
161
+ # Spyder project settings
162
+ .spyderproject
163
+ .spyproject
164
+
165
+ # Rope project settings
166
+ .ropeproject
167
+
168
+ # mkdocs documentation
169
+ /site
170
+
171
+ # mypy
172
+ .mypy_cache/
173
+ .dmypy.json
174
+ dmypy.json
175
+
176
+ # Pyre type checker
177
+ .pyre/
178
+
179
+ # pytype static type analyzer
180
+ .pytype/
181
+
182
+ # Cython debug symbols
183
+ cython_debug/
184
+
185
+ # PyCharm
186
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
187
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
188
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
189
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
190
+ # .idea/
191
+
192
+ # Abstra
193
+ # Abstra is an AI-powered process automation framework.
194
+ # Ignore directories containing user credentials, local state, and settings.
195
+ # Learn more at https://abstra.io/docs
196
+ .abstra/
197
+
198
+ # Visual Studio Code
199
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
200
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
201
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
202
+ # you could uncomment the following to ignore the entire vscode folder
203
+ # .vscode/
204
+
205
+ # Ruff stuff:
206
+ .ruff_cache/
207
+
208
+ # PyPI configuration file
209
+ .pypirc
210
+
211
+ # Marimo
212
+ marimo/_static/
213
+ marimo/_lsp/
214
+ __marimo__/
215
+
216
+ # Streamlit
217
+ .streamlit/secrets.toml
218
+
219
+ .vscode/*
220
+ !.vscode/settings.json
221
+ !.vscode/tasks.json
222
+ !.vscode/launch.json
223
+ !.vscode/extensions.json
224
+ !.vscode/*.code-snippets
225
+ !*.code-workspace
226
+
227
+ # Built Visual Studio Code Extensions
228
+ *.vsix
229
+
230
+ # Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
231
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
232
+
233
+ .idea/
234
+
235
+ # User-specific stuff
236
+ .idea/**/workspace.xml
237
+ .idea/**/tasks.xml
238
+ .idea/**/usage.statistics.xml
239
+ .idea/**/dictionaries
240
+ .idea/**/shelf
241
+
242
+ # AWS User-specific
243
+ .idea/**/aws.xml
244
+
245
+ # Generated files
246
+ .idea/**/contentModel.xml
247
+
248
+ # Sensitive or high-churn files
249
+ .idea/**/dataSources/
250
+ .idea/**/dataSources.ids
251
+ .idea/**/dataSources.local.xml
252
+ .idea/**/sqlDataSources.xml
253
+ .idea/**/dynamic.xml
254
+ .idea/**/uiDesigner.xml
255
+ .idea/**/dbnavigator.xml
256
+
257
+ # Gradle
258
+ .idea/**/gradle.xml
259
+ .idea/**/libraries
260
+
261
+ # Gradle and Maven with auto-import
262
+ # When using Gradle or Maven with auto-import, you should exclude module files,
263
+ # since they will be recreated, and may cause churn. Uncomment if using
264
+ # auto-import.
265
+ # .idea/artifacts
266
+ # .idea/compiler.xml
267
+ # .idea/jarRepositories.xml
268
+ # .idea/modules.xml
269
+ # .idea/*.iml
270
+ # .idea/modules
271
+ # *.iml
272
+ # *.ipr
273
+
274
+ # CMake
275
+ cmake-build-*/
276
+
277
+ # Mongo Explorer plugin
278
+ .idea/**/mongoSettings.xml
279
+
280
+ # File-based project format
281
+ *.iws
282
+
283
+ # IntelliJ
284
+ out/
285
+
286
+ # mpeltonen/sbt-idea plugin
287
+ .idea_modules/
288
+
289
+ # JIRA plugin
290
+ atlassian-ide-plugin.xml
291
+
292
+ # Cursive Clojure plugin
293
+ .idea/replstate.xml
294
+
295
+ # SonarLint plugin
296
+ .idea/sonarlint/
297
+ .idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119
298
+
299
+ # Crashlytics plugin (for Android Studio and IntelliJ)
300
+ com_crashlytics_export_strings.xml
301
+ crashlytics.properties
302
+ crashlytics-build.properties
303
+ fabric.properties
304
+
305
+ # Editor-based HTTP Client
306
+ .idea/httpRequests
307
+ http-client.private.env.json
308
+
309
+ # Android studio 3.1+ serialized cache file
310
+ .idea/caches/build_file_checksums.ser
311
+
312
+ # Apifox Helper cache
313
+ .idea/.cache/.Apifox_Helper
314
+ .idea/ApifoxUploaderProjectSetting.xml
315
+
316
+ # Github Copilot persisted session migrations, see: https://github.com/microsoft/copilot-intellij-feedback/issues/712#issuecomment-3322062215
317
+ .idea/**/copilot.data.migration.*.xml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-tty
3
- Version: 0.1.2rc2
3
+ Version: 0.1.5
4
4
  Summary: A multi-console TTY framework for complex CLI/TTY apps
5
5
  Home-page: https://github.com/ROOKIEMIE/python-tty
6
6
  Author: ROOKIEMIE
@@ -12,8 +12,12 @@ Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  License-File: NOTICE
15
+ Requires-Dist: fastapi>=0.110.0
16
+ Requires-Dist: grpcio>=1.60.0
15
17
  Requires-Dist: prompt_toolkit>=3.0.32
18
+ Requires-Dist: protobuf>=4.25.0
16
19
  Requires-Dist: tqdm
20
+ Requires-Dist: uvicorn>=0.27.0
17
21
  Dynamic: author
18
22
  Dynamic: classifier
19
23
  Dynamic: description
@@ -0,0 +1,12 @@
1
+ def injector(service):
2
+ def class_decorator(cls):
3
+ original_init = cls.__init__
4
+
5
+ def modified_init(self, *args, **kwargs):
6
+ self.service = service
7
+ original_init(self, *args, **kwargs)
8
+
9
+ cls.__init__ = modified_init
10
+ return cls
11
+
12
+ return class_decorator
@@ -0,0 +1,188 @@
1
+ import enum
2
+ import inspect
3
+ import re
4
+ import shlex
5
+ from abc import ABC
6
+ from functools import wraps
7
+
8
+ from prompt_toolkit.completion import NestedCompleter
9
+ from prompt_toolkit.document import Document
10
+ from prompt_toolkit.validation import DummyValidator, Validator, ValidationError
11
+ from demos.file_manager.consoles import proxy_print
12
+ from demos.file_manager.exceptions.console_exception import ConsoleExit, ConsoleInitException
13
+ from demos.file_manager.utils.table import Table
14
+
15
+
16
+ class CommandStyle(enum.Enum):
17
+ NONE = 0 # ClassName => ClassName
18
+ LOWERCASE = 1 # ClassName => classname
19
+ UPPERCASE = 2 # ClassName => CLASSNAME
20
+ POWERSHELL = 3 # ClassName => Class-Name
21
+ SLUGIFIED = 4 # ClassName => class-name
22
+
23
+
24
+ class CommandInfo:
25
+ def __init__(self, func_name, func_description,
26
+ completer=None, validator=None,
27
+ command_alias=None):
28
+ self.func_name = func_name
29
+ self.func_description = func_description
30
+ self.completer = completer
31
+ self.validator = validator
32
+ if command_alias is None:
33
+ self.alias = []
34
+ else:
35
+ if type(command_alias) == str:
36
+ self.alias = [command_alias]
37
+ elif type(command_alias) == list:
38
+ self.alias = command_alias
39
+ else:
40
+ self.alias = []
41
+
42
+
43
+ def define_command_style(command_name, style):
44
+ if style == CommandStyle.NONE:
45
+ return command_name
46
+ elif style == CommandStyle.LOWERCASE:
47
+ return command_name.lower()
48
+ elif style == CommandStyle.UPPERCASE:
49
+ return command_name.upper()
50
+ command_name = re.sub(r'(.)([A-Z][a-z]+)', r'\1-\2', command_name)
51
+ command_name = re.sub(r'([a-z0-9])([A-Z])', r'\1-\2', command_name)
52
+ if style == CommandStyle.POWERSHELL:
53
+ return command_name
54
+ elif style == CommandStyle.SLUGIFIED:
55
+ return command_name.lower()
56
+
57
+
58
+ def register_command(command_name: str, command_description: str, command_alias=None,
59
+ command_style=CommandStyle.LOWERCASE,
60
+ completer=None, validator=None):
61
+ def inner_wrapper(func):
62
+ func.info = CommandInfo(define_command_style(command_name, command_style), command_description,
63
+ completer, validator, command_alias)
64
+ func.type = None
65
+
66
+ @wraps(func)
67
+ def wrapper(*args, **kwargs):
68
+ result = func(*args, **kwargs)
69
+ return result
70
+
71
+ return wrapper
72
+
73
+ return inner_wrapper
74
+
75
+
76
+ class GeneralValidator(Validator, ABC):
77
+ def __init__(self, console, func):
78
+ self.console = console
79
+ self.func = func
80
+ super().__init__()
81
+
82
+
83
+ class NoneArgumentValidator(GeneralValidator):
84
+ def __init__(self, console, func):
85
+ super().__init__(console, func)
86
+
87
+ def validate(self, document: Document) -> None:
88
+ sig = inspect.signature(self.func)
89
+ name_list = []
90
+ for name, param in sig.parameters.items():
91
+ name_list.append(name)
92
+ args = shlex.split(document.text)
93
+ if len(args) != (len(name_list) - 1):
94
+ raise ValidationError(message="Too many arguments!")
95
+
96
+
97
+ class CommandValidator(Validator):
98
+ def __init__(self, command_validators: dict, enable_undefined_command=False):
99
+ self.command_validators = command_validators
100
+ self.enable_undefined_command = enable_undefined_command
101
+ super().__init__()
102
+
103
+ def validate(self, document: Document) -> None:
104
+ args_l = shlex.split(document.text.strip())
105
+ if len(args_l) <= 0:
106
+ return
107
+ cmd = args_l[0]
108
+ if cmd in self.command_validators.keys():
109
+ cmd_validator = self.command_validators[cmd]
110
+ cmd_validator.validate(Document(text=' '.join(args_l[1:])))
111
+ else:
112
+ if not self.enable_undefined_command:
113
+ raise ValidationError(message="Bad command")
114
+
115
+
116
+ class BaseCommands:
117
+ def __init__(self, console):
118
+ self.console = console
119
+ self.command_completers = {}
120
+ self.command_validators = {}
121
+ self.command_funcs = {}
122
+ self._init_funcs()
123
+ self.completer = NestedCompleter.from_nested_dict(self.command_completers)
124
+ self.validator = CommandValidator(self.command_validators, self.enable_undefined_command)
125
+
126
+ @property
127
+ def enable_undefined_command(self):
128
+ return False
129
+
130
+ def _init_funcs(self):
131
+ # Separate function and inner class
132
+ funcs = self._get_funcs()
133
+ self._collect_completer_and_validator(funcs)
134
+
135
+ def _get_funcs(self):
136
+ cls = self.__class__
137
+ funcs = []
138
+ for member_name in dir(cls):
139
+ # Skip all include '_' method
140
+ if member_name.startswith("_"):
141
+ continue
142
+ member = getattr(cls, member_name)
143
+ # Include 'type' attr should be collected
144
+ if (inspect.ismethod(member) or inspect.isfunction(member)) and hasattr(member, "type"):
145
+ funcs.append(member)
146
+ return funcs
147
+
148
+ def _collect_completer_and_validator(self, funcs):
149
+ if self.console is None:
150
+ raise ConsoleInitException("Console is None")
151
+ for func in funcs:
152
+ command_info: CommandInfo = func.info
153
+ self._map_components(command_info.func_name, func, command_info.completer, command_info.validator)
154
+ if len(command_info.alias) > 0:
155
+ for alia in command_info.alias:
156
+ self._map_components(alia, func, command_info.completer, command_info.validator)
157
+
158
+ def _map_components(self, command_name, func, completer, validator):
159
+ self.command_funcs[command_name] = func
160
+ if completer is None:
161
+ self.command_completers[command_name] = None
162
+ else:
163
+ self.command_completers[command_name] = completer(self.console)
164
+ if validator is None:
165
+ self.command_validators[command_name] = DummyValidator()
166
+ else:
167
+ self.command_validators[command_name] = validator(self.console, func)
168
+
169
+ @register_command("quit", "Quit Console", "exit", validator=NoneArgumentValidator)
170
+ def run_quit(self):
171
+ raise ConsoleExit
172
+
173
+ @register_command("help", "Display help information", ["?"], validator=NoneArgumentValidator)
174
+ def run_help(self):
175
+ header = ["Command", "Description"]
176
+ base_funcs = []
177
+ customer_funcs = []
178
+ base_commands_funcs = [member[1] for member in inspect.getmembers(BaseCommands, inspect.isfunction)]
179
+ for name, func in self.command_funcs.items():
180
+ if func in base_commands_funcs:
181
+ base_funcs.append([name, func.info.func_description])
182
+ else:
183
+ customer_funcs.append([name, func.info.func_description])
184
+ proxy_print("")
185
+ proxy_print(Table(header, base_funcs, "Core Commands"))
186
+ proxy_print("")
187
+ proxy_print(Table(header, customer_funcs, "Customer Commands"))
188
+ proxy_print("")
@@ -0,0 +1,51 @@
1
+ import inspect
2
+ import shlex
3
+
4
+ from prompt_toolkit.completion import WordCompleter
5
+ from prompt_toolkit.document import Document
6
+ from prompt_toolkit.validation import ValidationError
7
+
8
+ from demos.file_manager.commands import BaseCommands, register_command, GeneralValidator
9
+ from demos.file_manager.consoles import proxy_print
10
+
11
+
12
+ class RootCommands(BaseCommands):
13
+ # def enable_undefined_command(self):
14
+ # return True
15
+
16
+ class EnvValidator(GeneralValidator):
17
+ def __init__(self, console, func):
18
+ super().__init__(console, func)
19
+
20
+ def validate(self, document: Document) -> None:
21
+ sig = inspect.signature(self.func)
22
+ name_list = []
23
+ for name, param in sig.parameters.items():
24
+ name_list.append(name)
25
+ name_list = name_list[1:]
26
+ args = shlex.split(document.text)
27
+ if len(args) <= 0:
28
+ raise ValidationError(message="At least one parameter is missing")
29
+ elif len(args) > len(name_list):
30
+ raise ValidationError(message="Too many parameter to get")
31
+
32
+ class EnvCompleter(WordCompleter):
33
+ def __init__(self, console):
34
+ super().__init__(console.service.env_keys)
35
+
36
+ @register_command("env", "Display env information", [], completer=EnvCompleter, validator=EnvValidator)
37
+ def run_env(self, env_key):
38
+ proxy_print()
39
+ table = ""
40
+ if env_key == "disk":
41
+ table = self.console.service.display_disks()
42
+ elif env_key == "os":
43
+ table = self.console.service.display_os()
44
+ elif env_key == "network":
45
+ table = self.console.service.display_network()
46
+ proxy_print(table)
47
+ proxy_print()
48
+
49
+
50
+ if __name__ == '__main__':
51
+ pass