jsduck 0.5 → 0.6
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.
- data/README.md +30 -2
- data/bin/jsduck +24 -2
- data/jsduck.gemspec +2 -2
- data/lib/jsduck/aggregator.rb +53 -32
- data/lib/jsduck/app.rb +71 -22
- data/lib/jsduck/cfg_table.rb +2 -2
- data/lib/jsduck/class.rb +5 -0
- data/lib/jsduck/css_parser.rb +75 -0
- data/lib/jsduck/doc_formatter.rb +111 -20
- data/lib/jsduck/doc_parser.rb +64 -4
- data/lib/jsduck/event_table.rb +3 -3
- data/lib/jsduck/exporter.rb +11 -9
- data/lib/jsduck/inheritance_tree.rb +2 -7
- data/lib/jsduck/{parser.rb → js_parser.rb} +20 -4
- data/lib/jsduck/lexer.rb +1 -2
- data/lib/jsduck/long_params.rb +4 -8
- data/lib/jsduck/members.rb +15 -1
- data/lib/jsduck/merger.rb +50 -5
- data/lib/jsduck/method_table.rb +4 -8
- data/lib/jsduck/page.rb +20 -9
- data/lib/jsduck/property_table.rb +2 -2
- data/lib/jsduck/relations.rb +4 -0
- data/lib/jsduck/source_file.rb +101 -0
- data/lib/jsduck/{source_formatter.rb → source_writer.rb} +14 -27
- data/lib/jsduck/table.rb +12 -11
- data/template/resources/docs.js +43 -51
- metadata +8 -6
data/README.md
CHANGED
@@ -54,7 +54,7 @@ it would like that you wrote comments like that instead:
|
|
54
54
|
/**
|
55
55
|
* Basic text field. Can be used as a direct replacement for traditional
|
56
56
|
* text inputs, or as the base class for more sophisticated input controls
|
57
|
-
* (like
|
57
|
+
* (like Ext.form.TextArea and Ext.form.ComboBox).
|
58
58
|
*
|
59
59
|
* Validation
|
60
60
|
* ----------
|
@@ -197,12 +197,40 @@ Copying
|
|
197
197
|
JsDuck is distributed under the terms of the GNU General Public License version 3.
|
198
198
|
|
199
199
|
JsDuck was developed by [Rene Saarsoo](http://triin.net),
|
200
|
-
with contributions from [Ondřej Jirman](https://github.com/megous)
|
200
|
+
with contributions from [Ondřej Jirman](https://github.com/megous)
|
201
|
+
and [Nick Poulden](https://github.com/nick).
|
201
202
|
|
202
203
|
|
203
204
|
Changelog
|
204
205
|
---------
|
205
206
|
|
207
|
+
* 0.6 - JsDuck is now used for creating the official ExtJS4 documentation.
|
208
|
+
* Automatic linking of class names found in comments. Instead of writing
|
209
|
+
`{@link Ext.Panel}` one can simply write `Ext.Panel` and link will be
|
210
|
+
automatically created.
|
211
|
+
* In generated docs, method return types and parameter types are also
|
212
|
+
automatically linked to classes if such class is included to docs.
|
213
|
+
* Support for `{@img}` tag for including images to documentation.
|
214
|
+
The markup created by `{@link}` and `{@img}` tags can now be customized using
|
215
|
+
the --img and --link command line options to supply HTML templates.
|
216
|
+
* Links to source code are no more simply links to line numbers.
|
217
|
+
Instead the source code files will contain ID-s like `MyClass-cfg-style`.
|
218
|
+
* New tags: `@docauthor`, `@alternateClassName`, `@mixins`.
|
219
|
+
The latter two Ext4 class properties are both detected from code and
|
220
|
+
can also be defined (or overriden) in doc-comments.
|
221
|
+
* Global methods are now placed to separate "global" class.
|
222
|
+
Creation of this can be turned off using `--ignore-global`.
|
223
|
+
* Much improved search feature.
|
224
|
+
Search results are now ordered so that best matches are at the top.
|
225
|
+
No more is there a select-box to match at beginning/middle/end -
|
226
|
+
we automatically search first by exact match, then beginning and
|
227
|
+
finally by middle. Additionally the search no more lists a lot of
|
228
|
+
duplicates - only the class that defines a method is listed, ignoring
|
229
|
+
all the classes that inherit it.
|
230
|
+
* Support for doc-comments in [SASS](http://sass-lang.com/) .scss files:
|
231
|
+
For now, it's possible to document SASS variables and mixins.
|
232
|
+
* Several bug fixes.
|
233
|
+
|
206
234
|
* 0.5 - Search and export
|
207
235
|
* Search from the actually generated docs (not through sencha.com)
|
208
236
|
* JSON export with --json switch.
|
data/bin/jsduck
CHANGED
@@ -34,10 +34,28 @@ opts = OptionParser.new do | opts |
|
|
34
34
|
app.template_dir = path
|
35
35
|
end
|
36
36
|
|
37
|
-
opts.on('--json', "Produces JSON export instead of HTML documentation.") do
|
37
|
+
opts.on('--json', "Produces JSON export instead of HTML documentation.") do
|
38
38
|
app.export = :json
|
39
39
|
end
|
40
40
|
|
41
|
+
opts.on('--link=TPL', "HTML template for replacing {@link}.",
|
42
|
+
"Possible placeholders:",
|
43
|
+
"%c - full class name (e.g. 'Ext.Panel')",
|
44
|
+
"%m - class member name (e.g. 'urlEncode')",
|
45
|
+
"%M - class member name, prefixed with hash (e.g. '#urlEncode')",
|
46
|
+
"%a - anchor text for link",
|
47
|
+
"Default value in export: '<a href=\"%c%M\">%a</a>'") do |tpl|
|
48
|
+
app.link_tpl = tpl
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on('--img=TPL', "HTML template for replacing {@img}.",
|
52
|
+
"Possible placeholders:",
|
53
|
+
"%u - URL from @img tag (e.g. 'some/path.png')",
|
54
|
+
"%a - alt text for image",
|
55
|
+
"Default value in export: '<img src=\"%u\" alt=\"%a\"/>'") do |tpl|
|
56
|
+
app.img_tpl = tpl
|
57
|
+
end
|
58
|
+
|
41
59
|
# For debugging it's often useful to set --processes=0 to get deterministic results.
|
42
60
|
opts.on('-p', '--processes=COUNT', "The number of parallel processes to use.",
|
43
61
|
"Defaults to the number of processors/cores.",
|
@@ -45,6 +63,10 @@ opts = OptionParser.new do | opts |
|
|
45
63
|
app.processes = count.to_i
|
46
64
|
end
|
47
65
|
|
66
|
+
opts.on('--ignore-global', "Turns off the creation of global class.") do
|
67
|
+
app.ignore_global = true
|
68
|
+
end
|
69
|
+
|
48
70
|
opts.on('-v', '--verbose', "This will fill up your console.") do
|
49
71
|
app.verbose = true
|
50
72
|
end
|
@@ -60,7 +82,7 @@ js_files = []
|
|
60
82
|
opts.parse!(ARGV).each do |fname|
|
61
83
|
if File.exists?(fname)
|
62
84
|
if File.directory?(fname)
|
63
|
-
Dir[fname+"/**/*.js"].each {|f| js_files << f }
|
85
|
+
Dir[fname+"/**/*.{js,css,scss}"].each {|f| js_files << f }
|
64
86
|
else
|
65
87
|
js_files << fname
|
66
88
|
end
|
data/jsduck.gemspec
CHANGED
@@ -2,8 +2,8 @@ Gem::Specification.new do |s|
|
|
2
2
|
s.required_rubygems_version = ">= 1.3.7"
|
3
3
|
|
4
4
|
s.name = 'jsduck'
|
5
|
-
s.version = '0.
|
6
|
-
s.date = '2011-
|
5
|
+
s.version = '0.6'
|
6
|
+
s.date = '2011-04-27'
|
7
7
|
s.summary = "Simple JavaScript Duckumentation generator"
|
8
8
|
s.description = "Better ext-doc like JavaScript documentation generator for ExtJS"
|
9
9
|
s.homepage = "https://github.com/nene/jsduck"
|
data/lib/jsduck/aggregator.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'jsduck/merger'
|
2
|
-
|
3
1
|
module JsDuck
|
4
2
|
|
5
3
|
# Combines JavaScript Parser, DocParser and Merger.
|
@@ -10,44 +8,17 @@ module JsDuck
|
|
10
8
|
@classes = {}
|
11
9
|
@orphans = []
|
12
10
|
@current_class = nil
|
13
|
-
@merger = Merger.new
|
14
11
|
end
|
15
12
|
|
16
13
|
# Combines chunk of parsed JavaScript together with previously
|
17
14
|
# added chunks. The resulting documentation is accumulated inside
|
18
15
|
# this class and can be later accessed through #result method.
|
19
16
|
#
|
20
|
-
# -
|
21
|
-
# - filename name of the JS file where it came from
|
22
|
-
# - html_filename name of the HTML file where the source was saved.
|
17
|
+
# - file SoureFile class instance
|
23
18
|
#
|
24
|
-
def aggregate(
|
19
|
+
def aggregate(file)
|
25
20
|
@current_class = nil
|
26
|
-
|
27
|
-
doc = @merger.merge(docset[:comment], docset[:code])
|
28
|
-
|
29
|
-
add_source_data(doc, {
|
30
|
-
:filename => filename,
|
31
|
-
:html_filename => html_filename,
|
32
|
-
:linenr => docset[:linenr],
|
33
|
-
})
|
34
|
-
|
35
|
-
register(doc)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Links doc-object to source code where it came from.
|
40
|
-
def add_source_data(doc, src)
|
41
|
-
doc[:href] = src[:html_filename] + "#line-" + src[:linenr].to_s
|
42
|
-
doc[:filename] = src[:filename]
|
43
|
-
doc[:linenr] = src[:linenr]
|
44
|
-
# class-level doc-comment can contain constructor and config
|
45
|
-
# options, link those to the same location in source.
|
46
|
-
if doc[:tagname] == :class
|
47
|
-
doc[:cfg].each {|cfg| add_source_data(cfg, src) }
|
48
|
-
doc[:method].each {|method| add_source_data(method, src) }
|
49
|
-
end
|
50
|
-
doc
|
21
|
+
file.each {|doc| register(doc) }
|
51
22
|
end
|
52
23
|
|
53
24
|
# Registers documentation node either as class or as member of
|
@@ -80,6 +51,9 @@ module JsDuck
|
|
80
51
|
[:extends, :xtype, :singleton, :private].each do |tag|
|
81
52
|
old[tag] = old[tag] || new[tag]
|
82
53
|
end
|
54
|
+
[:mixins, :alternateClassNames].each do |tag|
|
55
|
+
old[tag] = old[tag] + new[tag]
|
56
|
+
end
|
83
57
|
old[:doc] = old[:doc].length > 0 ? old[:doc] : new[:doc]
|
84
58
|
old[:cfg] = old[:cfg] + new[:cfg]
|
85
59
|
end
|
@@ -121,6 +95,53 @@ module JsDuck
|
|
121
95
|
end
|
122
96
|
end
|
123
97
|
|
98
|
+
# Creates classes for orphans that have :member property defined,
|
99
|
+
# and then inserts orphans to these classes.
|
100
|
+
def classify_orphans
|
101
|
+
@orphans.each do |orph|
|
102
|
+
if orph[:member]
|
103
|
+
class_name = orph[:member]
|
104
|
+
if !@classes[class_name]
|
105
|
+
add_empty_class(class_name)
|
106
|
+
end
|
107
|
+
add_member(orph)
|
108
|
+
@orphans.delete(orph)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Creates class with name "global" and inserts all the remaining
|
114
|
+
# orphans into it (but only if there are any orphans).
|
115
|
+
def create_global_class
|
116
|
+
return if @orphans.length == 0
|
117
|
+
|
118
|
+
add_empty_class("global", "Global variables and functions.")
|
119
|
+
@orphans.each do |orph|
|
120
|
+
orph[:member] = "global"
|
121
|
+
add_member(orph)
|
122
|
+
end
|
123
|
+
@orphans = []
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_empty_class(name, doc = "")
|
127
|
+
add_class({
|
128
|
+
:tagname => :class,
|
129
|
+
:name => name,
|
130
|
+
:doc => doc,
|
131
|
+
:mixins => [],
|
132
|
+
:alternateClassNames => [],
|
133
|
+
:cfg => [],
|
134
|
+
:property => [],
|
135
|
+
:method => [],
|
136
|
+
:event => [],
|
137
|
+
:css_var => [],
|
138
|
+
:css_mixin => [],
|
139
|
+
:filename => "",
|
140
|
+
:html_filename => "",
|
141
|
+
:linenr => 0,
|
142
|
+
})
|
143
|
+
end
|
144
|
+
|
124
145
|
def result
|
125
146
|
@documentation + @orphans
|
126
147
|
end
|
data/lib/jsduck/app.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'jsduck/parser'
|
3
2
|
require 'jsduck/aggregator'
|
4
|
-
require 'jsduck/
|
3
|
+
require 'jsduck/source_file'
|
4
|
+
require 'jsduck/source_writer'
|
5
5
|
require 'jsduck/class'
|
6
6
|
require 'jsduck/tree'
|
7
7
|
require 'jsduck/tree_icons'
|
@@ -24,6 +24,9 @@ module JsDuck
|
|
24
24
|
attr_accessor :input_files
|
25
25
|
attr_accessor :verbose
|
26
26
|
attr_accessor :export
|
27
|
+
attr_accessor :link_tpl
|
28
|
+
attr_accessor :img_tpl
|
29
|
+
attr_accessor :ignore_global
|
27
30
|
|
28
31
|
def initialize
|
29
32
|
@output_dir = nil
|
@@ -31,6 +34,9 @@ module JsDuck
|
|
31
34
|
@input_files = []
|
32
35
|
@verbose = false
|
33
36
|
@export = nil
|
37
|
+
@link_tpl = nil
|
38
|
+
@img_tpl = nil
|
39
|
+
@ignore_global = false
|
34
40
|
@timer = Timer.new
|
35
41
|
@parallel = ParallelWrap.new
|
36
42
|
end
|
@@ -43,21 +49,21 @@ module JsDuck
|
|
43
49
|
|
44
50
|
# Call this after input parameters set
|
45
51
|
def run
|
46
|
-
clear_dir(@output_dir)
|
47
|
-
if @export
|
48
|
-
FileUtils.mkdir(@output_dir)
|
49
|
-
init_output_dirs(@output_dir)
|
50
|
-
else
|
51
|
-
copy_template(@template_dir, @output_dir)
|
52
|
-
end
|
53
|
-
|
54
52
|
parsed_files = @timer.time(:parsing) { parallel_parse(@input_files) }
|
55
53
|
result = @timer.time(:aggregating) { aggregate(parsed_files) }
|
56
54
|
relations = @timer.time(:aggregating) { filter_classes(result) }
|
55
|
+
warn_globals(relations)
|
56
|
+
warn_unnamed(relations)
|
57
57
|
|
58
|
+
clear_dir(@output_dir)
|
58
59
|
if @export == :json
|
60
|
+
FileUtils.mkdir(@output_dir)
|
61
|
+
init_output_dirs(@output_dir)
|
62
|
+
@timer.time(:generating) { write_src(@output_dir+"/source", parsed_files) }
|
59
63
|
@timer.time(:generating) { write_json(@output_dir+"/output", relations) }
|
60
64
|
else
|
65
|
+
copy_template(@template_dir, @output_dir)
|
66
|
+
@timer.time(:generating) { write_src(@output_dir+"/source", parsed_files) }
|
61
67
|
@timer.time(:generating) { write_tree(@output_dir+"/output/tree.js", relations) }
|
62
68
|
@timer.time(:generating) { write_members(@output_dir+"/output/members.js", relations) }
|
63
69
|
@timer.time(:generating) { write_pages(@output_dir+"/output", relations) }
|
@@ -68,15 +74,9 @@ module JsDuck
|
|
68
74
|
|
69
75
|
# Parses the files in parallel using as many processes as available CPU-s
|
70
76
|
def parallel_parse(filenames)
|
71
|
-
src = SourceFormatter.new(@output_dir + "/source", @export ? :format_pre : :format_page)
|
72
77
|
@parallel.map(filenames) do |fname|
|
73
78
|
puts "Parsing #{fname} ..." if @verbose
|
74
|
-
|
75
|
-
{
|
76
|
-
:filename => fname,
|
77
|
-
:html_filename => File.basename(src.write(code, fname)),
|
78
|
-
:data => Parser.new(code).parse,
|
79
|
-
}
|
79
|
+
SourceFile.new(IO.read(fname), fname)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -84,9 +84,11 @@ module JsDuck
|
|
84
84
|
def aggregate(parsed_files)
|
85
85
|
agr = Aggregator.new
|
86
86
|
parsed_files.each do |file|
|
87
|
-
puts "Aggregating #{file
|
88
|
-
agr.aggregate(file
|
87
|
+
puts "Aggregating #{file.filename} ..." if @verbose
|
88
|
+
agr.aggregate(file)
|
89
89
|
end
|
90
|
+
agr.classify_orphans
|
91
|
+
agr.create_global_class unless @ignore_global
|
90
92
|
agr.result
|
91
93
|
end
|
92
94
|
|
@@ -108,6 +110,35 @@ module JsDuck
|
|
108
110
|
Relations.new(classes)
|
109
111
|
end
|
110
112
|
|
113
|
+
# print warning for each global member
|
114
|
+
def warn_globals(relations)
|
115
|
+
global = relations["global"]
|
116
|
+
return unless global
|
117
|
+
[:cfg, :property, :method, :event].each do |type|
|
118
|
+
global.members(type).each do |member|
|
119
|
+
name = member[:name]
|
120
|
+
file = member[:filename]
|
121
|
+
line = member[:linenr]
|
122
|
+
puts "Warning: Global #{type}: #{name} in #{file} line #{line}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# print warning for each member with no name
|
128
|
+
def warn_unnamed(relations)
|
129
|
+
relations.each do |cls|
|
130
|
+
[:cfg, :property, :method, :event].each do |type|
|
131
|
+
cls[type].each do |member|
|
132
|
+
if !member[:name] || member[:name] == ""
|
133
|
+
file = member[:filename]
|
134
|
+
line = member[:linenr]
|
135
|
+
puts "Warning: Unnamed #{type} in #{file} line #{line}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
111
142
|
# Given all classes, generates namespace tree and writes it
|
112
143
|
# in JSON form into a file.
|
113
144
|
def write_tree(filename, relations)
|
@@ -133,14 +164,20 @@ module JsDuck
|
|
133
164
|
@parallel.each(relations.classes) do |cls|
|
134
165
|
filename = path + "/" + cls[:name] + ".html"
|
135
166
|
puts "Writing to #{filename} ..." if @verbose
|
136
|
-
|
137
|
-
|
167
|
+
page = Page.new(cls, relations, cache)
|
168
|
+
page.link_tpl = @link_tpl if @link_tpl
|
169
|
+
page.img_tpl = @img_tpl if @img_tpl
|
170
|
+
File.open(filename, 'w') {|f| f.write(page.to_html) }
|
138
171
|
end
|
139
172
|
end
|
140
173
|
|
141
174
|
# Writes JSON export file for each class
|
142
175
|
def write_json(path, relations)
|
143
|
-
|
176
|
+
formatter = DocFormatter.new
|
177
|
+
formatter.link_tpl = @link_tpl if @link_tpl
|
178
|
+
formatter.img_tpl = @img_tpl if @img_tpl
|
179
|
+
formatter.relations = relations
|
180
|
+
exporter = Exporter.new(relations, formatter)
|
144
181
|
@parallel.each(relations.classes) do |cls|
|
145
182
|
filename = path + "/" + cls[:name] + ".json"
|
146
183
|
puts "Writing to #{filename} ..." if @verbose
|
@@ -150,6 +187,18 @@ module JsDuck
|
|
150
187
|
end
|
151
188
|
end
|
152
189
|
|
190
|
+
# Writes formatted HTML source code for each input file
|
191
|
+
def write_src(path, parsed_files)
|
192
|
+
src = SourceWriter.new(path, @export ? nil : :page)
|
193
|
+
# Can't be done in parallel, because file.html_filename= method
|
194
|
+
# updates all the doc-objects related to the file
|
195
|
+
parsed_files.each do |file|
|
196
|
+
html_filename = src.write(file.to_html, file.filename)
|
197
|
+
puts "Writing to #{html_filename} ..." if @verbose
|
198
|
+
file.html_filename = File.basename(html_filename)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
153
202
|
def copy_template(template_dir, dir)
|
154
203
|
puts "Copying template files to #{dir}..." if @verbose
|
155
204
|
FileUtils.cp_r(template_dir, dir)
|
data/lib/jsduck/cfg_table.rb
CHANGED
@@ -3,8 +3,8 @@ require 'jsduck/table'
|
|
3
3
|
module JsDuck
|
4
4
|
|
5
5
|
class CfgTable < Table
|
6
|
-
def initialize(cls, cache={})
|
7
|
-
super(cls, cache)
|
6
|
+
def initialize(cls, formatter, cache={})
|
7
|
+
super(cls, formatter, cache)
|
8
8
|
@type = :cfg
|
9
9
|
@id = @cls.full_name + "-configs"
|
10
10
|
@title = "Config Options"
|
data/lib/jsduck/class.rb
CHANGED
@@ -36,6 +36,11 @@ module JsDuck
|
|
36
36
|
@doc[:mixins] ? @doc[:mixins].collect {|classname| lookup(classname) }.compact : []
|
37
37
|
end
|
38
38
|
|
39
|
+
# Returns all mixins this class and its parent classes
|
40
|
+
def all_mixins
|
41
|
+
mixins + (parent ? parent.all_mixins : [])
|
42
|
+
end
|
43
|
+
|
39
44
|
# Looks up class object by name
|
40
45
|
# When not found, prints warning message.
|
41
46
|
def lookup(classname)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'jsduck/lexer'
|
2
|
+
require 'jsduck/doc_parser'
|
3
|
+
|
4
|
+
module JsDuck
|
5
|
+
|
6
|
+
class CssParser
|
7
|
+
def initialize(input)
|
8
|
+
@lex = Lexer.new(input)
|
9
|
+
@doc_parser = DocParser.new(:css)
|
10
|
+
@docs = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Parses the whole CSS block and returns same kind of structure
|
14
|
+
# that JavaScript parser does.
|
15
|
+
def parse
|
16
|
+
while !@lex.empty? do
|
17
|
+
if look(:doc_comment)
|
18
|
+
comment = @lex.next(true)
|
19
|
+
@docs << {
|
20
|
+
:comment => @doc_parser.parse(comment[:value]),
|
21
|
+
:linenr => comment[:linenr],
|
22
|
+
:code => code_block
|
23
|
+
}
|
24
|
+
else
|
25
|
+
@lex.next
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@docs
|
29
|
+
end
|
30
|
+
|
31
|
+
# <code-block> := <mixin> | <nop>
|
32
|
+
def code_block
|
33
|
+
if look("@", "mixin")
|
34
|
+
mixin
|
35
|
+
else
|
36
|
+
{:type => :nop}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# <mixin> := "@mixin" <css-ident>
|
41
|
+
def mixin
|
42
|
+
match("@", "mixin")
|
43
|
+
return {
|
44
|
+
:type => :css_mixin,
|
45
|
+
:name => look(:ident) ? css_ident : nil,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# <css-ident> := <ident> [ "-" <ident> ]*
|
50
|
+
def css_ident
|
51
|
+
chain = [match(:ident)]
|
52
|
+
while look("-", :ident) do
|
53
|
+
chain << match("-", :ident)
|
54
|
+
end
|
55
|
+
return chain.join("-")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Matches all arguments, returns the value of last match
|
59
|
+
# When the whole sequence doesn't match, throws exception
|
60
|
+
def match(*args)
|
61
|
+
if look(*args)
|
62
|
+
last = nil
|
63
|
+
args.length.times { last = @lex.next }
|
64
|
+
last
|
65
|
+
else
|
66
|
+
throw "Expected: " + args.join(", ")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def look(*args)
|
71
|
+
@lex.look(*args)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|