asciidoctor 0.0.1 → 0.0.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.

@@ -9,6 +9,9 @@ class Asciidoctor::ListItem
9
9
  # Public: Get/Set the String list item anchor name.
10
10
  attr_accessor :anchor
11
11
 
12
+ # Public: Get/Set the Integer list level (for nesting list elements).
13
+ attr_accessor :level
14
+
12
15
  # Public: Initialize an Asciidoctor::ListItem object.
13
16
  #
14
17
  # content - the String content (default '')
@@ -16,4 +19,31 @@ class Asciidoctor::ListItem
16
19
  @content = content
17
20
  @blocks = []
18
21
  end
22
+
23
+ def render
24
+ output = "<li><p>#{content} (HTMLIFY) "
25
+ output += blocks.map{|block| block.render}.join
26
+ output += "</p></li>"
27
+ end
28
+
29
+ def splain(parent_level = 0)
30
+ parent_level += 1
31
+ Asciidoctor.puts_indented(parent_level, "List Item anchor: #{anchor}") unless self.anchor.nil?
32
+ Asciidoctor.puts_indented(parent_level, "Content: #{content}") unless self.content.nil?
33
+
34
+ Asciidoctor.puts_indented(parent_level, "Blocks: #{@blocks.count}")
35
+
36
+ if @blocks.any?
37
+ Asciidoctor.puts_indented(parent_level, "Blocks content (#{@blocks.count}):")
38
+ @blocks.each_with_index do |block, i|
39
+ Asciidoctor.puts_indented(parent_level, "v" * (60 - parent_level*2))
40
+ Asciidoctor.puts_indented(parent_level, "Block ##{i} is a #{block.class}")
41
+ Asciidoctor.puts_indented(parent_level, "Name is #{block.name rescue 'n/a'}")
42
+ Asciidoctor.puts_indented(parent_level, "=" * 40)
43
+ block.splain(parent_level) if block.respond_to? :splain
44
+ Asciidoctor.puts_indented(parent_level, "^" * (60 - parent_level*2))
45
+ end
46
+ end
47
+ nil
48
+ end
19
49
  end
@@ -0,0 +1,236 @@
1
+ # Public: Methods for retrieving lines from Asciidoc documents
2
+ class Asciidoctor::Reader
3
+
4
+ include Asciidoctor
5
+
6
+ # Public: Get the String document source.
7
+ attr_reader :source
8
+
9
+ # Public: Get the Hash of defines
10
+ attr_reader :defines
11
+
12
+ attr_reader :references
13
+
14
+ # Public: Convert a string to a legal attribute name.
15
+ #
16
+ # name - The String holding the Asciidoc attribute name.
17
+ #
18
+ # Returns a String with the legal name.
19
+ #
20
+ # Examples
21
+ #
22
+ # sanitize_attribute_name('Foo Bar')
23
+ # => 'foobar'
24
+ #
25
+ # sanitize_attribute_name('foo')
26
+ # => 'foo'
27
+ #
28
+ # sanitize_attribute_name('Foo 3 #-Billy')
29
+ # => 'foo3-billy'
30
+ def sanitize_attribute_name(name)
31
+ name.gsub(/[^\w\-_]/, '').downcase
32
+ end
33
+
34
+ # Public: Initialize the Reader object.
35
+ #
36
+ # data - The Array of Strings holding the Asciidoc source document.
37
+ # block - A block that can be used to retrieve external Asciidoc
38
+ # data to include in this document.
39
+ #
40
+ # Examples
41
+ #
42
+ # data = File.readlines(filename)
43
+ # reader = Asciidoctor::Reader.new(data)
44
+ def initialize(data = [], &block)
45
+ raw_source = []
46
+ @defines = {}
47
+ @references = {}
48
+
49
+ include_regexp = /^include::([^\[]+)\[\]\s*\n?\z/
50
+
51
+ data.each do |line|
52
+ if inc = line.match(include_regexp)
53
+ if block_given?
54
+ raw_source << yield(inc[1])
55
+ else
56
+ raw_source.concat(File.readlines(inc[1]))
57
+ end
58
+ else
59
+ raw_source << line
60
+ end
61
+ end
62
+
63
+ ifdef_regexp = /^(ifdef|ifndef)::([^\[]+)\[\]/
64
+ endif_regexp = /^endif::/
65
+ defattr_regexp = /^:([^:!]+):\s*(.*)\s*$/
66
+ delete_attr_regexp = /^:([^:]+)!:\s*$/
67
+ conditional_regexp = /^\s*\{([^\?]+)\?\s*([^\}]+)\s*\}/
68
+
69
+ skip_to = nil
70
+ continuing_value = nil
71
+ continuing_key = nil
72
+ @lines = []
73
+ raw_source.each do |line|
74
+ if skip_to
75
+ skip_to = nil if line.match(skip_to)
76
+ elsif continuing_value
77
+ close_continue = false
78
+ # Lines that start with whitespace and end with a '+' are
79
+ # a continuation, so gobble them up into `value`
80
+ if match = line.match(/\s+(.+)\s+\+\s*$/)
81
+ continuing_value += match[1]
82
+ elsif match = line.match(/\s+(.+)/)
83
+ # If this continued line doesn't end with a +, then this
84
+ # is the end of the continuation, no matter what the next
85
+ # line does.
86
+ continuing_value += match[1]
87
+ close_continue = true
88
+ else
89
+ # If this line doesn't start with whitespace, then it's
90
+ # not a valid continuation line, so push it back for processing
91
+ close_continue = true
92
+ raw_source.unshift(line)
93
+ end
94
+ if close_continue
95
+ @defines[continuing_key] = continuing_value
96
+ continuing_key = nil
97
+ continuing_value = nil
98
+ end
99
+ elsif match = line.match(ifdef_regexp)
100
+ attr = match[2]
101
+ skip = case match[1]
102
+ when 'ifdef'; !@defines.has_key?(attr)
103
+ when 'ifndef'; @defines.has_key?(attr)
104
+ end
105
+ skip_to = /^endif::#{attr}\[\]\s*\n/ if skip
106
+ elsif match = line.match(defattr_regexp)
107
+ key = sanitize_attribute_name(match[1])
108
+ value = match[2]
109
+ if match = value.match(Asciidoctor::REGEXP[:attr_continue])
110
+ # attribute value continuation line; grab lines until we run out
111
+ # of continuation lines
112
+ continuing_key = key
113
+ continuing_value = match[1] # strip off the spaces and +
114
+ Asciidoctor.debug "continuing key: #{continuing_key} with partial value: '#{continuing_value}'"
115
+ else
116
+ @defines[key] = value
117
+ Asciidoctor.debug "Defines[#{key}] is '#{value}'"
118
+ end
119
+ elsif match = line.match(delete_attr_regexp)
120
+ key = sanitize_attribute_name(match[1])
121
+ @defines.delete(key)
122
+ elsif !line.match(endif_regexp)
123
+ while match = line.match(conditional_regexp)
124
+ value = @defines.has_key?(match[1]) ? match[2] : ''
125
+ line.sub!(conditional_regexp, value)
126
+ end
127
+ @lines << line unless line.match(REGEXP[:comment])
128
+ end
129
+ end
130
+
131
+ # Process bibliography references, so they're available when text
132
+ # before the reference is being rendered.
133
+ @lines.each do |line|
134
+ if biblio = line.match(REGEXP[:biblio])
135
+ @references[biblio[1]] = "[#{biblio[1]}]"
136
+ end
137
+ end
138
+
139
+ Asciidoctor.debug "About to leave Reader#init, and references is #{@references.inspect}"
140
+ @source = @lines.join
141
+ Asciidoctor.debug "Leaving Reader#init, and I have #{@lines.count} lines"
142
+ Asciidoctor.debug "Also, has_lines? is #{self.has_lines?}"
143
+ end
144
+
145
+ # Public: Check whether there are any lines left to read.
146
+ #
147
+ # Returns true if @lines.any? is true, or false otherwise.
148
+ def has_lines?
149
+ @lines.any?
150
+ end
151
+
152
+ # Private: Strip off leading blank lines in the Array of lines.
153
+ #
154
+ # Returns nil.
155
+ #
156
+ # Examples
157
+ #
158
+ # @lines
159
+ # => ["\n", "\t\n", "Foo\n", "Bar\n", "\n"]
160
+ #
161
+ # skip_blank
162
+ # => nil
163
+ #
164
+ # @lines
165
+ # => ["Foo\n", "Bar\n"]
166
+ def skip_blank
167
+ while @lines.any? && @lines.first.strip.empty?
168
+ @lines.shift
169
+ end
170
+
171
+ nil
172
+ end
173
+
174
+ # Public: Get the next line of source data. Consumes the line returned.
175
+ #
176
+ # Returns the String of the next line of the source data if data is present.
177
+ # Returns nil if there is no more data.
178
+ def get_line
179
+ @lines.shift
180
+ end
181
+
182
+ # Public: Get the next line of source data. Does not consume the line returned.
183
+ #
184
+ # Returns a String dup of the next line of the source data if data is present.
185
+ # Returns nil if there is no more data.
186
+ def peek_line
187
+ @lines.first.dup if @lines.first
188
+ end
189
+
190
+ # Public: Push String `line` onto queue of source data lines, unless `line` is nil.
191
+ #
192
+ # Returns nil
193
+ def unshift(line)
194
+ @lines.unshift(line) if line
195
+ nil
196
+ end
197
+
198
+ # Public: Return all the lines from `@lines` until we (1) run out them,
199
+ # (2) find a blank line with :break_on_blank_lines => true, or (3) find
200
+ # a line for which the given block evals to true.
201
+ #
202
+ # options - an optional Hash of processing options:
203
+ # * :break_on_blank_lines may be used to specify to break on
204
+ # blank lines
205
+ # * :preserve_last_line may be used to specify that the String
206
+ # causing the method to stop processing lines should be
207
+ # pushed back onto the `lines` Array.
208
+ #
209
+ # Returns the Array of lines forming the next segment.
210
+ #
211
+ # Examples
212
+ #
213
+ # reader = Reader.new ["First paragraph\n", "Second paragraph\n",
214
+ # "Open block\n", "\n", "Can have blank lines\n",
215
+ # "--\n", "\n", "In a different segment\n"]
216
+ #
217
+ # reader.grab_lines_until
218
+ # => ["First paragraph\n", "Second paragraph\n", "Open block\n"]
219
+ def grab_lines_until(options = {}, &block)
220
+ buffer = []
221
+
222
+ while (this_line = self.get_line)
223
+ Asciidoctor.debug "Processing line: '#{this_line}'"
224
+ finis ||= true if options[:break_on_blank_lines] && this_line.strip.empty?
225
+ finis ||= true if block && value = yield(this_line)
226
+ if finis
227
+ self.unshift(this_line) if options[:preserve_last_line]
228
+ break
229
+ end
230
+
231
+ buffer << this_line
232
+ end
233
+ buffer
234
+ end
235
+
236
+ end
@@ -164,9 +164,9 @@ class SectionUlistTemplate < BaseTemplate
164
164
  @template ||= ERB.new <<-EOF
165
165
  <div class='ulist'>
166
166
  <ul>
167
- <% content.each do |li| %>
168
- <li><p><%= li %></p></li>
169
- <% end %>
167
+ <% content.each do |li| %>
168
+ <%= li %>
169
+ <% end %>
170
170
  </ul>
171
171
  </div>
172
172
  EOF
@@ -14,6 +14,26 @@ class Asciidoctor::Renderer
14
14
  @views[view] = tc.new
15
15
  end
16
16
 
17
+ # If user passed in a template dir, let them override our base templates
18
+ if template_dir = options.delete(:template_dir)
19
+ Asciidoctor.debug "Views going in are like so:"
20
+ @views.each_pair do |k, v|
21
+ Asciidoctor.debug "#{k}: #{v}"
22
+ end
23
+ Asciidoctor.debug "="*60
24
+ # Grab the files in the top level of the directory (we're not traversing)
25
+ files = Dir.glob(File.join(template_dir, '*')).select{|f| File.stat(f).file?}
26
+ files.inject(@views) do |view_hash, view|
27
+ name = File.basename(view).split('.').first
28
+ view_hash.merge!(name => Tilt.new(view, nil, :trim => '<>'))
29
+ end
30
+ Asciidoctor.debug "Views are now like so:"
31
+ @views.each_pair do |k, v|
32
+ Asciidoctor.debug "#{k}: #{v}"
33
+ end
34
+ Asciidoctor.debug "="*60
35
+ end
36
+
17
37
  @render_stack = []
18
38
  end
19
39
 
@@ -37,8 +57,8 @@ class Asciidoctor::Renderer
37
57
  STDERR.puts "Rendering:"
38
58
  @render_stack.each do |stack_view, stack_obj|
39
59
  obj_info = case stack_obj
40
- when Section; "SECTION #{stack_obj.name}"
41
- when Block;
60
+ when Asciidoctor::Section; "SECTION #{stack_obj.name}"
61
+ when Asciidoctor::Block;
42
62
  if stack_obj.context == :dlist
43
63
  dt_list = stack_obj.buffer.map{|dt,dd| dt.content.strip}.join(', ')
44
64
  "BLOCK :dlist (#{dt_list})"
@@ -1,3 +1,3 @@
1
1
  module Asciidoctor
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ context "Attributes" do
4
+ test "creates an attribute" do
5
+ doc = document_from_string(":frog: Tanglefoot")
6
+ assert_equal doc.defines['frog'], 'Tanglefoot'
7
+ end
8
+
9
+ test "deletes an attribute" do
10
+ doc = document_from_string(":frog: Tanglefoot\n:frog!:")
11
+ assert_equal nil, doc.defines['frog']
12
+ end
13
+
14
+ test "doesn't choke when deleting a non-existing attribute" do
15
+ doc = document_from_string(":frog!:")
16
+ assert_equal nil, doc.defines['frog']
17
+ end
18
+
19
+ test "render properly with simple names" do
20
+ html = render_string(":frog: Tanglefoot\nYo, {frog}!")
21
+ result = Nokogiri::HTML(html)
22
+ assert_equal 'Yo, Tanglefoot!', result.css("p").first.content.strip
23
+ end
24
+
25
+ test "convert multi-word names and render" do
26
+ html = render_string("Main Header\n===========\n:My frog: Tanglefoot\nYo, {myfrog}!")
27
+ result = Nokogiri::HTML(html)
28
+ assert_equal 'Yo, Tanglefoot!', result.css("p").first.content.strip
29
+ end
30
+
31
+ test "ignores lines with bad attributes" do
32
+ html = render_string("This is\nblah blah {foobarbaz}\nall there is.")
33
+ result = Nokogiri::HTML(html)
34
+ assert_no_match /blah blah/m, result.css("p").first.content.strip
35
+ end
36
+
37
+ # See above - AsciiDoc says we're supposed to delete lines with bad
38
+ # attribute refs in them. AsciiDoc is strange.
39
+ #
40
+ # test "Unknowns" do
41
+ # html = render_string("Look, a {gobbledygook}")
42
+ # result = Nokogiri::HTML(html)
43
+ # assert_equal("Look, a {gobbledygook}", result.css("p").first.content.strip)
44
+ # end
45
+
46
+ test "substitutes inside unordered list items" do
47
+ html = render_string(":foo: bar\n* snort at the {foo}\n* yawn")
48
+ result = Nokogiri::HTML(html)
49
+ assert_match /snort at the bar/, result.css("li").first.content.strip
50
+ end
51
+
52
+ test "renders attribute until it's deleted" do
53
+ pending "Not working yet (will require adding element-specific attributes or early attr substitution during parsing)"
54
+ # html = render_string(":foo: bar\nCrossing the {foo}\n\n:foo!:\nBelly up to the {foo}")
55
+ # result = Nokogiri::HTML(html)
56
+ # assert_match /Crossing the bar/, result.css("p").first.content.strip
57
+ # assert_no_match /Belly up to the bar/, result.css("p").last.content.strip
58
+ end
59
+
60
+ test "doesn't disturb attribute-looking things escaped with backslash" do
61
+ html = render_string(":foo: bar\nThis is a \\{foo} day.")
62
+ result = Nokogiri::HTML(html)
63
+ assert_equal 'This is a {foo} day.', result.css('p').first.content.strip
64
+ end
65
+
66
+ test "doesn't disturb attribute-looking things escaped with literals" do
67
+ html = render_string(":foo: bar\nThis is a +++{foo}+++ day.")
68
+ result = Nokogiri::HTML(html)
69
+ assert_equal 'This is a {foo} day.', result.css('p').first.content.strip
70
+ end
71
+
72
+ test "doesn't substitute attributes inside code blocks" do
73
+ pending "whut?"
74
+ end
75
+
76
+ test "doesn't substitute attributes inside literal blocks" do
77
+ pending "whut?"
78
+ end
79
+
80
+ test "Intrinsics" do
81
+ Asciidoctor::INTRINSICS.each_pair do |key, value|
82
+ html = render_string("Look, a {#{key}} is here")
83
+ result = Nokogiri::HTML(html)
84
+ assert_equal("Look, a #{value} is here", result.css("p").first.content.strip)
85
+ end
86
+ end
87
+
88
+ end
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  class DocumentTest < Test::Unit::TestCase
4
4
  # setup for test
5
5
  def setup
6
- @doc = Asciidoctor::Document.new(File.readlines(sample_doc_path(:asciidoc_index)))
6
+ @doc = example_document(:asciidoc_index)
7
7
  end
8
8
 
9
9
  def test_title
@@ -11,13 +11,7 @@ class DocumentTest < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
  def test_with_no_title
14
- d = Asciidoctor::Document.new("Snorf")
14
+ d = Asciidoctor::Document.new(["Snorf"])
15
15
  assert_nil d.title
16
16
  end
17
-
18
- def test_is_section_heading
19
- assert @doc.send(:is_section_heading?, "AsciiDoc Home Page", "==================")
20
- assert @doc.send(:is_section_heading?, "=== AsciiDoc Home Page")
21
- end
22
-
23
17
  end