366 lines
10 KiB
Plaintext
366 lines
10 KiB
Plaintext
|
|
#!/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)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|