Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolves #3135 add AbstractBlock#get_by to locate first block that matches criteria #4516

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Expand Up @@ -36,6 +36,7 @@ Enhancements::
* Set logdev to $stderr if no arguments are passed to Logger constructor (#4250)
* Add support for skipping TOML front matter (Hugo) when `skip-front-matter` attribute is set (#4300) (*@abhinav*)
* Add support for Wistia using the video macro
* Add AbstractBlock#get_by to locate first matching block; add `:single_result` option to `find_by` to limit results to 1 (#3135)

Compliance::

Expand Down
17 changes: 13 additions & 4 deletions lib/asciidoctor/abstract_block.rb
Expand Up @@ -182,6 +182,14 @@ def find_by selector = {}, &block

alias query find_by

# Like find_by, but only returns the first result or nil if no matching block-level node is found
def get_by selector = {}, &block
find_by_internal (selector.merge single_result: true), (result = []), &block
result[0]
rescue ::StopIteration
result[0]
end

# Move to the next adjacent block in document order. If the current block is the last
# item in a list, this method will return the following sibling of the list block.
def next_adjacent_block
Expand Down Expand Up @@ -451,28 +459,29 @@ def find_by_internal selector = {}, result = [], &block
(!(style_selector = selector[:style]) || style_selector == @style) &&
(!(role_selector = selector[:role]) || (has_role? role_selector)) &&
(!(id_selector = selector[:id]) || id_selector == @id)
single_result = id_selector ? true : selector[:single_result]
if block_given?
if (verdict = yield self)
case verdict
when :prune
result << self
raise ::StopIteration if id_selector
raise ::StopIteration if single_result
return result
when :reject
raise ::StopIteration if id_selector
raise ::StopIteration if single_result
return result
when :stop
raise ::StopIteration
else
result << self
raise ::StopIteration if id_selector
raise ::StopIteration if single_result
end
elsif id_selector
raise ::StopIteration
end
else
result << self
raise ::StopIteration if id_selector
raise ::StopIteration if single_result
end
end
case @context
Expand Down
85 changes: 85 additions & 0 deletions test/api_test.rb
Expand Up @@ -665,6 +665,37 @@ def [] key
assert_equal 'shoe.png', result[1].attr('target')
end

test 'find_by with :single_result option should return first block that match criteria' do
input = <<~'EOS'
= Document Title

preamble

== Section A

paragraph

--
Exhibit A::
+
[#tiger.animal]
image::tiger.png[Tiger]
--

image::shoe.png[Shoe]

== Section B

paragraph
EOS

doc = Asciidoctor.load input
result = doc.find_by context: :image, single_result: true
assert_equal 1, result.size
assert_equal :image, result[0].context
assert_equal 'tiger.png', result[0].attr('target')
end

test 'find_by should return an empty Array if no matches are found' do
input = 'paragraph'
doc = Asciidoctor.load input
Expand Down Expand Up @@ -1063,6 +1094,60 @@ def [] key
assert_kind_of Asciidoctor::ListItem, result[2]
end

test 'get_by should return nil if no matches are found' do
doc = Asciidoctor.load 'paragraph'
assert_nil doc.get_by context: :section
end

test 'get_by should return first block in document tree that match criteria' do
input = <<~'EOS'
= Document Title

preamble

== Section A

paragraph

--
Exhibit A::
+
[#tiger.animal]
image::tiger.png[Tiger]
--

image::shoe.png[Shoe]

== Section B

paragraph
EOS

doc = Asciidoctor.load input
result = doc.get_by context: :image
refute_nil result
assert_equal :image, result.context
assert_equal 'tiger.png', (result.attr 'target')
end

test 'get_by should return result when matching by id' do
input = <<~'EOS'
== Section

content

[#subsection]
=== Subsection

content
EOS
doc = Asciidoctor.load input
result = doc.get_by id: 'subsection'
refute_nil result
assert_equal :section, result.context
assert_equal 'Subsection', result.title
end

test 'dlist item should always have two entries for terms and desc' do
[
'term w/o desc::',
Expand Down