ppl 1.18.0 → 1.19.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a9d02380f3d44f146c22dd0e466d98ae56ac97d
4
- data.tar.gz: e85bf61f41a01927c72450f761563ad9669c5fdd
3
+ metadata.gz: 1e392faaea1882705856f5dbe483e493ca0c427a
4
+ data.tar.gz: 2334459a46cc72d92aa8c3c2f0a500a15de3fb5c
5
5
  SHA512:
6
- metadata.gz: 0f325d2c01e00c070edac9f3c6066185bf13bb20c7c25f1c659534485a833e28b7b5b6c7a25b42fe016f33b417800df2567a7ae591b8736a49027cc1cdada069
7
- data.tar.gz: a388cc2f1f2af9c50afa01da54ac54994c9d2b25f290a75e07bc041b8f9c4051c27697fc997bdaec46d2739c920d7d610eeb814261610c90525ac1e30b1c9cca
6
+ metadata.gz: 6227509847307b130c84049c0ce92063ea9079320e76e9530021a0fe7b3cd75425e1276abc2fbc35dcb2e6c0bb85f0c44181cd5235362e9fca4764f167cab584
7
+ data.tar.gz: 0e737fbd7ec0817918693c406587efab38d7f8f3dc8eb1f11577669665cf2a0750e5fdf70c6c445716848e94e6d45aeb7d47f6d316eb2625937c120e1cff60bc
data/completions/bash CHANGED
@@ -6,7 +6,7 @@ _ppl()
6
6
  prev="${COMP_WORDS[COMP_CWORD-1]}"
7
7
 
8
8
  # Complete options
9
- opts="add age bday email init ls mutt mv name nick org phone post pull push remote rm shell show url version"
9
+ opts="add age bday completion email init ls mutt mv name nick org phone post pull push remote rm scrape shell show url version"
10
10
  if [[ ${prev} == "ppl" ]]; then
11
11
  COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
12
12
  return 0
data/completions/zsh CHANGED
@@ -10,6 +10,7 @@ _1st_arguments=(
10
10
  "add:Add a new contact"
11
11
  "age:List or show contacts's ages"
12
12
  "bday:List, show or change birthdays"
13
+ "completion:Output shell completion function"
13
14
  "email:Show or change a contact's email address"
14
15
  "init:Create an empty address book"
15
16
  "ls:List all contacts"
@@ -24,6 +25,7 @@ _1st_arguments=(
24
25
  "push:Execute 'git push' in the address book directory"
25
26
  "remote:Execute 'git remote' in the address book directory"
26
27
  "rm:Delete a contact"
28
+ "scrape:Scrape contact details from stdin"
27
29
  "shell:Interactive mode"
28
30
  "show:Display the full details of a contact"
29
31
  "url:List, show or change URLs"
data/lib/ppl.rb CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Ppl
3
3
 
4
- Version = "1.18.0"
4
+ Version = "1.19.0"
5
5
 
6
6
  module Adapter
7
7
  end
@@ -26,6 +26,8 @@ end
26
26
 
27
27
  require "ppl/adapter/color"
28
28
  require "ppl/adapter/color/colored"
29
+ require "ppl/adapter/email_scraper"
30
+ require "ppl/adapter/email_scraper/mail"
29
31
  require "ppl/adapter/storage"
30
32
  require "ppl/adapter/storage/disk"
31
33
  require "ppl/adapter/storage/factory"
@@ -64,6 +66,7 @@ require "ppl/command/url"
64
66
  require "ppl/command/version"
65
67
  require "ppl/command/external"
66
68
  require "ppl/command/completion"
69
+ require "ppl/command/scrape"
67
70
 
68
71
  require "ppl/entity/address_book"
69
72
  require "ppl/entity/contact"
@@ -0,0 +1,9 @@
1
+
2
+ class Ppl::Adapter::EmailScraper
3
+
4
+ def scrape_contacts(email)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ end
9
+
@@ -0,0 +1,40 @@
1
+
2
+ require "mail"
3
+
4
+ class Ppl::Adapter::EmailScraper::Mail
5
+
6
+ def scrape_contacts(email)
7
+ email = Mail.new(email)
8
+ contacts = []
9
+
10
+ sender = scrape_sender(email)
11
+ unless sender.nil?
12
+ contacts << sender
13
+ end
14
+
15
+ contacts
16
+ end
17
+
18
+ private
19
+
20
+ def scrape_sender(email)
21
+ from = email[:from]
22
+ unless from.nil?
23
+ sender = Ppl::Entity::Contact.new
24
+ sender.name = from.tree.addresses.first.display_name
25
+ sender.email_addresses << from.tree.addresses.first.address
26
+ sender.id = generate_contact_id(sender)
27
+ sender
28
+ end
29
+ end
30
+
31
+ def generate_contact_id(contact)
32
+ if !contact.name.nil?
33
+ contact.name.downcase.tr(" ", "_")
34
+ elsif !contact.email_addresses.empty?
35
+ contact.email_addresses.first
36
+ end
37
+ end
38
+
39
+ end
40
+
@@ -139,6 +139,13 @@ class Ppl::Application::Bootstrap
139
139
  rm
140
140
  end
141
141
 
142
+ register :command_scrape do
143
+ scrape = Ppl::Command::Scrape.new
144
+ scrape.storage = storage_adapter
145
+ scrape.email_scraper = email_scraper
146
+ scrape
147
+ end
148
+
142
149
  register :command_shell do
143
150
  shell = Ppl::Command::Shell.new
144
151
  shell.storage = storage_adapter
@@ -186,6 +193,7 @@ class Ppl::Application::Bootstrap
186
193
  suite.add_command command_push
187
194
  suite.add_command command_remote
188
195
  suite.add_command command_rm
196
+ suite.add_command command_scrape
189
197
  suite.add_command command_shell
190
198
  suite.add_command command_show
191
199
  suite.add_command command_url
@@ -337,5 +345,9 @@ class Ppl::Application::Bootstrap
337
345
  Ppl::Adapter::Vcard::GreenCard.new
338
346
  end
339
347
 
348
+ register :email_scraper do
349
+ Ppl::Adapter::EmailScraper::Mail.new
350
+ end
351
+
340
352
  end
341
353
 
@@ -4,11 +4,13 @@ class Ppl::Application::Input
4
4
  attr_accessor :arguments
5
5
  attr_accessor :options
6
6
  attr_accessor :stdin
7
+ attr_accessor :argf
7
8
 
8
9
  def initialize(arguments=[])
9
10
  @arguments = arguments
10
11
  @options = {}
11
12
  @stdin = $stdin
13
+ @argf = ARGF
12
14
  end
13
15
 
14
16
  end
@@ -0,0 +1,65 @@
1
+
2
+ require "readline"
3
+
4
+ class Ppl::Command::Scrape < Ppl::Application::Command
5
+
6
+ name "scrape"
7
+ description "Scrape contact details from stdin"
8
+
9
+ attr_writer :email_scraper
10
+
11
+ def options(parser, options)
12
+ parser.banner = "usage: ppl scrape [<options>]"
13
+ parser.on("-q", "--quiet", "Add contacts to the address book without prompting") do |i|
14
+ options[:quiet] = i
15
+ end
16
+ parser.on("-s", "--sender", "Scrape the sender's contact details") do |i|
17
+ options[:sender] = i
18
+ end
19
+ end
20
+
21
+ def execute(input, output)
22
+ contacts = scrape_email(input)
23
+ contacts.each do |contact|
24
+ if store_contact?(contact, input)
25
+ @storage.save_contact(contact)
26
+ end
27
+ end
28
+ return true
29
+ end
30
+
31
+ private
32
+
33
+ def scrape_email(input)
34
+ ARGV.shift
35
+ email = input.stdin.read
36
+ contacts = []
37
+ if input.options[:sender]
38
+ contacts |= scrape_sender(email)
39
+ end
40
+ contacts
41
+ end
42
+
43
+ def scrape_sender(email)
44
+ @email_scraper.scrape_contacts(email)
45
+ end
46
+
47
+ def store_contact?(contact, input)
48
+ if input.options[:quiet]
49
+ true
50
+ else
51
+ input.stdin.reopen("/dev/tty", "r") if input.stdin.eof?
52
+ message = generate_prompt_string(contact)
53
+ Readline.readline(message).downcase != "n"
54
+ end
55
+ end
56
+
57
+ def generate_prompt_string(contact)
58
+ sprintf('Add "%s <%s>" to your address book [Y/n]? ',
59
+ contact.name,
60
+ contact.email_addresses.first
61
+ )
62
+ end
63
+
64
+ end
65
+
data/ppl.gemspec CHANGED
@@ -2,8 +2,8 @@
2
2
  Gem::Specification.new do |spec|
3
3
 
4
4
  spec.name = "ppl"
5
- spec.version = "1.18.0"
6
- spec.date = "2013-04-16"
5
+ spec.version = "1.19.0"
6
+ spec.date = "2013-04-19"
7
7
 
8
8
  spec.required_ruby_version = ">= 1.9.3"
9
9
 
@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
13
13
 
14
14
  spec.add_dependency("colored", "1.2")
15
15
  spec.add_dependency("inifile", "2.0.2")
16
+ spec.add_dependency("mail", "2.5.3")
16
17
  spec.add_dependency("morphine", "0.1.1")
17
18
  spec.add_dependency("rugged", "0.17.0.b6")
18
19
  spec.add_dependency("greencard", "0.0.5")
@@ -0,0 +1,81 @@
1
+
2
+ describe Ppl::Adapter::EmailScraper::Mail do
3
+
4
+ before(:each) do
5
+ @adapter = Ppl::Adapter::EmailScraper::Mail.new
6
+ end
7
+
8
+ describe "#scrape_contacts" do
9
+
10
+ it "should return an array" do
11
+ @adapter.scrape_contacts("").should eq []
12
+ end
13
+
14
+ it "should scrape the sender's name" do
15
+ email = [
16
+ "Date: Fri, 30 Nov 2012 17:09:33 +0000",
17
+ "From: Test User <test@example.org>",
18
+ "Message-ID: <qwertyuioasdfghjk@mail.example.org>",
19
+ "Subject: Test Email",
20
+ "To: henry@henrysmith.org",
21
+ "",
22
+ "Hey,",
23
+ "This is a test email.",
24
+ "Bye!",
25
+ ].join("\n")
26
+ contacts = @adapter.scrape_contacts(email)
27
+ contacts.first.name.should eq "Test User"
28
+ end
29
+
30
+ it "should scrape the sender's email address" do
31
+ email = [
32
+ "Date: Fri, 30 Nov 2012 17:09:33 +0000",
33
+ "From: Test User <test@example.org>",
34
+ "Message-ID: <qwertyuioasdfghjk@mail.example.org>",
35
+ "Subject: Test Email",
36
+ "To: henry@henrysmith.org",
37
+ "",
38
+ "Hey,",
39
+ "This is a test email.",
40
+ "Bye!",
41
+ ].join("\n")
42
+ contacts = @adapter.scrape_contacts(email)
43
+ contacts.first.email_addresses.first.should eq "test@example.org"
44
+ end
45
+
46
+ it "should generate an ID for the sender based on their name" do
47
+ email = [
48
+ "Date: Fri, 30 Nov 2012 17:09:33 +0000",
49
+ "From: Test User <test@example.org>",
50
+ "Message-ID: <qwertyuioasdfghjk@mail.example.org>",
51
+ "Subject: Test Email",
52
+ "To: henry@henrysmith.org",
53
+ "",
54
+ "Hey,",
55
+ "This is a test email.",
56
+ "Bye!",
57
+ ].join("\n")
58
+ contacts = @adapter.scrape_contacts(email)
59
+ contacts.first.id.should eq "test_user"
60
+ end
61
+
62
+ it "should generate a sender ID based on email address if there's no name" do
63
+ email = [
64
+ "Date: Fri, 30 Nov 2012 17:09:33 +0000",
65
+ "From: test@example.org",
66
+ "Message-ID: <qwertyuioasdfghjk@mail.example.org>",
67
+ "Subject: Test Email",
68
+ "To: henry@henrysmith.org",
69
+ "",
70
+ "Hey,",
71
+ "This is a test email.",
72
+ "Bye!",
73
+ ].join("\n")
74
+ contacts = @adapter.scrape_contacts(email)
75
+ contacts.first.id.should eq "test@example.org"
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+
@@ -0,0 +1,15 @@
1
+
2
+ describe Ppl::Adapter::EmailScraper do
3
+
4
+ before(:each) do
5
+ @adapter = Ppl::Adapter::EmailScraper.new
6
+ end
7
+
8
+ describe "#scrape_contacts" do
9
+ it "should raise not implemented error" do
10
+ expect{@adapter.scrape_contacts("")}.to raise_error(NotImplementedError)
11
+ end
12
+ end
13
+
14
+ end
15
+
@@ -127,6 +127,12 @@ describe Ppl::Application::Bootstrap do
127
127
  end
128
128
  end
129
129
 
130
+ describe "#command_scrape" do
131
+ it "should return a Ppl::Command::Scrape" do
132
+ @bootstrap.command_scrape.should be_a(Ppl::Command::Scrape)
133
+ end
134
+ end
135
+
130
136
  describe "#command_shell" do
131
137
  it "should return a Ppl::Command::Shell" do
132
138
  @bootstrap.command_shell.should be_a(Ppl::Command::Shell)
@@ -464,6 +470,9 @@ describe Ppl::Application::Bootstrap do
464
470
  it "should contain the 'rm' command" do
465
471
  @bootstrap.command_suite.find_command("rm").should_not be nil
466
472
  end
473
+ it "should contain the 'scrape' command" do
474
+ @bootstrap.command_suite.find_command("scrape").should_not be nil
475
+ end
467
476
  it "should contain the 'show' command" do
468
477
  @bootstrap.command_suite.find_command("show").should_not be nil
469
478
  end
@@ -0,0 +1,97 @@
1
+
2
+ describe Ppl::Command::Scrape do
3
+
4
+ before(:each) do
5
+ @command = Ppl::Command::Scrape.new
6
+ @input = Ppl::Application::Input.new
7
+ @input.stdin = double(IO)
8
+ @contact = Ppl::Entity::Contact.new
9
+ @output = double(Ppl::Application::Output)
10
+ @email_scraper = double(Ppl::Adapter::EmailScraper)
11
+ @storage = double(Ppl::Adapter::Storage)
12
+ @address_book = double(Ppl::Entity::AddressBook)
13
+ @command.storage = @storage
14
+ @command.email_scraper = @email_scraper
15
+ end
16
+
17
+ describe "#name" do
18
+ it "should be 'scrape'" do
19
+ @command.name.should eq "scrape"
20
+ end
21
+ end
22
+
23
+ describe "#execute" do
24
+
25
+ before(:each) do
26
+ @input.stdin.stub(:read)
27
+ @input.stdin.stub(:reopen)
28
+ @input.stdin.stub(:eof?)
29
+ @email_scraper.stub(:scrape_contacts).and_return([])
30
+ Readline.stub(:readline).and_return("")
31
+ @storage.stub(:save_contact)
32
+ end
33
+
34
+ it "should read input from stdin" do
35
+ @input.options[:sender] = true
36
+ @input.stdin.should_receive(:read)
37
+ @command.execute(@input, @output)
38
+ end
39
+
40
+ it "should pass input to the email scraper" do
41
+ @input.options[:sender] = true
42
+ @email_scraper.should_receive(:scrape_contacts)
43
+ @command.execute(@input, @output)
44
+ end
45
+
46
+ it "shouldn't do any scraping unless told which fields to scrape" do
47
+ @email_scraper.should_not_receive(:scrape_contacts)
48
+ @command.execute(@input, @output)
49
+ end
50
+
51
+ it "should always save contacts if in quiet mode" do
52
+ @input.options[:sender] = true
53
+ @input.options[:quiet] = true
54
+ @email_scraper.stub(:scrape_contacts).and_return([@contact])
55
+ Readline.should_not_receive(:readline)
56
+ @storage.should_receive(:save_contact)
57
+ @command.execute(@input, @output)
58
+ end
59
+
60
+ it "should reopen stdin to prompt the user" do
61
+ @input.options[:sender] = true
62
+ @input.stdin.should_receive(:eof?).and_return(true)
63
+ @input.stdin.should_receive(:reopen)
64
+ @email_scraper.stub(:scrape_contacts).and_return([@contact])
65
+ @command.execute(@input, @output)
66
+ end
67
+
68
+ it "should save the contact if the user approves" do
69
+ @input.options[:sender] = true
70
+ @email_scraper.stub(:scrape_contacts).and_return([@contact])
71
+ Readline.should_receive(:readline).and_return("y")
72
+ @storage.should_receive(:save_contact)
73
+ @command.execute(@input, @output)
74
+ end
75
+
76
+ it "should not save the contact if the user disapproves" do
77
+ @input.options[:sender] = true
78
+ @email_scraper.stub(:scrape_contacts).and_return([@contact])
79
+ Readline.should_receive(:readline).and_return("n")
80
+ @storage.should_not_receive(:save_contact)
81
+ @command.execute(@input, @output)
82
+ end
83
+
84
+ it "should prompt the user with the contact's name and email address" do
85
+ @input.options[:sender] = true
86
+ contact = Ppl::Entity::Contact.new
87
+ contact.name = "Test Person"
88
+ contact.email_addresses << "test@example.org"
89
+ @email_scraper.stub(:scrape_contacts).and_return([contact])
90
+ Readline.should_receive(:readline).with("Add \"Test Person <test@example.org>\" to your address book [Y/n]? ")
91
+ @command.execute(@input, @output)
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ppl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.18.0
4
+ version: 1.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henry Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-16 00:00:00.000000000 Z
11
+ date: 2013-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colored
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: mail
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 2.5.3
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 2.5.3
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: morphine
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -159,6 +173,8 @@ files:
159
173
  - lib/ppl.rb
160
174
  - lib/ppl/adapter/color.rb
161
175
  - lib/ppl/adapter/color/colored.rb
176
+ - lib/ppl/adapter/email_scraper.rb
177
+ - lib/ppl/adapter/email_scraper/mail.rb
162
178
  - lib/ppl/adapter/storage.rb
163
179
  - lib/ppl/adapter/storage/disk.rb
164
180
  - lib/ppl/adapter/storage/factory.rb
@@ -191,6 +207,7 @@ files:
191
207
  - lib/ppl/command/phone.rb
192
208
  - lib/ppl/command/post.rb
193
209
  - lib/ppl/command/rm.rb
210
+ - lib/ppl/command/scrape.rb
194
211
  - lib/ppl/command/shell.rb
195
212
  - lib/ppl/command/show.rb
196
213
  - lib/ppl/command/url.rb
@@ -230,6 +247,8 @@ files:
230
247
  - ppl.gemspec
231
248
  - spec/ppl/adapter/color/colored_spec.rb
232
249
  - spec/ppl/adapter/color_spec.rb
250
+ - spec/ppl/adapter/email_scraper/mail_spec.rb
251
+ - spec/ppl/adapter/email_scraper_spec.rb
233
252
  - spec/ppl/adapter/output_spec.rb
234
253
  - spec/ppl/adapter/storage/disk_spec.rb
235
254
  - spec/ppl/adapter/storage/factory_spec.rb
@@ -262,6 +281,7 @@ files:
262
281
  - spec/ppl/command/phone_spec.rb
263
282
  - spec/ppl/command/post_spec.rb
264
283
  - spec/ppl/command/rm_spec.rb
284
+ - spec/ppl/command/scrape_spec.rb
265
285
  - spec/ppl/command/shell_spec.rb
266
286
  - spec/ppl/command/show_spec.rb
267
287
  - spec/ppl/command/url_spec.rb