~srushe/indieweb-authorship

a95f7ecc3f5682ed1fda0c803af1c38d226af4dc — Stephen Rushe 2 years ago fcc9f61 + 0a2de14
Merge pull request #1 from srushe/version-0-1-0

Actually implement the authorship algorithm
37 files changed, 986 insertions(+), 0 deletions(-)

A .rspec
A CHANGELOG.md
A Gemfile
A Gemfile.lock
A indieweb-authorship.gemspec
A lib/indieweb/authorship.rb
A lib/indieweb/authorship/version.rb
A spec/examples/about
A spec/examples/about-no-url
A spec/examples/about-rel-me
A spec/examples/about-url-uid
A spec/examples/about-with-multiple-urls
A spec/examples/author-name-is-0
A spec/examples/h-entry-author-is-name
A spec/examples/h-entry-author-is-rel-link-to-h-card-on-page
A spec/examples/h-entry-author-is-rel-link-to-h-card-with-rel-me
A spec/examples/h-entry-author-is-rel-link-to-h-card-with-url-uid
A spec/examples/h-entry-author-is-url-to-h-card-on-page
A spec/examples/h-entry-author-is-url-to-h-card-with-multiple-links
A spec/examples/h-entry-author-is-url-to-h-card-with-no-url
A spec/examples/h-entry-has-h-card-and-url-author
A spec/examples/h-entry-has-h-card-author
A spec/examples/h-feed-has-h-card-author
A spec/examples/h-feed-has-multiple-entries-with-different-authors
A spec/h-entry-author-is-name_spec.rb
A spec/h-entry-author-is-rel-link-to-h-card-on-page_spec.rb
A spec/h-entry-author-is-rel-link-to-h-card-with-rel-me_spec.rb
A spec/h-entry-author-is-rel-link-to-h-card-with-url-uid_spec.rb
A spec/h-entry-author-is-url-to-h-card-on-page_spec.rb
A spec/h-entry-author-is-url-to-h-card-with-multiple-links_spec.rb
A spec/h-entry-author-is-url-to-h-card-with-no-url_spec.rb
A spec/h-entry-has-h-card-and-url-author_spec.rb
A spec/h-entry-has-h-card-author_spec.rb
A spec/h-feed-has-h-card-author_spec.rb
A spec/indieweb/authorship_spec.rb
A spec/spec_helper.rb
A spec/support/html_for.rb
A .rspec => .rspec +3 -0
@@ 0,0 1,3 @@
--format documentation
--color
--require spec_helper

A CHANGELOG.md => CHANGELOG.md +12 -0
@@ 0,0 1,12 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.0] - 2018-11-20

### Added
- Initial release.

A Gemfile => Gemfile +6 -0
@@ 0,0 1,6 @@
source "https://rubygems.org"

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in indieweb-authorship.gemspec
gemspec

A Gemfile.lock => Gemfile.lock +43 -0
@@ 0,0 1,43 @@
PATH
  remote: .
  specs:
    indieweb-authorship (0.1.0)
      microformats (~> 4.0, >= 4.0.9)

GEM
  remote: https://rubygems.org/
  specs:
    diff-lcs (1.3)
    json (2.1.0)
    microformats (4.0.9)
      json (~> 2.1)
      nokogiri (~> 1.8, >= 1.8.3)
    mini_portile2 (2.3.0)
    nokogiri (1.8.5)
      mini_portile2 (~> 2.3.0)
    rake (12.3.1)
    rspec (3.7.0)
      rspec-core (~> 3.7.0)
      rspec-expectations (~> 3.7.0)
      rspec-mocks (~> 3.7.0)
    rspec-core (3.7.1)
      rspec-support (~> 3.7.0)
    rspec-expectations (3.7.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.7.0)
    rspec-mocks (3.7.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.7.0)
    rspec-support (3.7.1)

PLATFORMS
  ruby

DEPENDENCIES
  bundler (~> 1.16)
  indieweb-authorship!
  rake (~> 12.3)
  rspec (~> 3.0)

BUNDLED WITH
   1.17.1

A indieweb-authorship.gemspec => indieweb-authorship.gemspec +36 -0
@@ 0,0 1,36 @@
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "indieweb/authorship/version"

Gem::Specification.new do |spec|
  spec.name          = "indieweb-authorship"
  spec.version       = Indieweb::Authorship::VERSION
  spec.authors       = ["Stephen Rushe"]
  spec.email         = ["steve+authorship@deeden.co.uk"]

  spec.summary       = 'Identify the author of an IndieWeb post'
  spec.description   = 'Identify the author of an IndieWeb post using the Authorship algorithm'
  spec.homepage      = "https://github.com/srushe/indieweb-authorship"
  spec.license       = "MIT"

  spec.metadata = {
    'bug_tracker_uri' => 'https://github.com/srushe/indieweb-authorship/issues',
    'changelog_uri'   => 'https://github.com/srushe/indieweb-authorship/blob/master/CHANGELOG.md',
    'homepage_uri'    => 'https://github.com/srushe/indieweb-authorship/'
  }

  # Specify which files should be added to the gem when it is released.
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]

  spec.add_runtime_dependency "microformats", "~> 4.0", ">= 4.0.9"

  spec.add_development_dependency "bundler", "~> 1.16"
  spec.add_development_dependency "rake", "~> 12.3"
  spec.add_development_dependency "rspec", "~> 3.0"
end

A lib/indieweb/authorship.rb => lib/indieweb/authorship.rb +186 -0
@@ 0,0 1,186 @@
# frozen_string_literal: true

require 'indieweb/authorship/version'
require 'microformats'
require 'net/http'

module Indieweb
  module Authorship
    def self.identify(url, html = nil)
      collection = microformats_from(html: html, url: url)

      # 1. start with a particular h-entry to determine authorship for, and
      #    no author. if no h-entry, then there's no post to find authorship
      #    for, abort.
      h_entry = hentry_from(collection['items'])
      return if h_entry.nil?

      # 3. if the h-entry has an author property, use that
      if h_entry['properties'].key?('author')
        author_data = h_entry['properties']['author']
      end

      # 4. otherwise if the h-entry has a parent h-feed with author property
      #    use that - TODO
      if author_data.nil?
        author_data = h_feed_author_for(h_entry, items: collection['items'])
      end

      # 5. if an author property was found
      unless author_data.nil?
        hcard = author_data.find { |entry| hcard?(entry) }

        # 5.1. if it has an h-card, use it, exit.
        if !hcard.nil?
          return hcard_data_for(
            URI.join(url, hcard['properties']['url'][0]).to_s,
            hcard['properties']['name'][0],
            URI.join(url, hcard['properties']['photo'][0]).to_s
          )

        # 5.2. otherwise if author property is an http(s) URL, let the
        #      author-page have that URL
        elsif author_data[0].start_with?('http://', 'https://')
          return find_author_from(url: author_data[0],
                                  original_page_items: collection['items'])

        # 5.3. otherwise use the author property as the author name, exit
        else
          return hcard_data_for(nil, author_data[0], nil)
        end
      end

      # 6. if there is no author-page and the h-entry's page is a permalink
      #    page, then
      # 6.1. if the page has a rel-author link, let the author-page's URL
      #      be the href of the rel-author link

      # 7. if there is an author-page URL
      return unless collection['rels'].key?('author')

      find_author_from(url: collection['rels']['author'][0],
                       original_page_items: collection['items'])
    end

    def self.microformats_from(html: nil, url:)
      html ||= Net::HTTP.get(URI(url))
      ::Microformats.parse(html, base: url)
    end
    private_class_method :microformats_from

    def self.hcard?(data)
      data.is_a?(Hash) && data.key?('type') &&
        data['type'].is_a?(Array) && data['type'].include?('h-card')
    end
    private_class_method :hcard?

    def self.hentry_from(source)
      h_entry = source.find { |item| item['type'][0] == 'h-entry' }
      return h_entry unless h_entry.nil?
      source.each do |item|
        if item['type'][0] == 'h-feed' && item.key?('children') && item['children'][0]['type'][0] == 'h-entry'
          return item['children'][0]
        end
      end

      nil
    end
    private_class_method :hentry_from

    def self.hcards_from(source)
      source.select { |entry| hcard?(entry) }
    end
    private_class_method :hcards_from

    def self.h_feed_author_for(h_entry, items:)
      h_feed = items.find do |item|
        item['type'][0] == 'h-feed' && item['children'].include?(h_entry)
      end
      return if h_feed.nil?

      h_feed['properties']['author']
    end
    private_class_method :h_feed_author_for

    def self.hcard_data_for(url, name, photo)
      {
        'url' => url,
        'name' => name,
        'photo' => photo
      }
    end
    private_class_method :hcard_data_for

    def self.find_author_from(url:, original_page_items:)
      # 7.1. get the author-page from that URL and parse it for microformats2
      collection = microformats_from(url: url)

      hcards = hcards_from(collection['items'])

      # 7.2, 7.3, and 7.4
      hcard_matching_url_uid_and_author_page(hcards, url) ||
        hcard_matching_rel_me_link(hcards, url, collection['rels']) ||
        hcard_matching_url_and_author_page_url(original_page_items, url)
    end
    private_class_method :find_author_from

    # 7.2. if author-page has 1+ h-card with url == uid == author-page's
    #      URL, then use first such h-card, exit.
    def self.cards_with_matching_url_and_uid(hcards)
      hcards = hcards.collect { |card| card['properties'] }
      hcards.select do |card|
        card.key?('url') && card.key?('uid') &&
          card['url'] == card['uid']
      end
    end
    private_class_method :cards_with_matching_url_and_uid

    def self.hcard_matching_url_uid_and_author_page(hcards = [], author_page)
      return if hcards.empty? || author_page.nil?

      cards_with_matching_url_and_uid = cards_with_matching_url_and_uid(hcards)
      hcard = cards_with_matching_url_and_uid.find do |card|
        card['url'].include?(author_page)
      end
      return if hcard.nil?

      hcard_data_for(author_page, hcard['name'][0], hcard['photo'][0])
    end
    private_class_method :hcard_matching_url_uid_and_author_page

    # 7.3. else if author-page has 1+ h-card with url property which
    #      matches the href of a rel-me link on the author-page (perhaps
    #      the same hyperlink element as the u-url, though not required
    #      to be), use first such h-card, exit.
    def self.card_with_matching_rel_me_link(hcards, rels_me)
      return if rels_me.nil?

      hcards.find do |card|
        !(card['properties']['url'] & rels_me).empty?
      end
    end
    private_class_method :card_with_matching_rel_me_link

    def self.hcard_matching_rel_me_link(hcards, url, rels = {})
      hcard = card_with_matching_rel_me_link(hcards, rels['me'])
      return if hcard.nil?

      hcard_data_for(url,
                     hcard['properties']['name'][0],
                     hcard['properties']['photo'][0])
    end
    private_class_method :hcard_matching_rel_me_link

    # 7.4. if the h-entry's page has 1+ h-card with url == author-page URL,
    #      use first such h-card, exit.
    def self.hcard_matching_url_and_author_page_url(items, url)
      hcard = hcards_from(items).find do |card|
        card['properties']['url'].include?(url)
      end

      hcard_data_for(url,
                     hcard['properties']['name'][0],
                     hcard['properties']['photo'][0])
    end
  end
end

A lib/indieweb/authorship/version.rb => lib/indieweb/authorship/version.rb +5 -0
@@ 0,0 1,5 @@
module Indieweb
  module Authorship
    VERSION = "0.1.0"
  end
end

A spec/examples/about => spec/examples/about +23 -0
@@ 0,0 1,23 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Author</title>
</head>
<body>

  <div class="h-card">
    <a href="/about" class="u-url">
      <img src="/photo.jpg" class="u-photo">
      <span class="p-name">Author Full Name</span>
      <!-- includes a different name here than is used in the files that link here, in order to ensure they are not fetching this page -->
    </a>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/about-no-url => spec/examples/about-no-url +22 -0
@@ 0,0 1,22 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Author</title>
</head>
<body>

  <div class="h-card">
    <h2 class="p-name">Author Full Name</h2>
    <a href="xmpp://user@host.example" class="u-url" rel="me">xmpp</a>
    <img src="/photo.jpg" class="u-photo">
  </div>
  <!-- this page has no u-url or rel=me pointing to itself but there is only one h-card -->
  
</body>
</html>
\ No newline at end of file

A spec/examples/about-rel-me => spec/examples/about-rel-me +23 -0
@@ 0,0 1,23 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Author</title>
  <link rel="me" href="/about-rel-me">
</head>
<body>

  <div class="h-card">
    <a href="/about-rel-me" class="u-url">
      <img src="/photo.jpg" class="u-photo">
      <span class="p-name">Author Full Name</span>
    </a>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/about-url-uid => spec/examples/about-url-uid +22 -0
@@ 0,0 1,22 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Author</title>
</head>
<body>

  <div class="h-card">
    <a href="/about-url-uid" class="u-url u-uid">
      <img src="/photo.jpg" class="u-photo">
      <span class="p-name">Author Full Name</span>
    </a>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/about-with-multiple-urls => spec/examples/about-with-multiple-urls +22 -0
@@ 0,0 1,22 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Author</title>
</head>
<body>

  <div class="h-card">
    <h2 class="p-name">Author Full Name</h2>
    <a href="xmpp://user@host.example" class="u-url" rel="me">xmpp</a>
    <a href="/about-with-multiple-urls" class="u-url" rel="me">Me</a>
    <img src="/photo.jpg" class="u-photo">
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/author-name-is-0 => spec/examples/author-name-is-0 +22 -0
@@ 0,0 1,22 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Author</title>
</head>
<body>

  <div class="h-card">
    <a href="/author-name-is-0" class="u-url">
      <img src="/photo.jpg" class="u-photo">
      <span class="p-name">0</span>
    </a>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-author-is-name => spec/examples/h-entry-author-is-name +20 -0
@@ 0,0 1,20 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">
    <div class="p-author">Author Name</div>
    <p class="p-name e-content">Hello World</p>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-author-is-rel-link-to-h-card-on-page => spec/examples/h-entry-author-is-rel-link-to-h-card-on-page +28 -0
@@ 0,0 1,28 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">
    <p class="p-name e-content">Hello World</p>
  </div>

  <div class="h-card">
    <a href="/about" class="u-url">
      <img src="/photo.jpg" class="u-photo">
      <span class="p-name">Author</span>
    </a>
  </div>

  <a href="/about" rel="author">Author</a>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-author-is-rel-link-to-h-card-with-rel-me => spec/examples/h-entry-author-is-rel-link-to-h-card-with-rel-me +21 -0
@@ 0,0 1,21 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">
    <p class="p-name e-content">Hello World</p>
  </div>

  <a href="/about-rel-me" rel="author">Author</a>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-author-is-rel-link-to-h-card-with-url-uid => spec/examples/h-entry-author-is-rel-link-to-h-card-with-url-uid +21 -0
@@ 0,0 1,21 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">
    <p class="p-name e-content">Hello World</p>
  </div>

  <a href="/about-url-uid" rel="author">Author</a>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-author-is-url-to-h-card-on-page => spec/examples/h-entry-author-is-url-to-h-card-on-page +28 -0
@@ 0,0 1,28 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">
    <p class="p-name e-content">Hello World</p>
    <a href="/about" class="u-author">Author</a>
    <a href="/h-entry-author-is-url-to-h-card-on-page" class="u-url">permalink</a>
  </div>

  <div class="h-card">
    <a href="/about" class="u-url">
      <img src="/photo.jpg" class="u-photo">
      <span class="p-name">Author</span>
    </a>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-author-is-url-to-h-card-with-multiple-links => spec/examples/h-entry-author-is-url-to-h-card-with-multiple-links +21 -0
@@ 0,0 1,21 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">
    <a href="/about-with-multiple-urls" class="u-author">Author</a>
    <p class="p-name e-content">Hello World</p>
    <a href="/h-entry-author-is-url-to-h-card-with-multiple-links" class="u-url">permalink</a>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-author-is-url-to-h-card-with-no-url => spec/examples/h-entry-author-is-url-to-h-card-with-no-url +21 -0
@@ 0,0 1,21 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">
    <a href="/about-no-url" class="u-author">Author</a>
    <p class="p-name e-content">Hello World</p>
    <a href="/h-entry-author-is-url-to-h-card-with-no-url" class="u-url">permalink</a>
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-has-h-card-and-url-author => spec/examples/h-entry-has-h-card-and-url-author +31 -0
@@ 0,0 1,31 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">

    <a href="/about" class="u-author">Author</a>
    
    <div class="p-author h-card">
      <a href="/about" class="u-url">
        <img src="/photo.jpg" class="u-photo">
        <span class="p-name">Author</span>
      </a>
    </div>

    <p class="p-name e-content">Hello World</p>

    <a href="/h-entry-has-h-card-and-url-author" class="u-url">permalink</a>    
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-entry-has-h-card-author => spec/examples/h-entry-has-h-card-author +28 -0
@@ 0,0 1,28 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-entry">

    <div class="p-author h-card">
      <a href="/about" class="u-url">
        <img src="/photo.jpg" class="u-photo">
        <span class="p-name">Author</span>
      </a>
    </div>

    <p class="p-name e-content">Hello World</p>
    
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-feed-has-h-card-author => spec/examples/h-feed-has-h-card-author +30 -0
@@ 0,0 1,30 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-feed">

    <div class="p-author h-card">
      <a href="/about" class="u-url">
        <img src="/photo.jpg" class="u-photo">
        <span class="p-name">Author</span>
      </a>
    </div>

    <div class="h-entry">
      <p class="p-name e-content">Hello World</p>
    </div>
    
  </div>

</body>
</html>
\ No newline at end of file

A spec/examples/h-feed-has-multiple-entries-with-different-authors => spec/examples/h-feed-has-multiple-entries-with-different-authors +23 -0
@@ 0,0 1,23 @@
HTTP/1.1 200 OK
Server: Apache
Date: Wed, 09 Dec 2015 03:29:14 GMT
Content-Type: text/html; charset=utf-8

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Example</title>
</head>
<body>

  <div class="h-feed">

    <div class="h-entry">
      <p class="p-name e-content">Hello World</p>
    </div>
    
  </div>

</body>
</html>
\ No newline at end of file

A spec/h-entry-author-is-name_spec.rb => spec/h-entry-author-is-name_spec.rb +24 -0
@@ 0,0 1,24 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-author-is-name' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:expected_data) do
    {
      'url' => nil,
      'name' => 'Author Name',
      'photo' => nil
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-entry-author-is-rel-link-to-h-card-on-page_spec.rb => spec/h-entry-author-is-rel-link-to-h-card-on-page_spec.rb +26 -0
@@ 0,0 1,26 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-author-is-rel-link-to-h-card-on-page' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { URI('http://author.example.com/about') }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about',
      'name' => 'Author',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-entry-author-is-rel-link-to-h-card-with-rel-me_spec.rb => spec/h-entry-author-is-rel-link-to-h-card-with-rel-me_spec.rb +26 -0
@@ 0,0 1,26 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-author-is-rel-link-to-h-card-with-rel-me' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { URI('http://author.example.com/about-rel-me') }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about-rel-me',
      'name' => 'Author Full Name',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about-rel-me') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-entry-author-is-rel-link-to-h-card-with-url-uid_spec.rb => spec/h-entry-author-is-rel-link-to-h-card-with-url-uid_spec.rb +26 -0
@@ 0,0 1,26 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-author-is-rel-link-to-h-card-with-url-uid' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { URI('http://author.example.com/about-url-uid') }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about-url-uid',
      'name' => 'Author Full Name',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about-url-uid') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-entry-author-is-url-to-h-card-on-page_spec.rb => spec/h-entry-author-is-url-to-h-card-on-page_spec.rb +26 -0
@@ 0,0 1,26 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-author-is-url-to-h-card-on-page' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { URI('http://author.example.com/about') }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about',
      'name' => 'Author',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-entry-author-is-url-to-h-card-with-multiple-links_spec.rb => spec/h-entry-author-is-url-to-h-card-with-multiple-links_spec.rb +27 -0
@@ 0,0 1,27 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-author-is-url-to-h-card-with-multiple-links' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { URI('http://author.example.com/about-with-multiple-urls') }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about-with-multiple-urls',
      'name' => 'Author Full Name',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about-rel-me') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end

end

A spec/h-entry-author-is-url-to-h-card-with-no-url_spec.rb => spec/h-entry-author-is-url-to-h-card-with-no-url_spec.rb +26 -0
@@ 0,0 1,26 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-author-is-url-to-h-card-with-no-url' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { URI('http://author.example.com/about-no-url') }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about-no-url',
      'name' => 'Author Full Name',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about-no-url') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-entry-has-h-card-and-url-author_spec.rb => spec/h-entry-has-h-card-and-url-author_spec.rb +26 -0
@@ 0,0 1,26 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-has-h-card-and-url-author' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { 'http://author.example.com/about' }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about',
      'name' => 'Author',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-entry-has-h-card-author_spec.rb => spec/h-entry-has-h-card-author_spec.rb +26 -0
@@ 0,0 1,26 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-entry-has-h-card-author' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { 'http://author.example.com/about' }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about',
      'name' => 'Author',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
    allow(Net::HTTP).to receive(:get).with(linked_url) { html_for('about') }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/h-feed-has-h-card-author_spec.rb => spec/h-feed-has-h-card-author_spec.rb +25 -0
@@ 0,0 1,25 @@
RSpec.describe Indieweb::Authorship do
  let(:page) { 'h-feed-has-h-card-author' }
  let(:url) { "http://author.example.com/#{page}" }
  let(:html) { html_for(page) }
  let(:linked_url) { 'http://author.example.com/about' }
  let(:expected_data) do
    {
      'url' => 'http://author.example.com/about',
      'name' => 'Author',
      'photo' => 'http://author.example.com/photo.jpg'
    }
  end

  before do
    allow(Net::HTTP).to receive(:get).with(URI(url)) { html }
  end

  context 'when given just a URL' do
    it { expect(described_class.identify(url)).to eq expected_data }
  end

  context 'when given both a URL and HTML' do
    it { expect(described_class.identify(url, html)).to eq expected_data }
  end
end

A spec/indieweb/authorship_spec.rb => spec/indieweb/authorship_spec.rb +5 -0
@@ 0,0 1,5 @@
RSpec.describe Indieweb::Authorship do
  it "has a version number" do
    expect(Indieweb::Authorship::VERSION).not_to be nil
  end
end

A spec/spec_helper.rb => spec/spec_helper.rb +20 -0
@@ 0,0 1,20 @@
require "bundler/setup"
require "indieweb/authorship"

Dir[
  File.join(__dir__, 'support/*rb')
].each { |file| require file }

RSpec.configure do |config|
  # Enable flags like --only-failures and --next-failure
  config.example_status_persistence_file_path = ".rspec_status"

  # Disable RSpec exposing methods globally on `Module` and `main`
  config.disable_monkey_patching!

  config.expect_with :rspec do |c|
    c.syntax = :expect
  end

  config.include HtmlFor
end

A spec/support/html_for.rb => spec/support/html_for.rb +6 -0
@@ 0,0 1,6 @@
module HtmlFor
  def html_for(filename)
    file = File.join(File.dirname(__FILE__), '../examples', filename)
    File.read(file).sub(/.*?(<!DOCTYPE html>.*)/m, '\1')
  end
end