#!/usr/bin/env nu # Analyze Nushell Features Script # Parses Cargo.toml to detect available features and their dependencies # # Usage: # analyze_nushell_features.nu # Show all features # analyze_nushell_features.nu --validate # Validate selected features # analyze_nushell_features.nu --export # Export to JSON use lib/common_lib.nu * # Configuration for desired features const DESIRED_FEATURES = [ "mcp", "plugin", "sqlite", "trash-support", "system-clipboard" ] # Main entry point def main [ --validate # Validate desired features --export # Export feature analysis to JSON --show-all # Show all features (not just desired) ] { log_info "Nushell Feature Analysis" # Check nushell source exists let nushell_dir = "./nushell" if not ($nushell_dir | path exists) { log_error "Nushell source not found at [$nushell_dir]" log_info "Run: download_nushell.nu " exit 1 } # Parse Cargo.toml let cargo_toml = $"($nushell_dir)/Cargo.toml" let features = parse_features $cargo_toml # Show version info let version = open $cargo_toml | get package.version log_info $"Analyzing Nushell version: ($version)" # Display features if $show_all { display_all_features $features } else { display_desired_features $features $DESIRED_FEATURES } # Validate if requested if $validate { validate_features $features $DESIRED_FEATURES } # Export if requested if $export { export_feature_analysis $features $version } } # Parse features from Cargo.toml def parse_features [ cargo_toml_path: string ]: nothing -> table { log_info "Parsing features from Cargo.toml..." let cargo_data = try { open $cargo_toml_path } catch { log_error $"Failed to read ($cargo_toml_path)" exit 1 } # Extract features section let features_raw = try { $cargo_data | get features } catch { log_error "No [features] section found in Cargo.toml" exit 1 } # Convert to table format let features_table = $features_raw | transpose feature dependencies | each {|row| { feature: $row.feature dependencies: ($row.dependencies | if ($in | describe) == "list" { $in } else { [] }) is_default: ($row.feature == "default") } } log_success $"Found ($features_table | length) features" $features_table } # Display all available features def display_all_features [ features: table ] { log_info "\n=== All Available Features ===" $features | each {|feat| let dep_str = if ($feat.dependencies | is-empty) { "(no dependencies)" } else { $feat.dependencies | str join ", " } let marker = if $feat.is_default { " [DEFAULT]" } else { "" } print $" • ($feat.feature)($marker)" print $" Dependencies: ($dep_str)" } } # Display only desired features def display_desired_features [ features: table desired: list ] { log_info "\n=== Desired Features Analysis ===" $desired | each {|desired_feat| let feat_info = $features | where feature == $desired_feat | first if ($feat_info | is-empty) { log_error $"Feature '($desired_feat)' NOT FOUND in Cargo.toml" } else { let dep_str = if ($feat_info.dependencies | is-empty) { "none" } else { $feat_info.dependencies | str join ", " } log_success $"✓ ($desired_feat)" log_info $" Dependencies: ($dep_str)" } } } # Validate desired features exist and resolve dependencies def validate_features [ features: table desired: list ] { log_info "\n=== Feature Validation ===" mut validation_passed = true # Check each desired feature exists for desired_feat in $desired { let feat_info = $features | where feature == $desired_feat if ($feat_info | is-empty) { log_error $"Feature '($desired_feat)' does not exist" $validation_passed = false } else { log_success $"✓ Feature '($desired_feat)' exists" # Validate dependencies recursively let feat_data = $feat_info | first if not ($feat_data.dependencies | is-empty) { validate_feature_dependencies $features $feat_data.dependencies 1 } } } # Check for conflicts (optional - advanced) check_feature_conflicts $features $desired if $validation_passed { log_success "\n✅ All desired features are valid!" } else { log_error "\n❌ Feature validation failed" exit 1 } } # Validate feature dependencies recursively def validate_feature_dependencies [ features: table dependencies: list depth: int ] { let indent = (0..<$depth | each {|_| " "} | str join) for dep in $dependencies { # Check if dependency is another feature let dep_info = $features | where feature == $dep if ($dep_info | is-empty) { # Not a feature, might be a crate dependency log_debug $"($indent)→ ($dep) (external crate)" } else { log_info $"($indent)→ ($dep) (feature)" # Recursively check dependencies let dep_data = $dep_info | first if not ($dep_data.dependencies | is-empty) { validate_feature_dependencies $features $dep_data.dependencies ($depth + 1) } } } } # Check for known feature conflicts def check_feature_conflicts [ features: table desired: list ] { log_info "\nChecking for known feature conflicts..." # Known conflicts (example: native-tls vs rustls-tls) let conflict_groups = [ ["native-tls", "rustls-tls"] ] mut has_conflicts = false for group in $conflict_groups { let found_in_group = $desired | where {|it| $it in $group} if ($found_in_group | length) > 1 { log_warn $"Potential conflict: ($found_in_group | str join ' vs ')" $has_conflicts = true } } if not $has_conflicts { log_success "No known conflicts detected" } } # Export feature analysis to JSON def export_feature_analysis [ features: table version: string ] { let output_file = "./tmp/feature_analysis.json" ensure_dir "./tmp" let analysis = { nushell_version: $version analyzed_at: (date now | format date "%Y-%m-%d %H:%M:%S") total_features: ($features | length) desired_features: $DESIRED_FEATURES all_features: $features } $analysis | to json | save -f $output_file log_success $"Feature analysis exported to: ($output_file)" } # Show feature dependency tree def "main tree" [ feature: string # Feature to show tree for ] { let nushell_dir = "./nushell" let cargo_toml = $"($nushell_dir)/Cargo.toml" if not ($cargo_toml | path exists) { log_error "Nushell source not found" exit 1 } let features = parse_features $cargo_toml let feat_info = $features | where feature == $feature if ($feat_info | is-empty) { log_error $"Feature '($feature)' not found" exit 1 } log_info $"Dependency tree for: ($feature)" let feat_data = $feat_info | first show_dependency_tree $features $feature $feat_data.dependencies 0 } # Recursively show dependency tree def show_dependency_tree [ features: table feature_name: string dependencies: list depth: int ] { let indent = (0..<$depth | each {|_| " "} | str join) print $"($indent)📦 ($feature_name)" if not ($dependencies | is-empty) { for dep in $dependencies { let dep_info = $features | where feature == $dep if ($dep_info | is-empty) { let dep_indent = (0..<($depth + 1) | each {|_| " "} | str join) print $"($dep_indent)└─ ($dep) (external)" } else { let dep_data = $dep_info | first show_dependency_tree $features $dep $dep_data.dependencies ($depth + 1) } } } } # Generate build command with desired features def "main build-cmd" [] { let nushell_dir = "./nushell" let cargo_toml = $"($nushell_dir)/Cargo.toml" if not ($cargo_toml | path exists) { log_error "Nushell source not found" exit 1 } let features = parse_features $cargo_toml validate_features $features $DESIRED_FEATURES let features_str = $DESIRED_FEATURES | str join "," log_info "Build command with desired features:" print "" print $" cargo build --workspace --release --features \"($features_str)\"" print "" log_info "To build nushell with these features, run:" print $" cd nushell && cargo build --workspace --release --features \"($features_str)\"" } # List features by category def "main categorize" [] { let nushell_dir = "./nushell" let cargo_toml = $"($nushell_dir)/Cargo.toml" if not ($cargo_toml | path exists) { log_error "Nushell source not found" exit 1 } let features = parse_features $cargo_toml # Categorize features (heuristic based on name patterns) let categories = { "Core Features": ($features | where {|f| $f.feature in ["default", "full", "stable"]}), "Network Features": ($features | where {|f| $f.feature =~ "tls|network"}), "Plugin Features": ($features | where {|f| $f.feature =~ "plugin|mcp"}), "Storage Features": ($features | where {|f| $f.feature =~ "sqlite|trash"}), "System Features": ($features | where {|f| $f.feature =~ "clipboard|system"}), "Other Features": ($features | where {|f| $f.feature not-in ["default", "full", "stable"] and ($f.feature !~ "tls|network|plugin|mcp|sqlite|trash|clipboard|system") }) } for category in ($categories | columns) { let feats = $categories | get $category if ($feats | length) > 0 { log_info $"\n=== ($category) ===" $feats | each {|f| print $" • ($f.feature)" } } } }