asciidoctor-confluence_publisher 0.1.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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +24 -0
- data/.gitignore +14 -0
- data/Gemfile +7 -0
- data/README.md +54 -0
- data/Rakefile +10 -0
- data/asciidoctor-confluence_publisher.gemspec +31 -0
- data/bin/confluence-publisher +7 -0
- data/lib/asciidoctor/confluence_publisher.rb +12 -0
- data/lib/asciidoctor/confluence_publisher/asciidoc.rb +39 -0
- data/lib/asciidoctor/confluence_publisher/command.rb +59 -0
- data/lib/asciidoctor/confluence_publisher/confluence_api.rb +236 -0
- data/lib/asciidoctor/confluence_publisher/invoker.rb +154 -0
- data/lib/asciidoctor/confluence_publisher/model/ancestor.rb +9 -0
- data/lib/asciidoctor/confluence_publisher/model/attachment.rb +15 -0
- data/lib/asciidoctor/confluence_publisher/model/base.rb +21 -0
- data/lib/asciidoctor/confluence_publisher/model/page.rb +26 -0
- data/lib/asciidoctor/confluence_publisher/model/property.rb +14 -0
- data/lib/asciidoctor/confluence_publisher/model/space.rb +9 -0
- data/lib/asciidoctor/confluence_publisher/model/version.rb +9 -0
- data/lib/asciidoctor/confluence_publisher/version.rb +5 -0
- data/lib/asciidoctor_confluence_publisher.rb +1 -0
- data/template/block_admonition.html.haml +6 -0
- data/template/block_example.haml.haml +4 -0
- data/template/block_image.html.haml +10 -0
- data/template/block_listing.html.haml +18 -0
- data/template/block_olist.html.haml +8 -0
- data/template/block_paragraph.html.haml +4 -0
- data/template/block_preamble.html.haml +1 -0
- data/template/block_quote.html.haml +9 -0
- data/template/block_stem.html.haml +3 -0
- data/template/block_table.html.haml +24 -0
- data/template/block_toc.html.haml +7 -0
- data/template/block_ulist.html.haml +15 -0
- data/template/block_verse.html.haml +9 -0
- data/template/block_video.html.haml +11 -0
- data/template/document.html.haml +1 -0
- data/template/embedded.html.haml +4 -0
- data/template/helpers.rb +171 -0
- data/template/inline_anchor.html.haml +20 -0
- data/template/inline_image.html.haml +7 -0
- data/template/section.html.haml +6 -0
- metadata +143 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Asciidoctor
|
4
|
+
module ConfluencePublisher
|
5
|
+
class Invoker
|
6
|
+
# default template directory for asciidoctor_confluence marcos
|
7
|
+
DEFAULT_TEMPLATE_DIR = File.expand_path("../../../../template", __FILE__)
|
8
|
+
|
9
|
+
attr_reader :options
|
10
|
+
def initialize(options)
|
11
|
+
@options = options.dup
|
12
|
+
@options[:to_file] = nil
|
13
|
+
@options[:catalog_assets] = true
|
14
|
+
|
15
|
+
@options[:backend] = 'xhtml5'
|
16
|
+
@options[:template_dirs] = Array(options[:template_dirs]) << DEFAULT_TEMPLATE_DIR
|
17
|
+
@options[:to_file] = false
|
18
|
+
@options[:header_footer] = false
|
19
|
+
# confluence related configuration
|
20
|
+
attributes = options[:attributes] || {}
|
21
|
+
attributes['confluence_host'] ||= ENV['CONFLUENCE_HOST']
|
22
|
+
attributes['space'] ||= ENV['SPACE']
|
23
|
+
attributes['username'] ||= ENV['CONFLUENCE_USERNAME']
|
24
|
+
attributes['password'] ||= ENV['CONFLUENCE_PASSWORD']
|
25
|
+
@ancestor_id = (attributes['ancestor_id'] ||= ENV['ANCESTOR_ID'])
|
26
|
+
check_confluence_config(attributes)
|
27
|
+
|
28
|
+
proxy = attributes[:proxy] || ENV['CONFLUENCE_PROXY']
|
29
|
+
skip_verify_ssl = attributes['skip_verify_ssl'].to_s == 'true'
|
30
|
+
@confluence_client = ConfluenceApi.new(attributes['confluence_host'],
|
31
|
+
attributes['space'],
|
32
|
+
attributes['username'],
|
33
|
+
attributes['password'],
|
34
|
+
skip_verify_ssl: skip_verify_ssl,
|
35
|
+
proxy: proxy)
|
36
|
+
end
|
37
|
+
|
38
|
+
def invoke!
|
39
|
+
input_files = options[:asciidoc_source_dir] ? Array(options[:asciidoc_source_dir]) : options[:input_files]
|
40
|
+
|
41
|
+
input_files.map(&method(:build_file_tree)).each do |node|
|
42
|
+
traverse_file_tree(@ancestor_id, node, &method(:convert_and_publish))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def convert_and_publish(ancestor_id, input_file)
|
48
|
+
if File.file?(input_file)
|
49
|
+
document = Asciidoctor.load_file input_file, options
|
50
|
+
confluence_page = find_or_create_page(document.title, ancestor_id)
|
51
|
+
process_page_content(confluence_page.id, document.title, document.content)
|
52
|
+
process_page_attachments(confluence_page.id, [document.references[:links], document.references[:images]].flatten, File.dirname(input_file))
|
53
|
+
else
|
54
|
+
title = File.basename(input_file)
|
55
|
+
confluence_page = find_or_create_page(title, ancestor_id)
|
56
|
+
end
|
57
|
+
confluence_page.id
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_page_attachments(page_id, assets, source_dir)
|
61
|
+
resource_property = 'resource_hash'
|
62
|
+
page_resource_prop = @confluence_client.get_page_property(page_id, resource_property)
|
63
|
+
page_attachment_hash = page_resource_prop && page_resource_prop.value || {}
|
64
|
+
attachment_hash = @confluence_client.get_attachments(page_id).map { |attachment| [attachment.title, attachment.id] }.to_h
|
65
|
+
|
66
|
+
has_asset_updated = false
|
67
|
+
assets.each do |link|
|
68
|
+
next if Asciidoctor::Helpers.uriish?(link)
|
69
|
+
source_file_dir = source_dir
|
70
|
+
if link.is_a? ::Struct
|
71
|
+
source_file_dir += "/#{link.imagesdir}"
|
72
|
+
end
|
73
|
+
file_path = File.join(source_file_dir, link.to_s)
|
74
|
+
next if !File.exist?(file_path)
|
75
|
+
file_md5 = Digest::MD5.hexdigest File.read(file_path)
|
76
|
+
filename = File.basename(file_path)
|
77
|
+
|
78
|
+
attachment_update_success = if attachment_hash.has_key?(filename)
|
79
|
+
if file_md5 != page_attachment_hash[filename]
|
80
|
+
@confluence_client.update_attachment(page_id, attachment_hash[filename], file_path)
|
81
|
+
has_asset_updated = true
|
82
|
+
end
|
83
|
+
else
|
84
|
+
@confluence_client.create_attachment(page_id, file_path)
|
85
|
+
has_asset_updated = true
|
86
|
+
end
|
87
|
+
page_attachment_hash[filename] = file_md5 if attachment_update_success
|
88
|
+
end
|
89
|
+
|
90
|
+
@confluence_client.set_page_property(page_id, resource_property, page_attachment_hash) if has_asset_updated
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_page_content(page_id, title, document_content)
|
94
|
+
content_property = 'content_hash'
|
95
|
+
page_hash_prop = @confluence_client.get_page_property(page_id, content_property)
|
96
|
+
page_content_hash = page_hash_prop && page_hash_prop.value
|
97
|
+
|
98
|
+
if (new_content_hash = Digest::MD5.hexdigest(document_content)) != page_content_hash
|
99
|
+
@confluence_client.update_page(page_id, title, document_content)
|
100
|
+
@confluence_client.set_page_property(page_id, content_property, new_content_hash)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_confluence_config(attributes)
|
105
|
+
empty_attrs = %w(confluence_host space username password ancestor_id).select do |prop|
|
106
|
+
attr = attributes[prop]
|
107
|
+
attr.nil? || attr.to_s.strip.length < 1
|
108
|
+
end
|
109
|
+
raise empty_attrs.join(', ') if empty_attrs.size > 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_file_tree(root)
|
113
|
+
node = nil
|
114
|
+
if File.directory?(root) || File.file?(root) && File.extname(root) =~ /.(adoc|asc|asciidoc)$/
|
115
|
+
node = Asciidoctor::ConfluencePublisher::Asciidoc.new(root)
|
116
|
+
end
|
117
|
+
return node if File.file?(root)
|
118
|
+
|
119
|
+
Dir.children(root).each do |dir|
|
120
|
+
next if dir.start_with?('.')
|
121
|
+
child = build_file_tree(File.join(root, dir))
|
122
|
+
node.add_child(child)
|
123
|
+
end
|
124
|
+
|
125
|
+
node
|
126
|
+
end
|
127
|
+
|
128
|
+
def traverse_file_tree(ancestor_id, root, &block)
|
129
|
+
new_ancestor_id = ancestor_id
|
130
|
+
if options[:asciidoc_source_dir] != root.path
|
131
|
+
new_ancestor_id = block.call(ancestor_id, root.path)
|
132
|
+
end
|
133
|
+
children = root.children
|
134
|
+
return if children.size.zero?
|
135
|
+
children.each do |child|
|
136
|
+
next if !child.has_any_leaves?
|
137
|
+
traverse_file_tree(new_ancestor_id, child, &block)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def find_or_create_page(title, ancestor_id)
|
142
|
+
confluence_pages = @confluence_client.get_pages_by_title(title).select { |page| page.contain_ancestor?(ancestor_id) }
|
143
|
+
if confluence_pages.size > 1
|
144
|
+
raise 'Too many duplicate page title'
|
145
|
+
elsif confluence_pages.size == 1
|
146
|
+
confluence_page = confluence_pages[0]
|
147
|
+
else
|
148
|
+
confluence_page = @confluence_client.create_page(title, '', ancestor_id)
|
149
|
+
end
|
150
|
+
confluence_page
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
module ConfluencePublisher
|
3
|
+
module Model
|
4
|
+
class Base
|
5
|
+
def initialize(hash = {})
|
6
|
+
accessor_methods = public_methods(false).select { |mth| mth.to_s =~ /\w+=$/ }
|
7
|
+
hash.each do |key, val|
|
8
|
+
setter_method = "#{key}="
|
9
|
+
if accessor_methods.include?(setter_method.to_sym)
|
10
|
+
public_send(setter_method, val)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
inspect
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
module ConfluencePublisher
|
3
|
+
module Model
|
4
|
+
class Page < Base
|
5
|
+
attr_accessor :id, :title
|
6
|
+
attr_reader :version, :ancestors, :space
|
7
|
+
|
8
|
+
def version=(ver)
|
9
|
+
@version = Version.new(ver)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ancestors=(ancestors)
|
13
|
+
@ancestors = ancestors.map { |ans| Ancestor.new(ans) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def space=(space)
|
17
|
+
@space = Space.new(space)
|
18
|
+
end
|
19
|
+
|
20
|
+
def contain_ancestor?(ancestor_id)
|
21
|
+
@ancestors.any? { |ancestor| ancestor.id == ancestor_id.to_s }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "asciidoctor/confluence_publisher"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
- haml_tag_if((attr? :link), :a, {href: (attr :link)}) do
|
2
|
+
- href_target = attr :target
|
3
|
+
%ac:image{ "ac:title" => title? && title, "ac:alt" => (attr :alt), "ac:height" => (attr :height), "ac:width" => (attr :width) }
|
4
|
+
- if uri_link? href_target
|
5
|
+
%ri:url{ "ri:value" => href_target }/
|
6
|
+
- else
|
7
|
+
%ri:attachment{ "ri:filename" => File.basename(href_target) }/
|
8
|
+
- if title?
|
9
|
+
.cp-image-title
|
10
|
+
%em= captioned_title
|
@@ -0,0 +1,18 @@
|
|
1
|
+
- if @style == 'source'
|
2
|
+
%ac:structured-macro{"ac:name" => "code"}
|
3
|
+
- if confluence_supported_lang(source_lang)
|
4
|
+
%ac:parameter{"ac:name" => "language"}= source_lang
|
5
|
+
- if title?
|
6
|
+
%ac:parameter{"ac:name" => "title"}= title
|
7
|
+
- if attr? :linenums
|
8
|
+
%ac:parameter{"ac:name" => "linenumbers"} true
|
9
|
+
- if attr? :start
|
10
|
+
%ac:parameter{"ac:name" => "firstline"}= attr :start
|
11
|
+
- wrap_code = (@document.attr? :prewrap) || (has_option?(:nowrap) && !(option? :nowrap))
|
12
|
+
%ac:parameter{"ac:name" => "collapse"}= wrap_code
|
13
|
+
%ac:plain-text-body= "<![CDATA[#{content}]]>"
|
14
|
+
- else
|
15
|
+
%ac:structured-macro{"ac:name" => "noformat"}
|
16
|
+
- if title?
|
17
|
+
%ac:parameter{"ac:name" => "title"}= title
|
18
|
+
%ac:plain-text-body= "<![CDATA[#{content}]]>"
|
@@ -0,0 +1 @@
|
|
1
|
+
=content
|
@@ -0,0 +1,24 @@
|
|
1
|
+
%table
|
2
|
+
- if title?
|
3
|
+
%caption.title= captioned_title
|
4
|
+
- unless (attr :rowcount).zero?
|
5
|
+
- [:head, :foot, :body].select {|tblsec| !@rows[tblsec].empty? }.each do |tblsec|
|
6
|
+
- haml_tag "t#{tblsec}" do
|
7
|
+
- @rows[tblsec].each do |row|
|
8
|
+
%tr
|
9
|
+
- row.each do |cell|
|
10
|
+
- haml_tag "#{(tblsec == :head || cell.style == :header) ? 'th' : 'td'}", colspan: cell.colspan, rowspan: cell.rowspan do
|
11
|
+
- if tblsec == :head
|
12
|
+
= cell.text
|
13
|
+
- else
|
14
|
+
- case cell.style
|
15
|
+
- when :asciidoc
|
16
|
+
%div= cell.content
|
17
|
+
- when :verse
|
18
|
+
.verse= cell.text
|
19
|
+
- when :literal
|
20
|
+
.literal
|
21
|
+
%pre= cell.text
|
22
|
+
- else
|
23
|
+
- cell.content.each do |text|
|
24
|
+
= text
|
@@ -0,0 +1,7 @@
|
|
1
|
+
%ac:structured-macro{"ac:name" => "toc"}
|
2
|
+
%ac:parameter{"ac:name" => "maxLevel"} 4
|
3
|
+
%ac:parameter{"ac:name" => "minLevel"} 2
|
4
|
+
%ac:parameter{"ac:name" => "outline"} true
|
5
|
+
%ac:parameter{"ac:name" => "indent"} 0px
|
6
|
+
%ac:parameter{"ac:name" => "style"} none
|
7
|
+
%ac:parameter{"ac:name" => "separator"} pipe
|
@@ -0,0 +1,15 @@
|
|
1
|
+
- if title?
|
2
|
+
.cp-ulist-title =title
|
3
|
+
%ul
|
4
|
+
- items.each_with_index do |item, index|
|
5
|
+
%li
|
6
|
+
- if item.attr? :checkbox
|
7
|
+
%ac:task-list
|
8
|
+
%ac:task
|
9
|
+
%ac:task-id= index
|
10
|
+
%ac:task-status= (item.attr? :checked) ? 'complete' : 'imcomplete'
|
11
|
+
%ac:task-body= item.text
|
12
|
+
- else
|
13
|
+
=item.text
|
14
|
+
- if item.blocks?
|
15
|
+
= item.content
|
@@ -0,0 +1,11 @@
|
|
1
|
+
%div{:id=>@id, :class=>['videoblock', @style, role]}
|
2
|
+
- if title?
|
3
|
+
.title=captioned_title
|
4
|
+
.content
|
5
|
+
- if video_iframe?
|
6
|
+
%iframe{:width=>(attr :width), :height=>(attr :height), :src=>video_uri, :frameborder=>0, :allowfullscreen=>!(option? :nofullscreen)}
|
7
|
+
- else
|
8
|
+
%video{:src=>video_uri, :width=>(attr :width), :height=>(attr :height),
|
9
|
+
:poster=>((attr :poster) ? media_uri(attr :poster) : nil), :autoplay=>(option? :autoplay),
|
10
|
+
:controls=>!(option? :nocontrols), :loop=>(option? :loop)}
|
11
|
+
Your browser does not support the video tag.
|
@@ -0,0 +1 @@
|
|
1
|
+
= content
|
data/template/helpers.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
# Add custom functions to this module that you want to use in your Haml
|
2
|
+
# templates. Within the template you can invoke them as top-level functions
|
3
|
+
# just like the built-in helper functions that Haml provides.
|
4
|
+
module Haml::Helpers
|
5
|
+
|
6
|
+
CG_ALPHA = '[a-zA-Z]'
|
7
|
+
CC_ALNUM = 'a-zA-Z0-9'
|
8
|
+
|
9
|
+
# Detects strings that resemble URIs.
|
10
|
+
#
|
11
|
+
# Examples
|
12
|
+
# http://domain
|
13
|
+
# https://domain
|
14
|
+
# file:///path
|
15
|
+
# data:info
|
16
|
+
#
|
17
|
+
# not c:/sample.adoc or c:\sample.adoc
|
18
|
+
#
|
19
|
+
UriRegexp = %r{^#{CG_ALPHA}[#{CC_ALNUM}.+-]+:/{0,2}}
|
20
|
+
|
21
|
+
def has_option? name
|
22
|
+
@attributes.has_key? %(#{name}-option)
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Returns corrected section level.
|
27
|
+
#
|
28
|
+
# @param sec [Asciidoctor::Section] the section node (default: self).
|
29
|
+
# @return [Integer]
|
30
|
+
#
|
31
|
+
def section_level(sec = self)
|
32
|
+
@_section_level ||= (sec.level == 0 && sec.special) ? 1 : sec.level
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Returns the captioned section's title, optionally numbered.
|
37
|
+
#
|
38
|
+
# @param sec [Asciidoctor::Section] the section node (default: self).
|
39
|
+
# @return [String]
|
40
|
+
#
|
41
|
+
def section_title(sec = self)
|
42
|
+
sectnumlevels = document.attr(:sectnumlevels, 3).to_i
|
43
|
+
|
44
|
+
if sec.numbered && !sec.caption && sec.level <= sectnumlevels
|
45
|
+
[sec.sectnum, sec.captioned_title].join(' ')
|
46
|
+
else
|
47
|
+
sec.captioned_title
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#--------------------------------------------------------
|
52
|
+
# block_listing
|
53
|
+
#
|
54
|
+
|
55
|
+
def source_lang
|
56
|
+
attr :language, nil, false
|
57
|
+
end
|
58
|
+
|
59
|
+
def confluence_supported_lang(lang)
|
60
|
+
supported = ['actionscript3','applescript','bash','c#','cpp','css',
|
61
|
+
'coldfusion','delphi','diff','erl','groovy',
|
62
|
+
'xml','java','jfx','js','php','perl',
|
63
|
+
'text','powershell','py','ruby','sql','sass',
|
64
|
+
'scala','vb','yml']
|
65
|
+
supported.include? lang
|
66
|
+
end
|
67
|
+
|
68
|
+
#--------------------------------------------------------
|
69
|
+
# block_table
|
70
|
+
#
|
71
|
+
|
72
|
+
def autowidth?
|
73
|
+
option? :autowidth
|
74
|
+
end
|
75
|
+
|
76
|
+
def spread?
|
77
|
+
'spread' if !(option? 'autowidth') && (attr :tablepcwidth) == 100
|
78
|
+
end
|
79
|
+
|
80
|
+
#--------------------------------------------------------
|
81
|
+
# block_video
|
82
|
+
#
|
83
|
+
|
84
|
+
# @return [Boolean] +true+ if the video should be embedded in an iframe.
|
85
|
+
def video_iframe?
|
86
|
+
['vimeo', 'youtube'].include?(attr :poster)
|
87
|
+
end
|
88
|
+
|
89
|
+
def admonition_name
|
90
|
+
case (attr :name)
|
91
|
+
when 'note'
|
92
|
+
'info'
|
93
|
+
when 'tip'
|
94
|
+
'tip'
|
95
|
+
when 'caution', 'warning'
|
96
|
+
'note'
|
97
|
+
when 'important'
|
98
|
+
'warning'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Public: Efficiently checks whether the specified String resembles a URI
|
103
|
+
#
|
104
|
+
# Uses the Asciidoctor::UriSniffRx regex to check whether the String begins
|
105
|
+
# with a URI prefix (e.g., http://). No validation of the URI is performed.
|
106
|
+
#
|
107
|
+
# str - the String to check
|
108
|
+
#
|
109
|
+
# @return true if the String is a URI, false if it is not
|
110
|
+
def uri_link?(str)
|
111
|
+
str && (str.include? ':') && str =~ UriRegexp
|
112
|
+
end
|
113
|
+
|
114
|
+
def video_uri
|
115
|
+
case (attr :poster, '').to_sym
|
116
|
+
when :vimeo
|
117
|
+
params = {
|
118
|
+
:autoplay => (1 if option? 'autoplay'),
|
119
|
+
:loop => (1 if option? 'loop')
|
120
|
+
}
|
121
|
+
start_anchor = "#at=#{attr :start}" if attr? :start
|
122
|
+
"//player.vimeo.com/video/#{attr :target}#{start_anchor}#{url_query params}"
|
123
|
+
|
124
|
+
when :youtube
|
125
|
+
video_id, list_id = (attr :target).split('/', 2)
|
126
|
+
params = {
|
127
|
+
:rel => 0,
|
128
|
+
:start => (attr :start),
|
129
|
+
:end => (attr :end),
|
130
|
+
:list => (attr :list, list_id),
|
131
|
+
:autoplay => (1 if option? 'autoplay'),
|
132
|
+
:loop => (1 if option? 'loop'),
|
133
|
+
:controls => (0 if option? 'nocontrols')
|
134
|
+
}
|
135
|
+
"//www.youtube.com/embed/#{video_id}#{url_query params}"
|
136
|
+
else
|
137
|
+
anchor = [(attr :start), (attr :end)].join(',').chomp(',')
|
138
|
+
anchor.prepend '#t=' unless anchor.empty?
|
139
|
+
media_uri "#{attr :target}#{anchor}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Formats URL query parameters.
|
144
|
+
def url_query(params)
|
145
|
+
str = params.map { |k, v|
|
146
|
+
next if v.nil? || v.to_s.empty?
|
147
|
+
[k, v] * '='
|
148
|
+
}.compact.join('&')
|
149
|
+
|
150
|
+
str.prepend('?') unless str.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
#--------------------------------------------------------
|
154
|
+
# inline_anchor
|
155
|
+
#
|
156
|
+
# @return [String, nil] text of the xref anchor, or +nil+ if not found.
|
157
|
+
def xref_text
|
158
|
+
str = text || document.references[:ids][attr :refid || target]
|
159
|
+
str.tr_s("\n", ' ') if str
|
160
|
+
end
|
161
|
+
|
162
|
+
# removes leading hash from anchor targets
|
163
|
+
def anchor_name str
|
164
|
+
if str.start_with? "#"
|
165
|
+
str[1..str.length]
|
166
|
+
else
|
167
|
+
str
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|