micromicro 1.1.0 → 3.0.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.
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