Improving the Latin Command-Line Verb Conjugator

latverb -m -c "am\={o}, am\={a}re, am\={a}v\={\i}, amatus"
[amō amāre amāvī amatus {1}]

Active System
First Singular  amō       amābam          amābō
Second Singular amās      amābās         amābis
Third Singular  amat       amābat          amābit
First Plural    amāmus    amābāmus       amābimus
Second Plural   amātis    amābātis       amābitis
Third Plural    amant      amābant         amābunt


Perfect System
First Singular  amāvī    amāveram        amāverō
Second Singular amāvistī amāverās       amāveris
Third Singular  amāvit    amāverat        amāverit
First Plural    amāvimus  amāverāmus     amāverimus
Second Plural   amāvistis amāverātis     amāveritis
Third Plural    amāvērunt amāverant       amāverint


Passive System
First Singular  amor       amābar          amābō
Second Singular amāris    amābāris       amāberis
Third Singular  amātur    amābātur       amābitur
First Plural    amāmur    amābāmur       amābimur
Second Plural   amāminī  amābāminī     amābiminī
Third Plural    amantur    amābantur       amābintur


Passive Perfect System
First Singular amatus, amata, amatum      sum     eram     erō
Second Singular amatus, amata, amatum       es    erās     eris
Third Singular amatus, amata, amatum      est     erat     erit
First Plural amatī, amatae, amata    sumus  erāmus   erimus
Second Plural amatī, amatae, amata    estis  erātis   eritis
Third Plural amatī, amatae, amata     sunt    erant    erint


Participles associated with: amō, amāre, amāvī, amatus 
Present Active Participle: amāns, amantis
Future Active Participle: amatūrus, amatūra, amatūrum
Perfect Passive Participle amatus, amata, amatum
Future Passive Participle amandus, amanda, amandum


Infinitives associated with: amō, amāre, amāvī, amatus 
Present Active Infinitive: amāre
Perfect Active Infinitive: amāvīsse
Future Active Infinitive: amatūrus + esse
Present Passive Infinitive amī
Perfect Passive Infinitive amatus + esse


Imperatives:

amā
amāte

[code lang="ruby"]
#!/usr/bin/env ruby -Ku -rjcode
require 'rubygems'
require 'activesupport'
require 'getoptlong'
require 'pp'



module LatinSpelling
  @@macron_table = {"\xc4\x81" => 'a', "\xc4\x93" => 'e',"\xc4\xab"=>'i',"\xc5\x8d"=>'o',"\xc5\xab"=>'u'}

  def get_last_two_chars(input)
    return [ input.chars[-2,1].to_s.chomp, input.chars[-1,1].to_s.chomp ]
  end
  
  def check_macrons(input)
    # Three rules:  Long vowels are shortened when they are followed
    # 1.  by another vowel
    # 2.  by m/r/t at the end of a word
    # 3.  by nt or nd anywhere
    
    if (input.class.to_s == "Array")
      return input.map do |person|
        check_macrons(person)
      end
    end

    if (input.class.to_s == "String")
      # 1.  preceeding another macronized letter
      input = repair_double_macron(input).to_s if input =~ /[?????][?????aeiou]/
    
      # 2.  by m/r/t at the end of a word   
      if input =~ /[mrt]$/
           last_two=get_last_two_chars(input)
           if @@macron_table.keys.include?("#{last_two[0]}")
             last_two[0] = @@macron_table["#{last_two[0]}"] 
             returnString = input.chars[0..(input.jlength()-3)] + last_two.join('').to_s
             input = returnString.to_s
           end # end if @@macron_table
         end # end if input =~/[mrt]/    

      # 3.  by nt or nd anywhere
      input = repair_nt(input).to_s if input =~ /[?????]n[td]/
    end
    return input
  end # end check_macrons

  def repair_double_macron(dmString)
    return dmString if dmString !~ /[?????][?????aeiou]/
    
    dblPosition = dmString =~ /([?????][?????aeiou])/

    base=dmString[0..dblPosition-1]
    rest=dmString.chars[dblPosition+1..99]
    firstDblMacChar=$1.chars[0..0]

    base=base+ @@macron_table[firstDblMacChar.to_s] \
      if @@macron_table.keys.include?(firstDblMacChar)
      
    return base + repair_double_macron(rest)
  end
  
  def repair_nt(ntString)
    return ntString if ntString !~ /n[td]/
    
    ntPosition = ntString =~ /(n[td])/
        
    base=ntString[0..ntPosition-1]
    testchar=base.chars[base.jlength-1..base.jlength-1]
    rest=ntString.chars[base.jlength..99]
    nSuffix=rest.chars[0..1]
    rest=rest.chars[2..99]
    base=base.chars[0..base.jlength-2]
        
    if @@macron_table.keys.include?(testchar)
      base=base + @@macron_table[testchar.to_s] 
    end
    
    return base + nSuffix + repair_nt(rest)
  end
end

class Verb < Object
  include LatinSpelling
  attr_reader :firstpersonform, :infinitive, 
              :perfectstem, :passperfpart, :stem, :conjugation, :singular_string, :plural_string
  attr_writer :singular_string, :plural_string            

  def initialize(initString)
    @definition_string=initString
    (@firstpersonform, @infinitive, @perfectstem, @passperfpart)=initString.split(/,\s+/)
    pparts=initString.split(/,\s+/)
    @passive_endings = ["r", "ris", "tur", "mur", "min\xc4\xab", "ntur"]
    @personages = ["First Singular", "Second Singular", "Third Singular",
                   "First Plural", "Second Plural", "Third Plural"]
    @conjugation = evaluate_conjugation
    @singular_string= @plural_string = String.new
  end

  def active_present
    oneTwoEndings=%w(s t mus tis nt)
    thirdEndings=%w(o is it imus itis unt)
    thirdIOEndings=%w(is it imus itis iunt)

    if @conjugation == "1" or @conjugation == "2"
      return check_macrons([firstpersonform, oneTwoEndings.collect{|x| stem + x}]).flatten!
    end
    if @conjugation =="3"
      return check_macrons([thirdEndings.collect{|x| stem + x}]).flatten!
    end
    if conjugation=="3-io" or conjugation=="4"
      return check_macrons([firstpersonform, thirdIOEndings.collect{|x| stem + x}]).flatten!
    end
  end
  
  def active_imperfect   
    oneTwoEndings=%w(bam b?s bat b?mus b?tis bant)
    thirdEndings=%w(?bam ?b?s ?bat ?b?mus ?b?tis ?bant)
    if @conjugation == "1" or @conjugation == "2"
      return check_macrons([oneTwoEndings.collect{|x| stem + x}]).flatten
    end
    if @conjugation == "3"
      return check_macrons([thirdEndings.collect{|x| stem + x}]).flatten
    end
    if conjugation=="3-io" or conjugation=="4"
      base=firstpersonform.clone
      base.gsub!(/.$/,'')
      check_macrons([thirdEndings.collect{|x| base + x}]).flatten!
    end
  end

  def active_future
    oneTwoEndings=%w(b? bis bit bimus bitis bunt)
    thirdEndings=%w(am ?s et ?mus ?tis ent)
    if @conjugation == "1" or @conjugation=="2"
      return check_macrons([oneTwoEndings.collect{|x| stem + x}]).flatten
    end    
    if @conjugation == "3"
      return check_macrons([thirdEndings.collect{|x| stem + x}]).flatten
    end
    if @conjugation == "3-io" or @conjugation == "4"
      base=firstpersonform.clone
      base.gsub!(/.$/,'')      
      return check_macrons([thirdEndings.collect{|x| base + x}]).flatten
    end    
  end
  
  def evaluate_conjugation
    if @infinitive =~ /?re$/
      return "1"
    end
    
    if @infinitive =~ /?re$/
      return "2"
    end    

    if @infinitive =~ /ere$/
      if @firstpersonform =~ /i?/
        return "3-io"
      else     
        return "3"
      end
    end
    
    if @infinitive =~ /?re$/
      return "4"
    end    
    
  end
  
  def stem
    # For efficiency, if the iVar @stem is defined, don't go through this structure
    return @stem unless @stem.nil?
      
    if @infinitive =~ /?re$/
      return @stem = @infinitive.gsub(/(.*)?re$/,'\\1?')
    end
    if @infinitive =~ /?re$/
      return @stem = @infinitive.gsub(/(.*)?re$/,'\\1?')
    end    
    if @infinitive =~ /ere$/
      if firstpersonform =~ /io$/
        return @stem = @infinitive.gsub(/(.*)ere$/,'\\1')
      else
        return @stem = @infinitive.gsub(/(.*)ere$/,'\\1')
      end
    end    
    if @infinitive =~ /?re$/
      return @stem = @infinitive.gsub(/(.*)?re$/,'\\1')
    end
  end

  def to_s
    return "[#{self.firstpersonform} #{self.infinitive}" + 
             " #{self.perfectstem} #{self.passperfpart} {#{@conjugation}}]\n\n"
  end
  
  def present_passive
    if conjugation=="1" or conjugation=="2"
      local_pe=@passive_endings.clone
      return check_macrons([@firstpersonform + "r", local_pe[1..-1].map{|x| self.stem + x}].flatten!)
    end
    if conjugation=="3"
      thirdEndings=%w(r eris itur imur imin? untur)
      return check_macrons([@firstpersonform+"r", 
        thirdEndings[1..-1].map{|x| self.stem + x}].flatten!)
    end
    if @conjugation == "3-io"
      base=stem+"i"
      temp=[@firstpersonform+"r", 
        @passive_endings[1..-2].map{|x| base + x}, 
        base+"u"+@passive_endings[-1]].flatten!
      temp[1].sub!(/iris$/, 'eris')
      return check_macrons(temp)
    end
    if @conjugation=="4"
      base=stem+"i"
      temp=[@firstpersonform+"r", 
        @passive_endings[1..-2].map{|x| base + x}, 
        base+"u"+@passive_endings[-1]].flatten!
      return check_macrons(temp)
    end
  end

  def imperfect_passive
    if conjugation=="1" or conjugation=="2"
      imperfect_stem = self.stem + "b\xc4\x81"
      return check_macrons(@passive_endings.map{|x| "#{imperfect_stem}#{x}"})
    end
    if conjugation=="3" 
      ministem=self.stem + "?b?"
      return check_macrons(@passive_endings.map{|x| ministem + x})
    end
    if @conjugation == "3-io" or @conjugation=="4"
      base=stem+"i?b?"
      return check_macrons([@passive_endings.map{|x| base + x}].flatten!)
    end
  end
  
  def future_passive
    if conjugation=="1" or conjugation=="2"    
      fp_stem=self.stem+"bi"
      standards = @passive_endings[2..-1].map{|x| fp_stem + x}
      return [@stem + "b\xc5\x8d", @stem + "beris", standards].flatten!
    end
    if conjugation == "3"
      fp_stem=self.stem+"?"
      standards = @passive_endings[1..-1].map{|x| fp_stem + x}
      return [@stem + "ar", standards].flatten!      
    end
    if @conjugation == "3-io" or @conjugation=="4"
      base=stem+"i?"
      fp_stem=firstpersonform.clone
      fp_stem.sub!(/.$/,'')
      return check_macrons([fp_stem+"a"+@passive_endings[0], 
        @passive_endings[1..-1].map{|x| base + x}].flatten!)
    end    
  end
  
  def perfect_present
    substem=perfectstem.chars[0..perfectstem.jlength-2]
    endings=%w(ist? it imus istis ?runt)
    return [perfectstem, endings.collect{|x| substem+x}].flatten
  end
  
  def perfect_past
     substem=perfectstem.chars[0..perfectstem.jlength-2]
     endings=%w(eram er?s erat er?mus er?tis erant)
     return [endings.collect{|x| substem+x}].flatten     
  end
  
  def perfect_future
    substem=perfectstem.chars[0..perfectstem.jlength-2]
    endings=%w(er? eris erit erimus eritis erint)
    return [endings.collect{|x| substem+x}].flatten
  end
  
  def pass_perf_present
    endings=%w(sum es est sumus estis sunt)    
  end

  def pass_perf_past
    return %w(eram er?s erat er?mus er?tis erant)
  end

  def pass_perf_future
    return %w(er? eris erit erimus eritis erint)
  end
  
  def passive_system
    puts "Passive System"
    p_sys_hash= { :label=>@personages,
                  :present => self.present_passive,
                  :imperfect => self.imperfect_passive,
                  :future => self.future_passive}


    0.upto(5) do |index|
      printf("%-15s %-10s %-16s %s\n", 
                      p_sys_hash[:label][index],
                       p_sys_hash[:present][index],
                       p_sys_hash[:imperfect][index],
                       p_sys_hash[:future][index]
                      )
    end
    puts "\n\n"
  end
  
  def active_system
    puts "Active System"
    p_sys_hash= { :label=>@personages,
                  :present => self.active_present,
                  :imperfect => self.active_imperfect,
                  :future => self.active_future}

    0.upto(5) do |index|
      printf("%-15s %-10s %-16s %s\n", 
                      p_sys_hash[:label][index],
                       p_sys_hash[:present][index],
                       p_sys_hash[:imperfect][index],
                       p_sys_hash[:future][index]
                      )
    end
    puts "\n\n"
  end

    def perfect_system
      puts "Perfect System"
      p_sys_hash= { :label=>@personages,
                    :present => self.perfect_present,
                    :imperfect => self.perfect_past,
                    :future => self.perfect_future}

      0.upto(5) do |index|
        printf("%-15s %-10s %-16s %s\n", 
                        p_sys_hash[:label][index],
                         p_sys_hash[:present][index],
                                       p_sys_hash[:imperfect][index],
                                       p_sys_hash[:future][index]
                        )
      end
      puts "\n\n"
    end

    def pass_perfect_system
      puts "Passive Perfect System"

      
      substem=passperfpart.chars[0..passperfpart.jlength-3].to_s

      singular_endings=%w(a um)
      plural_endings=%w(? ae a)
 
      @singular_string= passperfpart + ', ' + singular_endings.collect{|x| substem + x}.join(', ')
      @plural_string=plural_endings.collect{|x| substem + x}.join(', ')

      prefixes=Array.new
      3.times{ prefixes.push(@singular_string)}
      3.times{ prefixes.push(@plural_string)}      

      p_sys_hash= { :label=>@personages,
                    :prefix=>prefixes,
                    :present => self.pass_perf_present,
                    :imperfect => self.pass_perf_past,
                    :future => self.pass_perf_future}

      0.upto(5) do |index|
        printf("%10s %10s %8s %8s %8s\n", 
                        p_sys_hash[:label][index],
                        p_sys_hash[:prefix][index],
                         p_sys_hash[:present][index],
                                       p_sys_hash[:imperfect][index],
                                       p_sys_hash[:future][index]
                        )
      end
    end
    
    def participial_stem
       if @infinitive =~ /(.*?)re$/
        return $1
      end

      if @infinitive =~ /(.*?)re$/
        return $1
      end    

      if @infinitive =~ /(.*)ere$/
        match=$1
        if @firstpersonform =~ /i?/
          return match + "i?"  
        else
          return match + "e" 
        end
      end
          
      if @infinitive =~ /(.*)?re$/
        return $1 + "i?" 
      end
    end
    
    def present_active_participle
      endings=%w(ns ntis)
      return check_macrons(endings.collect{|x|participial_stem+x.chomp}).join(', ')
    end
    
    def future_active_participle
      mybase=@passperfpart.gsub(/us$/, "?r")
      singular_endings=%w(us a um)
      return check_macrons(singular_endings.collect{|x| mybase+"#{x}".chomp}.join(', '))
      
      return base
    end
    
    def perfect_passive_participle
      mybase=passperfpart.sub(/us$/,'')
      singular_endings=%w(us a um)
      return check_macrons(singular_endings.collect{|x| mybase+"#{x}".chomp}.join(', '))
    end
    
    def future_passive_participle
      mybase = participial_stem+"nd"
      singular_endings=%w(us a um)
      return check_macrons(singular_endings.collect{|x| mybase+"#{x}".chomp}.join(', '))
    end
    
    def gerundive
      self.future_passive_participle
    end
    
    def full_conjugation
      active_system
      perfect_system
      passive_system
      pass_perfect_system
      participles
      infinitives
      puts imperative.join("\n")
    end
    
    def participles
      puts "\n\n"
      puts "Participles associated with: #{@definition_string} "
      puts "Present Active Participle: #{present_active_participle}"
      puts "Future Active Participle: #{future_active_participle}"
      puts "Perfect Passive Participle #{perfect_passive_participle}"
      puts "Future Passive Participle #{future_passive_participle}"
      puts "\n\n"
    end

    def present_passive_infinitive
      if @infinitive =~ /?re$/
        return  @infinitive.gsub(/(.*)?re$/,"\\1\xc4\xab")
      end
      if @infinitive =~ /?re$/
        return @infinitive.gsub(/(.*)?re$/,"\\1\xc4\xab")
      end    
      if @infinitive =~ /ere$/
        if firstpersonform =~ /io$/
          return @infinitive.gsub(/(.*)ere$/,"\\1\xc4\xab")
        else                                     
          return @infinitive.gsub(/(.*)ere$/,"\\1\xc4\xab")
        end
      end    
      if @infinitive =~ /?re$/
        return @infinitive.gsub(/(.*)?re$/,"\\1\xc4\xab")
      end      
    end
        
    def infinitives
      puts "Infinitives associated with: #{@definition_string} "
      
      puts "Present Active Infinitive: #{@infinitive}"
      puts "Perfect Active Infinitive: #{check_macrons(@perfectstem+"sse")}"      
      puts "Future Active Infinitive: #{future_active_participle.sub(/,.*/,'')} + esse"
      puts "Present Passive Infinitive #{present_passive_infinitive}"
      puts "Perfect Passive Infinitive #{perfect_passive_participle.sub(/,.*/,'')} + esse"  
      puts "\n\n"    
    end
    
    def imperative
      puts "Imperatives:\n\n"
      if @infinitive =~ /?re$/
        return [ check_macrons(stem), check_macrons(stem+"te")]
      end                            
      if @infinitive =~ /?re$/       
        return [ check_macrons(stem), check_macrons(stem+"te")]
      end    
      if @infinitive =~ /ere$/
          return [stem+"e", stem+"ite"]
      end    
      if @infinitive =~ /?re$/
        return [stem+"?", stem+"?te"]
      end
    end
end

opts = GetoptLong.new(
      [ '--macron', '-m', GetoptLong::NO_ARGUMENT ],
      [ '--html',   '-h', GetoptLong::NO_ARGUMENT ],
      [ '--utf8',   '-u', GetoptLong::NO_ARGUMENT ],
      [ '--content', '-c', GetoptLong::REQUIRED_ARGUMENT]
    )
    
module MacronConversions 
  LATEX_TO_MACRONS_CHARACTERS = {
    "\\={a}"   => "?",
    "\\={e}"   => "?",
    "\\={\\i}" => "?",
    "\\={o}"   => "?",
    "\\={u}"   => "?",
    "\\={A}"   => "?",
    "\\={E}"   => "?",
    "\\={\\I}" => "?",
    "\\={O}"   => "?",
    "\\={U}"   => "?",
  }
  
  LATEX_TO_UTF8 ={
    "\\={a}"   => "\\xc4\\x81",
    "\\={e}"   => "\\xc4\\x93",
    "\\={\\i}" => "\\xc4\\xab",
    "\\={o}"   => "\\xc5\\x8d",
    "\\={u}"   => "\\xc5\\xab",
    "\\={A}"   => "\\xc4\\x80",
    "\\={E}"   => "\\xc4\\x92",
    "\\={\\I}" => "\\xc4\\xaa",
    "\\={O}"   => "\\xc5\\x8c",
    "\\={U}"   => "\\xc5\\xaa",
  }
  
  LATEX_TO_HTML_ENTITIES = {
    "\\={a}"   => "ā",
    "\\={e}"   => "ē",
    "\\={\\i}" => "ī",
    "\\={o}"   => "ō",
    "\\={u}"   => "ū",
    "\\={A}"   => "Ā",
    "\\={E}"   => "Ē",
    "\\={\\I}" => "Ī",
    "\\={O}"   => "Ō",
    "\\={U}"   => "Ū",
  }

  class Converter < Object
    def initialize(*ray)
      @target = ray[0].nil? ? 'html' : ray[0]
      @index=Array.new

    end
    
    def processString(s)      
      firstSlash = s =~ /(\\=.*?\})/
      return s if $1.nil?
      
      testChar = $1
       
      if firstSlash == 0
        return processChar(testChar).to_s +
         processString(s[firstSlash+testChar.length..s.length])
      end
      
      return s[0..firstSlash-1] + processChar(testChar).to_s +
       processString(s[firstSlash+testChar.length..s.length])
    end
    
    def processChar(c)
      if @target == 'utf8'
        return LATEX_TO_UTF8[c]
      end
      if @target == 'html'
        return LATEX_TO_HTML_ENTITIES[c]
      end
      if @target == "mc"
        return LATEX_TO_MACRONS_CHARACTERS[c]
      end
    end
  end
end

unless STDIN.tty?
  $content=STDIN.read
else
  $content=ARGV[0]
end

$mode='html'

opts.each do |opt,value| 
  case opt
  when '--macron'
    $mode = 'mc'
  when '--html'
    $mode = 'html'
  when '--utf8'
    $mode = 'utf8'
  when '--content'
    $content = value
  end
end

# You can change this value to 'mc', '', or 'utf8'.  Empty, it means 'html'
puts x=Verb.new(MacronConversions::Converter.new($mode).processString($content))
x.full_conjugation

[/code]