richless 0.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.
@@ -0,0 +1,207 @@
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
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
richless-0.2.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 DavidJBianco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,356 @@
1
+ Metadata-Version: 2.4
2
+ Name: richless
3
+ Version: 0.2.1
4
+ Summary: LESSOPEN filter for Markdown rendering and syntax highlighting with less
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: pygments>=2.0.0
8
+ Requires-Dist: rich>=13.8.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # richless
14
+
15
+ A LESSOPEN filter for automatically rendering Markdown files and syntax highlighting source code when using `less`. View your Markdown documents with beautiful formatting and programming language files with syntax highlighting directly in the terminal, without changing how you use `less`.
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # 1. Install richless
21
+ git clone <repository-url>
22
+ cd richless
23
+ uv tool install .
24
+
25
+ # 2. Copy init script and add to your ~/.bashrc or ~/.zshrc
26
+ cp richless-init.sh ~/.richless-init.sh
27
+ echo 'source ~/.richless-init.sh' >> ~/.bashrc # or ~/.zshrc
28
+
29
+ # 3. Reload your shell
30
+ source ~/.bashrc # or ~/.zshrc
31
+
32
+ # 4. Try it!
33
+ less README.md # View rendered Markdown
34
+ less richless.py # View syntax-highlighted Python
35
+ ```
36
+
37
+ ## Features
38
+
39
+ - **Seamless Integration**: Works transparently with `less` via LESSOPEN
40
+ - **Automatic Markdown Rendering**: Recognizes `.md` and `.markdown` files automatically and renders them beautifully
41
+ - **Rich Terminal Formatting**: Beautiful rendering with headers, lists, code blocks, tables, and more
42
+ - **Data Format Highlighting**: Syntax highlighting for JSON, JSONL, YAML, and XML files with automatic detection
43
+ - **Code Highlighting**: Syntax highlighting for 500+ programming languages (Python, JavaScript, Go, Rust, and more)
44
+ - **Works with Wildcards**: `less *.md` or `less *.py` just works
45
+ - **Correct Filenames**: Shows actual filenames in less, not temporary files
46
+ - **Powered by rich and Pygments**: Leverages [rich](https://github.com/Textualize/rich) for beautiful terminal output and [Pygments](https://pygments.org/) for syntax highlighting
47
+
48
+ ## Installation
49
+
50
+ ### Prerequisites
51
+
52
+ - **Python 3.12+**
53
+ - **[uv](https://github.com/astral-sh/uv)** - Modern Python package manager
54
+ ```bash
55
+ # Install uv if you don't have it
56
+ curl -LsSf https://astral.sh/uv/install.sh | sh
57
+ ```
58
+ - **less pager** (standard on macOS and most Linux distributions)
59
+
60
+ ### Step 1: Install richless
61
+
62
+ Install richless in an isolated virtualenv using uv's tool install feature:
63
+
64
+ ```bash
65
+ # Clone or download this repository
66
+ git clone <repository-url>
67
+ cd richless
68
+
69
+ # Install richless as a uv tool
70
+ uv tool install .
71
+
72
+ # Or install directly from git (once published)
73
+ # uv tool install git+https://github.com/yourusername/richless
74
+ ```
75
+
76
+ This installs `richless` and all its dependencies in an isolated virtualenv, making the `richless` command available system-wide without polluting your project environments.
77
+
78
+ **Note:** If you see a warning about PATH, run:
79
+ ```bash
80
+ export PATH="$HOME/.local/bin:$PATH"
81
+ # Add this line to your ~/.bashrc or ~/.zshrc to make it permanent
82
+ ```
83
+
84
+ ### Step 2: Configure Shell Integration
85
+
86
+ You have two configuration options depending on your needs:
87
+
88
+ #### Option 1: Basic LESSOPEN (Minimal setup)
89
+
90
+ If you prefer not to use the shell wrapper, you can manually set two environment variables. This is a lighter-weight alternative but lacks some features.
91
+
92
+ **Setup:**
93
+
94
+ Add these lines to your `~/.bashrc`, `~/.zshrc`, or `~/.profile`:
95
+
96
+ ```bash
97
+ export LESSOPEN="|richless %s"
98
+ export LESS="-R"
99
+ ```
100
+
101
+ Then reload your shell:
102
+
103
+ ```bash
104
+ source ~/.bashrc # or source ~/.zshrc
105
+ ```
106
+
107
+ **What you get:**
108
+ - ✅ Automatic Markdown rendering when you run `less file.md`
109
+ - ✅ Syntax highlighting for programming language source files (Python, JavaScript, etc.)
110
+ - ✅ Works with wildcards: `less *.md` or `less *.py`
111
+ - ✅ Fast and lightweight
112
+
113
+ **Limitations:**
114
+ - ❌ Doesn't work with piped input: `cat file.md | less` won't render
115
+ - ❌ Can't force markdown on non-.md files through `less`
116
+
117
+ #### Option 2: Transparent Wrapper (Recommended - used by Quick Start)
118
+
119
+ This is the recommended setup and what Quick Start uses. It provides a smart wrapper around `less` that handles edge cases like piped input and forcing markdown on arbitrary files.
120
+
121
+ **Setup:**
122
+
123
+ 1. Copy the shell integration script to your home directory:
124
+ ```bash
125
+ # From the richless project directory
126
+ cp richless-init.sh ~/.richless-init.sh
127
+ ```
128
+
129
+ 2. Source it in your shell configuration:
130
+ ```bash
131
+ # Add this line to ~/.bashrc, ~/.zshrc, or ~/.profile
132
+ source ~/.richless-init.sh
133
+ ```
134
+
135
+ 3. Reload your shell:
136
+ ```bash
137
+ source ~/.bashrc # or source ~/.zshrc
138
+ ```
139
+
140
+ **What you get:**
141
+ - ✅ Everything from Option 1, plus:
142
+ - ✅ Piped input works: `cat file.md | less` renders markdown
143
+ - ✅ Force markdown flag: `less --md document.txt` forces rendering
144
+ - ✅ Auto-detection: Intelligently detects markdown in piped content
145
+ - ✅ Backward compatible: Acts like normal `less` when not needed
146
+
147
+ **Note:** The shell integration file is compatible with sh, bash, and zsh.
148
+
149
+ ## Usage
150
+
151
+ Once configured, just use `less` normally! Markdown files will be automatically rendered, and source code files will be syntax highlighted.
152
+
153
+ ### Basic Usage (Both Options)
154
+
155
+ ```bash
156
+ # View a Markdown file (automatically rendered)
157
+ less README.md
158
+
159
+ # View multiple Markdown files
160
+ less *.md
161
+
162
+ # View with wildcards
163
+ less docs/**/*.md
164
+
165
+ # Source code files get syntax highlighting automatically
166
+ less script.py # Python
167
+ less app.js # JavaScript
168
+ less main.go # Go
169
+ less config.json # JSON
170
+ less styles.css # CSS
171
+
172
+ # All standard less options work
173
+ less -N README.md # Show line numbers
174
+ less -i script.py # Case-insensitive search
175
+ less +50 README.md # Start at line 50
176
+ ```
177
+
178
+ ### Additional Features (Option 2 / Quick Start)
179
+
180
+ If you followed the Quick Start or are using Option 2, you also get these features:
181
+
182
+ ```bash
183
+ # Force Markdown rendering on non-.md files
184
+ less --md document.txt
185
+ less -m notes.txt
186
+
187
+ # Piped input works!
188
+ cat file.md | less
189
+ echo "# Hello\n**World**" | less --md
190
+
191
+ # Pipe from other commands
192
+ curl https://example.com/README.md | less --md
193
+ grep -A 50 "## Section" doc.md | less
194
+
195
+ # Auto-detection: if piped content looks like markdown, it renders automatically
196
+ cat file.md | less # Detects markdown syntax and renders
197
+ ```
198
+
199
+ ### Direct richless Usage
200
+
201
+ You can also call `richless` directly if needed:
202
+
203
+ ```bash
204
+ # Render and pipe to less
205
+ richless document.md | less -R
206
+
207
+ # Force markdown rendering
208
+ richless --md document.txt | less -R
209
+
210
+ # Read from stdin
211
+ cat file.md | richless --md - | less -R
212
+ echo "# Test" | richless --md - | less -R
213
+ ```
214
+
215
+ ### Standard less Commands
216
+
217
+ All standard `less` commands work normally inside the pager:
218
+
219
+ - `/pattern` - Search forward
220
+ - `?pattern` - Search backward
221
+ - `n` / `N` - Next/previous match
222
+ - `g` / `G` - Go to start/end
223
+ - `q` - Quit
224
+ - `h` - Help
225
+
226
+ ## How It Works
227
+
228
+ **Basic Mode (Option 1):**
229
+ 1. When you run `less file.md`, the `LESSOPEN` environment variable tells less to run `richless file.md` first
230
+ 2. `richless` detects the `.md` extension and uses the `rich` library to render the Markdown
231
+ 3. `rich` renders the Markdown to beautifully formatted ANSI text with proper table support
232
+ 4. The formatted output is piped to `less` for viewing
233
+ 5. For programming language source files (`.py`, `.js`, `.java`, etc.), `rich` automatically provides syntax highlighting using Pygments
234
+
235
+ **Transparent Wrapper (Option 2):**
236
+ - The shell function intercepts calls to `less` before they execute
237
+ - For regular files, it passes through to the basic LESSOPEN mechanism
238
+ - For piped input or when `--md` is specified, it saves the content to a temp file and renders it
239
+ - Auto-detection checks piped content for markdown patterns (headers, lists, links, etc.)
240
+
241
+ ## Troubleshooting
242
+
243
+ ### "richless: command not found"
244
+
245
+ Make sure `~/.local/bin` is in your PATH:
246
+ ```bash
247
+ export PATH="$HOME/.local/bin:$PATH"
248
+ # Add this to your ~/.bashrc or ~/.zshrc
249
+ ```
250
+
251
+ ### Piped input doesn't work
252
+
253
+ If you're using Option 1 (Basic LESSOPEN), piped input won't work. Either:
254
+ - Switch to Option 2 / Quick Start for piped input support
255
+ - Or use: `cat file.md | richless --md - | less -R`
256
+
257
+ ### Colors don't show up
258
+
259
+ If you're using Option 1 (manual setup), make sure you have the `-R` flag set:
260
+ ```bash
261
+ export LESS="-R"
262
+ ```
263
+ The init script (Option 2 / Quick Start) sets this automatically.
264
+
265
+ ### Shell function not loading
266
+
267
+ Make sure you're sourcing the init script, not executing it:
268
+ ```bash
269
+ source ~/.richless-init.sh # Correct
270
+ ./richless-init.sh # Wrong - this won't define the function in your shell
271
+ ```
272
+
273
+ ### After sourcing, "less file.md" shows an error
274
+
275
+ Make sure you've reinstalled richless after any updates:
276
+ ```bash
277
+ uv tool uninstall richless
278
+ uv tool install .
279
+ source ~/.richless-init.sh # Re-source to pick up changes
280
+ ```
281
+
282
+ ## Updating
283
+
284
+ ```bash
285
+ # Update to the latest version
286
+ cd /path/to/richless
287
+ git pull # Get latest changes
288
+ uv tool upgrade richless
289
+
290
+ # Re-source the init script to pick up any changes
291
+ source ~/.richless-init.sh
292
+ ```
293
+
294
+ ## Uninstalling
295
+
296
+ ```bash
297
+ # Remove the tool
298
+ uv tool uninstall richless
299
+
300
+ # Remove from your shell config (~/.bashrc or ~/.zshrc)
301
+ # Delete or comment out the source line:
302
+ # source ~/.richless-init.sh
303
+
304
+ # Remove the init script
305
+ rm ~/.richless-init.sh
306
+
307
+ # If using Option 1 instead, remove these lines:
308
+ # export LESSOPEN="|richless %s"
309
+ # export LESS="-R"
310
+ ```
311
+
312
+ ## Development
313
+
314
+ ```bash
315
+ # Clone the repository
316
+ git clone <repository-url>
317
+ cd richless
318
+
319
+ # Install dependencies
320
+ uv sync
321
+
322
+ # Install in development mode
323
+ uv tool install --editable .
324
+
325
+ # Make changes, then reinstall
326
+ uv tool install --editable . --force
327
+
328
+ # Test it
329
+ less README.md
330
+
331
+ # Run tests
332
+ uv run pytest tests/test_richless.py -v
333
+ ```
334
+
335
+ ## Dependencies
336
+
337
+ - [rich](https://github.com/Textualize/rich) - Python library for rich terminal output, Markdown rendering, and syntax highlighting
338
+ - [Pygments](https://pygments.org/) - Syntax highlighting library
339
+ - Python 3.12+
340
+ - uv - Modern Python package manager
341
+
342
+ ## Comparison to Alternatives
343
+
344
+ **vs. glow / mdcat / bat:**
345
+ - richless integrates directly with `less`, so you use your familiar pager commands
346
+ - Works transparently - no need to remember a different command
347
+ - Supports all standard `less` features (search, navigation, etc.)
348
+
349
+ **vs. vimpager:**
350
+ - Lighter weight, doesn't require Vim
351
+ - Uses rich library's excellent Markdown rendering with full table support
352
+ - Simple LESSOPEN integration
353
+
354
+ ## License
355
+
356
+ See LICENSE file for details.
@@ -0,0 +1,344 @@
1
+ # richless
2
+
3
+ A LESSOPEN filter for automatically rendering Markdown files and syntax highlighting source code when using `less`. View your Markdown documents with beautiful formatting and programming language files with syntax highlighting directly in the terminal, without changing how you use `less`.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # 1. Install richless
9
+ git clone <repository-url>
10
+ cd richless
11
+ uv tool install .
12
+
13
+ # 2. Copy init script and add to your ~/.bashrc or ~/.zshrc
14
+ cp richless-init.sh ~/.richless-init.sh
15
+ echo 'source ~/.richless-init.sh' >> ~/.bashrc # or ~/.zshrc
16
+
17
+ # 3. Reload your shell
18
+ source ~/.bashrc # or ~/.zshrc
19
+
20
+ # 4. Try it!
21
+ less README.md # View rendered Markdown
22
+ less richless.py # View syntax-highlighted Python
23
+ ```
24
+
25
+ ## Features
26
+
27
+ - **Seamless Integration**: Works transparently with `less` via LESSOPEN
28
+ - **Automatic Markdown Rendering**: Recognizes `.md` and `.markdown` files automatically and renders them beautifully
29
+ - **Rich Terminal Formatting**: Beautiful rendering with headers, lists, code blocks, tables, and more
30
+ - **Data Format Highlighting**: Syntax highlighting for JSON, JSONL, YAML, and XML files with automatic detection
31
+ - **Code Highlighting**: Syntax highlighting for 500+ programming languages (Python, JavaScript, Go, Rust, and more)
32
+ - **Works with Wildcards**: `less *.md` or `less *.py` just works
33
+ - **Correct Filenames**: Shows actual filenames in less, not temporary files
34
+ - **Powered by rich and Pygments**: Leverages [rich](https://github.com/Textualize/rich) for beautiful terminal output and [Pygments](https://pygments.org/) for syntax highlighting
35
+
36
+ ## Installation
37
+
38
+ ### Prerequisites
39
+
40
+ - **Python 3.12+**
41
+ - **[uv](https://github.com/astral-sh/uv)** - Modern Python package manager
42
+ ```bash
43
+ # Install uv if you don't have it
44
+ curl -LsSf https://astral.sh/uv/install.sh | sh
45
+ ```
46
+ - **less pager** (standard on macOS and most Linux distributions)
47
+
48
+ ### Step 1: Install richless
49
+
50
+ Install richless in an isolated virtualenv using uv's tool install feature:
51
+
52
+ ```bash
53
+ # Clone or download this repository
54
+ git clone <repository-url>
55
+ cd richless
56
+
57
+ # Install richless as a uv tool
58
+ uv tool install .
59
+
60
+ # Or install directly from git (once published)
61
+ # uv tool install git+https://github.com/yourusername/richless
62
+ ```
63
+
64
+ This installs `richless` and all its dependencies in an isolated virtualenv, making the `richless` command available system-wide without polluting your project environments.
65
+
66
+ **Note:** If you see a warning about PATH, run:
67
+ ```bash
68
+ export PATH="$HOME/.local/bin:$PATH"
69
+ # Add this line to your ~/.bashrc or ~/.zshrc to make it permanent
70
+ ```
71
+
72
+ ### Step 2: Configure Shell Integration
73
+
74
+ You have two configuration options depending on your needs:
75
+
76
+ #### Option 1: Basic LESSOPEN (Minimal setup)
77
+
78
+ If you prefer not to use the shell wrapper, you can manually set two environment variables. This is a lighter-weight alternative but lacks some features.
79
+
80
+ **Setup:**
81
+
82
+ Add these lines to your `~/.bashrc`, `~/.zshrc`, or `~/.profile`:
83
+
84
+ ```bash
85
+ export LESSOPEN="|richless %s"
86
+ export LESS="-R"
87
+ ```
88
+
89
+ Then reload your shell:
90
+
91
+ ```bash
92
+ source ~/.bashrc # or source ~/.zshrc
93
+ ```
94
+
95
+ **What you get:**
96
+ - ✅ Automatic Markdown rendering when you run `less file.md`
97
+ - ✅ Syntax highlighting for programming language source files (Python, JavaScript, etc.)
98
+ - ✅ Works with wildcards: `less *.md` or `less *.py`
99
+ - ✅ Fast and lightweight
100
+
101
+ **Limitations:**
102
+ - ❌ Doesn't work with piped input: `cat file.md | less` won't render
103
+ - ❌ Can't force markdown on non-.md files through `less`
104
+
105
+ #### Option 2: Transparent Wrapper (Recommended - used by Quick Start)
106
+
107
+ This is the recommended setup and what Quick Start uses. It provides a smart wrapper around `less` that handles edge cases like piped input and forcing markdown on arbitrary files.
108
+
109
+ **Setup:**
110
+
111
+ 1. Copy the shell integration script to your home directory:
112
+ ```bash
113
+ # From the richless project directory
114
+ cp richless-init.sh ~/.richless-init.sh
115
+ ```
116
+
117
+ 2. Source it in your shell configuration:
118
+ ```bash
119
+ # Add this line to ~/.bashrc, ~/.zshrc, or ~/.profile
120
+ source ~/.richless-init.sh
121
+ ```
122
+
123
+ 3. Reload your shell:
124
+ ```bash
125
+ source ~/.bashrc # or source ~/.zshrc
126
+ ```
127
+
128
+ **What you get:**
129
+ - ✅ Everything from Option 1, plus:
130
+ - ✅ Piped input works: `cat file.md | less` renders markdown
131
+ - ✅ Force markdown flag: `less --md document.txt` forces rendering
132
+ - ✅ Auto-detection: Intelligently detects markdown in piped content
133
+ - ✅ Backward compatible: Acts like normal `less` when not needed
134
+
135
+ **Note:** The shell integration file is compatible with sh, bash, and zsh.
136
+
137
+ ## Usage
138
+
139
+ Once configured, just use `less` normally! Markdown files will be automatically rendered, and source code files will be syntax highlighted.
140
+
141
+ ### Basic Usage (Both Options)
142
+
143
+ ```bash
144
+ # View a Markdown file (automatically rendered)
145
+ less README.md
146
+
147
+ # View multiple Markdown files
148
+ less *.md
149
+
150
+ # View with wildcards
151
+ less docs/**/*.md
152
+
153
+ # Source code files get syntax highlighting automatically
154
+ less script.py # Python
155
+ less app.js # JavaScript
156
+ less main.go # Go
157
+ less config.json # JSON
158
+ less styles.css # CSS
159
+
160
+ # All standard less options work
161
+ less -N README.md # Show line numbers
162
+ less -i script.py # Case-insensitive search
163
+ less +50 README.md # Start at line 50
164
+ ```
165
+
166
+ ### Additional Features (Option 2 / Quick Start)
167
+
168
+ If you followed the Quick Start or are using Option 2, you also get these features:
169
+
170
+ ```bash
171
+ # Force Markdown rendering on non-.md files
172
+ less --md document.txt
173
+ less -m notes.txt
174
+
175
+ # Piped input works!
176
+ cat file.md | less
177
+ echo "# Hello\n**World**" | less --md
178
+
179
+ # Pipe from other commands
180
+ curl https://example.com/README.md | less --md
181
+ grep -A 50 "## Section" doc.md | less
182
+
183
+ # Auto-detection: if piped content looks like markdown, it renders automatically
184
+ cat file.md | less # Detects markdown syntax and renders
185
+ ```
186
+
187
+ ### Direct richless Usage
188
+
189
+ You can also call `richless` directly if needed:
190
+
191
+ ```bash
192
+ # Render and pipe to less
193
+ richless document.md | less -R
194
+
195
+ # Force markdown rendering
196
+ richless --md document.txt | less -R
197
+
198
+ # Read from stdin
199
+ cat file.md | richless --md - | less -R
200
+ echo "# Test" | richless --md - | less -R
201
+ ```
202
+
203
+ ### Standard less Commands
204
+
205
+ All standard `less` commands work normally inside the pager:
206
+
207
+ - `/pattern` - Search forward
208
+ - `?pattern` - Search backward
209
+ - `n` / `N` - Next/previous match
210
+ - `g` / `G` - Go to start/end
211
+ - `q` - Quit
212
+ - `h` - Help
213
+
214
+ ## How It Works
215
+
216
+ **Basic Mode (Option 1):**
217
+ 1. When you run `less file.md`, the `LESSOPEN` environment variable tells less to run `richless file.md` first
218
+ 2. `richless` detects the `.md` extension and uses the `rich` library to render the Markdown
219
+ 3. `rich` renders the Markdown to beautifully formatted ANSI text with proper table support
220
+ 4. The formatted output is piped to `less` for viewing
221
+ 5. For programming language source files (`.py`, `.js`, `.java`, etc.), `rich` automatically provides syntax highlighting using Pygments
222
+
223
+ **Transparent Wrapper (Option 2):**
224
+ - The shell function intercepts calls to `less` before they execute
225
+ - For regular files, it passes through to the basic LESSOPEN mechanism
226
+ - For piped input or when `--md` is specified, it saves the content to a temp file and renders it
227
+ - Auto-detection checks piped content for markdown patterns (headers, lists, links, etc.)
228
+
229
+ ## Troubleshooting
230
+
231
+ ### "richless: command not found"
232
+
233
+ Make sure `~/.local/bin` is in your PATH:
234
+ ```bash
235
+ export PATH="$HOME/.local/bin:$PATH"
236
+ # Add this to your ~/.bashrc or ~/.zshrc
237
+ ```
238
+
239
+ ### Piped input doesn't work
240
+
241
+ If you're using Option 1 (Basic LESSOPEN), piped input won't work. Either:
242
+ - Switch to Option 2 / Quick Start for piped input support
243
+ - Or use: `cat file.md | richless --md - | less -R`
244
+
245
+ ### Colors don't show up
246
+
247
+ If you're using Option 1 (manual setup), make sure you have the `-R` flag set:
248
+ ```bash
249
+ export LESS="-R"
250
+ ```
251
+ The init script (Option 2 / Quick Start) sets this automatically.
252
+
253
+ ### Shell function not loading
254
+
255
+ Make sure you're sourcing the init script, not executing it:
256
+ ```bash
257
+ source ~/.richless-init.sh # Correct
258
+ ./richless-init.sh # Wrong - this won't define the function in your shell
259
+ ```
260
+
261
+ ### After sourcing, "less file.md" shows an error
262
+
263
+ Make sure you've reinstalled richless after any updates:
264
+ ```bash
265
+ uv tool uninstall richless
266
+ uv tool install .
267
+ source ~/.richless-init.sh # Re-source to pick up changes
268
+ ```
269
+
270
+ ## Updating
271
+
272
+ ```bash
273
+ # Update to the latest version
274
+ cd /path/to/richless
275
+ git pull # Get latest changes
276
+ uv tool upgrade richless
277
+
278
+ # Re-source the init script to pick up any changes
279
+ source ~/.richless-init.sh
280
+ ```
281
+
282
+ ## Uninstalling
283
+
284
+ ```bash
285
+ # Remove the tool
286
+ uv tool uninstall richless
287
+
288
+ # Remove from your shell config (~/.bashrc or ~/.zshrc)
289
+ # Delete or comment out the source line:
290
+ # source ~/.richless-init.sh
291
+
292
+ # Remove the init script
293
+ rm ~/.richless-init.sh
294
+
295
+ # If using Option 1 instead, remove these lines:
296
+ # export LESSOPEN="|richless %s"
297
+ # export LESS="-R"
298
+ ```
299
+
300
+ ## Development
301
+
302
+ ```bash
303
+ # Clone the repository
304
+ git clone <repository-url>
305
+ cd richless
306
+
307
+ # Install dependencies
308
+ uv sync
309
+
310
+ # Install in development mode
311
+ uv tool install --editable .
312
+
313
+ # Make changes, then reinstall
314
+ uv tool install --editable . --force
315
+
316
+ # Test it
317
+ less README.md
318
+
319
+ # Run tests
320
+ uv run pytest tests/test_richless.py -v
321
+ ```
322
+
323
+ ## Dependencies
324
+
325
+ - [rich](https://github.com/Textualize/rich) - Python library for rich terminal output, Markdown rendering, and syntax highlighting
326
+ - [Pygments](https://pygments.org/) - Syntax highlighting library
327
+ - Python 3.12+
328
+ - uv - Modern Python package manager
329
+
330
+ ## Comparison to Alternatives
331
+
332
+ **vs. glow / mdcat / bat:**
333
+ - richless integrates directly with `less`, so you use your familiar pager commands
334
+ - Works transparently - no need to remember a different command
335
+ - Supports all standard `less` features (search, navigation, etc.)
336
+
337
+ **vs. vimpager:**
338
+ - Lighter weight, doesn't require Vim
339
+ - Uses rich library's excellent Markdown rendering with full table support
340
+ - Simple LESSOPEN integration
341
+
342
+ ## License
343
+
344
+ See LICENSE file for details.
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "richless"
3
+ version = "0.2.1"
4
+ description = "LESSOPEN filter for Markdown rendering and syntax highlighting with less"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "rich>=13.8.0",
9
+ "pygments>=2.0.0",
10
+ ]
11
+
12
+ [project.optional-dependencies]
13
+ dev = [
14
+ "pytest>=7.0.0",
15
+ ]
16
+
17
+ [project.scripts]
18
+ richless = "richless:main"
19
+
20
+ [build-system]
21
+ requires = ["hatchling"]
22
+ build-backend = "hatchling.build"
23
+
24
+ [tool.hatch.build.targets.sdist]
25
+ include = ["richless.py", "richless-init.sh", "LICENSE", "README.md"]
@@ -0,0 +1,128 @@
1
+ #!/bin/sh
2
+ # richless-init.sh
3
+ #
4
+ # Shell integration for richless - transparent Markdown rendering in less
5
+ #
6
+ # Usage: Add this line to your ~/.bashrc, ~/.zshrc, or ~/.profile:
7
+ # source /path/to/richless-init.sh
8
+ #
9
+ # This will override the 'less' command to automatically render Markdown files
10
+ # and support additional features like piped input and force-markdown mode.
11
+ #
12
+ # Compatible with: sh, bash, zsh
13
+
14
+ # Only initialize if richless is available
15
+ if ! command -v richless >/dev/null 2>&1; then
16
+ echo "Warning: richless command not found. Please install richless first." >&2
17
+ return 1 2>/dev/null || exit 1
18
+ fi
19
+
20
+ # Configure LESSOPEN for automatic Markdown detection
21
+ export LESSOPEN="|richless %s"
22
+ export LESS="${LESS:--R}" # Add -R flag if LESS not already set, otherwise preserve existing
23
+
24
+ # Transparent wrapper function for less
25
+ # This handles cases where LESSOPEN doesn't work (piped input, force markdown)
26
+ less() {
27
+ # Check if stdin is a pipe (not a terminal)
28
+ if [ ! -t 0 ]; then
29
+ # Reading from pipe - check for --md flag (must be exact match, not substring)
30
+ local force_markdown=0
31
+ for arg in "$@"; do
32
+ case "$arg" in
33
+ --md|-m) force_markdown=1; break ;;
34
+ esac
35
+ done
36
+
37
+ # Save piped input to temp file
38
+ local tmpfile
39
+ tmpfile=$(mktemp "${TMPDIR:-/tmp}/richless.XXXXXX") || return 1
40
+ cat > "$tmpfile"
41
+
42
+ # Collect non --md/--m arguments for less
43
+ local clean_args=""
44
+ for arg in "$@"; do
45
+ case "$arg" in
46
+ --md|-m)
47
+ # Skip our custom flag
48
+ ;;
49
+ *)
50
+ clean_args="${clean_args} ${arg}"
51
+ ;;
52
+ esac
53
+ done
54
+
55
+ # Check if we should render as markdown
56
+ if [ $force_markdown -eq 1 ]; then
57
+ richless --md "$tmpfile" | command less -R ${clean_args}
58
+ else
59
+ # Check for YAML/JSON first - these should use syntax highlighting, not markdown
60
+ first_line=$(head -1 "$tmpfile" 2>/dev/null)
61
+ if printf '%s\n' "$first_line" | grep -qE '^---$|^%YAML|^[[:space:]]*[{[]'; then
62
+ # Looks like YAML or JSON - use syntax highlighting
63
+ richless "$tmpfile" | command less -R ${clean_args}
64
+ # Check for YAML with comments: first non-comment line has key: pattern
65
+ elif grep -m1 -vE '^[[:space:]]*#|^[[:space:]]*$' "$tmpfile" 2>/dev/null | grep -qE '^[a-zA-Z_][a-zA-Z0-9_-]*:'; then
66
+ # Looks like YAML with comments - use syntax highlighting
67
+ richless "$tmpfile" | command less -R ${clean_args}
68
+ # Auto-detect: check if piped content looks like markdown
69
+ elif grep -qE '^#{1,6} |^\* |^- |^[0-9]+\. |^\[.*\]\(.*\)|^```|\*\*.*\*\*|^>|^\||^-{3,}|^={3,}' "$tmpfile" 2>/dev/null; then
70
+ richless --md "$tmpfile" | command less -R ${clean_args}
71
+ else
72
+ command less -R ${clean_args} "$tmpfile"
73
+ fi
74
+ fi
75
+
76
+ # Clean up temp file
77
+ rm -f "$tmpfile"
78
+ else
79
+ # No pipe - check for --md flag among arguments (must be exact match, not substring)
80
+ local force_markdown=0
81
+ local has_md_flag=0
82
+
83
+ for arg in "$@"; do
84
+ case "$arg" in
85
+ --md|-m) has_md_flag=1; break ;;
86
+ esac
87
+ done
88
+
89
+ if [ $has_md_flag -eq 1 ]; then
90
+ # Build arrays for files and options
91
+ local files=""
92
+ local opts=""
93
+
94
+ for arg in "$@"; do
95
+ case "$arg" in
96
+ --md|-m)
97
+ force_markdown=1
98
+ ;;
99
+ -*)
100
+ opts="${opts} ${arg}"
101
+ ;;
102
+ *)
103
+ files="${files} ${arg}"
104
+ ;;
105
+ esac
106
+ done
107
+
108
+ # Render each file with markdown
109
+ for file in ${files}; do
110
+ if [ -n "$file" ]; then
111
+ richless --md "$file" | command less -R ${opts}
112
+ fi
113
+ done
114
+ else
115
+ # Normal less operation - let LESSOPEN handle it
116
+ command less "$@"
117
+ fi
118
+ fi
119
+ }
120
+
121
+ # Export the function so it's available in subshells (bash only)
122
+ # Note: zsh doesn't support 'export -f', but functions are automatically available in subshells
123
+ if [ -n "$BASH_VERSION" ]; then
124
+ export -f less 2>/dev/null || true
125
+ fi
126
+
127
+ # Suppress any function output
128
+ : # null command to ensure clean sourcing
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ richless - A LESSOPEN filter for Markdown rendering and syntax highlighting.
4
+
5
+ This utility works as a preprocessor for 'less', automatically rendering
6
+ Markdown files and syntax highlighting code using the rich library.
7
+ """
8
+
9
+ import argparse
10
+ import os
11
+ import re
12
+ import shutil
13
+ import sys
14
+ from pathlib import Path
15
+ from rich.console import Console
16
+ from rich.markdown import Markdown
17
+ from rich.syntax import Syntax
18
+
19
+
20
+ def is_markdown_file(filepath: str) -> bool:
21
+ """Check if the file has a Markdown extension."""
22
+ ext = Path(filepath).suffix.lower()
23
+ return ext in ['.md', '.markdown']
24
+
25
+
26
+ def detect_syntax_from_content(content: str) -> str:
27
+ """Detect file type from content when extension is unknown."""
28
+ if not content:
29
+ return "text"
30
+
31
+ lines = content.split('\n', 20) # Check first 20 lines
32
+ first_line = lines[0].strip() if lines else ''
33
+
34
+ # YAML detection: starts with --- or %YAML
35
+ if first_line == '---' or first_line.startswith('%YAML'):
36
+ return "yaml"
37
+
38
+ # JSON detection: starts with { or [
39
+ if first_line.startswith('{') or first_line.startswith('['):
40
+ return "json"
41
+
42
+ # Shebang detection
43
+ if first_line.startswith('#!'):
44
+ if 'python' in first_line:
45
+ return "python"
46
+ elif 'bash' in first_line or '/sh' in first_line:
47
+ return "bash"
48
+ elif 'node' in first_line:
49
+ return "javascript"
50
+ elif 'ruby' in first_line:
51
+ return "ruby"
52
+ elif 'perl' in first_line:
53
+ return "perl"
54
+
55
+ # XML/HTML detection
56
+ if first_line.startswith('<?xml') or first_line.startswith('<!DOCTYPE'):
57
+ return "xml"
58
+
59
+ # YAML detection: look for key: value patterns (possibly after # comments)
60
+ # Skip comment lines and look for YAML structure
61
+ for line in lines:
62
+ stripped = line.strip()
63
+ # Skip empty lines and comments
64
+ if not stripped or stripped.startswith('#'):
65
+ continue
66
+ # Check for YAML key: value pattern (word followed by colon)
67
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_-]*:\s*', stripped):
68
+ return "yaml"
69
+ # If first non-comment line doesn't look like YAML, stop checking
70
+ break
71
+
72
+ return "text"
73
+
74
+
75
+ def get_terminal_width() -> int:
76
+ """Get terminal width, even when stdout is piped."""
77
+ # Try stderr since stdout is piped through LESSOPEN
78
+ try:
79
+ return os.get_terminal_size(sys.stderr.fileno()).columns
80
+ except (OSError, ValueError):
81
+ pass
82
+ # Fall back to shutil (checks COLUMNS env var, then defaults to 80)
83
+ return shutil.get_terminal_size().columns
84
+
85
+
86
+ def render_markdown(content: str) -> None:
87
+ """Render Markdown content using rich."""
88
+ width = get_terminal_width()
89
+ console = Console(force_terminal=True, width=width)
90
+ md = Markdown(content)
91
+ console.print(md)
92
+
93
+
94
+ def render_syntax(filepath: str, content: str) -> None:
95
+ """Render code with syntax highlighting using rich."""
96
+ # Determine lexer from file extension
97
+ path = Path(filepath)
98
+ ext = path.suffix.lstrip('.')
99
+
100
+ # Map non-standard extensions to their lexer names
101
+ ext_map = {
102
+ 'jsonl': 'json', # JSON Lines uses JSON syntax
103
+ }
104
+ if ext in ext_map:
105
+ ext = ext_map[ext]
106
+
107
+ # If no recognizable extension, try to detect from content
108
+ # Temp files from shell wrapper are named richless.XXXXXX (random suffix)
109
+ if not ext or path.stem == 'richless':
110
+ ext = detect_syntax_from_content(content)
111
+
112
+ # Calculate width needed to avoid truncating long lines
113
+ # This allows 'less' to handle horizontal scrolling
114
+ lines = content.splitlines()
115
+ max_line_length = max((len(line) for line in lines), default=80)
116
+ width = max(max_line_length + 1, 80)
117
+
118
+ # Create console with width to accommodate longest line
119
+ console = Console(force_terminal=True, width=width)
120
+
121
+ # Create Syntax object - use default background to avoid padding
122
+ syntax = Syntax(content, ext or "text", theme="monokai", line_numbers=False,
123
+ background_color="default")
124
+ console.print(syntax)
125
+
126
+
127
+ def main():
128
+ """Main entry point for richless."""
129
+ parser = argparse.ArgumentParser(
130
+ description='LESSOPEN filter for Markdown rendering and syntax highlighting',
131
+ add_help=True,
132
+ )
133
+
134
+ parser.add_argument('file',
135
+ help='File to process (use "-" for stdin)')
136
+ parser.add_argument('--md', '--markdown',
137
+ dest='force_markdown',
138
+ action='store_true',
139
+ help='Force Markdown rendering even for non-.md files')
140
+
141
+ args = parser.parse_args()
142
+
143
+ # Strip whitespace from filename (less adds leading space via LESSOPEN)
144
+ filepath = args.file.strip()
145
+
146
+ # Handle stdin input
147
+ input_file = filepath
148
+ temp_file = None
149
+ content = None
150
+
151
+ try:
152
+ if filepath == '-' or filepath == '/dev/stdin':
153
+ # Read from stdin
154
+ content = sys.stdin.read()
155
+ input_file = 'stdin.md' if args.force_markdown else 'stdin.txt'
156
+ else:
157
+ # Read from file
158
+ with open(filepath, 'r', encoding='utf-8') as f:
159
+ content = f.read()
160
+ input_file = filepath
161
+
162
+ # Determine if we should render as markdown
163
+ is_markdown = args.force_markdown or is_markdown_file(input_file)
164
+
165
+ if is_markdown:
166
+ render_markdown(content)
167
+ else:
168
+ # Syntax highlighting for code files
169
+ render_syntax(input_file, content)
170
+
171
+ return 0
172
+
173
+ except FileNotFoundError:
174
+ print(f"richless: File not found: {args.file}", file=sys.stderr)
175
+ return 1
176
+ except Exception as e:
177
+ print(f"richless: Error: {e}", file=sys.stderr)
178
+ # Fall back to plain output
179
+ try:
180
+ if content:
181
+ print(content, end='')
182
+ elif filepath not in ['-', '/dev/stdin']:
183
+ with open(filepath, 'r', encoding='utf-8') as f:
184
+ print(f.read(), end='')
185
+ return 0
186
+ except Exception:
187
+ return 1
188
+
189
+
190
+ if __name__ == "__main__":
191
+ sys.exit(main())