pp-contacts 1.5.1 → 1.5.2

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.
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ pkg/*
3
+ test/accounts.yml
4
+ .rvmrc
5
+ .idea
6
+ *.swp
7
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ gem "rake"
5
+ gem 'gdata19', :git => 'git://github.com/paperlesspost/gdata.git'
6
+ gem 'hpricot'
7
+ gem 'nokogiri'
8
+ gem "json", '> 0.4.1'
9
+
10
+ group :development do
11
+ gem 'gem-release'
12
+ end
13
+
14
+ group :test do
15
+ gem 'shoulda'
16
+ end
@@ -0,0 +1,27 @@
1
+ GIT
2
+ remote: git://github.com/paperlesspost/gdata.git
3
+ revision: 91f3a5b2e701c879ae5dfcb48dec5bb039e37a7b
4
+ specs:
5
+ gdata19 (0.1.9.2)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ gem-release (0.3.1)
11
+ hpricot (0.8.4)
12
+ json (1.5.3)
13
+ nokogiri (1.5.2)
14
+ rake (0.9.2)
15
+ shoulda (2.11.3)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ gdata19!
22
+ gem-release
23
+ hpricot
24
+ json (> 0.4.1)
25
+ nokogiri
26
+ rake
27
+ shoulda
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "pp-contacts"
3
+ s.version = "1.5.2"
4
+ s.date = "2012-04-26"
5
+ s.summary = "A universal interface to grab contact list information from various providers including Outlook, Address Book, Yahoo, AOL, Gmail, Hotmail, and Plaxo."
6
+ s.homepage = "http://github.com/paperlesspost/contacts"
7
+ s.description = "A universal interface to grab contact list information from various providers including Outlook, Address Book, Yahoo, AOL, Gmail, Hotmail, and Plaxo."
8
+ s.has_rdoc = false
9
+ s.authors = ["Lucas Carlson", "Paperless"]
10
+ s.files = `git ls-files`.split($\)
11
+ s.add_dependency("json", ">= 1.1.1")
12
+ s.add_dependency('gdata19')
13
+ s.add_dependency('hpricot')
14
+ s.add_dependency('nokogiri')
15
+ s.add_dependency('encryptor')
16
+ end
@@ -0,0 +1,22 @@
1
+ # Project-specific configuration for CruiseControl.rb
2
+
3
+ Project.configure do |project|
4
+
5
+ # Send email notifications about broken and fixed builds to email1@your.site, email2@your.site (default: send to nobody)
6
+ # if building this on your own CI box, please remove!
7
+ project.email_notifier.emails = ['opensource@pivotallabs.com']
8
+
9
+ # Set email 'from' field to john@doe.com:
10
+ # project.email_notifier.from = 'john@doe.com'
11
+
12
+ # Build the project by invoking rake task 'custom'
13
+ # project.rake_task = 'custom'
14
+
15
+ # Build the project by invoking shell script "build_my_app.sh". Keep in mind that when the script is invoked, current working directory is
16
+ # [cruise]/projects/your_project/work, so if you do not keep build_my_app.sh in version control, it should be '../build_my_app.sh' instead
17
+ # project.build_command = 'build_my_app.sh'
18
+
19
+ # Ping Subversion for new revisions every 5 minutes (default: 30 seconds)
20
+ # project.scheduler.polling_interval = 5.minutes
21
+
22
+ end
@@ -0,0 +1,8 @@
1
+ ---
2
+ defaults:
3
+ install_options: --no-ri --no-rdoc
4
+ gems:
5
+ - name: json
6
+ version: >= 1.1.1
7
+ - name: gdata
8
+ version: >= 1.1.1
@@ -10,7 +10,6 @@ require "erb"
10
10
  class Contacts
11
11
  TYPES = {}
12
12
  FILETYPES = {}
13
- VERSION = "1.4.1"
14
13
 
15
14
  class Base
16
15
  def initialize(login, password, options={})
@@ -0,0 +1,59 @@
1
+ require 'csv'
2
+
3
+ class Contacts
4
+ class Outlook < Base
5
+
6
+ def initialize(file)
7
+ @contact_file = Array.new
8
+ file = file.respond_to?(:read) ? file.read : file
9
+ file.each_line do |line|
10
+ @contact_file << CSV.parse(line)[0]
11
+ end
12
+ @full_name = false
13
+ @header_indexes = Hash.new
14
+ @header_indexes[:email_address] = Array.new
15
+
16
+ headers = @contact_file[0]
17
+
18
+ @contact_file = @contact_file[1, @contact_file.length]
19
+
20
+ headers.each_with_index do |header, i|
21
+ if header.match(/^Name$/)
22
+ @full_name = true
23
+ @header_indexes[:full_name] = i
24
+ elsif header.match(/^First Name/)
25
+ @header_indexes[:first_name] = i
26
+ elsif header.match(/^Last Name/)
27
+ @header_indexes[:last_name] = i
28
+ elsif header.match(/E-mail/)
29
+ @header_indexes[:email_address] << i
30
+ end
31
+ end
32
+ end
33
+
34
+ def contacts
35
+
36
+ contacts = Array.new
37
+
38
+ @contact_file.each_with_index do |line, i|
39
+ contacts[i] = Array.new unless contacts[i]
40
+ if(@full_name)
41
+ contacts[i][0] = line[@header_indexes[:full_name]]
42
+ else
43
+ contacts[i][0] = "#{line[@header_indexes[:first_name]]} #{line[@header_indexes[:last_name]]}"
44
+ end
45
+ @header_indexes[:email_address].each do |index|
46
+ if line[index] && !contacts[i][1]
47
+ contacts[i][1] = line[index]
48
+ end
49
+ end
50
+ end
51
+
52
+ contacts
53
+ end
54
+
55
+ end
56
+
57
+ private
58
+ FILETYPES[:outlook] = Outlook
59
+ end
@@ -0,0 +1,25 @@
1
+ class Contacts
2
+ class Vcf < Base
3
+
4
+ def initialize(file)
5
+ @contact_file = file
6
+ end
7
+
8
+ def contacts
9
+ contacts = Array.new
10
+ i = 0
11
+ @contact_file.each do |line|
12
+ contacts[i] = Array.new unless contacts[i]
13
+ if line.match(/FN:/)
14
+ contacts[i] << line.gsub(/FN:/, '').strip
15
+ elsif line.match(/EMAIL;/)
16
+ contacts[i] << line.gsub(/^.*:/, '').strip
17
+ elsif line.match(/END:VCARD/)
18
+ i += 1
19
+ end
20
+ end
21
+ contacts
22
+ end
23
+ end
24
+ FILETYPES[:vcf] = Vcf
25
+ end
@@ -0,0 +1,40 @@
1
+ gmail:
2
+ username: <changeme>
3
+ password: <changeme>
4
+ contacts:
5
+ -
6
+ name: "FirstName1 LastName1"
7
+ email_address: "firstname1@example.com"
8
+ -
9
+ name: "FirstName2 LastName2"
10
+ email_address: "firstname2@example.com"
11
+ yahoo:
12
+ username: <changeme>
13
+ password: <changeme>
14
+ contacts:
15
+ -
16
+ name: "FirstName1 LastName1"
17
+ email_address: "firstname1@example.com"
18
+ -
19
+ name: "FirstName2 LastName2"
20
+ email_address: "firstname2@example.com"
21
+ hotmail:
22
+ username: <changeme>
23
+ password: <changeme>
24
+ contacts:
25
+ -
26
+ name: "FirstName1 LastName1"
27
+ email_address: "firstname1@example.com"
28
+ -
29
+ name: "FirstName2 LastName2"
30
+ email_address: "firstname2@example.com"
31
+ aol:
32
+ username: <changeme>
33
+ password: <changeme>
34
+ contacts:
35
+ -
36
+ name: "FirstName1 LastName1"
37
+ email_address: "firstname1@example.com"
38
+ -
39
+ name: "FirstName2 LastName2"
40
+ email_address: "firstname2@example.com"
@@ -0,0 +1,3 @@
1
+ First Name,Last Name,E-mail
2
+ FirstName1,LastName1,firstname1@example.com
3
+ FirstName2,LastName2,firstname2@example.com
@@ -0,0 +1,3 @@
1
+ First Name,Last Name,E-mail
2
+ FirstName1,LastName1,firstname1@example.com
3
+ FirstName2,LastName2,firstname2@example.com
@@ -0,0 +1,32 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(dir + "/../lib/")
3
+ require 'test/unit'
4
+ require 'contacts'
5
+ require 'yaml'
6
+ require 'shoulda'
7
+
8
+ class ContactImporterTestCase < Test::Unit::TestCase
9
+ # Add more helper methods to be used by all tests here...
10
+ def default_test
11
+ assert true
12
+ end
13
+ end
14
+
15
+ class TestAccounts
16
+ def self.[](type)
17
+ load[type]
18
+ end
19
+
20
+ def self.load(file = File.dirname(__FILE__) + "/accounts.yml")
21
+ raise "/test/accounts.yml file not found, please create, see /test/example_accounts.yml for information" unless File.exist?(file)
22
+
23
+ accounts = {}
24
+ YAML::load(File.open(file)).each do |type, contents|
25
+ contacts = contents["contacts"].collect {|contact| [contact["name"], contact["email_address"]]}
26
+ accounts[type.to_sym] = Account.new(type.to_sym, contents["username"], contents["password"], contacts)
27
+ end
28
+ accounts
29
+ end
30
+
31
+ Account = Struct.new :type, :username, :password, :contacts
32
+ end
@@ -0,0 +1,4 @@
1
+ dir = File.dirname(__FILE__)
2
+ Dir["#{dir}/**/*_test.rb"].each do |file|
3
+ require file
4
+ end
@@ -0,0 +1,33 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class AolContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:aol]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:aolImporter, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_blank_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:aolImporter, @account.username, "")
18
+ end
19
+ end
20
+
21
+ def test_importer_fails_with_blank_username
22
+ assert_raise(Contacts::AuthenticationError) do
23
+ Contacts.new(:aolImporter, "", @account.password)
24
+ end
25
+ end
26
+
27
+ def test_fetch_contacts
28
+ contacts = Contacts.new(:aolImporter, @account.username, @account.password).contacts
29
+ @account.contacts.each do |contact|
30
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class GmailContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:gmail]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:gmail, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:gmail, @account.username, "wrong_password")
18
+ end
19
+ end
20
+
21
+ def test_importer_fails_with_blank_password
22
+ assert_raise(Contacts::AuthenticationError) do
23
+ Contacts.new(:gmail, @account.username, "")
24
+ end
25
+ end
26
+
27
+ def test_importer_fails_with_blank_username
28
+ assert_raise(Contacts::AuthenticationError) do
29
+ Contacts.new(:gmail, "", @account.password)
30
+ end
31
+ end
32
+
33
+ def test_fetch_contacts
34
+ contacts = Contacts.new(:gmail, @account.username, @account.password).contacts
35
+ @account.contacts.each do |contact|
36
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class HotmailContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:hotmail]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:hotmail, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_msn_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:hotmail, "test@msn.com","wrong_password")
18
+ end
19
+ end
20
+
21
+ def test_importer_fails_with_invalid_password
22
+ assert_raise(Contacts::AuthenticationError) do
23
+ Contacts.new(:hotmail, @account.username,"wrong_password")
24
+ end
25
+ end
26
+
27
+ def test_fetch_contacts
28
+ contacts = Contacts.new(:hotmail, @account.username, @account.password).contacts
29
+ @account.contacts.each do |contact|
30
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
31
+ end
32
+ end
33
+
34
+ # Since the hotmail scraper doesn't read names, test email
35
+ def test_fetch_email
36
+ contacts = Contacts.new(:hotmail, @account.username, @account.password).contacts
37
+ @account.contacts.each do |contact|
38
+ assert contacts.any?{|book_contact| book_contact.last == contact.last }, "Could not find: #{contact.inspect} in #{contacts.inspect}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+
4
+ class OutlookTest < ContactImporterTestCase
5
+ context 'An instance of Outlook' do
6
+ should 'open file properly' do
7
+ outlookobject = Contacts.new(:outlook, File.open(File.dirname(__FILE__) + '/../outlook_fullname.csv'))
8
+ assert_not_nil outlookobject
9
+ end
10
+
11
+ should 'get contacts from file with Full Name' do
12
+ outlookobject = Contacts.new(:outlook, File.open(File.dirname(__FILE__) + '/../outlook_fullname.csv'))
13
+ contacts = outlookobject.contacts
14
+ assert_not_nil contacts
15
+ end
16
+
17
+ should 'get contacts from file with split up names' do
18
+ outlookobject = Contacts.new(:outlook, File.open(File.dirname(__FILE__) + '/../outlook_splitname.csv'))
19
+ contacts = outlookobject.contacts
20
+ assert_not_nil contacts
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+
4
+ class TestAccountsTest < ContactImporterTestCase
5
+ def test_test_accounts_loads_data_from_example_accounts_file
6
+ account = TestAccounts.load(File.dirname(__FILE__) + "/../example_accounts.yml")[:gmail]
7
+
8
+ assert_equal :gmail, account.type
9
+ assert_equal "<changeme>", account.username
10
+ assert_equal "<changeme>", account.password
11
+ assert_equal [["FirstName1 LastName1", "firstname1@example.com"], ["FirstName2 LastName2", "firstname2@example.com"]], account.contacts
12
+ end
13
+
14
+ def test_test_accounts_blows_up_if_file_doesnt_exist
15
+ assert_raise(RuntimeError) do
16
+ TestAccounts.load("file_that_does_not_exist.yml")
17
+ end
18
+ end
19
+
20
+ def test_we_can_load_from_account_file
21
+ assert_not_nil TestAccounts[:gmail].username
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+
4
+ class VcfTest < ContactImporterTestCase
5
+ context 'An instance of Vcf' do
6
+ should 'open file properly' do
7
+ vcfobject = Contacts.new(:vcf, File.open(File.dirname(__FILE__) + '/../vCards.vcf'))
8
+ assert_not_nil vcfobject
9
+ end
10
+
11
+ should 'get contacts from file' do
12
+ vcfobject = Contacts.new(:vcf, File.open(File.dirname(__FILE__) + '/../vCards.vcf'))
13
+ contacts = vcfobject.contacts
14
+ assert_not_nil contacts
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class YahooContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:yahoo]
9
+ end
10
+
11
+ def test_a_successful_login
12
+ Contacts.new(:yahoo, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:yahoo, @account.username, "wrong_password")
18
+ end
19
+ # run the "successful" login test to ensure we reset yahoo's failed login lockout counter
20
+ # See http://www.pivotaltracker.com/story/show/138210
21
+ # yahoo needs some time to unset the failed login state, apparently...
22
+ # ...1 sec and 5 secs still failed sporadically
23
+ sleep 10
24
+ assert_nothing_raised do
25
+ Contacts.new(:yahoo, @account.username, @account.password)
26
+ end
27
+ end
28
+
29
+ def test_a_fetch_contacts
30
+ contacts = Contacts.new(:yahoo, @account.username, @account.password).contacts
31
+ @account.contacts.each do |contact|
32
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ BEGIN:VCARD
2
+ VERSION:3.0
3
+ N:LastName1;FirstName1;;;
4
+ FN:FirstName1 LastName1
5
+ EMAIL;type=INTERNET;type=WORK;type=pref:firstname1@example.com
6
+ CATEGORIES:test
7
+ X-ABUID:3BCA01C2-3EF7-4BB8-BFD4-08ED31416C8F\:ABPerson
8
+ END:VCARD
9
+ BEGIN:VCARD
10
+ VERSION:3.0
11
+ N:LastName2;FirstName2;;;
12
+ FN:FirstName2 LastName2
13
+ EMAIL;type=INTERNET;type=WORK;type=pref:firstname2@example.com
14
+ CATEGORIES:test
15
+ X-ABUID:53F6702D-D2B6-4B46-8C41-CD31F743260B\:ABPerson
16
+ END:VCARD
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pp-contacts
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.5.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -14,7 +14,7 @@ date: 2012-04-26 00:00:00.000000000Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
17
- requirement: &70264084117900 !ruby/object:Gem::Requirement
17
+ requirement: &70303491442280 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 1.1.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70264084117900
25
+ version_requirements: *70303491442280
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: gdata19
28
- requirement: &70264084117380 !ruby/object:Gem::Requirement
28
+ requirement: &70303491441860 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *70264084117380
36
+ version_requirements: *70303491441860
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: hpricot
39
- requirement: &70264084116800 !ruby/object:Gem::Requirement
39
+ requirement: &70303491441400 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: '0'
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *70264084116800
47
+ version_requirements: *70303491441400
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: nokogiri
50
- requirement: &70264084116260 !ruby/object:Gem::Requirement
50
+ requirement: &70303491440980 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: '0'
56
56
  type: :runtime
57
57
  prerelease: false
58
- version_requirements: *70264084116260
58
+ version_requirements: *70303491440980
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: encryptor
61
- requirement: &70264084115760 !ruby/object:Gem::Requirement
61
+ requirement: &70303491440560 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ! '>='
@@ -66,7 +66,7 @@ dependencies:
66
66
  version: '0'
67
67
  type: :runtime
68
68
  prerelease: false
69
- version_requirements: *70264084115760
69
+ version_requirements: *70303491440560
70
70
  description: A universal interface to grab contact list information from various providers
71
71
  including Outlook, Address Book, Yahoo, AOL, Gmail, Hotmail, and Plaxo.
72
72
  email:
@@ -74,18 +74,39 @@ executables: []
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - Gemfile.lock
77
80
  - LICENSE
78
- - Rakefile
79
81
  - README.md
82
+ - Rakefile
83
+ - contacts.gemspec
84
+ - cruise_config.rb
80
85
  - examples/grab_contacts.rb
86
+ - geminstaller.yml
81
87
  - lib/contacts.rb
88
+ - lib/contacts/aol_importer.rb
82
89
  - lib/contacts/base.rb
83
- - lib/contacts/json_picker.rb
84
90
  - lib/contacts/gmail.rb
85
- - lib/contacts/aol_importer.rb
86
91
  - lib/contacts/hotmail.rb
92
+ - lib/contacts/json_picker.rb
93
+ - lib/contacts/outlook.rb
87
94
  - lib/contacts/plaxo.rb
95
+ - lib/contacts/vcf.rb
88
96
  - lib/contacts/yahoo.rb
97
+ - test/example_accounts.yml
98
+ - test/outlook_fullname.csv
99
+ - test/outlook_splitname.csv
100
+ - test/test_helper.rb
101
+ - test/test_suite.rb
102
+ - test/unit/aol_contact_importer_test.rb
103
+ - test/unit/gmail_contact_importer_test.rb
104
+ - test/unit/hotmail_contact_importer_test.rb
105
+ - test/unit/outlook_test.rb
106
+ - test/unit/test_accounts_test.rb
107
+ - test/unit/vcf_test.rb
108
+ - test/unit/yahoo_csv_contact_importer_test.rb
109
+ - test/vCards.vcf
89
110
  homepage: http://github.com/paperlesspost/contacts
90
111
  licenses: []
91
112
  post_install_message: