asciidoctor 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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