micromicro 1.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -1
  3. data/CONTRIBUTING.md +3 -3
  4. data/README.md +9 -102
  5. data/lib/micro_micro/collectible.rb +2 -0
  6. data/lib/micro_micro/collections/base_collection.rb +8 -1
  7. data/lib/micro_micro/collections/items_collection.rb +84 -1
  8. data/lib/micro_micro/collections/properties_collection.rb +111 -0
  9. data/lib/micro_micro/collections/relationships_collection.rb +85 -6
  10. data/lib/micro_micro/document.rb +21 -103
  11. data/lib/micro_micro/helpers.rb +94 -0
  12. data/lib/micro_micro/implied_property.rb +15 -0
  13. data/lib/micro_micro/item.rb +93 -79
  14. data/lib/micro_micro/parsers/base_implied_property_parser.rb +29 -0
  15. data/lib/micro_micro/parsers/base_property_parser.rb +6 -12
  16. data/lib/micro_micro/parsers/date_time_parser.rb +61 -25
  17. data/lib/micro_micro/parsers/date_time_property_parser.rb +10 -6
  18. data/lib/micro_micro/parsers/embedded_markup_property_parser.rb +4 -2
  19. data/lib/micro_micro/parsers/implied_name_property_parser.rb +15 -16
  20. data/lib/micro_micro/parsers/implied_photo_property_parser.rb +21 -43
  21. data/lib/micro_micro/parsers/implied_url_property_parser.rb +12 -30
  22. data/lib/micro_micro/parsers/plain_text_property_parser.rb +4 -1
  23. data/lib/micro_micro/parsers/url_property_parser.rb +22 -12
  24. data/lib/micro_micro/parsers/value_class_pattern_parser.rb +29 -42
  25. data/lib/micro_micro/property.rb +126 -56
  26. data/lib/micro_micro/relationship.rb +38 -13
  27. data/lib/micro_micro/version.rb +3 -1
  28. data/lib/micromicro.rb +32 -26
  29. data/micromicro.gemspec +11 -6
  30. metadata +22 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 468713aa68dff8d4c8cf860b8b66e973edc430d44ad246f0079ac0a66cfed10f
4
- data.tar.gz: ca17b8077a3934e23c73664ddaba7bd4e3a3e481cea778137fad5870fb6a9734
3
+ metadata.gz: 6734c4d9cbd071d3288a5f8c72de07095fc6db2915224f97684a0577362bcdb0
4
+ data.tar.gz: 9225043ef91161f2a92fa338bc874771dc55d9e81ffe969fe7b005c9dbc28403
5
5
  SHA512:
6
- metadata.gz: ccd1d37c3b516590e84b6e28369bf90acd000b9ada071c10554cecfdeab190416efc1b8d1fca14615c78350d4608c1972d4d45939756c2bbd2dc7b097a25285f
7
- data.tar.gz: ec458a5204f172e33eefdee4898f1d98c7cf21d14772d6cb91f0c3e75645c0d55807057cbc6d1b11828b12cd84bf4eb0522b8c093607e28b6461696eb4bbd73f
6
+ metadata.gz: 58824c1699a2aff5e22d906cbb449f95f55a665d7374ba62e26ddfa3fd6986ae952e22965446e4bbe973d2e17a1f05cba32e74f7d30e1e327fbc262ebce7b1f1
7
+ data.tar.gz: aa9db8c15e10ecc08fa65db0cf6964914681ef72b552a300c4b95899b4a837835c1c69baff9b8e48586df34eefb7bb4156e55a5c81582deda4a9948554224f48
data/CHANGELOG.md CHANGED
@@ -1,10 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.0.0 / 2022-08-28
4
+
5
+ - Improved YARD documentation
6
+ - New `Item` instance methods (8105d6f):
7
+ - `MicroMicro::Item#children?`
8
+ - `MicroMicro::Item#id?`
9
+ - **Breaking change:** Remove property-centric methods from `MicroMicro::Item` (926dedb):
10
+ - `MicroMicro::Item#plain_text_properties`
11
+ - `MicroMicro::Item#url_properties`
12
+ - Add predicate methods to `MicroMicro::Collections::PropertiesCollection` (82e91c8):
13
+ - `MicroMicro::Collections::PropertiesCollection#plain_text_properties?`
14
+ - `MicroMicro::Collections::PropertiesCollection#url_properties?`
15
+ - Add collections search methods `#where` and `#find_by` (847cb77)
16
+ - **Breaking change:** Refactor `.node_set_from` class methods into private classes (b18a714)
17
+
18
+ ## 2.0.1 / 2022-08-20
19
+
20
+ - Use ruby/debug instead of pry-byebug (2965b2e)
21
+ - Update nokogiri-html-ext to v0.2.2 (921c486)
22
+ - Include root items with property class names (dd14212)
23
+
24
+ ## 2.0.0 / 2022-08-12
25
+
26
+ - Refactor implied property parsers (203fec9)
27
+ - Add `Helpers` module (caa1c02)
28
+ - New `PropertiesCollection` and `Property` instance methods (e9bb38b):
29
+ - `PropertiesCollection#plain_text_properties`
30
+ - `PropertiesCollection#url_properties`
31
+ - `Property#date_time_property?`
32
+ - `Property#embedded_markup_property?`
33
+ - `Property#plain_text_property?`
34
+ - `Property#url_property?`
35
+ - Remove Addressable (66c2bb4)
36
+ - Refactor classes to use nokogiri-html-ext (33fdf4a)
37
+ - Update activesupport (563bf56)
38
+ - **Breaking change:** Set minimum supported Ruby to 2.7 (ba17d05)
39
+ - Update development Ruby to 2.7.6 (ba17d05)
40
+ - Remove Reek (c1e76c5)
41
+ - Update runtime dependency version constraints (f83f26a)
42
+ - ~~**Breaking change:** Set minimum supported Ruby to 2.6~~ (fc588cd)
43
+ - ~~Update development Ruby to 2.6.10~~ (d05a2ac)
44
+
3
45
  ## 1.1.0 / 2021-06-10
4
46
 
5
47
  - Replace Absolutely dependency with Addressable (e93721b)
6
48
  - Add support for Ruby 3.0 (d897c54)
7
- - Update development Ruby version to 2.5.9 (051c9ad)
49
+ - Update development Ruby version to 2.6.10 (051c9ad)
8
50
 
9
51
  ## 1.0.0 / 2020-11-08
10
52
 
data/CONTRIBUTING.md CHANGED
@@ -8,9 +8,9 @@ There are a couple ways you can help improve MicroMicro:
8
8
 
9
9
  ## Getting Started
10
10
 
11
- MicroMicro is developed using Ruby 2.5.9 and is additionally tested against Ruby 2.6, 2.7, and 3.0 using [CircleCI](https://app.circleci.com/pipelines/github/jgarber623/micromicro).
11
+ MicroMicro is developed using Ruby 2.7.6 and is additionally tested against Ruby 3.0 and 3.1 using [GitHub Actions](https://github.com/jgarber623/micromicro/actions).
12
12
 
13
- Before making changes to MicroMicro, you'll want to install Ruby 2.5.9. It's recommended that you use a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm). Once you've installed Ruby 2.5.9 using your method of choice, install the project's gems by running:
13
+ Before making changes to MicroMicro, you'll want to install Ruby 2.7.6. It's recommended that you use a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm). Once you've installed Ruby 2.7.6 using your method of choice, install the project's gems by running:
14
14
 
15
15
  ```sh
16
16
  bundle install
@@ -22,7 +22,7 @@ bundle install
22
22
  1. Install development dependencies as outlined above.
23
23
  1. Create a feature branch for the code changes you're looking to make: `git checkout -b my-new-feature`.
24
24
  1. _Write some code!_
25
- 1. If your changes would benefit from testing, add the necessary tests and verify everything passes by running `bin/ci`.
25
+ 1. If your changes would benefit from testing, add the necessary tests and verify everything passes by running `bundle exec rspec`.
26
26
  1. Commit your changes: `git commit -am 'Add some new feature or fix some issue'`. _(See [this excellent article](https://chris.beams.io/posts/git-commit/) for tips on writing useful Git commit messages.)_
27
27
  1. Push the branch to your fork: `git push -u origin my-new-feature`.
28
28
  1. Create a new [pull request][pulls] and we'll review your changes.
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![Gem](https://img.shields.io/gem/v/micromicro.svg?logo=rubygems&style=for-the-badge)](https://rubygems.org/gems/micromicro)
6
6
  [![Downloads](https://img.shields.io/gem/dt/micromicro.svg?logo=rubygems&style=for-the-badge)](https://rubygems.org/gems/micromicro)
7
- [![Build](https://img.shields.io/circleci/build/github/jgarber623/micromicro?logo=circleci&style=for-the-badge)](https://app.circleci.com/pipelines/github/jgarber623/micromicro)
7
+ [![Build](https://img.shields.io/github/workflow/status/jgarber623/micromicro/CI?logo=github&style=for-the-badge)](https://github.com/jgarber623/micromicro/actions/workflows/ci.yml)
8
8
  [![Maintainability](https://img.shields.io/codeclimate/maintainability/jgarber623/micromicro.svg?logo=code-climate&style=for-the-badge)](https://codeclimate.com/github/jgarber623/micromicro)
9
9
  [![Coverage](https://img.shields.io/codeclimate/c/jgarber623/micromicro.svg?logo=code-climate&style=for-the-badge)](https://codeclimate.com/github/jgarber623/micromicro/code)
10
10
 
@@ -12,43 +12,31 @@
12
12
 
13
13
  - Parses microformats2-encoded HTML documents according to the [microformats2 parsing specification](https://microformats.org/wiki/microformats2-parsing)
14
14
  - Passes all microformats2 tests from [the official test suite](https://github.com/microformats/tests)¹
15
- - Supports Ruby 2.5 and newer
15
+ - Supports Ruby 2.7 and newer
16
16
 
17
17
  **Note:** MicroMicro **does not** parse [Classic Microformats](https://microformats.org/wiki/Main_Page#Classic_Microformats) (referred to in [the parsing specification](https://microformats.org/wiki/microformats2-parsing#note_backward_compatibility_details) as "backcompat root classes" and "backcompat properties" and in vocabulary specifications in the "Parser Compatibility" sections [e.g. [h-entry](https://microformats.org/wiki/h-entry#Parser_Compatibility)]). To parse documents marked up with Classic Microformats, consider using [the official microformats-ruby parser](https://github.com/microformats/microformats-ruby).
18
18
 
19
19
  <small>¹ …with some exceptions until [this pull request](https://github.com/microformats/tests/pull/112) is merged.</small>
20
20
 
21
- ## Getting Started
22
-
23
- Before installing and using MicroMicro, you'll want to have [Ruby](https://www.ruby-lang.org) 2.5 (or newer) installed. It's recommended that you use a Ruby version managment tool like [rbenv](https://github.com/rbenv/rbenv), [chruby](https://github.com/postmodern/chruby), or [rvm](https://github.com/rvm/rvm).
24
-
25
- MicroMicro is developed using Ruby 2.5.9 and is additionally tested against Ruby 2.6, 2.7, and 3.0 using [CircleCI](https://app.circleci.com/pipelines/github/jgarber623/micromicro).
26
-
27
21
  ## Installation
28
22
 
29
- If you're using [Bundler](https://bundler.io), add MicroMicro to your project's `Gemfile`:
23
+ Before installing and using MicroMicro, you'll want to have [Ruby](https://www.ruby-lang.org) 2.7 (or newer) installed. If you're using [Bundler](https://bundler.io) to manage gem dependencies, add MicroMicro to your project's Gemfile:
30
24
 
31
25
  ```ruby
32
- source 'https://rubygems.org'
33
-
34
26
  gem 'micromicro'
35
27
  ```
36
28
 
37
- …and hop over to your command prompt and run…
29
+ …and run `bundle install` in your shell.
30
+
31
+ To install the gem manually, run the following in your shell:
38
32
 
39
33
  ```sh
40
- $ bundle install
34
+ gem install micromicro
41
35
  ```
42
36
 
43
37
  ## Usage
44
38
 
45
- ### Basic Usage
46
-
47
- MicroMicro's `parse` method accepts two arguments: a `String` of markup and a `String` representing the URL associated with that markup.
48
-
49
- The markup (typically HTML) can be retrieved from the Web using a library of your choosing or provided inline as a simple `String` (e.g. `<div class="h-card">Jason Garber</div>`) The URL provided is used to resolve relative URLs in accordance with the document's language rules.
50
-
51
- An example using a simple `String` of HTML as input:
39
+ MicroMicro's `parse` method accepts two arguments: a `String` of markup and a `String` representing the URL associated with that markup. The resulting `MicroMicro::Document` may be converted to a `Hash` which may be further manipulated using conventional Ruby tooling.
52
40
 
53
41
  ```ruby
54
42
  require 'micromicro'
@@ -60,88 +48,7 @@ doc.to_h
60
48
  #=> { :items => [{ :type => ["h-card"], :properties => { :name => ["Jason Garber"] } }], :rels => {}, :"rel-urls" => {} }
61
49
  ```
62
50
 
63
- The `Hash` produced by calling `doc.to_h` may be converted to JSON (e.g. `doc.to_h.to_json`) for storage, additional manipulation, or use with other tools.
64
-
65
- Another example pulling the source HTML from [Tantek](https://tantek.com)'s website:
66
-
67
- ```ruby
68
- require 'net/http'
69
- require 'micromicro'
70
-
71
- url = 'https://tantek.com'
72
- rsp = Net::HTTP.get(URI.parse(url))
73
-
74
- doc = MicroMicro.parse(rsp, url)
75
- #=> #<MicroMicro::Document items: #<MicroMicro::Collections::ItemsCollection count: 1, members: […]>, relationships: #<MicroMicro::Collections::RelationshipsCollection count: 31, members: […]>>
76
-
77
- doc.to_h
78
- #=> { :items => [{ :type => ["h-card"], :properties => {…}, :children => […]}], :rels => {…}, :'rel-urls' => {…} }
79
- ```
80
-
81
- ### Advanced Usage
82
-
83
- Building on the example above, a MicroMicro-parsed document is navigable and manipulable using a familiar `Enumerable`-esque interface.
84
-
85
- #### Items
86
-
87
- ```ruby
88
- doc.items.first
89
- #=> #<MicroMicro::Item types: ["h-card"], properties: 42, children: 6>
90
-
91
- # 🆕 in v1.0.0
92
- doc.items.types
93
- #=> ["h-card"]
94
-
95
- doc.items.first.children
96
- #=> #<MicroMicro::Collections::ItemsCollection count: 6, members: […]>
97
- ```
98
-
99
- #### Properties
100
-
101
- ```ruby
102
- doc.items.first.properties
103
- #=> #<MicroMicro::Collections::PropertiesCollection count: 42, members: […]>
104
-
105
- # 🆕 in v1.0.0
106
- doc.items.first.plain_text_properties
107
- #=> #<MicroMicro::Collections::PropertiesCollection count: 34, members: […]>
108
-
109
- # 🆕 in v1.0.0
110
- doc.items.first.url_properties
111
- #=> #<MicroMicro::Collections::PropertiesCollection count: 11, members: […]>
112
-
113
- # 🆕 in v1.0.0
114
- doc.items.first.properties.names
115
- #=> ["category", "name", "note", "org", "photo", "pronoun", "pronouns", "role", "uid", "url"]
116
-
117
- # 🆕 in v1.0.0
118
- doc.items.first.properties.values
119
- #=> [{:value=>"https://tantek.com/photo.jpg", :alt=>""}, "https://tantek.com/", "Tantek Çelik", "Inventor, writer, teacher, runner, coder, more.", "Inventor", "writer", "teacher", "runner", "coder", …]
120
-
121
- doc.items.first.properties[7]
122
- #=> #<MicroMicro::Property name: "category", prefix: "p", value: "teacher">
123
-
124
- doc.items.first.properties.take(5).map { |property| [property.name, property.value] }
125
- #=> [["photo", { :value => "https://tantek.com/photo.jpg", :alt => "" }], ["url", "https://tantek.com/"], ["uid", "https://tantek.com/"], ["name", "Tantek Çelik"], ["role", "Inventor, writer, teacher, runner, coder, more."]]
126
- ```
127
-
128
- #### Relationships
129
-
130
- ```ruby
131
- doc.relationships.first
132
- #=> #<MicroMicro::Relationship href: "https://tantek.com/", rels: ["canonical"]>
133
-
134
- # 🆕 in v1.0.0
135
- doc.relationships.rels
136
- #=> ["alternate", "apple-touch-icon-precomposed", "author", "authorization_endpoint", "bookmark", "canonical", "hub", "icon", "me", "microsub", …]
137
-
138
- # 🆕 in v1.0.0
139
- doc.relationships.urls
140
- #=> ["http://dribbble.com/tantek/", "http://last.fm/user/tantekc", "https://aperture.p3k.io/microsub/277", "https://en.wikipedia.org/wiki/User:Tantek", "https://github.com/tantek", "https://indieauth.com/auth", "https://indieauth.com/openid", "https://micro.blog/t", "https://pubsubhubbub.superfeedr.com/", "https://tantek.com/", …]
141
-
142
- doc.relationships.find { |relationship| relationship.rels.include?('webmention') }
143
- # => #<MicroMicro::Relationship href: "https://webmention.io/tantek.com/webmention", rels: ["webmention"]>
144
- ```
51
+ See [USAGE.md](https://github.com/jgarber623/micromicro/blob/main/USAGE.md) for detailed examples of MicroMicro's features.
145
52
 
146
53
  ## Contributing
147
54
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Collectible
3
5
  attr_accessor :collection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Collections
3
5
  class BaseCollection
@@ -13,9 +15,14 @@ module MicroMicro
13
15
  end
14
16
 
15
17
  # @return [String]
18
+ #
19
+ # :nocov:
16
20
  def inspect
17
- format(%(#<#{self.class.name}:%#0x count: #{count}, members: #{members.inspect}>), object_id)
21
+ "#<#{self.class}:#{format('%#0x', object_id)} " \
22
+ "count: #{count}, " \
23
+ "members: #{members.inspect}>"
18
24
  end
25
+ # :nocov:
19
26
 
20
27
  # @param member [MicroMicro::Item, MicroMicro::Property, MicroMicro::Relationship]
21
28
  def push(member)
@@ -1,14 +1,97 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Collections
3
5
  class ItemsCollection < BaseCollection
6
+ class ItemsCollectionSearch
7
+ attr_reader :results
8
+
9
+ def initialize
10
+ @results = []
11
+ end
12
+
13
+ def search(items, **args, &block)
14
+ items.each do |item|
15
+ results << item if item_matches_conditions?(item, **args, &block)
16
+
17
+ search(item.properties.filter_map { |property| property.item if property.item_node? }, **args, &block)
18
+ search(item.children, **args, &block)
19
+ end
20
+
21
+ results
22
+ end
23
+
24
+ private
25
+
26
+ def item_matches_conditions?(item, **args)
27
+ return yield(item) if args.none?
28
+
29
+ args.all? { |key, value| (Array(item.public_send(key.to_sym)) & Array(value)).any? }
30
+ end
31
+ end
32
+
33
+ private_constant :ItemsCollectionSearch
34
+
35
+ # Return the first {MicroMicro::Item} from a search.
36
+ #
37
+ # @see #where
38
+ #
39
+ # @param (see #where)
40
+ # @yieldparam (see #where))
41
+ # @return [MicroMicro::Item, nil]
42
+ def find_by(**args, &block)
43
+ where(**args, &block).first
44
+ end
45
+
46
+ # Return an Array of this collection's {MicroMicro::Item}s as Hashes.
47
+ #
48
+ # @see MicroMicro::Item#to_h
49
+ #
4
50
  # @return [Array<Hash{Symbol => Array<String, Hash>}>]
5
51
  def to_a
6
52
  map(&:to_h)
7
53
  end
8
54
 
55
+ # Retrieve an Array of this collection's unique {MicroMicro::Item} types.
56
+ #
57
+ # @see MicroMicro::Item#types
58
+ #
9
59
  # @return [Array<String>]
10
60
  def types
11
- @types ||= map(&:types).flatten.uniq.sort
61
+ @types ||= flat_map(&:types).uniq.sort
62
+ end
63
+
64
+ # Recursively search this collection for {MicroMicro::Item}s matching the
65
+ # given conditions.
66
+ #
67
+ # If a Hash is supplied, the returned collection will include
68
+ # {MicroMicro::Item}s matching _all_ conditions. Keys must be Symbols
69
+ # matching an instance method on {MicroMicro::Item} and values may be
70
+ # either a String or an Array of Strings.
71
+ #
72
+ # @example Search using a Hash with a String value
73
+ # MicroMicro.parse(markup, url).items.where(types: 'h-card')
74
+ #
75
+ # @example Search using a Hash with an Array value
76
+ # MicroMicro.parse(markup, url).items.where(types: ['h-card', 'h-entry'])
77
+ #
78
+ # When passing a block, each {MicroMicro::Item} in this collection is
79
+ # yielded to the block and the returned collection will include
80
+ # {MicroMicro::Item}s that cause the block to return a value other than
81
+ # +false+ or +nil+.
82
+ #
83
+ # @example Search using a block
84
+ # MicroMicro.parse(markup, url).items.where do |item|
85
+ # item.properties.names.include?('email')
86
+ # end
87
+ #
88
+ # @param args [Hash{Symbol => String, Array<String>}]
89
+ # @yieldparam item [MicroMicro::Item]
90
+ # @return [MicroMicro::Collections::ItemsCollection]
91
+ def where(**args, &block)
92
+ return self if args.none? && !block
93
+
94
+ self.class.new(ItemsCollectionSearch.new.search(self, **args, &block))
12
95
  end
13
96
  end
14
97
  end
@@ -1,20 +1,131 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Collections
3
5
  class PropertiesCollection < BaseCollection
6
+ class PropertiesCollectionSearch
7
+ def search(properties, **args, &block)
8
+ properties.select { |property| property_matches_conditions?(property, **args, &block) }
9
+ end
10
+
11
+ private
12
+
13
+ def property_matches_conditions?(property, **args)
14
+ return yield(property) if args.none?
15
+
16
+ args.all? { |key, value| (Array(property.public_send(key.to_sym)) & Array(value)).any? }
17
+ end
18
+ end
19
+
20
+ private_constant :PropertiesCollectionSearch
21
+
22
+ # Return the first {MicroMicro::Property} from a search.
23
+ #
24
+ # @see #where
25
+ #
26
+ # @param (see #where)
27
+ # @yieldparam (see #where))
28
+ # @return [MicroMicro::Property, nil]
29
+ def find_by(**args, &block)
30
+ where(**args, &block).first
31
+ end
32
+
33
+ # Retrieve an Array of this collection's unique {MicroMicro::Property}
34
+ # names.
35
+ #
36
+ # @see MicroMicro::Property#name
37
+ #
4
38
  # @return [Array<String>]
5
39
  def names
6
40
  @names ||= map(&:name).uniq.sort
7
41
  end
8
42
 
43
+ # A collection of plain text {MicroMicro::Property}s parsed from the node.
44
+ #
45
+ # @see MicroMicro::Property#plain_text_property?
46
+ #
47
+ # @return [MicroMicro::Collections::PropertiesCollection]
48
+ def plain_text_properties
49
+ @plain_text_properties ||= self.class.new(select(&:plain_text_property?))
50
+ end
51
+
52
+ # Does this {MicroMicro::Collections::PropertiesCollection} include any
53
+ # plain text {MicroMicro::Property}s?
54
+ #
55
+ # @return [Boolean]
56
+ def plain_text_properties?
57
+ plain_text_properties.any?
58
+ end
59
+
60
+ # Return a Hash of this collection's {MicroMicro::Property}s as Arrays.
61
+ #
62
+ # @see MicroMicro::Property#name
63
+ # @see MicroMicro::Property#value
64
+ #
9
65
  # @return [Hash{Symbol => Array<String, Hash>}]
10
66
  def to_h
11
67
  group_by(&:name).symbolize_keys.deep_transform_values(&:value)
12
68
  end
13
69
 
70
+ # A collection of url {MicroMicro::Property}s parsed from the node.
71
+ #
72
+ # @see MicroMicro::Property#url_property?
73
+ #
74
+ # @return [MicroMicro::Collections::PropertiesCollection]
75
+ def url_properties
76
+ @url_properties ||= self.class.new(select(&:url_property?))
77
+ end
78
+
79
+ # Does this {MicroMicro::Collections::PropertiesCollection} include any
80
+ # url {MicroMicro::Property}s?
81
+ #
82
+ # @return [Boolean]
83
+ def url_properties?
84
+ url_properties.any?
85
+ end
86
+
87
+ # Return an Array of this collection's unique {MicroMicro::Property}
88
+ # values.
89
+ #
90
+ # @see MicroMicro::Property#value
91
+ #
14
92
  # @return [Array<String, Hash>]
15
93
  def values
16
94
  @values ||= map(&:value).uniq
17
95
  end
96
+
97
+ # Search this collection for {MicroMicro::Property}s matching the given
98
+ # conditions.
99
+ #
100
+ # If a Hash is supplied, the returned collection will include
101
+ # {MicroMicro::Property}s matching _all_ conditions. Keys must be Symbols
102
+ # matching an instance method on {MicroMicro::Property} and values may be
103
+ # either a String or an Array of Strings.
104
+ #
105
+ # @example Search using a Hash with a String value
106
+ # MicroMicro.parse(markup, url).properties.where(name: 'url')
107
+ #
108
+ # @example Search using a Hash with an Array value
109
+ # MicroMicro.parse(markup, url).properties.where(name: ['name', 'url'])
110
+ #
111
+ # When passing a block, each {MicroMicro::Property} in this collection
112
+ # is yielded to the block and the returned collection will include
113
+ # {MicroMicro::Property}s that cause the block to return a value other
114
+ # than +false+ or +nil+.
115
+ #
116
+ # @example Search using a block
117
+ # MicroMicro.parse(markup, url).properties.where do |property|
118
+ # property.value.is_a?(Hash)
119
+ # end
120
+ #
121
+ # @param args [Hash{Symbol => String, Array<String>}]
122
+ # @yieldparam property [MicroMicro::Property]
123
+ # @return [MicroMicro::Collections::PropertiesCollection]
124
+ def where(**args, &block)
125
+ return self if args.none? && !block
126
+
127
+ self.class.new(PropertiesCollectionSearch.new.search(self, **args, &block))
128
+ end
18
129
  end
19
130
  end
20
131
  end
@@ -1,32 +1,111 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MicroMicro
2
4
  module Collections
3
5
  class RelationshipsCollection < BaseCollection
4
- # @see https://microformats.org/wiki/microformats2-parsing#parse_a_hyperlink_element_for_rel_microformats
6
+ class RelationshipsCollectionSearch
7
+ def search(relationships, **args, &block)
8
+ relationships.select { |relationship| relationship_matches_conditions?(relationship, **args, &block) }
9
+ end
10
+
11
+ private
12
+
13
+ def relationship_matches_conditions?(relationship, **args)
14
+ return yield(relationship) if args.none?
15
+
16
+ args.all? { |key, value| (Array(relationship.public_send(key.to_sym)) & Array(value)).any? }
17
+ end
18
+ end
19
+
20
+ private_constant :RelationshipsCollectionSearch
21
+
22
+ # Return the first {MicroMicro::Relationship} from a search.
5
23
  #
6
- # @return [Hash{Symbol => Hash{Symbol => Array, String}}]
7
- def group_by_url
8
- group_by(&:href).symbolize_keys.transform_values { |relationships| relationships.first.to_h.slice!(:href) }
24
+ # @see #where
25
+ #
26
+ # @param (see #where)
27
+ # @yieldparam (see #where))
28
+ # @return [MicroMicro::Relationship, nil]
29
+ def find_by(**args, &block)
30
+ where(**args, &block).first
9
31
  end
10
32
 
33
+ # Return a Hash of this collection's {MicroMicro::Relationship}s grouped
34
+ # by their +rel+ attribute value.
35
+ #
11
36
  # @see https://microformats.org/wiki/microformats2-parsing#parse_a_hyperlink_element_for_rel_microformats
37
+ # microformats.org: microformats2 parsing specification § Parse a hyperlink element for rel microformats
12
38
  #
13
39
  # @return [Hash{Symbol => Array<String>}]
14
40
  def group_by_rel
15
- # flat_map { |member| member.rels.map { |rel| [rel, member.href] } }.group_by(&:shift).symbolize_keys.transform_values(&:flatten).transform_values(&:uniq)
16
41
  each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |member, hash|
17
42
  member.rels.each { |rel| hash[rel] << member.href }
18
43
  end.symbolize_keys.transform_values(&:uniq)
19
44
  end
20
45
 
46
+ # Return a Hash of this collection's {MicroMicro::Relationship}s grouped
47
+ # by their +href+ attribute value.
48
+ #
49
+ # @see https://microformats.org/wiki/microformats2-parsing#parse_a_hyperlink_element_for_rel_microformats
50
+ # microformats.org: microformats2 parsing specification § Parse a hyperlink element for rel microformats
51
+ #
52
+ # @return [Hash{Symbol => Hash{Symbol => Array, String}}]
53
+ def group_by_url
54
+ group_by(&:href).symbolize_keys.transform_values { |relationships| relationships.first.to_h.slice!(:href) }
55
+ end
56
+
57
+ # Retrieve an Array of this collection's unique {MicroMicro::Relationship}
58
+ # +rel+ attrivute values.
59
+ #
60
+ # @see MicroMicro::Relationship#rels
61
+ #
21
62
  # @return [Array<String>]
22
63
  def rels
23
- @rels ||= map(&:rels).flatten.uniq.sort
64
+ @rels ||= flat_map(&:rels).uniq.sort
24
65
  end
25
66
 
67
+ # Retrieve an Array of this collection's unique {MicroMicro::Relationship}
68
+ # +href+ attribute values.
69
+ #
70
+ # @see MicroMicro::Relationship#urls
71
+ #
26
72
  # @return [Array<String>]
27
73
  def urls
28
74
  @urls ||= map(&:href).uniq.sort
29
75
  end
76
+
77
+ # Search this collection for {MicroMicro::Relationship}s matching the
78
+ # given conditions.
79
+ #
80
+ # If a Hash is supplied, the returned collection will include
81
+ # {MicroMicro::Relationship}s matching _all_ conditions. Keys must be
82
+ # Symbols matching an instance method on {MicroMicro::Relationship} and
83
+ # values may be either a String or an Array of Strings.
84
+ #
85
+ # @example Search using a Hash with a String value
86
+ # MicroMicro.parse(markup, url).relationships.where(rels: 'webmention')
87
+ #
88
+ # @example Search using a Hash with an Array value
89
+ # MicroMicro.parse(markup, url).relationships.where(rels: ['me', 'webmention'])
90
+ #
91
+ # When passing a block, each {MicroMicro::Relationship} in this collection
92
+ # is yielded to the block and the returned collection will include
93
+ # {MicroMicro::Relationship}s that cause the block to return a value other
94
+ # than +false+ or +nil+.
95
+ #
96
+ # @example Search using a block
97
+ # MicroMicro.parse(markup, url).relationships.where do |relationship|
98
+ # relationship.href.match?(%r{https://webmention.io/.+})
99
+ # end
100
+ #
101
+ # @param args [Hash{Symbol => String, Array<String>}]
102
+ # @yieldparam relationship [MicroMicro::Relationship]
103
+ # @return [MicroMicro::Collections::RelationshipsCollection]
104
+ def where(**args, &block)
105
+ return self if args.none? && !block
106
+
107
+ self.class.new(RelationshipsCollectionSearch.new.search(self, **args, &block))
108
+ end
30
109
  end
31
110
  end
32
111
  end