repent/rwanda

View on GitHub
lib/rwanda.rb

Summary

Maintainability
B
4 hrs
Test Coverage
#!/usr/bin/env ruby

require 'rwanda/array'
require 'rwanda/version'
require 'rwanda/village'
require 'rwanda/location'

require 'singleton'
require 'fuzzy_match'
require 'csv'
require 'pry'

DATA = File.join(File.dirname(File.expand_path(__FILE__)), 'rwanda/data.csv')

class Rwanda
  include Singleton
  
  attr_accessor :villages
  DIVISIONS=[:province,:district,:sector,:cell,:village]
  RW = {
    'Northern Province' => 'Amajyaruguru',
    'Southern Province' => 'Amajyepfo',
    'Eastern Province' => 'Iburasirazuba',
    'Western Province' => 'Iburengerazuba',
    'Kigali City' => 'Umujyi wa Kigali'
  }
  
  def initialize
    @villages = []
    CSV.foreach(DATA, headers: :first_row) do |row|
      villages << Village.new(row)
    end
  end

  # Singular Ofs ((
  def province_of(district, rw=false)
    #village = @villages.select_first {|v| v.district.downcase == district.downcase}
    village = @villages.select {|v| v.district.downcase == district.downcase}.first
    if village
      if rw then RW[village.province] else village.province end
    else
      nil
    end
  end
  def district_of(sector)
    villages = @villages.select {|v| v.sector.downcase == sector.downcase}.collect {|v| { sector: v.sector, district: v.district } }.uniq
    case villages.length
      when 0
        nil
      when 1
        villages[0][:district]
      else
        villages.collect {|v| v[:district] }.sort
    end
  end
  # )) Plural Ofs ((
  def districts_of(province)
    districts = @villages.select {|v| v.province.downcase == province.downcase }.collect {|v| v.district}.uniq
    #binding.pry
    districts.empty? ? nil : districts
  end
  def sectors_of(district)
    sectors = @villages.select {|v| v.district.downcase == district.downcase }.collect {|v| v.sector}.uniq
    sectors.empty? ? nil : sectors
  end
  def cells_of(district, sector)
    @villages.select {|v| v.district.downcase == district.downcase and v.sector.downcase == sector.downcase}.collect {|v| v.cell}.uniq
  end
  def villages_of(district, sector, cell)
    @villages.select {|v| v.district.downcase == district.downcase and v.sector.downcase == sector.downcase and v.cell.downcase == cell.downcase}.collect {|v| v.village}
    #@villages.select {|v| v.to_h(:district, :sector, :cell).to_a.sort == { district: district, sector: sector, cell: cell }.to_a.collect{|v| [v[0],v[1].downcase] }.collect {|v| v.village}
  end
  def subdivisions_of(arr)
    case arr.length
      when 0
        districts
      when 1
        sectors_of *arr
      when 2
        cells_of *arr
      when 3
        villages_of *arr
      else
        raise "subdivisions_of requires an array of between 0 and 3 elements (do NOT include a province): received #{arr}"
    end
  end
  # )) Calleds ((
  #def districts_called(district)
  #  @villages
  #end
  # )) Lists ((
  def provinces; @villages.collect{|v| v.province}.uniq.sort; end
  def districts; @villages.collect{|v| v.district}.uniq.sort; end
  # already introduces ambiguity from sectors down: 37 districts are duplicate names
  def sectors
    @sectors ||= @villages.collect {|v| [v.district, v.sector] }.uniq.collect {|ds| ds[1]}.sort
    #@villages.collect{|v| v.sector}.uniq
  end
  
  # )) Matching ((
  def province_like(province)
    @fmp ||= FuzzyMatch.new(provinces)
    @fmp.find(province)
  end
  def district_like(district)
    @fmd ||= FuzzyMatch.new(districts)
    @fmd.find(district)
  end
  def sector_like(sector)
    # Already problematic here: there are identical sector names
    @fms ||= FuzzyMatch.new(sectors)
    @fms.find(sector)
  end
  
  # )) Testing ((
  # is_division?
  DIVISIONS.each do |division|
    define_method("is_#{division}?") do |argument|
      @villages.any? {|v| v.send(division).downcase == argument.downcase}
    end
  end
  def exist?(district=false, sector=false, cell=false, village=false)
    villages = @villages.dup
    return false unless district
    {district: district, sector: sector, cell: cell, village: village}.each_pair do |division_name,division|
      #binding.pry
      return true unless division
      villages.select! {|v| v.send(division_name).downcase == division.downcase}
      return false if villages.empty?
    end
    true    
  end
  def exists?(*p); exist?(*p); end
  def valid?(*p)
    # Is this location either (a) real, or (b) nil (both of which are valid if an object's address can be null)
    return true if p.reject{|i| i.nil?}.empty? # all nils is fine
    exist? *p
  end
  
  # )) Translation ((
  def translate(province)
    kin = RW.find { |eng,kin| eng.downcase == province.downcase } # returns [key, val]
    return kin[1] if kin
    eng = RW.find { |eng,kin| kin.downcase == province.downcase }
    return eng[0] if eng
    nil
  end
  
  # )) To help with testing ((
  def first
    @villages.first
  end
  
  # )) Where is...? ((
  def where_is?(division)
    #matching = { province: [], district: [], sector: [], cell: [], village: [] }
    lines = []
    @villages.each do |village|
      matches = village.match(division)
      unless matches.empty?
        matches.each do |div|
          #matching[div].push village
          # convert each match into a line
          lines << create_line(village, div)
        end
      end
    end
    lines.uniq!
    print_result(lines, division)
  end
  
  private
  def create_line(village, div)
    new_line = "  #{village.send(div)} is a #{div}#{' in' unless div==:province}"
    unless div == :province
      (0...Rwanda::DIVISIONS.index(div)).to_a.reverse.each do |n|
        new_line << " #{village[n]}#{' '+Rwanda::DIVISIONS[n].to_s.capitalize+',' unless n==0}"
      end
    end
    new_line
  end
  def print_result(lines, division)
    output = ''
    
    # summary line
    if lines.empty?
      output += "Rwanda has no divisions called #{division.capitalize}\n"
    else
      output += 'Rwanda has'
      n = []
      comma = ''
      (0..3).each do |i|
        n = lines.count { |l| l.count(',') == i }
        output << "#{comma} #{i == 3 ? 'and ' : ''}#{n} #{Rwanda::DIVISIONS[i+1]}#{n == 1 ? '' : 's'}"
        comma = ','
      end
      output << " called #{division.capitalize}:\n"
    end
    
    # detail
    lines.each { |line| output << line << "\n" }
    puts output
  end
end