# frozen_string_literal: true
require 'open3'
require 'socket'
require 'tmpdir'
module RSpec::ExampleGroupHelpers
module TareFirstPageContentStreamNoop
def tare_first_page_content_stream
yield
end
end
module_function
def gem_available? gem_name
Gem.loaded_specs.key? gem_name
end
def jruby?
RUBY_ENGINE == 'jruby'
end
def windows?
Gem.win_platform?
end
end
module RSpec::ExampleHelpers
def asciidoctor_bin
bin_script 'asciidoctor', gem: 'asciidoctor'
end
def asciidoctor_pdf_bin
bin_script 'asciidoctor-pdf'
end
def asciidoctor_pdf_optimize_bin
bin_script 'asciidoctor-pdf-optimize'
end
def bin_script name, gem: 'asciidoctor-pdf'
if gem != 'asciidoctor-pdf' || (defined? Bundler)
bin_path = Gem.bin_path gem, name
else
bin_path = File.join project_dir, 'bin', name
end
if (defined? DeepCover) && !(DeepCover.const_defined? :TAKEOVER_IS_ON)
[Gem.ruby, '-rdeep_cover', bin_path]
elsif windows?
[Gem.ruby, bin_path]
else
bin_path
end
end
def build_pdf_theme overrides = {}, extends = nil
(Asciidoctor::PDF::ThemeLoader.load_theme extends).tap {|theme| overrides.each {|k, v| theme[k] = v } }
end
def create_class super_class = Object, &block
klass = Class.new super_class, &block
Object.const_set %(AnonymousClass#{klass.object_id}).to_sym, klass
klass
end
def docs_dir
File.join project_dir, 'docs'
end
def doc_file path
File.absolute_path path, docs_dir
end
def examples_dir
File.join project_dir, 'examples'
end
def example_file path
File.join examples_dir, path
end
def extract_outline pdf, list = pdf.outlines
result = []
objects = pdf.objects
pages = pdf.pages
labels = get_page_labels pdf
entry = list[:First] if list
while entry
entry = objects[entry]
title = (((title = entry[:Title]).slice 2, title.size).unpack 'n*').pack 'U*'
dest = entry[:Dest]
dest_page_object = objects[dest[0]]
dest_page = pages.find {|candidate| candidate.page_object == dest_page_object }
top = dest_page.attributes[:MediaBox][3] == dest[3]
if (count = entry[:Count]) == 0
closed = true
children = []
else
closed = count < 0
children = extract_outline pdf, entry
end
result << { title: title, dest: { pagenum: dest_page.number, label: labels[dest_page.number - 1], x: dest[2], y: dest[3], top: top }, closed: closed, children: children }
entry = entry[:Next]
end
result
end
def fixtures_dir
File.join spec_dir, 'fixtures'
end
def fixture_file path, relative: false
if relative
(((Pathname.new fixtures_dir) / path).relative_path_from Pathname.new Dir.pwd).to_s
else
File.join fixtures_dir, path
end
end
def gem_available? gem_name
RSpec::ExampleGroupHelpers.gem_available? gem_name
end
def get_annotations pdf, page_num = nil
objects = pdf.objects
if page_num
(pdf.page page_num).attributes[:Annots].to_a.map {|ref| objects[ref] }
else
pdf.pages.each_with_object([]) {|page, accum| page.attributes[:Annots].to_a.each {|ref| accum << objects[ref] } }
end
end
def get_dest pdf, name
if (name_ref = (get_names pdf)[name]) && (dest = pdf.objects[name_ref])
{ page: pdf.objects[(page_ref = dest[0])], page_number: (get_page_number pdf, page_ref), x: dest[2], y: dest[3] }
end
end
def get_images pdf, page_num = nil
if page_num
(pdf.page page_num).xobjects.select {|_, candidate| candidate.hash[:Subtype] == :Image }.values
else
pdf.pages.each_with_object([]) {|page, accum| page.xobjects.each {|_, candidate| candidate.hash[:Subtype] == :Image ? (accum << candidate) : accum } }
end
end
def get_names pdf
if (names = pdf.catalog[:Names])
objects = pdf.objects
Hash[*objects[objects[names][:Dests]][:Names]]
else
{}
end
end
def get_page_labels pdf
objects = pdf.objects
Hash[*objects[pdf.catalog[:PageLabels]][:Nums]].each_with_object([]) {|(idx, val), accum| accum[idx] = val[:P] }
end
def get_page_number pdf, page
page = pdf.objects[page] if PDF::Reader::Reference === page
pdf.pages.find {|candidate| candidate.page_object == page }&.number
end
def get_page_size pdf, page_num = 1
if PDF::Reader === pdf
(pdf.page page_num).attributes[:MediaBox].slice 2, 2
else
pdf.pages[page_num - 1][:size]
end
end
def home_dir
windows? ? (Dir.home.tr ?\\, '/') : Dir.home
end
def lorem_ipsum id
(@lorem_ipsum_data ||= (YAML.load_file fixture_file 'lorem-ipsum.yml'))[id]
end
def output_dir
File.join spec_dir, 'output'
end
def output_file path
File.join output_dir, path
end
def project_dir
File.absolute_path '..', spec_dir
end
def run_command cmd, *args
Dir.chdir spec_dir do
if Array === cmd
args.unshift(*cmd)
cmd = args.shift
end
kw_args = Hash === args[-1] ? args.pop : {}
env_override = kw_args[:env] || {}
unless kw_args[:use_bundler]
env_override['RUBYOPT'] = nil
end
if (out = kw_args[:out])
Open3.pipeline_w([env_override, cmd, *args, out: out]) {} # rubocop:disable Lint/EmptyBlock
else
Open3.capture3 env_override, cmd, *args
end
end
end
def resolve_localhost
Socket.ip_address_list.find(&:ipv4?).ip_address
end
def spec_dir
File.absolute_path '..', __dir__
end
def tmp_dir
File.join spec_dir, 'tmp'
end
def to_pdf input, opts = {}
analyze = opts.delete :analyze
if (debug = opts.delete :debug) && ENV['CI']
raise ArgumentError, 'debug flag not permitted in CI'
end
enable_footer = opts.delete :enable_footer
safe_mode = opts.fetch :safe, :safe
opts[:attributes] = { 'imagesdir' => fixtures_dir } unless opts.key? :attributes
opts[:attributes]['nofooter'] = '' unless enable_footer
if (attribute_overrides = opts.delete :attribute_overrides)
(opts[:attributes] ||= {}).update attribute_overrides
end
opts = opts.merge backend: 'pdf' unless opts.key? :backend
if Hash === (pdf_theme = opts[:pdf_theme])
pdf_theme_extends = (pdf_theme = pdf_theme.dup).delete :extends if pdf_theme.key? :extends
opts[:pdf_theme] = build_pdf_theme pdf_theme, pdf_theme_extends
end
if Pathname === input
opts[:to_dir] = output_dir unless opts.key? :to_dir
doc = Asciidoctor.convert_file input, (opts.merge safe: safe_mode)
return doc.converter if analyze == :document
pdf_io = Pathname.new doc.attr 'outfile'
elsif analyze == :document
return Asciidoctor.convert input, (opts.merge safe: safe_mode, standalone: true)
else
Asciidoctor.convert input, (opts.merge safe: safe_mode, to_file: (pdf_io = StringIO.new), standalone: true)
end
File.write (File.join Dir.tmpdir, 'debug.pdf'), (Pathname === pdf_io ? pdf_io.read : pdf_io.string) if debug
analyze ? (PDF_INSPECTOR_CLASS[analyze].analyze pdf_io) : (PDF::Reader.new pdf_io)
end
def to_pdf_file input, output_filename, opts = {}
opts[:to_file] = (to_file = File.join output_dir, output_filename)
enable_footer = opts.delete :enable_footer
opts[:attributes] = { 'imagesdir' => fixtures_dir } unless opts.key? :attributes
opts[:attributes]['nofooter'] = '' unless enable_footer
if (attribute_overrides = opts.delete :attribute_overrides)
(opts[:attributes] ||= {}).update attribute_overrides
end
if Hash === (pdf_theme = opts[:pdf_theme])
pdf_theme_extends = (pdf_theme = pdf_theme.dup).delete :extends if pdf_theme.key? :extends
opts[:pdf_theme] = build_pdf_theme pdf_theme, pdf_theme_extends
end
opts = opts.merge backend: 'pdf' unless opts.key? :backend
if Pathname === input
Asciidoctor.convert_file input, (opts.merge safe: :safe)
else
Asciidoctor.convert input, (opts.merge safe: :safe, standalone: true)
end
to_file
end
def windows?
RSpec::ExampleGroupHelpers.windows?
end
def with_content_spacer width, height, units: 'pt', fill: '#999999'
contents = <<~END
END
with_tmp_file '.svg', contents: contents do |spacer_file|
yield spacer_file.path
end
end
def with_local_webserver host = resolve_localhost, port = 9876
base_dir = fixtures_dir
server = TCPServer.new host, port
server_thread = Thread.start do
Thread.current[:requests] = requests = []
while (session = server.accept)
requests << (request = session.gets)
if %r/^GET (\S+) HTTP\/1\.1$/ =~ request.chomp
resource = (resource = $1) == '' ? '.' : resource
else
session.print %(HTTP/1.1 405 Method Not Allowed\r\nContent-Type: text/plain\r\n\r\n)
session.print %(405 - Method not allowed\r\n)
session.close
next
end
resource, _query_string = resource.split '?', 2 if resource.include? '?'
if File.file? (resource_file = (File.join base_dir, resource))
if (ext = (File.extname resource_file)[1..-1])
mimetype = ext == 'adoc' ? 'text/plain' : %(image/#{ext})
else
mimetype = 'text/plain'
end
session.print %(HTTP/1.1 200 OK\r\nContent-Type: #{mimetype}\r\n\r\n)
File.open resource_file, 'rb:utf-8:utf-8' do |fd|
session.write fd.read 256 until fd.eof?
end
else
session.print %(HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\n\r\n)
session.print %(404 - Resource not found.\r\n)
end
session.close
end
end
begin
yield %(http://#{host}:#{port}), server_thread
ensure
server_thread.exit
server_thread.value
server.close
end
end
def with_pdf_theme_file data
with_tmp_file '-theme.yml', contents: data do |theme_file|
yield theme_file.path
end
end
def with_tmp_file basename, contents: nil, tmpdir: tmp_dir
basename = ['tmp-', basename] unless Array === basename
Tempfile.create basename, tmpdir, encoding: 'UTF-8', newline: :universal do |tmp_file|
if contents
tmp_file.write contents
tmp_file.close
end
yield tmp_file
end
end
def with_svg_with_remote_image
refname = 'main' if ((refname = %(v#{Asciidoctor::PDF::VERSION})).count '[a-z]') > 0
image_url = "https://cdn.jsdelivr.net/gh/asciidoctor/asciidoctor-pdf@#{refname}/spec/fixtures/logo.png"
svg_data = <<~END
END
with_tmp_file '.svg', contents: svg_data do |tmp_file|
yield tmp_file.path, image_url
end
end
end