asciidoctor 0.1.4 → 1.5.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +209 -25
- data/{LICENSE → LICENSE.adoc} +4 -3
- data/README.adoc +392 -395
- data/Rakefile +94 -137
- data/benchmark/benchmark.rb +127 -0
- data/benchmark/sample-data/mdbasics.adoc +334 -0
- data/bin/asciidoctor +5 -8
- data/bin/asciidoctor-safe +4 -8
- data/compat/asciidoc.conf +78 -11
- data/compat/font-awesome-3-compat.css +397 -0
- data/data/stylesheets/asciidoctor-default.css +399 -0
- data/data/stylesheets/coderay-asciidoctor.css +89 -0
- data/features/open_block.feature +92 -0
- data/features/pass_block.feature +66 -0
- data/features/step_definitions.rb +42 -0
- data/features/text_formatting.feature +55 -0
- data/features/xref.feature +116 -0
- data/lib/asciidoctor.rb +1155 -605
- data/lib/asciidoctor/abstract_block.rb +157 -71
- data/lib/asciidoctor/abstract_node.rb +150 -93
- data/lib/asciidoctor/attribute_list.rb +85 -90
- data/lib/asciidoctor/block.rb +51 -24
- data/lib/asciidoctor/callouts.rb +4 -7
- data/lib/asciidoctor/cli.rb +3 -0
- data/lib/asciidoctor/cli/invoker.rb +86 -76
- data/lib/asciidoctor/cli/options.rb +111 -61
- data/lib/asciidoctor/converter.rb +232 -0
- data/lib/asciidoctor/converter/base.rb +58 -0
- data/lib/asciidoctor/converter/composite.rb +66 -0
- data/lib/asciidoctor/converter/docbook45.rb +94 -0
- data/lib/asciidoctor/converter/docbook5.rb +684 -0
- data/lib/asciidoctor/converter/factory.rb +225 -0
- data/lib/asciidoctor/converter/html5.rb +1081 -0
- data/lib/asciidoctor/converter/template.rb +296 -0
- data/lib/asciidoctor/core_ext.rb +7 -0
- data/lib/asciidoctor/core_ext/object/nil_or_empty.rb +23 -0
- data/lib/asciidoctor/core_ext/string/chr.rb +6 -0
- data/lib/asciidoctor/core_ext/symbol/length.rb +6 -0
- data/lib/asciidoctor/document.rb +590 -304
- data/lib/asciidoctor/extensions.rb +1100 -308
- data/lib/asciidoctor/helpers.rb +109 -46
- data/lib/asciidoctor/inline.rb +16 -9
- data/lib/asciidoctor/list.rb +23 -15
- data/lib/asciidoctor/opal_ext.rb +4 -0
- data/lib/asciidoctor/opal_ext/comparable.rb +38 -0
- data/lib/asciidoctor/opal_ext/dir.rb +13 -0
- data/lib/asciidoctor/opal_ext/error.rb +2 -0
- data/lib/asciidoctor/opal_ext/file.rb +125 -0
- data/lib/asciidoctor/{lexer.rb → parser.rb} +646 -455
- data/lib/asciidoctor/path_resolver.rb +141 -77
- data/lib/asciidoctor/reader.rb +257 -187
- data/lib/asciidoctor/section.rb +12 -16
- data/lib/asciidoctor/stylesheets.rb +91 -0
- data/lib/asciidoctor/substitutors.rb +1548 -0
- data/lib/asciidoctor/table.rb +73 -57
- data/lib/asciidoctor/timings.rb +39 -0
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +22 -14
- data/man/asciidoctor.adoc +18 -10
- data/test/attributes_test.rb +314 -14
- data/test/blocks_test.rb +763 -118
- data/test/converter_test.rb +352 -0
- data/test/document_test.rb +518 -199
- data/test/extensions_test.rb +273 -103
- data/test/fixtures/asciidoc_index.txt +27 -13
- data/test/fixtures/basic-docinfo.xml +1 -1
- data/test/fixtures/chapter-a.adoc +3 -0
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
- data/test/fixtures/docinfo.xml +1 -1
- data/test/fixtures/include-file.asciidoc +2 -0
- data/test/fixtures/master.adoc +5 -0
- data/test/invoker_test.rb +173 -61
- data/test/links_test.rb +97 -21
- data/test/lists_test.rb +181 -22
- data/test/options_test.rb +86 -2
- data/test/paragraphs_test.rb +47 -5
- data/test/{lexer_test.rb → parser_test.rb} +128 -57
- data/test/paths_test.rb +36 -1
- data/test/preamble_test.rb +25 -17
- data/test/reader_test.rb +404 -249
- data/test/sections_test.rb +623 -58
- data/test/substitutions_test.rb +609 -132
- data/test/tables_test.rb +198 -24
- data/test/test_helper.rb +101 -31
- data/test/text_test.rb +88 -31
- metadata +160 -64
- data/Gemfile +0 -12
- data/Guardfile +0 -18
- data/asciidoctor.gemspec +0 -143
- data/lib/asciidoctor/backends/_stylesheets.rb +0 -466
- data/lib/asciidoctor/backends/base_template.rb +0 -114
- data/lib/asciidoctor/backends/docbook45.rb +0 -774
- data/lib/asciidoctor/backends/docbook5.rb +0 -103
- data/lib/asciidoctor/backends/html5.rb +0 -1214
- data/lib/asciidoctor/renderer.rb +0 -259
- data/lib/asciidoctor/substituters.rb +0 -1083
- data/test/fixtures/asciidoc.txt +0 -105
- data/test/fixtures/ascshort.txt +0 -32
- data/test/fixtures/list_elements.asciidoc +0 -10
- data/test/renderer_test.rb +0 -162
data/lib/asciidoctor/helpers.rb
CHANGED
@@ -1,47 +1,124 @@
|
|
1
1
|
module Asciidoctor
|
2
2
|
module Helpers
|
3
|
-
# Internal:
|
4
|
-
#
|
3
|
+
# Internal: Require the specified library using Kernel#require.
|
4
|
+
#
|
5
|
+
# Attempts to load the library specified in the first argument using the
|
6
|
+
# Kernel#require. Rescues the LoadError if the library is not available and
|
7
|
+
# passes a message to Kernel#fail to communicate to the user that processing
|
8
|
+
# is being aborted. If a gem_name is specified, the failure message
|
9
|
+
# communicates that a required gem is not installed.
|
5
10
|
#
|
6
11
|
# name - the String name of the library to require.
|
12
|
+
# gem - a Boolean that indicates whether this library is provided by a RubyGem,
|
13
|
+
# or the String name of the RubyGem if it differs from the library name
|
14
|
+
# (default: true)
|
7
15
|
#
|
8
|
-
# returns
|
9
|
-
#
|
10
|
-
def self.require_library
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
# returns the return value of Kernel#require if the library is available,
|
17
|
+
# otherwise Kernel#fail is called with an appropriate message.
|
18
|
+
def self.require_library name, gem = true
|
19
|
+
require name
|
20
|
+
rescue ::LoadError => e
|
21
|
+
if gem
|
22
|
+
fail %(asciidoctor: FAILED: required gem '#{gem == true ? name : gem}' is not installed. Processing aborted.)
|
23
|
+
else
|
24
|
+
fail %(asciidoctor: FAILED: #{e.message.chomp '.'}. Processing aborted.)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Normalize the data to prepare for parsing
|
29
|
+
#
|
30
|
+
# Delegates to Helpers#normalize_lines_from_string if data is a String.
|
31
|
+
# Delegates to Helpers#normalize_lines_array if data is a String Array.
|
32
|
+
#
|
33
|
+
# returns a String Array of normalized lines
|
34
|
+
def self.normalize_lines data
|
35
|
+
data.class == ::String ? (normalize_lines_from_string data) : (normalize_lines_array data)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Normalize the array of lines to prepare them for parsing
|
39
|
+
#
|
40
|
+
# Force encodes the data to UTF-8 and removes trailing whitespace from each line.
|
41
|
+
#
|
42
|
+
# If a BOM is present at the beginning of the data, a best attempt
|
43
|
+
# is made to encode from the specified encoding to UTF-8.
|
44
|
+
#
|
45
|
+
# data - a String Array of lines to normalize
|
46
|
+
#
|
47
|
+
# returns a String Array of normalized lines
|
48
|
+
def self.normalize_lines_array data
|
49
|
+
return [] if data.empty?
|
50
|
+
|
51
|
+
# NOTE if data encoding is UTF-*, we only need 0..1
|
52
|
+
leading_bytes = (first_line = data[0])[0..2].bytes.to_a
|
53
|
+
if COERCE_ENCODING
|
54
|
+
utf8 = ::Encoding::UTF_8
|
55
|
+
if (leading_2_bytes = leading_bytes[0..1]) == BOM_BYTES_UTF_16LE
|
56
|
+
# Ruby messes up trailing whitespace on UTF-16LE, so take a different route
|
57
|
+
return ((data.join.force_encoding ::Encoding::UTF_16LE)[1..-1].encode utf8).lines.map {|line| line.rstrip }
|
58
|
+
elsif leading_2_bytes == BOM_BYTES_UTF_16BE
|
59
|
+
data[0] = (first_line.force_encoding ::Encoding::UTF_16BE)[1..-1]
|
60
|
+
return data.map {|line| "#{((line.force_encoding ::Encoding::UTF_16BE).encode utf8).rstrip}" }
|
61
|
+
elsif leading_bytes[0..2] == BOM_BYTES_UTF_8
|
62
|
+
data[0] = (first_line.force_encoding utf8)[1..-1]
|
63
|
+
end
|
64
|
+
|
65
|
+
data.map {|line| line.encoding == utf8 ? line.rstrip : (line.force_encoding utf8).rstrip }
|
66
|
+
else
|
67
|
+
# Ruby 1.8 has no built-in re-encoding, so no point in removing the UTF-16 BOMs
|
68
|
+
if leading_bytes == BOM_BYTES_UTF_8
|
69
|
+
data[0] = first_line[3..-1]
|
19
70
|
end
|
71
|
+
data.map {|line| line.rstrip }
|
20
72
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Normalize the String and split into lines to prepare them for parsing
|
76
|
+
#
|
77
|
+
# Force encodes the data to UTF-8 and removes trailing whitespace from each line.
|
78
|
+
# Converts the data to a String Array.
|
79
|
+
#
|
80
|
+
# If a BOM is present at the beginning of the data, a best attempt
|
81
|
+
# is made to encode from the specified encoding to UTF-8.
|
82
|
+
#
|
83
|
+
# data - a String of lines to normalize
|
84
|
+
#
|
85
|
+
# returns a String Array of normalized lines
|
86
|
+
def self.normalize_lines_from_string data
|
87
|
+
return [] if data.nil_or_empty?
|
88
|
+
|
89
|
+
if COERCE_ENCODING
|
90
|
+
utf8 = ::Encoding::UTF_8
|
91
|
+
# NOTE if data encoding is UTF-*, we only need 0..1
|
92
|
+
leading_bytes = data[0..2].bytes.to_a
|
93
|
+
if (leading_2_bytes = leading_bytes[0..1]) == BOM_BYTES_UTF_16LE
|
94
|
+
data = (data.force_encoding ::Encoding::UTF_16LE)[1..-1].encode utf8
|
95
|
+
elsif leading_2_bytes == BOM_BYTES_UTF_16BE
|
96
|
+
data = (data.force_encoding ::Encoding::UTF_16BE)[1..-1].encode utf8
|
97
|
+
elsif leading_bytes[0..2] == BOM_BYTES_UTF_8
|
98
|
+
data = data.encoding == utf8 ? data[1..-1] : (data.force_encoding utf8)[1..-1]
|
26
99
|
else
|
27
|
-
|
100
|
+
data = data.force_encoding utf8 unless data.encoding == utf8
|
101
|
+
end
|
102
|
+
else
|
103
|
+
# Ruby 1.8 has no built-in re-encoding, so no point in removing the UTF-16 BOMs
|
104
|
+
if data[0..2].bytes.to_a == BOM_BYTES_UTF_8
|
105
|
+
data = data[3..-1]
|
28
106
|
end
|
29
107
|
end
|
108
|
+
data.each_line.map {|line| line.rstrip }
|
30
109
|
end
|
31
110
|
|
111
|
+
# Matches the characters in a URI to encode
|
112
|
+
REGEXP_ENCODE_URI_CHARS = /[^\w\-.!~*';:@=+$,()\[\]]/
|
113
|
+
|
32
114
|
# Public: Encode a string for inclusion in a URI
|
33
115
|
#
|
34
116
|
# str - the string to encode
|
35
117
|
#
|
36
118
|
# returns an encoded version of the str
|
37
119
|
def self.encode_uri(str)
|
38
|
-
str.gsub(
|
39
|
-
|
40
|
-
buf = ''
|
41
|
-
match.each_byte do |c|
|
42
|
-
buf << sprintf('%%%02X', c)
|
43
|
-
end
|
44
|
-
buf
|
120
|
+
str.gsub(REGEXP_ENCODE_URI_CHARS) do
|
121
|
+
$&.each_byte.map {|c| sprintf '%%%02X', c}.join
|
45
122
|
end
|
46
123
|
end
|
47
124
|
|
@@ -56,32 +133,18 @@ module Helpers
|
|
56
133
|
#
|
57
134
|
# Returns the String filename with the file extension removed
|
58
135
|
def self.rootname(file_name)
|
59
|
-
|
60
|
-
|
61
|
-
file_name
|
62
|
-
else
|
63
|
-
file_name[0...-ext.length]
|
64
|
-
end
|
136
|
+
# alternatively, this could be written as ::File.basename file_name, ((::File.extname file_name) || '')
|
137
|
+
(ext = ::File.extname(file_name)).empty? ? file_name : file_name[0...-ext.length]
|
65
138
|
end
|
66
139
|
|
67
140
|
def self.mkdir_p(dir)
|
68
|
-
unless File.directory? dir
|
69
|
-
parent_dir = File.dirname(dir)
|
70
|
-
if
|
141
|
+
unless ::File.directory? dir
|
142
|
+
parent_dir = ::File.dirname(dir)
|
143
|
+
if !::File.directory?(parent_dir = ::File.dirname(dir)) && parent_dir != '.'
|
71
144
|
mkdir_p(parent_dir)
|
72
145
|
end
|
73
|
-
Dir.mkdir(dir)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Public: Create a copy of options such that no references are shared
|
78
|
-
# returns A deep clone of the options Hash
|
79
|
-
def self.clone_options(opts)
|
80
|
-
clone = opts.dup
|
81
|
-
if opts.has_key? :attributes
|
82
|
-
clone[:attributes] = opts[:attributes].dup
|
146
|
+
::Dir.mkdir(dir)
|
83
147
|
end
|
84
|
-
clone
|
85
148
|
end
|
86
149
|
end
|
87
150
|
end
|
data/lib/asciidoctor/inline.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
module Asciidoctor
|
2
2
|
# Public: Methods for managing inline elements in AsciiDoc block
|
3
3
|
class Inline < AbstractNode
|
4
|
-
# Public: Get/Set the String name of the render template
|
5
|
-
attr_accessor :template_name
|
6
|
-
|
7
4
|
# Public: Get the text of this inline element
|
8
5
|
attr_reader :text
|
9
6
|
|
@@ -15,22 +12,32 @@ class Inline < AbstractNode
|
|
15
12
|
|
16
13
|
def initialize(parent, context, text = nil, opts = {})
|
17
14
|
super(parent, context)
|
18
|
-
@
|
15
|
+
@node_name = %(inline_#{context})
|
19
16
|
|
20
17
|
@text = text
|
21
18
|
|
22
19
|
@id = opts[:id]
|
23
20
|
@type = opts[:type]
|
24
21
|
@target = opts[:target]
|
25
|
-
|
26
|
-
|
27
|
-
update_attributes
|
22
|
+
|
23
|
+
unless (more_attributes = opts[:attributes]).nil_or_empty?
|
24
|
+
update_attributes more_attributes
|
28
25
|
end
|
29
26
|
end
|
30
27
|
|
31
|
-
def
|
32
|
-
|
28
|
+
def block?
|
29
|
+
false
|
33
30
|
end
|
34
31
|
|
32
|
+
def inline?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def convert
|
37
|
+
converter.convert self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Alias render to convert to maintain backwards compatibility
|
41
|
+
alias :render :convert
|
35
42
|
end
|
36
43
|
end
|
data/lib/asciidoctor/list.rb
CHANGED
@@ -6,8 +6,8 @@ class List < AbstractBlock
|
|
6
6
|
alias :items :blocks
|
7
7
|
alias :items? :blocks?
|
8
8
|
|
9
|
-
def initialize
|
10
|
-
super
|
9
|
+
def initialize parent, context
|
10
|
+
super
|
11
11
|
end
|
12
12
|
|
13
13
|
# Public: Get the items in this list as an Array
|
@@ -15,10 +15,21 @@ class List < AbstractBlock
|
|
15
15
|
@blocks
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def convert
|
19
|
+
if @context == :colist
|
20
|
+
result = super
|
21
|
+
@document.callouts.next_list
|
22
|
+
result
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Alias render to convert to maintain backwards compatibility
|
29
|
+
alias :render :convert
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
%(#<#{self.class}@#{object_id} {context: #{@context.inspect}, style: #{@style.inspect}, items: #{items.size}}>)
|
22
33
|
end
|
23
34
|
|
24
35
|
end
|
@@ -33,14 +44,14 @@ class ListItem < AbstractBlock
|
|
33
44
|
#
|
34
45
|
# parent - The parent list block for this list item
|
35
46
|
# text - the String text (default nil)
|
36
|
-
def initialize
|
37
|
-
super
|
47
|
+
def initialize parent, text = nil
|
48
|
+
super parent, :list_item
|
38
49
|
@text = text
|
39
50
|
@level = parent.level
|
40
51
|
end
|
41
52
|
|
42
53
|
def text?
|
43
|
-
!@text.
|
54
|
+
!@text.nil_or_empty?
|
44
55
|
end
|
45
56
|
|
46
57
|
def text
|
@@ -59,23 +70,20 @@ class ListItem < AbstractBlock
|
|
59
70
|
#
|
60
71
|
# Returns nothing
|
61
72
|
def fold_first(continuation_connects_first_block = false, content_adjacent = false)
|
62
|
-
if
|
73
|
+
if (first_block = @blocks[0]) && first_block.is_a?(Block) &&
|
63
74
|
((first_block.context == :paragraph && !continuation_connects_first_block) ||
|
64
75
|
((content_adjacent || !continuation_connects_first_block) && first_block.context == :literal &&
|
65
76
|
first_block.option?('listparagraph')))
|
66
77
|
|
67
78
|
block = blocks.shift
|
68
|
-
unless @text.
|
69
|
-
block.lines.unshift("#@text\n")
|
70
|
-
end
|
71
|
-
|
79
|
+
block.lines.unshift @text unless @text.nil_or_empty?
|
72
80
|
@text = block.source
|
73
81
|
end
|
74
82
|
nil
|
75
83
|
end
|
76
84
|
|
77
85
|
def to_s
|
78
|
-
|
86
|
+
%(#<#{self.class}@#{object_id} {list_context: #{parent.context.inspect}, text: #{@text.inspect}, blocks: #{(@blocks || []).size}}>)
|
79
87
|
end
|
80
88
|
|
81
89
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# workaround for an infinite loop in Opal 0.6.2 when comparing numbers
|
2
|
+
module Comparable
|
3
|
+
def == other
|
4
|
+
return true if equal? other
|
5
|
+
return false unless cmp = (self <=> other)
|
6
|
+
return `cmp == 0`
|
7
|
+
rescue StandardError
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def > other
|
12
|
+
unless cmp = (self <=> other)
|
13
|
+
raise ArgumentError, "comparison of #{self.class} with #{other.class} failed"
|
14
|
+
end
|
15
|
+
`cmp > 0`
|
16
|
+
end
|
17
|
+
|
18
|
+
def >= other
|
19
|
+
unless cmp = (self <=> other)
|
20
|
+
raise ArgumentError, "comparison of #{self.class} with #{other.class} failed"
|
21
|
+
end
|
22
|
+
`cmp >= 0`
|
23
|
+
end
|
24
|
+
|
25
|
+
def < other
|
26
|
+
unless cmp = (self <=> other)
|
27
|
+
raise ArgumentError, "comparison of #{self.class} with #{other.class} failed"
|
28
|
+
end
|
29
|
+
`cmp < 0`
|
30
|
+
end
|
31
|
+
|
32
|
+
def <= other
|
33
|
+
unless cmp = (self <=> other)
|
34
|
+
raise ArgumentError, "comparison of #{self.class} with #{other.class} failed"
|
35
|
+
end
|
36
|
+
`cmp <= 0`
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
class Kernel
|
2
|
+
# basic implementation of open, enough to work
|
3
|
+
# with reading files over XmlHttpRequest
|
4
|
+
def open(path, *rest)
|
5
|
+
file = File.new(path, *rest)
|
6
|
+
if block_given?
|
7
|
+
yield file
|
8
|
+
else
|
9
|
+
file
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class File
|
15
|
+
SEPARATOR = '/'
|
16
|
+
ALT_SEPARATOR = nil
|
17
|
+
|
18
|
+
attr_reader :eof
|
19
|
+
attr_reader :lineno
|
20
|
+
attr_reader :path
|
21
|
+
|
22
|
+
def initialize(path, mode = 'r')
|
23
|
+
@path = path
|
24
|
+
@contents = nil
|
25
|
+
@eof = false
|
26
|
+
@lineno = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def read
|
30
|
+
if @eof
|
31
|
+
''
|
32
|
+
else
|
33
|
+
res = File.read(@path)
|
34
|
+
@eof = true
|
35
|
+
@lineno = res.size
|
36
|
+
res
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_line(separator = $/, &block)
|
41
|
+
if @eof
|
42
|
+
return block_given? ? self : [].to_enum
|
43
|
+
end
|
44
|
+
|
45
|
+
if block_given?
|
46
|
+
lines = File.read(@path)
|
47
|
+
%x{
|
48
|
+
self.eof = false;
|
49
|
+
self.lineno = 0;
|
50
|
+
var chomped = #{lines.chomp},
|
51
|
+
trailing = lines.length != chomped.length,
|
52
|
+
splitted = chomped.split(separator);
|
53
|
+
|
54
|
+
for (var i = 0, length = splitted.length; i < length; i++) {
|
55
|
+
self.lineno += 1;
|
56
|
+
if (i < length - 1 || trailing) {
|
57
|
+
#{yield `splitted[i] + separator`};
|
58
|
+
}
|
59
|
+
else {
|
60
|
+
#{yield `splitted[i]`};
|
61
|
+
}
|
62
|
+
}
|
63
|
+
self.eof = true;
|
64
|
+
}
|
65
|
+
self
|
66
|
+
else
|
67
|
+
read.each_line
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.expand_path(path)
|
72
|
+
path
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.join(*paths)
|
76
|
+
paths * SEPARATOR
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.basename(path)
|
80
|
+
(offset = path.rindex SEPARATOR) ? path[(offset + 1)..-1] : path
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.dirname(path)
|
84
|
+
(offset = path.rindex SEPARATOR) ? path[0..(offset - 1)] : '.'
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.extname(path)
|
88
|
+
return '' if path.nil_or_empty?
|
89
|
+
last_dot_idx = path[1..-1].rindex('.')
|
90
|
+
last_dot_idx.nil? ? '' : path[(last_dot_idx + 1)..-1]
|
91
|
+
end
|
92
|
+
|
93
|
+
# TODO use XMLHttpRequest HEAD request unless in local file mode
|
94
|
+
def self.file?(path)
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.read(path)
|
99
|
+
%x{
|
100
|
+
var data = ''
|
101
|
+
var status = -1;
|
102
|
+
try {
|
103
|
+
var xhr = new XMLHttpRequest();
|
104
|
+
xhr.open('GET', path, false);
|
105
|
+
xhr.addEventListener('load', function() {
|
106
|
+
status = this.status;
|
107
|
+
// status is 0 for local file mode (i.e., file://)
|
108
|
+
if (status == 0 || status == 200) {
|
109
|
+
data = this.responseText;
|
110
|
+
}
|
111
|
+
});
|
112
|
+
xhr.overrideMimeType('text/plain');
|
113
|
+
xhr.send();
|
114
|
+
}
|
115
|
+
catch (e) {
|
116
|
+
status = 0;
|
117
|
+
}
|
118
|
+
// assume that no data in local file mode means it doesn't exist
|
119
|
+
if (status == 404 || (status == 0 && data == '')) {
|
120
|
+
throw #{IOError.new `'No such file or directory: ' + path`};
|
121
|
+
}
|
122
|
+
}
|
123
|
+
`data`
|
124
|
+
end
|
125
|
+
end
|