# toggle
A Rust CLI for toggling comment blocks in source code. Comment / uncomment line
ranges, named sections, or grouped variants across one or many files —
deterministic, atomic, language-aware.
## Install
```bash
cargo install --path .
# or, from crates.io:
cargo install togl
```
Pre-built binaries via Homebrew are not yet published; see the [Distribution](#distribution) section.
## Quick start
```bash
# Toggle a line range in a Python file
toggle -l 10:20 main.py
# Toggle a named section across all matching files
toggle -S featureXYZ src/
# Force a section commented across a tree
toggle -S debug --force on -R src/
# Discover what sections exist in a tree
toggle --scan -R src/
```
## Section markers
Wrap any block in a paired marker comment that the tool can find:
```python
# toggle:start ID=featureX desc="Optional description"
print("guarded code")
# toggle:end ID=featureX
```
The single-line comment style is inferred from the file extension; override
with `--comment-style "//"` (single) or `--comment-style "//" "/*" "*/"` (with
multi-line delimiters).
## Section variants (`group:variant`)
Use a `:` in the ID to mark variants of the same group. The CLI then knows
how to swap, activate, or fan-out across them.
```python
# toggle:start ID=db:sqlite
import sqlite3
# toggle:end ID=db:sqlite
# toggle:start ID=db:postgres
# import psycopg2
# toggle:end ID=db:postgres
```
| `toggle -S db file.py` | **Pair flip** — swap active and commented variants (errors on 3+ variants without a qualifier) |
| `toggle -S db:postgres file.py` | **Activate** — uncomment `db:postgres`, comment every other `db:*` |
| `toggle -S db --force on file.py` | **Force all** — comment every variant in `db` |
| `toggle -S db --pair file.py` | **Guard** — fail before any write if `db` does not have exactly 2 variants |
## Scan & check
```bash
# Per-file table with a TYPE column (solo / pair / group)
toggle --scan src/app.py
# Recursive summary, one row per group
toggle --scan -R src/
# Detailed view of one group: file refs + state per variant
toggle --scan -S db -R src/
# Validate without modifying: unclosed markers, duplicate IDs, cross-file gaps
toggle --scan --check -R src/
# Same, but only flag groups that should be pairs
toggle --scan --check --pair -R src/
# Machine-readable nested JSON
toggle --scan -R src/ --json
```
`--check` exits non-zero on any error finding (unclosed markers, duplicate
IDs); warnings (variant gaps, pair-count mismatches) do not fail the run.
## Atomic multi-file mode
```bash
# All files succeed or none are modified — backups created by default
toggle -S db:postgres --atomic -R src/
# Recover from an interrupted atomic run
toggle --recover # rolls back
toggle --recover --recover-forward # completes the commit
```
## Distribution
- **From source:** `cargo install --path .`
- **Shell completions:** `toggle --completions bash > /etc/bash_completion.d/toggle`
(also `zsh`, `fish`, `powershell`, `elvish`)
- **Man page:** `toggle --man > toggle.1 && man ./toggle.1`
- **crates.io:** [`togl`](https://crates.io/crates/togl) — installs the `toggle` binary.
- **Homebrew:** not yet published.
---
## Reference
The remainder of this README is the original design spec, retained for
historical context. CLI semantics in the spec match what's implemented unless
called out above.
## 1. Overview
**Goal**
Create a Rust-based CLI tool, **`toggle`**, that can:
- Comment or uncomment designated lines or blocks of text in code files.
- Detect and apply correct single-line or multi-line comment styles by file extension.
- Work off a configuration file (`.toggleConfig`) or command-line arguments.
- Identify labeled "sections" to toggle on or off across multiple files.
- Provide granular control (line-based, section-based, file-based, multi-file).
**Core Objectives**
1. **Line-based toggling**: Support start/end line numbers, or a start line with a fixed number of lines, or a start line to the end of the file.
2. **Section-based toggling**: Recognize in-file sections tagged with an ID (e.g., `SECTION_ID=foo`) and toggle all occurrences (on/off) across a codebase.
3. **Configurable comment styles**: Auto-detect comment style by file extension or override with custom settings.
4. **Extendable**: Allow a `.toggleConfig` file to hold global or per-language comment preferences.
---
## 2. Command-Line Interface (CLI)
### 2.1 Basic Command Syntax
```
toggle [OPTIONS] <file_or_directory_paths>...
```
### 2.2 Primary Flags & Arguments
| `-l, --line` (repeatable) | Specify line-based toggles in the format `<start_line>:<end_line>` or `<start_line>:+<count>`. | `--line 10:20` or `--line 15:+5` |
| `-S, --section` (repeatable) | Specify section ID(s) to toggle. | `--section featureXYZ` |
| `-f, --force [on\|off]` | Force a toggle state for line-based or section-based operations. | `--force on` |
| `-m, --mode [auto\|single\|multi]` | Defines the comment mode. `auto` will use file extension to determine the style, `single`/`multi` overrides. | `--mode single` |
| `-c, --comment-style` | Manually specify exact delimiters for single/multi-line comments (overrides auto detection). | `--comment-style "//" "/*" "*/"` |
| `--to-end` | If set, toggling continues from `<start_line>` to the end of the file. | `--line 50 --to-end` |
| `--config <path>` | Points to a custom `.toggleConfig` file. Default is `.toggleConfig` in current directory if present. | `--config /path/to/altConfig` |
| `-R, --recursive` | Recursively search directories for files that match the toggled sections or line references. | `-R src/` |
| `-v, --verbose` | Show detailed logs (lines changed, files modified, etc.). | `--verbose` |
| `--dry-run` | Show which changes **would** be made, without altering files. | `--dry-run --verbose` |
### 2.3 Behavior Examples
1. **Line Range Toggle**
```bash
toggle --line 10:20 main.py
```
- Auto-detects `.py` → uses `#` for single-line comments.
- Comments out lines 10 to 20 (or toggles them if already commented).
2. **Line Range to End**
```bash
toggle --line 30 --to-end MyClass.java
```
- Auto-detects `.java` → uses `//` or `/*...*/`.
- Comments out from line 30 to EOF.
3. **Section-Based Toggle**
```bash
toggle --section signupFlow --force on src/
```
- Recursively scans `src/` to find any sections labeled `signupFlow`.
- Forces them all to become commented (on).
4. **Override Comment Style**
```bash
toggle --mode multi --comment-style "//" "/*" "*/" --line 40:45 test.cc
```
- Forces multi-line mode but uses custom single-line prefix `//` if needed.
- The multi-line delimiters are explicitly `/*` and `*/`.
5. **Multiple Toggles in One Command**
```bash
toggle --line 10:20 --section adminUI --force off module.ts
```
- Toggles lines 10–20 and a named section `adminUI` in `module.ts`.
- Forces off any commented region that is identified by `adminUI`.
---
## 3. Configuration File (`.toggleConfig`)
### 3.1 Purpose
- Defines default behavior per file extension or globally.
- Acts as the fallback if command-line arguments are not specified.
### 3.2 Format
```toml
[global]
default_mode = "auto"
force_state = "none" # valid: on, off, none (i.e., invert if toggling)
single_line_delimiter = "//"
multi_line_delimiter_start = "/*"
multi_line_delimiter_end = "*/"
[language.python]
single_line_delimiter = "#"
multi_line_delimiter_start = "\"\"\""
multi_line_delimiter_end = "\"\"\""
[language.ruby]
single_line_delimiter = "#"
[language.java]
single_line_delimiter = "//"
multi_line_delimiter_start = "/*"
multi_line_delimiter_end = "*/"
```
**Notes**:
- `global` section sets the baseline.
- Each `[language.xxx]` overrides settings for `.xxx` files.
- If no extension is recognized, the program either throws an error or uses `global` defaults.
---
## 4. Section Markers in Source Files
### 4.1 Marker Convention
A standard marker might look like this (example for Java/JS/C-style):
```java
// toggle:start ID=featureX desc="Enable the new feature"
System.out.println("New feature code here...");
// toggle:end ID=featureX
```
Or for Python:
```python
# toggle:start ID=featureX desc="Enable the new feature"
print("New feature code here...")
# toggle:end ID=featureX
```
**Proposed Format**:
```
[toggle:start ID=<identifier> desc="<description>"]
...
[toggle:end ID=<identifier>]
```
- **`ID`** is mandatory.
- **`desc`** is optional.
- The line format must be recognizable by `toggle`. For example:
- `// toggle:start ID=featureX desc="..."`
- `# toggle:start ID=featureX desc="..."`
- `/* toggle:start ID=featureX desc="..." */` (depending on language)
### 4.2 Behavior
- **Toggling ON**: If the block is not commented, comment it out. If already commented, do nothing.
- **Toggling OFF**: If the block is commented, uncomment it. If already uncommented, do nothing.
- **No Force**: If neither `--force on` nor `--force off` is set, `toggle` inverts the current state.
- **Global Toggle**: The tool can scan multiple files (via `-R` or listing files) and apply toggles to all occurrences of an `ID`.
---
## 5. Implementation Outline (Rust)
1. **Argument Parsing**
- Use a crate like [**Clap**](https://docs.rs/clap/latest/clap/) or [**StructOpt**](https://docs.rs/structopt/) for robust CLI handling.
- Collect line toggles (`Vec<String>` for `<start_line>:<end_line>`, etc.), sections, force states, and mode overrides.
2. **Configuration Handling**
- On startup, attempt to load `.toggleConfig` (or alternative path if `--config` is specified).
- Parse with a TOML library (e.g., [**toml**](https://docs.rs/toml/latest/toml/)).
- Merge config values with command-line overrides.
3. **File Scanner**
- If user inputs directories and `-R` is set, recursively walk the directory using [**walkdir**](https://docs.rs/walkdir/latest/walkdir/).
- Filter files by extension or by presence of `toggle` markers.
4. **Comment Style Determination**
- If `--mode auto`, map extension → comment style. If not found, throw an error.
- If `--comment-style ...` is passed, override the style.
- If `.toggleConfig` contains `[language.xyz]` that matches extension, use those defaults unless overridden.
5. **Parsing the File**
- For each file, read line by line into a buffer (e.g., `Vec<String>`).
- For line-based toggles:
- Identify the relevant range(s).
- Comment or uncomment accordingly.
- For section-based toggles:
- Detect lines that match `toggle:start ID=...` and `toggle:end ID=...`.
- Determine if the block is currently commented or not.
- Apply on/off or invert logic.
6. **Commenting / Uncommenting Logic**
- **Single-line** approach (e.g., `#`, `//`): Prepend or remove the token from each line.
- **Multi-line** approach (e.g., `/* ... */`):
- Insert `/*` at the first line, `*/` at the last line (or for partial lines, handle carefully).
- Alternatively, comment each line singly if that's simpler for toggling.
- Keep track of lines that are already partially or fully commented to avoid double-commenting.
7. **Output & Write-Back**
- After toggling, write the modified buffer back to the file (unless `--dry-run` is set).
- If `--dry-run`, print a summary of changes.
8. **Edge Cases**
- Overlapping toggles for the same lines or sections.
- Nested sections (some languages permit nested comment blocks).
- Files with unusual line endings (CRLF vs. LF).
- Extremely large files (consider streaming vs. loading entire file).
---
## 6. Example Use Case Scenarios
**Scenario A: Toggling a Feature in Multiple Files**
```
toggle --section featureX --force off -R src/
```
- Recursively looks for `toggle:start ID=featureX`/`toggle:end ID=featureX`.
- Forces it off. If some blocks were on, they get uncommented.
**Scenario B: Automated Build Script**
- Integrate `toggle` in a CI script to enable certain code blocks for a staging environment:
```bash
toggle --section stagingFeature --force on path/to/config.yaml path/to/server.java
```
- Re-run with `--force off` after tests complete.
---
## 7. Error Handling & Logging
1. **Unknown Extension**
- If `--mode auto` and the file extension has no known mapping, error: “Cannot detect comment style for ‘.xyz’. Use `--mode` or `.toggleConfig` to specify.”
2. **Conflicting Options**
- If `--mode single` and `--comment-style` multi-line tokens are provided, prefer the explicit `--comment-style` or show a warning and proceed with single-line prepends.
3. **Invalid Ranges**
- If `start_line` > `end_line`, skip or warn.
- If lines exceed file length, skip out-of-bound lines and log a warning.
4. **Section Mismatch**
- If `toggle:start ID=foo` is found but no matching `toggle:end ID=foo`, log a warning: “Unclosed section ID=foo in filename.”
5. **Verbose Logging**
- If `-v, --verbose`, show each line range or section ID processed and the new state.