Skip to content

Commit 828e821

Browse files
committed
Initial commit
0 parents  commit 828e821

File tree

9 files changed

+281
-0
lines changed

9 files changed

+281
-0
lines changed

.github/workflows/publish.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Publish Console App
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
8+
jobs:
9+
publish:
10+
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: write
14+
15+
steps:
16+
- uses: actions/checkout@v3
17+
- name: Set up Python 3.10
18+
uses: actions/setup-python@v3
19+
with:
20+
python-version: "3.10"
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install -r requirements.txt
26+
27+
- name: Build app
28+
run: python -m build
29+
30+
- name: Create GitHub release
31+
uses: softprops/action-gh-release@v1
32+
with:
33+
files: dist/wtf-${{ github.ref_name }}-py3-none-any.whl
34+
token: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/.idea
2+
/.venv
3+
/dist
4+
/*.egg-info
5+
.env

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Alexander Preibisch
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.

README.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# wtf
2+
3+
**A small helper to display cheatsheets, shortcuts and others inside your terminal.**
4+
5+
![Demo](assets/demo.gif)
6+
7+
## Requirements
8+
9+
- fzf
10+
- config file (see Configuration)
11+
12+
## Installation
13+
14+
Download [latest release](https://github.com/TheAxelander/wtf/releases/latest) and install it via `pipx`
15+
16+
``` bash
17+
apt install python3 python3-pipx
18+
pipx install wtf-x.x.x-py3-none-any.whl
19+
```
20+
21+
Optionally create a venv for development
22+
23+
``` bash
24+
git clone https://github.com/TheAxelander/wtf.git
25+
cd wtf
26+
python3 -m venv venv
27+
source venv/bin/activate
28+
pip install -r requirements.txt
29+
```
30+
31+
## Usage
32+
33+
Without any parameter `fzf` is used to select a file which is then rendered.
34+
35+
``` bash
36+
wtf
37+
38+
>>> fzf screen is shown
39+
```
40+
41+
Appending a `sheet` (simple file name) renders the file directly without any further selection
42+
43+
``` bash
44+
wtf tmux
45+
46+
>>> tmux file will be displayed
47+
```
48+
49+
## Configuration
50+
51+
See below example config file which is required in `~/.config/wtf/wtf.conf`
52+
53+
``` ini
54+
CHEATSHEET_REPO=/home/axelander/.local/wtf
55+
TABLE_DELIMITER=|
56+
PREVIEW_COMMAND=cat #You could also use wtf itself to render the file inside the fzf preview
57+
```
58+
59+
## Example file
60+
61+
Files need to be written to be compatible with [Rich](https://github.com/Textualize/rich) to render a table like below. The first line will be displayed as header.
62+
63+
```
64+
Command |Description
65+
tmux |New session
66+
tmux new |New session
67+
tmux new -s sessionname |New session with name
68+
tmux a |Attach session
69+
tmux a -t sessionname |Attach to named session
70+
[bold cyan]CTRL[/bold cyan] + [bold cyan]B[/bold cyan] [bold yellow]D[/bold yellow]|Detach session
71+
```

assets/demo.gif

359 KB
Loading

pyproject.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[build-system]
2+
requires = ["setuptools>=42", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "wtf"
7+
version = "1.0.0"
8+
authors = [{ name = "Alexander Preibisch", email = "[email protected]" }]
9+
description = "Print some common notes in table format"
10+
readme = "README.md"
11+
license = { file = "LICENSE" }
12+
classifiers = [
13+
"Programming Language :: Python :: 3.10",
14+
"Programming Language :: Python :: 3.11",
15+
"Programming Language :: Python :: 3.12",
16+
"License :: OSI Approved :: MIT License",
17+
"Operating System :: OS Independent"
18+
]
19+
requires-python = ">=3.10"
20+
dependencies = [
21+
"python-dotenv>=1.1.0",
22+
"rich>=13.7.1"
23+
]
24+
25+
[project.scripts]
26+
wtf = "wtf.main:main"
27+
28+
[project.urls]
29+
homepage = "https://github.com/TheAxelander/wtf"
30+
repository = "https://github.com/TheAxelander/wtf"

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build
2+
python-dotenv
3+
rich

setup.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name='wtf',
5+
version='0.1',
6+
packages=['wtf'],
7+
install_requires=[
8+
'rich'
9+
],
10+
entry_points={
11+
'console_scripts': [
12+
'wtf=wtf.main:main'
13+
],
14+
},
15+
url='',
16+
license='MIT',
17+
author='Alexander Preibisch',
18+
author_email='[email protected]',
19+
description=''
20+
)

wtf/main.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import argparse
2+
import subprocess
3+
from dotenv import dotenv_values
4+
from rich.console import Console
5+
from rich.table import Table
6+
from pathlib import Path
7+
8+
console = Console()
9+
10+
def main():
11+
parser = argparse.ArgumentParser()
12+
parser.add_argument('sheet', nargs='?', type=str, help="Specific cheatsheet which should be printed")
13+
14+
try:
15+
config = get_config()
16+
directory = Path(config['CHEATSHEET_REPO']).expanduser()
17+
except Exception as e:
18+
console.print(f"[bold red]Error reading config: {e}[/bold red]")
19+
exit(1)
20+
21+
args = parser.parse_args()
22+
if args.sheet:
23+
render_file(directory / args.sheet)
24+
else:
25+
select_file_via_fzf(directory)
26+
27+
28+
def get_config():
29+
config_path = Path.home() / '.config' / 'wtf' / 'wtf.conf'
30+
if not config_path.exists():
31+
raise FileNotFoundError(f"Config file not found: {config_path}")
32+
33+
return dotenv_values(config_path)
34+
35+
36+
def select_file_via_fzf(directory):
37+
if not directory.exists():
38+
console.print(f"[bold red]Directory {directory} does not exist![/bold red]")
39+
exit(1)
40+
41+
# Run fzf to list files in from directory
42+
try:
43+
try:
44+
config = get_config()
45+
preview_command = config.get('PREVIEW_COMMAND', 'cat')
46+
except Exception as e:
47+
console.print(f"[bold red]Error reading config: {e}[/bold red]")
48+
exit(1)
49+
50+
selected_file = subprocess.check_output(
51+
['fzf', '--preview', f'{preview_command} {{}}'], # Run fzf and show a preview of the file content
52+
cwd=str(directory), # working directory
53+
text=True
54+
).strip()
55+
56+
if selected_file:
57+
render_file(directory / selected_file)
58+
59+
except subprocess.CalledProcessError as e:
60+
console.print(f"[bold red]Error with fzf command: {e}[/bold red]")
61+
62+
63+
def render_file(file_path):
64+
if not file_path.exists():
65+
console.print(f"[red]File not found: {file_path}[/red]")
66+
return
67+
68+
try:
69+
config = get_config()
70+
delimiter = config.get('TABLE_DELIMITER', ',')
71+
except Exception as e:
72+
console.print(f"[bold red]Error reading config: {e}[/bold red]")
73+
exit(1)
74+
75+
with open(file_path, 'r') as f:
76+
lines = [line.strip() for line in f if line.strip()]
77+
78+
if not lines:
79+
console.print("[yellow]File is empty.[/yellow]")
80+
return
81+
82+
headers = lines[0].split(delimiter)
83+
table = Table(show_header=True, header_style="bold cyan", show_lines=True)
84+
85+
for header in headers:
86+
table.add_column(header.strip())
87+
table.columns[0].no_wrap=True
88+
89+
for line in lines[1:]:
90+
row = [col.strip() for col in line.split(delimiter)]
91+
table.add_row(*row)
92+
93+
console.print(table)
94+
95+
96+
if __name__ == '__main__':
97+
main()

0 commit comments

Comments
 (0)