<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Meeran's Blog]]></title><description><![CDATA[Full-Stack Developer | FastAPI, Web Scraping &amp; Automation | Building AI Apps, Chatbots &amp; Data Solutions | Open to Freelance Projects]]></description><link>https://emeeran.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 08:48:44 GMT</lastBuildDate><atom:link href="https://emeeran.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Dawnstar ReadAloud — Highlight, Hotkey, Hear]]></title><description><![CDATA[If you prefer listening to text, Dawnstar ReadAloud makes it effortless on Linux. Many TTS options today sound robotic, are paid, or require copying text into a browser. I wanted a faster, more natura]]></description><link>https://emeeran.dev/dawnstar-readaloud-highlight-hotkey-hear</link><guid isPermaLink="true">https://emeeran.dev/dawnstar-readaloud-highlight-hotkey-hear</guid><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Thu, 26 Feb 2026 14:26:35 GMT</pubDate><content:encoded><![CDATA[<p>If you prefer listening to text, Dawnstar ReadAloud makes it effortless on Linux. Many TTS options today sound robotic, are paid, or require copying text into a browser. I wanted a faster, more natural workflow: highlight any text, press a hotkey, and hear neural voices instantly. Here's how I built it</p>
<h2>The Goal</h2>
<p>A simple TTS system that:</p>
<ul>
<li><p>Uses neural voices (not robotic synthesis)</p>
</li>
<li><p>Works with global keyboard shortcuts in any app</p>
</li>
<li><p>Caches audio for instant replay of repeated phrases</p>
</li>
<li><p>Handles files, PDFs, EPUBs, and URLs</p>
</li>
<li><p>Works on both X11 and Wayland</p>
</li>
</ul>
<h2>The Solution</h2>
<p>A Python CLI that leverages Microsoft Edge TTS (Azure neural voices, free) with MD5-based caching and desktop integration.</p>
<h3>How It Works</h3>
<ol>
<li><p><strong>Text Input</strong>: Accepts direct text, files, URLs, or the clipboard via <code>xclip</code> (X11) or <code>wl-paste</code> (Wayland)</p>
</li>
<li><p><strong>Chunking</strong>: Splits text at sentence boundaries (<code>. ! ?</code>) for natural speech rhythm</p>
</li>
<li><p><strong>TTS Engine</strong>: Primary Edge TTS, with gTTS and eSpeak as fallbacks</p>
</li>
<li><p><strong>Caching</strong>: Stores audio in <code>~/.cache/tts_app/</code> keyed by <code>md5(text + lang + speed)</code></p>
</li>
<li><p><strong>Playback</strong>: Auto-detects player (mpg123 → paplay → cvlc → ffplay)</p>
</li>
</ol>
<h2>The Setup</h2>
<pre><code class="language-bash">#!/bin/bash
# Install Dawnstar ReadAloud

# System dependencies
sudo apt install mpg123 xclip poppler-utils

# Clone and install
git clone https://github.com/emeeran/Dawnstar-ReadAloud.git tts &amp;&amp; cd tts
python3 -m venv .venv
./.venv/bin/pip install -e .

# Test it
./tts "Hello! This sounds surprisingly natural."

# Set up global hotkeys
python3 configure.py
</code></pre>
<h2>Setting Up Keyboard Shortcuts</h2>
<p>The <code>configure.py</code> Script auto-detects your desktop and registers hotkeys. For GNOME via <code>gsettings</code>:</p>
<pre><code class="language-bash"># Speak selection: Ctrl+Alt+S
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ name 'TTS Speak'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ command '/home/you/.local/bin/tts-speak'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ binding '&lt;Ctrl&gt;&lt;Alt&gt;s'

# Stop speaking: Ctrl+Alt+Q
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/ name 'TTS Stop'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/ command '/home/you/.local/bin/tts-stop'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/ binding '&lt;Ctrl&gt;&lt;Alt&gt;q'

# Register them
gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "['/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/', '/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/']"
</code></pre>
<p>On X11, the primary selection (highlighted text) is prioritized over clipboard—you don't need to copy, just highlight.</p>
<h2>The Result</h2>
<p>Highlight any text, press <code>Ctrl+Alt+S</code>, and hear it spoken:</p>
<pre><code class="language-bash"># Cache stats after using the hotkey
./tts --cache-stats

Cache Statistics:
  Files: 12
  Size: 4.2 MB / 500 MB
  Location: /home/you/.cache/tts_app/
</code></pre>
<p>First playback takes ~1 second (network request), subsequent plays of the same text are instant.</p>
<h2>Usage Examples</h2>
<pre><code class="language-bash">./tts "Direct text"              # Speak directly
./tts document.txt               # Read a file
./tts book.epub                  # Read EPUB (skips front matter)
./tts https://example.com        # Read a webpage
cat notes.txt | ./tts -          # Read from stdin
./tts -l en-uk -s slow file.txt  # British English, slow speed
</code></pre>
<h2>Requirements</h2>
<ul>
<li><p><strong>Python 3.12+</strong></p>
</li>
<li><p><strong>mpg123</strong> (recommended audio player)</p>
</li>
<li><p><strong>xclip</strong> or <strong>wl-clipboard</strong> (for keyboard shortcuts)</p>
</li>
<li><p><strong>poppler-utils</strong> (PDF support, optional)</p>
</li>
</ul>
<pre><code class="language-bash"># Ubuntu/Debian
sudo apt install mpg123 xclip poppler-utils
</code></pre>
<h2>Why This Matters</h2>
<p>The best TTS system is the one you actually use. By reducing friction to a single hotkey, I find myself listening more—documentation while cooking, proofreading by ear, articles during walks. The neural voices make it pleasant rather than tolerable.</p>
<p>The modular architecture makes it easy to extend: add languages in <code>core/constants.py</code>, swap backends in <code>core/engine.py</code>. Full documentation at <a href="USER_MANUAL.md">USER_MANUAL.md</a>.</p>
<p>Happy listening!</p>
]]></content:encoded></item><item><title><![CDATA[Building a Quick Capture Tool for Obsidian on Linux]]></title><description><![CDATA[If you use Obsidian for note-taking, you know how tedious capturing content from applications on your desktop: copy, switch windows, paste, format. I wanted something faster: highlight text, hit a hot]]></description><link>https://emeeran.dev/building-a-quick-capture-tool-for-obsidian-on-linux</link><guid isPermaLink="true">https://emeeran.dev/building-a-quick-capture-tool-for-obsidian-on-linux</guid><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Sun, 22 Feb 2026 17:17:30 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/65427f6bf75565d4b32820b8/0218f185-a683-410d-bccb-c83b82e07492.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you use Obsidian for note-taking, you know how tedious capturing content from applications on your desktop: copy, switch windows, paste, format. I wanted something faster: highlight text, hit a hotkey, done. Here's how I built it.</p>
<h2>The Goal</h2>
<p>A simple capture system that:</p>
<ul>
<li><p>Grabs any highlighted text instantly</p>
</li>
<li><p>Optionally includes screenshots</p>
</li>
<li><p>Works with Obsidian's Local REST API</p>
</li>
<li><p>Integrates with Linux desktop shortcuts</p>
</li>
</ul>
<h2>The Solution</h2>
<p>A Python script that leverages the <a href="https://github.com/czottmann/obsidian-local-rest-api">Local REST API plugin</a> for Obsidian. The plugin exposes a REST API that lets you read, create, and modify notes programmatically.</p>
<h3>How It Works</h3>
<ol>
<li><p><strong>Text Selection</strong>: Uses <code>xclip</code> (X11) or <code>wl-paste</code> (Wayland) to read the primary selection—text you've highlighted but haven't copied</p>
</li>
<li><p><strong>Screenshots</strong>: Integrates with Flameshot (X11) or grim/slurp (Wayland) for area selection</p>
</li>
<li><p><strong>REST API</strong>: Uploads images and appends content to your vault via HTTP</p>
</li>
<li><p><strong>Notifications</strong>: Desktop feedback via <code>notify-send</code></p>
</li>
</ol>
<h2>The Script</h2>
<pre><code class="language-python">#!/usr/bin/env python3
"""
Obsidian Quick Capture Script

Captures highlighted text (and optionally screenshots) to an Obsidian note
via the Local REST API plugin.

Usage:
    obs_capture.py           # Capture selected text only
    obs_capture.py -s        # Capture text + screenshot
    obs_capture.py -n Note   # Use custom target note
"""

import argparse
import subprocess
import requests
import datetime
import os
import sys
import urllib3

# Suppress insecure HTTPS request warnings (expected for Local REST API self-signed certs)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# --- Configuration ---
API_KEY = "YOUR_API_KEY_HERE"  # Get from Obsidian Local REST API plugin settings
BASE_URL = "https://127.0.0.1:27124"

# Default paths (relative to vault root)
DEFAULT_NOTE = "00-Inbox/Quick Captures.md"
ATTACHMENT_DIR = "Attachments/"

HEADERS = {
    "Authorization": f"Bearer {API_KEY}"
}


def notify(title: str, message: str, urgency: str = "normal"):
    """Send a desktop notification using notify-send."""
    try:
        subprocess.run(
            ["notify-send", "-u", urgency, title, message],
            check=False
        )
    except FileNotFoundError:
        print(f"[{urgency.upper()}] {title}: {message}")


def get_selected_text() -&gt; str:
    """Grab currently highlighted text from primary selection (X11 or Wayland)."""
    # Try xclip first (X11 primary selection)
    try:
        result = subprocess.run(
            ["xclip", "-o", "-selection", "primary"],
            capture_output=True,
            text=True,
            check=True
        )
        return result.stdout.strip()
    except (subprocess.CalledProcessError, FileNotFoundError):
        pass

    # Fallback to wl-paste (Wayland)
    try:
        result = subprocess.run(
            ["wl-paste", "-p"],
            capture_output=True,
            text=True,
            check=True
        )
        return result.stdout.strip()
    except (subprocess.CalledProcessError, FileNotFoundError):
        pass

    return ""


def take_screenshot(filepath: str) -&gt; bool:
    """Allow user to draw a rectangle and capture it (X11 with Flameshot or Wayland with grim/slurp)."""
    # Try Flameshot first (X11)
    try:
        subprocess.run(
            ["flameshot", "gui", "-p", filepath],
            check=True,
            capture_output=True
        )
        if os.path.exists(filepath):
            return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        pass

    # Fallback to grim/slurp (Wayland)
    try:
        subprocess.run(
            f'grim -g "$(slurp)" "{filepath}"',
            shell=True,
            check=True
        )
        return os.path.exists(filepath)
    except subprocess.CalledProcessError:
        return False

    return False


def check_api_connection() -&gt; bool:
    """Verify the Obsidian Local REST API is reachable."""
    try:
        response = requests.get(
            f"{BASE_URL}/",
            headers=HEADERS,
            verify=False,
            timeout=5
        )
        return response.status_code in (200, 404)
    except requests.exceptions.RequestException:
        return False


def ensure_note_exists(note_path: str) -&gt; bool:
    """Create the target note if it doesn't exist."""
    url = f"{BASE_URL}/vault/{note_path}"
    try:
        response = requests.get(url, headers=HEADERS, verify=False)
        if response.status_code == 200:
            return True

        if response.status_code == 404:
            create_response = requests.put(
                url,
                headers={**HEADERS, "Content-Type": "text/markdown"},
                data="# Quick Captures\n".encode("utf-8"),
                verify=False
            )
            return create_response.status_code in (200, 201, 204)
        return False
    except requests.exceptions.RequestException:
        return False


def upload_image(img_path: str, dest_filename: str) -&gt; bool:
    """Upload an image to the Obsidian vault."""
    url = f"{BASE_URL}/vault/{ATTACHMENT_DIR}{dest_filename}"

    try:
        with open(img_path, "rb") as f:
            img_data = f.read()

        response = requests.put(
            url,
            headers={**HEADERS, "Content-Type": "image/png"},
            data=img_data,
            verify=False
        )
        return response.status_code in (200, 201, 204)
    except requests.exceptions.RequestException:
        return False


def append_to_note(note_path: str, content: str) -&gt; bool:
    """Append content to a note in the vault."""
    url = f"{BASE_URL}/vault/{note_path}"

    try:
        response = requests.post(
            url,
            headers={**HEADERS, "Content-Type": "text/markdown"},
            data=content.encode("utf-8"),
            verify=False
        )
        return response.status_code in (200, 201, 204)
    except requests.exceptions.RequestException:
        return False


def parse_args() -&gt; argparse.Namespace:
    """Parse command-line arguments."""
    parser = argparse.ArgumentParser(
        description="Capture highlighted text (and optionally screenshots) to Obsidian.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s              Capture selected text only
  %(prog)s -s           Capture text + screenshot
  %(prog)s -n Inbox.md  Use custom target note
"""
    )
    parser.add_argument(
        "-s", "--screenshot",
        action="store_true",
        help="Also capture a screenshot (text is always captured)"
    )
    parser.add_argument(
        "-n", "--note",
        default=DEFAULT_NOTE,
        help=f"Target note path (default: {DEFAULT_NOTE})"
    )
    return parser.parse_args()


def main():
    args = parse_args()
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    # 1. Check API connection
    if not check_api_connection():
        notify(
            "Obsidian Capture Failed",
            "Cannot connect to Obsidian Local REST API.\nMake sure Obsidian is running with the plugin enabled.",
            "critical"
        )
        sys.exit(1)

    # 2. Get selected text
    text = get_selected_text()
    if not text:
        notify(
            "Obsidian Capture Failed",
            "No text selected. Highlight some text first.",
            "critical"
        )
        sys.exit(1)

    # 3. Ensure target note exists
    if not ensure_note_exists(args.note):
        notify(
            "Obsidian Capture Failed",
            f"Could not create or access note: {args.note}",
            "critical"
        )
        sys.exit(1)

    # 4. Build content to append
    content_to_append = f"\n\n---\n\n**{timestamp}**\n\n"
    content_to_append += f"&gt; {text}\n"

    # 5. Handle screenshot if requested
    img_filename = None
    if args.screenshot:
        img_filename = f"capture_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
        img_filepath = f"/tmp/{img_filename}"

        if take_screenshot(img_filepath):
            if upload_image(img_filepath, img_filename):
                content_to_append += f"\n![[{img_filename}]]\n"
            else:
                notify("Warning", "Screenshot taken but failed to upload to Obsidian.")
            if os.path.exists(img_filepath):
                os.remove(img_filepath)
        else:
            notify("Warning", "Screenshot cancelled or failed. Text will still be captured.")

    # 6. Append to note
    if append_to_note(args.note, content_to_append):
        preview = text[:50] + "..." if len(text) &gt; 50 else text
        msg = f'Captured: "{preview}"'
        if img_filename:
            msg += " + screenshot"
        notify("Obsidian Capture", msg)
    else:
        notify("Obsidian Capture Failed", "Failed to append content to note.", "critical")
        sys.exit(1)


if __name__ == "__main__":
    main()
</code></pre>
<h2>Setting Up Keyboard Shortcuts</h2>
<p>On GNOME, register global hotkeys via <code>gsettings</code>:</p>
<pre><code class="language-bash"># Text capture: Super+Shift+C
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ name 'Obsidian Capture Text'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ command '/home/you/.local/bin/obs_capture.py'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/ binding '&lt;Super&gt;&lt;Shift&gt;c'

# Text + screenshot: Super+Shift+S
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/ name 'Obsidian Capture Screenshot'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/ command '/home/you/.local/bin/obs_capture.py -s'
gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/ binding '&lt;Super&gt;&lt;Shift&gt;s'

# Register them
gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "['/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/', '/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom1/']"
</code></pre>
<h2>The Result</h2>
<p>Highlight any text, press <code>Super+Shift+C</code>, and it appears in your vault:</p>
<pre><code class="language-markdown">---

**2026-02-22 21:34:19**

&gt; Your captured text here
</code></pre>
<p>With <code>-s</code> flag or <code>Super+Shift+S</code>, you get text plus an embedded screenshot.</p>
<h2>Requirements</h2>
<ul>
<li><p><strong>Obsidian</strong> with Local REST API plugin enabled</p>
</li>
<li><p><strong>Python 3</strong> + <code>requests</code> library</p>
</li>
<li><p><strong>X11</strong>: <code>xclip</code>, <code>flameshot</code>, <code>libnotify-bin</code></p>
</li>
<li><p><strong>Wayland</strong>: <code>wl-clipboard</code>, <code>grim</code>, <code>slurp</code></p>
</li>
</ul>
<pre><code class="language-bash"># Ubuntu/Debian (X11)
sudo apt install xclip flameshot libnotify-bin
pip install requests
</code></pre>
<h2>Why This Matters</h2>
<p>The best capture system is the one you actually use. By reducing friction to a single hotkey, I find myself capturing more—interesting quotes, code snippets, error messages—without breaking my workflow. Everything flows into a single inbox note that I process later.</p>
<p>The full documentation is available at <code>~/.local/share/doc/obs_capture.md</code>.</p>
<p>Happy capturing!</p>
]]></content:encoded></item><item><title><![CDATA[Youtube Clipper]]></title><description><![CDATA[https://github.com/emeeran/yt-clipper.git
If you are an Obsidian enthusiast like me and youtube is your one of the source for learning, I have a good news for you. I have developed plugin with chrome extension that transforms YouTube videos content i...]]></description><link>https://emeeran.dev/youtube-clipper</link><guid isPermaLink="true">https://emeeran.dev/youtube-clipper</guid><category><![CDATA[#youtube-clipper]]></category><category><![CDATA[obsidian]]></category><category><![CDATA[clipper]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Mon, 08 Dec 2025 19:26:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765213364228/280906b9-0f5b-4caf-a80c-c3311b723325.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/emeeran/yt-clipper.git"><code>https://github.com/emeeran/yt-clipper.git</code></a></p>
<p>If you are an <strong>Obsidian</strong> enthusiast like me and youtube is your one of the source for learning, I have a good news for you. I have developed plugin with chrome extension that transforms YouTube videos content into four type of structured notes using advanced AI analysis. Whether you're creating educational content, research notes, or learning materials, this plugin automatically extracts key insights, generates summaries, and creates step-by-step tutorials from YouTube videos.</p>
<ul>
<li><p><strong>Executive Summary</strong> - best for quick overview and decision making with 250 words, key insights, main points and recommendations</p>
</li>
<li><p><strong>Comprehensive Summary</strong> - useful for learning and implementation with detailed 8K words and actionable steps</p>
</li>
<li><p><strong>Brief Summary</strong> - Quick summaries with essential points and key takeaways with 100 words</p>
</li>
<li><p><strong>Custom Summary</strong> - based on your custom prompts for your needs</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765221868683/925d92ce-b660-422f-a8cb-20fd90e29ef4.jpeg" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Mastering Claude Code CLI:]]></title><description><![CDATA[Mastering Claude Code CLI: Your Ultimate Command Guide
I'm happy to share my comprehensive compilation of commands for the Claude Code CLI. This guide I hope will help you navigate and utilize the Claude Code CLI's full potential, whether you're auto...]]></description><link>https://emeeran.dev/mastering-claude-code-cli</link><guid isPermaLink="true">https://emeeran.dev/mastering-claude-code-cli</guid><category><![CDATA[claude-code]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Thu, 16 Oct 2025 15:01:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XJXWbfSo2f0/upload/b92055b77cf3f01af3a605d5a4d1d082.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-mastering-claude-code-cli-your-ultimate-command-guide">Mastering Claude Code CLI: Your Ultimate Command Guide</h1>
<p>I'm happy to share my comprehensive compilation of commands for the Claude Code CLI. This guide I hope will help you navigate and utilize the Claude Code CLI's full potential, whether you're automating tasks, managing permissions, or working across multiple directories. With this, you'll be able to handle everything from basic commands to advanced workflows, ensuring you can optimize your use of Claude Code CLI for any project.</p>
<h2 id="heading-key-highlights">Key Highlights:</h2>
<h3 id="heading-most-useful-flags">🎯 Most Useful Flags</h3>
<ul>
<li><p><code>-p, --print</code> - Headless mode (perfect for scripts/CI)</p>
</li>
<li><p><code>-y, --dangerously-skip-permissions</code> - YOLO mode</p>
</li>
<li><p><code>--model</code> - Switch between Sonnet/Opus</p>
</li>
<li><p><code>--add-dir</code> - Work across multiple directories</p>
</li>
<li><p><code>--output-format json</code> - Parse responses programmatically</p>
</li>
<li><p><code>--max-turns N</code> - Control cost and prevent runaway agents</p>
</li>
<li><p><code>--permission-mode plan</code> - Review before execution</p>
</li>
</ul>
<h3 id="heading-power-combos">🔥 Power Combos</h3>
<p><strong>For Automation:</strong></p>
<pre><code class="lang-bash">claude -p --output-format json --max-turns 3 <span class="hljs-string">"task"</span>
</code></pre>
<p><strong>For Safe Refactoring:</strong></p>
<pre><code class="lang-bash">claude --permission-mode plan --allowedTools <span class="hljs-string">"Read"</span> <span class="hljs-string">"Edit"</span> <span class="hljs-string">"task"</span>
</code></pre>
<p><strong>For Complex Multi-repo Tasks:</strong></p>
<pre><code class="lang-bash">claude -y --model opus --add-dir ../backend ../frontend <span class="hljs-string">"task"</span>
</code></pre>
<h3 id="heading-interactive-commands">📋 Interactive Commands</h3>
<ul>
<li><p><code>/help</code>, <code>/undo</code>, <code>/retry</code>, <code>/model</code>, <code>/history</code></p>
</li>
<li><p><code>@filename</code>, <code>@folder/</code>, <code>@url</code>, <code>@git</code> mentions</p>
</li>
</ul>
<h3 id="heading-permission-control">🛡️ Permission Control</h3>
<ul>
<li><p><code>--allowedTools</code> - Whitelist safe operations</p>
</li>
<li><p><code>--disallowedTools</code> - Blacklist dangerous commands</p>
</li>
<li><p><code>--permission-mode</code> - Choose approval level</p>
</li>
</ul>
<p>The artifact above has everything organized with examples for CI/CD, pre-commit hooks, batch processing, and more!</p>
<h2 id="heading-basic-commands">Basic Commands</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Command</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>claude</code></td><td>Start interactive REPL</td><td><code>claude</code></td></tr>
<tr>
<td><code>claude "query"</code></td><td>Start REPL with initial prompt</td><td><code>claude "explain this project"</code></td></tr>
<tr>
<td><code>claude -p "query"</code></td><td>Query via SDK, then exit (headless)</td><td><code>claude -p "explain this function"</code></td></tr>
<tr>
<td>`cat file</td><td>claude -p "query"`</td><td>Process piped content</td><td>`cat logs.txt</td><td>claude -p "explain"`</td></tr>
<tr>
<td><code>claude -c</code></td><td>Continue most recent conversation</td><td><code>claude -c</code></td></tr>
<tr>
<td><code>claude -c -p "query"</code></td><td>Continue via SDK</td><td><code>claude -c -p "Check for type errors"</code></td></tr>
<tr>
<td><code>claude -r "&lt;session-id&gt;"</code></td><td>Resume session by ID</td><td><code>claude -r "abc123" "Finish this PR"</code></td></tr>
<tr>
<td><code>claude update</code></td><td>Update to latest version</td><td><code>claude update</code></td></tr>
<tr>
<td><code>claude mcp</code></td><td>Configure Model Context Protocol servers</td><td><code>claude mcp</code></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-essential-flags">Essential Flags</h2>
<h3 id="heading-automation-amp-output">🚀 Automation &amp; Output</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Flag</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>-p, --print</code></td><td>Headless mode - execute and exit</td><td><code>claude -p "refactor this"</code></td></tr>
<tr>
<td><code>--output-format</code></td><td>Output format: <code>text</code>, <code>json</code>, <code>stream-json</code></td><td><code>claude -p "query" --output-format json</code></td></tr>
<tr>
<td><code>--input-format</code></td><td>Input format: <code>text</code>, <code>stream-json</code></td><td><code>claude -p --input-format stream-json</code></td></tr>
<tr>
<td><code>--include-partial-messages</code></td><td>Include partial messages in streaming output</td><td><code>claude -p --output-format stream-json --include-partial-messages</code></td></tr>
<tr>
<td><code>--verbose</code></td><td>Enable detailed logging</td><td><code>claude --verbose</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-model-amp-behavior">⚙️ Model &amp; Behavior</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Flag</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>--model</code></td><td>Specify model (<code>sonnet</code>, <code>opus</code>, or full name)</td><td><code>claude --model claude-sonnet-4-20250514</code></td></tr>
<tr>
<td><code>--max-turns</code></td><td>Limit conversation turns</td><td><code>claude -p --max-turns 3 "query"</code></td></tr>
<tr>
<td><code>--append-system-prompt</code></td><td>Add custom system instructions</td><td><code>claude --append-system-prompt "Be concise"</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-working-directories">📁 Working Directories</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Flag</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>--add-dir</code></td><td>Add additional working directories</td><td><code>claude --add-dir ../apps ../lib</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-permissions-amp-safety">🔐 Permissions &amp; Safety</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Flag</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>--permission-mode</code></td><td>Permission mode: <code>auto</code>, <code>plan</code>, <code>prompt</code></td><td><code>claude --permission-mode plan</code></td></tr>
<tr>
<td><code>--dangerously-skip-permissions</code></td><td>Skip ALL permission prompts (YOLO mode)</td><td><code>claude --dangerously-skip-permissions</code></td></tr>
<tr>
<td><code>-y</code></td><td>Short alias for skip permissions</td><td><code>claude -y "task"</code></td></tr>
<tr>
<td><code>--permission-prompt-tool</code></td><td>Always prompt for specific tool</td><td><code>claude --permission-prompt-tool mcp_auth_tool</code></td></tr>
<tr>
<td><code>--allowedTools</code></td><td>Whitelist tools to auto-approve</td><td><code>claude --allowedTools "Bash(git *)" "Read"</code></td></tr>
<tr>
<td><code>--disallowedTools</code></td><td>Blacklist tools to always block</td><td><code>claude --disallowedTools "Bash(rm *)" "Edit"</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-session-management">🔄 Session Management</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Flag</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>-c, --continue</code></td><td>Continue most recent conversation</td><td><code>claude -c</code></td></tr>
<tr>
<td><code>-r, --resume</code></td><td>Resume specific session by ID</td><td><code>claude --resume abc123</code></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-interactive-slash-commands">Interactive Slash Commands</h2>
<p>Use these inside the Claude Code REPL:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Command</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>/help</code></td><td>Show help information</td></tr>
<tr>
<td><code>/exit</code></td><td>Exit Claude Code</td></tr>
<tr>
<td><code>/clear</code></td><td>Clear conversation history</td></tr>
<tr>
<td><code>/reset</code></td><td>Reset to new conversation</td></tr>
<tr>
<td><code>/model</code></td><td>Switch model</td></tr>
<tr>
<td><code>/undo</code></td><td>Undo last action</td></tr>
<tr>
<td><code>/retry</code></td><td>Retry last message</td></tr>
<tr>
<td><code>/logout</code></td><td>Logout from Claude</td></tr>
<tr>
<td><code>/copy</code></td><td>Copy last response</td></tr>
<tr>
<td><code>/history</code></td><td>Show conversation history</td></tr>
<tr>
<td><code>/search</code></td><td>Search conversation</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-mentions">@ Mentions</h2>
<p>Reference context in your prompts:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Mention</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>@filename</code></td><td>Reference specific file</td><td><code>@</code><a target="_blank" href="http://app.py"><code>app.py</code></a> <code>explain this</code></td></tr>
<tr>
<td><code>@folder/</code></td><td>Reference entire folder</td><td><code>@src/ refactor all files</code></td></tr>
<tr>
<td><code>@url</code></td><td>Reference URL content</td><td><code>@</code><a target="_blank" href="https://example.com"><code>https://example.com</code></a> <code>summarize</code></td></tr>
<tr>
<td><code>@git</code></td><td>Reference git context</td><td><code>@git review recent commits</code></td></tr>
<tr>
<td><code>@search</code></td><td>Search codebase</td><td><code>@search handleAuth fix the bug</code></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-practical-examples">Practical Examples</h2>
<h3 id="heading-1-headless-mode-cicd-scripts">1. <strong>Headless Mode (CI/CD, Scripts)</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Simple one-shot</span>
claude -p <span class="hljs-string">"run tests and fix failures"</span>

<span class="hljs-comment"># With JSON output for parsing</span>
claude -p <span class="hljs-string">"analyze code"</span> --output-format json

<span class="hljs-comment"># Streaming JSON</span>
claude -p <span class="hljs-string">"refactor"</span> --output-format stream-json
</code></pre>
<h3 id="heading-2-yolo-mode-auto-approve-everything">2. <strong>YOLO Mode (Auto-approve everything)</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Skip all confirmations</span>
claude -y <span class="hljs-string">"migrate from Jest to Vitest"</span>

<span class="hljs-comment"># Or full flag</span>
claude --dangerously-skip-permissions <span class="hljs-string">"rebuild the API"</span>
</code></pre>
<h3 id="heading-3-custom-models">3. <strong>Custom Models</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Use Opus</span>
claude --model opus <span class="hljs-string">"complex refactoring task"</span>

<span class="hljs-comment"># Use specific version</span>
claude --model claude-sonnet-4.5-20250929 <span class="hljs-string">"task"</span>
</code></pre>
<h3 id="heading-4-multi-directory-projects">4. <strong>Multi-directory Projects</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Work across multiple dirs</span>
claude --add-dir ../backend ../frontend <span class="hljs-string">"sync API types"</span>
</code></pre>
<h3 id="heading-5-permission-control">5. <strong>Permission Control</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Auto-approve safe operations</span>
claude --allowedTools <span class="hljs-string">"Read"</span> <span class="hljs-string">"Bash(git log:*)"</span> <span class="hljs-string">"task"</span>

<span class="hljs-comment"># Block dangerous operations</span>
claude --disallowedTools <span class="hljs-string">"Bash(rm *)"</span> <span class="hljs-string">"Bash(sudo *)"</span> <span class="hljs-string">"task"</span>

<span class="hljs-comment"># Plan mode (review before executing)</span>
claude --permission-mode plan <span class="hljs-string">"refactor database"</span>
</code></pre>
<h3 id="heading-6-session-management">6. <strong>Session Management</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Continue last conversation</span>
claude -c

<span class="hljs-comment"># Continue with new prompt</span>
claude -c -p <span class="hljs-string">"now add error handling"</span>

<span class="hljs-comment"># Resume specific session</span>
claude -r <span class="hljs-string">"abc123"</span> <span class="hljs-string">"continue the migration"</span>
</code></pre>
<h3 id="heading-7-piped-input">7. <strong>Piped Input</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Analyze logs</span>
cat error.log | claude -p <span class="hljs-string">"find the root cause"</span>

<span class="hljs-comment"># Process command output</span>
git diff | claude -p <span class="hljs-string">"write commit message"</span>

<span class="hljs-comment"># Multiple files</span>
cat *.py | claude -p <span class="hljs-string">"find security issues"</span>
</code></pre>
<h3 id="heading-8-custom-instructions">8. <strong>Custom Instructions</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Add system-level instructions</span>
claude --append-system-prompt <span class="hljs-string">"Use TypeScript strict mode"</span> <span class="hljs-string">"build API"</span>

<span class="hljs-comment"># Combine with other flags</span>
claude -y --append-system-prompt <span class="hljs-string">"Write tests for everything"</span> <span class="hljs-string">"task"</span>
</code></pre>
<h3 id="heading-9-limit-turns-cost-control">9. <strong>Limit Turns (Cost Control)</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Max 3 back-and-forth turns</span>
claude -p --max-turns 3 <span class="hljs-string">"fix the bug"</span>

<span class="hljs-comment"># Prevent runaway agents</span>
claude -y --max-turns 5 <span class="hljs-string">"complex task"</span>
</code></pre>
<h3 id="heading-10-verbose-debugging">10. <strong>Verbose Debugging</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># See detailed logs</span>
claude --verbose <span class="hljs-string">"task"</span>

<span class="hljs-comment"># Combine with headless</span>
claude -p --verbose <span class="hljs-string">"task"</span> 2&gt; debug.log
</code></pre>
<hr />
<h2 id="heading-advanced-workflows">Advanced Workflows</h2>
<h3 id="heading-pre-commit-hook">Pre-commit Hook</h3>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-comment"># .git/hooks/pre-commit</span>

changed_files=$(git diff --cached --name-only)
<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$changed_files</span>"</span> | claude -p --max-turns 1 \
  <span class="hljs-string">"Review these changes for issues"</span> --output-format json
</code></pre>
<h3 id="heading-cicd-integration">CI/CD Integration</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># In GitHub Actions</span>
- name: Code Review
  run: |
    git diff main..HEAD | claude -p \
      --output-format json \
      <span class="hljs-string">"Review changes and suggest improvements"</span>
</code></pre>
<h3 id="heading-automated-refactoring">Automated Refactoring</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Safe refactoring with plan mode</span>
claude --permission-mode plan \
  --allowedTools <span class="hljs-string">"Read"</span> <span class="hljs-string">"Edit"</span> \
  <span class="hljs-string">"refactor all components to use hooks"</span>
</code></pre>
<h3 id="heading-batch-processing">Batch Processing</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Process multiple files</span>
<span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> src/*.ts; <span class="hljs-keyword">do</span>
  claude -p <span class="hljs-string">"add JSDoc to <span class="hljs-variable">$file</span>"</span> --max-turns 1
<span class="hljs-keyword">done</span>
</code></pre>
<hr />
<h2 id="heading-configuration-tips">Configuration Tips</h2>
<h3 id="heading-environment-variables">Environment Variables</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Set default model</span>
<span class="hljs-built_in">export</span> CLAUDE_CODE_MODEL=<span class="hljs-string">"claude-sonnet-4.5-20250929"</span>

<span class="hljs-comment"># Set API key</span>
<span class="hljs-built_in">export</span> ANTHROPIC_API_KEY=<span class="hljs-string">"your-key"</span>

<span class="hljs-comment"># Set base URL (for proxies)</span>
<span class="hljs-built_in">export</span> ANTHROPIC_BASE_URL=<span class="hljs-string">"http://localhost:8000"</span>
</code></pre>
<h3 id="heading-create-aliases">Create Aliases</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Add to ~/.bashrc or ~/.zshrc</span>
<span class="hljs-built_in">alias</span> cc=<span class="hljs-string">"claude"</span>
<span class="hljs-built_in">alias</span> ccp=<span class="hljs-string">"claude -p"</span>
<span class="hljs-built_in">alias</span> ccy=<span class="hljs-string">"claude -y"</span>
<span class="hljs-built_in">alias</span> ccplan=<span class="hljs-string">"claude --permission-mode plan"</span>
<span class="hljs-built_in">alias</span> ccverbose=<span class="hljs-string">"claude --verbose"</span>
</code></pre>
<hr />
<h2 id="heading-permission-modes-explained">Permission Modes Explained</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Mode</td><td>Description</td><td>Use Case</td></tr>
</thead>
<tbody>
<tr>
<td><code>auto</code></td><td>Auto-approve safe operations</td><td>Default, balanced</td></tr>
<tr>
<td><code>plan</code></td><td>Show plan before executing</td><td>Review changes first</td></tr>
<tr>
<td><code>prompt</code></td><td>Prompt for every action</td><td>Maximum control</td></tr>
<tr>
<td><code>--dangerously-skip-permissions</code></td><td>Skip all prompts</td><td>Automation, trusted tasks</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-tool-patterns-for-allowedtoolsdisallowedtools">Tool Patterns for allowedTools/disallowedTools</h2>
<pre><code class="lang-bash"><span class="hljs-comment"># Git operations</span>
<span class="hljs-string">"Bash(git log:*)"</span> <span class="hljs-string">"Bash(git diff:*)"</span> <span class="hljs-string">"Bash(git status:*)"</span>

<span class="hljs-comment"># File operations</span>
<span class="hljs-string">"Read"</span> <span class="hljs-string">"Edit"</span> <span class="hljs-string">"Write"</span>

<span class="hljs-comment"># Safe commands</span>
<span class="hljs-string">"Bash(ls:*)"</span> <span class="hljs-string">"Bash(cat:*)"</span> <span class="hljs-string">"Bash(grep:*)"</span>

<span class="hljs-comment"># Package managers</span>
<span class="hljs-string">"Bash(npm *)"</span> <span class="hljs-string">"Bash(pip *)"</span> <span class="hljs-string">"Bash(cargo *)"</span>

<span class="hljs-comment"># Dangerous (for disallowedTools)</span>
<span class="hljs-string">"Bash(rm *)"</span> <span class="hljs-string">"Bash(sudo *)"</span> <span class="hljs-string">"Bash(chmod *)"</span>
</code></pre>
<hr />
<h2 id="heading-cost-optimization">Cost Optimization</h2>
<pre><code class="lang-bash"><span class="hljs-comment"># Limit turns to control cost</span>
claude -p --max-turns 2 <span class="hljs-string">"task"</span>

<span class="hljs-comment"># Use Sonnet for most tasks (cheaper than Opus)</span>
claude --model sonnet <span class="hljs-string">"task"</span>

<span class="hljs-comment"># Headless mode for automation (no interactive overhead)</span>
claude -p <span class="hljs-string">"task"</span>
</code></pre>
<hr />
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<h3 id="heading-check-version">Check Version</h3>
<pre><code class="lang-bash">claude --version
</code></pre>
<h3 id="heading-view-logs">View Logs</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Enable verbose logging</span>
claude --verbose <span class="hljs-string">"task"</span> 2&gt;&amp;1 | tee debug.log
</code></pre>
<h3 id="heading-test-connection">Test Connection</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Simple test</span>
claude -p <span class="hljs-string">"echo hello"</span>
</code></pre>
<h3 id="heading-clear-cache">Clear Cache</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Remove session data</span>
rm -rf ~/.claude-code/sessions/
</code></pre>
<hr />
<h2 id="heading-quick-reference-card">Quick Reference Card</h2>
<p><strong>Most Used Flags:</strong></p>
<pre><code class="lang-bash">-p              <span class="hljs-comment"># Headless mode</span>
-y              <span class="hljs-comment"># Skip confirmations</span>
-c              <span class="hljs-comment"># Continue conversation</span>
--model opus    <span class="hljs-comment"># Use Opus model</span>
--add-dir       <span class="hljs-comment"># Add directories</span>
--verbose       <span class="hljs-comment"># Debug logging</span>
--max-turns N   <span class="hljs-comment"># Limit turns</span>
</code></pre>
<p><strong>Permission Control:</strong></p>
<pre><code class="lang-bash">--permission-mode plan          <span class="hljs-comment"># Review first</span>
--allowedTools <span class="hljs-string">"Read"</span> <span class="hljs-string">"Edit"</span>    <span class="hljs-comment"># Whitelist</span>
--disallowedTools <span class="hljs-string">"Bash(rm *)"</span>  <span class="hljs-comment"># Blacklist</span>
</code></pre>
<p><strong>Output Formats:</strong></p>
<pre><code class="lang-bash">--output-format text         <span class="hljs-comment"># Human readable</span>
--output-format json         <span class="hljs-comment"># Structured</span>
--output-format stream-json  <span class="hljs-comment"># Streaming</span>
</code></pre>
<hr />
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p>Official Docs: <a target="_blank" href="https://docs.claude.com/en/docs/claude-code">https://docs.claude.com/en/docs/claude-code</a></p>
</li>
<li><p>Best Practices: <a target="_blank" href="https://www.anthropic.com/engineering/claude-code-best-practices">https://www.anthropic.com/engineering/claude-code-best-practices</a></p>
</li>
<li><p>GitHub: <a target="_blank" href="https://github.com/anthropics/claude-code">https://github.com/anthropics/claude-code</a></p>
</li>
</ul>
<hr />
<p><strong>Pro Tip:</strong> Combine flags for powerful workflows:</p>
<pre><code class="lang-bash">claude -y --model opus --add-dir ../shared --max-turns 5 \
  --allowedTools <span class="hljs-string">"Read"</span> <span class="hljs-string">"Edit"</span> <span class="hljs-string">"Bash(git *)"</span> \
  <span class="hljs-string">"migrate entire codebase to TypeScript"</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Create Your Own Text Expander with Python]]></title><description><![CDATA[I have been using text expansion as a powerful productivity tool to save countless keystrokes and hours of typing time. I was used to using a-Tex and AutoHotKey on Windows, but when I switched to Linux, I searched for an alternative. I tried AutoKey ...]]></description><link>https://emeeran.dev/create-your-own-text-expander-with-python</link><guid isPermaLink="true">https://emeeran.dev/create-your-own-text-expander-with-python</guid><category><![CDATA[Text_expander]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Mon, 26 Aug 2024 19:16:55 GMT</pubDate><content:encoded><![CDATA[<p>I have been using text expansion as a powerful productivity tool to save countless keystrokes and hours of typing time. I was used to using a-Tex and AutoHotKey on Windows, but when I switched to Linux, I searched for an alternative. I tried AutoKey and Espanso but couldn't find a suitable option. Finally, I decided to create my own text expander.</p>
<p>In this post, I will walk you through the creation of Auto Text Expander for your Ubuntu 2024.04 LTS machine.</p>
<h2 id="heading-what-is-a-text-expander">What is a Text Expander?</h2>
<p>A text expander is a tool that automatically replaces shortcuts (triggers) with longer pieces of text (expansions). For example, by typing ";; email," the tool can automatically expand it to your full email address. This tool can be incredibly useful for frequently typed phrases, addresses, code snippets, or any text you find yourself repeating often.</p>
<h2 id="heading-how-our-text-expander-works">How Our Text Expander Works</h2>
<p>Our custom text expander uses Python to:</p>
<ol>
<li><p>Listen for keyboard input</p>
</li>
<li><p>Match typed text against predefined triggers</p>
</li>
<li><p>Replace triggers with their corresponding expansions</p>
</li>
</ol>
<p>The script runs in the background as a system service, ensuring it's always available when you need it.</p>
<h2 id="heading-setting-up-the-text-expander">Setting Up the Text Expander</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li><p>Python 3</p>
</li>
<li><p><code>pynput</code> library (install with <code>pip install pynput</code>)</p>
</li>
</ul>
<h3 id="heading-step-1-create-the-python-script">Step 1: Create the Python Script</h3>
<p>Create a new file named <code>text_</code><a target="_blank" href="http://expander.py"><code>expander.py</code></a> in your project directory (e.g., <code>~/dev/AutoExpander/text_</code><a target="_blank" href="http://expander.py"><code>expander.py</code></a>). Paste the following code:</p>
<pre><code class="lang-python"><span class="hljs-comment">### Step 1:</span>

<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">import</span> threading
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> pynput <span class="hljs-keyword">import</span> keyboard

<span class="hljs-comment"># Set up logging</span>
logging.basicConfig(level=logging.INFO, format=<span class="hljs-string">'%(asctime)s - %(levelname)s - %(message)s'</span>)

<span class="hljs-comment"># Buffer to store typed characters</span>
buffer = <span class="hljs-string">""</span>
<span class="hljs-comment"># Create a keyboard controller instance globally</span>
controller = keyboard.Controller()

<span class="hljs-comment"># Default expansions</span>
DEFAULT_EXPANSIONS = {
    <span class="hljs-string">";;email"</span>: <span class="hljs-string">"meeran@duck.com"</span>,
    }

TEXT_EXPANSION_CONFIG_FILE = <span class="hljs-string">"~/dotfiles/local/text-expander-config.json"</span>

<span class="hljs-comment"># Load or create expansions JSON file</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">load_or_create_expansions</span>():</span>
    json_path = os.path.expanduser(TEXT_EXPANSION_CONFIG_FILE)
    directory = os.path.dirname(json_path)

    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># Create directory if it doesn't exist</span>
        os.makedirs(directory, exist_ok=<span class="hljs-literal">True</span>)

        <span class="hljs-comment"># Try to load existing file</span>
        <span class="hljs-keyword">if</span> os.path.exists(json_path):
            <span class="hljs-keyword">with</span> open(json_path, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> file:
                <span class="hljs-keyword">return</span> json.load(file)
        <span class="hljs-keyword">else</span>:
            <span class="hljs-comment"># Create new file with default expansions</span>
            <span class="hljs-keyword">with</span> open(json_path, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> file:
                json.dump(DEFAULT_EXPANSIONS, file, indent=<span class="hljs-number">4</span>)
            logging.info(<span class="hljs-string">f"Created new expansions file at <span class="hljs-subst">{json_path}</span>"</span>)
            <span class="hljs-keyword">return</span> DEFAULT_EXPANSIONS
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        logging.error(<span class="hljs-string">f"Error loading or creating expansions: <span class="hljs-subst">{str(e)}</span>"</span>)
        <span class="hljs-keyword">return</span> {}

<span class="hljs-comment"># Load expansions</span>
expansions = load_or_create_expansions()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">type_string</span>(<span class="hljs-params">string</span>):</span>
    <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> string.split(<span class="hljs-string">'\n'</span>):
        <span class="hljs-keyword">for</span> char <span class="hljs-keyword">in</span> line:
            <span class="hljs-keyword">if</span> char == <span class="hljs-string">'@'</span>:
                controller.press(keyboard.Key.shift)
                controller.press(<span class="hljs-string">'2'</span>)
                controller.release(<span class="hljs-string">'2'</span>)
                controller.release(keyboard.Key.shift)
            <span class="hljs-keyword">else</span>:
                controller.press(char)
                controller.release(char)
            time.sleep(<span class="hljs-number">0.01</span>)  <span class="hljs-comment"># Small delay between keystrokes</span>
        controller.press(keyboard.Key.enter)
        controller.release(keyboard.Key.enter)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_press</span>(<span class="hljs-params">key</span>):</span>
    <span class="hljs-keyword">global</span> buffer
    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># Check if the key has a character value</span>
        char = key.char
        buffer += char
        <span class="hljs-comment"># Check if the buffer ends with any of the triggers</span>
        <span class="hljs-keyword">for</span> trigger, expansion <span class="hljs-keyword">in</span> expansions.items():
            <span class="hljs-keyword">if</span> buffer.endswith(trigger):
                <span class="hljs-comment"># Delete the trigger characters using backspace</span>
                <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(len(trigger)):
                    controller.press(keyboard.Key.backspace)
                    controller.release(keyboard.Key.backspace)
                <span class="hljs-comment"># Type the expansion</span>
                type_string(expansion)
                <span class="hljs-comment"># Clear the buffer</span>
                buffer = <span class="hljs-string">""</span>
                <span class="hljs-keyword">break</span>
        <span class="hljs-comment"># Keep buffer size reasonable (last 20 characters)</span>
        buffer = buffer[<span class="hljs-number">-20</span>:]
    <span class="hljs-keyword">except</span> AttributeError:
        <span class="hljs-comment"># Handle non-character keys (shift, ctrl, etc.) without affecting buffer</span>
        <span class="hljs-keyword">pass</span>
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        logging.error(<span class="hljs-string">f"Error in on_press: <span class="hljs-subst">{str(e)}</span>"</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_listener</span>():</span>
    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># Start listening for keypresses</span>
        <span class="hljs-keyword">with</span> keyboard.Listener(on_press=on_press) <span class="hljs-keyword">as</span> listener:
            logging.info(<span class="hljs-string">"Keyboard listener started"</span>)
            listener.join()
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        logging.error(<span class="hljs-string">f"Error in run_listener: <span class="hljs-subst">{str(e)}</span>"</span>)

<span class="hljs-comment"># Running the listener in a background thread</span>
listener_thread = threading.Thread(target=run_listener, daemon=<span class="hljs-literal">True</span>)
listener_thread.start()

<span class="hljs-comment"># Main thread can perform other tasks or just sleep</span>
<span class="hljs-keyword">try</span>:
    logging.info(<span class="hljs-string">"Auto Expander script started"</span>)
    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        time.sleep(<span class="hljs-number">1</span>)  <span class="hljs-comment"># Keep the main thread alive</span>
<span class="hljs-keyword">except</span> KeyboardInterrupt:
    logging.info(<span class="hljs-string">"Script interrupted by user."</span>)
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
    logging.error(<span class="hljs-string">f"Unexpected error in main thread: <span class="hljs-subst">{str(e)}</span>"</span>)
</code></pre>
<pre><code class="lang-python">
<span class="hljs-comment">### Step 2: Create a System Service</span>

To run the text expander <span class="hljs-keyword">as</span> a system service, create a new service file:

```bash
sudo nano /etc/systemd/system/text-expander.service
</code></pre>
<p>Add the following content (adjust paths as necessary):</p>
<pre><code class="lang-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=Text Expander Python Script
<span class="hljs-attr">After</span>=network.target

<span class="hljs-section">[Service]</span>
<span class="hljs-attr">ExecStart</span>=/usr/bin/python3 /home/your_username/dev/AutoExpander/text_expander.py
<span class="hljs-attr">WorkingDirectory</span>=/home/your_username/dev/AutoExpander
<span class="hljs-attr">StandardOutput</span>=journal
<span class="hljs-attr">StandardError</span>=journal
<span class="hljs-attr">Restart</span>=<span class="hljs-literal">on</span>-failure
<span class="hljs-attr">RestartSec</span>=<span class="hljs-number">5</span>
<span class="hljs-attr">User</span>=your_username
<span class="hljs-attr">Environment</span>=DISPLAY=:<span class="hljs-number">0</span>
<span class="hljs-attr">Environment</span>=XAUTHORITY=/home/your_username/.Xauthority

<span class="hljs-section">[Install]</span>
<span class="hljs-attr">WantedBy</span>=default.target
</code></pre>
<p>Replace <code>your_username</code> with your actual username.</p>
<h3 id="heading-step-3-enable-and-start-the-service">Step 3: Enable and Start the Service</h3>
<p>Run these commands to enable and start the service:</p>
<pre><code class="lang-bash">sudo systemctl daemon-reload
sudo systemctl <span class="hljs-built_in">enable</span> text-expander
sudo systemctl start text-expander
</code></pre>
<h2 id="heading-configuring-your-text-expansions">Configuring Your Text Expansions</h2>
<p>The text expander uses a JSON file to store your expansions. By default, it's located at <code>~/dotfiles/local/text-expansion.json</code>. If the file doesn't exist, the script will create it with some default expansions.</p>
<p>To add or modify expansions, edit this JSON file:</p>
<pre><code class="lang-bash">nano ~/dev/text-expansion.json
</code></pre>
<p>The file structure is simple:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">";;email"</span>: <span class="hljs-string">"your.email@example.com"</span>,
    <span class="hljs-attr">";;phone"</span>: <span class="hljs-string">"123-456-7890"</span>,
    <span class="hljs-attr">";;addr"</span>: <span class="hljs-string">"123 Main St, City, State, ZIP"</span>
}
</code></pre>
<p>Add new triggers and expansions as needed. You can use any character as prefix instead of ";;"</p>
<h2 id="heading-making-changes">Making Changes</h2>
<ol>
<li><p><strong>Modify Expansions</strong>: Edit the <code>text-expansion.json</code> file and save your changes. Restart the service for changes to take effect:</p>
<pre><code class="lang-bash"> sudo systemctl restart text-expander
</code></pre>
</li>
<li><p><strong>Update the Script</strong>: If you make changes to the Python script, save the file and restart the service:</p>
<pre><code class="lang-bash"> sudo systemctl restart text-expander
</code></pre>
</li>
<li><p><strong>Check Status</strong>: To check if the service is running correctly:</p>
<pre><code class="lang-bash"> sudo systemctl status text-expander
</code></pre>
</li>
<li><p><strong>View Logs</strong>: To view the logs for troubleshooting:</p>
<pre><code class="lang-bash"> sudo journalctl -u text-expander -n 50 --no-pager
</code></pre>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>"With this custom text expander, you have a powerful and flexible tool to boost your productivity. It is easily customizable and runs smoothly as a system service. Remember, with great power comes great responsibility."</p>
]]></content:encoded></item><item><title><![CDATA[Build your own AI chatbot with Python.]]></title><description><![CDATA[Today, I will explain how I built a chat app using Python and Streamlit that runs on the OpenAI API. It is a fun and rewarding project that allows you to customize your chatbot to fit your needs.
To start building the chat app, you need to obtain you...]]></description><link>https://emeeran.dev/build-your-own-ai-chatbot-with-python</link><guid isPermaLink="true">https://emeeran.dev/build-your-own-ai-chatbot-with-python</guid><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Sat, 17 Aug 2024 03:56:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/PEJtZfT6C1Q/upload/fbb04d2cd0951fd9f24e5ebaa13f94b6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today, I will explain how I built a chat app using Python and Streamlit that runs on the OpenAI API. It is a fun and rewarding project that allows you to customize your chatbot to fit your needs.</p>
<p>To start building the chat app, you need to obtain your OpenAI API key. The API key authenticates your requests to the OpenAI API. Not having the API key will prevent you from accessing the OpenAI API. You won't be able to utilize its features. After creating an account on the OpenAI website, you can obtain the API key from there.</p>
<p>Step 1: Sign up for an account on the OpenAI website to access your API key.</p>
<p>Step 2: Create your project directory. Its structure should look like this:</p>
<pre><code class="lang-bash">project
├── .env
├── .gitignore
├── app.py
├── requirements.txt
└── README.md
</code></pre>
<p>Step 3: copy and paste to the requirements.txt. Run in terminal <code>pip install -r requirements.txt</code></p>
<pre><code class="lang-python">aiohappyeyeballs==<span class="hljs-number">2.3</span><span class="hljs-number">.5</span>
aiohttp==<span class="hljs-number">3.10</span><span class="hljs-number">.3</span>
aiosignal==<span class="hljs-number">1.3</span><span class="hljs-number">.1</span>
altair==<span class="hljs-number">5.4</span><span class="hljs-number">.0</span>
annotated-types==<span class="hljs-number">0.7</span><span class="hljs-number">.0</span>
anyio==<span class="hljs-number">4.4</span><span class="hljs-number">.0</span>
attrs==<span class="hljs-number">24.2</span><span class="hljs-number">.0</span>
blinker==<span class="hljs-number">1.8</span><span class="hljs-number">.2</span>
cachetools==<span class="hljs-number">5.4</span><span class="hljs-number">.0</span>
certifi==<span class="hljs-number">2024.7</span><span class="hljs-number">.4</span>
charset-normalizer==<span class="hljs-number">3.3</span><span class="hljs-number">.2</span>
click==<span class="hljs-number">8.1</span><span class="hljs-number">.7</span>
distro==<span class="hljs-number">1.9</span><span class="hljs-number">.0</span>
frozenlist==<span class="hljs-number">1.4</span><span class="hljs-number">.1</span>
gitdb==<span class="hljs-number">4.0</span><span class="hljs-number">.11</span>
GitPython==<span class="hljs-number">3.1</span><span class="hljs-number">.43</span>
h11==<span class="hljs-number">0.14</span><span class="hljs-number">.0</span>
httpcore==<span class="hljs-number">1.0</span><span class="hljs-number">.5</span>
httpx==<span class="hljs-number">0.27</span><span class="hljs-number">.0</span>
idna==<span class="hljs-number">3.7</span>
Jinja2==<span class="hljs-number">3.1</span><span class="hljs-number">.4</span>
jiter==<span class="hljs-number">0.5</span><span class="hljs-number">.0</span>
jsonschema==<span class="hljs-number">4.23</span><span class="hljs-number">.0</span>
jsonschema-specifications==<span class="hljs-number">2023.12</span><span class="hljs-number">.1</span>
markdown-it-py==<span class="hljs-number">3.0</span><span class="hljs-number">.0</span>
MarkupSafe==<span class="hljs-number">2.1</span><span class="hljs-number">.5</span>
mdurl==<span class="hljs-number">0.1</span><span class="hljs-number">.2</span>
multidict==<span class="hljs-number">6.0</span><span class="hljs-number">.5</span>
narwhals==<span class="hljs-number">1.3</span><span class="hljs-number">.0</span>
numpy==<span class="hljs-number">2.0</span><span class="hljs-number">.1</span>
openai==<span class="hljs-number">1.40</span><span class="hljs-number">.3</span>
packaging==<span class="hljs-number">24.1</span>
pandas==<span class="hljs-number">2.2</span><span class="hljs-number">.2</span>
pillow==<span class="hljs-number">10.4</span><span class="hljs-number">.0</span>
protobuf==<span class="hljs-number">5.27</span><span class="hljs-number">.3</span>
pyarrow==<span class="hljs-number">17.0</span><span class="hljs-number">.0</span>
pydantic==<span class="hljs-number">2.8</span><span class="hljs-number">.2</span>
pydantic_core==<span class="hljs-number">2.20</span><span class="hljs-number">.1</span>
pydeck==<span class="hljs-number">0.9</span><span class="hljs-number">.1</span>
Pygments==<span class="hljs-number">2.18</span><span class="hljs-number">.0</span>
python-dateutil==<span class="hljs-number">2.9</span><span class="hljs-number">.0</span>.post0
python-dotenv==<span class="hljs-number">1.0</span><span class="hljs-number">.1</span>
pytz==<span class="hljs-number">2024.1</span>
referencing==<span class="hljs-number">0.35</span><span class="hljs-number">.1</span>
requests==<span class="hljs-number">2.32</span><span class="hljs-number">.3</span>
rich==<span class="hljs-number">13.7</span><span class="hljs-number">.1</span>
rpds-py==<span class="hljs-number">0.20</span><span class="hljs-number">.0</span>
six==<span class="hljs-number">1.16</span><span class="hljs-number">.0</span>
smmap==<span class="hljs-number">5.0</span><span class="hljs-number">.1</span>
sniffio==<span class="hljs-number">1.3</span><span class="hljs-number">.1</span>
streamlit==<span class="hljs-number">1.37</span><span class="hljs-number">.1</span>
tenacity==<span class="hljs-number">8.5</span><span class="hljs-number">.0</span>
toml==<span class="hljs-number">0.10</span><span class="hljs-number">.2</span>
tornado==<span class="hljs-number">6.4</span><span class="hljs-number">.1</span>
tqdm==<span class="hljs-number">4.66</span><span class="hljs-number">.5</span>
typing_extensions==<span class="hljs-number">4.12</span><span class="hljs-number">.2</span>
tzdata==<span class="hljs-number">2024.1</span>
urllib3==<span class="hljs-number">2.2</span><span class="hljs-number">.2</span>
watchdog==<span class="hljs-number">4.0</span><span class="hljs-number">.2</span>
yarl==<span class="hljs-number">1.9</span><span class="hljs-number">.4</span>
</code></pre>
<p>Step 4: Open the <a target="_blank" href="http://app.py">app.py</a> file in a text editor like VS Code and start to write or copy the code below into it.</p>
<ul>
<li><p>Model Options: Place the models you want to use. At present, GPT-4o-mini is efficient and cheap.</p>
</li>
<li><p>Persona: This is a system message that will directly affect the way our chat app responds to our prompt. So, it should be carefully crafted.</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> streamlit <span class="hljs-keyword">as</span> st
<span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">import</span> openai
<span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">import</span> os

<span class="hljs-comment"># Constants</span>
MODEL_OPTIONS = [<span class="hljs-string">"gpt-4o-mini"</span>, <span class="hljs-string">"gpt-4o"</span>, <span class="hljs-string">"gpt-3-5"</span>]
PERSONAS_OPTIONS = {
    <span class="hljs-string">"Analytical"</span>: <span class="hljs-string">"Provide detailed, logical analyses."</span>,
    <span class="hljs-string">"Business_Consultant"</span>: <span class="hljs-string">"Offer strategic business advice and insights."</span>,
    <span class="hljs-string">"Chef"</span>: <span class="hljs-string">"Share cooking tips, recipes, and culinary advice."</span>,
    <span class="hljs-string">"Code_Reviewer"</span>: <span class="hljs-string">"Analyze code snippets for best practices and potential bugs."</span>,
    <span class="hljs-string">"Concise"</span>: <span class="hljs-string">"Give brief, to-the-point responses."</span>,
    <span class="hljs-string">"Creative"</span>: <span class="hljs-string">"Offer imaginative and original responses."</span>,
    <span class="hljs-string">"Default"</span>: <span class="hljs-string">"Act as a helpful assistant."</span>,  <span class="hljs-comment"># Default persona</span>
}
TONE_OPTIONS = [
                <span class="hljs-string">"Professional"</span>, 
                <span class="hljs-string">"Casual"</span>, 
                <span class="hljs-string">"Friendly"</span>, 
                <span class="hljs-string">"Formal"</span>, 
                <span class="hljs-string">"Humorous"</span>
            ]

DEFAULT_MODEL = <span class="hljs-string">"gpt-4o-mini"</span>
DEFAULT_PERSONA = <span class="hljs-string">"Default"</span>
</code></pre>
<p>Step 5: Export the API key to the system environment so that our app can safely access the key.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Initialize OpenAI client</span>
client = OpenAI(api_key=os.environ.get(<span class="hljs-string">"OPENAI_API_KEY"</span>))
</code></pre>
<p>Step 6: Now, let's set up the SQLite3 database. After setting up the database, we need to set up the cache. The cache is a temporary storage location for data that is frequently accessed. It speeds up the processing of user input.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Database setup</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init_db</span>():</span>
    conn = sqlite3.connect(<span class="hljs-string">'chat_history.db'</span>)
    c = conn.cursor()
    c.execute(<span class="hljs-string">'''CREATE TABLE IF NOT EXISTS messages
                 (role TEXT, content TEXT, timestamp REAL)'''</span>)
    conn.commit()
    <span class="hljs-keyword">return</span> conn

st.set_page_config(page_title=<span class="hljs-string">"Chatbee🐝"</span>, page_icon=<span class="hljs-string">"🐝"</span>)

<span class="hljs-comment"># Cache setup</span>
<span class="hljs-meta">@st.cache_data(ttl=3600)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_openai_response</span>(<span class="hljs-params">messages, model, max_tokens, temperature</span>):</span>
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        max_tokens=max_tokens,
        temperature=temperature
    )
    <span class="hljs-keyword">return</span> response.choices[<span class="hljs-number">0</span>].message.content

<span class="hljs-comment"># Process user input</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_user_input</span>(<span class="hljs-params">prompt</span>):</span>
    <span class="hljs-comment"># Add user message to chat history</span>
    st.session_state.messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt})
    <span class="hljs-keyword">with</span> st.chat_message(<span class="hljs-string">"user"</span>):
        st.markdown(prompt)

    <span class="hljs-comment"># Prepare messages for API call</span>
    messages = [
        {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">f"You are acting as a <span class="hljs-subst">{persona_key}</span> persona. <span class="hljs-subst">{persona}</span>\n\nTone: <span class="hljs-subst">{tone}</span>"</span>},
        *st.session_state.messages
    ]

    <span class="hljs-comment"># Get AI response</span>
    <span class="hljs-keyword">with</span> st.chat_message(<span class="hljs-string">"assistant"</span>):
        message_placeholder = st.empty()
        full_response = <span class="hljs-string">""</span>
        response = get_openai_response(messages, model, max_tokens, temperature)
        full_response += response
        message_placeholder.markdown(full_response + <span class="hljs-string">"▌"</span>)
        message_placeholder.markdown(full_response)

    <span class="hljs-comment"># Add AI response to chat history</span>
    st.session_state.messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"assistant"</span>, <span class="hljs-string">"content"</span>: full_response})

    <span class="hljs-comment"># Save to database</span>
    conn = init_db()
    c = conn.cursor()
    c.executemany(<span class="hljs-string">"INSERT INTO messages VALUES (?, ?, ?)"</span>, [
        (<span class="hljs-string">"user"</span>, prompt, time.time()),
        (<span class="hljs-string">"assistant"</span>, full_response, time.time())
    ])
    conn.commit()
    conn.close()
</code></pre>
<p>Step 7: All the configuration and parameters for this application are placed in the sidebar. We can select the model, persona, and tone that we want to use. We can also adjust the advanced settings, such as the maximum number of tokens and the temperature.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Sidebar configuration</span>
<span class="hljs-keyword">with</span> st.sidebar:
    st.markdown(<span class="hljs-string">"&lt;h3 style='text-align: center;'&gt;⚙️ Configurations 🔧&lt;/h3&gt;"</span>, unsafe_allow_html=<span class="hljs-literal">True</span>)

    model = st.sidebar.selectbox(<span class="hljs-string">"Model"</span>, MODEL_OPTIONS, index=MODEL_OPTIONS.index(DEFAULT_MODEL))
    persona_key = st.sidebar.selectbox(<span class="hljs-string">"Persona"</span>, list(PERSONAS_OPTIONS.keys()), index=list(PERSONAS_OPTIONS.keys()).index(DEFAULT_PERSONA))
    persona = PERSONAS_OPTIONS[persona_key]
    tone = st.sidebar.selectbox(<span class="hljs-string">"Tone"</span>, TONE_OPTIONS)

    <span class="hljs-keyword">with</span> st.sidebar.expander(<span class="hljs-string">"Advanced Settings"</span>):
        max_tokens = st.slider(<span class="hljs-string">"Max Tokens"</span>, min_value=<span class="hljs-number">50</span>, max_value=<span class="hljs-number">2000</span>, value=<span class="hljs-number">150</span>, step=<span class="hljs-number">50</span>)
        temperature = st.slider(<span class="hljs-string">"Temperature"</span>, min_value=<span class="hljs-number">0.0</span>, max_value=<span class="hljs-number">1.0</span>, value=<span class="hljs-number">0.7</span>, step=<span class="hljs-number">0.1</span>)

    st.sidebar.markdown(<span class="hljs-string">"---"</span>)  <span class="hljs-comment"># Add a separator</span>
    <span class="hljs-keyword">if</span> st.sidebar.button(<span class="hljs-string">"Clear Chat History"</span>):
        st.session_state.messages = []
        st.rerun()

<span class="hljs-comment">#</span>
</code></pre>
<p>Step 9: The main chat window displays the chat history between we and the AI assistant. We can type a message and send it to the AI assistant, which will respond with a message that is tailored to our needs.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Main chat window</span>
st.markdown(<span class="hljs-string">'&lt;h1 style="text-align: center; color: #6ca395;"&gt;Chatbee🐝&lt;/h1&gt;'</span>, unsafe_allow_html=<span class="hljs-literal">True</span>)
st.markdown(<span class="hljs-string">'&lt;p style="text-align: center; color: #FF0000;"&gt;always at your service&lt;/p&gt;'</span>, unsafe_allow_html=<span class="hljs-literal">True</span>)

<span class="hljs-comment"># Initialize session state</span>
<span class="hljs-keyword">if</span> <span class="hljs-string">'messages'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> st.session_state:
    st.session_state.messages = []

<span class="hljs-comment"># Display chat history</span>
<span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> st.session_state.messages:
    <span class="hljs-keyword">with</span> st.chat_message(message[<span class="hljs-string">"role"</span>]):
        st.markdown(message[<span class="hljs-string">"content"</span>])

<span class="hljs-comment"># Chat input</span>
<span class="hljs-keyword">if</span> prompt := st.chat_input(<span class="hljs-string">"Hello, how can I help you?"</span>):
    process_user_input(prompt)
</code></pre>
<p>Complete code is available for cloning: <a target="_blank" href="https://github.com/emeeran/Chatbee.git">https://github.com/emeeran/Chatbee.git</a></p>
]]></content:encoded></item><item><title><![CDATA[Auto mount Synology NAS in Ubuntu 2024]]></title><description><![CDATA[Network-attached storage (NAS) systems, such as Synology, are crucial for safe data backup and storage for many people and organizations in the modern digital era. You can make sure that your critical data is always available by setting up your Ubunt...]]></description><link>https://emeeran.dev/auto-mount-synology-nas-in-ubuntu-2024</link><guid isPermaLink="true">https://emeeran.dev/auto-mount-synology-nas-in-ubuntu-2024</guid><category><![CDATA[mount_NAS]]></category><category><![CDATA[NAS_ubuntu]]></category><category><![CDATA[nas]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Sun, 11 Aug 2024 17:59:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_od_eW0yGF8/upload/973905588169e951a38445453248e7bd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Network-attached storage (NAS) systems, such as Synology, are crucial for safe data backup and storage for many people and organizations in the modern digital era. You can make sure that your critical data is always available by setting up your Ubuntu system to automatically mount your Synology NAS. In this guide, we will explore the steps to auto-mount a Synology NAS in Ubuntu 2024, allowing for seamless and efficient file management.</p>
<p><strong>Step 1: Install Required Packages</strong></p>
<ul>
<li>Open a terminal on your Ubuntu system and run the following command:</li>
</ul>
<pre><code class="lang-bash">sudo apt-get update &amp;&amp; sudo apt-get install cifs-utils
</code></pre>
<p><strong>Step 2: Create a Mount Point</strong></p>
<ul>
<li>Create a directory to mount your Synology NAS:</li>
</ul>
<pre><code class="lang-bash">sudo mkdir /media/synology
</code></pre>
<p><strong>Step 3: Get NAS IP Address</strong></p>
<ul>
<li><p>Find the IP address of your Synology NAS. You can do this by:</p>
<ul>
<li><p>Type in the browser: <a target="_blank" href="https://finds.synology.com/">https://finds.synology.com/</a></p>
</li>
<li><p>Logging into your Synology NAS web interface and looking for the IP address under</p>
</li>
<li><p>"Control Panel" &gt; "Network" &gt; "LAN"</p>
</li>
<li><p>Using the Synology Assistant software (if installed)</p>
</li>
<li><p>Checking your router's DHCP client list</p>
</li>
</ul>
</li>
</ul>
<p><strong>Step 4: Mount the NAS</strong></p>
<ul>
<li>Mount your Synology NAS using the following command:</li>
</ul>
<pre><code class="lang-bash">sudo mount -t cifs //NAS_IP_ADDRESS/share_name /media/synology -o username=your_username,password=your_password
</code></pre>
<p>Replace:</p>
<ul>
<li><p><code>NAS_IP_ADDRESS</code> with the IP address of your Synology NAS</p>
</li>
<li><p><code>share_name</code> with the name of the shared folder on your NAS</p>
</li>
<li><p><code>your_username</code> and <code>your_password</code> with your Synology NAS login credentials</p>
</li>
</ul>
<p><strong>Step 5: Use a credentials file</strong></p>
<p>Instead of passing the username and password directly in the command, create a credentials file and reference it in the mount command:</p>
<pre><code class="lang-bash">sudo nano /etc/cifs-credentials.txt
</code></pre>
<p>Add the following lines to the file:</p>
<pre><code class="lang-bash">username=Meeran
password=qvt****-YMQ8bpm!qrq
</code></pre>
<p>Then, mount the NAS using:</p>
<pre><code class="lang-bash">sudo mount -t cifs //192.168.1.100/home /media/synology -o credentials=/etc/cifs-credentials.txt
</code></pre>
<p><strong>Step 6: Open the fstab file</strong></p>
<p>Run the following command in a terminal:</p>
<p>Bash</p>
<pre><code class="lang-bash">sudo nano /etc/fstab
</code></pre>
<p><strong>Step 7: Add the mount entry.</strong></p>
<p>Add the following line at the end of the file:</p>
<pre><code class="lang-bash">//192.168.1.222/home /media/synology cifs credentials=/etc/cifs-credentials.txt,iocharset=utf8,vers=3.0 0 0
</code></pre>
<p>Replace:</p>
<ul>
<li><p><code>//192.168.1.222/home</code> with the UNC path to your Synology NAS share</p>
</li>
<li><p><code>/media/synology</code> with the mount point on your Ubuntu system</p>
</li>
<li><p><code>/etc/cifs-credentials.txt</code> with the path to your credentials file.</p>
</li>
</ul>
<p><strong>Step 8: Save and exit</strong></p>
<p>Save the changes and exit the editor.</p>
<p><strong>Step 9: Test the fstab entry</strong></p>
<p>Run the following command to test the fstab entry:</p>
<pre><code class="lang-bash">sudo mount -a
</code></pre>
<p>This should mount your Synology NAS share.</p>
<p><strong>Step 10: Reboot and verify</strong></p>
<p>Reboot your Ubuntu system and verify that the Synology NAS share is automatically mounted.</p>
<p>Note: Make sure the <code>/etc/cifs-credentials.txt</code> file has the correct permissions (e.g., <code>chmod 600 /etc/cifs-credentials.txt</code>) and contains the correct username and password.</p>
<p>By adding the entry<code>/etc/fstab</code>, your Ubuntu system will automatically mount the Synology NAS share on startup. That's it! You're all set up and ready to go. Enjoy seamless access to your Synology NAS share!</p>
]]></content:encoded></item><item><title><![CDATA[A GUI Note Taking App]]></title><description><![CDATA[Description:
A straightforward note-taking application saves your text notes in a database and retrieves them when needed. Users can effortlessly create, edit, and remove notes within the app, providing a handy means to organize and monitor crucial d...]]></description><link>https://emeeran.dev/a-gui-note-taking-app</link><guid isPermaLink="true">https://emeeran.dev/a-gui-note-taking-app</guid><category><![CDATA[tkinter]]></category><category><![CDATA[note-taking]]></category><category><![CDATA[GUI]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Fri, 19 Apr 2024 16:31:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713543759350/5776632c-b73f-4124-9463-e2c3d88949df.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-description"><strong>Description:</strong></h3>
<p>A straightforward note-taking application saves your text notes in a database and retrieves them when needed. Users can effortlessly create, edit, and remove notes within the app, providing a handy means to organize and monitor crucial details. Featuring a user-friendly interface, the app streamlines the task of storing and accessing vital information.</p>
<h3 id="heading-requirements"><strong>Requirements:</strong></h3>
<ul>
<li><p><strong>Python 12.x</strong></p>
</li>
<li><p><strong>Tkinter #</strong> <a target="_blank" href="https://docs.python.org/3/library/tkinter.html#">tkinter —documentation</a></p>
</li>
<li><p>Pyinstaller # <a target="_blank" href="https://pyinstaller.org/en/stable/index.html">PyInstaller 6.6.0 documentation</a></p>
</li>
</ul>
<h3 id="heading-features"><strong>Features:</strong></h3>
<ol>
<li><p><strong>Text note-taking</strong></p>
</li>
<li><p><strong>GUI</strong></p>
</li>
<li><p><strong>Can be run as script</strong></p>
</li>
<li><p><strong>Can be run EXE</strong></p>
</li>
<li><p><strong>Editing, deleting options.</strong></p>
</li>
<li><p><strong>SQLite database</strong></p>
</li>
</ol>
<h3 id="heading-installation"><strong>Installation:</strong></h3>
<ol>
<li><p>Clone the repository: <code>pip git clone</code> <a target="_blank" href="https://github.com/emeeran/note_taking_app.git"><code>https://github.com/emeeran/note_taking_app.git</code></a></p>
</li>
<li><p>Navigate to the project directory and activate the virtual environment.</p>
</li>
<li><p><code>Pip install tkinter</code></p>
</li>
<li><p><code>Pip install pyinstaller</code></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Essential pip commands for Python]]></title><description><![CDATA[PIP is an acronym for Package Installer for Python. Its primary function is to install and manage software packages written in Python that may be found in the Python Package Index (PyPI). Here is a list I've compiled for quick reference.
pip commands...]]></description><link>https://emeeran.dev/essential-pip-commands-for-python</link><guid isPermaLink="true">https://emeeran.dev/essential-pip-commands-for-python</guid><category><![CDATA[Python]]></category><category><![CDATA[pip]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Sun, 14 Apr 2024 16:42:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ZIPFteu-R8k/upload/dc8f746b2002d7f37cfd50a7114b2184.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>PIP is an acronym for Package Installer for Python. Its primary function is to install and manage software packages written in Python that may be found in the Python Package Index (PyPI). Here is a list I've compiled for quick reference.</p>
<h3 id="heading-pip-commands">pip commands:</h3>
<h3 id="heading-installation">Installation</h3>
<p><code>pip install &lt;package name&gt;</code> # install a package.</p>
<p><code>pip install pywhatkit --force-reinstall</code> # To force reinstall</p>
<p><code>pip freeze &gt; requirements.txt</code> # To generate a <code>requirements.txt</code> file</p>
<p><code>pip install -r requirements.txt</code> # To install packages from a <code>requirements.txt</code> file</p>
<p><code>pip check</code> # To check for conflicting dependencies</p>
<p><a target="_blank" href="https://www.notion.so/pip-install-more-options-f5309c7b6d0d4beaa19e711306a08fb9?pvs=21">pip install more options</a></p>
<p><a target="_blank" href="https://www.notion.so/pip-install-comprehensive-list-199e8b480eef4e9f8fcebd0d3527e88a?pvs=21">pip install comprehensive list</a></p>
<h3 id="heading-uninstall">Uninstall</h3>
<p><code>pip uninstall &lt;package name&gt;</code> # uninstall a package</p>
<p><a target="_blank" href="https://www.notion.so/pip-uninstall-2df7433acec3476c9ed47d155295b34d?pvs=21">pip uninstall</a></p>
<h3 id="heading-update-and-upgrade">Update and Upgrade</h3>
<p><code>python -m pip install --upgrade pip</code> # To upgrade pip</p>
<p><code>py -m pip install --upgrade SomePackage</code> # To upgrade package installed</p>
<h3 id="heading-checking-installed-packages">Checking Installed Packages</h3>
<p><code>pip --version</code> # To know pip version</p>
<p><code>pip show requests</code> # To check the version of an installed library/module</p>
<p><code>pip list --outdated</code> # To identify outdated packages</p>
<h3 id="heading-package-information">Package Information</h3>
<p><code>pip show --files &lt;package name&gt;</code> # To display the files installed by a package</p>
<p><code>pip show --verbose &lt;package name&gt;</code> # To show more information about a package</p>
<p><code>pip show --location &lt;package name&gt;</code> # To show the installation location of a package</p>
<p><code>pipdeptree</code> # To show a dependency tree of the installed packages</p>
<h3 id="heading-miscellaneous">Miscellaneous</h3>
<p><code>pip search &lt;package name&gt;</code> # To search for a package</p>
<p><code>pip show &lt;package name&gt;</code> # To show information about a package</p>
<p><code>pip download &lt;package name&gt;</code> # To download a package without installing it</p>
<p><code>pip wheel &lt;package name&gt;</code> # To create a binary wheel distribution</p>
<p><code>pip hash &lt;package name&gt;</code> # To compute the hash of a package file</p>
<p><code>pip config list</code> # To list the current configuration</p>
<p><code>pip config set &lt;option&gt; &lt;value&gt;</code> # To set a configuration option</p>
<p><code>pip cache purge</code> # To clear the pip cache</p>
<p><code>pip freeze</code> # Outputs installed packages in requirements format.</p>
<p><code>pip check</code> # Verify installed packages have compatible dependencies.</p>
<p><code>pip search &lt;query&gt;</code> # Search PyPI for packages.</p>
<p><code>pip wheel &lt;package name&gt;</code> # Build wheels from your requirements.</p>
<p><code>pip completion</code> # A helper command used for command completion.</p>
<p><code>pip debug</code> # Show information useful for debugging.</p>
<p><code>pip cache &lt;command&gt;</code> # Inspect and manage pip's wheel cache.</p>
<p><code>pip config &lt;command&gt;</code> # Manage local and global configuration.</p>
<p><code>pip help</code> # Show help for commands.</p>
<h3 id="heading-dependency-information">Dependency Information</h3>
<p><code>pipdeptree</code> # To show a dependency tree of the installed packages</p>
<p><code>pip completion --bash</code> # To enable bash shell completion</p>
<p><code>pip completion --zsh</code> # To enable ZSH shell completion</p>
<h3 id="heading-environment-information">Environment Information</h3>
<p><code>pip debug --verbose</code> # To display detailed information about the pip environment</p>
<h3 id="heading-troubleshooting">Troubleshooting</h3>
<p><code>pip check</code> # To check for conflicting dependencies</p>
<p><code>pip list --outdated</code> # To identify outdated packages</p>
<h3 id="heading-cache-options">Cache Options</h3>
<p><code>pip cache info</code> # To display information about the cache</p>
<p><code>pip cache dir</code> # To show the cache directory</p>
<p><code>pip cache purge</code> # To clear the pip cache</p>
<p><code>pip cache remove &lt;package name&gt;</code> # To remove a specific package from the cache</p>
<p><code>pip cache list</code> # To list all packages stored in the cache</p>
<h3 id="heading-configuration-options">Configuration Options</h3>
<p><code>pip config list</code> # To list the current configuration</p>
<p><code>pip config set &lt;option&gt; &lt;value&gt;</code> # To set a configuration option</p>
<p><code>pip config unset &lt;option&gt;</code> # To unset a configuration option</p>
<p><code>pip config edit</code> # To edit the pip configuration file</p>
<p><code>pip config debug</code> # To display configuration debug information</p>
<h3 id="heading-debugging-and-testing">Debugging and Testing</h3>
<p><code>pip debug</code> # Show information useful for debugging</p>
<p><code>pip debug --verbose</code> # To display detailed information about the pip environment</p>
<p><code>pip test &lt;package name&gt;</code> # To test a package</p>
<p><code>pip test --verbose &lt;package name&gt;</code> # To test a package with verbose output</p>
<h3 id="heading-download-options">Download Options</h3>
<p><code>py -m pip download [options] &lt;requirement specifier&gt; [package-index-options] ...</code> # To download with options</p>
<p><code>py -m pip download [options] -r &lt;requirements file&gt; [package-index-options] ...</code> # To download packages from a requirements file with options</p>
<p><code>py -m pip download [options] &lt;vcs project url&gt; ...</code> # To download from a version control system (VCS) project URL with options</p>
<p><code>py -m pip download [options] &lt;local project path&gt; ...</code> # To download from a local project path with options.</p>
<p><code>py -m pip download [options] &lt;archive url/path&gt; ...</code> # To download from an archived URL or path with options</p>
<h3 id="heading-shell-completion">Shell Completion</h3>
<p><code>pip completion --bash</code> # To enable bash shell completion.</p>
<p><code>pip completion --zsh</code> # To enable ZSH shell completion.</p>
<p><code>pip debug --verbose</code> # To display detailed information about the pip environment.</p>
]]></content:encoded></item><item><title><![CDATA[WhatsApp Automation.]]></title><description><![CDATA[Description:
Automating the process of sending WhatsApp messages to multiple people not in your contact list can simplify what is otherwise a tedious task. This project is designed to facilitate communication and save time for those who often send me...]]></description><link>https://emeeran.dev/whatsapp-automation</link><guid isPermaLink="true">https://emeeran.dev/whatsapp-automation</guid><category><![CDATA[automation]]></category><category><![CDATA[WhatsApp automation]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Sat, 13 Apr 2024 18:15:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_qsuER9xYOY/upload/5125d61bb1ca226b36b069587f793b64.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-description"><strong>Description:</strong></h3>
<p>Automating the process of sending WhatsApp messages to multiple people not in your contact list can simplify what is otherwise a tedious task. This project is designed to facilitate communication and save time for those who often send messages to non-contacts on WhatsApp. Simply input the mobile numbers into the contacts.txt file and execute the necessary script to send messages, with the option to schedule them as well.</p>
<p>For these scripts to function, you must have a WhatsApp account and be logged into your browser, as this utilizes WhatsApp Web.</p>
<h3 id="heading-features"><strong>Features:</strong></h3>
<ol>
<li><p><strong>Sending text messages</strong></p>
</li>
<li><p><strong>Sending text messages with pictures</strong></p>
</li>
<li><p><strong>Sending messages to groups</strong></p>
</li>
<li><p><strong>Can schedule messages to be sent at a specific date and time.</strong></p>
</li>
</ol>
<h3 id="heading-installation"><strong>Installation:</strong></h3>
<ol>
<li><p>Clone the repository: <a target="_blank" href="https://github.com/emeeran/whatsApp_automation_Python_with_selenium.git"><code>https://github.com/emeeran/whatsApp_automation_Python.git</code></a></p>
</li>
<li><p>Navigate to the project directory and activate the virtual environment.</p>
</li>
<li><p><code>pip install pywhatkit</code></p>
</li>
<li><p><code>pip install pywin32</code>`</p>
</li>
</ol>
<h3 id="heading-documentation"><strong>Documentation</strong>:</h3>
<p>pywhatkit: <a target="_blank" href="https://www.bing.com/ck/a?!&amp;&amp;p=b13501df09c082ccJmltdHM9MTcxMjk2NjQwMCZpZ3VpZD0wYzM5NjQzMy0zMzFhLTYwNDktMDI0Yi03MDBjMzI3YTYxNGUmaW5zaWQ9NTIyNw&amp;ptn=3&amp;ver=2&amp;hsh=3&amp;fclid=0c396433-331a-6049-024b-700c327a614e&amp;psq=pywhatkit+documentation&amp;u=a1aHR0cHM6Ly9weXBpLm9yZy9wcm9qZWN0L3B5d2hhdGtpdC8&amp;ntb=1">PyPI https://pypi.org/project/pywhatkit (bing.com)</a></p>
<p>pywin32: <a target="_blank" href="https://www.bing.com/ck/a?!&amp;&amp;p=dd6165452e12402dJmltdHM9MTcxMjk2NjQwMCZpZ3VpZD0wYzM5NjQzMy0zMzFhLTYwNDktMDI0Yi03MDBjMzI3YTYxNGUmaW5zaWQ9NTIyOA&amp;ptn=3&amp;ver=2&amp;hsh=3&amp;fclid=0c396433-331a-6049-024b-700c327a614e&amp;psq=pywin32+documentation&amp;u=a1aHR0cHM6Ly9weXBpLm9yZy9wcm9qZWN0L3B5d2luMzIv&amp;ntb=1">pywin32 · PyPI (bing.com)</a></p>
]]></content:encoded></item><item><title><![CDATA[Essential git CLI commands]]></title><description><![CDATA[Frequently used commands:

git init # initializes.

git add . # adds all files

git add <file 1> <filel 2> # adds individual file.

git commit -m "descriptive message." # commits

git branch <branch name> # creates a new branch

git checkout <branch ...]]></description><link>https://emeeran.dev/essential-git-cli-commands</link><guid isPermaLink="true">https://emeeran.dev/essential-git-cli-commands</guid><category><![CDATA[git CLI commands]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Git]]></category><category><![CDATA[Git Commands]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Thu, 11 Apr 2024 05:52:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/842ofHC6MaI/upload/210d6fd46be9899c8d05b7620c4376c7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Frequently used commands:</p>
<ul>
<li><p><code>git init</code> # initializes.</p>
</li>
<li><p><code>git add .</code> # adds all files</p>
</li>
<li><p><code>git add &lt;file 1&gt; &lt;filel 2&gt;</code> # adds individual file.</p>
</li>
<li><p><code>git commit -m "descriptive message."</code> # commits</p>
</li>
<li><p><code>git branch &lt;branch name&gt;</code> # creates a new branch</p>
</li>
<li><p><code>git checkout &lt;branch name&gt;</code> # checkouts to a branch</p>
</li>
<li><p><code>git branch -n &lt;branch name&gt;</code> # creates new branch and checkouts</p>
</li>
<li><p><code>git merge &lt;branch_name&gt;</code> # checkout to the main branch, then merge</p>
</li>
<li><p><code>git checkout main/master &amp;&amp; git merge &lt;current branch&gt;</code> # merging from being in the branch to be merged.</p>
</li>
<li><p><code>git pull origin &lt;branch_name&gt;</code> # pull</p>
</li>
<li><p><code>git push origin &lt;branch_name&gt;</code> # push to web repository.</p>
</li>
<li><p><code>git branch -d &lt;branch_name&gt;</code> # removing a branch.</p>
</li>
<li><p><code>git status</code> # check status.</p>
</li>
<li><p>``git remote set-url` # To rename your local Git repository to match the name of the remote repository,</p>
</li>
<li><p><code>git rm --cached .env</code> # If the <code>.env</code> file was already tracked by Git before, you’ll need to remove it from the index, and do the following:</p>
<ul>
<li><code>git add .gitignore</code> <code>git commit -m "Add .env to .gitignore"</code> <code>git push</code></li>
</ul>
</li>
<li><p><code>git branch</code> # to see local branches.</p>
</li>
<li><p><code>git fetch</code> # before listing remote branch, run this.</p>
</li>
<li><p><code>git branch -r</code> # to see all remote branches.</p>
</li>
<li><p><code>git branch -a</code> # to see both.</p>
</li>
<li><p><code>git branch -vv</code> # to see both in detail or.</p>
</li>
<li><p><code>git branch -vva</code> # to see both in detail.</p>
</li>
<li><p><code>git checkout -</code> # to checkout last branch.</p>
</li>
<li><p><code>git branch -m &lt;new name&gt;</code> # to rename current branch</p>
</li>
<li><p><code>git branch -m old-branch-name new-branch-name</code> # to rename a branch.</p>
</li>
<li><p><code>git push origin -u new-branch-name</code> # push the new branch to remote</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Python to Extract URLs from a YouTube Playlist]]></title><description><![CDATA[YouTube has become a crucial part of my learning journey. Whenever I find a valuable video on YouTube, I save it to my library on the platform. However, as the list has grown over time, it has become difficult to search for a specific video that I wa...]]></description><link>https://emeeran.dev/python-to-extract-urls-from-a-youtube-playlist</link><guid isPermaLink="true">https://emeeran.dev/python-to-extract-urls-from-a-youtube-playlist</guid><category><![CDATA[youtube playlist]]></category><category><![CDATA[Python]]></category><category><![CDATA[youtube]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Mon, 08 Apr 2024 17:40:48 GMT</pubDate><content:encoded><![CDATA[<p>YouTube has become a crucial part of my learning journey. Whenever I find a valuable video on YouTube, I save it to my library on the platform. However, as the list has grown over time, it has become difficult to search for a specific video that I want to revisit. To make it more efficient, I decided to create a Python script that extracts all the URLs from my YouTube playlist. Using this method, I can easily search for and access the videos I need without manually browsing through my library. The script has made my learning process much more organized, allowing me to focus on the valuable information presented in the videos.</p>
<p><strong>Features</strong>:</p>
<ul>
<li><p>Extracts video titles along with URLs</p>
</li>
<li><p>Exports to a text file</p>
</li>
</ul>
<p><strong>Installation</strong>:</p>
<p>Assumes you have set up a project directory and activated a virtual environment.</p>
<ul>
<li><p><a target="_blank" href="https://github.com/emeeran/youtube_playlist_url_extractor.git"><code>https://github.com/emeeran/youtube_playlist_url_extractor.git</code></a></p>
</li>
<li><p><code>pip install pytube</code></p>
</li>
</ul>
<p><strong>Reference</strong>:</p>
<p><a target="_blank" href="https://pytube.io/en/latest/index.html">pytube — pytube 15.0.0 documentation</a></p>
]]></content:encoded></item><item><title><![CDATA[PY to EXE]]></title><description><![CDATA[What did I learn today?
Today, I discovered how to transform a Python script into an executable. There are two approaches: one involves using the command line interface (CLI), and the other employs a graphical user interface (GUI). We will now explor...]]></description><link>https://emeeran.dev/py-to-exe</link><guid isPermaLink="true">https://emeeran.dev/py-to-exe</guid><category><![CDATA[py_exe]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Fri, 05 Apr 2024 12:35:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702091853369/fffcce44-ecb5-41c2-8ae7-ea742ff23376.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-did-i-learn-today">What did I learn today?</h2>
<p>Today, I discovered how to transform a Python script into an executable. There are two approaches: one involves using the command line interface (CLI), and the other employs a graphical user interface (GUI). We will now explore CLI method.</p>
<h3 id="heading-installation">Installation:</h3>
<p>Python Library: <strong>pyinstaller</strong> Refer to the <a target="_blank" href="https://pyinstaller.org/en/stable/index.html">documentation</a> for details.</p>
<p>Activate the virtual environment before running pyinstaller.</p>
<p>Step 1: install pyinstaller using pip:</p>
<pre><code class="lang-python">  pip install pyinstaller
</code></pre>
<p>Step 2: package your script after installing pyinstaller, navigate to the directory containing your Python script, and run the following command:</p>
<pre><code class="lang-python">pyinstaller --onefile --noconsole your_script.py
</code></pre>
<p>This command:</p>
<ul>
<li><p>The <code>--onefile</code> option bundles everything into a single file, simplifying distribution.</p>
</li>
<li><p>No console window appears when the application launches.</p>
</li>
<li><p>Ideal for finished GUI applications.</p>
</li>
</ul>
<pre><code class="lang-python">pyinstaller your_script.py
</code></pre>
<p>This command:</p>
<ul>
<li><p>Creates a folder with an executable and dependencies.</p>
</li>
<li><p>Suitable for development and debugging.</p>
</li>
</ul>
<p>After running this command, PyInstaller will create a 'dist' directory in the same location as your script. Within the 'dist' directory, you will find the standalone executable file, which will have the same name as your Python script.</p>
<p>Replace <strong>your_</strong><a target="_blank" href="https://pyinstaller.readthedocs.io/en/stable/index.html"><strong>script.py</strong></a> with the name of your Python script.</p>
<p>Be aware that PyInstaller includes the Python interpreter, which can increase the size of the executable. Additionally, the executable is specific to the platform it's created on; an executable made on Windows will not work on MacOS or Linux. To support different platforms, you must run PyInstaller on each one individually.</p>
]]></content:encoded></item><item><title><![CDATA[Python Project Boilerplate.]]></title><description><![CDATA[A smart way to start a project.
Description.:
The Python project boilerplate is designed to streamline the setup process by automating the creation of essential directories and files, such as 'src' and 'tests', as well as 'README.md' and '.gitignore'...]]></description><link>https://emeeran.dev/python-project-boilerplate</link><guid isPermaLink="true">https://emeeran.dev/python-project-boilerplate</guid><category><![CDATA[python boiler]]></category><category><![CDATA[Python]]></category><category><![CDATA[boilerplate]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Fri, 05 Apr 2024 09:29:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712309320261/ad35cd0e-3fdd-46a7-be63-ca4477b37ea8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-a-smart-way-to-start-a-project"><strong>A smart way to start a project.</strong></h3>
<p><strong>Description.:</strong></p>
<p>The Python project boilerplate is designed to streamline the setup process by automating the creation of essential directories and files, such as 'src' and 'tests', as well as '<a target="_blank" href="http://README.md">README.md</a>' and '.gitignore'. By adhering to best practices, it not only saves developers time but also promotes consistency across projects with standardized structures and formats. Consequently, developers can focus more on coding rather than on the initial project configuration.</p>
<p>Developers can swiftly begin work on their projects without configuring project structures, enabling smooth project initiation. This approach saves time by establishing a clean and organized project structure from the outset, fostering improved project management practices. The tool is designed to boost developer efficiency and productivity by automating project setup, minimizing manual tasks, and encouraging standardized project configurations.</p>
<p><strong>Features:</strong></p>
<ul>
<li><p>creates the basic project structure while adhering to best practices.</p>
</li>
<li><p>creates a <strong>README.md</strong> file with a content template.</p>
</li>
<li><p>Create a .<strong>gitignore</strong> file with the content required for a Python project.</p>
</li>
<li><p>Creates a <strong>virtual environment</strong>.</p>
</li>
<li><p>Updates PIP and installs Flake8, pre-commits</p>
</li>
<li><p>executes commands git init, git add ., git commit -m "First Commit".</p>
</li>
</ul>
<p><strong>Installation:</strong></p>
<ol>
<li><p><strong>Clone the repository:</strong><a target="_blank" href="https://github.com/emeeran/python_project_boilerplate.git">https://github.com/emeeran/python_project_boilerplate.git</a></p>
</li>
<li><p><strong>Install dependencies:</strong><code>pip install -r Requirements.txt.</code></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Python QR UPI Code Generator]]></title><description><![CDATA[Description:
A Quick Response, otherwise known as a QR code, is a two-dimensional barcode. They are widely popular for their snappy readability and ample storage capacity. Comprised of black squares, you know, arranged on a grid of white squares. It ...]]></description><link>https://emeeran.dev/python-qr-upi-code-generator</link><guid isPermaLink="true">https://emeeran.dev/python-qr-upi-code-generator</guid><category><![CDATA[Qrcode]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Thu, 04 Apr 2024 09:03:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/nP9WOiM41WE/upload/6f4279bad28d4d1955607808b1ebf2d5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Description</strong>:</p>
<p>A Quick Response, otherwise known as a QR code, is a two-dimensional barcode. They are widely popular for their snappy readability and ample storage capacity. Comprised of black squares, you know, arranged on a grid of white squares. It has a lot of capacity for encoding various types of data.</p>
<p><strong>Usage</strong>:</p>
<p>This QR code generator is incredibly user-friendly and versatile, catering to a wide audience. Whether you're a small business owner looking to market your products or an event organizer seeking to provide instant access to essential information, this tool is designed to support you. QR codes can significantly enhance communication and enrich the experience for your target audience. Try this tool today and see for yourself how effortlessly you can create and distribute custom QR codes tailored to your unique requirements!</p>
<p><strong>Features:</strong></p>
<ul>
<li><p>QR Code Generation</p>
</li>
<li><p>UPI Code Generation</p>
</li>
<li><p>Color Customization</p>
</li>
<li><p>Size Customization</p>
</li>
</ul>
<p>Installation:</p>
<p>Clone the repository: <code>git clone https://</code><a target="_blank" href="http://github.com/emeeran/qr_generator.git"><code>github.com/emeeran/qr_generator.git</code></a></p>
<p>Navigate to the project directory: cd qr_generaor</p>
<p>The standard installation uses pypng to make PNG files and can render QR codes straight onto the console, <code>pip install qrcode</code></p>
<p>For better image functionality, install qrcode along with the pil dependency it will install! pillow and facilitate image generation, <code>pip install "qrcode[pil]"</code></p>
<p>Library Documentation:</p>
<p>Qrcode: <a target="_blank" href="https://pypi.org/project/qrcode/">https://pypi.org/project/qrcode/</a></p>
]]></content:encoded></item><item><title><![CDATA[Automate Your Project Setup]]></title><description><![CDATA[Tired of setting up project directories manually? Streamline workflow by automating tasks like creating directories, files, and setting up virtual environments. You're in the right place.!
In this blog post, I'll walk you through the process of creat...]]></description><link>https://emeeran.dev/automate-your-project-setup</link><guid isPermaLink="true">https://emeeran.dev/automate-your-project-setup</guid><category><![CDATA[Python]]></category><category><![CDATA[automation]]></category><category><![CDATA[project management]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Mon, 26 Feb 2024 16:31:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1708964927286/7f064a0d-d884-4753-8359-7da7014e19da.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Tired of setting up project directories manually? Streamline workflow by automating tasks like creating directories, files, and setting up virtual environments. You're in the right place.!</p>
<p>In this blog post, I'll walk you through the process of creating a Python script to automate project setup. Whether you're a seasoned developer looking to optimize your workflow or a beginner eager to dive into automation, this guide is for you.</p>
<h3 id="heading-why-automate-project-setup"><strong>Why Automate Project Setup?</strong></h3>
<p>Setting up a new project involves repetitive tasks such as creating directories, initializing Git repositories, creating configuration files, and more. These tasks, while necessary, can be time-consuming and error-prone if done manually. By automating the setup process, you can ensure consistency across projects, reduce the chance of human error, and focus more on actual development tasks.</p>
<h3 id="heading-requirements"><strong>Requirements</strong></h3>
<p>For this tutorial, you'll need:</p>
<ul>
<li><p>Basic knowledge of Python programming</p>
</li>
<li><p>A text editor or an IDE of your choice</p>
</li>
<li><p>Python installed on your system</p>
</li>
</ul>
<h3 id="heading-step-1-define-project-structure"><strong>Step 1: Define Project Structure</strong></h3>
<p>Before diving into coding, it's essential to define the structure of your project. Decide on the directory layout, subdirectories, and files you want to create for each new project. Common subdirectories include <code>src</code> for source code, <code>docs</code> for documentation, <code>data</code> for data files, and <code>.env</code> for environment variables.</p>
<h3 id="heading-step-2-write-the-python-script"><strong>Step 2: Write the Python Script</strong></h3>
<p>Now, let's start coding! We'll create a Python script that prompts the user for input, such as the parent directory path and project name, and then generates the project structure accordingly.</p>
<p>We'll use Python's built-in <code>os</code> module for file and directory operations and the <code>subprocess</code> module to execute system commands.</p>
<p>Here's the script:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> subprocess


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_project</span>():</span>
    <span class="hljs-comment"># Step 1: Prompt for the parent directory path</span>
    parent_dir = input(<span class="hljs-string">"Enter the parent directory path: "</span>)

    <span class="hljs-comment"># Step 2: Prompt for project directory name</span>
    project_name = input(<span class="hljs-string">"Enter the project directory name: "</span>)

    <span class="hljs-comment"># Step 3: Sub-directories to be hard coded</span>
    subdirectories = [
        <span class="hljs-string">"doc"</span>,
        <span class="hljs-string">"src"</span>,
        <span class="hljs-string">"data"</span>,
        <span class="hljs-string">"notes"</span>,
        <span class="hljs-string">".env"</span>,
    ]

    <span class="hljs-comment"># Step 4: Files to be created</span>
    files_to_create = [
        <span class="hljs-string">"README.md"</span>,
        <span class="hljs-string">"scribble_pad.md"</span>,
    ]

    <span class="hljs-comment"># Step 5: Create project directory</span>
    project_path = os.path.join(
        parent_dir, project_name.lower()
    )  <span class="hljs-comment"># Convert to lowercase</span>
    os.makedirs(project_path, exist_ok=<span class="hljs-literal">True</span>)  <span class="hljs-comment"># Ensure parent directory exists</span>

    <span class="hljs-comment"># Step 6: Create subdirectories</span>
    <span class="hljs-keyword">for</span> subdir <span class="hljs-keyword">in</span> subdirectories:
        subdir_path = os.path.join(project_path, subdir)
        os.makedirs(subdir_path, exist_ok=<span class="hljs-literal">True</span>)  <span class="hljs-comment"># Ensure subdirectories are created</span>
        <span class="hljs-comment"># Create main.py and test.py within the "src" subdirectory</span>
        <span class="hljs-keyword">if</span> subdir == <span class="hljs-string">"src"</span>:
            <span class="hljs-keyword">with</span> open(
                os.path.join(subdir_path, <span class="hljs-string">"main.py"</span>), <span class="hljs-string">"w"</span>
            ) <span class="hljs-keyword">as</span> f:  <span class="hljs-comment"># Convert to lowercase</span>
                <span class="hljs-keyword">pass</span>  <span class="hljs-comment"># Create an empty main.py file</span>
            <span class="hljs-keyword">with</span> open(
                os.path.join(subdir_path, <span class="hljs-string">"test.py"</span>), <span class="hljs-string">"w"</span>
            ) <span class="hljs-keyword">as</span> f:  <span class="hljs-comment"># Convert to lowercase</span>
                <span class="hljs-keyword">pass</span>  <span class="hljs-comment"># Create an empty test.py file</span>

    <span class="hljs-comment"># Step 7: Create files</span>
    <span class="hljs-keyword">for</span> file_name <span class="hljs-keyword">in</span> files_to_create:
        file_path = os.path.join(
            project_path, file_name.lower()
        )  <span class="hljs-comment"># Convert to lowercase</span>
        <span class="hljs-keyword">with</span> open(file_path, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> f:
            <span class="hljs-keyword">pass</span>  <span class="hljs-comment"># Create an empty file</span>

    <span class="hljs-comment"># Step 8: Copy content from root .gitignore to project .gitignore if it exists</span>
    root_gitignore_path = os.path.join(parent_dir, <span class="hljs-string">".gitignore"</span>)
    project_gitignore_path = os.path.join(project_path, <span class="hljs-string">".gitignore"</span>)
    <span class="hljs-keyword">if</span> os.path.exists(root_gitignore_path):
        <span class="hljs-keyword">with</span> open(root_gitignore_path, <span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> src_file, open(
            project_gitignore_path, <span class="hljs-string">"w"</span>
        ) <span class="hljs-keyword">as</span> dest_file:
            dest_file.write(src_file.read())

    <span class="hljs-comment"># Step 9: Open cmd window and load project directory</span>
    subprocess.run(<span class="hljs-string">f"cd <span class="hljs-subst">{project_path}</span> &amp;&amp; start cmd"</span>, shell=<span class="hljs-literal">True</span>)

    <span class="hljs-comment"># Step 10: Create virtual environment and activate</span>
    subprocess.run(
        <span class="hljs-string">f"cd <span class="hljs-subst">{project_path}</span> &amp;&amp; python -m venv .venv &amp;&amp; .venv\\Scripts\\activate"</span>,
        shell=<span class="hljs-literal">True</span>,
    )


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    create_project()
</code></pre>
<h3 id="heading-step-3-customize-and-expand"><strong>Step 3: Customize and Expand</strong></h3>
<p>Feel free to customize the script according to your specific project requirements. You can add more subdirectories, create additional files, or integrate version control systems like Git.</p>
<h3 id="heading-step-4-testing-and-refinement"><strong>Step 4: Testing and Refinement</strong></h3>
<p>Before using the script in an actual project, it's essential to test it thoroughly in different environments and scenarios.</p>
<h3 id="heading-step-5-sharing-and-collaboration"><strong>Step 5: Sharing and Collaboration</strong></h3>
<p>Once you're satisfied with your script, consider sharing it with the community! You can publish it on platforms like GitHub, GitLab, or Hashnode, where other developers can discover, use, and contribute to it.</p>
<hr />
<p>Congratulations! You've learned how to automate project setup using Python scripting. By automating repetitive tasks, you can boost your productivity, maintain consistency across projects, and focus on what matters most—building great software.</p>
<p>Happy coding! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Scraping Premium News Articles]]></title><description><![CDATA[Web scraping is the process of extracting data from websites by using automated tools or scripts. It allows users to gather specific information, such as news articles, job offers, gadget prices, reviews, etc. Commonly, webscraping is done for data a...]]></description><link>https://emeeran.dev/scraping-premium-news-articles</link><guid isPermaLink="true">https://emeeran.dev/scraping-premium-news-articles</guid><category><![CDATA[Scraping]]></category><category><![CDATA[BeautifulSoup]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Fri, 19 Jan 2024 16:44:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705682371355/baa8de17-13ad-436f-b71a-550b8444e62d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Web scraping is the process of extracting data from websites by using automated tools or scripts. It allows users to gather specific information, such as news articles, job offers, gadget prices, reviews, etc. Commonly, webscraping is done for data analysis, research, and monitoring purposes.</p>
<p><strong>Modern-day websites can be classified into:</strong></p>
<p><strong>Static websites</strong></p>
<p>These websites display fixed content for all users. The information remains constant and doesn't change unless the webmaster manually edits the HTML source code. URLs often have clear, straightforward structures without parameters. Each page typically corresponds to a specific file or directory.</p>
<p><strong>Dynamic websites</strong></p>
<p>Dynamic websites generate content on the fly, customizing it based on user interactions or other variables. They often use server-side languages, databases, and scripting languages to create a more interactive and personalized experience. URLs may contain parameters or variables, reflecting the dynamic nature of the content. These parameters often influence what content is displayed.</p>
<p><strong>Both dynamic and static mixed websites</strong></p>
<p>Dynamic websites combine elements of both static and dynamic websites. They may have certain pages or sections that are static, while others are generated dynamically. This allows for a balance between personalized content and efficient loading times. These mixed websites often utilize caching techniques to optimize performance and deliver a seamless user experience.</p>
<p><strong>Ways to identify website types:</strong></p>
<p>Static website URLs often have clear, straightforward structures without parameters. Each page typically corresponds to a specific file or directory. The source code is relatively simple and doesn't involve server-side scripting languages like PHP, Python, or Node.js. Limited interactivity, usually basic hyperlinks and forms</p>
<p>Dynamic websites URLs may contain parameters or variables, reflecting the dynamic nature of the content. These parameters often influence what content is displayed. You may find server-side scripting languages in the source code, indicating that content is generated dynamically. Rich interactivity, AJAX requests, and real-time updates are common.</p>
<p>You should be able to tell whether a website is dynamic or static by looking at these indicators. It will take practice and a thorough examination of the HTML code to identify this sort of website.</p>
<p>For scraping both dynamic and static websites, different libraries are often used. Here are some popular choices:</p>
<ul>
<li><p><strong>Static Websites:</strong></p>
<ul>
<li><strong>Beautiful Soup:</strong> This Python library is excellent for parsing HTML and XML documents. It's commonly used for extracting data from static web pages.</li>
</ul>
</li>
</ul>
<pre><code class="lang-xml">from bs4 import BeautifulSoup
import requests

url = 'your_static_website_url'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

# Now you can navigate and extract information from 'soup'.
</code></pre>
<p><strong>Dynamic Websites:</strong></p>
<ul>
<li><ul>
<li><strong>Selenium:</strong> When dealing with dynamic content, especially that loaded through JavaScript, Selenium is a powerful tool. It automates browsers, allowing you to interact with the web page as a user would.</li>
</ul>
</li>
</ul>
<pre><code class="lang-xml">
from selenium import webdriver
​
url = 'your_dynamic_website_url'
driver = webdriver.Chrome()  # You need to have the appropriate WebDriver installed
driver.get(url)
​
# Now you can interact with the dynamically loaded content using Selenium.
​
</code></pre>
<ul>
<li><strong>Scrapy with Splash:</strong> Scrapy is a versatile web crawling framework, and when combined with Splash, it can handle dynamic content effectively.</li>
</ul>
<pre><code class="lang-xml">
import scrapy
from scrapy_splash import SplashRequest
​
class MySpider(scrapy.Spider):
    # Your spider configuration here...
​
    def start_requests(self):
        url = 'your_dynamic_website_url'
        yield SplashRequest(url, self.parse, args={'wait': 0.5})
​
    def parse(self, response):
        # Parse the dynamically loaded content here.
​
</code></pre>
<p>Choose the library based on your specific requirements, and consider factors such as ease of use, community support, and the nature of the website you are scraping.</p>
<p>Let's dive into the actual web scraping project. For demonstration purposes, we are now going to scrape a premium paywalled article from https://indianexpress.com/ using Python and the BeautifulSoup4 library. The reason for choosing this website for a demo is that it is static and simple.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705682549717/aacfad28-379b-4566-97a9-cd400033f641.png" alt class="image--center mx-auto" /></p>
<p>I assume you have little knowledge about web scraping and programming and have installed Python 12.x.</p>
<p>Project Workflow:</p>
<ol>
<li><p>Create a project directory in Windows Explorer and open it in VS Code.</p>
</li>
<li><p>Press <code>Ctrl+Shift+P</code> to open the command palette and create a new virtual environment by selecting the option "Python:create virtual environment." As a convention, it is recommended to name the virtual environment folder ".venv.".</p>
</li>
<li><p>Once the virtual environment is created, activate it by running the command.venv/Scripts/activate in the VS Code terminal.</p>
</li>
<li><p>Install the required libraries. <code>pip install beautifulSoup4</code></p>
</li>
<li><p>visit https://www.gitignore.io/ and generate a gitignore file for your project.</p>
</li>
<li><p>Copy the content of the gitignore file and paste it into a new file named .gitignore in the root directory of your project.</p>
</li>
<li><p>initialize git <code>git init</code></p>
</li>
<li><p>Create a file in the root directory named main.py.</p>
</li>
<li><p>Copy and paste the entire code below into the main.py file and save it..</p>
</li>
<li><p>After successful scraping, save the installed packages and their versions in "Requirements.txt" in the root directory of your project. Run this command in the VS Code terminal: <code>pip freeze &gt; Requirements.txt</code>.</p>
</li>
</ol>
<p>This code will extract the text from the article and save it as an HTML file named "article-header as name.html" in the root directory of your project.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">from</span> html <span class="hljs-keyword">import</span> escape

<span class="hljs-comment"># URL of the web page</span>
url = <span class="hljs-string">"https://indianexpress.com/article/explained/explained-climate/kashmir-ladakh-without-snow-why-implications-9110841/"</span>

<span class="hljs-comment"># Make a GET request to the URL</span>
response = requests.get(url)

<span class="hljs-comment"># Check if the request was successful (status code 200)</span>
<span class="hljs-keyword">if</span> response.status_code == <span class="hljs-number">200</span>:
    <span class="hljs-comment"># Parse the HTML content using BeautifulSoup</span>
    soup = BeautifulSoup(response.text, <span class="hljs-string">'html.parser'</span>)

    <span class="hljs-comment"># Extract text from the header &lt;h1&gt;&lt;/h1&gt; tag</span>
    section_content = soup.find(<span class="hljs-string">'div'</span>, {<span class="hljs-string">'id'</span>: <span class="hljs-string">'section'</span>, <span class="hljs-string">'class'</span>: <span class="hljs-string">'ie_single_story_container'</span>, <span class="hljs-string">'data-env'</span>: <span class="hljs-string">'production'</span>})
    h1_content = section_content.find(<span class="hljs-string">'h1'</span>).text.strip()

    pcl_full_content = soup.find(<span class="hljs-string">'div'</span>, {<span class="hljs-string">'id'</span>: <span class="hljs-string">'pcl-full-content'</span>, <span class="hljs-string">'class'</span>: <span class="hljs-string">'story_details'</span>})

    <span class="hljs-comment"># Check if sub_header &lt;h2&gt;&lt;/h2&gt; tags are found before accessing its text attribute</span>
    h2_element = pcl_full_content.find(<span class="hljs-string">'h2'</span>)
    h2_content = h2_element.text.strip() <span class="hljs-keyword">if</span> h2_element <span class="hljs-keyword">else</span> <span class="hljs-string">""</span>

    <span class="hljs-comment"># Find all &lt;p&gt;&lt;/p&gt; tags and extract their text content</span>
    p_elements = pcl_full_content.find_all(<span class="hljs-string">'p'</span>)
    p_contents = [p.text.strip() <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> p_elements]

    <span class="hljs-comment"># Create a valid filename by replacing invalid characters with underscores</span>
    valid_filename = re.sub(<span class="hljs-string">r'[\/:*?"&lt;&gt;|]'</span>, <span class="hljs-string">'_'</span>, h1_content.lower()) + <span class="hljs-string">'.html'</span>

    <span class="hljs-comment"># Save as HTML file with the valid filename</span>
    <span class="hljs-keyword">with</span> open(valid_filename, <span class="hljs-string">'w'</span>, encoding=<span class="hljs-string">'utf-8'</span>) <span class="hljs-keyword">as</span> file:
        file.write(<span class="hljs-string">f"&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n\t&lt;title&gt;<span class="hljs-subst">{escape(h1_content)}</span>&lt;/title&gt;\n&lt;/head&gt;\n&lt;body&gt;\n"</span>)
        file.write(<span class="hljs-string">f"\t&lt;h1&gt;<span class="hljs-subst">{escape(h1_content)}</span>&lt;/h1&gt;\n"</span>)
        file.write(<span class="hljs-string">f"\t&lt;h2&gt;<span class="hljs-subst">{escape(h2_content)}</span>&lt;/h2&gt;\n"</span>)
        <span class="hljs-keyword">for</span> p_content <span class="hljs-keyword">in</span> p_contents:
            file.write(<span class="hljs-string">f"\t&lt;p&gt;<span class="hljs-subst">{escape(p_content)}</span>&lt;/p&gt;\n"</span>)
        file.write(<span class="hljs-string">"&lt;/body&gt;\n&lt;/html&gt;"</span>)

    print(<span class="hljs-string">f"Content saved to <span class="hljs-subst">{valid_filename}</span>"</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">"Failed to retrieve the web page. Status code:"</span>, response.status_code)
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Must have Extension For Edge Browser]]></title><description><![CDATA[Reader View: This extension enables you to remove clutter from webpages and read them in "Reader View" mode. You can switch between normal view and reader view by clicking the page-action button. Amazingly, this let me read articles from thehindu.com...]]></description><link>https://emeeran.dev/must-have-extension-for-edge-browser</link><guid isPermaLink="true">https://emeeran.dev/must-have-extension-for-edge-browser</guid><category><![CDATA[extension]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Fri, 19 Jan 2024 16:16:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705680815634/64875c1e-cc49-4ec1-b906-062295b49dc6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://microsoftedge.microsoft.com/addons/detail/reader-view/lpmbefndcmjoaepdpgmoonafikcalmnf"><strong>Reader View</strong></a>: This extension enables you to remove clutter from webpages and read them in "Reader View" mode. You can switch between normal view and reader view by clicking the page-action button. Amazingly, this let me read articles from thehindu.com, <a target="_blank" href="http://frontline.thehindu.com">thehindu.com</a>, time.com etc.</p>
<pre><code class="lang-xml">Features:
-&gt; Remove distraction
-&gt; Read in fullscreen mode
-&gt; Remove advertisements
-&gt; Save in HTML format
-&gt; Print document
-&gt; Read content using a powerful Text to Speech (TTS) engine
-&gt; Edit HTML content (live editor)
-&gt; Email document (with title, body, and reference to the original document)
-&gt; Correctly display mathematical formulas (MathJax equations are Supported)
-&gt; Highlight selected text
-&gt; Move to the next and previous pages
-&gt; Keyboard shortcuts for almost all actions
-&gt; Resize images on the design-mode
-&gt; Display DOI (Digital Object Identifier) details
-&gt; Display publish date

For more info please visit:
http://add0n.com/chrome-reader-view.html
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Python and BeautifulSoup4]]></title><description><![CDATA[Introduction
In the fast-paced digital age, harnessing data has become crucial to understand patterns, make informed decisions, or simply collect information. Web scraping or web harvesting, is a technique that helps us do just that. This blog post a...]]></description><link>https://emeeran.dev/python-and-beautifulsoup4</link><guid isPermaLink="true">https://emeeran.dev/python-and-beautifulsoup4</guid><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Mon, 08 Jan 2024 15:36:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_yMciiStJyY/upload/2f5d443026f96c1f06af03069fc264e3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>In the fast-paced digital age, harnessing data has become crucial to understand patterns, make informed decisions, or simply collect information. Web scraping or web harvesting, is a technique that helps us do just that. This blog post aims to delve into the utility and efficiency of web scraping, leveraging Python and BeautifulSoup4, an excellent duo for the purpose.</p>
<h3 id="heading-understanding-web-scraping">Understanding Web Scraping</h3>
<p>Web scraping is a method used to extract large amounts of data from websites. This information is collected and then exported into a format that is more useful for the user, be it a.csv file or database.</p>
<p>Ethical and legal considerations are crucial when scraping a website, as not all websites allow it, and the typical workflow involves requesting, receiving, and extracting information.</p>
<h3 id="heading-introduction-to-python-and-beautifulsoup4">Introduction to Python and BeautifulSoup4</h3>
<p>Python has become the go-to language for developers involved in web scraping due to its simplicity and robust libraries, such as Beautiful Soup 4.</p>
<p>Beautiful Soup 4 is a Python library designed for parsing HTML and XML documents, converting them into parse trees that can be used to extract data easily.</p>
<h3 id="heading-web-scraping-techniques-with-python-and-beautifulsoup4">Web Scraping Techniques with Python and BeautifulSoup4</h3>
<p>BeautifulSoup4's powerful functionalities allow users to locate HTML elements quickly. It's possible to extract data by drilling down into tags or directly accessing attributes.</p>
<p>However, there's more to it than this: Web scraping has evolved beyond static HTML pages. It's now exceedingly common to encounter dynamic web pages driven by AJAX. To handle these, one can supplement BeautifulSoup4 with libraries like Selenium or Requests-HTML.</p>
<p>Be aware that the script worked fine today may not work next month as web pages are frequently updated. In that case, you will have to modify your code to get it working again.</p>
<h3 id="heading-best-practices-for-effective-web-scraping">Best Practices for Effective Web Scraping</h3>
<p>First, write down in a note book the workflow:</p>
<ol>
<li><p>what data you want?</p>
</li>
<li><p>carefully inspect the web page html code</p>
</li>
<li><p>Identify the tag, class, and div where the data you need is located.</p>
</li>
<li><p>write the code in steps, often ensuring that the code works as expected.</p>
</li>
<li><p>comment on each function as a code block so that others can understand.</p>
</li>
</ol>
<h3 id="heading-web-scraping-project">Web Scraping Project</h3>
<ol>
<li><p>Create a project directory:<code>mkdir web_scraping</code></p>
</li>
<li><p>load the directory:<code>cd web_scraping</code></p>
</li>
<li><p>virtualize the project:<code>python -m venv .venv</code></p>
</li>
<li><p>activate the virtual environment:<code>.venv/scripts/activate</code></p>
<p> pip install modules:</p>
<pre><code class="lang-bash"> pip install requests BeautifulSoup4
</code></pre>
</li>
</ol>
<p>import libraries:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> re
</code></pre>
<p>Here is the complete code with comments for easy understanding of each piece of code and its function. It scrapes the text content of a page from <a target="_blank" href="https://www.marrowmatters.com/Aplastic-Anemia.html">https://www.marrowmatters.com/Aplastic-Anemia.html</a> and saves it as a markdown file.</p>
<pre><code class="lang-python"><span class="hljs-comment"># URL of the website</span>
url = <span class="hljs-string">"https://www.marrowmatters.com/Aplastic-Anemia.html"</span>

<span class="hljs-comment"># Send a GET request to the URL</span>
response = requests.get(url)

<span class="hljs-comment"># Check if the request was successful (status code 200)</span>
<span class="hljs-keyword">if</span> response.status_code == <span class="hljs-number">200</span>:
    <span class="hljs-comment"># Parse the HTML content using BeautifulSoup</span>
    soup = BeautifulSoup(response.content, <span class="hljs-string">'html.parser'</span>)

    <span class="hljs-comment"># Extract text within &lt;h1&gt;, &lt;h2&gt;, &lt;h3&gt;, and &lt;p&gt; tags</span>
    h1_texts = [h1.text.strip() <span class="hljs-keyword">for</span> h1 <span class="hljs-keyword">in</span> soup.find_all(<span class="hljs-string">'h1'</span>)]
    h2_and_p_texts = []
    h3_and_p_texts = []

    <span class="hljs-comment"># Iterate through each &lt;h2&gt; tag</span>
    <span class="hljs-keyword">for</span> h2 <span class="hljs-keyword">in</span> soup.find_all(<span class="hljs-string">'h2'</span>):
        h2_text = h2.text.strip()
        p_texts = []

        <span class="hljs-comment"># Find all &lt;p&gt; tags following the current &lt;h2&gt; tag</span>
        next_sibling = h2.find_next_sibling()
        <span class="hljs-keyword">while</span> next_sibling <span class="hljs-keyword">and</span> (next_sibling.name == <span class="hljs-string">'p'</span> <span class="hljs-keyword">or</span> next_sibling.name == <span class="hljs-string">'h3'</span>):
            <span class="hljs-keyword">if</span> next_sibling.name == <span class="hljs-string">'p'</span>:
                p_texts.append(next_sibling.text.strip())
            <span class="hljs-keyword">elif</span> next_sibling.name == <span class="hljs-string">'h3'</span>:
                h3_text = next_sibling.text.strip()
                h3_p_texts = []

                <span class="hljs-comment"># Find all &lt;p&gt; tags following the current &lt;h3&gt; tag</span>
                h3_next_sibling = next_sibling.find_next_sibling()
                <span class="hljs-keyword">while</span> h3_next_sibling <span class="hljs-keyword">and</span> h3_next_sibling.name == <span class="hljs-string">'p'</span>:
                    h3_p_texts.append(h3_next_sibling.text.strip())
                    h3_next_sibling = h3_next_sibling.find_next_sibling()

                h3_and_p_texts.append((h3_text, h3_p_texts))

            next_sibling = next_sibling.find_next_sibling()

        h2_and_p_texts.append((h2_text, p_texts))

    <span class="hljs-comment"># Determine the filename based on the first &lt;h1&gt; content</span>
    filename = h1_texts[<span class="hljs-number">0</span>] <span class="hljs-keyword">if</span> h1_texts <span class="hljs-keyword">else</span> <span class="hljs-string">"extracted_content"</span>

    <span class="hljs-comment"># Replace invalid characters in the filename</span>
    filename = re.sub(<span class="hljs-string">r'[\\/:*?"&lt;&gt;|]'</span>, <span class="hljs-string">'_'</span>, filename)

    <span class="hljs-comment"># Create the 'data_extracted' directory if it doesn't exist</span>
    output_directory = <span class="hljs-string">"data_extracted"</span>
    os.makedirs(output_directory, exist_ok=<span class="hljs-literal">True</span>)

    <span class="hljs-comment"># Save the extracted content as a Markdown file</span>
    output_path = os.path.join(output_directory, <span class="hljs-string">f"<span class="hljs-subst">{filename}</span>.md"</span>)
    <span class="hljs-keyword">with</span> open(output_path, <span class="hljs-string">"w"</span>, encoding=<span class="hljs-string">"utf-8"</span>) <span class="hljs-keyword">as</span> file:
        file.write(<span class="hljs-string">"# "</span> + <span class="hljs-string">"\n# "</span>.join(h1_texts) + <span class="hljs-string">"\n\n"</span>)

        <span class="hljs-keyword">for</span> h2_text, p_texts <span class="hljs-keyword">in</span> h2_and_p_texts:
            file.write(<span class="hljs-string">f"## <span class="hljs-subst">{h2_text}</span>\n"</span>)
            file.write(<span class="hljs-string">"\n"</span>.join(<span class="hljs-string">f"<span class="hljs-subst">{text}</span>\n"</span> <span class="hljs-keyword">for</span> text <span class="hljs-keyword">in</span> p_texts))

        <span class="hljs-keyword">for</span> h3_text, h3_p_texts <span class="hljs-keyword">in</span> h3_and_p_texts:
            file.write(<span class="hljs-string">f"### <span class="hljs-subst">{h3_text}</span>\n"</span>)
            file.write(<span class="hljs-string">"\n"</span>.join(<span class="hljs-string">f"<span class="hljs-subst">{text}</span>\n"</span> <span class="hljs-keyword">for</span> text <span class="hljs-keyword">in</span> h3_p_texts))

    print(<span class="hljs-string">f"Extraction and saving completed successfully. File saved as <span class="hljs-subst">{filename}</span>.md in <span class="hljs-subst">{output_directory}</span>"</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">f"Failed to retrieve the webpage. Status code: <span class="hljs-subst">{response.status_code}</span>"</span>)
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Web scraping with Python and BeautifulSoup4 is crucial for efficiently accessing needed information from unstructured data. Enjoy the power of data at your fingertips and be sure to use it responsibly.</p>
<p>Your web scraping journey now awaits. Remember, make sure to respect your data sources, understand what you need, and keep refining your code. With time and practice, you will become proficient at effectively harvesting data from the web. Remember, good web scraping isn’t fulfilled by having data but by having the right data. Happy web scraping!</p>
]]></content:encoded></item><item><title><![CDATA[Python Project Setup]]></title><description><![CDATA[Today I have prepared the workflow for my Python projects.

Win+R in the run box wt.exe to run terminal

Use cd command to navigate to the directory where you want your project directory to be placed

type mkdir your_project_name to create project fo...]]></description><link>https://emeeran.dev/python-project-setup</link><guid isPermaLink="true">https://emeeran.dev/python-project-setup</guid><category><![CDATA[workflow]]></category><dc:creator><![CDATA[Meeran E. Mandhini]]></dc:creator><pubDate>Sun, 10 Dec 2023 10:05:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/qWwpHwip31M/upload/d1f031c0ff4533049a063ee3fb654153.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today I have prepared the workflow for my Python projects.</p>
<ul>
<li><p><code>Win+R</code> in the run box <code>wt.exe</code> to run terminal</p>
</li>
<li><p>Use cd command to navigate to the directory where you want your project directory to be placed</p>
</li>
<li><p>type <code>mkdir your_project_name</code> to create project folder</p>
</li>
<li><p>type <code>python -m venv .venv</code> to create virtual environment using “venv” module or</p>
</li>
<li><p>type <code>conda create -n your_project_name python=3.x</code> to create a new environment using "conda" your_project_name.</p>
</li>
<li><p>type <code>.venv\\Scripts\\activate</code> on Windows or source <code>.env/bin/activate</code> on macOS/Linux. or <code>conda activate your_project_name</code></p>
</li>
<li><p>type <code>type nul&gt; your_project_</code><a target="_blank" href="http://name.py"><code>name.py</code></a> to create a new file for writing script</p>
</li>
<li><p>type <code>git init</code> to initialize a Git repository within your project directory.</p>
</li>
<li><p>type <code>git add</code> <a target="_blank" href="http://main.py"><code>main.py</code></a> or <code>git add *</code></p>
</li>
<li><p>type <code>git commit -m "Initial commit"</code> to commit the staged changes to the Git repository with a descriptive message.</p>
</li>
<li><p>Open the VS Code terminal or navigate back to the project directory in your terminal</p>
</li>
<li><p>type in vs code terminal<code>pip install -r requirements.txt</code> to install any required packages listed in a requirements.txt file within your project.</p>
</li>
<li><p>type <code>git push origin main</code> to push your local changes to the remote repository</p>
</li>
<li><p>type <code>git pull origin main</code> to pull any changes from the remote repository</p>
</li>
<li><p>after finishing work in your project, install pyinstaller <code>pip install pyinstalller</code></p>
</li>
<li><p>type <code>pip freeze &gt; requirements.txt</code> to create requirements file</p>
</li>
<li><p>type <code>pyinstaller your_</code><a target="_blank" href="http://script.py"><code>script.py</code></a> to convert your script as exe if you want</p>
</li>
<li><p>type <code>deactivate</code> to deactivate the virtual environment.</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>