nushell-plugins/scripts/analyze_nushell_features.nu

366 lines
10 KiB
Plaintext
Raw Permalink Normal View History

#!/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 <version>"
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<string>
] {
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<string>
] {
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<string>
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<string>
] {
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<string>
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)"
}
}
}
}