discodactyl 0.4.1 → 0.5.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.
data/MANIFEST CHANGED
@@ -12,6 +12,8 @@ bin/webfinger
12
12
  discodactyl.gemspec
13
13
  lib/discodactyl.rb
14
14
  lib/discodactyl/acct_uri.rb
15
+ lib/discodactyl/cli.rb
16
+ lib/discodactyl/cli/format.rb
15
17
  lib/discodactyl/host_meta.rb
16
18
  lib/discodactyl/host_meta_jrd.rb
17
19
  lib/discodactyl/jrd.rb
@@ -30,6 +32,7 @@ test/test_helper.rb
30
32
  test/test_host_meta.rb
31
33
  test/test_host_meta_jrd.rb
32
34
  test/test_jrd_document.rb
35
+ test/test_known_accounts.rb
33
36
  test/test_link_header.rb
34
37
  test/test_resource_discovery.rb
35
38
  test/test_uri_template.rb
data/NEWS CHANGED
@@ -1,3 +1,34 @@
1
+ === 0.5.0 / 2010-11-17
2
+
3
+ Brought Out of Retirement!
4
+
5
+ Now using ISC licence instead of AGPL
6
+
7
+ New Features
8
+
9
+ * Support pluggable formatters for rel types
10
+
11
+ Breaking Changes
12
+
13
+ * Discodactyl::KNOWN_RELS has switched its keys and values
14
+
15
+ Bug Fixes
16
+
17
+ * Replace mofo with prism
18
+
19
+ === 0.4.1 / 2011-03-14
20
+
21
+ Bug Fixes
22
+
23
+ * Handle missing JRD hostmeta
24
+
25
+ === 0.4.0 / 2010-11-17
26
+
27
+ New Features
28
+
29
+ * JRD parsing, querying
30
+ * Support many known rel formats
31
+
1
32
  === 0.3.0 / 2010-07-08
2
33
 
3
34
  New Features
@@ -1,14 +1,15 @@
1
1
  = Discodactyl
2
2
 
3
- Code :: http://github.com/josephholsten/discodactyl
4
- Bugs :: http://josephholsten.lighthouseapp.com/projects/36040-discodactyl
5
- Tests :: http://discodactyl-ci.heroku.com/
6
- Docs :: http://rdoc.info/projects/josephholsten/discodactyl
7
- Author :: mailto:joseph@josephholsten.com
3
+ {<img src="https://secure.travis-ci.org/josephholsten/discodactyl.png" alt="Build Status" />}[http://travis-ci.org/josephholsten/discodactyl] | {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/josephholsten/discodactyl] | {<img src="https://gemnasium.com/josephholsten/discodactyl.png" alt="Dependency Status" />}[https://gemnasium.com/josephholsten/discodactyl]
4
+
5
+ {Docs}[http://rdoc.info/projects/josephholsten/discodactyl] | {Code}[https:/github.com/josephholsten/discodactyl] | {Bugs}[http://github.com/josephholsten/discodactyl/issues]
8
6
 
9
7
  == DESCRIPTION:
10
8
 
11
- Discodactyl is an experimental toolkit for XRD service discovery documents and related protocols. It includes implementations of XRD URITemplate Link-Patterns, basic site-meta support, HTTP Link header parsing, acct: URIs and a webfinger poking stick.
9
+ Discodactyl is an experimental toolkit for XRD service discovery
10
+ documents and related protocols. It includes implementations of XRD
11
+ URITemplate Link-Patterns, basic site-meta support, HTTP Link header
12
+ parsing, acct: URIs and a webfinger poking stick.
12
13
 
13
14
  == FEATURES/PROBLEMS:
14
15
 
@@ -18,9 +19,10 @@ Discodactyl is an experimental toolkit for XRD service discovery documents and r
18
19
  * HTTP Link header parsing.
19
20
  * acct: URI parsing.
20
21
  * Minimal URI Template parsing and application.
21
- * Probably filled with bugs. Don't forget your bug powder.
22
22
  * JRD parsing, querying.
23
- * Yahoo 'webfinger' doesn't work. Sadly, they need to update their format to the most recent one.
23
+ * Probably filled with bugs. Don't forget your bug powder.
24
+ * Yahoo 'webfinger' doesn't work. Sadly, they need to update their
25
+ format to the most recent one.
24
26
 
25
27
  == SYNOPSIS:
26
28
 
@@ -46,9 +48,9 @@ For ruby mongers:
46
48
  == REQUIREMENTS:
47
49
 
48
50
  * nokogiri 1.4
49
- * actionpack 2.3
51
+ * actionpack 3.0
50
52
  * feedzirra 0.0.23
51
- * mofo 0.2
53
+ * prism 0.1
52
54
 
53
55
  == INSTALLING:
54
56
 
@@ -56,22 +58,17 @@ See the INSTALL file for details.
56
58
 
57
59
  == LICENSE:
58
60
 
59
- This software is also available in a permissive license. Please contact Joseph Holsten <joseph@josephholsten.com> for more information.
60
-
61
- (The GNU Affero General Public License)
62
-
63
- Copyright (c) 2009, 2010, 2011 Joseph Anthony Pasquale Holsten
64
-
65
- This program is free software: you can redistribute it and/or modify
66
- it under the terms of the GNU Affero General Public License as published by
67
- the Free Software Foundation, either version 3 of the License, or
68
- (at your option) any later version.
69
61
 
70
- This program is distributed in the hope that it will be useful,
71
- but WITHOUT ANY WARRANTY; without even the implied warranty of
72
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
73
- GNU Affero General Public License for more details.
62
+ Copyright (c) 2009, 2010, 2011, 2012 Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
74
63
 
75
- You should have received a copy of the GNU Affero General Public License
76
- along with this program. If not, see <http://www.gnu.org/licenses/>.
64
+ Permission to use, copy, modify, and/or distribute this software for
65
+ any purpose with or without fee is hereby granted, provided that the
66
+ above copyright notice and this permission notice appear in all copies.
77
67
 
68
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
69
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
70
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
71
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
72
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
73
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
74
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/Rakefile CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env rake
2
2
 
3
+ require 'bundler'
4
+ Bundler.setup
5
+
3
6
  require "rake/testtask"
4
7
  Rake::TestTask.new do |test|
5
8
  test.test_files = Dir['test/**/test_*.rb']
data/TODO CHANGED
@@ -25,16 +25,6 @@ Reference Specs
25
25
  * link header http://tools.ietf.org/html/rfc5988
26
26
  * versioning-rels http://tools.ietf.org/html/rfc5829
27
27
 
28
- Interesting webfinger variants
29
- * joseph@josephholsten.com
30
- * romeda@gmail.com
31
- * will@willmeyer.com
32
- * dclinton@gmail.com
33
- * bradfitz@gmail.com
34
- * dclinton@yahoo.com
35
- * beestage@yahoo.com
36
- * gffletch@aol.com
37
-
38
28
  other clients
39
29
  * webfingerclient-dclinton.appspot.com/
40
30
  * webfinger.org/lookup/darron@froese.org
@@ -3,13 +3,14 @@ libdir = File.expand_path('../../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
4
4
 
5
5
  require 'discodactyl'
6
+ require 'discodactyl/cli'
6
7
  require 'open-uri'
7
8
  require 'action_view'
8
- require 'mofo'
9
+ require 'prism'
9
10
  require 'feedzirra'
10
11
  require 'logger'
11
12
 
12
- log = Logger.new(STDOUT)
13
+ log = Logger.new(STDERR)
13
14
  Discodactyl.log = log
14
15
 
15
16
  if ARGV.length == 0
@@ -76,32 +77,13 @@ else
76
77
  log.warn("Disco doc didn't list a subject. Assuming everything is well.")
77
78
  end
78
79
 
79
- hcard_uri = disco.uris_by_rel('http://microformats.org/profile/hcard').first
80
- hcard = hCard.find(:first => hcard_uri) rescue
81
- if hcard
82
- puts "Name: #{hcard.fn.gsub(/\s+/, ' ')}" if hcard.properties.include?('fn')
83
- puts "Title: #{hcard.title}" if hcard.properties.include?('title')
84
- puts "Organization: #{hcard.org}" if hcard.properties.include?('org')
85
- end
86
-
87
80
  links_with_titles = disco.links.select{|l| l.title.present? }
88
81
  links_with_titles.each do |link|
89
82
  puts "#{link.title}: #{link.to_uri}"
90
83
  end
91
84
 
92
- Discodactyl::KNOWN_RELS.each do |name, rel|
85
+ Discodactyl::KNOWN_RELS.each do |rel, name|
93
86
  uri = disco.uris_by_rel(rel).first
94
- puts "#{name}: #{uri}" if uri
95
- end
96
-
97
- activities = disco.uris_by_rel('http://schemas.google.com/g/2010#updates-from').first
98
- if activities
99
- feed = Feedzirra::Feed.fetch_and_parse(activities)
100
- entry = feed.entries.first
101
- if entry
102
- include ActionView::Helpers::DateHelper
103
- silence_warnings do
104
- puts "Status: #{Loofah::Helpers.strip_tags(entry.content)} #{time_ago_in_words(entry.published)} ago"
105
- end
106
- end
87
+ formatter = Discodactyl::CLI::Format.for_rel(rel)
88
+ formatter.call(uri, STDOUT)
107
89
  end
@@ -16,10 +16,10 @@ Gem::Specification.new do |spec|
16
16
  spec.files = File.read("MANIFEST").split(/\r?\n\r?/)
17
17
  spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(/^test\/.*test_.*\.rb$/)
19
- spec.add_runtime_dependency 'nokogiri', '~>1.4.2'
20
- spec.add_runtime_dependency 'actionpack', '~>3.0.0'
21
- spec.add_runtime_dependency 'feedzirra', '~>0.0.23'
22
- spec.add_runtime_dependency 'mofo', '~>0.2.16'
23
- spec.add_development_dependency 'rr', '~>0.10.11'
24
- spec.add_development_dependency 'rake', '~>0.8.7'
19
+ spec.add_runtime_dependency 'nokogiri', '>= 1.4.2'
20
+ spec.add_runtime_dependency 'actionpack', '>= 3.0.8'
21
+ spec.add_runtime_dependency 'feedzirra', '>= 0.0.22'
22
+ spec.add_runtime_dependency 'prism', '>= 0.1.0'
23
+ spec.add_development_dependency 'mocha', '>= 0.12.7'
24
+ spec.add_development_dependency 'rake', '>= 0.9.2.2'
25
25
  end
@@ -9,5 +9,5 @@ require 'discodactyl/uri_template'
9
9
  require 'discodactyl/xrd'
10
10
 
11
11
  module Discodactyl # :nodoc:
12
- VERSION = '0.4.1'
12
+ VERSION = '0.5.0'
13
13
  end
@@ -0,0 +1 @@
1
+ require 'discodactyl/cli/format'
@@ -0,0 +1,69 @@
1
+ module Discodactyl # :nodoc
2
+ module CLI # :nodoc
3
+ # Formatting helpers for various rels
4
+ class Format
5
+ def self.for_rel(rel)
6
+ case rel
7
+ when 'http://microformats.org/profile/hcard' then
8
+ lambda {|uri, io| Discodactyl::CLI::Format.hcard_from_uri(uri, io) }
9
+ when 'http://schemas.google.com/g/2010#updates-from' then
10
+ lambda {|uri, io| Discodactyl::CLI::Format.activities_from_uri(uri, io) }
11
+ else
12
+ if name = Discodactyl::KNOWN_RELS[rel]
13
+ lambda {|uri, io| io.puts "#{name}: #{uri}" if uri }
14
+ else
15
+ lambda {|uri, io| }
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.hcard_adr(adr)
21
+ segments = []
22
+ segments << adr.street_address if adr.properties.include?(:street_address)
23
+ segments << adr.locality if adr.properties.include?(:locality)
24
+ segments << adr.region if adr.properties.include?(:region)
25
+ segments << adr.postal_code if adr.properties.include?(:postal_code)
26
+ segments << adr.country_name if adr.properties.include?(:country_name)
27
+ segments.join(' ')
28
+ end
29
+
30
+ def self.hcard_from_uri(uri, io)
31
+ begin
32
+ Discodactyl.log.debug("hCard lookup for #{uri}") if Discodactyl.log
33
+ if hcards = Prism.find(uri, :hcard)
34
+ hcards.each do |hcard|
35
+ if hcard
36
+ io << "Name: #{hcard.fn.gsub(/\s+/, ' ')}\n" if hcard.properties.include?(:fn)
37
+ io << "Title: #{hcard.title}\n" if hcard.properties.include?(:title)
38
+ io << "Organization: #{hcard.org}\n" if hcard.properties.include?(:org)
39
+ io << "Address: #{hcard_adr(hcard.adr)}\n" if hcard.properties.include?(:adr)
40
+ end
41
+ end
42
+ end
43
+ rescue RuntimeError => e
44
+ Discodactyl.log.warn(e.message) if Discodactyl.log
45
+ Discodactyl.log.debug(e) if Discodactyl.log
46
+ end
47
+ end
48
+
49
+ def self.activities_from_uri(uri, io)
50
+ Discodactyl.log.debug("Activity feed lookup for #{uri}") if Discodactyl.log
51
+ begin
52
+ if uri && (feed = Feedzirra::Feed.fetch_and_parse(uri)) && feed.respond_to?(:entries)
53
+ Array(feed.entries).each do |entry|
54
+ include ActionView::Helpers::DateHelper
55
+ silence_warnings do
56
+ io.puts "Status: #{Loofah::Helpers.strip_tags(entry.content)} #{time_ago_in_words(entry.published)} ago"
57
+ end
58
+ end
59
+ else
60
+ Discodactyl.log.debug("Failed activity feed lookup with uri: #{uri.inspect} feed: #{feed.inspect}") if Discodactyl.log
61
+ end
62
+ rescue
63
+ Discodactyl.log.warn($!) if Discodactyl.log
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -1,12 +1,14 @@
1
1
  module Discodactyl # :nodoc:
2
2
  KNOWN_RELS = {
3
- 'OpenID' => 'http://specs.openid.net/auth/',
4
- 'OpenID Provider' => 'http://specs.openid.net/auth/2.0/provider',
5
- 'OpenID Provider' => 'openid2.provider',
6
- "Profile" => 'http://webfinger.net/rel/profile-page',
7
- "Profile data" => 'http://portablecontacts.net/spec/1.0#me',
8
- "Contacts" => 'http://portablecontacts.net/spec/1.0',
9
- "describedby" => 'describedby', # from POWDER
10
- "Webfinger/LRDD" => 'lrdd'
3
+ 'http://microformats.org/profile/hcard' => 'hcard',
4
+ 'http://specs.openid.net/auth/' => 'OpenID',
5
+ 'http://specs.openid.net/auth/2.0/provider' => 'OpenID Provider',
6
+ 'openid2.provider' => 'OpenID Provider',
7
+ 'http://webfinger.net/rel/profile-page' => 'Profile',
8
+ 'http://portablecontacts.net/spec/1.0#me' => 'Profile data',
9
+ 'http://portablecontacts.net/spec/1.0' => 'Contacts',
10
+ 'describedby' => 'describedby', # from POWDER
11
+ 'lrdd' => 'Webfinger/LRDD',
12
+ 'http://schemas.google.com/g/2010#updates-from' => 'activities'
11
13
  }
12
- end
14
+ end
@@ -1,4 +1,6 @@
1
1
  require "test/unit"
2
+ require "mocha"
3
+
2
4
  module Test::Unit::Assertions
3
5
  def assert_length(expected, enum, message = nil)
4
6
  message = build_message message, '<?> is not length <?>', enum, expected
@@ -10,8 +12,3 @@ module Test::Unit::Assertions
10
12
  assert enum.include?(atom), message
11
13
  end
12
14
  end
13
-
14
- class Test::Unit::TestCase
15
- require 'rr'
16
- include RR::Adapters::TestUnit
17
- end
@@ -1,12 +1,22 @@
1
1
  #!/usr/bin/env ruby -w
2
2
  libdir = File.expand_path('../../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
4
+ testdir = File.expand_path('../../test', __FILE__)
5
+ $LOAD_PATH.unshift(testdir) unless $LOAD_PATH.include?(testdir)
4
6
 
5
- require "test/unit"
6
- require "discodactyl/host_meta_jrd"
7
+ require 'test/unit'
8
+ require 'test_helper'
9
+ require 'discodactyl/host_meta_jrd'
7
10
  require 'discodactyl/acct_uri'
8
11
 
9
12
  class TestHostMetaJRD < Test::Unit::TestCase
13
+ def setup
14
+ @json = <<JSON
15
+ { "links": {
16
+ "lrdd": [{
17
+ "href":"http://host.example/discovery.jrd" }]}}
18
+ JSON
19
+ end
10
20
  def test_get_uri_from_host
11
21
  uri = 'host.example'
12
22
  expected = URI.parse 'http://host.example/.well-known/host-meta.json'
@@ -27,20 +37,16 @@ class TestHostMetaJRD < Test::Unit::TestCase
27
37
 
28
38
  def test_from_uri
29
39
  uri = URI.parse 'acct:josephholsten@localhost'
30
- assert_equal({"links"=>{"lrdd"=>[{"href"=>"http://localhost/discovery.jrd"}]}}, Discodactyl::HostMetaJRD.from_uri(uri))
40
+ URI::HTTP.any_instance.stubs(:open).returns(StringIO.new(@json))
41
+ assert_equal({'links'=>{'lrdd'=>[{'href'=>'http://host.example/discovery.jrd'}]}}, Discodactyl::HostMetaJRD.from_uri(uri))
31
42
  end
32
43
 
33
44
  def test_parse
34
- json = <<JSON
35
- { "links": {
36
- "lrdd": [{
37
- "href":"http://host.example/discovery.jrd" }]}}
38
- JSON
39
- assert_equal({"links"=>{"lrdd"=>[{"href"=>"http://host.example/discovery.jrd"}]}}, Discodactyl::HostMetaJRD.parse(json))
45
+ assert_equal({'links'=>{'lrdd'=>[{'href'=>'http://host.example/discovery.jrd'}]}}, Discodactyl::HostMetaJRD.parse(@json))
40
46
  end
41
47
 
42
48
  def test_links_by_rel
43
- jrd = Discodactyl::HostMetaJRD[{"links"=>{"lrdd"=>[{"href"=>"http://host.example/discovery.jrd"}]}}]
49
+ jrd = Discodactyl::HostMetaJRD[{'links'=>{'lrdd'=>[{'href'=>'http://host.example/discovery.jrd'}]}}]
44
50
  expected = [Discodactyl::JRD::Link.parse({'href'=>'http://host.example/discovery.jrd', 'rel' => 'lrdd'})]
45
51
  assert_equal(expected, jrd.links_by_rel('lrdd'))
46
52
  end
@@ -52,12 +58,12 @@ JSON
52
58
  end
53
59
 
54
60
  def test_uris_by_rel
55
- jrd = Discodactyl::HostMetaJRD[{"links"=>{"lrdd"=>[{"href"=>"http://host.example/discovery.jrd"}]}}]
61
+ jrd = Discodactyl::HostMetaJRD[{'links'=>{'lrdd'=>[{'href'=>'http://host.example/discovery.jrd'}]}}]
56
62
  assert_equal(['http://host.example/discovery.jrd'], jrd.uris_by_rel('lrdd'))
57
63
  end
58
64
 
59
65
  def test_uris_by_rel_with_template
60
- jrd = Discodactyl::HostMetaJRD[{"links"=>{"lrdd"=>[{"template"=>"http://host.example/{id}"}]}}]
66
+ jrd = Discodactyl::HostMetaJRD[{'links'=>{'lrdd'=>[{'template'=>'http://host.example/{id}'}]}}]
61
67
  assert_equal(['http://host.example/foo'], jrd.uris_by_rel('lrdd', 'id' => 'foo'))
62
68
  end
63
69
  end
File without changes
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby -w
2
+ libdir = File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
4
+
5
+ require "test/unit"
6
+
7
+ class TestKnownAccounts < Test::Unit::TestCase
8
+
9
+ def test_dclinton
10
+ expected =<<EOF
11
+ OpenID Provider: http://www.google.com/profiles/dclinton
12
+ Profile: http://www.google.com/profiles/dclinton
13
+ Profile data: http://www-opensocial.googleusercontent.com/api/people/117377434815709898403/
14
+ Contacts: http://www-opensocial.googleusercontent.com/api/people/
15
+ describedby: http://www.google.com/profiles/dclinton
16
+ EOF
17
+ assert_equal expected, `bin/webfinger dclinton@gmail.com`
18
+ end
19
+
20
+ def test_romeda
21
+ expected =<<EOF
22
+ OpenID Provider: http://www.google.com/profiles/romeda
23
+ Profile: http://www.google.com/profiles/romeda
24
+ Profile data: http://www-opensocial.googleusercontent.com/api/people/113651174506128852447/
25
+ Contacts: http://www-opensocial.googleusercontent.com/api/people/
26
+ describedby: http://www.google.com/profiles/romeda
27
+ EOF
28
+ assert_equal expected, `bin/webfinger romeda@gmail.com`
29
+ end
30
+
31
+ def test_bradfitz
32
+ expected =<<EOF
33
+ OpenID Provider: http://www.google.com/profiles/bradfitz
34
+ Profile: http://www.google.com/profiles/bradfitz
35
+ Profile data: http://www-opensocial.googleusercontent.com/api/people/115863474911002159675/
36
+ Contacts: http://www-opensocial.googleusercontent.com/api/people/
37
+ describedby: http://www.google.com/profiles/bradfitz
38
+ EOF
39
+ assert_equal expected, `bin/webfinger bradfitz@gmail.com`
40
+ end
41
+
42
+ def test_willmeyer
43
+ expected =<<EOF
44
+ describedby: http://www.google.com/profiles/willmeyer
45
+ EOF
46
+ assert_equal expected, `bin/webfinger will@willmeyer.com`
47
+ end
48
+
49
+ def test_josephholsten
50
+ expected =<<EOF
51
+ Name: Joseph Anthony Pasquale Holsten
52
+ Address: 5813 East 64th Place Tulsa Oklahoma 74136-2132 USA
53
+ OpenID Provider: https://www.myopenid.com/server
54
+ Profile: http://josephholsten.com
55
+ describedby: http://josephholsten.com
56
+ EOF
57
+ assert_equal expected, `bin/webfinger joseph@josephholsten.com`
58
+ end
59
+
60
+ # Yahoo webfinger causes my ruby to segfault!
61
+ # def test_gffletch
62
+ # expected =<<EOF
63
+ # assert_equal expected, `bin/webfinger dclinton@yahoo.com`
64
+ # assert_equal expected, `bin/webfinger beestage@yahoo.com`
65
+ # end
66
+
67
+ # AOL webfinger now dies at http://www.aol.com/.well-known/host-meta with 503
68
+ # RIP
69
+ # def test_gffletch
70
+ # expected =<<EOF
71
+ # assert_equal expected, `bin/webfinger gffletch@aol.com`
72
+ # end
73
+ end