tact 1.2.23 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 646f2aeb31eeec006ea63f14c1e323cd8f4943c4
4
- data.tar.gz: 03ae8aee41865c91c1052b986b261c6077f021b3
3
+ metadata.gz: 0c97568e1e4da1c76294dd69bdfc31af58cd5ded
4
+ data.tar.gz: 887f421efa7a7fb0a5272f798706518403a4fbb2
5
5
  SHA512:
6
- metadata.gz: 22c70b517f55076452ed7580571c8d609ef11608a3cd8a9f26d192ff6eaf401b0c6d2adb0fdd6ebeb6a7f200d13bd6689748f442b4755370a1916b9f351037bb
7
- data.tar.gz: 5bb758c735ccf9df330fa73efa6def114e46543843b7d79313b896022a8cf60e2bf027240935086b6144bb7d19e1c0f7417afcf246a16b02edfa6ab5b97c97d7
6
+ metadata.gz: d65d88384b495953b11699693eae7179b22a0af7cdfa818079768da6b26e8f7e7e72d1a6046eb1350eb0eec9e404721956a471906860dd4c7ae75400cf78adfd
7
+ data.tar.gz: 611fcd81f8c5d8d0b451065607195adde2380be76db5640dd0f3d908f2f4133d83b12fce69cc33a97ecee86caac9f34e594383e82463661a42e0bc4a41ee2da8
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ client_secret.json
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/ollieshmollie/tact.
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 "bundler/gem_tasks"
2
- task :default => :spec
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
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'tact'
3
3
 
4
- Tact::Tact.new(ARGV).run
4
+ Tact::Tact.new(ARGV).run
@@ -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
@@ -0,0 +1,6 @@
1
+ class AddForeignKeys < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :phone_numbers, :contact_id, :integer
4
+ add_column :emails, :contact_id, :integer
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class ChangeTypeOnPhoneNumbers < ActiveRecord::Migration[4.2]
2
+ def change
3
+ rename_column :phone_numbers, :type, :kind
4
+ end
5
+ end
data/lib/tact.rb CHANGED
@@ -1 +1,50 @@
1
- require 'tact/tact'
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
- @phone_numbers.each_with_index {|number, i| string += "\s\s" + "[#{i + 1}] " + number.to_s + "\n"}
17
- @emails.each_with_index {|address, i| string += "\s\s\s\s" + "[#{i + 1}] " + address.to_s + "\n"}
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
- attr_reader :id
8
- attr_accessor :index, :first_name, :last_name
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
- def emails
70
- email_hashes = @@db.execute("select * from emails where contact_id = ?", [@id])
71
- email_hashes.map {|e_hash| Email.from_hash(e_hash) }
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
- attr_reader :id
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
- "<#{@address}>"
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
@@ -1,78 +1,21 @@
1
- require 'colored'
2
- require_relative 'database'
3
-
4
1
  module Tact
5
- class PhoneNumber
6
- attr_reader :id
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
- def self.find_by_id(id)
45
- number_hashes = @@db.execute("select * from phone_numbers where id = ?", [id])
46
- self.from_hash(number_hashes[0]) if !number_hashes.empty?
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 self.find_by_number(number)
50
- number_hashes = @@db.execute("select * from phone_numbers where number = ?", [number])
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 save
62
- if @id == nil
63
- if @@db.execute("INSERT INTO phone_numbers (type, number, contact_id) values (?, ?, ?);", [@type, @number, @contact_id])
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
- Contact.new(first_name, last_name).save
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, type, number)
26
+ def add_phone_number(contact_index, kind, number)
26
27
  contact = find_contact(contact_index)
27
- PhoneNumber.new(type, number, contact.id).save
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.new(address, contact.id).save
37
+ Email.create(
38
+ address: address,
39
+ contact: contact
40
+ )
33
41
  end
34
42
 
35
43
  def delete_contact(contact_index)
36
- contact = find_contact(contact_index)
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
- phone_number = find_phone_number(contact_index, num_index)
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
- email = find_email(contact_index, email_index)
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.first_name = new_first_name.downcase.capitalize
53
- contact.last_name = new_last_name.downcase.capitalize
54
- contact.save
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.type = new_type
62
- phone_number.number = new_number
63
- phone_number.save
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 = new_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.capitalize }.join(" ")
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
- contact_ids = @db.execute("select distinct contact_id from phone_numbers where number like '%#{param}%';")
110
- contact_ids.map {|hash| convert_to_card(hash["contact_id"]) }
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
- contact_ids = @db.execute("select distinct contact_id from emails where address like '%#{param}%';")
115
- contact_ids.map {|hash| convert_to_card(hash["contact_id"]) }
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
- require 'tact/version'
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', 'Edit phone number') do
23
+ opt.on('-p', '--phone', 'Phone number') do
24
24
  @options[:number] = true
25
25
  end
26
- opt.on('-e', '--email', 'Edit email') do
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
- <<~EOF
45
-
46
- -v Current version
47
- -h Help
47
+ <<-EOF
48
+
49
+ -v Current version
50
+ -h Help
51
+ -s Sync with Google Contacts
48
52
 
49
- <param> Search by name
50
- -p <param> Search by number
51
- -e <param> Search by email
52
- -n <first> <last> Adds new name
53
- -np <index> <type> <num> Adds contact number
54
- -ne <index> <address> Adds contact email
55
- -d <index> Deletes contact
56
- -dp <index> <num_index> Deletes contact number
57
- -de <index> <e_index> Deletes contact email
58
- -u <index> <first> <last> Edits contact name
59
- -up <index> <num_index> <type> <num> Edits contact number
60
- -ue <index> <e_index> <address> Edits contact email
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
@@ -1,3 +1,3 @@
1
1
  module Tact
2
- VERSION = "1.2.23"
2
+ VERSION = "2.0.17"
3
3
  end
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: 1.2.23
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: 2016-12-20 00:00:00.000000000 Z
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.5.1
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