Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .
pip install pytest # For running tests

- name: Run tests
run: |
pytest
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Use an official Python runtime as a parent image
FROM python:3.10-slim

# Set the working directory in the container
WORKDIR /app

# Copy the project files into the container
COPY . .

# Install the project and its dependencies
RUN pip install --upgrade pip && \
pip install typing_extensions && \
pip install .

# Set the entrypoint for the container
ENTRYPOINT ["ddg-cli"]

# Default command to show help (optional)
CMD ["--help"]
50 changes: 0 additions & 50 deletions Makefile

This file was deleted.

64 changes: 36 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,17 @@ A feature-rich command-line interface for DuckDuckGo search that brings the powe
## Quick Start

```bash
# Install dependencies
pip install -r requirements.txt

# Make the script executable
chmod +x ddgs
# Install the package
pip install .

# Search for something
PYTHONPATH=./src python3 ddgs search "python web scraping"
ddg-cli search "python web scraping"

# Get more results
PYTHONPATH=./src python3 ddgs search "machine learning tutorials" --results 20
ddg-cli search "machine learning tutorials" --results 20

# Export to JSON for processing
PYTHONPATH=./src python3 ddgs search "api documentation" --format json --output results.json
ddg-cli search "api documentation" --format json --output results.json
```

## Installation
Expand All @@ -42,26 +39,35 @@ PYTHONPATH=./src python3 ddgs search "api documentation" --format json --output
- Python 3.8 or higher
- pip package manager

### Step-by-Step Installation
### Method 1: Install with pip (Recommended)

1. **Clone the repository**
```bash
git clone https://github.com/GrecAndrei/duckduckgo-cli.git
cd duckduckgo-cli
```

2. **Install dependencies**
2. **Install the package**
```bash
pip install -r requirements.txt
pip install .
```

3. **Set up the tool (optional)**
3. **Start using the CLI**
```bash
chmod +x ddgs
./setup.sh # Creates system-wide symlink
ddg-cli search "your search query"
```

After setup, you can use `ddg-cli` from anywhere in your terminal.
### Method 2: Docker

1. **Build the Docker image**
```bash
docker build -t ddg-cli .
```

2. **Run searches in Docker**
```bash
docker run ddg-cli search "hello world"
```

## Core Features

Expand Down Expand Up @@ -228,8 +234,8 @@ This project is well-organized for easy maintenance and extension:

```
duckduckgo-cli/
├── ddgs # Main executable
├── src/ # Core modules
├── duckduckgo_cli/ # Main package
├── main.py # Main CLI entry point
│ ├── search.py # DuckDuckGo search integration
│ ├── display.py # Result formatting
│ ├── history.py # Search history management
Expand All @@ -239,19 +245,21 @@ duckduckgo-cli/
│ ├── filter.py # Result filtering
│ └── utils.py # Browser and download utilities
├── tests/ # Test suite
└── docs/ # Additional documentation
├── pyproject.toml # Modern Python packaging
├── Dockerfile # Container configuration
└── .github/workflows/ # CI/CD configuration
```

## Testing

Run the comprehensive test suite to ensure everything works correctly:

```bash
# Run all tests
PYTHONPATH=./src python3 tests/test_ddgs.py
# Run all tests with pytest
pytest

# Or use make
make test
# Or run tests with verbose output
pytest -v
```

## Troubleshooting
Expand All @@ -260,8 +268,8 @@ make test

**"Module not found" errors**
```bash
# Ensure PYTHONPATH is set correctly
export PYTHONPATH="./src:$PYTHONPATH"
# Make sure the package is installed
pip install .
```

**Network connectivity issues**
Expand All @@ -271,8 +279,8 @@ export PYTHONPATH="./src:$PYTHONPATH"

**Permission denied on installation**
```bash
# Make scripts executable
chmod +x ddgs setup.sh install.sh
# Install in user mode if needed
pip install --user .
```

### Getting Help
Expand Down Expand Up @@ -325,8 +333,8 @@ done
While primarily a CLI tool, the modular architecture makes it easy to import individual components:

```python
from src.search import search_duckduckgo
from src.filter import filter_results
from duckduckgo_cli.search import search_duckduckgo
from duckduckgo_cli.filter import filter_results

results = search_duckduckgo("python programming", 10)
filtered = filter_results(results, include="github.com")
Expand Down
26 changes: 26 additions & 0 deletions duckduckgo_cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
DuckDuckGo CLI Package
"""

from .search import search_duckduckgo
from .display import display_results, format_results
from .history import HistoryManager
from .bookmarks import BookmarkManager
from .config import ConfigManager
from .export import export_results
from .filter import filter_results
from .utils import open_urls, download_content

__version__ = "1.1.0"
__all__ = [
"search_duckduckgo",
"display_results",
"format_results",
"HistoryManager",
"BookmarkManager",
"ConfigManager",
"export_results",
"filter_results",
"open_urls",
"download_content"
]
55 changes: 55 additions & 0 deletions duckduckgo_cli/bookmarks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Bookmarks module for the enhanced ddgs tool
"""

import json
import os
from pathlib import Path

class BookmarkManager:
def __init__(self, bookmarks_file=None):
if bookmarks_file is None:
self.bookmarks_file = Path.home() / '.ddgs_bookmarks.json'
else:
self.bookmarks_file = Path(bookmarks_file)

# Create bookmarks file if it doesn't exist
if not self.bookmarks_file.exists():
self.bookmarks_file.write_text('[]')

def add_bookmark(self, query):
"""Add a query to bookmarks."""
bookmarks = self.load_bookmarks()
if query not in bookmarks:
bookmarks.append(query)
self.save_bookmarks(bookmarks)
print(f"Bookmark added: {query}")
else:
print(f"Query already bookmarked: {query}")

def load_bookmarks(self):
"""Load bookmarks from file."""
try:
return json.loads(self.bookmarks_file.read_text())
except (json.JSONDecodeError, FileNotFoundError):
return []

def save_bookmarks(self, bookmarks):
"""Save bookmarks to file."""
self.bookmarks_file.write_text(json.dumps(bookmarks, indent=2))

def list_bookmarks(self):
"""List bookmarks."""
bookmarks = self.load_bookmarks()
if not bookmarks:
print("No bookmarks found.")
return

print("Bookmarks:")
for i, query in enumerate(bookmarks, 1):
print(f"{i}. {query}")

def clear_bookmarks(self):
"""Clear bookmarks."""
self.bookmarks_file.write_text('[]')
print("Bookmarks cleared.")
59 changes: 59 additions & 0 deletions duckduckgo_cli/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Configuration module for the enhanced ddgs tool
"""

import configparser
import os
from pathlib import Path

class ConfigManager:
def __init__(self, config_file=None):
if config_file is None:
# Use XDG standard configuration path
self.config_file = Path.home() / ".config" / "duckduckgo-cli" / "config.ini"
# Create directory if it doesn't exist
self.config_file.parent.mkdir(parents=True, exist_ok=True)
else:
self.config_file = Path(config_file)

# Default configuration
self.default_config = {
'general': {
'default_results': '10',
'default_format': 'text',
'safe_search': '0'
},
'display': {
'color_output': '1',
'max_snippet_length': '200'
},
'network': {
'timeout': '10',
'retries': '3'
}
}

def load_config(self):
"""Load configuration from file."""
config = configparser.ConfigParser()

# Set defaults
for section, options in self.default_config.items():
config.add_section(section)
for key, value in options.items():
config.set(section, key, value)

# Load user config if it exists
if self.config_file.exists():
config.read(self.config_file)

return config

def save_config(self, config):
"""Save configuration to file."""
with open(self.config_file, 'w') as f:
config.write(f)

def get_config_path(self):
"""Get the configuration file path."""
return self.config_file
Loading
Loading