docs: public static void generateBashAutoComplete(JCommander jCommander, String fileName, String bashFunctName, List<String> excludeParams)
              throws IOException {
          Map<String, JCommander> jCommands = jCommander.getCommands();
          StringBuilder mainCommands = new StringBuilder();
          StringBuilder subCommmands = new StringBuilder();
          StringBuilder subCommmandsOptions = new StringBuilder();
  
          // Create a HashSet to skip excluded parameters
          Set<String> excludeParamsSet;
          if (excludeParams == null) {
              excludeParamsSet = new HashSet<>();
          } else {
              excludeParamsSet = new HashSet<>(excludeParams);
          }
  
          for (String command : jCommands.keySet()) {
              JCommander subCommand = jCommands.get(command);
              mainCommands.append(command).append(" ");
              subCommmands.append(command + "=" + "\"");
              Map<String, JCommander> subSubCommands = subCommand.getCommands();
              for (String sc : subSubCommands.keySet()) {
                  subCommmands.append(sc).append(" ");
                  subCommmandsOptions.append(command + "_");
                  // - is not allowed in bash main variable name, replacing it with _
                  subCommmandsOptions.append(sc.replaceAll("[-.]+", "_") + "_" + "options=" + "\"");
                  JCommander subCommandOptions = subSubCommands.get(sc);
                  for (ParameterDescription param : subCommandOptions.getParameters()) {
                      // Add parameter if it is not excluded
                      if (!excludeParamsSet.contains(param.getLongestName())) {
                          subCommmandsOptions.append(param.getLongestName()).append(' ');
                      }
                  }
                  subCommmandsOptions.replace(0, subCommmandsOptions.length(), subCommmandsOptions.toString().trim()).append("\"" + "\n");
              }
              subCommmands.replace(0, subCommmands.length(), subCommmands.toString().trim()).append("\"" + "\n");
          }
  
          // Commands(Commands, subCommands and subCommandOptions) are populated intro three strings until this point,
          // Now we write bash script commands and blend these strings into those as appropriate
          StringBuilder autoComplete = new StringBuilder();
          autoComplete.append("_" + bashFunctName + "() \n { \n local cur prev opts \n COMPREPLY=() \n cur="
                  + "$" + "{COMP_WORDS[COMP_CWORD]} \n prev=" + "$" + "{COMP_WORDS[COMP_CWORD-1]} \n");
  
          autoComplete.append("commands=\"").append(mainCommands.toString().trim()).append('"').append('\n');
          autoComplete.append(subCommmands.toString());
          autoComplete.append(subCommmandsOptions.toString());
          autoComplete.append("if [[ ${#COMP_WORDS[@]} > 2 && ${#COMP_WORDS[@]} < 4 ]] ; then \n local options \n case "
                  + "$" + "{prev} in \n");
  
          for (String command : mainCommands.toString().split(" ")) {
              autoComplete.append("\t" + command + ") options=" + "\"" + "${" + command + "}" + "\"" + " ;; \n");
          }
  
          autoComplete.append("*) ;; \n esac \n COMPREPLY=( $( compgen -W " + "\"" + "$" + "options" + "\""
                  + " -- ${cur}) ) \n return 0 \n elif [[ ${#COMP_WORDS[@]} > 3 ]] ; then \n local options \n case " + "$"
                  + "{COMP_WORDS[1]} in \n");
  
          int subCommandIndex = 0;
          for (String command : mainCommands.toString().split(" ")) {
              String[] splittedSubCommands = subCommmands.toString().split("\n")[subCommandIndex]
                      .replace(command + "=" + "\"", "").replace("\"", "").split(" ");
  
              if (splittedSubCommands[0].isEmpty()) {
                  ++subCommandIndex;
              } else {
                  autoComplete.append('\t').append(command).append(") \n");
                  autoComplete.append("\t\t case " + "$" + "{COMP_WORDS[2]} in \n");
  
                  for (String subCommand : splittedSubCommands) {
                      autoComplete.append("\t\t").append(subCommand).append(") options=").append("\"").append("${").
                              append(command).append("_").append(subCommand.replaceAll("[-.]+", "_")).append("_options}").
                              append("\"").append(" ;; \n");
                  }
                  autoComplete.append("\t\t *) ;; esac ;; \n");
                  ++subCommandIndex;
              }
          }
          autoComplete.append("*) ;;  esac \n COMPREPLY=( $( compgen -W " + "\"" + "$" + "options" + "\"" + " -- ${cur}) )"
                  + " \n return 0 \n fi \n if [[ ${cur} == * ]] ; then \n COMPREPLY=( $(compgen -W " + "\"" + "$"
                  + "{commands}" + "\"" + " -- ${cur}) ) \n return 0 \n fi \n } \n");
  
          autoComplete.append("\n");
          autoComplete.append("complete -F _" + bashFunctName + " " + bashFunctName + ".sh").append('\n');
  //        autoComplete.append("complete -F _" + bashFunctName + " ./bin/" + bashFunctName + ".sh").append('\n');
  //        autoComplete.append("complete -F _" + bashFunctName + " /opt/" + bashFunctName + "/bin/" + bashFunctName + ".sh").append('\n');
  
          try {
              PrintWriter pw = new PrintWriter(new FileOutputStream(fileName));
              pw.write(autoComplete.toString());
              pw.close();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }