urwid 4.1.7__tar.gz → 4.2.1__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 (119) hide show
  1. {urwid-4.1.7 → urwid-4.2.1}/.github/workflows/documentation.yml +3 -12
  2. {urwid-4.1.7 → urwid-4.2.1}/.github/workflows/isolated_mypy.yml +2 -6
  3. {urwid-4.1.7 → urwid-4.2.1}/.github/workflows/isolated_pylint.yml +3 -12
  4. {urwid-4.1.7 → urwid-4.2.1}/.github/workflows/pythonpackage.yml +10 -54
  5. {urwid-4.1.7 → urwid-4.2.1}/.github/workflows/yamllint.yml +1 -1
  6. {urwid-4.1.7 → urwid-4.2.1}/PKG-INFO +2 -2
  7. {urwid-4.1.7 → urwid-4.2.1}/README.rst +1 -1
  8. {urwid-4.1.7 → urwid-4.2.1}/conftest.py +3 -0
  9. urwid-4.2.1/pylint-requirements.txt +1 -0
  10. {urwid-4.1.7 → urwid-4.2.1}/pyproject.toml +17 -12
  11. {urwid-4.1.7 → urwid-4.2.1}/urwid/__init__.py +4 -1
  12. {urwid-4.1.7 → urwid-4.2.1}/urwid/ansi_parser.py +27 -26
  13. {urwid-4.1.7 → urwid-4.2.1}/urwid/canvas.py +61 -74
  14. {urwid-4.1.7 → urwid-4.2.1}/urwid/command_map.py +13 -6
  15. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/_posix_raw_display.py +15 -31
  16. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/_raw_display_base.py +39 -54
  17. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/_win32.py +17 -13
  18. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/_win32_raw_display.py +10 -12
  19. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/common.py +98 -33
  20. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/curses.py +19 -25
  21. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/escape.py +11 -6
  22. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/html_fragment.py +32 -29
  23. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/lcd.py +46 -46
  24. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/raw.py +1 -3
  25. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/web.py +24 -65
  26. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/abstract_loop.py +14 -23
  27. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/asyncio_loop.py +8 -14
  28. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/glib_loop.py +21 -18
  29. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/main_loop.py +59 -69
  30. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/select_loop.py +11 -20
  31. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/tornado_loop.py +13 -8
  32. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/trio_loop.py +27 -31
  33. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/twisted_loop.py +14 -24
  34. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/zmq_loop.py +35 -37
  35. {urwid-4.1.7 → urwid-4.2.1}/urwid/font.py +30 -4
  36. {urwid-4.1.7 → urwid-4.2.1}/urwid/graphics.py +7 -10
  37. {urwid-4.1.7 → urwid-4.2.1}/urwid/numedit.py +12 -11
  38. {urwid-4.1.7 → urwid-4.2.1}/urwid/signals.py +32 -36
  39. {urwid-4.1.7 → urwid-4.2.1}/urwid/split_repr.py +8 -8
  40. {urwid-4.1.7 → urwid-4.2.1}/urwid/str_util.py +12 -10
  41. {urwid-4.1.7 → urwid-4.2.1}/urwid/text_layout.py +233 -26
  42. {urwid-4.1.7 → urwid-4.2.1}/urwid/util.py +38 -42
  43. {urwid-4.1.7 → urwid-4.2.1}/urwid/version.py +3 -3
  44. {urwid-4.1.7 → urwid-4.2.1}/urwid/vterm.py +122 -146
  45. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/__init__.py +2 -0
  46. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/ansi_text.py +9 -10
  47. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/attr_map.py +10 -10
  48. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/attr_wrap.py +12 -8
  49. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/bar_graph.py +30 -41
  50. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/big_text.py +11 -4
  51. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/box_adapter.py +14 -9
  52. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/columns.py +20 -26
  53. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/constants.py +26 -24
  54. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/container.py +37 -48
  55. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/deque_walker.py +10 -9
  56. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/divider.py +3 -3
  57. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/edit.py +72 -68
  58. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/filler.py +6 -4
  59. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/frame.py +21 -26
  60. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/grid_flow.py +27 -25
  61. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/line_box.py +6 -1
  62. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/list_walker.py +31 -16
  63. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/listbox.py +36 -36
  64. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/monitored_deque.py +36 -20
  65. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/monitored_list.py +63 -38
  66. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/overlay.py +20 -11
  67. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/padding.py +13 -17
  68. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/pile.py +16 -14
  69. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/popup.py +19 -8
  70. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/progress_bar.py +13 -12
  71. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/scrollable.py +36 -15
  72. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/solid_fill.py +3 -3
  73. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/text.py +37 -46
  74. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/treetools.py +43 -22
  75. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/widget.py +73 -52
  76. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/widget_decoration.py +17 -7
  77. {urwid-4.1.7 → urwid-4.2.1}/urwid/widget/wimp.py +31 -20
  78. {urwid-4.1.7 → urwid-4.2.1}/urwid.egg-info/PKG-INFO +2 -2
  79. {urwid-4.1.7 → urwid-4.2.1}/urwid.egg-info/scm_file_list.json +2 -0
  80. urwid-4.2.1/urwid.egg-info/scm_version.json +8 -0
  81. urwid-4.1.7/pylint-requirements.txt +0 -1
  82. urwid-4.1.7/urwid.egg-info/scm_version.json +0 -8
  83. {urwid-4.1.7 → urwid-4.2.1}/.devcontainer/dev.dockerfile +0 -0
  84. {urwid-4.1.7 → urwid-4.2.1}/.devcontainer/devcontainer.json +0 -0
  85. {urwid-4.1.7 → urwid-4.2.1}/.dockerignore +0 -0
  86. {urwid-4.1.7 → urwid-4.2.1}/.editorconfig +0 -0
  87. {urwid-4.1.7 → urwid-4.2.1}/.github/CODE_OF_CONDUCT.md +0 -0
  88. {urwid-4.1.7 → urwid-4.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  89. {urwid-4.1.7 → urwid-4.2.1}/.github/ISSUE_TEMPLATE/docs.md +0 -0
  90. {urwid-4.1.7 → urwid-4.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  91. {urwid-4.1.7 → urwid-4.2.1}/.github/ISSUE_TEMPLATE/question.md +0 -0
  92. {urwid-4.1.7 → urwid-4.2.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  93. {urwid-4.1.7 → urwid-4.2.1}/.github/dependabot.yml +0 -0
  94. {urwid-4.1.7 → urwid-4.2.1}/.github/labeler.yml +0 -0
  95. {urwid-4.1.7 → urwid-4.2.1}/.github/release.yml +0 -0
  96. {urwid-4.1.7 → urwid-4.2.1}/.github/workflows/labels.yml +0 -0
  97. {urwid-4.1.7 → urwid-4.2.1}/.gitignore +0 -0
  98. {urwid-4.1.7 → urwid-4.2.1}/.pre-commit-config.yaml +0 -0
  99. {urwid-4.1.7 → urwid-4.2.1}/.yamllint.yml +0 -0
  100. {urwid-4.1.7 → urwid-4.2.1}/COPYING +0 -0
  101. {urwid-4.1.7 → urwid-4.2.1}/MANIFEST.in +0 -0
  102. {urwid-4.1.7 → urwid-4.2.1}/SECURITY.md +0 -0
  103. {urwid-4.1.7 → urwid-4.2.1}/classifiers.txt +0 -0
  104. {urwid-4.1.7 → urwid-4.2.1}/mypy-requirements.txt +0 -0
  105. {urwid-4.1.7 → urwid-4.2.1}/pytest_requirements.txt +0 -0
  106. {urwid-4.1.7 → urwid-4.2.1}/requirements.txt +0 -0
  107. {urwid-4.1.7 → urwid-4.2.1}/setup.cfg +0 -0
  108. {urwid-4.1.7 → urwid-4.2.1}/tox.ini +0 -0
  109. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/__init__.py +0 -0
  110. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/_web.css +0 -0
  111. {urwid-4.1.7 → urwid-4.2.1}/urwid/display/_web.js +0 -0
  112. {urwid-4.1.7 → urwid-4.2.1}/urwid/event_loop/__init__.py +0 -0
  113. {urwid-4.1.7 → urwid-4.2.1}/urwid/py.typed +0 -0
  114. {urwid-4.1.7 → urwid-4.2.1}/urwid.egg-info/SOURCES.txt +0 -0
  115. {urwid-4.1.7 → urwid-4.2.1}/urwid.egg-info/dependency_links.txt +0 -0
  116. {urwid-4.1.7 → urwid-4.2.1}/urwid.egg-info/not-zip-safe +0 -0
  117. {urwid-4.1.7 → urwid-4.2.1}/urwid.egg-info/requires.txt +0 -0
  118. {urwid-4.1.7 → urwid-4.2.1}/urwid.egg-info/top_level.txt +0 -0
  119. {urwid-4.1.7 → urwid-4.2.1}/yamllint-requirements.txt +0 -0
@@ -26,23 +26,14 @@ jobs:
26
26
  - uses: actions/checkout@v7
27
27
  with:
28
28
  fetch-depth: 0 # need for setuptools_scm
29
- - name: Set up Python
30
- uses: actions/setup-python@v7
31
- with:
32
- python-version: '3.x'
33
29
  - name: Install uv
34
- uses: astral-sh/setup-uv@v10.1.0
35
- - name: Install Ubuntu dependencies
36
- run: |
37
- sudo apt-get -qq update
38
- sudo apt-get install -y python3-dev python3-gi python3-gi-cairo
39
- sudo apt-get install -y gobject-introspection libgirepository-2.0-dev libcairo2-dev
30
+ uses: astral-sh/setup-uv@v10.2.0
40
31
  - name: Create virtual environment
41
- run: uv venv --python-preference only-system
32
+ run: uv venv
42
33
  - name: Install python dependencies
43
34
  run: uv pip install -U --group pyproject.toml:documentation
44
35
  - name: Install main package with all extras
45
- run: uv pip install -U -e ".[curses,glib,serial,tornado,trio,twisted,zmq]"
36
+ run: uv pip install -U -e ".[curses,serial,tornado,trio,twisted,zmq]"
46
37
  - name: Build documentation
47
38
  run: uv run --no-project sphinx-build docs build/documentation
48
39
  - uses: actions/upload-artifact@v7
@@ -16,14 +16,10 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
18
  - uses: actions/checkout@v7
19
- - name: Set up Python
20
- uses: actions/setup-python@v7
21
- with:
22
- python-version: '3.x'
23
19
  - name: Install uv
24
- uses: astral-sh/setup-uv@v10.1.0
20
+ uses: astral-sh/setup-uv@v10.2.0
25
21
  - name: Create virtual environment
26
- run: uv venv --python-preference only-system
22
+ run: uv venv
27
23
  - name: Install package with all extras
28
24
  run: uv pip install -e ".[curses,serial,tornado,trio,twisted,zmq]"
29
25
  - name: Install dependencies
@@ -16,21 +16,12 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
18
  - uses: actions/checkout@v7
19
- - name: Install Ubuntu dependencies
20
- run: |
21
- sudo apt-get -qq update
22
- sudo apt-get install -y python3-dev python3-gi python3-gi-cairo
23
- sudo apt-get install -y gobject-introspection libgirepository-2.0-dev libcairo2-dev
24
- - name: Set up Python
25
- uses: actions/setup-python@v7
26
- with:
27
- python-version: '3.x'
28
19
  - name: Install uv
29
- uses: astral-sh/setup-uv@v10.1.0
20
+ uses: astral-sh/setup-uv@v10.2.0
30
21
  - name: Create virtual environment
31
- run: uv venv --python-preference only-system
22
+ run: uv venv
32
23
  - name: Install package with all extras
33
- run: uv pip install -e ".[curses,glib,serial,tornado,trio,twisted,zmq]"
24
+ run: uv pip install -e ".[curses,serial,tornado,trio,twisted,zmq]"
34
25
  - name: Install dependencies
35
26
  run: uv pip install --upgrade -r pylint-requirements.txt
36
27
  - name: Lint with pylint
@@ -52,21 +52,12 @@ jobs:
52
52
  runs-on: ubuntu-latest
53
53
  steps:
54
54
  - uses: actions/checkout@v7
55
- - name: Install Ubuntu dependencies
56
- run: |
57
- sudo apt-get -qq update
58
- sudo apt-get install -y python3-dev python3-gi python3-gi-cairo
59
- sudo apt-get install -y gobject-introspection libgirepository-2.0-dev libcairo2-dev
60
- - name: Set up Python
61
- uses: actions/setup-python@v7
62
- with:
63
- python-version: '3.x'
64
55
  - name: Install uv
65
- uses: astral-sh/setup-uv@v10.1.0
56
+ uses: astral-sh/setup-uv@v10.2.0
66
57
  - name: Create virtual environment
67
- run: uv venv --python-preference only-system
58
+ run: uv venv
68
59
  - name: Install package with all extras
69
- run: uv pip install -e ".[curses,glib,serial,tornado,trio,twisted,zmq]"
60
+ run: uv pip install -e ".[curses,serial,tornado,trio,twisted,zmq]"
70
61
  - name: Install dependencies
71
62
  run: uv pip install --upgrade -r pylint-requirements.txt
72
63
  - name: Lint with pylint
@@ -77,14 +68,10 @@ jobs:
77
68
  runs-on: ubuntu-latest
78
69
  steps:
79
70
  - uses: actions/checkout@v7
80
- - name: Set up Python
81
- uses: actions/setup-python@v7
82
- with:
83
- python-version: '3.x'
84
71
  - name: Install uv
85
- uses: astral-sh/setup-uv@v10.1.0
72
+ uses: astral-sh/setup-uv@v10.2.0
86
73
  - name: Create virtual environment
87
- run: uv venv --python-preference only-system
74
+ run: uv venv
88
75
  - name: Install package with all extras
89
76
  run: uv pip install -e ".[curses,serial,tornado,trio,twisted,zmq]"
90
77
  - name: Install dependencies
@@ -102,9 +89,6 @@ jobs:
102
89
  # , "macos-latest"
103
90
  # enable macOS only if OS specific branch will be added
104
91
  python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ]
105
- exclude:
106
- - os: "windows-latest"
107
- python-version: "3.14"
108
92
 
109
93
  steps:
110
94
  - uses: actions/checkout@v7
@@ -116,24 +100,11 @@ jobs:
116
100
  python-version: ${{ matrix.python-version }}
117
101
  allow-prereleases: true
118
102
  - name: Install uv
119
- uses: astral-sh/setup-uv@v10.1.0
120
- - name: Install Ubuntu dependencies
121
- if: runner.os == 'Linux'
122
- run: |
123
- sudo apt-get -qq update
124
- sudo apt-get install -y python3-dev python3-gi python3-gi-cairo
125
- sudo apt-get install -y gobject-introspection libgirepository-2.0-dev libcairo2-dev
126
- - name: Install Mac OS X dependencies
127
- if: runner.os == 'macOS'
128
- run: |
129
- brew install gtk+3 gobject-introspection gtk-mac-integration
103
+ uses: astral-sh/setup-uv@v10.2.0
130
104
  - name: Create virtual environment
131
105
  run: uv venv --python-preference only-system --python ${{ matrix.python-version }}
132
106
  - name: Install dependencies
133
107
  run: uv pip install -r pytest_requirements.txt
134
- - name: Install PyGObject
135
- if: runner.os == 'Linux' || runner.os == 'macOS'
136
- run: uv pip install PyGObject
137
108
  - name: Install package with all extras
138
109
  run: uv pip install -e ".[curses,serial,tornado,trio,twisted,zmq]"
139
110
  - name: Test
@@ -165,18 +136,11 @@ jobs:
165
136
  with:
166
137
  fetch-depth: 0 # need for setuptools_scm
167
138
 
168
- - uses: actions/setup-python@v7
169
- with:
170
- python-version: "3.x"
171
-
172
139
  - name: Install uv
173
- uses: astral-sh/setup-uv@v10.1.0
174
-
175
- - name: Install dependencies
176
- run: uv pip install --system -U build
140
+ uses: astral-sh/setup-uv@v10.2.0
177
141
 
178
142
  - name: Build dist
179
- run: python -m build
143
+ run: uv build
180
144
 
181
145
  - uses: actions/upload-artifact@v7
182
146
  with:
@@ -196,15 +160,8 @@ jobs:
196
160
  needs: [ build ]
197
161
  steps:
198
162
  - uses: actions/checkout@v7
199
- - name: Set up Python
200
- uses: actions/setup-python@v7
201
- with:
202
- python-version: '3.x'
203
163
  - name: Install uv
204
- uses: astral-sh/setup-uv@v10.1.0
205
- - name: Install dependencies
206
- run: |
207
- uv pip install --system --upgrade twine
164
+ uses: astral-sh/setup-uv@v10.2.0
208
165
  - uses: actions/download-artifact@v8
209
166
  with:
210
167
  # unpacks default artifact into dist/
@@ -214,8 +171,7 @@ jobs:
214
171
  path: dist
215
172
 
216
173
  - name: Validate metadata
217
- run: |
218
- twine check dist/*
174
+ run: uvx twine check dist/*
219
175
 
220
176
  upload_pypi:
221
177
  needs: [ build, Metadata ]
@@ -17,7 +17,7 @@ jobs:
17
17
  steps:
18
18
  - uses: actions/checkout@v7
19
19
  - name: Install uv
20
- uses: astral-sh/setup-uv@v10.1.0
20
+ uses: astral-sh/setup-uv@v10.2.0
21
21
  - name: Lint YAML files
22
22
  run: uv run --no-project --with-requirements yamllint-requirements.txt yamllint . -f github
23
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urwid
3
- Version: 4.1.7
3
+ Version: 4.2.1
4
4
  Summary: A full-featured console (xterm et al.) user interface library
5
5
  Author-email: Ian Ward <ian@excess.org>
6
6
  License-Expression: LGPL-2.1-only
@@ -76,7 +76,7 @@ It includes many features useful for text console application developers includi
76
76
  - Automatic, programmable text alignment and wrapping
77
77
  - Simple markup for setting text attributes within blocks of text
78
78
  - Powerful list box with programmable content for scrolling all widget types
79
- - Your choice of event loops: Twisted, Glib, Tornado, asyncio, trio, ZeroMQ or select-based loop
79
+ - Your choice of event loops: Twisted, Glib (deprecated), Tornado, asyncio, trio, ZeroMQ or select-based loop
80
80
  - Pre-built widgets include edit boxes, buttons, check boxes and radio buttons
81
81
  - Display modules include raw, curses, and experimental LCD and web displays
82
82
  - Support for UTF-8, simple 8-bit and CJK encodings
@@ -14,7 +14,7 @@ It includes many features useful for text console application developers includi
14
14
  - Automatic, programmable text alignment and wrapping
15
15
  - Simple markup for setting text attributes within blocks of text
16
16
  - Powerful list box with programmable content for scrolling all widget types
17
- - Your choice of event loops: Twisted, Glib, Tornado, asyncio, trio, ZeroMQ or select-based loop
17
+ - Your choice of event loops: Twisted, Glib (deprecated), Tornado, asyncio, trio, ZeroMQ or select-based loop
18
18
  - Pre-built widgets include edit boxes, buttons, check boxes and radio buttons
19
19
  - Display modules include raw, curses, and experimental LCD and web displays
20
20
  - Support for UTF-8, simple 8-bit and CJK encodings
@@ -1,3 +1,5 @@
1
+ """Pytest configuration: skip collecting modules that cannot be imported on this platform."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import importlib.util
@@ -52,6 +54,7 @@ def _backend_available(name: str) -> bool:
52
54
 
53
55
 
54
56
  def pytest_ignore_collect(collection_path: Path) -> bool | None:
57
+ """Skip collecting a platform-only or optional-backend test module that cannot run here."""
55
58
  name = collection_path.name
56
59
 
57
60
  if not _PLATFORM_ONLY.get(name, True):
@@ -0,0 +1 @@
1
+ pylint==4.0.9
@@ -144,6 +144,7 @@ extend-select = [
144
144
  "PL",
145
145
  "PERF",
146
146
  "RUF", # tryceratops, pyupgrade, isort, pylint + perflint, Ruff-specific
147
+ "D", # pydocstyle
147
148
  ]
148
149
  extend-ignore = [
149
150
  "E221",
@@ -164,12 +165,24 @@ extend-ignore = [
164
165
  "SIM108", # Use ternary operator,
165
166
  "TRY003", # long messages prepare outside, ...
166
167
  "PYI063", # MonitoredList is a list subclass, this expected to change
168
+ "ISC001", # conflicts with the formatter, which can produce single-line implicit concatenation
169
+ # suspicious-xml-*-usage: recommend defusedxml, which is not a dependency
170
+ "S313",
171
+ "S314",
172
+ "S315",
173
+ "S316",
174
+ "S317",
175
+ "S318",
176
+ "S319",
167
177
  ]
168
178
  [tool.ruff.lint.isort]
169
179
  known-third-party = []
170
180
  [tool.ruff.lint.per-file-ignores]
171
181
  "__init__.py" = [ "F401", "SIM105" ]
172
182
  "urwid/display/curses.py" = [ "A005" ]
183
+ # Shown verbatim in the docs as numbered listings, wcur2.py from a fixed line (`:lines: 7-`).
184
+ "docs/tutorial/*.py" = [ "D100", "D101", "D102", "D103", "D107" ]
185
+ "docs/manual/*.py" = [ "D100", "D101", "D102", "D103", "D107" ]
173
186
  [tool.ruff.lint.pydocstyle]
174
187
  convention = "pep257"
175
188
  [tool.ruff.lint.pylint]
@@ -182,27 +195,22 @@ allow-dunder-method-names = [
182
195
  ]
183
196
 
184
197
  [tool.pylint]
185
- reports = false
186
198
  disable = [
187
199
  "arguments-differ",
188
200
  "attribute-defined-outside-init", # old bad practice, fixed step-by-step
189
- "broad-except",
201
+ "broad-exception-caught",
190
202
  "broad-exception-raised",
191
203
  "consider-using-assignment-expr",
192
- # iterable modification during iteration
193
- "consider-using-enumerate",
204
+ "consider-using-enumerate", # iterable modification during iteration
194
205
  "cyclic-import",
195
206
  "file-ignored",
196
207
  "fixme",
197
208
  "import-outside-toplevel",
198
209
  "invalid-name",
199
- # Magic part
200
- "invalid-overridden-method",
210
+ "invalid-overridden-method", # Magic part
201
211
  "locally-disabled",
202
212
  "logging-format-interpolation",
203
213
  "logging-fstring-interpolation",
204
- "missing-docstring",
205
- "redefined-variable-type", # makes not happy typechecking, but super common
206
214
  "similarities",
207
215
  "superfluous-parens",
208
216
  "suppressed-message",
@@ -220,12 +228,10 @@ disable = [
220
228
  "too-many-statements",
221
229
  "unused-argument",
222
230
  "unused-private-member",
223
- # too complex union
224
- "use-implicit-booleaness-not-comparison-to-string",
231
+ "use-implicit-booleaness-not-comparison-to-string", # too complex union
225
232
  "use-implicit-booleaness-not-comparison-to-zero",
226
233
  ]
227
234
  enable = "all"
228
- extension-pkg-whitelist = []
229
235
  ignore = [ "CVS", "version.py" ]
230
236
  # gi (PyGObject) is only installed with the "glib" extra; static checks also run without it.
231
237
  ignored-modules = [ "gi" ]
@@ -237,7 +243,6 @@ load-plugins = [
237
243
  "pylint.extensions.dunder",
238
244
  "pylint.extensions.for_any_all",
239
245
  "pylint.extensions.overlapping_exceptions",
240
- "pylint.extensions.redefined_variable_type",
241
246
  "pylint.extensions.typing",
242
247
  ]
243
248
  max-line-length = 120
@@ -19,6 +19,8 @@
19
19
  # Urwid web site: https://urwid.org/
20
20
 
21
21
 
22
+ """Urwid, a console user interface library: the public API re-exported from its submodules."""
23
+
22
24
  from __future__ import annotations
23
25
 
24
26
  import importlib.util
@@ -517,7 +519,8 @@ class _MovedModule(types.ModuleType):
517
519
  self._moved_to = moved_to
518
520
 
519
521
  def __getattr__(self, name: str) -> typing.Any:
520
- """
522
+ """Resolve `name` by importing the module this one moved to.
523
+
521
524
  :raises AttributeError: *name* is not found, including when *self._moved_to* itself cannot be imported.
522
525
  """
523
526
  try:
@@ -109,20 +109,20 @@ class SkippedOp:
109
109
 
110
110
  @dataclasses.dataclass(frozen=True)
111
111
  class ParsedLine:
112
- """The result of parsing one physical line of ANSI-coloured text.
112
+ r"""The result of parsing one physical line of ANSI-coloured text.
113
113
 
114
114
  :ivar text: the resolved, escape-free text of this line. When produced by :class:`AnsiParser` constructed with
115
- ``one_line=True`` (or :func:`parse_ansi_line`), this is guaranteed never to contain a ``\\n`` -- line ends
115
+ ``one_line=True`` (or :func:`parse_ansi_line`), this is guaranteed never to contain a ``\n`` -- line ends
116
116
  are structural split points that produce *separate* :class:`ParsedLine` instances. That invariant is
117
117
  deliberately **not** upheld when :class:`AnsiParser` is constructed with ``one_line=False`` (or by
118
118
  :func:`parse_ansi_text`): since that mode resolves a whole multi-row block into a single :class:`ParsedLine`,
119
- its ``text`` legitimately contains embedded ``\\n`` characters, one per row boundary, by design.
119
+ its ``text`` legitimately contains embedded ``\n`` characters, one per row boundary, by design.
120
120
  :ivar attrib: run-length encoded display attributes for :attr:`text`, the same shape as
121
121
  :attr:`urwid.Text.attrib`. ``None`` is a valid RLE tag (meaning "no attribute"), matching
122
122
  :func:`urwid.util.decompose_tagmarkup`'s convention.
123
123
  :ivar last_attr: the SGR/colour state in effect at the end of this line; feed this back in as ``previous_attr``
124
124
  to continue the same colour state into a subsequently parsed line.
125
- :ivar bel: the number of BEL (``\\a``) characters seen on this line (``0`` if none were seen).
125
+ :ivar bel: the number of BEL (``\a``) characters seen on this line (``0`` if none were seen).
126
126
  :ivar title: the last OSC window-title string seen on this line, or ``None`` if none was seen.
127
127
  :ivar leds: the last keyboard-LED state requested on this line (``CSI n q``), or ``None`` if none was seen.
128
128
  :ivar skipped: every :class:`SkippedOp` stripped while parsing this line, in encounter order, for programmatic
@@ -139,7 +139,7 @@ class ParsedLine:
139
139
 
140
140
 
141
141
  class AnsiParser:
142
- """Incremental ANSI parser that resolves escape sequences against a 2-D cell grid, in one of two modes.
142
+ r"""Incremental ANSI parser that resolves escape sequences against a 2-D cell grid, in one of two modes.
143
143
 
144
144
  Feed text in with :meth:`feed` -- any number of times, e.g. as chunks arrive from a subprocess -- and call
145
145
  :meth:`finalize` once to retrieve the result. :meth:`finalize` is a one-shot, terminal operation: construct a
@@ -154,18 +154,18 @@ class AnsiParser:
154
154
  treated, and in whether the side channels (``bel``, ``title``, ``leds``, ``skipped``) are reset per line or
155
155
  aggregated over the whole input:
156
156
 
157
- * ``one_line=True``: a line-end sequence (``\\n``, ``\\r``, ``\\r\\n`` or ``\\n\\r``, the latter two pairs
157
+ * ``one_line=True``: a line-end sequence (``\n``, ``\r``, ``\r\n`` or ``\n\r``, the latter two pairs
158
158
  collapsing into a single event) is a **structural split point**, not discarded data -- the moment one is
159
159
  seen, the current row is completed into a :class:`ParsedLine` and pushed onto an internal completed-lines
160
160
  queue, and a fresh row is started for what follows, carrying the SGR/colour state
161
161
  (:attr:`ParsedLine.last_attr`) forward but resetting the per-line side channels. Vertical cursor movement
162
162
  (``CSI n A/B/E/F/d`` and ``CSI r;c H``/``f``) is meaningless outside a single line, so it is stripped and
163
163
  logged as a :class:`SkippedOp` rather than reinterpreted.
164
- * ``one_line=False`` (the default): a full newline (``\\n``, or a collapsed ``\\r\\n``/``\\n\\r`` pair) genuinely
164
+ * ``one_line=False`` (the default): a full newline (``\n``, or a collapsed ``\r\n``/``\n\r`` pair) genuinely
165
165
  moves to a fresh row, resetting the column, while everything keeps accumulating in the same persistent grid
166
- -- nothing is split off. A **bare** ``\\r`` (not part of a ``\\r\\n``/``\\n\\r`` pair) is carriage-return-only:
166
+ -- nothing is split off. A **bare** ``\r`` (not part of a ``\r\n``/``\n\r`` pair) is carriage-return-only:
167
167
  it resets the column without changing row, letting progress-bar-style in-place redraw sequences
168
- (``"progress: 10%\\rprogress: 20%"``) resolve correctly onto a single row, mirroring real terminal behaviour.
168
+ (``"progress: 10%\rprogress: 20%"``) resolve correctly onto a single row, mirroring real terminal behaviour.
169
169
  Vertical cursor movement genuinely repositions the cursor within the grid instead of being stripped. The side
170
170
  channels are aggregated over the *entire* input rather than reset per row: ``bel`` is the total BEL count,
171
171
  ``title``/``leds`` are the last value seen anywhere, and ``skipped`` lists every :class:`SkippedOp` in
@@ -180,7 +180,7 @@ class AnsiParser:
180
180
  :meth:`finalize` returns a uniform ``tuple[ParsedLine, ...]`` in both modes: in ``one_line=True`` mode, every
181
181
  line completed since construction (queue plus a final flush of whatever remains, possibly empty or
182
182
  unterminated); in ``one_line=False`` mode, always exactly one :class:`ParsedLine` -- built from the whole grid,
183
- rows joined with ``"\\n"`` -- wrapped in a 1-tuple.
183
+ rows joined with ``"\n"`` -- wrapped in a 1-tuple.
184
184
 
185
185
  :param previous_attr: SGR/colour state to seed the parser with, as if it were carried over from previously
186
186
  parsed text.
@@ -188,17 +188,18 @@ class AnsiParser:
188
188
  results; if false (the default), resolve the whole input into a single multi-row :class:`ParsedLine`.
189
189
 
190
190
  >>> parser = AnsiParser(one_line=True)
191
- >>> parser.feed("hello\\nworld")
191
+ >>> parser.feed("hello\nworld")
192
192
  >>> [line.text for line in parser.finalize()]
193
193
  ['hello', 'world']
194
194
 
195
195
  >>> parser = AnsiParser()
196
- >>> parser.feed("hello\\nworld")
196
+ >>> parser.feed("hello\nworld")
197
197
  >>> parser.finalize()[0].text
198
- 'hello\\nworld'
198
+ 'hello\nworld'
199
199
  """
200
200
 
201
201
  def __init__(self, previous_attr: AttrSpec | None = None, *, one_line: bool = False) -> None:
202
+ """Create a parser, as described in the class docstring."""
202
203
  self._one_line = one_line
203
204
  self._attrspec: AttrSpec | None = previous_attr
204
205
 
@@ -285,12 +286,12 @@ class AnsiParser:
285
286
  i += 1
286
287
 
287
288
  def finalize(self) -> tuple[ParsedLine, ...]:
288
- """Retrieve the parsed result, in a uniform ``tuple[ParsedLine, ...]`` shape regardless of mode.
289
+ r"""Retrieve the parsed result, in a uniform ``tuple[ParsedLine, ...]`` shape regardless of mode.
289
290
 
290
291
  In ``one_line=True`` mode, whatever remains in the current (possibly empty, possibly unterminated) row is
291
292
  flushed as one final :class:`ParsedLine`, and every line completed since construction is returned, in
292
293
  order. In ``one_line=False`` mode, the whole accumulated grid is resolved into a single :class:`ParsedLine`
293
- (its ``text`` joining every resolved row with ``"\\n"``) and returned as a 1-tuple.
294
+ (its ``text`` joining every resolved row with ``"\n"``) and returned as a 1-tuple.
294
295
 
295
296
  This is a terminal operation: construct a new :class:`AnsiParser` to parse a fresh "session" of input.
296
297
 
@@ -324,10 +325,10 @@ class AnsiParser:
324
325
  # -- internal: line/row completion and cursor management -------------
325
326
 
326
327
  def _handle_line_end(self) -> None:
327
- """Complete the current line, aborting any in-flight escape sequence.
328
+ r"""Complete the current line, aborting any in-flight escape sequence.
328
329
 
329
330
  Only called in one_line mode: a line end always aborts any in-flight escape sequence, since real escape
330
- sequences never legitimately contain a raw \\r or \\n. In screen mode a newline is instead handled directly
331
+ sequences never legitimately contain a raw \r or \n. In screen mode a newline is instead handled directly
331
332
  in :meth:`feed` without touching escape-framing state.
332
333
  """
333
334
  self._within_escape = False
@@ -587,9 +588,9 @@ class AnsiParser:
587
588
 
588
589
 
589
590
  def parse_ansi_line(text: str, previous_attr: AttrSpec | None = None) -> tuple[ParsedLine, ...]:
590
- """Feed ``text`` through a fresh :class:`AnsiParser` (``one_line=True``) and return its finalised result.
591
+ r"""Feed ``text`` through a fresh :class:`AnsiParser` (``one_line=True``) and return its finalised result.
591
592
 
592
- Returns one :class:`ParsedLine` per line found in ``text`` (split on ``\\n``/``\\r``/``\\r\\n``/``\\n\\r``), plus
593
+ Returns one :class:`ParsedLine` per line found in ``text`` (split on ``\n``/``\r``/``\r\n``/``\n\r``), plus
593
594
  a trailing entry for a final unterminated fragment, if any -- never fewer than one element, even for empty
594
595
  input, and every character of the original data is preserved across the returned lines (line-end characters are
595
596
  structural split points, not stripped content).
@@ -599,11 +600,11 @@ def parse_ansi_line(text: str, previous_attr: AttrSpec | None = None) -> tuple[P
599
600
  previously parsed line.
600
601
  :returns: every :class:`ParsedLine` found in ``text``, in order.
601
602
 
602
- >>> [line.text for line in parse_ansi_line("one\\ntwo\\nthree")]
603
+ >>> [line.text for line in parse_ansi_line("one\ntwo\nthree")]
603
604
  ['one', 'two', 'three']
604
605
  >>> [line.text for line in parse_ansi_line("")]
605
606
  ['']
606
- >>> [line.text for line in parse_ansi_line("a\\nb")]
607
+ >>> [line.text for line in parse_ansi_line("a\nb")]
607
608
  ['a', 'b']
608
609
  """
609
610
  parser = AnsiParser(previous_attr, one_line=True)
@@ -612,22 +613,22 @@ def parse_ansi_line(text: str, previous_attr: AttrSpec | None = None) -> tuple[P
612
613
 
613
614
 
614
615
  def parse_ansi_text(text: str, previous_attr: AttrSpec | None = None) -> ParsedLine:
615
- """Feed ``text`` through a fresh :class:`AnsiParser` (``one_line=False``) and return its sole resolved line.
616
+ r"""Feed ``text`` through a fresh :class:`AnsiParser` (``one_line=False``) and return its sole resolved line.
616
617
 
617
618
  Unlike :func:`parse_ansi_line`, vertical cursor movement and newlines are resolved into real multi-row output
618
619
  rather than stripped/split: the returned single :class:`ParsedLine`'s ``text`` joins every resolved row with
619
- ``"\\n"``.
620
+ ``"\n"``.
620
621
 
621
622
  :param text: the text to parse.
622
623
  :param previous_attr: SGR/colour state to seed the parser with, as if it were carried over from previously
623
624
  parsed text.
624
625
  :returns: a single :class:`ParsedLine` representing the whole resolved block.
625
626
 
626
- >>> parse_ansi_text("one\\ntwo\\nthree").text
627
- 'one\\ntwo\\nthree'
627
+ >>> parse_ansi_text("one\ntwo\nthree").text
628
+ 'one\ntwo\nthree'
628
629
  >>> parse_ansi_text("").text
629
630
  ''
630
- >>> parse_ansi_text("progress: 10%\\rprogress: 20%").text
631
+ >>> parse_ansi_text("progress: 10%\rprogress: 20%").text
631
632
  'progress: 20%'
632
633
  """
633
634
  parser = AnsiParser(previous_attr, one_line=False)