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
# or, from crates.io:
Pre-built binaries via Homebrew are not yet published; see the Distribution section.
Quick start
# Toggle a line range in a Python file
# Toggle a named section across all matching files
# Force a section commented across a tree
# Discover what sections exist in a tree
Section markers
Wrap any block in a paired marker comment that the tool can find:
# toggle:start ID=featureX desc="Optional description"
# 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.
# toggle:start ID=db:sqlite
# toggle:end ID=db:sqlite
# toggle:start ID=db:postgres
# import psycopg2
# toggle:end ID=db:postgres
| Command | Behavior |
|---|---|
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
# Per-file table with a TYPE column (solo / pair / group)
# Recursive summary, one row per group
# Detailed view of one group: file refs + state per variant
# Validate without modifying: unclosed markers, duplicate IDs, cross-file gaps
# Same, but only flag groups that should be pairs
# Machine-readable nested 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
# All files succeed or none are modified — backups created by default
# Recover from an interrupted atomic run
Distribution
- From source:
cargo install --path . - Shell completions:
toggle --completions bash > /etc/bash_completion.d/toggle(alsozsh,fish,powershell,elvish) - Man page:
toggle --man > toggle.1 && man ./toggle.1 - crates.io:
togl— installs thetogglebinary. - 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
- 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.
- 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. - Configurable comment styles: Auto-detect comment style by file extension or override with custom settings.
- Extendable: Allow a
.toggleConfigfile 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
| Flag / Arg | Description | Example |
|---|---|---|
-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
-
Line Range Toggle
- Auto-detects
.py→ uses#for single-line comments. - Comments out lines 10 to 20 (or toggles them if already commented).
- Auto-detects
-
Line Range to End
- Auto-detects
.java→ uses//or/*...*/. - Comments out from line 30 to EOF.
- Auto-detects
-
Section-Based Toggle
- Recursively scans
src/to find any sections labeledsignupFlow. - Forces them all to become commented (on).
- Recursively scans
-
Override Comment Style
- Forces multi-line mode but uses custom single-line prefix
//if needed. - The multi-line delimiters are explicitly
/*and*/.
- Forces multi-line mode but uses custom single-line prefix
-
Multiple Toggles in One Command
- Toggles lines 10–20 and a named section
adminUIinmodule.ts. - Forces off any commented region that is identified by
adminUI.
- Toggles lines 10–20 and a named section
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
[]
= "auto"
= "none" # valid: on, off, none (i.e., invert if toggling)
= "//"
= "/*"
= "*/"
[]
= "#"
= "\"\"\""
= "\"\"\""
[]
= "#"
[]
= "//"
= "/*"
= "*/"
Notes:
globalsection sets the baseline.- Each
[language.xxx]overrides settings for.xxxfiles. - If no extension is recognized, the program either throws an error or uses
globaldefaults.
4. Section Markers in Source Files
4.1 Marker Convention
A standard marker might look like this (example for Java/JS/C-style):
// toggle:start ID=featureX desc="Enable the new feature"
System.out.;
// toggle:end ID=featureX
Or for Python:
# toggle:start ID=featureX desc="Enable the new feature"
# toggle:end ID=featureX
Proposed Format:
[toggle:start ID=<identifier> desc="<description>"]
...
[toggle:end ID=<identifier>]
IDis mandatory.descis 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 onnor--force offis set,toggleinverts the current state. - Global Toggle: The tool can scan multiple files (via
-Ror listing files) and apply toggles to all occurrences of anID.
5. Implementation Outline (Rust)
-
Argument Parsing
-
Configuration Handling
- On startup, attempt to load
.toggleConfig(or alternative path if--configis specified). - Parse with a TOML library (e.g., toml).
- Merge config values with command-line overrides.
- On startup, attempt to load
-
File Scanner
- If user inputs directories and
-Ris set, recursively walk the directory using walkdir. - Filter files by extension or by presence of
togglemarkers.
- If user inputs directories and
-
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
.toggleConfigcontains[language.xyz]that matches extension, use those defaults unless overridden.
- If
-
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=...andtoggle:end ID=.... - Determine if the block is currently commented or not.
- Apply on/off or invert logic.
- Detect lines that match
- For each file, read line by line into a buffer (e.g.,
-
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.
- Insert
- Keep track of lines that are already partially or fully commented to avoid double-commenting.
- Single-line approach (e.g.,
-
Output & Write-Back
- After toggling, write the modified buffer back to the file (unless
--dry-runis set). - If
--dry-run, print a summary of changes.
- After toggling, write the modified buffer back to the file (unless
-
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
togglein a CI script to enable certain code blocks for a staging environment: - Re-run with
--force offafter tests complete.
7. Error Handling & Logging
-
Unknown Extension
- If
--mode autoand the file extension has no known mapping, error: “Cannot detect comment style for ‘.xyz’. Use--modeor.toggleConfigto specify.”
- If
-
Conflicting Options
- If
--mode singleand--comment-stylemulti-line tokens are provided, prefer the explicit--comment-styleor show a warning and proceed with single-line prepends.
- If
-
Invalid Ranges
- If
start_line>end_line, skip or warn. - If lines exceed file length, skip out-of-bound lines and log a warning.
- If
-
Section Mismatch
- If
toggle:start ID=foois found but no matchingtoggle:end ID=foo, log a warning: “Unclosed section ID=foo in filename.”
- If
-
Verbose Logging
- If
-v, --verbose, show each line range or section ID processed and the new state.
- If