openduck-cli 0.1.0__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 (39) hide show
  1. openduck_cli-0.1.0/PKG-INFO +204 -0
  2. openduck_cli-0.1.0/README.md +186 -0
  3. openduck_cli-0.1.0/openduck/__init__.py +42 -0
  4. openduck_cli-0.1.0/openduck/__main__.py +6 -0
  5. openduck_cli-0.1.0/openduck/core/__init__.py +36 -0
  6. openduck_cli-0.1.0/openduck/core/clipboard.py +57 -0
  7. openduck_cli-0.1.0/openduck/core/config.py +108 -0
  8. openduck_cli-0.1.0/openduck/core/error_classifier.py +53 -0
  9. openduck_cli-0.1.0/openduck/core/sql_utils.py +63 -0
  10. openduck_cli-0.1.0/openduck/gui/__init__.py +5 -0
  11. openduck_cli-0.1.0/openduck/gui/app.py +802 -0
  12. openduck_cli-0.1.0/openduck/gui/dialogs/__init__.py +7 -0
  13. openduck_cli-0.1.0/openduck/gui/dialogs/add_connection_dialog.py +77 -0
  14. openduck_cli-0.1.0/openduck/gui/dialogs/export_dialog.py +109 -0
  15. openduck_cli-0.1.0/openduck/gui/dialogs/save_query_dialog.py +41 -0
  16. openduck_cli-0.1.0/openduck/gui/screens/__init__.py +6 -0
  17. openduck_cli-0.1.0/openduck/gui/screens/about.py +84 -0
  18. openduck_cli-0.1.0/openduck/gui/screens/matrix.py +90 -0
  19. openduck_cli-0.1.0/openduck/gui/styles.py +71 -0
  20. openduck_cli-0.1.0/openduck/gui/widgets/__init__.py +17 -0
  21. openduck_cli-0.1.0/openduck/gui/widgets/database_tree.py +106 -0
  22. openduck_cli-0.1.0/openduck/gui/widgets/file_tree.py +14 -0
  23. openduck_cli-0.1.0/openduck/gui/widgets/query_tab.py +63 -0
  24. openduck_cli-0.1.0/openduck/gui/widgets/resize_handle.py +36 -0
  25. openduck_cli-0.1.0/openduck/gui/widgets/results_table.py +54 -0
  26. openduck_cli-0.1.0/openduck/gui/widgets/sql_editor.py +207 -0
  27. openduck_cli-0.1.0/openduck/main.py +25 -0
  28. openduck_cli-0.1.0/openduck_cli.egg-info/PKG-INFO +204 -0
  29. openduck_cli-0.1.0/openduck_cli.egg-info/SOURCES.txt +37 -0
  30. openduck_cli-0.1.0/openduck_cli.egg-info/dependency_links.txt +1 -0
  31. openduck_cli-0.1.0/openduck_cli.egg-info/entry_points.txt +3 -0
  32. openduck_cli-0.1.0/openduck_cli.egg-info/requires.txt +11 -0
  33. openduck_cli-0.1.0/openduck_cli.egg-info/top_level.txt +1 -0
  34. openduck_cli-0.1.0/pyproject.toml +35 -0
  35. openduck_cli-0.1.0/setup.cfg +4 -0
  36. openduck_cli-0.1.0/tests/test.py +29 -0
  37. openduck_cli-0.1.0/tests/test_all_errors.py +98 -0
  38. openduck_cli-0.1.0/tests/test_save_load.py +171 -0
  39. openduck_cli-0.1.0/tests/test_unit.py +80 -0
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: openduck-cli
3
+ Version: 0.1.0
4
+ Summary: The Modern, Lightning-Fast Terminal SQL IDE & Data Explorer for DuckDB
5
+ Author: Bertatron
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: textual[syntax]>=0.50.0
10
+ Requires-Dist: duckdb>=0.9.0
11
+ Requires-Dist: pandas>=2.0.0
12
+ Requires-Dist: openpyxl>=3.1.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: pyinstaller; extra == "dev"
16
+ Provides-Extra: db
17
+ Requires-Dist: pymssql; extra == "db"
18
+
19
+ # 🦆 OpenDuck
20
+
21
+ <p align="center">
22
+ <b>The Modern, Lightning-Fast Terminal SQL IDE & Data Explorer for DuckDB</b>
23
+ </p>
24
+
25
+ <p align="center">
26
+ <img src="https://img.shields.io/badge/Python-3.10%2B-blue?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.10+" />
27
+ <img src="https://img.shields.io/badge/DuckDB-Fast%20Analytics-yellow?style=for-the-badge&logo=duckdb&logoColor=black" alt="DuckDB" />
28
+ <img src="https://img.shields.io/badge/TUI-Textual-green?style=for-the-badge" alt="Textual" />
29
+ <img src="https://img.shields.io/badge/License-MIT-orange?style=for-the-badge" alt="MIT License" />
30
+ </p>
31
+
32
+ ---
33
+
34
+ <p align="center">
35
+ <img width="100%" alt="OpenDuck Interface" src="https://github.com/user-attachments/assets/4aea9df6-6f00-494d-a171-d76a9a3b8133" />
36
+ </p>
37
+
38
+ **OpenDuck** brings the power and speed of **DuckDB** straight to your terminal in an intuitive, multi-tab Text User Interface (TUI). Effortlessly browse files, inspect datasets, write queries with full syntax highlighting, sort and filter millions of rows in milliseconds, and export results — all without leaving your shell.
39
+
40
+ ---
41
+
42
+ ## ✨ Features
43
+
44
+ - ⚡ **Instant File Queries**: Click or select any file (`.csv`, `.parquet`, `.xlsx`, `.json`, `.duckdb`, `.sqlite`, `.arrow`) from the sidebar to instantly load and execute queries with zero boilerplates.
45
+ - 🎨 **Rich SQL Syntax Highlighting**: Tree-sitter powered VS Code Dark styling with real-time token highlighting for SQL keywords, strings, numbers, identifiers, and comments.
46
+ - 🚀 **Asynchronous & Non-Blocking**: Queries execute in dedicated background threads — the interface remains buttery smooth and never freezes, even on heavy multi-gigabyte queries.
47
+ - 📑 **Multi-Tab Workspace**: Work with multiple datasets simultaneously with tab navigation, close buttons (`✕`), and tab titles dynamically bound to files and queries.
48
+ - 📊 **Interactive Data Viewer**:
49
+ - Sort columns ascending/descending with clickable headers (safe null handling).
50
+ - Quick column filters.
51
+ - Live query execution runtime, row count, and memory consumption stats.
52
+ - 📋 **Terminal & System Clipboard Integration**:
53
+ - Copy highlighted cells or entire rows (`Ctrl+C`).
54
+ - Copy full query results as TSV (`Ctrl+A`).
55
+ - Native terminal copy & paste with multi-level Undo/Redo (`Cmd+Z` / `Ctrl+Z` / `Ctrl+Y`).
56
+ - Toggle SQL comments effortlessly (`F3` / `Cmd+/`).
57
+ - 💾 **One-Click Export**: Export results to `.csv` or `.xlsx` files with a single click.
58
+ - 📜 **History & Saved Queries**: Automatically preserves query history and lets you save reusable named queries with quick one-click recall into your editor.
59
+ - 🎛️ **Draggable Resizable Sidebar**: Adjust the width of your sidebar on the fly with mouse drag.
60
+ - 🛡️ **Intelligent Error Diagnostics**: Corrupted file, wrong delimiter, permission issue, or missing dataset? OpenDuck identifies the exact cause and displays human-readable actionable solutions.
61
+ - 🐇 **Easter Egg**: Secret Matrix rain screensaver!
62
+
63
+ ---
64
+
65
+ ## 📂 Supported File Formats
66
+
67
+ OpenDuck automatically recognises and formats queries for:
68
+
69
+ | Format | Extensions | Query Mode |
70
+ | :--- | :--- | :--- |
71
+ | **Parquet** | `.parquet` | Native direct query |
72
+ | **Arrow** | `.arrow` | Native direct query |
73
+ | **CSV & GZ** | `.csv`, `.csv.gz` | `read_csv_auto(...)` |
74
+ | **JSON & JSONL** | `.json`, `.jsonl` | `read_json_auto(...)` |
75
+ | **Excel** | `.xlsx`, `.xls` | In-memory table registration (`pandas` + `openpyxl`) |
76
+ | **SQLite** | `.sqlite`, `.sqlite3`, `.db` | Automatic SQLite extension attach |
77
+ | **DuckDB** | `.duckdb` | Database attach & table explore |
78
+
79
+ ---
80
+
81
+ ## 🚀 Quickstart
82
+
83
+ ### 1. Installation
84
+
85
+ **Via PyPI:**
86
+ ```bash
87
+ pip install openduck-cli
88
+ ```
89
+
90
+ **From source:**
91
+ ```bash
92
+ git clone https://github.com/klandermans/openduck.git
93
+ cd openduck
94
+ pip install -r requirements.txt
95
+ # or: pip install -e .
96
+ ```
97
+
98
+ > **Note:** `textual[syntax]` installs the required `tree-sitter` and `tree-sitter-sql` engines for syntax highlighting out of the box.
99
+
100
+ ### 2. Run OpenDuck
101
+
102
+ ```bash
103
+ python openduck.py
104
+ ```
105
+
106
+ Or make it available system-wide as a command:
107
+
108
+ ```bash
109
+ chmod +x openduck.sh
110
+ sudo cp openduck.sh /usr/local/bin/openduck
111
+ openduck
112
+ ```
113
+
114
+ ---
115
+
116
+ ## ⌨️ Keyboard & Mouse Shortcuts
117
+
118
+ | Shortcut | Description |
119
+ | :--- | :--- |
120
+ | `Ctrl+Enter` / `Cmd+Enter` | **Run Query** (runs selection if text is selected, or full editor) |
121
+ | `Ctrl+S` / `Cmd+S` | **Save Query** with custom name to `openduck.json` |
122
+ | `F3` / `Cmd+/` / `Ctrl+/` | **Toggle SQL Comment** (`--`) on current line or selection |
123
+ | `Ctrl+Z` / `Cmd+Z` | **Undo** last edit |
124
+ | `Ctrl+Y` / `Cmd+Shift+Z` | **Redo** undone edit |
125
+ | `Ctrl+W` / `Cmd+W` | **Close Current Tab** |
126
+ | `Ctrl+M` | **Toggle Mouse Mode** (switch between TUI mouse and native terminal text selection) |
127
+ | `Ctrl+C` / `Cmd+C` | **Copy** (Editor selection, or table row/cell to system clipboard) |
128
+ | `Ctrl+A` / `Cmd+A` | **Copy Table** (when focused on results) / Select all (in editor) |
129
+ | `F1` | **About Dialog** (click the duck for a surprise! 🕶️) |
130
+ | `Ctrl+Q` / `Cmd+Q` | **Quit** OpenDuck |
131
+
132
+ ---
133
+
134
+ ## ⚙️ Configuration & Storage
135
+
136
+ OpenDuck automatically manages `openduck.json` in your working directory. It persists:
137
+ - The last 200 executed queries in chronological order.
138
+ - Your named saved queries.
139
+ - Database configurations.
140
+
141
+ ```json
142
+ {
143
+ "history": [
144
+ {
145
+ "sql": "SELECT * FROM read_csv_auto('sales.csv') WHERE revenue > 1000 LIMIT 100;",
146
+ "timestamp": "2026-10-14T15:30:00.123456"
147
+ }
148
+ ],
149
+ "saved_queries": [
150
+ {
151
+ "name": "Q3 Revenue by Region",
152
+ "sql": "SELECT region, SUM(amount) FROM read_parquet('data/*.parquet') GROUP BY region;",
153
+ "timestamp": "2026-10-14T15:32:00.654321"
154
+ }
155
+ ],
156
+ "connections": []
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## 💡 Tips & Tricks
163
+
164
+ - **Click to Query**: Clicking any file in the left sidebar automatically populates the query into your active tab, updates the tab name, and executes the preview.
165
+ - **Run Selection Only**: Highlight any part of your SQL script and press `Ctrl+Enter` to run only the highlighted snippet — just like in DBeaver, DataGrip, or VS Code!
166
+ - **Selective Comments**: Select multiple lines and press `F3` or `Cmd+/` to comment or uncomment SQL blocks simultaneously.
167
+ - **Instant Clipboard Sharing**: Results copied via `Ctrl+C` or `Ctrl+A` are immediately synced to your macOS (`pbcopy`), Linux (`wl-copy`/`xclip`), or Windows clipboard.
168
+
169
+ ---
170
+
171
+ ## 📁 Project Structure
172
+
173
+ ```text
174
+ openduck/
175
+ ├── openduck/ # Core Python package
176
+ │ ├── __init__.py # Package entry and exports
177
+ │ ├── __main__.py # CLI invocation (python -m openduck)
178
+ │ ├── main.py # Application bootstrap & logging setup
179
+ │ ├── core/ # Core logic & backend
180
+ │ │ ├── config.py # Config persistence, query history, saved queries
181
+ │ │ ├── clipboard.py # Cross-platform system clipboard integration
182
+ │ │ ├── sql_utils.py # SQL formatting, file-to-query mapper, comments
183
+ │ │ └── error_classifier.py # Diagnostic error classifier
184
+ │ └── gui/ # Textual user interface
185
+ │ ├── app.py # Main DuckCLI application
186
+ │ ├── styles.py # Textual CSS stylesheets
187
+ │ ├── screens/ # Modal screens (About, Matrix screensaver)
188
+ │ ├── dialogs/ # Modal dialogs (Save Query, Export, Add DB)
189
+ │ └── widgets/ # Custom widgets (Editor, ResultsTable, Trees, etc.)
190
+ ├── docs/ # Documentation & guides
191
+ ├── scripts/ # Build scripts & packaging specs
192
+ ├── tests/ # Automated test suite
193
+ ├── samples/ # Sample datasets for testing & exploration
194
+ ├── openduck.py # Root executable script
195
+ ├── openduck.sh # Shell launcher script
196
+ └── pyproject.toml # Python package configuration
197
+ ```
198
+
199
+ ---
200
+
201
+ ## 📜 License
202
+
203
+ Distributed under the **MIT License**. See `LICENSE` for details.
204
+
@@ -0,0 +1,186 @@
1
+ # 🦆 OpenDuck
2
+
3
+ <p align="center">
4
+ <b>The Modern, Lightning-Fast Terminal SQL IDE & Data Explorer for DuckDB</b>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <img src="https://img.shields.io/badge/Python-3.10%2B-blue?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.10+" />
9
+ <img src="https://img.shields.io/badge/DuckDB-Fast%20Analytics-yellow?style=for-the-badge&logo=duckdb&logoColor=black" alt="DuckDB" />
10
+ <img src="https://img.shields.io/badge/TUI-Textual-green?style=for-the-badge" alt="Textual" />
11
+ <img src="https://img.shields.io/badge/License-MIT-orange?style=for-the-badge" alt="MIT License" />
12
+ </p>
13
+
14
+ ---
15
+
16
+ <p align="center">
17
+ <img width="100%" alt="OpenDuck Interface" src="https://github.com/user-attachments/assets/4aea9df6-6f00-494d-a171-d76a9a3b8133" />
18
+ </p>
19
+
20
+ **OpenDuck** brings the power and speed of **DuckDB** straight to your terminal in an intuitive, multi-tab Text User Interface (TUI). Effortlessly browse files, inspect datasets, write queries with full syntax highlighting, sort and filter millions of rows in milliseconds, and export results — all without leaving your shell.
21
+
22
+ ---
23
+
24
+ ## ✨ Features
25
+
26
+ - ⚡ **Instant File Queries**: Click or select any file (`.csv`, `.parquet`, `.xlsx`, `.json`, `.duckdb`, `.sqlite`, `.arrow`) from the sidebar to instantly load and execute queries with zero boilerplates.
27
+ - 🎨 **Rich SQL Syntax Highlighting**: Tree-sitter powered VS Code Dark styling with real-time token highlighting for SQL keywords, strings, numbers, identifiers, and comments.
28
+ - 🚀 **Asynchronous & Non-Blocking**: Queries execute in dedicated background threads — the interface remains buttery smooth and never freezes, even on heavy multi-gigabyte queries.
29
+ - 📑 **Multi-Tab Workspace**: Work with multiple datasets simultaneously with tab navigation, close buttons (`✕`), and tab titles dynamically bound to files and queries.
30
+ - 📊 **Interactive Data Viewer**:
31
+ - Sort columns ascending/descending with clickable headers (safe null handling).
32
+ - Quick column filters.
33
+ - Live query execution runtime, row count, and memory consumption stats.
34
+ - 📋 **Terminal & System Clipboard Integration**:
35
+ - Copy highlighted cells or entire rows (`Ctrl+C`).
36
+ - Copy full query results as TSV (`Ctrl+A`).
37
+ - Native terminal copy & paste with multi-level Undo/Redo (`Cmd+Z` / `Ctrl+Z` / `Ctrl+Y`).
38
+ - Toggle SQL comments effortlessly (`F3` / `Cmd+/`).
39
+ - 💾 **One-Click Export**: Export results to `.csv` or `.xlsx` files with a single click.
40
+ - 📜 **History & Saved Queries**: Automatically preserves query history and lets you save reusable named queries with quick one-click recall into your editor.
41
+ - 🎛️ **Draggable Resizable Sidebar**: Adjust the width of your sidebar on the fly with mouse drag.
42
+ - 🛡️ **Intelligent Error Diagnostics**: Corrupted file, wrong delimiter, permission issue, or missing dataset? OpenDuck identifies the exact cause and displays human-readable actionable solutions.
43
+ - 🐇 **Easter Egg**: Secret Matrix rain screensaver!
44
+
45
+ ---
46
+
47
+ ## 📂 Supported File Formats
48
+
49
+ OpenDuck automatically recognises and formats queries for:
50
+
51
+ | Format | Extensions | Query Mode |
52
+ | :--- | :--- | :--- |
53
+ | **Parquet** | `.parquet` | Native direct query |
54
+ | **Arrow** | `.arrow` | Native direct query |
55
+ | **CSV & GZ** | `.csv`, `.csv.gz` | `read_csv_auto(...)` |
56
+ | **JSON & JSONL** | `.json`, `.jsonl` | `read_json_auto(...)` |
57
+ | **Excel** | `.xlsx`, `.xls` | In-memory table registration (`pandas` + `openpyxl`) |
58
+ | **SQLite** | `.sqlite`, `.sqlite3`, `.db` | Automatic SQLite extension attach |
59
+ | **DuckDB** | `.duckdb` | Database attach & table explore |
60
+
61
+ ---
62
+
63
+ ## 🚀 Quickstart
64
+
65
+ ### 1. Installation
66
+
67
+ **Via PyPI:**
68
+ ```bash
69
+ pip install openduck-cli
70
+ ```
71
+
72
+ **From source:**
73
+ ```bash
74
+ git clone https://github.com/klandermans/openduck.git
75
+ cd openduck
76
+ pip install -r requirements.txt
77
+ # or: pip install -e .
78
+ ```
79
+
80
+ > **Note:** `textual[syntax]` installs the required `tree-sitter` and `tree-sitter-sql` engines for syntax highlighting out of the box.
81
+
82
+ ### 2. Run OpenDuck
83
+
84
+ ```bash
85
+ python openduck.py
86
+ ```
87
+
88
+ Or make it available system-wide as a command:
89
+
90
+ ```bash
91
+ chmod +x openduck.sh
92
+ sudo cp openduck.sh /usr/local/bin/openduck
93
+ openduck
94
+ ```
95
+
96
+ ---
97
+
98
+ ## ⌨️ Keyboard & Mouse Shortcuts
99
+
100
+ | Shortcut | Description |
101
+ | :--- | :--- |
102
+ | `Ctrl+Enter` / `Cmd+Enter` | **Run Query** (runs selection if text is selected, or full editor) |
103
+ | `Ctrl+S` / `Cmd+S` | **Save Query** with custom name to `openduck.json` |
104
+ | `F3` / `Cmd+/` / `Ctrl+/` | **Toggle SQL Comment** (`--`) on current line or selection |
105
+ | `Ctrl+Z` / `Cmd+Z` | **Undo** last edit |
106
+ | `Ctrl+Y` / `Cmd+Shift+Z` | **Redo** undone edit |
107
+ | `Ctrl+W` / `Cmd+W` | **Close Current Tab** |
108
+ | `Ctrl+M` | **Toggle Mouse Mode** (switch between TUI mouse and native terminal text selection) |
109
+ | `Ctrl+C` / `Cmd+C` | **Copy** (Editor selection, or table row/cell to system clipboard) |
110
+ | `Ctrl+A` / `Cmd+A` | **Copy Table** (when focused on results) / Select all (in editor) |
111
+ | `F1` | **About Dialog** (click the duck for a surprise! 🕶️) |
112
+ | `Ctrl+Q` / `Cmd+Q` | **Quit** OpenDuck |
113
+
114
+ ---
115
+
116
+ ## ⚙️ Configuration & Storage
117
+
118
+ OpenDuck automatically manages `openduck.json` in your working directory. It persists:
119
+ - The last 200 executed queries in chronological order.
120
+ - Your named saved queries.
121
+ - Database configurations.
122
+
123
+ ```json
124
+ {
125
+ "history": [
126
+ {
127
+ "sql": "SELECT * FROM read_csv_auto('sales.csv') WHERE revenue > 1000 LIMIT 100;",
128
+ "timestamp": "2026-10-14T15:30:00.123456"
129
+ }
130
+ ],
131
+ "saved_queries": [
132
+ {
133
+ "name": "Q3 Revenue by Region",
134
+ "sql": "SELECT region, SUM(amount) FROM read_parquet('data/*.parquet') GROUP BY region;",
135
+ "timestamp": "2026-10-14T15:32:00.654321"
136
+ }
137
+ ],
138
+ "connections": []
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## 💡 Tips & Tricks
145
+
146
+ - **Click to Query**: Clicking any file in the left sidebar automatically populates the query into your active tab, updates the tab name, and executes the preview.
147
+ - **Run Selection Only**: Highlight any part of your SQL script and press `Ctrl+Enter` to run only the highlighted snippet — just like in DBeaver, DataGrip, or VS Code!
148
+ - **Selective Comments**: Select multiple lines and press `F3` or `Cmd+/` to comment or uncomment SQL blocks simultaneously.
149
+ - **Instant Clipboard Sharing**: Results copied via `Ctrl+C` or `Ctrl+A` are immediately synced to your macOS (`pbcopy`), Linux (`wl-copy`/`xclip`), or Windows clipboard.
150
+
151
+ ---
152
+
153
+ ## 📁 Project Structure
154
+
155
+ ```text
156
+ openduck/
157
+ ├── openduck/ # Core Python package
158
+ │ ├── __init__.py # Package entry and exports
159
+ │ ├── __main__.py # CLI invocation (python -m openduck)
160
+ │ ├── main.py # Application bootstrap & logging setup
161
+ │ ├── core/ # Core logic & backend
162
+ │ │ ├── config.py # Config persistence, query history, saved queries
163
+ │ │ ├── clipboard.py # Cross-platform system clipboard integration
164
+ │ │ ├── sql_utils.py # SQL formatting, file-to-query mapper, comments
165
+ │ │ └── error_classifier.py # Diagnostic error classifier
166
+ │ └── gui/ # Textual user interface
167
+ │ ├── app.py # Main DuckCLI application
168
+ │ ├── styles.py # Textual CSS stylesheets
169
+ │ ├── screens/ # Modal screens (About, Matrix screensaver)
170
+ │ ├── dialogs/ # Modal dialogs (Save Query, Export, Add DB)
171
+ │ └── widgets/ # Custom widgets (Editor, ResultsTable, Trees, etc.)
172
+ ├── docs/ # Documentation & guides
173
+ ├── scripts/ # Build scripts & packaging specs
174
+ ├── tests/ # Automated test suite
175
+ ├── samples/ # Sample datasets for testing & exploration
176
+ ├── openduck.py # Root executable script
177
+ ├── openduck.sh # Shell launcher script
178
+ └── pyproject.toml # Python package configuration
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 📜 License
184
+
185
+ Distributed under the **MIT License**. See `LICENSE` for details.
186
+
@@ -0,0 +1,42 @@
1
+ """OpenDuck - The Modern Terminal SQL IDE & Data Explorer for DuckDB."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from openduck.core.config import (
6
+ CWD,
7
+ CONFIG_FILE,
8
+ MAX_UI_ROWS,
9
+ DUCKDB_EXTENSIONS,
10
+ add_to_history,
11
+ delete_connection,
12
+ load_config,
13
+ save_config,
14
+ save_connection,
15
+ save_query,
16
+ )
17
+ from openduck.core.error_classifier import classify_file_error
18
+ from openduck.core.sql_utils import is_duckdb_file, sql_for_file, toggle_sql_comment_lines
19
+ from openduck.core.clipboard import copy_to_system_clipboard, get_system_clipboard
20
+ from openduck.gui.app import DuckCLI
21
+ from openduck.main import main
22
+
23
+ __all__ = [
24
+ "DuckCLI",
25
+ "main",
26
+ "load_config",
27
+ "save_config",
28
+ "add_to_history",
29
+ "save_query",
30
+ "save_connection",
31
+ "delete_connection",
32
+ "classify_file_error",
33
+ "is_duckdb_file",
34
+ "sql_for_file",
35
+ "toggle_sql_comment_lines",
36
+ "copy_to_system_clipboard",
37
+ "get_system_clipboard",
38
+ "CWD",
39
+ "CONFIG_FILE",
40
+ "MAX_UI_ROWS",
41
+ "DUCKDB_EXTENSIONS",
42
+ ]
@@ -0,0 +1,6 @@
1
+ """Allow running openduck as a module: python -m openduck."""
2
+
3
+ from openduck.main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,36 @@
1
+ """Core modules for OpenDuck: config, clipboard, SQL utilities, and error classification."""
2
+
3
+ from openduck.core.clipboard import copy_to_system_clipboard, get_system_clipboard
4
+ from openduck.core.config import (
5
+ CONFIG_FILE,
6
+ CWD,
7
+ DUCKDB_EXTENSIONS,
8
+ MAX_UI_ROWS,
9
+ add_to_history,
10
+ delete_connection,
11
+ load_config,
12
+ save_config,
13
+ save_connection,
14
+ save_query,
15
+ )
16
+ from openduck.core.error_classifier import classify_file_error
17
+ from openduck.core.sql_utils import is_duckdb_file, sql_for_file, toggle_sql_comment_lines
18
+
19
+ __all__ = [
20
+ "CWD",
21
+ "CONFIG_FILE",
22
+ "MAX_UI_ROWS",
23
+ "DUCKDB_EXTENSIONS",
24
+ "load_config",
25
+ "save_config",
26
+ "add_to_history",
27
+ "save_query",
28
+ "save_connection",
29
+ "delete_connection",
30
+ "copy_to_system_clipboard",
31
+ "get_system_clipboard",
32
+ "is_duckdb_file",
33
+ "sql_for_file",
34
+ "toggle_sql_comment_lines",
35
+ "classify_file_error",
36
+ ]
@@ -0,0 +1,57 @@
1
+ """Cross-platform system clipboard integration."""
2
+
3
+ import logging
4
+ import subprocess
5
+ import sys
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def copy_to_system_clipboard(text: str) -> bool:
11
+ """Copy text to system clipboard (macOS pbcopy, xclip/wl-copy on Linux, clip on Windows)."""
12
+ if not text:
13
+ return False
14
+ try:
15
+ if sys.platform == "darwin":
16
+ p = subprocess.Popen(["pbcopy"], stdin=subprocess.PIPE, text=True)
17
+ p.communicate(input=text)
18
+ return True
19
+ elif sys.platform.startswith("linux"):
20
+ for cmd in (
21
+ ["wl-copy"],
22
+ ["xclip", "-selection", "clipboard"],
23
+ ["xsel", "--clipboard", "--input"],
24
+ ):
25
+ try:
26
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE, text=True)
27
+ p.communicate(input=text)
28
+ return True
29
+ except FileNotFoundError:
30
+ continue
31
+ elif sys.platform == "win32":
32
+ p = subprocess.Popen(["clip"], stdin=subprocess.PIPE, text=True)
33
+ p.communicate(input=text)
34
+ return True
35
+ except Exception as e:
36
+ logger.error(f"Failed to copy to system clipboard: {e}")
37
+ return False
38
+
39
+
40
+ def get_system_clipboard() -> str:
41
+ """Read text from system clipboard (macOS pbpaste, wl-paste/xclip on Linux)."""
42
+ try:
43
+ if sys.platform == "darwin":
44
+ return subprocess.check_output(["pbpaste"], text=True)
45
+ elif sys.platform.startswith("linux"):
46
+ for cmd in (
47
+ ["wl-paste"],
48
+ ["xclip", "-selection", "clipboard", "-o"],
49
+ ["xsel", "--clipboard", "--output"],
50
+ ):
51
+ try:
52
+ return subprocess.check_output(cmd, text=True)
53
+ except FileNotFoundError:
54
+ continue
55
+ except Exception as e:
56
+ logger.error(f"Failed to read from system clipboard: {e}")
57
+ return ""
@@ -0,0 +1,108 @@
1
+ """Configuration and persistence management for OpenDuck."""
2
+
3
+ from datetime import datetime
4
+ import json
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ CWD = Path(os.getcwd())
12
+ CONFIG_FILE = CWD / "openduck.json"
13
+ MAX_UI_ROWS = 10000
14
+ DUCKDB_EXTENSIONS = {
15
+ ".parquet", ".arrow", ".duckdb", ".csv", ".csv.gz",
16
+ ".json", ".jsonl", ".sqlite", ".sqlite3", ".db", ".xlsx", ".xls"
17
+ }
18
+
19
+
20
+ def load_config() -> dict:
21
+ """Load config from openduck.json, create if doesn't exist or is invalid."""
22
+ if CONFIG_FILE.exists():
23
+ try:
24
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
25
+ config = json.load(f)
26
+ if isinstance(config, dict):
27
+ if "connections" not in config:
28
+ config["connections"] = []
29
+ if "history" not in config:
30
+ config["history"] = []
31
+ if "saved_queries" not in config:
32
+ config["saved_queries"] = []
33
+ return config
34
+ except Exception as e:
35
+ logger.error(f"Failed to read config file, falling back to default: {e}")
36
+
37
+ default_config = {
38
+ "history": [],
39
+ "saved_queries": [],
40
+ "connections": []
41
+ }
42
+ save_config(default_config)
43
+ return default_config
44
+
45
+
46
+ def save_config(config: dict) -> None:
47
+ """Save config to openduck.json."""
48
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
49
+ json.dump(config, f, indent=2, ensure_ascii=False)
50
+
51
+
52
+ def add_to_history(sql: str) -> dict:
53
+ """Add a query to history in the config file, capped at the last 200 queries."""
54
+ config = load_config()
55
+ timestamp = datetime.now().isoformat()
56
+ config["history"].append({
57
+ "sql": sql.strip(),
58
+ "timestamp": timestamp
59
+ })
60
+ # Keep only the last 200 queries to prevent boundless file growth
61
+ config["history"] = config["history"][-200:]
62
+ save_config(config)
63
+ return config
64
+
65
+
66
+ def save_query(name: str, sql: str) -> dict:
67
+ """Save a query with a name to the config file."""
68
+ config = load_config()
69
+ timestamp = datetime.now().isoformat()
70
+ # Check if query with same name already exists and update it
71
+ for i, query in enumerate(config["saved_queries"]):
72
+ if query["name"] == name:
73
+ config["saved_queries"][i] = {
74
+ "name": name,
75
+ "sql": sql.strip(),
76
+ "timestamp": timestamp
77
+ }
78
+ save_config(config)
79
+ return config
80
+ # If not found, add as new query
81
+ config["saved_queries"].append({
82
+ "name": name,
83
+ "sql": sql.strip(),
84
+ "timestamp": timestamp
85
+ })
86
+ save_config(config)
87
+ return config
88
+
89
+
90
+ def save_connection(conn_info: dict) -> dict:
91
+ """Save a database connection to config."""
92
+ config = load_config()
93
+ for i, c in enumerate(config["connections"]):
94
+ if c["id"] == conn_info["id"]:
95
+ config["connections"][i] = conn_info
96
+ save_config(config)
97
+ return config
98
+ config["connections"].append(conn_info)
99
+ save_config(config)
100
+ return config
101
+
102
+
103
+ def delete_connection(conn_id: str) -> dict:
104
+ """Delete a database connection from config."""
105
+ config = load_config()
106
+ config["connections"] = [c for c in config["connections"] if c["id"] != conn_id]
107
+ save_config(config)
108
+ return config