<commit_before>require "pro/version"
require "find"
require "fuzzy_match"
require "colored"

SHELL_FUNCTION = <<END

# pro cd function
{{name}}() {
  local projDir=$(pro search $1)
  cd ${projDir}
}
END

CD_INFO = <<END
This installs a command to allow you to cd to a git repo
arbitrarily deep in your PRO_BASE based on fuzzy matching.

Example:

  ~/randomFolder/ $ pd pro
  pro/ $ pwd
  /Users/tristan/Box/Dev/Projects/pro

========
END


module Pro
  DIRTY_MESSAGE = 'uncommitted'.red
  UNPUSHED_MESSAGE = 'unpushed'.blue
  JOIN_STRING = ' + '
  # Finds the base directory where repos are kept
  # Checks the environment variable PRO_BASE and the
  # file .proBase
  def self.base_dir()
    # check environment first
    base = ENV['PRO_BASE']
    return base if base
    # next check proBase file
    path = ENV['HOME'] + "/.proBase"
    base = ENV['HOME'] # default to home
    if File.exists?(path)
      base = IO.read(path).chomp
      base = File.expand_path(base)
    end
    base
  end

  # Searches for all the git repositories in the base directory.
  # returns an array of [repo_name, path] pairs.
  def self.repo_list
    repos = []
    Find.find(Pro.base_dir) do |path|
      if FileTest.directory?(path)
        # is this folder a git repo
        if File.exists?(path+"/.git")
          base_name = File.basename(path)
          repos << [base_name,path]
          Find.prune
        end
      end
    end
    repos
  end

  # Fuzzy search for a git repository by name
  # Returns the full path to the repository.
  def self.find_repo(name)
    repos = Pro.repo_list
    match = FuzzyMatch.new(repos, :read => :first).find(name)
    match[1] unless match.nil?
  end

  def self.run_command(command, confirm = true)
    if confirm
      print "Do you really want to run '#{command.bold}' on all repos [Y/n]? "
      ans = STDIN.gets
      return if ans.chomp != "Y"
    end
    repos = Pro.repo_list
    repos.each do |r|
      Dir.chdir(r[1])
      result = `#{command}`
      puts "#{r.first}:".bold.red
      puts result
    end
  end

  # prints a status list showing repos with
  # unpushed commits or uncommitted changes
  def self.status
    repos = Pro.repo_list
    max_name = repos.map {|pair| pair.first.length}.max + 1
    repos.each do |pair|
      path = pair.last
      status = Pro.repo_status(path)
      next if status.empty?
      name = format("%-#{max_name}s",pair.first).bold
      puts "#{name} > #{status}"
    end
  end

  # returns a short status message for the repo
  def self.repo_status(path)
    messages = []
    messages << DIRTY_MESSAGE unless Pro.repo_clean?(path)
    messages << UNPUSHED_MESSAGE if Pro.repo_unpushed?(path)
    messages.join(JOIN_STRING)
  end

  # Checks if there are any uncommitted changes
  def self.repo_clean?(path)
    status = ""
    Dir.chdir(path) do
      status = `git status 2>/dev/null`
    end
    return status.end_with?("(working directory clean)\n")
  end

  # Finds if there are any commits which have not been pushed to origin
  def self.repo_unpushed?(path)
    unpushed = ""
    Dir.chdir(path) do
      branch_ref = `/usr/bin/git symbolic-ref HEAD 2>/dev/null`
      branch = branch_ref.chomp.split('/').last
      unpushed = `git cherry -v origin/#{branch} 2>/dev/null`
    end
    return !(unpushed.empty?)
  end

  # Adds a shell function to the shell config files that
  # allows easy directory changing.
  def self.install_cd
    puts CD_INFO
    print "Continue with installation (yN)? "
    return unless gets.chomp == "y"
    # get name
    print "Name of pro cd command (default 'pd'): "
    name = gets.strip<commit_msg>Fix bug #1<commit_after>require "pro/version"
require "find"
require "fuzzy_match"
require "colored"

SHELL_FUNCTION = <<END

# pro cd function
{{name}}() {
  local projDir=$(pro search $1)
  cd ${projDir}
}
END

CD_INFO = <<END
This installs a command to allow you to cd to a git repo
arbitrarily deep in your PRO_BASE based on fuzzy matching.

Example:

  ~/randomFolder/ $ pd pro
  pro/ $ pwd
  /Users/tristan/Box/Dev/Projects/pro

========
END


module Pro
  DIRTY_MESSAGE = 'uncommitted'.red
  UNPUSHED_MESSAGE = 'unpushed'.blue
  JOIN_STRING = ' + '
  # Finds the base directory where repos are kept
  # Checks the environment variable PRO_BASE and the
  # file .proBase
  def self.base_dir()
    # check environment first
    base = ENV['PRO_BASE']
    return base if base
    # next check proBase file
    path = ENV['HOME'] + "/.proBase"
    base = ENV['HOME'] # default to home
    if File.exists?(path)
      base = IO.read(path).chomp
      base = File.expand_path(base)
    end
    base
  end

  # Searches for all the git repositories in the base directory.
  # returns an array of [repo_name, path] pairs.
  def self.repo_list
    repos = []
    Find.find(Pro.base_dir) do |path|
      if FileTest.directory?(path)
        # is this folder a git repo
        if File.exists?(path+"/.git")
          base_name = File.basename(path)
          repos << [base_name,path]
          Find.prune
        end
      end
    end
    repos
  end

  # Fuzzy search for a git repository by name
  # Returns the full path to the repository.
  def self.find_repo(name)
    repos = Pro.repo_list
    match = FuzzyMatch.new(repos, :read => :first).find(name)
    match[1] unless match.nil?
  end

  def self.run_command(command, confirm = true)
    if confirm
      print "Do you really want to run '#{command.bold}' on all repos [Y/n]? "
      ans = STDIN.gets
      return if ans.chomp != "Y"
    end
    repos = Pro.repo_list
    repos.each do |r|
      Dir.chdir(r[1])
      result = `#{command}`
      puts "#{r.first}:".bold.red
      puts result
    end
  end

  # prints a status list showing repos with
  # unpushed commits or uncommitted changes
  def self.status
    repos = Pro.repo_list
    max_name = repos.map {|pair| pair.first.length}.max + 1
    repos.each do |pair|
      path = pair.last
      status = Pro.repo_status(path)
      next if status.empty?
      name = format("%-#{max_name}s",pair.first).bold
      puts "#{name} > #{status}"
    end
  end

  # returns a short status message for the repo
  def self.repo_status(path)
    messages = []
    messages << DIRTY_MESSAGE unless Pro.repo_clean?(path)
    messages << UNPUSHED_MESSAGE if Pro.repo_unpushed?(path)
    messages.join(JOIN_STRING)
  end

  # Checks if there are any uncommitted changes
  def self.repo_clean?(path)
    status = ""
    Dir.chdir(path) do
      status = `git status 2>/dev/null`
    end
    return status.end_with?("(working directory clean)\n") || status.end_with?("working directory clean\n")
  end

  # Finds if there are any commits which have not been pushed to origin
  def self.repo_unpushed?(path)
    unpushed = ""
    Dir.chdir(path) do
      branch_ref = `/usr/bin/git symbolic-ref HEAD 2>/dev/null`
      branch = branch_ref.chomp.split('/').last
      unpushed = `git cherry -v origin/#{branch} 2>/dev/null`
    end
    return !(unpushed.empty?)
  end

  # Adds a shell function to the shell config files that
  # allows easy directory changing.
  def self.install_cd
    puts CD_INFO
    print "Continue with installation (yN)? "
    return unless gets.chomp == "y"
    # get name
    print "Name of pro cd command (default 'pd'): "
    name = gets.strip
{'fraction_non_alphanumeric': 0.10885659446120098, 'fraction_numerical': 0.002056484782012613, 'mean_word_length': 2.7049263585576435, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2033848', 'n_tokens_mistral': 2466, 'n_tokens_neox': 2407, 'n_words': 750}