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

Asciidoctor: Copy images for inline image macro #736

Merged
merged 5 commits into from
Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions resources/asciidoctor/lib/copy_images/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,50 @@ class CopyImages < TreeProcessorScaffold
'deleted' => 'warning',
}.freeze
CALLOUT_RX = /CO\d+-(\d+)/
INLINE_IMAGE_RX = /(\\)?image:([^:\s\[](?:[^\n\[]*[^\s\[])?)\[/m

def initialize(name)
super
@copier = Copier.new
end

def process_block(block)
process_image block
process_inline_image block
process_block_image block
process_callout block
process_admonition block
end

def process_image(block)
def process_block_image(block)
return unless block.context == :image

uri = block.image_uri(block.attr 'target')
process_image block, uri
end

def process_inline_image(block)
return unless block.content_model == :simple

# One day Asciidoc will parse inline things into the AST and we can
# get at them nicely. Today, we have to scrape them from the source
# of the node.
block.source.scan(INLINE_IMAGE_RX) do |(escape, target)|
next if escape

# We have to resolve attributes inside the target. But there is a
# "funny" ritual for that because attribute substitution is always
# against the document. We have to play the block's attributes against
# the document, then clear them on the way out.
block.document.playback_attributes block.attributes
target = block.sub_attributes target
block.document.clear_playback_attributes block.attributes
uri = block.image_uri target
process_image block, uri
end
end

def process_image(block, uri)
return unless uri
return if Asciidoctor::Helpers.uriish? uri # Skip external images

@copier.copy_image block, uri
Expand Down
155 changes: 118 additions & 37 deletions resources/asciidoctor/spec/copy_images_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
require 'fileutils'
require 'tmpdir'

RSpec.describe CopyImages::CopyImages do
RSpec.describe CopyImages do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can also just write describe without calling it on Rspec

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That causes an undefined method error for me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, maybe your version of RSpec is older
Anyway, it doesn't really matter how you call describe

RSpec::Matchers.define_negated_matcher :not_match, :match

before(:each) do
Expand All @@ -31,46 +31,127 @@ def copy_attributes(copied)

spec_dir = File.dirname(__FILE__)

it "copies a file when directly referenced" do
copied = []
attributes = copy_attributes copied
input = <<~ASCIIDOC
== Example
image::resources/copy_images/example1.png[]
ASCIIDOC
convert input, attributes,
eq("INFO: <stdin>: line 2: copying #{spec_dir}\/resources\/copy_images\/example1.png")
expect(copied).to eq([
["resources/copy_images/example1.png", "#{spec_dir}/resources/copy_images/example1.png"],
])
##
# Like the 'convert' shared context, but also captures any images that
# would be copied by the conversion process to the `convert` array. That
# array contains tuples of the form
# [image_path_from_asciidoc_file, image_path_on_disk] and is in the order
# that the images source be copied.
shared_context 'convert intercepting images' do
include_context 'convert'

# [] is the initial value but it is mutated by the conversion
let(:copied) { [].dup }
let(:convert_attributes) { copy_attributes(copied) }
end

it "copies a file when it can be found in a sub tree" do
copied = []
attributes = copy_attributes copied
input = <<~ASCIIDOC
== Example
image::example1.png[]
ASCIIDOC
convert input, attributes,
eq("INFO: <stdin>: line 2: copying #{spec_dir}/resources/copy_images/example1.png")
expect(copied).to eq([
["example1.png", "#{spec_dir}/resources/copy_images/example1.png"],
])
##
# Asserts that a particular `image_command` copies the appropriate image
# when the image is referred to in many ways. The `image_command` should
# read `target` for the location of the image.
shared_examples 'copies images with various paths' do
let(:input) do
<<~ASCIIDOC
== Example
#{image_command}
ASCIIDOC
end
let(:include_line) { 2 }
##
# Asserts that some `input` causes just the `example1.png` image to be copied.
shared_examples 'copies example1' do
include_context 'convert intercepting images'
let(:expected_logs) do
"INFO: <stdin>: line #{include_line}: copying #{spec_dir}/resources/copy_images/example1.png"
end
it 'copies the image' do
expect(copied).to eq([
[resolved, "#{spec_dir}/resources/copy_images/example1.png"],
])
end
it 'logs that it copied the image' do
expect(logs).to eq(expected_logs)
end
end
context 'when the image ref matches that path exactly' do
let(:target) { 'resources/copy_images/example1.png' }
let(:resolved) { 'resources/copy_images/example1.png' }
include_examples 'copies example1'
end
context 'when the image ref is just the name of the image' do
let(:target) { 'example1.png' }
let(:resolved) { 'example1.png' }
include_examples 'copies example1'
end
context 'when the image ref matches the end of the path' do
let(:target) { 'copy_images/example1.png' }
let(:resolved) { 'copy_images/example1.png' }
include_examples 'copies example1'
end
context 'when the image contains attributes' do
let(:target) { 'example1.{ext}' }
let(:resolved) { 'example1.png' }
let(:input) do
<<~ASCIIDOC
== Example
:ext: png

#{image_command}
ASCIIDOC
end
let(:include_line) { 4 }
include_examples 'copies example1'
end
end

it "copies a path when it can be found in a sub tree" do
copied = []
attributes = copy_attributes copied
input = <<~ASCIIDOC
== Example
image::copy_images/example1.png[]
ASCIIDOC
convert input, attributes,
eq("INFO: <stdin>: line 2: copying #{spec_dir}/resources/copy_images/example1.png")
expect(copied).to eq([
["copy_images/example1.png", "#{spec_dir}/resources/copy_images/example1.png"],
])
context 'for the image block macro' do
let(:image_command) { "image::#{target}[]" }
include_examples 'copies images with various paths'
end
context 'for the image inline macro' do
let(:image_command) { "Words image:#{target}[] words" }
include_examples 'copies images with various paths'
context 'when the macro is escaped' do
let(:target) { 'example1.jpg' }
let(:input) do
<<~ASCIIDOC
== Example
"Words \\image:#{target}[] words"
ASCIIDOC
end
include_context 'convert intercepting images'
it "doesn't log anything" do
expect(logs).to eq('')
end
it "doesn't copy the image" do
expect(copied).to eq([])
end
end
context 'when there are multiple images on a line' do
let(:input) do
<<~ASCIIDOC
== Example

words image:example1.png[] words words image:example2.png[] words
ASCIIDOC
end
let(:expected_logs) do
<<~LOGS
INFO: <stdin>: line 3: copying #{spec_dir}/resources/copy_images/example1.png
INFO: <stdin>: line 3: copying #{spec_dir}/resources/copy_images/example2.png
LOGS
end
include_context 'convert intercepting images'
it 'copies the images' do
expect(copied).to eq([
['example1.png', "#{spec_dir}/resources/copy_images/example1.png"],
['example2.png', "#{spec_dir}/resources/copy_images/example2.png"],
])
end
it 'logs that it copied the image' do
expect(logs).to eq(expected_logs.strip)
end
end
end

it "warns when it can't find a file" do
Expand Down
33 changes: 33 additions & 0 deletions resources/asciidoctor/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,36 @@ def convert(input, extra_attributes = {}, warnings_matcher = eq(''))
expect(warnings_string).to warnings_matcher
result
end

##
# Converts asciidoc to docbook
#
# In:
# input - asciidoc text to convert
# extra_attributes - attributes added to the conversion - defaults to {}
#
# Out:
# converted - converted docbook text
# logs - lines logged
RSpec.shared_context 'convert' do
let(:convert_logger) { Asciidoctor::MemoryLogger.new }
let!(:converted) do
# We use let! here to force the conversion because it populates the logger
attributes = {
'docdir' => File.dirname(__FILE__),
}
attributes.merge! convert_attributes if defined?(convert_attributes)
Asciidoctor.convert input,
:safe => :unsafe, # Used to include "funny" files.
:backend => :docbook45,
:logger => convert_logger,
:doctype => :book,
:attributes => attributes,
:sourcemap => true
end
let(:logs) do
convert_logger.messages
.map { |l| "#{l[:severity]}: #{l[:message].inspect}" }
.join("\n")
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! This looks good and more rspec-y