asciidoctor 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of asciidoctor might be problematic. Click here for more details.

Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +1 -1
  3. data/LICENSE +2 -2
  4. data/README.adoc +461 -0
  5. data/asciidoctor.gemspec +27 -16
  6. data/compat/asciidoc.conf +139 -0
  7. data/lib/asciidoctor.rb +212 -69
  8. data/lib/asciidoctor/abstract_block.rb +41 -0
  9. data/lib/asciidoctor/abstract_node.rb +128 -81
  10. data/lib/asciidoctor/attribute_list.rb +5 -2
  11. data/lib/asciidoctor/backends/base_template.rb +16 -4
  12. data/lib/asciidoctor/backends/docbook45.rb +112 -42
  13. data/lib/asciidoctor/backends/html5.rb +206 -90
  14. data/lib/asciidoctor/block.rb +5 -5
  15. data/lib/asciidoctor/cli/invoker.rb +38 -34
  16. data/lib/asciidoctor/cli/options.rb +3 -3
  17. data/lib/asciidoctor/document.rb +115 -13
  18. data/lib/asciidoctor/helpers.rb +16 -0
  19. data/lib/asciidoctor/lexer.rb +486 -359
  20. data/lib/asciidoctor/path_resolver.rb +360 -0
  21. data/lib/asciidoctor/reader.rb +122 -23
  22. data/lib/asciidoctor/renderer.rb +1 -33
  23. data/lib/asciidoctor/section.rb +1 -1
  24. data/lib/asciidoctor/substituters.rb +103 -19
  25. data/lib/asciidoctor/version.rb +1 -1
  26. data/man/asciidoctor.1 +6 -6
  27. data/man/asciidoctor.ad +5 -3
  28. data/stylesheets/asciidoctor.css +274 -0
  29. data/test/attributes_test.rb +133 -10
  30. data/test/blocks_test.rb +302 -17
  31. data/test/document_test.rb +269 -6
  32. data/test/fixtures/basic-docinfo.html +1 -0
  33. data/test/fixtures/basic-docinfo.xml +4 -0
  34. data/test/fixtures/basic.asciidoc +4 -0
  35. data/test/fixtures/docinfo.html +1 -0
  36. data/test/fixtures/docinfo.xml +2 -0
  37. data/test/fixtures/include-file.asciidoc +22 -1
  38. data/test/fixtures/stylesheets/custom.css +3 -0
  39. data/test/invoker_test.rb +38 -6
  40. data/test/lexer_test.rb +64 -21
  41. data/test/links_test.rb +4 -0
  42. data/test/lists_test.rb +251 -12
  43. data/test/paragraphs_test.rb +225 -30
  44. data/test/paths_test.rb +174 -0
  45. data/test/reader_test.rb +89 -2
  46. data/test/sections_test.rb +518 -16
  47. data/test/substitutions_test.rb +121 -10
  48. data/test/tables_test.rb +53 -13
  49. data/test/test_helper.rb +2 -2
  50. data/test/text_test.rb +5 -5
  51. metadata +46 -50
  52. data/README.asciidoc +0 -296
  53. data/lib/asciidoctor/errors.rb +0 -5
@@ -6,39 +6,41 @@
6
6
  ## http://docs.rubygems.org/read/chapter/20
7
7
  Gem::Specification.new do |s|
8
8
  s.specification_version = 2 if s.respond_to? :specification_version=
9
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
- s.rubygems_version = '1.3.5'
9
+ s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.8.5'
11
11
 
12
- ## Leave these as is they will be modified for you by the rake gemspec task.
13
- ## If your rubyforge_project name is different, then edit it and comment out
14
- ## the sub! line in the Rakefile
12
+ ## This group of properties is updated automatically by the Rake build when
13
+ ## cutting a new release (see the validate task)
15
14
  s.name = 'asciidoctor'
16
- s.version = '0.1.1'
17
- s.date = '2013-02-26'
15
+ s.version = '0.1.2'
16
+ s.date = '2013-04-25'
18
17
  s.rubyforge_project = 'asciidoctor'
19
18
 
20
19
  ## Make sure your summary is short. The description may be as long
21
20
  ## as you like.
22
- s.summary = "Pure Ruby Asciidoc to HTML rendering."
23
- s.description = "A pure Ruby processor to turn Asciidoc-formatted documents into HTML (and, eventually, other formats perhaps)."
21
+ s.summary = 'A native Ruby AsciiDoc syntax processor and publishing toolchain'
22
+ s.description = <<-EOS
23
+ An open source text processor and publishing toolchain, written entirely in Ruby, for converting AsciiDoc markup into HTML 5, DocBook 4.5 and other formats.
24
+ EOS
25
+ s.license = 'MIT'
24
26
 
25
27
  ## List the primary authors. If there are a bunch of authors, it's probably
26
28
  ## better to set the email to an email list or something. If you don't have
27
29
  ## a custom homepage, consider using your GitHub URL or the like.
28
- s.authors = ["Ryan Waldron", "Dan Allen", "Jeremy McAnally"]
29
- s.email = 'rew@erebor.com'
30
- s.homepage = 'http://github.com/asciidoctor'
30
+ s.authors = ['Ryan Waldron', 'Dan Allen', 'Jeremy McAnally', 'Jason Porter']
31
+ s.email = ['rew@erebor.com', 'dan.j.allen@gmail.com']
32
+ s.homepage = 'http://asciidoctor.org'
31
33
 
32
34
  ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
33
35
  ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
34
36
  s.require_paths = %w[lib]
35
37
 
36
38
  ## If your gem includes any executables, list them here.
37
- s.executables = ["asciidoctor"]
39
+ s.executables = ['asciidoctor', 'asciidoctor-safe']
38
40
 
39
41
  ## Specify any RDoc options here. You'll want to add your README and
40
42
  ## LICENSE files to the extra_rdoc_files list.
41
- s.rdoc_options = ["--charset=UTF-8"]
43
+ s.rdoc_options = ['--charset=UTF-8']
42
44
  s.extra_rdoc_files = %w[LICENSE]
43
45
 
44
46
  ## List your runtime dependencies here. Runtime dependencies are those
@@ -63,11 +65,12 @@ Gem::Specification.new do |s|
63
65
  s.files = %w[
64
66
  Gemfile
65
67
  LICENSE
66
- README.asciidoc
68
+ README.adoc
67
69
  Rakefile
68
70
  asciidoctor.gemspec
69
71
  bin/asciidoctor
70
72
  bin/asciidoctor-safe
73
+ compat/asciidoc.conf
71
74
  lib/asciidoctor.rb
72
75
  lib/asciidoctor/abstract_block.rb
73
76
  lib/asciidoctor/abstract_node.rb
@@ -81,11 +84,11 @@ Gem::Specification.new do |s|
81
84
  lib/asciidoctor/cli/options.rb
82
85
  lib/asciidoctor/debug.rb
83
86
  lib/asciidoctor/document.rb
84
- lib/asciidoctor/errors.rb
85
87
  lib/asciidoctor/helpers.rb
86
88
  lib/asciidoctor/inline.rb
87
89
  lib/asciidoctor/lexer.rb
88
90
  lib/asciidoctor/list_item.rb
91
+ lib/asciidoctor/path_resolver.rb
89
92
  lib/asciidoctor/reader.rb
90
93
  lib/asciidoctor/renderer.rb
91
94
  lib/asciidoctor/section.rb
@@ -94,17 +97,24 @@ Gem::Specification.new do |s|
94
97
  lib/asciidoctor/version.rb
95
98
  man/asciidoctor.1
96
99
  man/asciidoctor.ad
100
+ stylesheets/asciidoctor.css
97
101
  test/attributes_test.rb
98
102
  test/blocks_test.rb
99
103
  test/document_test.rb
100
104
  test/fixtures/asciidoc.txt
101
105
  test/fixtures/asciidoc_index.txt
102
106
  test/fixtures/ascshort.txt
107
+ test/fixtures/basic-docinfo.html
108
+ test/fixtures/basic-docinfo.xml
109
+ test/fixtures/basic.asciidoc
110
+ test/fixtures/docinfo.html
111
+ test/fixtures/docinfo.xml
103
112
  test/fixtures/dot.gif
104
113
  test/fixtures/encoding.asciidoc
105
114
  test/fixtures/include-file.asciidoc
106
115
  test/fixtures/list_elements.asciidoc
107
116
  test/fixtures/sample.asciidoc
117
+ test/fixtures/stylesheets/custom.css
108
118
  test/fixtures/tip.gif
109
119
  test/invoker_test.rb
110
120
  test/lexer_test.rb
@@ -112,6 +122,7 @@ Gem::Specification.new do |s|
112
122
  test/lists_test.rb
113
123
  test/options_test.rb
114
124
  test/paragraphs_test.rb
125
+ test/paths_test.rb
115
126
  test/preamble_test.rb
116
127
  test/reader_test.rb
117
128
  test/renderer_test.rb
@@ -0,0 +1,139 @@
1
+ # This file is an AsciiDoc configuration file that makes
2
+ # AsciiDoc conform with Asciidoctor's fixes and customizations.
3
+ #
4
+ # Place this file in the same directory as your AsciiDoc document and the
5
+ # AsciiDoc processor (asciidoc) will automatically use it.
6
+
7
+ [miscellaneous]
8
+ newline=\n
9
+
10
+ [attributes]
11
+ # make html5 the default html backend
12
+ backend-alias-html=html5
13
+ linkcss=
14
+ apostrophe='
15
+ asterisk=*
16
+ caret=^
17
+ backtick=`
18
+ # plus introduced in AsciiDoc 8.6.9
19
+ plus=&#43;
20
+ space=" "
21
+ tilde=~
22
+
23
+ # enables fenced code blocks
24
+ # I haven't sorted out yet how to do syntax highlighting
25
+ [blockdef-fenced-code]
26
+ delimiter=^```\w*$
27
+ template::[blockdef-listing]
28
+
29
+ # enables literal block to be used as code block
30
+ [blockdef-literal]
31
+ template::[source-filter-style]
32
+
33
+ ifdef::basebackend-html[]
34
+
35
+ [literal-inlinemacro]
36
+ <code>{passtext}</code>
37
+
38
+ [tags]
39
+ monospaced=<code{1? class="{1}"}>|</code>
40
+
41
+ [monospacedwords]
42
+ <code>{words}</code>
43
+
44
+ [tabletags-monospaced]
45
+ paragraph=<p class="tableblock"><code>|</code></p>
46
+
47
+ # support for document title in embedded documents
48
+ ifeval::[not config.header_footer]
49
+ [preamble]
50
+ <h1>{title={doctitle}}</h1>{set:title-rendered:}
51
+ <div id="preamble">
52
+ <div class="sectionbody">
53
+ |
54
+ </div>
55
+ </div>
56
+
57
+ [sect1]
58
+ {title-rendered%}<h1>{doctitle}</h1>
59
+ <div class="sect1{style? {style}}{role? {role}}">
60
+ <h2{id? id="{id}"}>{numbered?{sectnum} }{title}</h2>
61
+ <div class="sectionbody">
62
+ |
63
+ </div>
64
+ </div>
65
+ endif::[]
66
+
67
+ # override to add the admonition name to the class attribute of the outer element
68
+ [admonitionblock]
69
+ <div class="admonitionblock {name}{role? {role}}{unbreakable-option? unbreakable}"{id? id="{id}"}>
70
+ <table><tr>
71
+ <td class="icon">
72
+ {data-uri%}{icons#}<img src="{icon={iconsdir}/{name}.png}" alt="{caption}">
73
+ {data-uri#}{icons#}<img alt="{caption}" src="data:image/png;base64,
74
+ {data-uri#}{icons#}{sys:"{python}" -u -c "import base64,sys; base64.encode(sys.stdin,sys.stdout)" < "{eval:os.path.join(r"{indir={outdir}}",r"{icon={iconsdir}/{name}.png}")}"}">
75
+ {icons%}<div class="title">{caption}</div>
76
+ </td>
77
+ <td class="content">
78
+ <div class="title">{title}</div>
79
+ |
80
+ </td>
81
+ </tr></table>
82
+ </div>
83
+
84
+ # a common template for emitting the attribute for a quote or verse block
85
+ # don't output attribution div if attribution or citetitle are both empty
86
+ [attribution]
87
+ {attribution,citetitle#}<div class="attribution">
88
+ <cite>{citetitle}</cite>{attribution?<br>}
89
+ &#8212; {attribution}
90
+ {attribution,citetitle#}</div>
91
+
92
+ # override to use blockquote element for content and cite element for cite title
93
+ [quoteblock]
94
+ <div class="quoteblock{role? {role}}{unbreakable-option? unbreakable}"{id? id="{id}"}>
95
+ <div class="title">{title}</div>
96
+ <blockquote>
97
+ |
98
+ </blockquote>
99
+ template::[attribution]
100
+ </div>
101
+
102
+ # override to use cite element for cite title
103
+ [verseblock]
104
+ <div class="verseblock{role? {role}}{unbreakable-option? unbreakable}"{id? id="{id}"}>
105
+ <div class="title">{title}</div>
106
+ <pre class="content">
107
+ |
108
+ </pre>
109
+ template::[attribution]
110
+ </div>
111
+
112
+ endif::basebackend-html[]
113
+
114
+ # Override docinfo to support subtitle
115
+ ifdef::basebackend-docbook[]
116
+
117
+ [docinfo]
118
+ ifndef::notitle[]
119
+ {set2:subtitle_offset:{eval:'{doctitle}'.rfind(': ')}}
120
+ {eval:{subtitle_offset} != -1} <title>{eval:'{doctitle}'[0:{subtitle_offset}]}</title>
121
+ {eval:{subtitle_offset} != -1} <subtitle>{eval:'{doctitle}'[{subtitle_offset} + 2:]}</subtitle>
122
+ {eval:{subtitle_offset} < 0} <title>{doctitle}</title>
123
+ endif::notitle[]
124
+ <date>{revdate}</date>
125
+ # To ensure valid articleinfo/bookinfo when there is no AsciiDoc header.
126
+ {doctitle%}{revdate%}<date>{docdate}</date>
127
+ {authored#}<author>
128
+ <firstname>{firstname}</firstname>
129
+ <othername>{middlename}</othername>
130
+ <surname>{lastname}</surname>
131
+ <email>{email}</email>
132
+ {authored#}</author>
133
+ <authorinitials>{authorinitials}</authorinitials>
134
+ <revhistory><revision>{revnumber?<revnumber>{revnumber}</revnumber>}<date>{revdate}</date>{authorinitials?<authorinitials>{authorinitials}</authorinitials>}{revremark?<revremark>{revremark}</revremark>}</revision></revhistory>
135
+ {docinfo1,docinfo2#}{include:{docdir}/docinfo.xml}
136
+ {docinfo,docinfo2#}{include:{docdir}/{docname}-docinfo.xml}
137
+ <orgname>{orgname}</orgname>
138
+
139
+ endif::basebackend-docbook[]
@@ -1,5 +1,6 @@
1
- require 'rubygems'
1
+ require 'rubygems' unless RUBY_VERSION >= '1.9'
2
2
  require 'strscan'
3
+ require 'set'
3
4
 
4
5
  $:.unshift(File.dirname(__FILE__))
5
6
  #$:.unshift(File.join(File.dirname(__FILE__), '..', 'vendor'))
@@ -87,6 +88,9 @@ module Asciidoctor
87
88
 
88
89
  end
89
90
 
91
+ # The root path of the Asciidoctor gem
92
+ ROOT_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..'))
93
+
90
94
  # The default document type
91
95
  # Can influence markup generated by render templates
92
96
  DEFAULT_DOCTYPE = 'article'
@@ -94,6 +98,12 @@ module Asciidoctor
94
98
  # The backend determines the format of the rendered output, default to html5
95
99
  DEFAULT_BACKEND = 'html5'
96
100
 
101
+ DEFAULT_STYLESHEET_PATH = File.join(ROOT_PATH, 'stylesheets', 'asciidoctor.css')
102
+
103
+ DEFAULT_STYLESHEET_KEYS = ['', 'DEFAULT'].to_set
104
+
105
+ DEFAULT_STYLESHEET_NAME = File.basename(DEFAULT_STYLESHEET_PATH)
106
+
97
107
  # Pointers to the preferred version for a given backend.
98
108
  BACKEND_ALIASES = {
99
109
  'html' => 'html5',
@@ -113,19 +123,35 @@ module Asciidoctor
113
123
  'markdown' => '.md'
114
124
  }
115
125
 
126
+ SECTION_LEVELS = {
127
+ '=' => 0,
128
+ '-' => 1,
129
+ '~' => 2,
130
+ '^' => 3,
131
+ '+' => 4
132
+ }
133
+
134
+ ADMONITION_STYLES = ['NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION'].to_set
135
+
136
+ # NOTE: AsciiDoc doesn't support pass style for paragraph
137
+ PARAGRAPH_STYLES = ['comment', 'example', 'literal', 'listing', 'normal', 'pass', 'quote', 'sidebar', 'source', 'verse'].to_set
138
+
139
+ VERBATIM_STYLES = ['literal', 'listing', 'source', 'verse'].to_set
140
+
116
141
  DELIMITED_BLOCKS = {
117
- '--' => :open,
118
- '----' => :listing,
119
- '....' => :literal,
120
- '====' => :example,
121
- '****' => :sidebar,
122
- '____' => :quote,
123
- '++++' => :pass,
124
- '|===' => :table,
125
- '!===' => :table,
126
- '////' => :comment,
127
- '```' => :fenced_code,
128
- '~~~' => :fenced_code
142
+ # NOTE: AsciiDoc doesn't support pass style for open block
143
+ '--' => [:open, ['comment', 'example', 'literal', 'listing', 'pass', 'quote', 'sidebar', 'source', 'verse', 'admonition'].to_set],
144
+ '----' => [:listing, ['literal', 'source'].to_set],
145
+ '....' => [:literal, ['listing', 'source'].to_set],
146
+ '====' => [:example, ['admonition'].to_set],
147
+ '****' => [:sidebar, Set.new],
148
+ '____' => [:quote, ['verse'].to_set],
149
+ '++++' => [:pass, Set.new],
150
+ '|===' => [:table, Set.new],
151
+ '!===' => [:table, Set.new],
152
+ '////' => [:comment, Set.new],
153
+ '```' => [:fenced_code, Set.new],
154
+ '~~~' => [:fenced_code, Set.new]
129
155
  }
130
156
 
131
157
  BREAK_LINES = {
@@ -156,6 +182,27 @@ module Asciidoctor
156
182
 
157
183
  LINE_FEED_ENTITY = '&#10;' # or &#x0A;
158
184
 
185
+ # Flags to control compliance with the behavior of AsciiDoc
186
+ COMPLIANCE = {
187
+ # AsciiDoc terminates paragraphs adjacent to
188
+ # block content (delimiter or block attribute list)
189
+ # Compliance value: true
190
+ # TODO what about literal paragraph?
191
+ :block_terminates_paragraph => true,
192
+
193
+ # AsciiDoc does not treat paragraphs labeled with a
194
+ # verbatim style (literal, listing, source, verse)
195
+ # as verbatim; override this behavior
196
+ # Compliance value: false
197
+ :strict_verbatim_paragraphs => true,
198
+
199
+ # AsciiDoc allows start and end delimiters around
200
+ # a block to be different lengths
201
+ # this option requires that they be the same
202
+ # Compliance value: false
203
+ :congruent_block_delimiters => true
204
+ }
205
+
159
206
  # The following pattern, which appears frequently, captures the contents between square brackets,
160
207
  # ignoring escaped closing brackets (closing brackets prefixed with a backslash '\' character)
161
208
  #
@@ -164,8 +211,11 @@ module Asciidoctor
164
211
  # Matches:
165
212
  # [enclosed text here] or [enclosed [text\] here]
166
213
  REGEXP = {
214
+ # NOTE: this is a inline admonition note
215
+ :admonition_inline => /^(#{ADMONITION_STYLES.to_a * '|'}):\s/,
216
+
167
217
  # [[Foo]]
168
- :anchor => /^\[\[([^\[\]]+)\]\]$/,
218
+ :anchor => /^\[\[([^\s\[\]]+)\]\]$/,
169
219
 
170
220
  # Foowhatevs [[Bar]]
171
221
  :anchor_embedded => /^(.*?)\s*\[\[([^\[\]]+)\]\]$/,
@@ -178,6 +228,14 @@ module Asciidoctor
178
228
  # NOTE position the most common blocks towards the front of the pattern
179
229
  :any_blk => %r{^(?:--|(?:-|\.|=|\*|_|\+|/){4,}|[\|!]={3,}|(?:`|~){3,}.*)$},
180
230
 
231
+ # detect a list item of any sort
232
+ # [[:graph:]] is a non-blank character
233
+ :any_list => /^(?:
234
+ <?\d+>[[:blank:]]+[[:graph:]]|
235
+ [[:blank:]]*(?:(?:-|\*|\.){1,5}|\d+\.|[A-Za-z]\.|[IVXivx]+\))[[:blank:]]+[[:graph:]]|
236
+ [[:blank:]]*.*?(?::{2,4}|;;)(?:[[:blank:]]+[[:graph:]]|$)
237
+ )/x,
238
+
181
239
  # :foo: bar
182
240
  # :Author: Dan
183
241
  # :numbered!:
@@ -204,10 +262,10 @@ module Asciidoctor
204
262
  # [NOTE, caption="Good to know"]
205
263
  # Can be defined by an attribute
206
264
  # [{lead}]
207
- :blk_attr_list => /^\[([\w\{].*)\]$/,
265
+ :blk_attr_list => /^\[(|[[:blank:]]*[\w\{,"'].*)\]$/,
208
266
 
209
267
  # block attribute list or block id (bulk query)
210
- :attr_line => /^\[([\w\{].*|\[[^\[\]]+\])\]$/,
268
+ :attr_line => /^\[(|[[:blank:]]*[\w\{,"'].*|\[[^\[\]]*\])\]$/,
211
269
 
212
270
  # attribute reference
213
271
  # {foo}
@@ -216,7 +274,7 @@ module Asciidoctor
216
274
 
217
275
  # The author info line the appears immediately following the document title
218
276
  # John Doe <john@anonymous.com>
219
- :author_info => /^\s*(\w[\w\-'.]*)(?: +(\w[\w\-'.]*))?(?: +(\w[\w\-'.]*))?(?: +<([^>]+)>)?$/,
277
+ :author_info => /^(\w[\w\-'.]*)(?: +(\w[\w\-'.]*))?(?: +(\w[\w\-'.]*))?(?: +<([^>]+)>)?$/,
220
278
 
221
279
  # [[[Foo]]] (anywhere inline)
222
280
  :biblio_macro => /\\?\[\[\[([\w:][\w:.-]*?)\]\]\]/,
@@ -229,7 +287,7 @@ module Asciidoctor
229
287
  :callout_scan => /\\?<(\d+)>/,
230
288
 
231
289
  # <1> Foo
232
- :colist => /^<?(\d+)> (.*)/,
290
+ :colist => /^<?(\d+)>[[:blank:]]+(.*)/,
233
291
 
234
292
  # ////
235
293
  # comment block
@@ -242,7 +300,15 @@ module Asciidoctor
242
300
  # one,two
243
301
  # one, two
244
302
  # one , two
245
- :csv_delimiter => /[[:space:]]*,[[:space:]]*/,
303
+ :csv_delimiter => /[[:blank:]]*,[[:blank:]]*/,
304
+
305
+ # one;two
306
+ # one; two
307
+ # one ; two
308
+ :semicolon_delim => /[[:blank:]]*;[[:blank:]]*/,
309
+
310
+ # one,two;three;four
311
+ :scsv_csv_delim => /[[:blank:]]*[,;][[:blank:]]*/,
246
312
 
247
313
  # 29
248
314
  :digits => /^\d+$/,
@@ -255,13 +321,14 @@ module Asciidoctor
255
321
  # That which precedes 'bar' (see also, <<bar>>)
256
322
  # The term may be an attribute reference
257
323
  # {term_foo}:: {def_foo}
258
- :dlist => /^\s*(.*?)(:{2,4}|;;)(?:[[:blank:]]+(.*))?$/,
324
+ # REVIEW leading space has already been stripped, so may not need in regex
325
+ :dlist => /^[[:blank:]]*(.*?)(:{2,4}|;;)(?:[[:blank:]]+(.*))?$/,
259
326
  :dlist_siblings => {
260
327
  # (?:.*?[^:])? - a non-capturing group which grabs longest sequence of characters that doesn't end w/ colon
261
- '::' => /^\s*((?:.*[^:])?)(::)(?:[[:blank:]]+(.*))?$/,
262
- ':::' => /^\s*((?:.*[^:])?)(:::)(?:[[:blank:]]+(.*))?$/,
263
- '::::' => /^\s*((?:.*[^:])?)(::::)(?:[[:blank:]]+(.*))?$/,
264
- ';;' => /^\s*(.*)(;;)(?:[[:blank:]]+(.*))?$/
328
+ '::' => /^[[:blank:]]*((?:.*[^:])?)(::)(?:[[:blank:]]+(.*))?$/,
329
+ ':::' => /^[[:blank:]]*((?:.*[^:])?)(:::)(?:[[:blank:]]+(.*))?$/,
330
+ '::::' => /^[[:blank:]]*((?:.*[^:])?)(::::)(?:[[:blank:]]+(.*))?$/,
331
+ ';;' => /^[[:blank:]]*(.*)(;;)(?:[[:blank:]]+(.*))?$/
265
332
  },
266
333
  # ====
267
334
  #:example => /^={4,}$/,
@@ -269,13 +336,15 @@ module Asciidoctor
269
336
  # footnote:[text]
270
337
  # footnoteref:[id,text]
271
338
  # footnoteref:[id]
272
- :footnote_macro => /\\?(footnote|footnoteref):\[((?:\\\]|[^\]])*?)\]/m,
339
+ :footnote_macro => /\\?(footnote|footnoteref):\[((?:\\\]|[^\]])*?)\]/,
273
340
 
274
341
  # image::filename.png[Caption]
275
- :image_blk => /^image::(\S+?)\[(.*?)\]$/,
342
+ # video::http://youtube.com/12345[Cats vs Dogs]
343
+ :media_blk_macro => /^(image|video|audio)::(\S+?)\[((?:\\\]|[^\]])*?)\]$/,
276
344
 
277
345
  # image:filename.png[Alt Text]
278
- :image_macro => /\\?image:([^\[]+)(?:\[([^\]]*)\])/,
346
+ # image:filename.png[More [Alt\] Text] (alt text becomes "More [Alt] Text")
347
+ :image_macro => /\\?image:([^:\[]+)\[((?:\\\]|[^\]])*?)\]/,
279
348
 
280
349
  # indexterm:[Tigers,Big cats]
281
350
  # (((Tigers,Big cats)))
@@ -288,6 +357,9 @@ module Asciidoctor
288
357
  # whitespace at the beginning of the line
289
358
  :leading_blanks => /^([[:blank:]]*)/,
290
359
 
360
+ # leading parent directory references in path
361
+ :leading_parent_dirs => /^(?:\.\.\/)*/,
362
+
291
363
  # + From the Asciidoc User Guide: "A plus character preceded by at
292
364
  # least one space character at the end of a non-blank line forces
293
365
  # a line break. It generates a line break (br) tag for HTML outputs.
@@ -299,11 +371,15 @@ module Asciidoctor
299
371
 
300
372
  # inline link and some inline link macro
301
373
  # FIXME revisit!
302
- :link_inline => %r{(^|link:|\s|>|&lt;|[\(\)\[\]])(\\?(?:https?|ftp)://[^\s\[<]*[^\s.\)\[<])(?:\[((?:\\\]|[^\]])*?)\])?},
374
+ :link_inline => %r{(^|link:|\s|>|&lt;|[\(\)\[\]])(\\?(?:https?|ftp)://[^\s\[<]*[^\s.,\[<])(?:\[((?:\\\]|[^\]])*?)\])?},
303
375
 
304
376
  # inline link macro
305
377
  # link:path[label]
306
- :link_macro => /\\?link:([^\s\[]+)(?:\[((?:\\\]|[^\]])*?)\])/,
378
+ :link_macro => /\\?(?:link|mailto):([^\s\[]+)(?:\[((?:\\\]|[^\]])*?)\])/,
379
+
380
+ # inline email address
381
+ # doc.writer@asciidoc.org
382
+ :email_inline => /[\\>:]?\w[\w.%+-]*@[[:alnum:]][[:alnum:].-]*\.[[:alpha:]]{2,4}\b/,
307
383
 
308
384
  # ----
309
385
  #:listing => /^\-{4,}$/,
@@ -323,7 +399,8 @@ module Asciidoctor
323
399
  # A. Foo (upperalpha)
324
400
  # i. Foo (lowerroman)
325
401
  # I. Foo (upperroman)
326
- :olist => /^\s*(\d+\.|[a-z]\.|[ivx]+\)|\.{1,5}) +(.*)$/i,
402
+ # REVIEW leading space has already been stripped, so may not need in regex
403
+ :olist => /^[[:blank:]]*(\.{1,5}|\d+\.|[A-Za-z]\.|[IVXivx]+\))[[:blank:]]+(.*)$/,
327
404
 
328
405
  # ''' (ruler)
329
406
  # <<< (pagebreak)
@@ -394,8 +471,10 @@ module Asciidoctor
394
471
  # .Foo but not . Foo or ..Foo
395
472
  :blk_title => /^\.([^\s.].*)$/,
396
473
 
474
+ # matches double quoted text, capturing quote char and text (single-line)
397
475
  :dbl_quoted => /^("|)(.*)\1$/,
398
476
 
477
+ # matches double quoted text, capturing quote char and text (multi-line)
399
478
  :m_dbl_quoted => /^("|)(.*)\1$/m,
400
479
 
401
480
  # == Foo
@@ -417,11 +496,17 @@ module Asciidoctor
417
496
  :section_name => /^((?=.*\w+.*)[^.].*?)$/,
418
497
 
419
498
  # ====== || ------ || ~~~~~~ || ^^^^^^ || ++++++
420
- :section_underline => /^([=\-~^\+])+$/,
499
+ # TODO build from SECTION_LEVELS keys
500
+ :section_underline => /^(?:=|-|~|\^|\+)+$/,
501
+
502
+ # toc::[]
503
+ # toc::[levels=2]
504
+ :toc => /^toc::\[(.*?)\]$/,
421
505
 
422
506
  # * Foo (up to 5 consecutive asterisks)
423
507
  # - Foo
424
- :ulist => /^ \s* (- | \*{1,5}) \s+ (.*) $/x,
508
+ # REVIEW leading space has already been stripped, so may not need in regex
509
+ :ulist => /^[[:blank:]]*(-|\*{1,5})[[:blank:]]+(.*)$/,
425
510
 
426
511
  # inline xref macro
427
512
  # <<id,reftext>> (special characters have already been escaped, hence the entity references)
@@ -443,15 +528,16 @@ module Asciidoctor
443
528
  #:eval_expr => /^(true|false|("|'|)\{\w+(?:\-\w+)*\}\2|("|')[^\3]*\3|\-?\d+(?:\.\d+)*)[[:blank:]]*(==|!=|<=|>=|<|>)[[:blank:]]*(true|false|("|'|)\{\w+(?:\-\w+)*\}\6|("|')[^\7]*\7|\-?\d+(?:\.\d+)*)$/,
444
529
 
445
530
  # include::chapter1.ad[]
446
- :include_macro => /^\\?include::([^\[]+)\[\]$/,
531
+ # include::example.txt[lines=1;2;5..10]
532
+ :include_macro => /^\\?include::([^\[]+)\[(.*?)\]$/,
447
533
 
448
534
  # http://domain
449
535
  # https://domain
450
536
  # data:info
451
- :uri_sniff => /^[[:alpha:]][[:alnum:].+-]*:/i
452
- }
537
+ :uri_sniff => /^[[:alpha:]][[:alnum:].+-]*:/i,
453
538
 
454
- ADMONITION_STYLES = ['NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION']
539
+ :uri_encode_chars => /[^\w\-.!~*';:@=+$,()\[\]]/
540
+ }
455
541
 
456
542
  INTRINSICS = Hash.new{|h,k| STDERR.puts "Missing intrinsic: #{k.inspect}"; "{#{k}}"}.merge(
457
543
  {
@@ -493,6 +579,7 @@ module Asciidoctor
493
579
  }
494
580
 
495
581
  SPECIAL_CHARS_PATTERN = /[#{SPECIAL_CHARS.keys.join}]/
582
+ #SPECIAL_CHARS_PATTERN = /(?:<|>|&(?![[:alpha:]]{2,};|#[[:digit:]]{2,}+;|#x[[:alnum:]]{2,}+;))/
496
583
 
497
584
  # unconstrained quotes:: can appear anywhere
498
585
  # constrained quotes:: must be bordered by non-word characters
@@ -545,33 +632,29 @@ module Asciidoctor
545
632
  # order is significant
546
633
  REPLACEMENTS = [
547
634
  # (C)
548
- [/(^|[^\\])\(C\)/, '\1&#169;'],
635
+ [/\\?\(C\)/, '&#169;', :none],
549
636
  # (R)
550
- [/(^|[^\\])\(R\)/, '\1&#174;'],
637
+ [/\\?\(R\)/, '&#174;', :none],
551
638
  # (TM)
552
- [/(^|[^\\])\(TM\)/, '\1&#8482;'],
639
+ [/\\?\(TM\)/, '&#8482;', :none],
553
640
  # foo -- bar
554
- [/(^|\n| )--( |\n|$)/, '&#8201;&#8212;&#8201;'],
641
+ [/(^|\n| |\\)--( |\n|$)/, '&#8201;&#8212;&#8201;', :none],
555
642
  # foo--bar
556
- [/(\w)--(?=\w)/, '\1&#8212;'],
643
+ [/(\w)\\?--(?=\w)/, '&#8212;', :leading],
557
644
  # ellipsis
558
- [/(^|[^\\])\.\.\./, '\1&#8230;'],
645
+ [/\\?\.\.\./, '&#8230;', :leading],
559
646
  # single quotes
560
- [/(\w)'(\w)/, '\1&#8217;\2'],
561
- # escaped single quotes
562
- [/(\w)\\'(\w)/, '\1\'\2'],
647
+ [/(\w)\\?'(\w)/, '&#8217;', :bounding],
563
648
  # right arrow ->
564
- [/(^|[^\\])-&gt;/, '\1&#8594;'],
649
+ [/\\?-&gt;/, '&#8594;', :none],
565
650
  # right double arrow =>
566
- [/(^|[^\\])=&gt;/, '\1&#8658;'],
651
+ [/\\?=&gt;/, '&#8658;', :none],
567
652
  # left arrow <-
568
- [/(^|[^\\])&lt;-/, '\1&#8592;'],
653
+ [/\\?&lt;-/, '&#8592;', :none],
569
654
  # right left arrow <=
570
- [/(^|[^\\])&lt;=/, '\1&#8656;'],
571
- # and so on...
572
-
573
- # restore entities; TODO needs cleanup
574
- [/(^|[^\\])&amp;(#[a-z0-9]+;)/i, '\1&\2']
655
+ [/\\?&lt;=/, '&#8656;', :none],
656
+ # restore entities
657
+ [/\\?(&)amp;((?:[[:alpha:]]+|#[[:digit:]]+|#x[[:alnum:]]+);)/, '', :bounding]
575
658
  ]
576
659
 
577
660
  # Public: Parse the AsciiDoc source input into an Asciidoctor::Document
@@ -587,6 +670,10 @@ module Asciidoctor
587
670
  #
588
671
  # returns the Asciidoctor::Document
589
672
  def self.load(input, options = {}, &block)
673
+ if (monitor = options.fetch(:monitor, false))
674
+ start = Time.now
675
+ end
676
+
590
677
  lines = nil
591
678
  if input.is_a?(File)
592
679
  options[:attributes] ||= {}
@@ -612,7 +699,19 @@ module Asciidoctor
612
699
  raise "Unsupported input type: #{input.class}"
613
700
  end
614
701
 
615
- Document.new(lines, options, &block)
702
+ if monitor
703
+ read_time = Time.now - start
704
+ start = Time.now
705
+ end
706
+
707
+ doc = Document.new(lines, options, &block)
708
+ if monitor
709
+ parse_time = Time.now - start
710
+ monitor[:read] = read_time
711
+ monitor[:parse] = parse_time
712
+ monitor[:load] = read_time + parse_time
713
+ end
714
+ doc
616
715
  end
617
716
 
618
717
  # Public: Parse the contents of the AsciiDoc source file into an Asciidoctor::Document
@@ -658,18 +757,22 @@ module Asciidoctor
658
757
  # see Asciidoctor::Document#initialize for details
659
758
  # block - a callback block for handling include::[] directives
660
759
  #
661
- # returns nothing if the rendered output String is written to a file,
662
- # otherwise the rendered output String is returned
760
+ # returns the Document object if the rendered result String is written to a
761
+ # file, otherwise the rendered result String
663
762
  def self.render(input, options = {}, &block)
664
763
  in_place = options.delete(:in_place) || false
665
764
  to_file = options.delete(:to_file)
666
765
  to_dir = options.delete(:to_dir)
667
766
  mkdirs = options.delete(:mkdirs) || false
767
+ monitor = options.fetch(:monitor, false)
668
768
 
669
769
  write_in_place = in_place && input.is_a?(File)
670
770
  write_to_target = to_file || to_dir
771
+ stream_output = !to_file.nil? && to_file.respond_to?(:write)
671
772
 
672
- raise ArgumentError, ':in_place with input file must not accompany :to_dir or :to_file' if write_in_place && write_to_target
773
+ if write_in_place && write_to_target
774
+ raise ArgumentError, 'the option :in_place cannot be used with either the :to_dir or :to_file option'
775
+ end
673
776
 
674
777
  if !options.has_key?(:header_footer) && (write_in_place || write_to_target)
675
778
  options[:header_footer] = true
@@ -677,21 +780,26 @@ module Asciidoctor
677
780
 
678
781
  doc = Asciidoctor.load(input, options, &block)
679
782
 
680
- if write_in_place
783
+ if to_file == '/dev/null'
784
+ return doc
785
+ elsif write_in_place
681
786
  to_file = File.join(File.dirname(input.path), "#{doc.attributes['docname']}#{doc.attributes['outfilesuffix']}")
682
- elsif write_to_target
787
+ elsif !stream_output && write_to_target
788
+ working_dir = options.has_key?(:base_dir) ? File.expand_path(opts[:base_dir]) : File.expand_path(Dir.pwd)
789
+ # QUESTION should the jail be the working_dir or doc.base_dir???
790
+ jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
683
791
  if to_dir
684
- to_dir = doc.normalize_asset_path(to_dir, 'to_dir', false)
792
+ to_dir = doc.normalize_system_path(to_dir, working_dir, jail, :target_name => 'to_dir', :recover => false)
685
793
  if to_file
686
- # normalize again, to_file could have dirty bits
687
- to_file = doc.normalize_asset_path(File.expand_path(to_file, to_dir), 'to_file', false)
794
+ to_file = doc.normalize_system_path(to_file, to_dir, nil, :target_name => 'to_dir', :recover => false)
688
795
  # reestablish to_dir as the final target directory (in the case to_file had directory segments)
689
796
  to_dir = File.dirname(to_file)
690
797
  else
691
798
  to_file = File.join(to_dir, "#{doc.attributes['docname']}#{doc.attributes['outfilesuffix']}")
692
799
  end
693
800
  elsif to_file
694
- to_file = doc.normalize_asset_path(to_file, 'to_file', false)
801
+ to_file = doc.normalize_system_path(to_file, working_dir, jail, :target_name => 'to_dir', :recover => false)
802
+ # establish to_dir as the final target directory (in the case to_file had directory segments)
695
803
  to_dir = File.dirname(to_file)
696
804
  end
697
805
 
@@ -705,11 +813,46 @@ module Asciidoctor
705
813
  end
706
814
  end
707
815
 
816
+ start = Time.now if monitor
817
+ output = doc.render
818
+
819
+ if monitor
820
+ render_time = Time.now - start
821
+ monitor[:render] = render_time
822
+ monitor[:load_render] = monitor[:load] + render_time
823
+ end
824
+
708
825
  if to_file
709
- File.open(to_file, 'w') {|file| file.write doc.render }
710
- nil
826
+ start = Time.now if monitor
827
+ if stream_output
828
+ to_file.write output.rstrip
829
+ # ensure there's a trailing endline
830
+ to_file.write "\n"
831
+ else
832
+ File.open(to_file, 'w') {|file| file.write output }
833
+ # these assignments primarily for testing, diagnostics or reporting
834
+ doc.attributes['outfile'] = outfile = File.expand_path(to_file)
835
+ doc.attributes['outdir'] = File.dirname(outfile)
836
+ end
837
+ if monitor
838
+ write_time = Time.now - start
839
+ monitor[:write] = write_time
840
+ monitor[:total] = monitor[:load_render] + write_time
841
+ end
842
+
843
+ # NOTE document cannot control this behavior if safe >= SafeMode::SERVER
844
+ if !stream_output && doc.attr?('copycss') &&
845
+ doc.attr?('linkcss') && DEFAULT_STYLESHEET_KEYS.include?(doc.attr('stylesheet'))
846
+ Helpers.require_library 'fileutils'
847
+ outdir = doc.attr('outdir')
848
+ stylesdir = doc.normalize_system_path(doc.attr('stylesdir'), outdir,
849
+ doc.safe >= SafeMode::SAFE ? outdir : nil)
850
+ FileUtils.mkdir_p stylesdir
851
+ FileUtils.cp DEFAULT_STYLESHEET_PATH, stylesdir, :preserve => true
852
+ end
853
+ doc
711
854
  else
712
- doc.render
855
+ output
713
856
  end
714
857
  end
715
858
 
@@ -721,8 +864,8 @@ module Asciidoctor
721
864
  # see Asciidoctor::Document#initialize for details
722
865
  # block - a callback block for handling include::[] directives
723
866
  #
724
- # returns nothing if the rendered output String is written to a file,
725
- # otherwise the rendered output String is returned
867
+ # returns the Document object if the rendered result String is written to a
868
+ # file, otherwise the rendered result String
726
869
  def self.render_file(filename, options = {}, &block)
727
870
  Asciidoctor.render(File.new(filename), options, &block)
728
871
  end
@@ -750,10 +893,10 @@ module Asciidoctor
750
893
  require 'asciidoctor/block'
751
894
  require 'asciidoctor/callouts'
752
895
  require 'asciidoctor/document'
753
- #require 'asciidoctor/errors'
754
896
  require 'asciidoctor/inline'
755
897
  require 'asciidoctor/lexer'
756
898
  require 'asciidoctor/list_item'
899
+ require 'asciidoctor/path_resolver'
757
900
  require 'asciidoctor/reader'
758
901
  require 'asciidoctor/renderer'
759
902
  require 'asciidoctor/section'