tact 1.2.23 → 2.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +2 -1
- data/Rakefile +95 -2
- data/bin/tact +1 -1
- data/db/migrate/20170515014719_create_tables.rb +17 -0
- data/db/migrate/20170515015242_add_foreign_keys.rb +6 -0
- data/db/migrate/20170515130759_change_type_on_phone_numbers.rb +5 -0
- data/lib/tact.rb +50 -1
- data/lib/tact/authorizable.rb +10 -0
- data/lib/tact/card.rb +3 -7
- data/lib/tact/contact.rb +8 -70
- data/lib/tact/email.rb +5 -52
- data/lib/tact/google_client.rb +156 -0
- data/lib/tact/phone_number.rb +12 -69
- data/lib/tact/rolodex.rb +45 -32
- data/lib/tact/tact.rb +35 -23
- data/lib/tact/version.rb +1 -1
- data/tact.gemspec +9 -0
- metadata +55 -6
- data/lib/tact/database.rb +0 -47
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 0c97568e1e4da1c76294dd69bdfc31af58cd5ded
         | 
| 4 | 
            +
              data.tar.gz: 887f421efa7a7fb0a5272f798706518403a4fbb2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d65d88384b495953b11699693eae7179b22a0af7cdfa818079768da6b26e8f7e7e72d1a6046eb1350eb0eec9e404721956a471906860dd4c7ae75400cf78adfd
         | 
| 7 | 
            +
              data.tar.gz: 611fcd81f8c5d8d0b451065607195adde2380be76db5640dd0f3d908f2f4133d83b12fce69cc33a97ecee86caac9f34e594383e82463661a42e0bc4a41ee2da8
         | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -11,6 +11,7 @@ A command line rolodex. | |
| 11 11 | 
             
            ```     
         | 
| 12 12 | 
             
              -v                                    Current version
         | 
| 13 13 | 
             
              -h                                    Help
         | 
| 14 | 
            +
              -s                                    Sync Google contacts
         | 
| 14 15 |  | 
| 15 16 | 
             
              <param>                               Search by name
         | 
| 16 17 | 
             
              -p <param>                            Search by number
         | 
| @@ -28,7 +29,7 @@ A command line rolodex. | |
| 28 29 |  | 
| 29 30 | 
             
            ## Contributing
         | 
| 30 31 |  | 
| 31 | 
            -
            Bug reports and pull requests are welcome on GitHub at https://github.com/ | 
| 32 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/olishmollie/tact.
         | 
| 32 33 |  | 
| 33 34 |  | 
| 34 35 | 
             
            ## License
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,2 +1,95 @@ | |
| 1 | 
            -
            require  | 
| 2 | 
            -
             | 
| 1 | 
            +
            require 'bundler/gem_tasks'
         | 
| 2 | 
            +
            require 'fileutils'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            task :environment do
         | 
| 5 | 
            +
              require 'tact'
         | 
| 6 | 
            +
            end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            namespace :generate do
         | 
| 9 | 
            +
              desc "Create an empty migration in db/migrate, e.g., rake generate:migration NAME=create_tasks"
         | 
| 10 | 
            +
              task :migration => :environment do
         | 
| 11 | 
            +
                unless ENV.has_key?('NAME')
         | 
| 12 | 
            +
                  raise "Must specificy migration name, e.g., rake generate:migration NAME=create_tasks"
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                name     = ENV['NAME'].camelize
         | 
| 16 | 
            +
                filename = "%s_%s.rb" % [Time.now.strftime('%Y%m%d%H%M%S'), ENV['NAME'].underscore]
         | 
| 17 | 
            +
                path     = File.join('db', 'migrate', filename)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                if File.exist?(path)
         | 
| 20 | 
            +
                  raise "ERROR: File '#{path}' already exists"
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                puts "Creating #{path}"
         | 
| 24 | 
            +
                File.open(path, 'w+') do |f|
         | 
| 25 | 
            +
                  f.write(<<-EOF.strip_heredoc)
         | 
| 26 | 
            +
                    class #{name} < ActiveRecord::Migration
         | 
| 27 | 
            +
                      def change
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  EOF
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            task :make_tact_dir do
         | 
| 36 | 
            +
              if !File.exists?("#{File.expand_path('~')}/.tact")
         | 
| 37 | 
            +
                FileUtils.mkdir("#{File.expand_path('~')}/.tact")
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            namespace :db do
         | 
| 42 | 
            +
              desc "Create databases"
         | 
| 43 | 
            +
              task :create => [:environment, :make_tact_dir] do
         | 
| 44 | 
            +
                SQLite3::Database.new(DEV_DB)
         | 
| 45 | 
            +
                SQLite3::Database.new(TEST_DB)
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              desc "Drop databases"
         | 
| 49 | 
            +
              task :drop => :environment do
         | 
| 50 | 
            +
                FileUtils.rm DEV_DB
         | 
| 51 | 
            +
                FileUtils.rm TEST_DB
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              desc "Run migrations"
         | 
| 55 | 
            +
              task :migrate => :environment do
         | 
| 56 | 
            +
                ActiveRecord::Migrator.migrations_paths << File.dirname(__FILE__) + 'db/migrate'
         | 
| 57 | 
            +
                ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
         | 
| 58 | 
            +
                ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
         | 
| 59 | 
            +
                  ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
         | 
| 64 | 
            +
              task :rollback => :environment do
         | 
| 65 | 
            +
                step = ENV['STEP'] ? ENV['STEP'].to_i : 1
         | 
| 66 | 
            +
                ActiveRecord::Migrator.rollback MIGRATIONS_DIR, step
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              desc "Retrieves the current schema version number"
         | 
| 70 | 
            +
              task :version do
         | 
| 71 | 
            +
                puts "Current version: #{ActiveRecord::Migrator.current_version}"
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            desc "Run specs in test environment"
         | 
| 76 | 
            +
            task :spec => :environment do
         | 
| 77 | 
            +
              sh 'rspec'
         | 
| 78 | 
            +
            end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            desc "Open console with this library required"
         | 
| 81 | 
            +
            task :console do
         | 
| 82 | 
            +
              sh 'irb -I lib -r tact.rb'
         | 
| 83 | 
            +
            end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            desc "Uninstall local version of gem"
         | 
| 86 | 
            +
            task :uninstall do
         | 
| 87 | 
            +
              sh 'yes | gem uninstall tact'  
         | 
| 88 | 
            +
              sh 'rm tact-*' unless Dir.glob('./tact-*').empty?
         | 
| 89 | 
            +
            end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            desc "Build and install local gem version"
         | 
| 92 | 
            +
            task :build => :uninstall do
         | 
| 93 | 
            +
              sh 'gem build tact.gemspec && gem install tact'
         | 
| 94 | 
            +
            end
         | 
| 95 | 
            +
             | 
    
        data/bin/tact
    CHANGED
    
    
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            class CreateTables < ActiveRecord::Migration[4.2]
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                create_table :contacts do |t|
         | 
| 4 | 
            +
                  t.string :first_name, limit: 20
         | 
| 5 | 
            +
                  t.string :last_name, limit: 20
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  t.timestamps
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
                create_table :phone_numbers do |t|
         | 
| 10 | 
            +
                  t.string :number, limit: 15
         | 
| 11 | 
            +
                  t.string :type, limit: 10
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
                create_table :emails do |t|
         | 
| 14 | 
            +
                  t.string :address, limit: 50
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
    
        data/lib/tact.rb
    CHANGED
    
    | @@ -1 +1,50 @@ | |
| 1 | 
            -
            require  | 
| 1 | 
            +
            # require needed gems
         | 
| 2 | 
            +
            require 'fileutils'
         | 
| 3 | 
            +
            require 'active_record'
         | 
| 4 | 
            +
            require 'sqlite3'
         | 
| 5 | 
            +
            require 'colored'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # require lib files
         | 
| 8 | 
            +
            require 'tact/authorizable'
         | 
| 9 | 
            +
            require 'tact/card'
         | 
| 10 | 
            +
            require 'tact/contact'
         | 
| 11 | 
            +
            require 'tact/email'
         | 
| 12 | 
            +
            require 'tact/google_client'
         | 
| 13 | 
            +
            require 'tact/phone_number'
         | 
| 14 | 
            +
            require 'tact/rolodex'
         | 
| 15 | 
            +
            require 'tact/tact'
         | 
| 16 | 
            +
            require 'tact/version'
         | 
| 17 | 
            +
             | 
| 18 | 
            +
             | 
| 19 | 
            +
            APP_ROOT ||= File.join(File.dirname(__FILE__), '../')
         | 
| 20 | 
            +
            DEV_DB ||= File.join(File.expand_path('~'), '.tact', 'tact.sqlite3')
         | 
| 21 | 
            +
            TEST_DB ||= File.join(File.expand_path('~'), '.tact', 'tact_test.sqlite3')
         | 
| 22 | 
            +
            MIGRATIONS_DIR ||= 'db/migrate'
         | 
| 23 | 
            +
            CLIENT_SECRET ||= File.join(APP_ROOT, 'client_secret.json')
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            # tells AR what db file to use
         | 
| 26 | 
            +
            if ENV['GEM_ENV'] == 'test'
         | 
| 27 | 
            +
              ActiveRecord::Base.establish_connection(
         | 
| 28 | 
            +
                :adapter => 'sqlite3',
         | 
| 29 | 
            +
                :database => TEST_DB 
         | 
| 30 | 
            +
              )
         | 
| 31 | 
            +
            else
         | 
| 32 | 
            +
              ActiveRecord::Base.establish_connection(
         | 
| 33 | 
            +
                :adapter => 'sqlite3',
         | 
| 34 | 
            +
                :database => DEV_DB 
         | 
| 35 | 
            +
              )
         | 
| 36 | 
            +
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            # Make tact directory
         | 
| 39 | 
            +
            if !File.exists?("#{File.expand_path('~')}/.tact")
         | 
| 40 | 
            +
              FileUtils.mkdir("#{File.expand_path('~')}/.tact")
         | 
| 41 | 
            +
            end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            # Create database
         | 
| 44 | 
            +
            SQLite3::Database.new(DEV_DB)
         | 
| 45 | 
            +
            SQLite3::Database.new(TEST_DB)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            # Run migrations
         | 
| 48 | 
            +
            ActiveRecord::Migrator.migrations_paths << APP_ROOT + 'db/migrate'
         | 
| 49 | 
            +
            ActiveRecord::Migration.verbose = false
         | 
| 50 | 
            +
            ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths)
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            module Authorizable
         | 
| 2 | 
            +
              def authorize
         | 
| 3 | 
            +
                system("oauth2l fetch --json #{CLIENT_SECRET} https://www.googleapis.com/auth/contacts https://www.googleapis.com/auth/contacts.readonly")
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def authorized?
         | 
| 7 | 
            +
                credentials = File.join(File.expand_path('~'), '.oauth2l.token')
         | 
| 8 | 
            +
                File.exists?(credentials) && !File.zero?(credentials)
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
    
        data/lib/tact/card.rb
    CHANGED
    
    | @@ -1,21 +1,17 @@ | |
| 1 | 
            -
            require_relative 'contact'
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            module Tact
         | 
| 4 2 | 
             
              class Card
         | 
| 5 3 | 
             
                attr_reader :contact
         | 
| 6 4 | 
             
                def initialize(contact, index="*")
         | 
| 7 5 | 
             
                  @contact = contact
         | 
| 8 6 | 
             
                  @index = index
         | 
| 9 | 
            -
                  @phone_numbers = contact.phone_numbers
         | 
| 10 | 
            -
                  @emails = contact.emails
         | 
| 11 7 | 
             
                end
         | 
| 12 8 |  | 
| 13 9 | 
             
                def to_s
         | 
| 14 10 | 
             
                  string = "=" * 40 + "\n"
         | 
| 15 11 | 
             
                  string += "[#{@index}]".red + " #{@contact.to_s}"
         | 
| 16 | 
            -
                   | 
| 17 | 
            -
                   | 
| 12 | 
            +
                  contact.phone_numbers.each_with_index {|number, i| string += "\s\s" + "[#{i + 1}] " + number.to_s + "\n"}
         | 
| 13 | 
            +
                  contact.emails.each_with_index {|address, i| string += "\s\s\s\s" + "[#{i + 1}] " + address.to_s + "\n"}
         | 
| 18 14 | 
             
                  string += "=" * 40 + "\n"
         | 
| 19 15 | 
             
                end
         | 
| 20 16 | 
             
              end
         | 
| 21 | 
            -
            end
         | 
| 17 | 
            +
            end
         | 
    
        data/lib/tact/contact.rb
    CHANGED
    
    | @@ -1,74 +1,12 @@ | |
| 1 | 
            -
            require 'colored'
         | 
| 2 | 
            -
            require_relative 'phone_number'
         | 
| 3 | 
            -
            require_relative "email"
         | 
| 4 | 
            -
             | 
| 5 1 | 
             
            module Tact
         | 
| 6 | 
            -
              class Contact
         | 
| 7 | 
            -
                 | 
| 8 | 
            -
                 | 
| 9 | 
            -
             | 
| 10 | 
            -
                @@db = Database.new.db
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                def self.from_hash(hash)
         | 
| 13 | 
            -
                  contact = self.new(hash["first_name"], hash["last_name"], hash["id"])
         | 
| 14 | 
            -
                end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                def self.all
         | 
| 17 | 
            -
                  contact_hashes = @@db.execute("select * from contacts order by last_name asc, first_name asc;")
         | 
| 18 | 
            -
                  contact_hashes.each_with_index.map {|c_hash, index| self.from_hash(c_hash) }
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                def self.find_by_id(id)
         | 
| 22 | 
            -
                  contact_hashes = @@db.execute("select * from contacts where id = ?", [id])
         | 
| 23 | 
            -
                  self.from_hash(contact_hashes[0]) if !contact_hashes.empty?
         | 
| 24 | 
            -
                end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                def self.find_by_first_name(first_name)
         | 
| 27 | 
            -
                  results = @@db.execute("select * from contacts where upper(first_name) = upper(?);", [first_name])
         | 
| 28 | 
            -
                  results.map {|c_hash| self.from_hash(c_hash) }
         | 
| 29 | 
            -
                end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                def self.find_by_last_name(last_name)
         | 
| 32 | 
            -
                  results = @@db.execute("select * from contacts where upper(last_name) = upper(?);", [last_name])
         | 
| 33 | 
            -
                  results.map {|c_hash| self.from_hash(c_hash) }
         | 
| 34 | 
            -
                end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                def self.delete(id)
         | 
| 37 | 
            -
                  @@db.execute("delete from contacts where id = ?;", [id])
         | 
| 38 | 
            -
                  @@db.execute("select changes();")[0]["changes()"] == 1 ? true : false
         | 
| 39 | 
            -
                end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                def initialize(first_name, last_name, primary_key=nil)
         | 
| 42 | 
            -
                  @id = primary_key
         | 
| 43 | 
            -
                  @first_name = first_name.downcase.capitalize
         | 
| 44 | 
            -
                  @last_name = last_name.downcase.capitalize
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                def save
         | 
| 48 | 
            -
                  begin
         | 
| 49 | 
            -
                    if @id == nil
         | 
| 50 | 
            -
                      if @@db.execute("insert into contacts (first_name, last_name) values (?, ?);", [@first_name, @last_name])
         | 
| 51 | 
            -
                        @id = @@db.execute("select last_insert_rowid()")[0]["last_insert_rowid()"]
         | 
| 52 | 
            -
                        self
         | 
| 53 | 
            -
                      else
         | 
| 54 | 
            -
                        false
         | 
| 55 | 
            -
                      end
         | 
| 56 | 
            -
                    else
         | 
| 57 | 
            -
                      @@db.execute("update contacts set first_name = ?, last_name = ? where id = ?;", [@first_name, @last_name, @id]) ? self : false
         | 
| 58 | 
            -
                    end
         | 
| 59 | 
            -
                  rescue
         | 
| 60 | 
            -
                    puts "Error: Contact already exists".red 
         | 
| 61 | 
            -
                  end
         | 
| 62 | 
            -
                end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                def phone_numbers
         | 
| 65 | 
            -
                  number_hashes = @@db.execute("select * from phone_numbers where contact_id = ? order by type asc;", [@id])
         | 
| 66 | 
            -
                  number_hashes.map {|n_hash| PhoneNumber.from_hash(n_hash) }
         | 
| 67 | 
            -
                end
         | 
| 2 | 
            +
              class Contact < ActiveRecord::Base
         | 
| 3 | 
            +
                has_many :phone_numbers, dependent: :destroy
         | 
| 4 | 
            +
                has_many :emails, dependent: :destroy
         | 
| 5 | 
            +
                validates :last_name, uniqueness: { scope: :first_name }
         | 
| 68 6 |  | 
| 69 | 
            -
                 | 
| 70 | 
            -
                   | 
| 71 | 
            -
                   | 
| 7 | 
            +
                before_validation do
         | 
| 8 | 
            +
                  first_name.upcase!
         | 
| 9 | 
            +
                  last_name.upcase!
         | 
| 72 10 | 
             
                end
         | 
| 73 11 |  | 
| 74 12 | 
             
                def full_name
         | 
| @@ -79,4 +17,4 @@ module Tact | |
| 79 17 | 
             
                  "#{full_name}\n".green.bold
         | 
| 80 18 | 
             
                end
         | 
| 81 19 | 
             
              end
         | 
| 82 | 
            -
            end
         | 
| 20 | 
            +
            end
         | 
    
        data/lib/tact/email.rb
    CHANGED
    
    | @@ -1,57 +1,10 @@ | |
| 1 | 
            -
            require_relative 'database'
         | 
| 2 | 
            -
             | 
| 3 1 | 
             
            module Tact
         | 
| 4 | 
            -
              class Email
         | 
| 5 | 
            -
                 | 
| 6 | 
            -
                attr_accessor :address, :index
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                @@db = Database.new.db
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                def self.from_hash(hash)
         | 
| 11 | 
            -
                  self.new(hash["address"], hash["contact_id"], hash["id"])
         | 
| 12 | 
            -
                end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                def self.all
         | 
| 15 | 
            -
                  email_hashes = @@db.execute("select * from emails;")
         | 
| 16 | 
            -
                  email_hashes.map {|e_hash| Email.from_hash(e_hash) }
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                def self.find_by_id(id)
         | 
| 20 | 
            -
                  email_hashes = @@db.execute("select * from emails where id = ?", [id])
         | 
| 21 | 
            -
                  self.from_hash(email_hashes[0]) if !email_hashes.empty?
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                def self.find_by_address(address)
         | 
| 25 | 
            -
                  email_hashes = @@db.execute("select * from emails where address = ?", [address])
         | 
| 26 | 
            -
                  email_hashes.map {|e_hash| self.from_hash(e_hash) }
         | 
| 27 | 
            -
                end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                def self.delete(id)
         | 
| 30 | 
            -
                  @@db.execute("delete from emails where id = ?;", [id])
         | 
| 31 | 
            -
                  @@db.execute("select changes();")[0]["changes()"] == 1 ? true : false
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                def initialize(address, contact_id, primary_key=nil)
         | 
| 35 | 
            -
                  @id = primary_key
         | 
| 36 | 
            -
                  @address = address
         | 
| 37 | 
            -
                  @contact_id = contact_id
         | 
| 38 | 
            -
                end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                def save
         | 
| 41 | 
            -
                  if @id == nil
         | 
| 42 | 
            -
                    if @@db.execute("insert into emails (address, contact_id) values (?, ?);", [@address, @contact_id])
         | 
| 43 | 
            -
                      @id = @@db.execute("select last_insert_rowid();")[0]["last_insert_rowid()"]
         | 
| 44 | 
            -
                      self
         | 
| 45 | 
            -
                    else
         | 
| 46 | 
            -
                      false
         | 
| 47 | 
            -
                    end
         | 
| 48 | 
            -
                  else
         | 
| 49 | 
            -
                    @@db.execute("update emails set address = ?, contact_id = ? where id = ?;", [@address, @contact_id, @id]) ? self : false
         | 
| 50 | 
            -
                  end
         | 
| 51 | 
            -
                end
         | 
| 2 | 
            +
              class Email < ActiveRecord::Base
         | 
| 3 | 
            +
                belongs_to :contact
         | 
| 52 4 |  | 
| 53 5 | 
             
                def to_s
         | 
| 54 | 
            -
                  "<#{ | 
| 6 | 
            +
                  "<#{address}>"
         | 
| 55 7 | 
             
                end
         | 
| 8 | 
            +
             | 
| 56 9 | 
             
              end
         | 
| 57 | 
            -
            end
         | 
| 10 | 
            +
            end
         | 
| @@ -0,0 +1,156 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Tact
         | 
| 4 | 
            +
              module GoogleContacts
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                class Entry
         | 
| 7 | 
            +
                  attr_reader :info
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def self.all
         | 
| 10 | 
            +
                    collection
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def initialize(info)
         | 
| 14 | 
            +
                    @info = info
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def first_name
         | 
| 18 | 
            +
                    names[:givenName]
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def last_name
         | 
| 22 | 
            +
                    names[:familyName]
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def phone_numbers
         | 
| 26 | 
            +
                    if info[:phoneNumbers]
         | 
| 27 | 
            +
                      info[:phoneNumbers].map do |phone_number|
         | 
| 28 | 
            +
                        PhoneNumber.new(
         | 
| 29 | 
            +
                          number: phone_number[:value],
         | 
| 30 | 
            +
                          kind: phone_number[:type]
         | 
| 31 | 
            +
                        )
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def emails
         | 
| 37 | 
            +
                    if info[:emailAddresses]
         | 
| 38 | 
            +
                      info[:emailAddresses].map do |email_address|
         | 
| 39 | 
            +
                        Email.new(address: email_address[:value])
         | 
| 40 | 
            +
                      end 
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  private
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    def names
         | 
| 47 | 
            +
                      info[:names][0]
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def self.collection
         | 
| 51 | 
            +
                    @@collection ||= Fetcher.info_list.reduce(EntriesCollection.new) do |collection, info|
         | 
| 52 | 
            +
                      collection << new(info)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                  
         | 
| 56 | 
            +
                  private_class_method :collection
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
             | 
| 60 | 
            +
                class EntriesCollection
         | 
| 61 | 
            +
                  include Enumerable
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def initialize(entries=[])
         | 
| 64 | 
            +
                    @entries = entries
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  def <<(entry)
         | 
| 68 | 
            +
                    @entries << entry
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  def each
         | 
| 72 | 
            +
                    @entries.each { |c| yield(c) }  
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
                
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                class Syncer
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  def initialize(entry)
         | 
| 80 | 
            +
                    @entry = entry
         | 
| 81 | 
            +
                    @new_numbers = []
         | 
| 82 | 
            +
                    @new_emails = []
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  def sync
         | 
| 86 | 
            +
                    contact = find_contact || Contact.new(
         | 
| 87 | 
            +
                      first_name: entry.first_name.upcase,
         | 
| 88 | 
            +
                      last_name: (entry.last_name || "").upcase
         | 
| 89 | 
            +
                    )
         | 
| 90 | 
            +
                    merge_properties(contact)
         | 
| 91 | 
            +
                    contact.save
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def find_contact
         | 
| 95 | 
            +
                    Contact.find_by(
         | 
| 96 | 
            +
                      first_name: entry.first_name.upcase,
         | 
| 97 | 
            +
                      last_name: (entry.last_name || "").upcase
         | 
| 98 | 
            +
                    )
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  def merge_properties(contact)
         | 
| 102 | 
            +
                    get_new_phone_numbers(contact)
         | 
| 103 | 
            +
                    get_new_emails(contact)
         | 
| 104 | 
            +
                    add_new_phone_numbers(contact)
         | 
| 105 | 
            +
                    add_new_emails(contact)
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  def add_new_phone_numbers(contact)
         | 
| 109 | 
            +
                    contact.phone_numbers << new_numbers
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  def add_new_emails(contact)
         | 
| 113 | 
            +
                    contact.emails << new_emails
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  def get_new_phone_numbers(contact)
         | 
| 117 | 
            +
                    entry.phone_numbers.each do |number|
         | 
| 118 | 
            +
                      if !contact.phone_numbers.any? { |n| n.number == number.number }
         | 
| 119 | 
            +
                        new_numbers << number
         | 
| 120 | 
            +
                      end
         | 
| 121 | 
            +
                    end if entry.phone_numbers
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  def get_new_emails(contact)
         | 
| 125 | 
            +
                    entry.emails.each do |email|
         | 
| 126 | 
            +
                      if !contact.emails.any? { |e| e.address == email.address }
         | 
| 127 | 
            +
                        new_emails << email
         | 
| 128 | 
            +
                      end
         | 
| 129 | 
            +
                    end if entry.emails
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  private
         | 
| 133 | 
            +
                    attr_reader :entry, :new_numbers, :new_emails
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                
         | 
| 138 | 
            +
                class Fetcher
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  def self.info_list
         | 
| 141 | 
            +
                    info = JSON.parse(json, symbolize_names: true)
         | 
| 142 | 
            +
                    if info[:error]
         | 
| 143 | 
            +
                      puts "ERROR: Please authorize your Google account.".red 
         | 
| 144 | 
            +
                      exit
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
                    info[:connections]  
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  def self.json
         | 
| 150 | 
            +
                    `curl -H "$(oauth2l header --json #{CLIENT_SECRET} https://www.googleapis.com/auth/contacts https://www.googleapis.com/auth/contacts.readonly)"\
         | 
| 151 | 
            +
                      https://people.googleapis.com/v1/people/me/connections?requestMask.includeField=person.names,person.phone_numbers,person.email_addresses`
         | 
| 152 | 
            +
                  end
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
              end
         | 
| 156 | 
            +
            end
         | 
    
        data/lib/tact/phone_number.rb
    CHANGED
    
    | @@ -1,78 +1,21 @@ | |
| 1 | 
            -
            require 'colored'
         | 
| 2 | 
            -
            require_relative 'database'
         | 
| 3 | 
            -
             | 
| 4 1 | 
             
            module Tact
         | 
| 5 | 
            -
              class PhoneNumber
         | 
| 6 | 
            -
                 | 
| 7 | 
            -
                attr_accessor :number, :index, :type
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                @@db = Database.new.db
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                def self.format_number(number)
         | 
| 12 | 
            -
                  n = number.gsub(/[^\d]/, "")
         | 
| 13 | 
            -
                  chars = n.chars
         | 
| 14 | 
            -
                  if chars.count == 10
         | 
| 15 | 
            -
                    chars.insert(3, '-')
         | 
| 16 | 
            -
                    chars.insert(7, '-')
         | 
| 17 | 
            -
                  elsif chars.count == 11
         | 
| 18 | 
            -
                    chars.insert(0, '+')
         | 
| 19 | 
            -
                    chars.insert(2, ' ')
         | 
| 20 | 
            -
                    chars.insert(6, '-')
         | 
| 21 | 
            -
                    chars.insert(10, '-')
         | 
| 22 | 
            -
                  elsif chars.count == 7
         | 
| 23 | 
            -
                    chars.insert(3, '-')
         | 
| 24 | 
            -
                  else
         | 
| 25 | 
            -
                    return n
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
                  return chars.join
         | 
| 28 | 
            -
                end
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                def self.from_hash(hash)
         | 
| 31 | 
            -
                  self.new(hash["type"], hash["number"], hash["contact_id"], hash["id"])
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                def self.all
         | 
| 35 | 
            -
                  number_hashes = @@db.execute("select * from phone_numbers;")
         | 
| 36 | 
            -
                  number_hashes.map {|n_hash| self.from_hash(n_hash) }
         | 
| 37 | 
            -
                end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                def self.delete(id)
         | 
| 40 | 
            -
                  @@db.execute("delete from phone_numbers where id = ?;", [id])
         | 
| 41 | 
            -
                  @@db.execute("select changes();")[0]["changes()"] == 1 ? true : false
         | 
| 42 | 
            -
                end
         | 
| 2 | 
            +
              class PhoneNumber < ActiveRecord::Base
         | 
| 3 | 
            +
                belongs_to :contact
         | 
| 43 4 |  | 
| 44 | 
            -
                 | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 5 | 
            +
                after_initialize :format_number
         | 
| 6 | 
            +
                before_save :format_number
         | 
| 7 | 
            +
                before_save do
         | 
| 8 | 
            +
                  self.kind = kind ? kind.downcase.capitalize : "Cell"
         | 
| 47 9 | 
             
                end
         | 
| 48 10 |  | 
| 49 | 
            -
                def  | 
| 50 | 
            -
                   | 
| 51 | 
            -
                  number_hashes.map {|n_hash| self.from_hash(n_hash) }
         | 
| 52 | 
            -
                end
         | 
| 53 | 
            -
                
         | 
| 54 | 
            -
                def initialize(type, number, contact_id, primary_key=nil)
         | 
| 55 | 
            -
                  @id = primary_key
         | 
| 56 | 
            -
                  @type = type.downcase.capitalize
         | 
| 57 | 
            -
                  @number = number.gsub(/\D/, "")
         | 
| 58 | 
            -
                  @contact_id = contact_id
         | 
| 11 | 
            +
                def to_s
         | 
| 12 | 
            +
                  "#{kind.yellow}: #{number.bold}"
         | 
| 59 13 | 
             
                end
         | 
| 60 14 |  | 
| 61 | 
            -
                def  | 
| 62 | 
            -
                   | 
| 63 | 
            -
             | 
| 64 | 
            -
                      @id = @@db.execute("select last_insert_rowid();")[0]["last_insert_rowid()"]
         | 
| 65 | 
            -
                      self
         | 
| 66 | 
            -
                    else
         | 
| 67 | 
            -
                      false
         | 
| 68 | 
            -
                    end
         | 
| 69 | 
            -
                  else
         | 
| 70 | 
            -
                    @@db.execute("update phone_numbers set type = ?, number = ?, contact_id = ? where id = ?;", [@type, @number, @contact_id]) ? self : false
         | 
| 71 | 
            -
                  end
         | 
| 15 | 
            +
                def format_number
         | 
| 16 | 
            +
                  n = number.gsub(/[^\d]/, "")
         | 
| 17 | 
            +
                  self.number = n.gsub(/\A(?:\d{1,3})?(\d{3})(\d{3})(\d{4})\z/, '(\1) \2-\3')
         | 
| 72 18 | 
             
                end
         | 
| 73 19 |  | 
| 74 | 
            -
                def to_s
         | 
| 75 | 
            -
                  "#{type.yellow}: #{PhoneNumber.format_number(number).bold}"
         | 
| 76 | 
            -
                end
         | 
| 77 20 | 
             
              end
         | 
| 78 | 
            -
            end
         | 
| 21 | 
            +
            end
         | 
    
        data/lib/tact/rolodex.rb
    CHANGED
    
    | @@ -1,72 +1,76 @@ | |
| 1 | 
            -
            require 'sqlite3'
         | 
| 2 | 
            -
            require 'colored'
         | 
| 3 | 
            -
            require_relative 'card'
         | 
| 4 | 
            -
            require_relative 'database'
         | 
| 5 | 
            -
             | 
| 6 1 | 
             
            module Tact
         | 
| 7 2 | 
             
              class Rolodex
         | 
| 8 3 |  | 
| 9 4 | 
             
                def initialize
         | 
| 10 5 | 
             
                  @cards = load_cards
         | 
| 11 | 
            -
                  @db = Database.new.db
         | 
| 12 6 | 
             
                end
         | 
| 13 7 |  | 
| 14 8 | 
             
                def load_cards
         | 
| 15 | 
            -
                  cards = Contact.all.each_with_index.map do |contact, index|
         | 
| 9 | 
            +
                  cards = Contact.all.order(last_name: :asc, first_name: :asc).each_with_index.map do |contact, index|
         | 
| 16 10 | 
             
                    Card.new(contact, index + 1)
         | 
| 17 11 | 
             
                  end
         | 
| 18 12 | 
             
                  cards
         | 
| 19 13 | 
             
                end
         | 
| 20 14 |  | 
| 21 15 | 
             
                def add_contact(first_name, last_name)
         | 
| 22 | 
            -
                   | 
| 16 | 
            +
                  begin
         | 
| 17 | 
            +
                    Contact.create!(
         | 
| 18 | 
            +
                      first_name: first_name,
         | 
| 19 | 
            +
                      last_name: last_name
         | 
| 20 | 
            +
                    )
         | 
| 21 | 
            +
                  rescue
         | 
| 22 | 
            +
                    puts 'Error: Contact already exists'.red
         | 
| 23 | 
            +
                  end
         | 
| 23 24 | 
             
                end
         | 
| 24 25 |  | 
| 25 | 
            -
                def add_phone_number(contact_index,  | 
| 26 | 
            +
                def add_phone_number(contact_index, kind, number)
         | 
| 26 27 | 
             
                  contact = find_contact(contact_index)
         | 
| 27 | 
            -
                  PhoneNumber. | 
| 28 | 
            +
                  PhoneNumber.create(
         | 
| 29 | 
            +
                    kind: kind,
         | 
| 30 | 
            +
                    number: number,
         | 
| 31 | 
            +
                    contact: contact
         | 
| 32 | 
            +
                  )
         | 
| 28 33 | 
             
                end
         | 
| 29 34 |  | 
| 30 35 | 
             
                def add_email(contact_index, address)
         | 
| 31 36 | 
             
                  contact = find_contact(contact_index)
         | 
| 32 | 
            -
                  Email. | 
| 37 | 
            +
                  Email.create(
         | 
| 38 | 
            +
                    address: address, 
         | 
| 39 | 
            +
                    contact: contact
         | 
| 40 | 
            +
                  )
         | 
| 33 41 | 
             
                end
         | 
| 34 42 |  | 
| 35 43 | 
             
                def delete_contact(contact_index)
         | 
| 36 | 
            -
                   | 
| 37 | 
            -
                  Contact.delete(contact.id)
         | 
| 44 | 
            +
                  find_contact(contact_index).destroy
         | 
| 38 45 | 
             
                end
         | 
| 39 46 |  | 
| 40 47 | 
             
                def delete_phone_number(contact_index, num_index)
         | 
| 41 | 
            -
                   | 
| 42 | 
            -
                  PhoneNumber.delete(phone_number.id)
         | 
| 48 | 
            +
                  find_phone_number(contact_index, num_index).destroy
         | 
| 43 49 | 
             
                end
         | 
| 44 50 |  | 
| 45 51 | 
             
                def delete_email(contact_index, email_index)
         | 
| 46 | 
            -
                   | 
| 47 | 
            -
                  Email.delete(email.id)
         | 
| 52 | 
            +
                  find_email(contact_index, email_index).destroy
         | 
| 48 53 | 
             
                end
         | 
| 49 54 |  | 
| 50 55 | 
             
                def edit_contact_name(contact_index, new_first_name, new_last_name)
         | 
| 51 56 | 
             
                  contact = find_contact(contact_index)
         | 
| 52 | 
            -
                  contact. | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 57 | 
            +
                  contact.update_attributes(
         | 
| 58 | 
            +
                    first_name: new_first_name,
         | 
| 59 | 
            +
                    last_name: new_last_name
         | 
| 60 | 
            +
                  )
         | 
| 55 61 | 
             
                end
         | 
| 56 62 |  | 
| 57 63 | 
             
                def edit_phone_number(contact_index, num_index, new_type, new_number)
         | 
| 58 | 
            -
                  new_type = new_type.downcase.capitalize
         | 
| 59 | 
            -
                  new_number = new_number.gsub(/\D/, "")
         | 
| 60 64 | 
             
                  phone_number = find_phone_number(contact_index, num_index)
         | 
| 61 | 
            -
                  phone_number. | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 65 | 
            +
                  phone_number.update_attributes(
         | 
| 66 | 
            +
                    type: new_type,
         | 
| 67 | 
            +
                    number: new_number
         | 
| 68 | 
            +
                  )
         | 
| 64 69 | 
             
                end
         | 
| 65 70 |  | 
| 66 71 | 
             
                def edit_email(contact_index, email_index, new_address)
         | 
| 67 72 | 
             
                  email = find_email(contact_index, email_index)
         | 
| 68 | 
            -
                  email.address  | 
| 69 | 
            -
                  email.save
         | 
| 73 | 
            +
                  email.update_attributes(address: new_address)
         | 
| 70 74 | 
             
                end
         | 
| 71 75 |  | 
| 72 76 | 
             
                def find_contact(contact_index)
         | 
| @@ -98,21 +102,26 @@ module Tact | |
| 98 102 | 
             
                  end
         | 
| 99 103 | 
             
                end
         | 
| 100 104 |  | 
| 105 | 
            +
                # TODO: Add specs for find methods
         | 
| 101 106 | 
             
                def find_by_name(param)
         | 
| 102 | 
            -
                  param = param.split(" ").map {|name| name. | 
| 107 | 
            +
                  param = param.split(" ").map {|name| name.upcase }.join(" ")
         | 
| 103 108 | 
             
                  search_results = []
         | 
| 104 109 | 
             
                  @cards.each {|card| search_results.push(card) if card.contact.full_name.include?(param)}
         | 
| 105 110 | 
             
                  search_results
         | 
| 106 111 | 
             
                end
         | 
| 107 112 |  | 
| 108 113 | 
             
                def find_by_number(param)
         | 
| 109 | 
            -
                   | 
| 110 | 
            -
                   | 
| 114 | 
            +
                  phone_numbers = PhoneNumber.includes(:contact).where('number LIKE ?', "%#{param}%")
         | 
| 115 | 
            +
                  phone_numbers.map do |phone_number|
         | 
| 116 | 
            +
                    convert_to_card(phone_number.contact.id)
         | 
| 117 | 
            +
                  end
         | 
| 111 118 | 
             
                end
         | 
| 112 119 |  | 
| 113 120 | 
             
                def find_by_email(param)
         | 
| 114 | 
            -
                   | 
| 115 | 
            -
                   | 
| 121 | 
            +
                  emails = Email.includes(:contact).where('address LIKE ?', "%#{param}%")
         | 
| 122 | 
            +
                  emails.map do |email|
         | 
| 123 | 
            +
                    convert_to_card(email.contact.id)
         | 
| 124 | 
            +
                  end
         | 
| 116 125 | 
             
                end
         | 
| 117 126 |  | 
| 118 127 | 
             
                def convert_to_card(contact_id)
         | 
| @@ -120,6 +129,10 @@ module Tact | |
| 120 129 | 
             
                  results[0]
         | 
| 121 130 | 
             
                end
         | 
| 122 131 |  | 
| 132 | 
            +
                def length
         | 
| 133 | 
            +
                  @cards.count
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 123 136 | 
             
                def to_s
         | 
| 124 137 | 
             
                  string = ""
         | 
| 125 138 | 
             
                  @cards.each {|card| string += card.to_s}
         | 
    
        data/lib/tact/tact.rb
    CHANGED
    
    | @@ -1,10 +1,10 @@ | |
| 1 | 
            -
            require_relative "rolodex.rb"
         | 
| 2 1 | 
             
            require 'optparse'
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            require 'colored'
         | 
| 2 | 
            +
            require_relative 'authorizable.rb'
         | 
| 5 3 |  | 
| 6 4 | 
             
            module Tact
         | 
| 7 5 | 
             
              class Tact
         | 
| 6 | 
            +
                include Authorizable
         | 
| 7 | 
            +
             | 
| 8 8 | 
             
                def initialize(args)
         | 
| 9 9 | 
             
                  @dex = Rolodex.new
         | 
| 10 10 | 
             
                  @options = {}
         | 
| @@ -20,12 +20,15 @@ module Tact | |
| 20 20 | 
             
                      opt.on('-d', '--delete', 'Delete entry') do
         | 
| 21 21 | 
             
                        @options[:delete] = true
         | 
| 22 22 | 
             
                      end
         | 
| 23 | 
            -
                      opt.on('-p', '--phone', ' | 
| 23 | 
            +
                      opt.on('-p', '--phone', 'Phone number') do
         | 
| 24 24 | 
             
                        @options[:number] = true
         | 
| 25 25 | 
             
                      end
         | 
| 26 | 
            -
                      opt.on('-e', '--email', ' | 
| 26 | 
            +
                      opt.on('-e', '--email', 'Email') do
         | 
| 27 27 | 
             
                        @options[:email] = true
         | 
| 28 28 | 
             
                      end
         | 
| 29 | 
            +
                      opt.on('-s', '--sync', 'Sync Google contacts') do
         | 
| 30 | 
            +
                        @options[:sync] = true
         | 
| 31 | 
            +
                      end
         | 
| 29 32 | 
             
                      opt.on("-v", "--version", "Version") do 
         | 
| 30 33 | 
             
                        @options[:version] = true
         | 
| 31 34 | 
             
                      end
         | 
| @@ -41,23 +44,24 @@ module Tact | |
| 41 44 | 
             
                end
         | 
| 42 45 |  | 
| 43 46 | 
             
                def help_message
         | 
| 44 | 
            -
                   | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 47 | 
            +
                  <<-EOF
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            -v                                    Current version
         | 
| 50 | 
            +
            -h                                    Help
         | 
| 51 | 
            +
            -s                                    Sync with Google Contacts
         | 
| 48 52 |  | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 53 | 
            +
            <param>                               Search by name
         | 
| 54 | 
            +
            -p <param>                            Search by number
         | 
| 55 | 
            +
            -e <param>                            Search by email
         | 
| 56 | 
            +
            -n <first> <last>                     Adds new name
         | 
| 57 | 
            +
            -np <index> <type> <num>              Adds contact number
         | 
| 58 | 
            +
            -ne <index> <address>                 Adds contact email
         | 
| 59 | 
            +
            -d <index>                            Deletes contact
         | 
| 60 | 
            +
            -dp <index> <num_index>               Deletes contact number
         | 
| 61 | 
            +
            -de <index> <e_index>                 Deletes contact email
         | 
| 62 | 
            +
            -u <index> <first> <last>             Edits contact name
         | 
| 63 | 
            +
            -up <index> <num_index> <type> <num>  Edits contact number
         | 
| 64 | 
            +
            -ue <index> <e_index> <address>       Edits contact email
         | 
| 61 65 |  | 
| 62 66 | 
             
                  EOF
         | 
| 63 67 | 
             
                end
         | 
| @@ -117,7 +121,15 @@ module Tact | |
| 117 121 | 
             
                      new_address = @args[2]
         | 
| 118 122 | 
             
                      @dex.edit_email(contact_index, email_index, new_address)
         | 
| 119 123 | 
             
                    end
         | 
| 120 | 
            -
             | 
| 124 | 
            +
             | 
| 125 | 
            +
                  elsif @options[:sync]
         | 
| 126 | 
            +
                    authorize unless authorized?
         | 
| 127 | 
            +
                    puts "Syncing Google contacts..." 
         | 
| 128 | 
            +
                    GoogleContacts::Entry.all.each do |entry|
         | 
| 129 | 
            +
                      syncer = GoogleContacts::Syncer.new(entry)
         | 
| 130 | 
            +
                      syncer.sync
         | 
| 131 | 
            +
                    end
         | 
| 132 | 
            +
             | 
| 121 133 | 
             
                  elsif @options[:help]
         | 
| 122 134 | 
             
                    puts help_message
         | 
| 123 135 | 
             
                  elsif @options[:version]
         | 
| @@ -202,4 +214,4 @@ module Tact | |
| 202 214 | 
             
                  end
         | 
| 203 215 | 
             
                end
         | 
| 204 216 | 
             
              end
         | 
| 205 | 
            -
            end
         | 
| 217 | 
            +
            end
         | 
    
        data/lib/tact/version.rb
    CHANGED
    
    
    
        data/tact.gemspec
    CHANGED
    
    | @@ -16,12 +16,21 @@ Gem::Specification.new do |spec| | |
| 16 16 | 
             
              spec.files         = `git ls-files -z`.split("\x0").reject do |f|
         | 
| 17 17 | 
             
                f.match(%r{^(test|spec|features)/})
         | 
| 18 18 | 
             
              end
         | 
| 19 | 
            +
              spec.files << 'client_secret.json'
         | 
| 20 | 
            +
             | 
| 19 21 | 
             
              spec.executables   = ["tact"]
         | 
| 20 22 | 
             
              spec.require_paths = ["lib"]
         | 
| 21 23 |  | 
| 22 24 | 
             
              spec.add_runtime_dependency "sqlite3", "~> 1.3"
         | 
| 23 25 | 
             
              spec.add_runtime_dependency "colored", "~> 1.2"
         | 
| 26 | 
            +
              spec.add_runtime_dependency "activerecord", "~> 5.0"
         | 
| 24 27 |  | 
| 25 28 | 
             
              spec.add_development_dependency "bundler", "~> 1.13"
         | 
| 26 29 | 
             
              spec.add_development_dependency "rake", "~> 10.0"
         | 
| 30 | 
            +
              spec.add_development_dependency "rspec", "~> 3.6"
         | 
| 31 | 
            +
              spec.add_development_dependency "database_cleaner", "~> 1.4"
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              spec.requirements << "oauth2l"
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              spec.post_install_message = "Thanks for installing tact! Run `tact -h` for help. Note: If you would like to sync your contacts with Google, please install oauth2l: 'https://github.com/google/oauth2l'"
         | 
| 27 36 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: tact
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 2.0.17
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - ollieshmollie
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2017-06-21 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: sqlite3
         | 
| @@ -38,6 +38,20 @@ dependencies: | |
| 38 38 | 
             
                - - "~>"
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 40 | 
             
                    version: '1.2'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: activerecord
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '5.0'
         | 
| 48 | 
            +
              type: :runtime
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '5.0'
         | 
| 41 55 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 42 56 | 
             
              name: bundler
         | 
| 43 57 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -66,6 +80,34 @@ dependencies: | |
| 66 80 | 
             
                - - "~>"
         | 
| 67 81 | 
             
                  - !ruby/object:Gem::Version
         | 
| 68 82 | 
             
                    version: '10.0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: rspec
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - "~>"
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '3.6'
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - "~>"
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '3.6'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: database_cleaner
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - "~>"
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '1.4'
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - "~>"
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '1.4'
         | 
| 69 111 | 
             
            description: 
         | 
| 70 112 | 
             
            email:
         | 
| 71 113 | 
             
            - oliverduncan@icloud.com
         | 
| @@ -82,11 +124,16 @@ files: | |
| 82 124 | 
             
            - bin/console
         | 
| 83 125 | 
             
            - bin/setup
         | 
| 84 126 | 
             
            - bin/tact
         | 
| 127 | 
            +
            - client_secret.json
         | 
| 128 | 
            +
            - db/migrate/20170515014719_create_tables.rb
         | 
| 129 | 
            +
            - db/migrate/20170515015242_add_foreign_keys.rb
         | 
| 130 | 
            +
            - db/migrate/20170515130759_change_type_on_phone_numbers.rb
         | 
| 85 131 | 
             
            - lib/tact.rb
         | 
| 132 | 
            +
            - lib/tact/authorizable.rb
         | 
| 86 133 | 
             
            - lib/tact/card.rb
         | 
| 87 134 | 
             
            - lib/tact/contact.rb
         | 
| 88 | 
            -
            - lib/tact/database.rb
         | 
| 89 135 | 
             
            - lib/tact/email.rb
         | 
| 136 | 
            +
            - lib/tact/google_client.rb
         | 
| 90 137 | 
             
            - lib/tact/phone_number.rb
         | 
| 91 138 | 
             
            - lib/tact/rolodex.rb
         | 
| 92 139 | 
             
            - lib/tact/tact.rb
         | 
| @@ -96,7 +143,8 @@ homepage: https://www.github.com/ollieshmollie/tact | |
| 96 143 | 
             
            licenses:
         | 
| 97 144 | 
             
            - MIT
         | 
| 98 145 | 
             
            metadata: {}
         | 
| 99 | 
            -
            post_install_message: 
         | 
| 146 | 
            +
            post_install_message: 'Thanks for installing tact! Run `tact -h` for help. Note: If
         | 
| 147 | 
            +
              you would like to sync your contacts with Google, please install oauth2l: ''https://github.com/google/oauth2l'''
         | 
| 100 148 | 
             
            rdoc_options: []
         | 
| 101 149 | 
             
            require_paths:
         | 
| 102 150 | 
             
            - lib
         | 
| @@ -110,9 +158,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 110 158 | 
             
              - - ">="
         | 
| 111 159 | 
             
                - !ruby/object:Gem::Version
         | 
| 112 160 | 
             
                  version: '0'
         | 
| 113 | 
            -
            requirements: | 
| 161 | 
            +
            requirements:
         | 
| 162 | 
            +
            - oauth2l
         | 
| 114 163 | 
             
            rubyforge_project: 
         | 
| 115 | 
            -
            rubygems_version: 2. | 
| 164 | 
            +
            rubygems_version: 2.6.11
         | 
| 116 165 | 
             
            signing_key: 
         | 
| 117 166 | 
             
            specification_version: 4
         | 
| 118 167 | 
             
            summary: A command line rolodex.
         | 
    
        data/lib/tact/database.rb
    DELETED
    
    | @@ -1,47 +0,0 @@ | |
| 1 | 
            -
            require 'sqlite3'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module Tact
         | 
| 4 | 
            -
              class Database
         | 
| 5 | 
            -
                attr_reader :db
         | 
| 6 | 
            -
             | 
| 7 | 
            -
                def initialize
         | 
| 8 | 
            -
                  home_dir = File.expand_path("~")
         | 
| 9 | 
            -
                  Dir.mkdir("#{home_dir}/.tact") unless File.exists?("#{home_dir}/.tact")
         | 
| 10 | 
            -
                  @db = SQLite3::Database.new("#{File.expand_path("~")}/.tact/tact.sqlite3")
         | 
| 11 | 
            -
                  @db.results_as_hash = true
         | 
| 12 | 
            -
                  @db.execute("pragma foreign_keys = on")
         | 
| 13 | 
            -
                  create_tables
         | 
| 14 | 
            -
                end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                def create_tables
         | 
| 17 | 
            -
                  contact_table = <<~eof
         | 
| 18 | 
            -
                    CREATE TABLE IF NOT EXISTS contacts(
         | 
| 19 | 
            -
                      id integer primary key,
         | 
| 20 | 
            -
                      first_name varchar(255) not null,
         | 
| 21 | 
            -
                      last_name varchar(255) not null,
         | 
| 22 | 
            -
                      constraint name_unique unique (first_name, last_name)
         | 
| 23 | 
            -
                    );
         | 
| 24 | 
            -
                    eof
         | 
| 25 | 
            -
                  phone_numbers_table = <<~eof
         | 
| 26 | 
            -
                    CREATE TABLE IF NOT EXISTS phone_numbers(
         | 
| 27 | 
            -
                      id integer primary key,
         | 
| 28 | 
            -
                      type varchar(255),
         | 
| 29 | 
            -
                      number varchar(255),
         | 
| 30 | 
            -
                      contact_id int,
         | 
| 31 | 
            -
                      foreign key (contact_id) references contacts(id) on delete cascade
         | 
| 32 | 
            -
                    );
         | 
| 33 | 
            -
                    eof
         | 
| 34 | 
            -
                  emails_table = <<~eof
         | 
| 35 | 
            -
                    CREATE TABLE IF NOT EXISTS emails(
         | 
| 36 | 
            -
                      id integer primary key,
         | 
| 37 | 
            -
                      address varchar(255),
         | 
| 38 | 
            -
                      contact_id,
         | 
| 39 | 
            -
                      foreign key (contact_id) references contacts(id) on delete cascade
         | 
| 40 | 
            -
                    );
         | 
| 41 | 
            -
                    eof
         | 
| 42 | 
            -
                  @db.execute(contact_table)
         | 
| 43 | 
            -
                  @db.execute(phone_numbers_table)
         | 
| 44 | 
            -
                  @db.execute(emails_table)
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
              end
         | 
| 47 | 
            -
            end
         |