#!/bin/bash # ----------- # Cubit Generator Script # ----------- # Usage: ./cubit_generator.sh --class_name= --target_dir= [--auto_run_build=true|false] # Example: ./cubit_generator.sh --class_name=Chat --target_dir=lib/ui/pages --auto_run_build=false # # # chmod +x cubit_generator.sh # # # Read package name from pubspec.yaml PUBSPEC="pubspec.yaml" if [ -f "$PUBSPEC" ]; then PACKAGE_NAME=$(grep -m1 '^name:' "$PUBSPEC" | sed 's/name:\s*//' | tr -d '[:space:]') else PACKAGE_NAME="unknown" fi CLASS_NAME="" OUTPUT_DIR="" AUTO_RUN_BUILD="false" for arg in "$@"; do case $arg in --class_name=*) CLASS_NAME="${arg#*=}" ;; --target_dir=*) OUTPUT_DIR="${arg#*=}" ;; --auto_run_build=*) AUTO_RUN_BUILD="${arg#*=}" ;; esac done # If class name is not provided as an argument, prompt the user if [[ -z "$CLASS_NAME" ]]; then echo -n "📦 Enter the class name (e.g., chat, videos, call, follow_users): " read CLASS_NAME fi echo "className: $CLASS_NAME" echo "outputDir: $OUTPUT_DIR" echo "autoRunBuild: $AUTO_RUN_BUILD" echo "package: $PACKAGE_NAME" # Validate if [[ -z "$CLASS_NAME" ]]; then echo "❌ Feature name cannot be empty." exit 1 fi # Name conversions: snake_case, PascalCase, camelCase snake_case=$(echo "$CLASS_NAME" \ | sed -E 's/([a-z0-9])([A-Z])/\1_\2/g' \ | tr '[:upper:]' '[:lower:]' \ | tr -s ' _-' '_' \ | tr -cd '[:alnum:]_') pascal_case=$(echo "$CLASS_NAME" \ | awk -F '[_ -]' '{for (i=1; i<=NF; i++) if ($i!="") $i=toupper(substr($i,1,1)) substr($i,2)}1' OFS='') camel_case=$(echo "${pascal_case:0:1}" | tr '[:upper:]' '[:lower:]')${pascal_case:1} # Target dir OUTPUT_DIR="${OUTPUT_DIR#/}" target_dir="$OUTPUT_DIR/$snake_case" import_dir="${target_dir#lib/}" mkdir -p "$target_dir" # Files state_file="$target_dir/${snake_case}_state.dart" view_model_file="$target_dir/${snake_case}_view_model.dart" page_file="$target_dir/${snake_case}_page.dart" # -------------------------- # STATE TEMPLATE # -------------------------- cat > "$state_file" < "$view_model_file" < init() async { } void dispose() { } void setLoading(bool value) => state = state.copyWith(isLoading: value); void showMessage(String message) => state = state.copyWith(userMessage: UserMessage(message)); void clearMessage() => state = state.copyWith(userMessage: null); void navigate(${pascal_case}Navigator nav) => state = state.copyWith(goTo: nav); void clearNavigation() => state = state.copyWith(goTo: null); } EOF # -------------------------- # PAGE TEMPLATE # -------------------------- cat > "$page_file" < useEffect(() { viewModel.init(); return () { viewModel.dispose(); }; }, [viewModel]); void _goTo( BuildContext context, ${pascal_case}ViewModel viewModel, ${pascal_case}State state, ) => useEffect(() { WidgetsBinding.instance.addPostFrameCallback((_) async { switch (state.goTo) { // break; case null: break; } }); return null; }, [state.goTo]); } EOF # Remove conflicting outputs conflicts=( "$target_dir/${snake_case}_state.freezed.dart" "$target_dir/${snake_case}_view_model.g.dart" ) for file in "${conflicts[@]}"; do [ -f "$file" ] && rm "$file" done # Ask to run build_runner if [[ -z "$AUTO_RUN_BUILD" ]]; then echo -n "❓ Do you want to run build_runner now? (yes/no): " # shellcheck disable=SC2162 read run_build AUTO_RUN_BUILD="$run_build" fi if [[ "$AUTO_RUN_BUILD" == "true" || "$AUTO_RUN_BUILD" == "yes" ]]; then dart run build_runner build --delete-conflicting-outputs if [[ $? -eq 0 ]]; then echo "✅ build_runner finished successfully" else echo "❌ build_runner failed" fi else echo "⏭️ Skipped build_runner" fi echo "🎉 Feature \"$pascal_case\" generated in ${target_dir}"