diff --git a/.gitignore b/.gitignore index ca28c92..4d1dcde 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ build/ NOTES.md DRAFT*.md SCRATCH*.md +TODO.md notes/ drafts/ diff --git a/CLAUDE.md b/CLAUDE.md index 8b4f49d..e5e6b19 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -358,18 +358,42 @@ When releasing a new version: **Important:** Release notes should focus on **user-facing changes** since the last release tag, not internal bug fixes or refactoring. -#### What to Include: -- ✅ **New features** that users can see or use -- ✅ **Behavior changes** that affect user workflow -- ✅ **UI/UX improvements** (interactive modes, better output formatting) -- ✅ **Configuration changes** (new config options, migration requirements) -- ✅ **Breaking changes** (if any) +#### Release Notes Standards + +**General Principles:** +- Each release note document focuses **only on changes in that specific version** +- Previous versions' content is **not included** (except for reference like "Previous Version: v1.5.0") +- Users can read individual release notes to see what changed in each version +- This allows quick understanding of changes without reading through cumulative lists + +**Version Types:** +- **Bug fix releases (patch versions like 1.5.1):** Document only the bug fixes + - Focus on what was broken and how it's fixed + - Include impact and migration notes if needed + - Example: `RELEASE_NOTES_v1.5.1.md` (hierarchical category display fix) +- **Feature releases (minor versions like 1.6.0):** Document new features and enhancements + - New features, improvements, and user-visible changes + - Include migration guides if behavior changes + - Example: `RELEASE_NOTES_v1.5.0.md` (configurable bookmark shortcuts) +- **Major releases (2.0.0):** Document breaking changes and major updates + - Breaking changes, major architecture changes + - Migration guides required + - Deprecation notices + +**What to Include:** +- ✅ **User-facing changes** that affect behavior or features +- ✅ **Bug fixes** that users might notice or benefit from +- ✅ **Breaking changes** and migration guides +- ✅ **New features** and enhancements - ✅ **Documentation updates** (new guides, improved examples) -#### What to Exclude: +**What to Exclude:** +- ❌ **Internal refactoring** without functional changes +- ❌ **Code quality improvements** (unless they fix user-reported issues) +- ❌ **Test improvements** (unless they fix user-visible bugs) +- ❌ **Previous version content** (users can read older release notes separately) - ❌ **Internal bug fixes** that don't change user-visible behavior - ❌ **Code refactoring** without functional changes -- ❌ **Test improvements** (unless they fix user-reported issues) - ❌ **ShellCheck/formatting fixes** (code quality improvements) - ❌ **Internal tooling changes** (CI/CD, development workflow) @@ -421,6 +445,15 @@ git log v1.4.1..HEAD --oneline │ ├── search.sh # File search using find │ ├── isjsonvalid.sh # Centralized JSON validation engine │ └── ll.sh # ls -la shortcut +├── docs/ +│ ├── BOOKMARK_ALIAS_GUIDE.md # Comprehensive alias setup guide +│ ├── BOOKMARK_QUICK_REFERENCE.md # Quick reference for bookmark commands +│ ├── RELEASE_NOTES_v1.5.1.md # Release notes for v1.5.1 +│ ├── RELEASE_NOTES_v1.5.0.md # Release notes for v1.5.0 +│ ├── assets/ +│ │ └── MLH-Bookmark.gif # Bookmark demo animation +│ └── config/ +│ └── mlh.conf.example # Example configuration file └── tests/ ├── test # Main test runner framework (285+ tests total) ├── bookmark/ diff --git a/README.md b/README.md index 8fafc7b..04747ad 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ bash -c "$(curl -fsSL https://raw.githubusercontent.com/melihcelenk/MyLinuxHelpe || bash -c "$(wget -qO- https://raw.githubusercontent.com/melihcelenk/MyLinuxHelper/main/get-mlh.sh)" ``` -After installation, you can configure a custom shortcut for the `bookmark` command (e.g., `bm`) by editing `~/.mylinuxhelper/mlh.conf` and running `./setup.sh` again. See the [Bookmark Alias Guide](docs/BOOKMARK_ALIAS_GUIDE.md) for details. +After installation, you can configure a custom shortcut for the `bookmark` command (e.g., `bm`) by editing `~/.mylinuxhelper/mlh.conf` and setting `BOOKMARK_ALIAS=bm`, then running `setup.sh` in the installation directory to apply changes. ## 🚀 Usage @@ -76,12 +76,49 @@ mlh docker in web # Select container [1-3]: 1 ``` +> **💡 Note:** `mlh docker in` automatically detects if Docker requires sudo permissions and uses `sudo docker` when needed. You don't need to run `sudo mlh docker in` - just run `mlh docker in ` and it will handle sudo automatically if required. +> +> **⚠️ Troubleshooting:** If you get `mlh: command not found` when running `sudo mlh docker in`, it's because `sudo` resets the PATH. Solutions: +> - **Recommended:** Run without sudo: `mlh docker in ` (script handles sudo internally) +> - **Alternative:** Use `sudo -E env PATH=$PATH mlh docker in ` to preserve PATH +> - **Best long-term:** Add your user to the docker group: `sudo usermod -aG docker $USER` (then logout/login) + +--- + +### 📦 `linux` - Container Management + +Launch and manage isolated Linux containers quickly: + +![Linux & Docker Demo](docs/assets/MLH-Linux-Docker.gif) + +```bash +# Create ephemeral container (auto-removed on exit) +linux mycontainer + +# Create permanent container +linux -p mycontainer + +# Stop container +linux -s mycontainer + +# Delete container +linux -d mycontainer + +# Use different base image +linux -i debian:12 mycontainer + +# Bind mount directory +linux -m "$PWD:/workspace" -p mycontainer +``` + --- ### 🔖 `bookmark` - Quick Directory Bookmarks Save and jump to frequently used directories instantly: +![Bookmark Demo](docs/assets/MLH-Bookmark.gif) + > **💡 Configurable Shortcut:** Configure your preferred alias (e.g., `bm`, `fav`, `goto`) in `~/.mylinuxhelper/mlh.conf`: > ```bash > BOOKMARK_ALIAS=bm @@ -159,30 +196,6 @@ bookmark --help # or: bm --help --- -### 📦 `linux` - Container Management -Launch and manage isolated Linux containers quickly: -```bash -# Create ephemeral container (auto-removed on exit) -linux mycontainer - -# Create permanent container -linux -p mycontainer - -# Stop container -linux -s mycontainer - -# Delete container -linux -d mycontainer - -# Use different base image -linux -i debian:12 mycontainer - -# Bind mount directory -linux -m "$PWD:/workspace" -p mycontainer -``` - ---- - ### 📜 `mlh history` - Enhanced Command History View command history with dates, search, and filtering: ```bash @@ -335,8 +348,6 @@ search "*.conf" /etc ├── setup.sh # Main setup script (creates symlinks, configures PATH) ├── install.sh # Universal package installer (provides 'i' command) ├── README.md # User documentation with usage examples -├── CLAUDE.md # Development documentation -├── TODO.md # Feature roadmap and implementation checklist ├── LICENSE # Project license ├── plugins/ │ ├── mlh.sh # Interactive menu and command dispatcher @@ -352,29 +363,8 @@ search "*.conf" /etc │ ├── bookmark-alias.sh # Bookmark alias proxy (delegates to mlh-bookmark.sh) │ └── ll.sh # Shortcut for "ls -la" ├── docs/ -│ ├── BOOKMARK_ALIAS_GUIDE.md # Comprehensive alias setup guide -│ ├── BOOKMARK_QUICK_REFERENCE.md # Quick reference for bookmark commands -│ ├── RELEASE_NOTES_v1.5.0.md # Release notes for v1.5.0 │ └── config/ │ └── mlh.conf.example # Example configuration file -└── tests/ - ├── test # Main test runner (293 tests) - ├── bookmark/ - │ ├── test-mlh-bookmark.sh # 80 tests - Bookmark functionality - │ ├── test-bookmark-alias.sh # 28 tests - Alias configuration - │ └── test-bookmark-alias-integration.sh # 13 tests - Alias integration - ├── test-mlh-history.sh # 34 tests - Command history - ├── test-linux.sh # 15 tests - Container management - ├── test-mlh-json.sh # 18 tests - JSON operations - ├── test-mlh-docker.sh # 18 tests - Docker shortcuts - ├── test-mlh.sh # 20 tests - Main dispatcher - ├── test-search.sh # 16 tests - File search - ├── test-isjsonvalid.sh # 18 tests - JSON validation - ├── test-ll.sh # 10 tests - Directory listing - ├── test-mlh-about.sh # 12 tests - About page - ├── test-shellcheck.sh # 6 tests - Code quality validation - ├── test-current-session.sh # 1 test - Session history - └── test-time-debug.sh # 4 tests - Time parsing ``` ## 🧪 Testing diff --git a/TODO.md b/TODO.md deleted file mode 100644 index a024600..0000000 --- a/TODO.md +++ /dev/null @@ -1,487 +0,0 @@ -# MyLinuxHelper - Bookmark Feature Improvements - -Bu dosya bookmark özelliğini nasıl geliştirebileceğimize dair önerileri içerir. - ---- - -## 🎯 Usability İyileştirmeleri (High Priority) - -### 1. Kısa Komut Alias'ı - `bm` - -**Problem**: `bookmark` yazmak uzun, hızlı kullanımda yavaşlatıyor. - -**Önerilen Çözüm**: - -```bash -# bm alias'ı ekle (bookmark'un kısa hali) -bm . # bookmark . ile aynı -bm list # bookmark list ile aynı -bm -l # bookmark list -i (interactive) -bm -s myapp # bookmark myapp (jump - "s" = switch) -bm -a myapp # bookmark . -n myapp (add with name) -``` - -**Implementation**: - -- `setup.sh`: `bm` symlink'i ekle -- `plugins/bm.sh`: Yeni script, argümanları parse edip `mlh-bookmark.sh`'a delege et -- Flag-based shortcuts ekle (-l, -s, -a) - -**Impact**: ⭐⭐⭐⭐⭐ (Günlük kullanımda büyük fark) - ---- - -### 2. Otomatik Git Repo Detection - -**Problem**: Git repo'larda çalışırken, root dizini bulmak için manuel bookmark kaydetmek gerekiyor. - -**Önerilen Çözüm**: - -```bash -# Git repo root'unu otomatik bookmark'la -bookmark . -g # Git root'unu kaydet -bookmark . -n myrepo -g # Git root'unu isimle kaydet - -# Otomatik kategori: git/ -# Örnek: projects/myrepo → git/myrepo -``` - -**Implementation**: - -- `mlh-bookmark.sh`: `-g` flag ekle -- `git rev-parse --show-toplevel` ile repo root bul -- Otomatik kategori: `git/` - -**Impact**: ⭐⭐⭐⭐ (Developer'lar için çok kullanışlı) - ---- - -### 3. Fuzzy Finder Integration (fzf) - -**Problem**: Interactive mode güzel ama büyük listelerde arama yok. - -**Önerilen Çözüm**: - -```bash -# fzf ile fuzzy search -bookmark list -f # fzf ile filtrele -bm -f # Kısa hali - -# Preview window ile path göster -# Real-time filtering -# Multi-select destekle (birden fazla bookmark'ı sil/edit) -``` - -**Implementation**: - -- `fzf` varsa kullan, yoksa fallback olarak mevcut interactive mode -- Preview window: `bookmark list` output'u göster -- Multi-select ile toplu işlem - -**Impact**: ⭐⭐⭐⭐⭐ (Power user'lar için harika) - ---- - -### 4. Tab Completion - -**Problem**: Bookmark isimleri ve kategorileri tab ile complete edilemiyor. - -**Önerilen Çözüm**: - -```bash -# Bash completion ekle -bookmark my # myapp, myproject gibi isimleri complete et -bookmark list pro # projects kategorisini complete et -bm -s my # Jump için bookmark isimlerini complete et -``` - -**Implementation**: - -- `completions/bookmark.bash`: Bash completion script -- `setup.sh`: Completion'ı yükle -- JSON'dan bookmark isimlerini ve kategorileri parse et - -**Impact**: ⭐⭐⭐⭐ (UX için önemli) - ---- - -## 🚀 Feature Enhancements (Medium Priority) - -### 5. Frecency-Based Sorting - -**Problem**: En çok/son kullanılan bookmark'lar listenin en üstünde değil. - -**Önerilen Çözüm**: - -```bash -# Frequency + Recency = Frecency -bookmark list # Frecency'ye göre sırala (default) -bookmark list -c # Created time'a göre sırala -bookmark list -a # Alphabetical sırala -bookmark list -f # Frequency'ye göre sırala -``` - -**Implementation**: - -- JSON'a `access_count` ve `last_accessed` zaten var -- Frecency score hesapla: `score = frequency * decay_factor(time_since_access)` -- Liste çıktısında sıralama seçeneği ekle - -**Impact**: ⭐⭐⭐⭐ (Kullanım kolaylığı artar) - ---- - -### 6. Bookmark Descriptions/Notes - -**Problem**: Bookmark ismi yeterli bilgi vermiyor bazen. - -**Önerilen Çözüm**: - -```bash -# Description ekle -bookmark . -n myapp -d "Production API server" -bookmark edit myapp # Description da düzenlenebilir - -# Liste görünümünde description göster -bookmark list -# Output: -# [myapp] /home/user/projects/myapp -# → Production API server -``` - -**Implementation**: - -- JSON'a `description` field ekle -- `save_named_bookmark()`: `-d` flag parse et -- Liste çıktısında description'ı GRAY renkte göster - -**Impact**: ⭐⭐⭐ (Nice-to-have, büyük workspace'lerde kullanışlı) - ---- - -### 7. Bookmark Export/Import - -**Problem**: Bookmark'ları başka makineye taşımak zor. - -**Önerilen Çözüm**: - -```bash -# Export -bookmark export bookmarks.json # Tüm bookmark'ları export et -bookmark export -c projects out.json # Sadece bir kategoriyi export et - -# Import -bookmark import bookmarks.json # Import et (mevcut bookmark'ları koru) -bookmark import -r bookmarks.json # Replace (mevcut bookmark'ları sil) -``` - -**Implementation**: - -- Export: JSON dosyasını kopyala (opsiyonel: sadece named bookmarks) -- Import: JSON merge et, duplicate check yap -- `-r` flag ile replace modu - -**Impact**: ⭐⭐⭐ (Team/multi-machine setup için önemli) - ---- - -### 8. Bookmark Sync (Cloud/Git) - -**Problem**: Bookmark'lar sadece lokal, başka makinede yok. - -**Önerilen Çözüm**: - -```bash -# Git sync -bookmark sync init # Git repo oluştur (~/.mylinuxhelper) -bookmark sync push # Commit + push -bookmark sync pull # Pull + merge - -# Otomatik sync -bookmark sync auto on # Her save/edit/delete'de otomatik push -``` - -**Implementation**: - -- `~/.mylinuxhelper/.git` klasörü oluştur -- `bookmark sync`: Git operasyonları (add, commit, push, pull) -- Conflict resolution: Last-write-wins veya interactive merge - -**Impact**: ⭐⭐⭐⭐ (Multi-device kullanıcılar için killer feature) - ---- - -### 9. Bookmark Aliases - -**Problem**: Bazı bookmark'lara birden fazla isimle erişmek istiyoruz. - -**Önerilen Çözüm**: - -```bash -# Alias ekle -bookmark alias prod myapp # prod -> myapp alias'ı -bookmark prod # myapp'e gider - -# Alias listesi -bookmark aliases # Tüm alias'ları göster -bookmark alias rm prod # Alias'ı sil -``` - -**Implementation**: - -- JSON'a `aliases` array ekle: `["prod", "production"]` -- Jump fonksiyonunda alias check ekle -- Liste çıktısında alias'ları göster: `[myapp] (aliases: prod, production)` - -**Impact**: ⭐⭐⭐ (Nice-to-have, isim kolaylığı) - ---- - -## 🎨 UI/UX İyileştirmeleri (Low Priority) - -### 10. Kategori Renklendirme - -**Problem**: Interactive mode'da kategoriler renksiz, ayırt etmek zor. - -**Önerilen Çözüm**: - -```bash -# Kategori başına farklı renk -# projects/ → GREEN -# git/ → CYAN -# tools/ → YELLOW -# work/ → BLUE -``` - -**Implementation**: - -- Kategori ismine göre hash hesapla -- Hash'den renk seç (6-8 farklı renk) -- Interactive mode ve list çıktısında uygula - -**Impact**: ⭐⭐ (Görsel iyileştirme) - ---- - -### 11. Bookmark Preview - -**Problem**: Bookmark seçerken içinde ne olduğu görünmüyor. - -**Önerilen Çözüm**: -```bash -# Interactive mode'da preview -bookmark list -i -p # Preview window ile - -# Preview gösterir: -# - Directory tree (ls -la) -# - Git status (eğer git repo ise) -# - Dosya sayısı, toplam boyut -``` - -**Implementation**: - -- fzf preview window kullan (fzf varsa) -- Split screen: Sol taraf liste, sağ taraf preview -- Preview command: `ls -la $path | head -20` - -**Impact**: ⭐⭐⭐ (fzf ile birlikte güçlü) - ---- - -### 12. CD History Tracking (pushd/popd gibi) - -**Problem**: Bookmark sisteminden bağımsız, geçici cd history tutulmuyor. - -**Önerilen Çözüm**: - -```bash -# CD history -bookmark history # Son 10 CD'yi göster (stack) -bookmark back # Önceki dizine dön (popd gibi) -bookmark forward # İleri git (forward stack) - -# Alias -bm -b # back -bm -F # forward -bm -h # history -``` - -**Implementation**: - -- Wrapper function'da her CD'yi stack'e ekle -- Stack file: `~/.mylinuxhelper/cd_history.json` -- Max 50 entry, LIFO -- Back/forward stack ile bidirectional gezinme - -**Impact**: ⭐⭐⭐⭐ (Browser gibi navigation) - ---- - -## 🔧 Code Organization & Refactoring - -### 13. Modüler Yapı - -**Öneri**: - -```bash -plugins/ -├── mlh-bookmark.sh # Main entry point -├── mlh-bookmark/ -│ ├── core.sh # Core functions (save, jump, remove) -│ ├── interactive.sh # Interactive mode -│ ├── search.sh # Find, fuzzy search -│ ├── category.sh # Category management -│ ├── git.sh # Git integration -│ ├── sync.sh # Cloud/Git sync -│ └── completion.bash # Tab completion -``` - -**Benefit**: - -- Her modül bağımsız test edilebilir -- Code reusability artar -- Maintenance kolaylaşır - ---- - -### 14. Config System - -**Öneri**: - -```bash -# Kullanıcı config -~/.mylinuxhelper/bookmark-config.json - -{ - "max_unnamed": 10, - "default_sort": "frecency", - "auto_git_detect": true, - "enable_sync": false, - "sync_remote": "git@github.com:user/bookmarks.git", - "colors": { - "category": "auto", - "bookmark": "green" - } -} - -# Config komutları -bookmark config set max_unnamed 20 -bookmark config get max_unnamed -bookmark config list -``` - -**Benefit**: - -- Kullanıcı tercihleri -- Değişiklik için kod değiştirmeye gerek yok - ---- - -### 15. Plugin API - -**Öneri**: - -```bash -# Bookmark event hooks -~/.mylinuxhelper/hooks/bookmark-post-save.sh -~/.mylinuxhelper/hooks/bookmark-post-jump.sh - -# Hook çağrılır: -# $1 = event (save, jump, delete) -# $2 = bookmark name/number -# $3 = path - -# Örnek kullanım: -# - Slack'e notification gönder -# - Log file'a yaz -# - External tool ile entegre et -``` - -**Benefit**: - -- Extensibility -- Custom workflows -- Community plugins - ---- - -## 📊 Test Coverage Genişletme - -### 16. Yeni Test Senaryoları - -**Eklenecek Testler**: - -- [ ] fzf integration tests -- [ ] Tab completion tests -- [ ] Git integration tests -- [ ] Sync tests (mock git remote) -- [ ] Frecency sorting tests -- [ ] Alias tests -- [ ] Export/import tests -- [ ] Config system tests -- [ ] Hook system tests - -**Target**: 100+ test (şu an 80) - ---- - -## 🏆 Priority Matrix - -| Özellik | Impact | Effort | Priority | -|------------------|--------|--------|----------| -| `bm` alias | ⭐⭐⭐⭐⭐ | Low | 🔥 HIGH | -| fzf integration | ⭐⭐⭐⭐⭐ | Medium | 🔥 HIGH | -| Tab completion | ⭐⭐⭐⭐ | Medium | 🔥 HIGH | -| Git integration | ⭐⭐⭐⭐ | Medium | ⚡ MEDIUM | -| Frecency sorting | ⭐⭐⭐⭐ | Low | ⚡ MEDIUM | -| CD history | ⭐⭐⭐⭐ | Medium | ⚡ MEDIUM | -| Bookmark sync | ⭐⭐⭐⭐ | High | ⚡ MEDIUM | -| Export/import | ⭐⭐⭐ | Low | ⚡ MEDIUM | -| Descriptions | ⭐⭐⭐ | Low | 💤 LOW | -| Aliases | ⭐⭐⭐ | Medium | 💤 LOW | -| Renklendirme | ⭐⭐ | Low | 💤 LOW | -| Preview | ⭐⭐⭐ | Medium | 💤 LOW | - ---- - -## 🎯 Implementation Roadmap - -### Phase 4: Usability (Sprint 1-2) - -- [ ] `bm` alias ve flag shortcuts -- [ ] Tab completion -- [ ] Frecency-based sorting - -### Phase 5: Integration (Sprint 3-4) - -- [ ] fzf integration -- [ ] Git repo detection -- [ ] CD history tracking - -### Phase 6: Advanced (Sprint 5-6) - -- [ ] Export/import -- [ ] Bookmark sync -- [ ] Config system - -### Phase 7: Polish (Sprint 7+) - -- [ ] Descriptions/notes -- [ ] Aliases -- [ ] Preview mode -- [ ] Modüler refactoring - ---- - -**Son Güncelleme**: 2025-11-07 -**Status**: ✅ Phase 1-3 Complete, Phase 4+ Planning - ---- - -## 📝 Notes - -- Her yeni özellik için **test-driven** yaklaşım -- Backward compatibility kır**ma** -- Breaking change gerekirse version bump (v2.0) -- Her feature için dokümantasyon güncelle -- Community feedback al (GitHub issues) diff --git a/docs/BOOKMARK_QUICK_REFERENCE.md b/docs/BOOKMARK_QUICK_REFERENCE.md index 3d0b6e4..34b2b6f 100644 --- a/docs/BOOKMARK_QUICK_REFERENCE.md +++ b/docs/BOOKMARK_QUICK_REFERENCE.md @@ -2,6 +2,8 @@ Quick directory bookmarking and navigation system. +![Bookmark Demo](assets/MLH-Bookmark.gif) + ## 🚀 Quick Start ### Basic Operations diff --git a/docs/RELEASE_NOTES_v1.5.0.md b/docs/RELEASE_NOTES_v1.5.0.md deleted file mode 100644 index 14aba23..0000000 --- a/docs/RELEASE_NOTES_v1.5.0.md +++ /dev/null @@ -1,241 +0,0 @@ -# MyLinuxHelper v1.5.0 - Release Notes - -**Release Date:** 2025-11-09 -**Previous Version:** v1.4.1 - ---- - -## 🎉 What's New in v1.5.0 - -### ⭐ Major Feature: Configurable Bookmark Shortcuts (Phase 4) - -We've added a highly requested usability feature - **configurable shortcut aliases** for the `bookmark` command! - -#### Key Highlights: - -- **Custom Shortcuts:** Create your own shortcut/alias for the `bookmark` command (e.g., `bm`, `fav`, `goto`) -- **Simple Setup:** Just add `BOOKMARK_ALIAS=bm` to `~/.mylinuxhelper/mlh.conf` and run `./setup.sh` -- **Smart Conflict Detection:** Setup warns if your chosen alias conflicts with existing commands -- **Dynamic Help:** Help text automatically shows examples using your configured shortcut -- **Full Feature Support:** All bookmark features work with the alias - it's just a convenient shortcut - -#### Example Usage: - -```bash -# After setup, if you chose 'bm': -bm . # Save current directory -bm list # Interactive list (NEW: default behavior) -bm myproject # Jump to named bookmark -bm --help # Help shows 'bm' in examples -``` - -#### Configuration: - -- **Config file:** `~/.mylinuxhelper/mlh.conf` (new centralized config for all MLH settings) -- **Format:** `BOOKMARK_ALIAS=bm` -- **Example:** See `docs/config/mlh.conf.example` in repository -- Change anytime by editing the config file and re-running `./setup.sh` -- Set to empty string to disable the shortcut - ---- - -### 🚀 Improved Default Behavior - -#### Interactive List by Default -- **`bookmark list` now shows interactive TUI by default** (was non-interactive) -- **Faster workflow:** No need to add `-i` flag anymore -- **New flag:** Use `bookmark list -n` for non-interactive simple output -- **Filter support:** `bookmark list ` opens interactive list filtered by category - -**Before:** -```bash -bookmark list # Non-interactive output -bookmark list -i # Interactive TUI (had to specify) -``` - -**Now:** -```bash -bookmark list # Interactive TUI (default - faster!) -bookmark list -n # Non-interactive output (when needed) -``` - ---- - - -### ✨ Enhancements - -#### Unified Configuration System -- **New:** Centralized `mlh.conf` for all MLH configuration -- **Better organized:** Clear sections for different features -- **Future-ready:** All future MLH settings will use this file -- **Example provided:** See `docs/config/mlh.conf.example` - -#### Bookmark System -- **Improved:** Hierarchical category display in list view - - Categories now display with proper indentation: `📂 projects/linux` - - Subcategories are shown nested under parent categories -- **Improved:** Path validation warnings with better visual indicators (⚠ symbol) -- **Improved:** Better handling of command name conflicts during bookmark creation - -#### Testing & Quality -- **Reorganized:** Bookmark tests moved to `tests/bookmark/` subdirectory -- **Added:** Comprehensive test suite for bookmark alias feature (41 new tests) - - Config file handling (28 tests) - - Dynamic help display - - Alias validation and conflict detection - - Integration tests (13 tests) -- **Added:** Automated ShellCheck validation test suite - - Validates all shell scripts for code quality - - Integrated into main test runner -- **Total Test Count:** Now **293 tests** (was 246) - - bookmark/mlh-bookmark: 80 tests - - bookmark/bookmark-alias: 28 tests - - bookmark/bookmark-alias-integration: 13 tests - - shellcheck: 6 tests (validates code quality) - - All other test suites: stable -- **Code Quality:** All scripts pass ShellCheck validation -- **Formatting:** All scripts formatted with shfmt for consistency - ---- - -### 📚 Documentation Updates - -#### New Documentation -- **`docs/config/mlh.conf.example`:** Template configuration file with all options documented -- **`docs/BOOKMARK_ALIAS_GUIDE.md`:** Comprehensive alias setup and troubleshooting guide (moved from old location) - -#### Updated Documentation -- **`CLAUDE.md`:** - - Updated with centralized config system architecture - - New test structure documentation - - Bookmark alias implementation details -- **`README.md`:** - - Updated bookmark examples to show new default behavior - - Config system reference - ---- - -## 📊 Test Results - -``` -Total tests: 293 -Passed: 288 (98.3%) -Skipped: 5 (shellcheck tests when shellcheck not available) -Failed: 0 - -Test Coverage by Component: -✅ bookmark/bookmark-alias-integration: 13/13 passed -✅ bookmark/bookmark-alias: 28/28 passed -✅ bookmark/mlh-bookmark: 80/80 passed -✅ current-session: 1/1 passed -✅ isjsonvalid: 18/18 passed -✅ linux: 15/15 passed -✅ ll: 10/10 passed -✅ mlh-about: 12/12 passed -✅ mlh-docker: 18/18 passed -✅ mlh-history: 34/34 passed -✅ mlh-json: 18/18 passed -✅ mlh: 20/20 passed -✅ search: 16/16 passed -✅ shellcheck: 6/6 passed (when shellcheck available) -✅ time-debug: 4/4 passed -``` - -**Note:** All tests pass when required tools (shellcheck, tmux) are available. Test suite now includes automated ShellCheck validation. - ---- - -## 🔄 Migration Guide - -### Upgrading from v1.4 or v1.4.1 - -1. **Run Update:** - ```bash - mlh update - # Or manually: - bash -c "$(curl -fsSL https://raw.githubusercontent.com/melihcelenk/MyLinuxHelper/main/get-mlh.sh)" - ``` - -2. **Configure Alias (Optional):** - ```bash - # Create/edit config file - mkdir -p ~/.mylinuxhelper - nano ~/.mylinuxhelper/mlh.conf - - # Add your preferred alias - BOOKMARK_ALIAS=bm - - # Re-run setup to apply changes - cd ~/.mylinuxhelper - ./setup.sh - - # Reload shell - source ~/.bashrc - ``` - -### No Breaking Changes - -- All existing `bookmark` commands continue to work exactly as before -- **New default:** `bookmark list` now shows interactive menu (faster workflow!) -- Existing bookmarks in `~/.mylinuxhelper/bookmarks.json` are fully compatible -- Shortcut/alias feature is completely optional - ---- - -## 📦 Installation - -### New Installation -```bash -bash -c "$(curl -fsSL https://raw.githubusercontent.com/melihcelenk/MyLinuxHelper/main/get-mlh.sh)" \ -|| bash -c "$(wget -qO- https://raw.githubusercontent.com/melihcelenk/MyLinuxHelper/main/get-mlh.sh)" -``` - -### Update Existing Installation -```bash -mlh update -``` - ---- - -## 🎯 Roadmap for v1.6 - -Based on the TODO.md and GitHub issues, future enhancements being considered: - -- **Centralized config system expansion** - Additional settings for history, docker, etc. -- **fzf integration** for fuzzy finding bookmarks -- **Tab completion** for bookmark names and categories -- **Git repo detection** - automatically bookmark git repository roots -- **Frecency-based sorting** - most frequently/recently used bookmarks first -- **Bookmark export/import** for sharing across machines -- **Bookmark sync** via Git for multi-device workflows - ---- - -## 🐛 Known Issues - -1. **ShellCheck Tests:** ShellCheck validation tests are skipped if `shellcheck` is not installed. Install with `sudo apt-get install shellcheck` (or use Docker) to run these tests. -2. **WSL Compatibility:** Interactive mode works but may require `/dev/tty` fallback in some WSL configurations. This is automatically handled by the code. - ---- - -## 📞 Support & Feedback - -- **Issues:** https://github.com/melihcelenk/MyLinuxHelper/issues -- **Discussions:** https://github.com/melihcelenk/MyLinuxHelper/discussions -- **Documentation:** See `README.md` and `CLAUDE.md` in the repository - ---- - -## 🙏 Acknowledgments - -Special thanks to all contributors and users who provided feedback on the bookmark system and requested the alias feature! - ---- - -**Full Changelog:** https://github.com/melihcelenk/MyLinuxHelper/compare/v1.4.1...v1.5.0 - ---- - -## 📝 Note on v1.4.1 - -v1.4.1 was an internal release with code quality improvements (ShellCheck fixes, formatting). This release (v1.5.0) includes all user-facing features and enhancements since v1.4.1. diff --git a/docs/assets/MLH-Bookmark.gif b/docs/assets/MLH-Bookmark.gif new file mode 100644 index 0000000..4f23055 Binary files /dev/null and b/docs/assets/MLH-Bookmark.gif differ diff --git a/docs/assets/MLH-Linux-Docker.gif b/docs/assets/MLH-Linux-Docker.gif new file mode 100644 index 0000000..e08c30c Binary files /dev/null and b/docs/assets/MLH-Linux-Docker.gif differ diff --git a/get-mlh.sh b/get-mlh.sh index c71520b..7c626f7 100755 --- a/get-mlh.sh +++ b/get-mlh.sh @@ -64,12 +64,27 @@ download_repo() { if command -v git >/dev/null 2>&1; then if [ -d "${INSTALL_DIR}/.git" ]; then green "Updating repo (git pull)…" - git -C "${INSTALL_DIR}" fetch --all --depth=1 - git -C "${INSTALL_DIR}" checkout "${REPO_BRANCH}" - git -C "${INSTALL_DIR}" reset --hard "origin/${REPO_BRANCH}" + # First, fetch all branches to ensure we have the remote branch + git -C "${INSTALL_DIR}" fetch origin 2>/dev/null || true + # Try to checkout the branch + if git -C "${INSTALL_DIR}" checkout "${REPO_BRANCH}" 2>/dev/null; then + # Branch exists locally, pull latest changes + git -C "${INSTALL_DIR}" pull origin "${REPO_BRANCH}" 2>/dev/null || + git -C "${INSTALL_DIR}" reset --hard "origin/${REPO_BRANCH}" 2>/dev/null + elif git -C "${INSTALL_DIR}" checkout -b "${REPO_BRANCH}" "origin/${REPO_BRANCH}" 2>/dev/null; then + # Successfully created tracking branch + : + else + # Branch doesn't exist, remove and re-clone + green "Branch not found, re-cloning repository…" + rm -rf "${INSTALL_DIR}" + git clone --depth=1 --branch "${REPO_BRANCH}" "${REPO_GIT_URL}" "${INSTALL_DIR}" 2>/dev/null || + git clone --branch "${REPO_BRANCH}" "${REPO_GIT_URL}" "${INSTALL_DIR}" + fi else green "Cloning repo (git)…" - git clone --depth=1 --branch "${REPO_BRANCH}" "${REPO_GIT_URL}" "${INSTALL_DIR}" + git clone --depth=1 --branch "${REPO_BRANCH}" "${REPO_GIT_URL}" "${INSTALL_DIR}" 2>/dev/null || + git clone --branch "${REPO_BRANCH}" "${REPO_GIT_URL}" "${INSTALL_DIR}" fi else green "Downloading repo (tarball)…" @@ -77,13 +92,27 @@ download_repo() { mkdir -p "${INSTALL_DIR}.tmp" local dlr dlr="$(ensure_downloader)" + # Use REPO_TARBALL_URL variable (already defined above) if [ "$dlr" = "curl" ]; then curl -fsSL "${REPO_TARBALL_URL}" | tar -xz -C "${INSTALL_DIR}.tmp" else wget -qO- "${REPO_TARBALL_URL}" | tar -xz -C "${INSTALL_DIR}.tmp" fi rm -rf "${INSTALL_DIR}" - mv "${INSTALL_DIR}.tmp/${REPO_NAME}-${REPO_BRANCH}" "${INSTALL_DIR}" + # Tarball extracts to directory with branch name + if [ -d "${INSTALL_DIR}.tmp/${REPO_NAME}-${REPO_BRANCH}" ]; then + mv "${INSTALL_DIR}.tmp/${REPO_NAME}-${REPO_BRANCH}" "${INSTALL_DIR}" + else + # Find the extracted directory (fallback) + local extracted_dir + extracted_dir=$(find "${INSTALL_DIR}.tmp" -mindepth 1 -maxdepth 1 -type d | head -1) + if [ -n "$extracted_dir" ]; then + mv "$extracted_dir" "${INSTALL_DIR}" + else + echo "Error: Could not find extracted directory" + exit 1 + fi + fi rm -rf "${INSTALL_DIR}.tmp" fi } @@ -96,6 +125,47 @@ ensure_local_bin_on_path() { grep -Fq "$line" "$PROFILE" 2>/dev/null || echo "$line" >>"$PROFILE" } +cleanup_unnecessary_files() { + green "Cleaning up unnecessary files…" + + # List of files/directories to remove (user doesn't need these) + # NOTE: .git is NOT removed - it's needed for git pull/update functionality + local cleanup_items=( + "tests" + "CLAUDE.md" + "docs/BOOKMARK_ALIAS_GUIDE.md" + "docs/BOOKMARK_QUICK_REFERENCE.md" + "docs/assets" + ".github" + "TODO.md" + ".gitignore" + ) + + # Keep docs/config/mlh.conf.example (needed for setup.sh) + # Keep README.md (useful for users) + # Keep LICENSE (required) + # Keep .git (needed for git pull/update functionality) + + for item in "${cleanup_items[@]}"; do + local item_path="${INSTALL_DIR}/${item}" + if [ -e "$item_path" ]; then + rm -rf "$item_path" + echo " Removed: $item" + fi + done + + # Clean up docs directory if it's now empty (except config/) + if [ -d "${INSTALL_DIR}/docs" ]; then + # Check if docs only contains config/ directory + local docs_contents + docs_contents=$(find "${INSTALL_DIR}/docs" -mindepth 1 -maxdepth 1 ! -name "config" | wc -l) + if [ "$docs_contents" -eq 0 ]; then + # Keep docs/config, but we can leave docs/ as is since it only has config/ + : + fi + fi +} + run_repo_setup() { green "Running repository setup…" chmod +x "${INSTALL_DIR}/setup.sh" || true @@ -105,6 +175,8 @@ run_repo_setup() { main() { green "Installing ${REPO_NAME} into ${INSTALL_DIR}" download_repo + # Cleanup unnecessary files (both git and tarball methods include development files) + cleanup_unnecessary_files ensure_local_bin_on_path run_repo_setup green "Done. Try:" diff --git a/plugins/mlh-bookmark.sh b/plugins/mlh-bookmark.sh index 5d55776..add55cb 100755 --- a/plugins/mlh-bookmark.sh +++ b/plugins/mlh-bookmark.sh @@ -61,7 +61,7 @@ check_jq() { if ! command -v jq >/dev/null 2>&1; then echo -e "${RED}Error: jq is required for bookmark functionality${NC}" >&2 echo -e "${YELLOW}Install with: sudo apt install jq${NC}" >&2 - echo -e "${YELLOW}Or run: mlh install jq${NC}" >&2 + echo -e "${YELLOW}Or run: i jq${NC}" >&2 exit 1 fi } @@ -666,7 +666,6 @@ interactive_list() { local selected=0 local total=${#entries[@]} - local current_category="" # Display function show_menu() { @@ -681,25 +680,62 @@ interactive_list() { echo "└─────────────────────────────────────────────────────────────────┘" echo "" - current_category="" + local current_category="" + local prev_category_parts=() for i in "${!entries[@]}"; do IFS='|' read -r type id_or_name path category created <<<"${entries[$i]}" - # Show category header + # Show category header (hierarchical) if [ "$type" = "named" ] && [ "$category" != "$current_category" ]; then - current_category="$category" - echo -e "${BLUE}📂 $category${NC}" + # Handle Uncategorized specially + if [ -z "$category" ] || [ "$category" = "null" ]; then + if [ "$current_category" != "Uncategorized" ]; then + current_category="Uncategorized" + prev_category_parts=() + echo -e "${GRAY}📁 Uncategorized${NC}" + fi + else + current_category="$category" + # Split category by / + IFS='/' read -ra category_parts <<<"$category" + + # Print each level of hierarchy that's new + for level in "${!category_parts[@]}"; do + # Check if this level is new compared to previous category + if [ "$level" -ge "${#prev_category_parts[@]}" ] || [ "${category_parts[$level]}" != "${prev_category_parts[$level]:-}" ]; then + # Build indent for this level + local indent="" + for ((j = 0; j < level; j++)); do + indent=" $indent" + done + echo -e "${indent}${BLUE}📂 ${category_parts[$level]}${NC}" + fi + done + + # Update prev_category_parts for next iteration + prev_category_parts=("${category_parts[@]}") + fi elif [ "$type" = "unnamed" ] && [ "$current_category" != "📌 Recent (Unnamed)" ]; then current_category="📌 Recent (Unnamed)" + prev_category_parts=() echo "" echo -e "${BLUE}$current_category${NC}" fi + # Calculate indent for bookmark based on category depth + local bookmark_indent="" + if [ "$type" = "named" ] && [ -n "$category" ] && [ "$category" != "null" ]; then + IFS='/' read -ra category_parts <<<"$category" + for ((j = 0; j < ${#category_parts[@]}; j++)); do + bookmark_indent=" $bookmark_indent" + done + fi + # Show bookmark if [ "$i" -eq "$selected" ]; then - echo -en "${GREEN} ▶ " + echo -en "${bookmark_indent}${GREEN}▶ " else - echo -n " " + echo -n "${bookmark_indent} " fi if [ "$type" = "named" ]; then diff --git a/plugins/mlh-docker.sh b/plugins/mlh-docker.sh index 94f0d5b..fa226bd 100755 --- a/plugins/mlh-docker.sh +++ b/plugins/mlh-docker.sh @@ -11,7 +11,9 @@ # Examples: # mlh docker in mycontainer # Enter container with 'mycontainer' in name -set -euo pipefail +set -uo pipefail +# Note: We don't use 'set -e' because we need to handle errors manually +# for proper sudo detection and error messages print_help() { cat <<'EOF' @@ -65,7 +67,29 @@ case "$COMMAND" in ;; in) # Check if docker is available (only for actual commands) - if ! command -v docker >/dev/null 2>&1; then + # Try to find docker in common locations if not in PATH (for sudo usage) + DOCKER_BIN="" + USE_SUDO=0 + + # Force use of system Docker daemon at /var/run/docker.sock + # Docker Desktop socket may be configured but not running + # Check if system Docker socket exists, if so use it + if [ -S "/var/run/docker.sock" ]; then + # Use system Docker daemon explicitly + export DOCKER_HOST="unix:///var/run/docker.sock" + elif [ -n "${DOCKER_HOST:-}" ] && echo "$DOCKER_HOST" | grep -q "docker-desktop\|\.docker/desktop"; then + # Docker Desktop socket configured but may not be running + # Unset it to let Docker client use default + unset DOCKER_HOST + fi + + if command -v docker >/dev/null 2>&1; then + DOCKER_BIN="docker" + elif [ -x "/usr/bin/docker" ]; then + DOCKER_BIN="/usr/bin/docker" + elif [ -x "/usr/local/bin/docker" ]; then + DOCKER_BIN="/usr/local/bin/docker" + else die "Docker is not installed or not in PATH" fi @@ -76,11 +100,90 @@ in) PATTERN="$1" + # Helper function to run docker command (with or without sudo) + # Use sudo -E to preserve environment variables (especially PATH) + run_docker() { + if [ "$USE_SUDO" -eq 1 ]; then + sudo -E "$DOCKER_BIN" "$@" + else + "$DOCKER_BIN" "$@" + fi + } + + # Test Docker access and determine if sudo is needed + # Strategy: Always try without sudo first, if it fails with permission error, use sudo + # Use array to avoid ShellCheck warning about command substitution in string + DOCKER_TEST_OUTPUT=$("$DOCKER_BIN" ps --format "{{.ID}}" 2>&1) + DOCKER_TEST_EXIT=$? + + # Check if we need sudo + if [ $DOCKER_TEST_EXIT -ne 0 ]; then + # Check if it's a permission error + if echo "$DOCKER_TEST_OUTPUT" | grep -qiE "permission denied|cannot connect to the docker daemon|Got permission denied|permission denied while trying to connect|dial unix.*permission denied"; then + # Try with sudo (but handle password prompts gracefully) + if command -v sudo >/dev/null 2>&1; then + # Check if we're in an interactive terminal (TTY) + if [ -t 0 ] && [ -t 1 ]; then + # Interactive mode: Try sudo (may prompt for password) + SUDO_TEST_OUTPUT=$(sudo "$DOCKER_BIN" ps --format "{{.ID}}" 2>&1) + SUDO_TEST_EXIT=$? + if [ $SUDO_TEST_EXIT -eq 0 ]; then + USE_SUDO=1 + else + # Check if it's a password prompt error + if echo "$SUDO_TEST_OUTPUT" | grep -qiE "password is required|a terminal is required"; then + die "Docker requires sudo permissions, but password authentication failed. Please run: sudo mlh docker in $PATTERN" + else + die "Cannot access Docker daemon even with sudo. Error: $SUDO_TEST_OUTPUT" + fi + fi + else + # Non-interactive mode: Cannot prompt for password + # Check if sudo can work without password (NOPASSWD) + SUDO_TEST_OUTPUT=$(sudo -n "$DOCKER_BIN" ps --format "{{.ID}}" 2>&1) + SUDO_TEST_EXIT=$? + if [ $SUDO_TEST_EXIT -eq 0 ]; then + USE_SUDO=1 + else + # Sudo requires password but we're non-interactive + die "Docker requires sudo permissions, but we're in a non-interactive session. Please either: + 1. Run this command in an interactive terminal: mlh docker in $PATTERN + 2. Or run with sudo directly: sudo mlh docker in $PATTERN + 3. Or add your user to the docker group: sudo usermod -aG docker \$USER (then logout/login) +Original error: $DOCKER_TEST_OUTPUT" + fi + fi + else + die "Docker requires sudo permissions, but sudo is not available. Error: $DOCKER_TEST_OUTPUT" + fi + else + # Other error + die "Docker command failed: $DOCKER_TEST_OUTPUT" + fi + fi + # Find matching containers (running only) - mapfile -t CONTAINERS < <(docker ps --format "{{.ID}}|{{.Names}}" | grep -i "$PATTERN" || true) + # Use run_docker function which handles sudo automatically + # Important: Use run_docker here, not direct docker command + CONTAINERS_OUTPUT=$(run_docker ps --format "{{.ID}}|{{.Names}}" 2>&1) + CONTAINERS_EXIT=$? + + if [ $CONTAINERS_EXIT -ne 0 ]; then + die "Failed to list Docker containers. Exit code: $CONTAINERS_EXIT. Error: $CONTAINERS_OUTPUT" + fi + + # Check if we got any output at all (even empty line means no containers) + if [ -z "$CONTAINERS_OUTPUT" ] || [ "$(echo "$CONTAINERS_OUTPUT" | tr -d '\n' | tr -d ' ')" = "" ]; then + die "No containers are currently running. Start a container first." + fi + + # Filter containers by pattern (case-insensitive) + mapfile -t CONTAINERS < <(echo "$CONTAINERS_OUTPUT" | grep -i "$PATTERN" || true) if [ ${#CONTAINERS[@]} -eq 0 ]; then - die "No running containers found matching pattern: $PATTERN" + # Show available containers to help user + AVAILABLE=$(echo "$CONTAINERS_OUTPUT" | cut -d'|' -f2 | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g') + die "No running containers found matching pattern: $PATTERN. Available containers: $AVAILABLE" fi if [ ${#CONTAINERS[@]} -eq 1 ]; then @@ -88,7 +191,26 @@ in) CONTAINER_ID="${CONTAINERS[0]%%|*}" CONTAINER_NAME="${CONTAINERS[0]##*|}" echo "Entering container: $CONTAINER_NAME" - exec docker exec -it "$CONTAINER_ID" bash + # Try bash first, then sh if bash is not available + if [ "$USE_SUDO" -eq 1 ]; then + # Check if bash is available in container + if sudo "$DOCKER_BIN" exec "$CONTAINER_ID" which bash >/dev/null 2>&1; then + exec sudo "$DOCKER_BIN" exec -it "$CONTAINER_ID" bash + elif sudo "$DOCKER_BIN" exec "$CONTAINER_ID" which sh >/dev/null 2>&1; then + exec sudo "$DOCKER_BIN" exec -it "$CONTAINER_ID" sh + else + die "Neither bash nor sh found in container $CONTAINER_NAME" + fi + else + # Check if bash is available in container + if "$DOCKER_BIN" exec "$CONTAINER_ID" which bash >/dev/null 2>&1; then + exec "$DOCKER_BIN" exec -it "$CONTAINER_ID" bash + elif "$DOCKER_BIN" exec "$CONTAINER_ID" which sh >/dev/null 2>&1; then + exec "$DOCKER_BIN" exec -it "$CONTAINER_ID" sh + else + die "Neither bash nor sh found in container $CONTAINER_NAME" + fi + fi else # Multiple matches - show menu echo "Multiple containers found matching '$PATTERN':" @@ -98,7 +220,7 @@ in) CONTAINER_NAME="${CONTAINERS[$i]##*|}" CONTAINER_ID="${CONTAINERS[$i]%%|*}" # Get container image and status - CONTAINER_INFO=$(docker ps --filter "id=$CONTAINER_ID" --format "{{.Image}} | {{.Status}}" | head -1) + CONTAINER_INFO=$(run_docker ps --filter "id=$CONTAINER_ID" --format "{{.Image}} | {{.Status}}" 2>/dev/null | head -1) echo " $((i + 1)). $CONTAINER_NAME ($CONTAINER_INFO)" done @@ -117,7 +239,26 @@ in) echo "" echo "Entering container: $CONTAINER_NAME" - exec docker exec -it "$CONTAINER_ID" bash + # Try bash first, then sh if bash is not available + if [ "$USE_SUDO" -eq 1 ]; then + # Check if bash is available in container + if sudo "$DOCKER_BIN" exec "$CONTAINER_ID" which bash >/dev/null 2>&1; then + exec sudo "$DOCKER_BIN" exec -it "$CONTAINER_ID" bash + elif sudo "$DOCKER_BIN" exec "$CONTAINER_ID" which sh >/dev/null 2>&1; then + exec sudo "$DOCKER_BIN" exec -it "$CONTAINER_ID" sh + else + die "Neither bash nor sh found in container $CONTAINER_NAME" + fi + else + # Check if bash is available in container + if "$DOCKER_BIN" exec "$CONTAINER_ID" which bash >/dev/null 2>&1; then + exec "$DOCKER_BIN" exec -it "$CONTAINER_ID" bash + elif "$DOCKER_BIN" exec "$CONTAINER_ID" which sh >/dev/null 2>&1; then + exec "$DOCKER_BIN" exec -it "$CONTAINER_ID" sh + else + die "Neither bash nor sh found in container $CONTAINER_NAME" + fi + fi fi ;; *) diff --git a/plugins/mlh-version.sh b/plugins/mlh-version.sh index 82c5a8e..9e13f1f 100755 --- a/plugins/mlh-version.sh +++ b/plugins/mlh-version.sh @@ -14,9 +14,9 @@ set -euo pipefail -readonly VERSION="1.5.0" +readonly VERSION="1.5.1" # shellcheck disable=SC2034 -readonly VERSION_DATE="08.11.2025" +readonly VERSION_DATE="09.11.2025" # shellcheck disable=SC2034 readonly FIRST_RELEASE_DATE="11.10.2025" readonly GITHUB_REPO="melihcelenk/MyLinuxHelper" @@ -24,6 +24,9 @@ readonly INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${GITHUB_REPO}/ma readonly CONFIG_DIR="${HOME}/.mylinuxhelper" readonly UPDATE_CONFIG="${CONFIG_DIR}/.update-config" readonly BASHRC="${HOME}/.bashrc" +readonly PROFILE="${HOME}/.profile" +readonly LOCAL_BIN="${HOME}/.local/bin" +readonly USR_LOCAL_BIN="/usr/local/bin" print_version() { echo "MyLinuxHelper v${VERSION}" @@ -40,11 +43,13 @@ Usage: mlh -v update Update to latest version mlh update Update to latest version mlh update -p Configure periodic updates + mlh --version uninstall Uninstall MyLinuxHelper Examples: mlh --version # Display: MyLinuxHelper v1.3.0 mlh --version update # Update to latest version from GitHub mlh update -p # Configure automatic periodic updates + mlh --version uninstall # Uninstall MyLinuxHelper (with confirmation) EOF } @@ -233,6 +238,335 @@ update_to_latest() { fi } +uninstall_mlh() { + echo "MyLinuxHelper Uninstall" + echo "=======================" + echo "" + echo "This will remove:" + echo " - ~/.mylinuxhelper directory" + echo " - Symlinks in ~/.local/bin (bookmark, bm, i, isjsonvalid, ll, linux, mlh, search)" + echo " - Symlinks in /usr/local/bin (if installed there)" + echo " - MyLinuxHelper entries from ~/.bashrc" + echo " - MyLinuxHelper entries from ~/.profile" + echo "" + echo "⚠️ WARNING: This action cannot be undone!" + echo "" + + read -rp "Are you sure you want to uninstall MyLinuxHelper? (type 'yes' to confirm): " CONFIRM + echo "" + + if [ "$CONFIRM" != "yes" ]; then + echo "Uninstall cancelled." + return 0 + fi + + echo "Uninstalling MyLinuxHelper..." + echo "" + + # Remove symlinks from ~/.local/bin + local symlinks=("bookmark" "i" "isjsonvalid" "ll" "linux" "mlh" "search") + local bookmark_alias="" + + # Check if bookmark alias exists in config + if [ -f "${CONFIG_DIR}/mlh.conf" ]; then + # shellcheck source=/dev/null + source "${CONFIG_DIR}/mlh.conf" 2>/dev/null || true + if [ -n "${BOOKMARK_ALIAS:-}" ]; then + bookmark_alias="$BOOKMARK_ALIAS" + fi + fi + + # Remove symlinks + for link in "${symlinks[@]}"; do + local link_path="${LOCAL_BIN}/${link}" + if [ -L "$link_path" ] || [ -f "$link_path" ]; then + rm -f "$link_path" + echo " Removed: $link_path" + fi + done + + # Remove bookmark alias symlink if exists + if [ -n "$bookmark_alias" ]; then + local alias_path="${LOCAL_BIN}/${bookmark_alias}" + if [ -L "$alias_path" ] || [ -f "$alias_path" ]; then + rm -f "$alias_path" + echo " Removed: $alias_path" + fi + fi + + # Remove symlinks from /usr/local/bin (if they exist and point to our plugins) + if [ -d "$USR_LOCAL_BIN" ] && command -v sudo >/dev/null 2>&1; then + for link in "${symlinks[@]}"; do + local link_path="${USR_LOCAL_BIN}/${link}" + if [ -L "$link_path" ]; then + local target + target="$(readlink -f "$link_path" 2>/dev/null || readlink "$link_path" 2>/dev/null || echo "")" + if echo "$target" | grep -q "MyLinuxHelper\|mylinuxhelper"; then + # shellcheck disable=SC2024 + if sudo rm -f "$link_path" 2>/dev/null; then + echo " Removed: $link_path" + fi + fi + fi + done + + # Remove bookmark alias from /usr/local/bin if exists + if [ -n "$bookmark_alias" ]; then + local alias_path="${USR_LOCAL_BIN}/${bookmark_alias}" + if [ -L "$alias_path" ]; then + local target + target="$(readlink -f "$alias_path" 2>/dev/null || readlink "$alias_path" 2>/dev/null || echo "")" + if echo "$target" | grep -q "MyLinuxHelper\|mylinuxhelper"; then + # shellcheck disable=SC2024 + if sudo rm -f "$alias_path" 2>/dev/null; then + echo " Removed: $alias_path" + fi + fi + fi + fi + fi + + # Remove entries from ~/.bashrc + if [ -f "$BASHRC" ]; then + local bashrc_backup + bashrc_backup="${BASHRC}.mlh-backup-$(date +%s)" + cp "$BASHRC" "$bashrc_backup" + local temp_bashrc + temp_bashrc="$(mktemp)" + + # Remove PATH export line + # shellcheck disable=SC2016 + local path_line='export PATH="$HOME/.local/bin:$PATH"' + if grep -Fq "$path_line" "$BASHRC" 2>/dev/null; then + # Check if this is the only PATH modification (safe to remove) + local path_count + path_count=$(grep -c 'export PATH=.*\.local/bin' "$BASHRC" 2>/dev/null || echo "0") + if [ "$path_count" -eq 1 ]; then + grep -vF "$path_line" "$BASHRC" >"$temp_bashrc" 2>/dev/null || cp "$BASHRC" "$temp_bashrc" + mv "$temp_bashrc" "$BASHRC" + echo " Removed PATH export from ~/.bashrc" + else + echo " Note: Multiple PATH entries found, not removing (manual cleanup may be needed)" + fi + fi + + # Remove mlh wrapper function (using Python for reliable multiline removal) + local mlh_marker="# MyLinuxHelper - mlh wrapper function" + if grep -Fq "$mlh_marker" "$BASHRC" 2>/dev/null; then + if command -v python3 >/dev/null 2>&1; then + python3 - "$BASHRC" "$mlh_marker" <<'PYEOF' +import sys + +bashrc_file = sys.argv[1] +marker = sys.argv[2] + +with open(bashrc_file, 'r') as f: + lines = f.readlines() + +output = [] +in_block = False +brace_count = 0 +skip_next_empty = False + +for i, line in enumerate(lines): + if not in_block: + if marker in line: + in_block = True + brace_count = 0 + skip_next_empty = (i > 0 and lines[i-1].strip() == '') + continue + else: + output.append(line) + else: + brace_count += line.count('{') - line.count('}') + if line.strip() == '}' and brace_count <= 0: + in_block = False + if skip_next_empty and output and output[-1].strip() == '': + output.pop() + continue + +with open(bashrc_file, 'w') as f: + f.writelines(output) +PYEOF + echo " Removed mlh wrapper function from ~/.bashrc" + else + # Fallback: simple sed (may not work perfectly for nested braces) + sed -i.bak "/${mlh_marker}/,/^}$/d" "$BASHRC" 2>/dev/null || true + echo " Removed mlh wrapper function from ~/.bashrc (fallback method)" + fi + fi + + # Remove bookmark wrapper function + local bookmark_marker="# MyLinuxHelper - bookmark wrapper function" + if grep -Fq "$bookmark_marker" "$BASHRC" 2>/dev/null; then + if command -v python3 >/dev/null 2>&1; then + python3 - "$BASHRC" "$bookmark_marker" <<'PYEOF' +import sys + +bashrc_file = sys.argv[1] +marker = sys.argv[2] + +with open(bashrc_file, 'r') as f: + lines = f.readlines() + +output = [] +in_block = False +brace_count = 0 +skip_next_empty = False + +for i, line in enumerate(lines): + if not in_block: + if marker in line: + in_block = True + brace_count = 0 + skip_next_empty = (i > 0 and lines[i-1].strip() == '') + continue + else: + output.append(line) + else: + brace_count += line.count('{') - line.count('}') + if line.strip() == '}' and brace_count <= 0: + in_block = False + if skip_next_empty and output and output[-1].strip() == '': + output.pop() + continue + +with open(bashrc_file, 'w') as f: + f.writelines(output) +PYEOF + echo " Removed bookmark wrapper function from ~/.bashrc" + else + sed -i.bak "/${bookmark_marker}/,/^}$/d" "$BASHRC" 2>/dev/null || true + echo " Removed bookmark wrapper function from ~/.bashrc (fallback method)" + fi + fi + + # Remove bookmark alias wrapper function if exists + if [ -n "$bookmark_alias" ]; then + local alias_marker="# MyLinuxHelper - ${bookmark_alias} alias wrapper" + if grep -Fq "$alias_marker" "$BASHRC" 2>/dev/null; then + if command -v python3 >/dev/null 2>&1; then + python3 - "$BASHRC" "$alias_marker" <<'PYEOF' +import sys + +bashrc_file = sys.argv[1] +marker = sys.argv[2] + +with open(bashrc_file, 'r') as f: + lines = f.readlines() + +output = [] +in_block = False +brace_count = 0 +skip_next_empty = False + +for i, line in enumerate(lines): + if not in_block: + if marker in line: + in_block = True + brace_count = 0 + skip_next_empty = (i > 0 and lines[i-1].strip() == '') + continue + else: + output.append(line) + else: + brace_count += line.count('{') - line.count('}') + if line.strip() == '}' and brace_count <= 0: + in_block = False + if skip_next_empty and output and output[-1].strip() == '': + output.pop() + continue + +with open(bashrc_file, 'w') as f: + f.writelines(output) +PYEOF + echo " Removed ${bookmark_alias} alias wrapper from ~/.bashrc" + else + sed -i.bak "/${alias_marker}/,/^}$/d" "$BASHRC" 2>/dev/null || true + echo " Removed ${bookmark_alias} alias wrapper from ~/.bashrc (fallback method)" + fi + fi + fi + + # Remove auto-update hook + local update_marker="# MyLinuxHelper auto-update check" + if grep -Fq "$update_marker" "$BASHRC" 2>/dev/null; then + if command -v python3 >/dev/null 2>&1; then + python3 - "$BASHRC" "$update_marker" <<'PYEOF' +import sys + +bashrc_file = sys.argv[1] +marker = sys.argv[2] + +with open(bashrc_file, 'r') as f: + lines = f.readlines() + +output = [] +in_block = False +skip_next_empty = False + +for i, line in enumerate(lines): + if not in_block: + if marker in line: + in_block = True + skip_next_empty = (i > 0 and lines[i-1].strip() == '') + continue + else: + output.append(line) + else: + if line.strip() == 'fi': + in_block = False + if skip_next_empty and output and output[-1].strip() == '': + output.pop() + continue + +with open(bashrc_file, 'w') as f: + f.writelines(output) +PYEOF + echo " Removed auto-update hook from ~/.bashrc" + else + sed -i.bak "/${update_marker}/,/^fi$/d" "$BASHRC" 2>/dev/null || true + echo " Removed auto-update hook from ~/.bashrc (fallback method)" + fi + fi + + # Clean up temp and backup files + rm -f "$temp_bashrc" 2>/dev/null || true + rm -f "${BASHRC}.bak" 2>/dev/null || true + rm -f "$bashrc_backup" 2>/dev/null || true + fi + + # Remove entries from ~/.profile + if [ -f "$PROFILE" ]; then + # shellcheck disable=SC2016 + local path_line='export PATH="$HOME/.local/bin:$PATH"' + if grep -Fq "$path_line" "$PROFILE" 2>/dev/null; then + # Check if this is the only PATH modification (safe to remove) + local path_count + path_count=$(grep -c 'export PATH=.*\.local/bin' "$PROFILE" 2>/dev/null || echo "0") + if [ "$path_count" -eq 1 ]; then + sed -i.bak "\|${path_line}|d" "$PROFILE" 2>/dev/null || true + echo " Removed PATH export from ~/.profile" + rm -f "${PROFILE}.bak" 2>/dev/null || true + else + echo " Note: Multiple PATH entries found in ~/.profile, not removing (manual cleanup may be needed)" + fi + fi + fi + + # Remove ~/.mylinuxhelper directory + if [ -d "$CONFIG_DIR" ]; then + rm -rf "$CONFIG_DIR" + echo " Removed: ~/.mylinuxhelper" + fi + + echo "" + echo "✅ Uninstall completed successfully!" + echo "" + echo "Note: You may need to restart your terminal or run 'source ~/.bashrc' to apply changes." + echo "" +} + main() { if [ $# -eq 0 ]; then print_version @@ -264,6 +598,10 @@ main() { check_and_update exit 0 ;; + uninstall) + uninstall_mlh + exit 0 + ;; *) echo "Error: Unknown command '$1'" >&2 echo "Run 'mlh --version --help' for usage information." >&2 diff --git a/plugins/mlh.sh b/plugins/mlh.sh index f4d16a8..3db41c8 100755 --- a/plugins/mlh.sh +++ b/plugins/mlh.sh @@ -81,11 +81,12 @@ MyLinuxHelper - App Settings & Updates 1. Show current version 2. Update to latest version 3. Configure periodic updates -4. Back to main menu +4. Uninstall MyLinuxHelper +5. Back to main menu EOF - read -rp "Select [1-4]: " SETTING_SELECTION + read -rp "Select [1-5]: " SETTING_SELECTION echo "" case "$SETTING_SELECTION" in @@ -100,7 +101,12 @@ EOF 3) exec "$SCRIPT_DIR/mlh-version.sh" update -p ;; - 4 | b | B) + 4) + "$SCRIPT_DIR/mlh-version.sh" uninstall + echo "" + read -rp "Press Enter to continue..." + ;; + 5 | b | B) return 0 ;; *) diff --git a/setup.sh b/setup.sh index 0bc5dba..71a636e 100755 --- a/setup.sh +++ b/setup.sh @@ -25,6 +25,17 @@ BOOKMARK_ALIAS="" if [ -f "$MLH_CONFIG_FILE" ]; then # shellcheck source=/dev/null source "$MLH_CONFIG_FILE" 2>/dev/null || true +else + # Config file doesn't exist - create it from example if available + EXAMPLE_CONFIG="$ROOT_DIR/docs/config/mlh.conf.example" + if [ -f "$EXAMPLE_CONFIG" ]; then + mkdir -p "$MLH_CONFIG_DIR" + cp "$EXAMPLE_CONFIG" "$MLH_CONFIG_FILE" + echo "Created config file: $MLH_CONFIG_FILE" + echo " (copied from example with default BOOKMARK_ALIAS=bm)" + # shellcheck source=/dev/null + source "$MLH_CONFIG_FILE" 2>/dev/null || true + fi fi # 1) Ensure ~/.local/bin exists and added to PATH for future shells diff --git a/tests/test-mlh-docker.sh b/tests/test-mlh-docker.sh index 4ee60e7..1e22d09 100755 --- a/tests/test-mlh-docker.sh +++ b/tests/test-mlh-docker.sh @@ -1,10 +1,21 @@ #!/usr/bin/env bash # test-mlh-docker.sh - Test suite for mlh-docker.sh +# +# This file is sourced by the main test runner (./tests/test) +# It should NOT be executed directly # Disable strict mode for tests set +euo pipefail 2>/dev/null || true set +e +# ROOT_DIR should be set by test runner when sourced +# If not set, try to determine it from script location (for direct execution) +if [ -z "${ROOT_DIR:-}" ]; then + # Try to determine ROOT_DIR from script location + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + ROOT_DIR="$(dirname "$SCRIPT_DIR")" +fi + PLUGIN_SCRIPT="$ROOT_DIR/plugins/mlh-docker.sh" # ============================================================ @@ -108,7 +119,9 @@ fi # ============================================================ # Test 13: Script uses docker ps for listing containers -if grep -q "docker ps" "$PLUGIN_SCRIPT"; then +# Accept both direct 'docker ps' and variable-based 'DOCKER_CMD.*ps' patterns +# The script uses "$DOCKER_CMD" ps, so we check for DOCKER_CMD and ps together +if grep -qE '(docker ps|DOCKER_CMD.*ps)' "$PLUGIN_SCRIPT"; then print_test_result "Script uses 'docker ps' to list containers" "PASS" else print_test_result "Script uses 'docker ps' to list containers" "FAIL" @@ -136,7 +149,10 @@ else fi # Test 17: Script uses docker exec to enter containers -if grep -q "docker exec -it" "$PLUGIN_SCRIPT"; then +# Accept various patterns: the script uses exec -it to enter containers +# It may use DOCKER_BIN, run_docker function, or sudo +# The key is that it uses 'exec -it' to enter containers +if grep -q "exec -it" "$PLUGIN_SCRIPT"; then print_test_result "Script uses 'docker exec -it' to enter" "PASS" else print_test_result "Script uses 'docker exec -it' to enter" "FAIL"