Skip to content

Commit ad2931c

Browse files
committed
Add comprehensive documentation on Jujutsu usage in CLAUDE.md
1 parent 69a737f commit ad2931c

File tree

6 files changed

+258
-9
lines changed

6 files changed

+258
-9
lines changed

DEVELOPMENT_PLAN.md

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,45 @@
11
# Smart Tree Development Plan
22

3+
## Version Management
4+
5+
Smart Tree uses a versioning system with the following components:
6+
7+
- **Single Source of Truth**: The version is stored in `Cargo.toml` and accessed via `env!("CARGO_PKG_VERSION")`
8+
- **Version Display**: The current version can be displayed with `smart-tree -v`
9+
- **Release Management**: The `release.sh` script manages version bumping and release creation
10+
11+
### Using the Release Script
12+
13+
The `release.sh` script provides a convenient way to manage versions:
14+
15+
```bash
16+
# Show current version
17+
./release.sh current
18+
19+
# Bump major version (x.0.0)
20+
./release.sh major
21+
22+
# Bump minor version (0.x.0)
23+
./release.sh minor
24+
25+
# Bump patch version (0.0.x)
26+
./release.sh patch
27+
```
28+
29+
The script will:
30+
1. Update the version in `Cargo.toml`
31+
2. Create a Jujutsu bookmark for the version (acts as a tag)
32+
3. Offer to push changes to the remote repository
33+
34+
### Release Workflow
35+
36+
1. Make your changes to the codebase
37+
2. Run tests and lint checks: `cargo test && cargo clippy`
38+
3. Bump the version: `./release.sh [major|minor|patch]`
39+
4. Push to GitHub when prompted by the script
40+
5. The GitHub Action will automatically build binaries for all platforms
41+
6. The GitHub Release will be created from the tag
42+
343
## Current Project Status
444

545
Smart Tree is a modern directory tree viewer that intelligently displays file hierarchies with an emphasis on readability. Unlike traditional `tree` commands that output everything (often producing thousands of lines), Smart Tree makes smart decisions about what to show and what to fold.
@@ -123,10 +163,11 @@ Smart Tree is a modern directory tree viewer that intelligently displays file hi
123163
### Phase 4: Distribution and Integration (Medium Term)
124164

125165
1. **Package and Distribution**
126-
- Create installation script (install.sh) for simple cross-platform installation
127-
- Package for Homebrew (brew) installation on macOS
128-
- Implement continuous integration/deployment pipeline
129-
- Set up automated GitHub releases with versioned branches (e.g., 'v0.2.0')
166+
- ✅ Create installation script (install.sh) for simple cross-platform installation
167+
- ✅ Package for Homebrew (brew) installation on macOS
168+
- ✅ Implement continuous integration/deployment pipeline
169+
- ✅ Set up automated GitHub releases with versioned tags (e.g., 'v0.2.0')
170+
- ✅ Create release script (release.sh) for managing versions
130171

131172
2. **Integration Features**
132173
- VCS integration (show modified files since last commit)

README.MD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ smart-tree --rule-debug
197197
# Disable all rules completely
198198
smart-tree --no-rules
199199

200+
# Show version
201+
smart-tree -v
202+
200203
# Show help with all options
201204
smart-tree --help
202205
```
@@ -217,6 +220,9 @@ cargo install --path .
217220

218221
# Now you can run from anywhere
219222
smart-tree [path] [options]
223+
224+
# Check version
225+
smart-tree -v
220226
```
221227

222228
## 🤝 Contributing

release.sh

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# Colors for output
5+
GREEN="\033[0;32m"
6+
YELLOW="\033[1;33m"
7+
RED="\033[0;31m"
8+
BLUE="\033[0;34m"
9+
RESET="\033[0m"
10+
11+
# Function to display usage instructions
12+
usage() {
13+
echo -e "${BLUE}Smart Tree Release Tool${RESET}"
14+
echo
15+
echo "This script helps manage version and releases for the Smart Tree project"
16+
echo
17+
echo "Usage:"
18+
echo " ./release.sh [command]"
19+
echo
20+
echo "Commands:"
21+
echo " major - Bump major version (X.0.0)"
22+
echo " minor - Bump minor version (0.X.0)"
23+
echo " patch - Bump patch version (0.0.X)"
24+
echo " current - Show current version"
25+
echo " help - Show this help message"
26+
echo
27+
echo "Examples:"
28+
echo " ./release.sh minor # Increases minor version (e.g., 0.2.0 -> 0.3.0)"
29+
echo " ./release.sh patch # Increases patch version (e.g., 0.2.0 -> 0.2.1)"
30+
echo
31+
}
32+
33+
# Function to extract current version from Cargo.toml
34+
get_current_version() {
35+
grep -m 1 '^version =' Cargo.toml | awk -F '"' '{print $2}' | tr -d '[:space:]'
36+
}
37+
38+
# Function to update version in Cargo.toml
39+
update_version_in_cargo_toml() {
40+
local new_version=$1
41+
sed -i "s/^version = \".*\"/version = \"$new_version\"/" Cargo.toml
42+
echo -e "${GREEN}Updated Cargo.toml to version $new_version${RESET}"
43+
}
44+
45+
# Function to tag release in Jujutsu
46+
tag_release() {
47+
local new_version=$1
48+
local tag_name="v$new_version"
49+
local message="Release $tag_name"
50+
51+
# Make sure the file is tracked by jj
52+
jj file track release.sh || echo "release.sh already tracked"
53+
54+
# Create a new change for the version update
55+
jj new -m "Bump version to $new_version"
56+
57+
# Create tag (bookmark) in Jujutsu
58+
jj bookmark set "$tag_name" -r @ -B
59+
60+
echo -e "${GREEN}Created bookmark/tag $tag_name${RESET}"
61+
62+
# Optional: Push changes to remote
63+
read -p "Push changes to remote? (y/n): " push_choice
64+
if [[ $push_choice == "y" || $push_choice == "Y" ]]; then
65+
jj bookmark set master -r @ -B
66+
jj git push -b master
67+
echo -e "${GREEN}Changes pushed to remote${RESET}"
68+
echo
69+
echo -e "${YELLOW}Now go to GitHub and create a release from tag $tag_name${RESET}"
70+
else
71+
echo -e "${YELLOW}Remember to push changes with: jj bookmark set master -r @ -B && jj git push -b master${RESET}"
72+
fi
73+
}
74+
75+
# Function to bump version
76+
bump_version() {
77+
local component=$1
78+
local current_version=$(get_current_version)
79+
local major minor patch
80+
81+
# Parse version components
82+
IFS='.' read -r major minor patch <<< "$current_version"
83+
84+
# Update appropriate component
85+
case $component in
86+
major)
87+
major=$((major + 1))
88+
minor=0
89+
patch=0
90+
;;
91+
minor)
92+
minor=$((minor + 1))
93+
patch=0
94+
;;
95+
patch)
96+
patch=$((patch + 1))
97+
;;
98+
*)
99+
echo -e "${RED}Invalid version component: $component${RESET}"
100+
exit 1
101+
;;
102+
esac
103+
104+
local new_version="$major.$minor.$patch"
105+
106+
echo -e "${BLUE}Current version: ${RESET}$current_version"
107+
echo -e "${BLUE}New version: ${RESET}$new_version"
108+
109+
# Confirm before proceeding
110+
read -p "Proceed with version update? (y/n): " choice
111+
if [[ $choice != "y" && $choice != "Y" ]]; then
112+
echo -e "${YELLOW}Version update cancelled${RESET}"
113+
exit 0
114+
fi
115+
116+
# Update version in files
117+
update_version_in_cargo_toml "$new_version"
118+
119+
# Tag the release
120+
tag_release "$new_version"
121+
}
122+
123+
# Main script execution
124+
command=${1:-help}
125+
126+
case $command in
127+
major|minor|patch)
128+
bump_version "$command"
129+
;;
130+
current)
131+
current_version=$(get_current_version)
132+
echo -e "${BLUE}Current version: ${RESET}$current_version"
133+
;;
134+
help)
135+
usage
136+
;;
137+
*)
138+
echo -e "${RED}Unknown command: $command${RESET}"
139+
usage
140+
exit 1
141+
;;
142+
esac
143+
144+
exit 0

src/main.rs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use anyhow::Result;
22
use clap::Parser;
3+
use log::debug;
34
use smart_tree::rules::create_default_registry;
45
use smart_tree::{
56
format_tree, scan_directory, ColorTheme, DisplayConfig, GitIgnoreContext, SortBy,
67
};
78
use std::path::PathBuf;
89

910
#[derive(Parser, Debug)]
10-
#[command(author, version, about)]
11+
#[command(author, version, about, disable_version_flag = true)]
1112
struct Args {
1213
/// Directory path to display
1314
#[arg(default_value = ".")]
@@ -92,6 +93,10 @@ struct Args {
9293
/// Disable smart filtering rules completely
9394
#[arg(long)]
9495
no_rules: bool,
96+
97+
/// Display current version
98+
#[arg(short = 'v', long)]
99+
version: bool,
95100
}
96101

97102
fn init_logger() {
@@ -111,6 +116,13 @@ fn init_logger() {
111116
fn main() -> Result<()> {
112117
init_logger();
113118
let args = Args::parse();
119+
120+
// Check if version flag was used
121+
if args.version {
122+
let version = env!("CARGO_PKG_VERSION");
123+
println!("smart-tree version {}", version);
124+
return Ok(());
125+
}
114126

115127
// Determine if we should use emoji (default to true unless --no-emoji is specified)
116128
let use_emoji = if args.no_emoji {
@@ -119,6 +131,10 @@ fn main() -> Result<()> {
119131
args.emoji || !args.no_emoji
120132
};
121133

134+
// Clone the rules vectors for later usage
135+
let disable_rules = args.disable_rule.clone();
136+
let enable_rules = args.enable_rule.clone();
137+
122138
let config = DisplayConfig {
123139
max_lines: args.max_lines,
124140
dir_limit: args.dir_limit,
@@ -175,9 +191,24 @@ fn main() -> Result<()> {
175191
None
176192
} else {
177193
// Create the rule registry
178-
let registry = create_default_registry(&args.path)?;
179-
180-
// TODO: Handle enable/disable rules here
194+
let mut registry = create_default_registry(&args.path)?;
195+
196+
// Handle enable/disable rules
197+
if !disable_rules.is_empty() || !enable_rules.is_empty() {
198+
// Apply rule enabling/disabling
199+
200+
// Process rule disabling
201+
for rule_id in &disable_rules {
202+
debug!("Disabling rule: {}", rule_id);
203+
registry.disable_rule(rule_id);
204+
}
205+
206+
// Process rule enabling
207+
for rule_id in &enable_rules {
208+
debug!("Enabling rule: {}", rule_id);
209+
registry.enable_rule(rule_id);
210+
}
211+
}
181212

182213
Some(registry)
183214
};

src/rules.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,15 @@ pub trait FilterRule: Send + Sync {
217217
pub struct FilterRegistry {
218218
rules: Vec<Box<dyn FilterRule>>,
219219
threshold: f32,
220+
disabled_rules: Vec<String>,
220221
}
221222

222223
impl Default for FilterRegistry {
223224
fn default() -> Self {
224225
Self {
225226
rules: Vec::new(),
226227
threshold: 0.5, // Default threshold is 0.5
228+
disabled_rules: Vec::new(),
227229
}
228230
}
229231
}
@@ -246,13 +248,35 @@ impl FilterRegistry {
246248
pub fn set_threshold(&mut self, threshold: f32) {
247249
self.threshold = threshold.clamp(0.0, 1.0);
248250
}
251+
252+
/// Disable a specific rule by ID
253+
pub fn disable_rule(&mut self, rule_id: &str) {
254+
if !self.disabled_rules.contains(&rule_id.to_string()) {
255+
self.disabled_rules.push(rule_id.to_string());
256+
}
257+
}
258+
259+
/// Enable a previously disabled rule
260+
pub fn enable_rule(&mut self, rule_id: &str) {
261+
self.disabled_rules.retain(|id| id != rule_id);
262+
}
263+
264+
/// Check if a rule is disabled
265+
pub fn is_rule_disabled(&self, rule_id: &str) -> bool {
266+
self.disabled_rules.contains(&rule_id.to_string())
267+
}
249268

250269
/// Evaluate if a path should be hidden based on all applicable rules
251270
pub fn should_hide(&self, context: &FilterContext) -> Option<(bool, &str)> {
252271
let mut max_score = 0.0;
253272
let mut annotation = "[filtered]";
254273

255274
for rule in &self.rules {
275+
// Skip disabled rules
276+
if self.is_rule_disabled(rule.id()) {
277+
continue;
278+
}
279+
256280
if rule.applies_to(context) {
257281
let score = rule.evaluate(context);
258282
if score > max_score {

src/scanner.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ pub fn scan_directory(
9898
};
9999

100100
// For filtered directories, decide whether to traverse or just provide basic metadata
101-
let should_skip = should_filter;
101+
// If this is the root path that was explicitly specified, never skip it regardless of filter rules
102+
let is_direct_path = root.canonicalize().unwrap_or_else(|_| root.to_path_buf())
103+
== Path::new(&root).canonicalize().unwrap_or_else(|_| root.to_path_buf());
104+
let should_skip = should_filter && !is_direct_path;
102105

103106
if should_skip {
104107
debug!(

0 commit comments

Comments
 (0)