textpow 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ == 0.9.0 / 2007-03-19
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
5
+
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ bin/plist2yaml
2
+ bin/plist2syntax
3
+ lib/textpow.rb
4
+ lib/textpow
5
+ lib/textpow/syntax.rb
6
+ lib/textpow/score_manager.rb
7
+ lib/textpow/debug_processor.rb
8
+ mm/manual.mm
9
+ test/test_textpow.rb
10
+ History.txt
11
+ Rakefile
12
+ Manifest.txt
13
+ README.txt
data/README.txt ADDED
@@ -0,0 +1,48 @@
1
+ == Textpow
2
+
3
+ A library for parsing TextMate[http://macromates.com/] bundles.
4
+
5
+ == SYNTAX
6
+
7
+
8
+ == REQUIREMENTS:
9
+
10
+ * Oniguruma for Ruby[http://oniguruma.rubyforge.org] v1.1.0 or higher.
11
+
12
+ == INSTALL:
13
+
14
+ sudo gem install -r mama
15
+
16
+ == BUGS/PROBLEMS/INCOMPATIBILITIES:
17
+
18
+
19
+ == TODO:
20
+
21
+
22
+ == CREDITS:
23
+
24
+
25
+ == LICENSE:
26
+
27
+ (The MIT License)
28
+
29
+ Copyright (c) 2007 FIX
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ 'Software'), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
45
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
46
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
47
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
48
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+
4
+ rubyforge_name = "textpow"
5
+
6
+ begin
7
+ require 'hoe'
8
+
9
+ class Hoe
10
+ # Dirty hack to eliminate Hoe from gem dependencies
11
+ def extra_deps
12
+ @extra_deps.delete_if{ |x| x.first == 'hoe' }
13
+ end
14
+ end
15
+
16
+ version = /^== *(\d+\.\d+\.\d+)/.match( File.read( 'History.txt' ) )[1]
17
+
18
+ h = Hoe.new('textpow', version) do |p|
19
+ p.rubyforge_name = rubyforge_name
20
+ p.author = ['Dizan Vasquez']
21
+ p.email = ['dichodaemon@gmail.com']
22
+ p.email = 'dichodaemon@gmail.com'
23
+ p.summary = 'An engine for parsing Textmate bundles'
24
+ p.description = p.paragraphs_of('README.txt', 1 ).join('\n\n')
25
+ p.url = 'http://textpow.rubyforge.org'
26
+ p.rdoc_pattern = /^(lib|bin|ext)|txt$/
27
+ p.changes = p.paragraphs_of('History.txt', 0).join("\n\n")
28
+ p.clean_globs = ["manual/*"]
29
+ p.extra_deps << ['oniguruma', '>= 1.1.0']
30
+ p.extra_deps << ['plist', '>= 3.0.0']
31
+ end
32
+
33
+ desc 'Create MaMa documentation'
34
+ task :mama => :clean do
35
+ system "mm -c -t refresh -o manual mm/manual.mm"
36
+ end
37
+
38
+ desc 'Publish MaMa documentation to RubyForge'
39
+ task :mama_publish => [:clean, :mama] do
40
+ config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
41
+ host = "#{config["username"]}@rubyforge.org"
42
+ remote_dir = "/var/www/gforge-projects/#{h.rubyforge_name}"
43
+ local_dir = 'manual'
44
+ system "rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}"
45
+ end
46
+
47
+ rescue LoadError => e
48
+ desc 'Run the test suite.'
49
+ task :test do
50
+ system "ruby -Ibin:lib:test test_#{rubyforge_name}.rb"
51
+ end
52
+ end
data/bin/plist2syntax ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'plist'
5
+ require 'yaml'
6
+
7
+ result = Plist::parse_xml( ARGV[0] )
8
+ standard_name = File.basename( ARGV[0] ).downcase.gsub(/\s+/, '_').gsub(/\.(plist|tm[Ll]anguage)/, '').gsub(/\(|\)|:/, '').gsub(/_+/, '_')
9
+ puts standard_name
10
+
11
+ File.open( "#{standard_name}.yaml", "w" ) {|f| YAML.dump( result, f ) }
data/bin/plist2yaml ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'plist'
5
+ require 'yaml'
6
+
7
+ result = Plist::parse_xml( ARGV[0] )
8
+ YAML.dump( result, STDOUT )
@@ -0,0 +1,34 @@
1
+ module Textpow
2
+ class DebugProcessor
3
+ def initialize
4
+ @line_number = 0
5
+ @printable_line = ""
6
+ end
7
+
8
+ def pprint line, string, position = 0
9
+ line.replace line.ljust( position + string.size, " ")
10
+ line[position,string.size] = string
11
+ line
12
+ end
13
+
14
+ def open_tag name, position
15
+ STDERR.puts pprint( "", "{#{name}", position + @line_marks.size)
16
+ end
17
+
18
+ def close_tag name, position
19
+ STDERR.puts pprint( "", "}#{name}", position + @line_marks.size)
20
+ end
21
+
22
+ def new_line line
23
+ @line_number += 1
24
+ @line_marks = "[#{@line_number.to_s.rjust( 4, '0' )}] "
25
+ STDERR.puts "#{@line_marks}#{line}"
26
+ end
27
+
28
+ def start_parsing
29
+ end
30
+
31
+ def end_parsing
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ module Textpow
2
+ class ScoreManager
3
+ POINT_DEPTH = 4
4
+ NESTING_DEPTH = 40
5
+ START_VALUE = 2 ** ( POINT_DEPTH * NESTING_DEPTH )
6
+ BASE = 2 ** POINT_DEPTH
7
+
8
+ def initialize
9
+ @scores = {}
10
+ end
11
+
12
+ def score search_scope, reference_scope
13
+ max = 0
14
+ search_scope.split( ',' ).each do |scope|
15
+ arrays = scope.split(/\B-\B/)
16
+ if arrays.size == 1
17
+ max = [max, score_term( arrays[0], reference_scope )].max
18
+ elsif arrays.size == 2
19
+ unless score_term( arrays[1], reference_scope ) > 0
20
+ max = [max, score_term( arrays[0], reference_scope )].max
21
+ end
22
+ else
23
+ raise ParsingError, "Error in scope string: '#{search_scope}'" if arrays.size < 1 || arrays.size > 2
24
+ end
25
+ end
26
+ max
27
+ end
28
+
29
+ private
30
+
31
+ def score_term search_scope, reference_scope
32
+ unless @scores[reference_scope] && @scores[reference_scope][search_scope]
33
+ @scores[reference_scope] ||= {}
34
+ @scores[reference_scope][search_scope] = score_array( search_scope.split(' '), reference_scope.split( ' ' ) )
35
+ end
36
+ @scores[reference_scope][search_scope]
37
+ end
38
+
39
+ def score_array search_array, reference_array
40
+ pending = search_array
41
+ current = reference_array.last
42
+ reg = Regexp.new( "^#{Regexp.escape( pending.last )}" )
43
+ multiplier = START_VALUE
44
+ result = 0
45
+ while pending.size > 0 && current
46
+ if reg =~ current
47
+ point_score = (2**POINT_DEPTH) - current.count( '.' ) + Regexp.last_match[0].count( '.' )
48
+ result += point_score * multiplier
49
+ pending.pop
50
+ reg = Regexp.new( "^#{Regexp.escape( pending.last )}" ) if pending.size > 0
51
+ end
52
+ multiplier = multiplier / BASE
53
+ reference_array.pop
54
+ current = reference_array.last
55
+ end
56
+ result = 0 if pending.size > 0
57
+ result
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,286 @@
1
+ require 'plist'
2
+
3
+ module Textpow
4
+
5
+ class SyntaxProxy
6
+ def initialize hash, syntax
7
+ @syntax = syntax
8
+ @proxy = hash["include"]
9
+ end
10
+
11
+ def method_missing method, *args, &block
12
+ if @proxy
13
+ @proxy_value = proxy unless @proxy_value
14
+ if @proxy_value
15
+ @proxy_value.send(method, *args, &block)
16
+ else
17
+ STDERR.puts "Failed proxying #{@proxy}.#{m}(#{args.join(', ')})"
18
+ end
19
+ end
20
+ end
21
+
22
+ def proxy
23
+ case @proxy
24
+ when /^#/
25
+ if @syntax.repository && @syntax.repository[@proxy[1..-1]]
26
+ #puts "Repository"
27
+ #@table["syntax"].repository.each_key{|k| puts k}
28
+ return @syntax.repository[@proxy[1..-1]]
29
+ end
30
+ when "$self"
31
+ return @syntax
32
+ when "$base"
33
+ return @syntax
34
+ else
35
+ return @syntax.syntaxes[@proxy]
36
+ end
37
+ end
38
+ end
39
+
40
+ class SyntaxNode
41
+ OPTIONS = {:options => Oniguruma::OPTION_CAPTURE_GROUP}
42
+
43
+ @@syntaxes = {}
44
+
45
+ attr_accessor :syntax
46
+ attr_accessor :firstLineMatch
47
+ attr_accessor :foldingStartMarker
48
+ attr_accessor :foldingStopMarker
49
+ attr_accessor :match
50
+ attr_accessor :begin
51
+ attr_accessor :content
52
+ attr_accessor :fileTypes
53
+ attr_accessor :name
54
+ attr_accessor :contentName
55
+ attr_accessor :end
56
+ attr_accessor :scopeName
57
+ attr_accessor :keyEquivalent
58
+ attr_accessor :captures
59
+ attr_accessor :beginCaptures
60
+ attr_accessor :endCaptures
61
+ attr_accessor :repository
62
+ attr_accessor :patterns
63
+
64
+ def self.load filename, name_space = :default
65
+ table = nil
66
+ case filename
67
+ when /(\.tmSyntax|\.plist)$/
68
+ table = Plist::parse_xml( filename )
69
+ else
70
+ File.open( filename ) do |f|
71
+ table = YAML.load( f )
72
+ end
73
+ end
74
+ if table
75
+ SyntaxNode.new( table, nil, name_space )
76
+ else
77
+ nil
78
+ end
79
+ end
80
+
81
+ def initialize hash, syntax = nil, name_space = :default
82
+ @name_space = name_space
83
+ @@syntaxes[@name_space] ||= {}
84
+ @@syntaxes[@name_space][hash["scopeName"]] = self if hash["scopeName"]
85
+ @syntax = syntax || self
86
+ hash.each do |key, value|
87
+ case key
88
+ when "firstLineMatch", "foldingStartMarker", "foldingStopMarker", "match", "begin"
89
+ begin
90
+ instance_variable_set( "@#{key}", Oniguruma::ORegexp.new( value, OPTIONS ) )
91
+ rescue ArgumentError => e
92
+ raise ParsingError, "Parsing error in #{value}: #{e.to_s}"
93
+ end
94
+ when "content", "fileTypes", "name", "contentName", "end", "scopeName", "keyEquivalent"
95
+ instance_variable_set( "@#{key}", value )
96
+ when "captures", "beginCaptures", "endCaptures"
97
+ instance_variable_set( "@#{key}", value.sort )
98
+ when "repository"
99
+ parse_repository value
100
+ when "patterns"
101
+ create_children value
102
+ else
103
+ STDERR.puts "Ignoring: #{key} => #{value.gsub("\n", "\n>>")}" if $DEBUG
104
+ end
105
+ end
106
+ end
107
+
108
+
109
+ def syntaxes
110
+ @@syntaxes[@name_space]
111
+ end
112
+
113
+ def parse( string, processor = nil )
114
+ processor.start_parsing if processor
115
+ stack = [[self, nil]]
116
+ string.each_line do |line|
117
+ parse_line stack, line, processor
118
+ end
119
+ processor.end_parsing if processor
120
+ processor
121
+ end
122
+
123
+ protected
124
+
125
+ def parse_repository repository
126
+ @repository = {}
127
+ repository.each do |key, value|
128
+ if value["include"]
129
+ @repository[key] = SyntaxProxy.new( value, self.syntax )
130
+ else
131
+ @repository[key] = SyntaxNode.new( value, self.syntax, @name_space )
132
+ end
133
+ end
134
+ end
135
+
136
+ def create_children patterns
137
+ @patterns = []
138
+ patterns.each do |p|
139
+ if p["include"]
140
+ @patterns << SyntaxProxy.new( p, self.syntax )
141
+ else
142
+ @patterns << SyntaxNode.new( p, self.syntax, @name_space )
143
+ end
144
+ end
145
+ end
146
+
147
+ def parse_captures name, pattern, match, processor
148
+ captures = pattern.match_captures( name, match )
149
+ captures.reject! { |group, range, name| ! range.first || range.first == range.last }
150
+ starts = []
151
+ ends = []
152
+ captures.each do |group, range, name|
153
+ starts << [range.first, group, name]
154
+ ends << [range.last, -group, name]
155
+ end
156
+
157
+ # STDERR.puts '-' * 100
158
+ # starts.sort!.reverse!.each{|c| STDERR.puts c.join(', ')}
159
+ # STDERR.puts
160
+ # ends.sort!.reverse!.each{|c| STDERR.puts c.join(', ')}
161
+ starts.sort!.reverse!
162
+ ends.sort!.reverse!
163
+
164
+ while ! starts.empty? || ! ends.empty?
165
+ if starts.empty?
166
+ pos, key, name = ends.pop
167
+ processor.close_tag name, pos
168
+ elsif ends.empty?
169
+ pos, key, name = starts.pop
170
+ processor.open_tag name, pos
171
+ elsif ends.last[1].abs < starts.last[1]
172
+ pos, key, name = ends.pop
173
+ processor.close_tag name, pos
174
+ else
175
+ pos, key, name = starts.pop
176
+ processor.open_tag name, pos
177
+ end
178
+ end
179
+ end
180
+
181
+ def match_captures name, match
182
+ matches = []
183
+ captures = instance_variable_get "@#{name}"
184
+ if captures
185
+ captures.each do |key, value|
186
+ if key =~ /^\d*$/
187
+ matches << [key.to_i, match.offset( key.to_i ), value["name"]] if key.to_i < match.size
188
+ else
189
+ matches << [match.to_index( key.to_sym ), match.offset( key.to_sym), value["name"]] if match.to_index( key.to_sym )
190
+ end
191
+ end
192
+ end
193
+ matches
194
+ end
195
+
196
+ def match_first string, position
197
+ if self.match
198
+ if match = self.match.match( string, position )
199
+ return [self, match]
200
+ end
201
+ elsif self.begin
202
+ if match = self.begin.match( string, position )
203
+ return [self, match]
204
+ end
205
+ elsif self.end
206
+ else
207
+ return match_first_son( string, position )
208
+ end
209
+ nil
210
+ end
211
+
212
+ def match_end string, match, position
213
+ regstring = self.end.clone
214
+ regstring.gsub!( /\\([1-9])/ ) { |s| match[$1.to_i] }
215
+ regstring.gsub!( /\\k<(.*?)>/ ) { |s| match[$1.to_sym] }
216
+ Oniguruma::ORegexp.new( regstring ).match( string, position )
217
+ end
218
+
219
+ def match_first_son string, position
220
+ match = nil
221
+ if self.patterns
222
+ self.patterns.each do |p|
223
+ tmatch = p.match_first string, position
224
+ if tmatch
225
+ if ! match || match[1].offset.first > tmatch[1].offset.first
226
+ match = tmatch
227
+ end
228
+ #break if tmatch[1].offset.first == position
229
+ end
230
+ end
231
+ end
232
+ match
233
+ end
234
+
235
+ def parse_line stack, line, processor
236
+ processor.new_line line if processor
237
+ top, match = stack.last
238
+ position = 0
239
+ #@ln ||= 0
240
+ #@ln += 1
241
+ #STDERR.puts @ln
242
+ while true
243
+ if top.patterns
244
+ pattern, pattern_match = top.match_first_son line, position
245
+ else
246
+ pattern, pattern_match = nil
247
+ end
248
+
249
+ end_match = nil
250
+ if top.end
251
+ end_match = top.match_end( line, match, position )
252
+ end
253
+
254
+ if end_match && ( ! pattern_match || pattern_match.offset.first >= end_match.offset.first )
255
+ pattern_match = end_match
256
+ start_pos = pattern_match.offset.first
257
+ end_pos = pattern_match.offset.last
258
+ processor.close_tag top.contentName, start_pos if top.contentName && processor
259
+ parse_captures "captures", top, pattern_match, processor if processor
260
+ parse_captures "endCaptures", top, pattern_match, processor if processor
261
+ processor.close_tag top.name, end_pos if top.name && processor
262
+ stack.pop
263
+ top, match = stack.last
264
+ else
265
+ break unless pattern
266
+ start_pos = pattern_match.offset.first
267
+ end_pos = pattern_match.offset.last
268
+ if pattern.begin
269
+ processor.open_tag pattern.name, start_pos if pattern.name && processor
270
+ parse_captures "captures", pattern, pattern_match, processor if processor
271
+ parse_captures "beginCaptures", pattern, pattern_match, processor if processor
272
+ processor.open_tag pattern.contentName, end_pos if pattern.contentName && processor
273
+ top = pattern
274
+ match = pattern_match
275
+ stack << [top, match]
276
+ elsif pattern.match
277
+ processor.open_tag pattern.name, start_pos if pattern.name && processor
278
+ parse_captures "captures", pattern, pattern_match, processor if processor
279
+ processor.close_tag pattern.name, end_pos if pattern.name && processor
280
+ end
281
+ end
282
+ position = end_pos
283
+ end
284
+ end
285
+ end
286
+ end
data/lib/textpow.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'yaml'
2
+ require 'oniguruma'
3
+ require 'textpow/syntax'
4
+ require 'textpow/debug_processor'
5
+ require 'textpow/score_manager'
6
+
7
+
8
+ module Textpow
9
+ class ParsingError < Exception; end
10
+ end
data/mm/manual.mm ADDED
@@ -0,0 +1,263 @@
1
+ ~ Textpow ~
2
+ ~~ A Library for Processing TextMate Bundles ~~
3
+
4
+ #Introduction# | index
5
+
6
+ = What is Textpow? =
7
+ Textpow is a library to parse and process
8
+ [Textmate http://macromates.com] bundles. Although created created for their
9
+ use in a word processor, these bundles have many other uses. For example, we
10
+ have used them to create a syntax highligting
11
+ [utility http://ultraviolet.rubyforge.org] and also the markup rendering
12
+ [engine http://mama.rubyforge.org] used to render this
13
+ documentation.
14
+
15
+ = Requirements =
16
+
17
+ * [Oniguruma http://www.geocities.jp/kosako3/oniguruma/] regular expression library (/>= 4.x.x/)
18
+ * [Oniguruma for Ruby http://www.geocities.jp/kosako3/oniguruma/] ruby bindings for oniguruma (/>= 1.1.x/)
19
+
20
+ = Installation =
21
+
22
+ If you have [rubygems http://docs.rubygems.org/] installation is straightforward by typing
23
+ (as root if needed):
24
+
25
+ --------hl shell-unix-generic,,false------
26
+ gem install -r textpow --include-dependencies
27
+ ------------------------------------------
28
+
29
+ If you prefer to get the sources, the last stable version may be
30
+ downloaded [here http://rubyforge.org/frs/?group_id=3513].
31
+
32
+ = Status =
33
+
34
+ The current version of Textpow (0.9.0) is able to parse syntax (/tmLanguage/, /tmSyntax/)
35
+ and theme (/tmTheme/) files.
36
+
37
+ #Use#
38
+
39
+ = Syntax files and text parsing =
40
+
41
+ The idea of parsing is to process some /input text/ using the rules defined in
42
+ a /syntax [file > syntax_files]/ (which are discussed in detail in Textmate
43
+ [documentation http://macromates.com/textmate/manual/language_grammars#language_grammars]).
44
+ The text is parsed line by line, and, events are sent to a /[processor > processors]/
45
+ according to how the text matches the syntax file.
46
+
47
+ -------dot parsing_overview, Overview of the parsing process.----------
48
+ digraph G {
49
+ node [fontname=Helvetica, fontsize=10];
50
+ edge [fontname=Helvetica, style=dashed, fontsize=10, decorate=true, fontcolor="#0066aa"];
51
+ center=true;
52
+ {
53
+ rank=same;
54
+ node [shape=invhouse, style="filled", color="#333399", fillcolor="#6666cc", fontcolor="white"];
55
+ syntax_file[label="Syntax file"];
56
+ text[label="Input text"];
57
+ }
58
+ {
59
+ node [shape=box, style="filled", color="#993333", fillcolor="#cc6666", fontcolor="white"]
60
+ syntax_object[label="SyntaxNode"];
61
+ processor[label="Processor", shape=box];
62
+ }
63
+ parse [label="", style=invis, fontsize=0,height=0,width=0,shape=none];
64
+ rank=same{syntax_object; parse};
65
+
66
+ syntax_file -> syntax_object [label=" SyntaxNode#load"];
67
+ syntax_object -> parse [arrowhead=none];
68
+ text -> parse [arrowhead=none];
69
+ parse -> processor [label=" SyntaxNode#parse"];
70
+
71
+ }
72
+ -----------------------------------------------------------------------
73
+
74
+ = For the impatient =
75
+
76
+ Parsing a file using Textpow is as easy as 1-2-3!
77
+
78
+ 1. Load the Syntax File:
79
+
80
+ ---hl ruby,,false---
81
+ require 'textpow'
82
+ syntax = Textpow::SyntaxNode.load("ruby.tmSyntax")
83
+ -------------
84
+
85
+ 2. Initialize a processor:
86
+ ---hl ruby,,false---
87
+ processor = Textpow::DebugProcessor.new
88
+ -------------
89
+
90
+ 3. Parse some text:
91
+ ---hl ruby,,false---
92
+ syntax.parse( text, processor )
93
+ -------------
94
+
95
+ = The gory details =
96
+
97
+
98
+ == Syntax files == | syntax_files
99
+ At the heart of syntax parsing are ..., well, syntax files. Lets see for instance
100
+ the example syntax that appears in textmate's
101
+ [documentation http://macromates.com/textmate/manual/language_grammars#language_grammars]:
102
+
103
+
104
+ ---------hl property_list---------
105
+ { scopeName = 'source.untitled';
106
+ fileTypes = ( txt );
107
+ foldingStartMarker = '\{\s*$';
108
+ foldingStopMarker = '^\s*\}';
109
+ patterns = (
110
+ { name = 'keyword.control.untitled';
111
+ match = '\b(if|while|for|return)\b';
112
+ },
113
+ { name = 'string.quoted.double.untitled';
114
+ begin = '"';
115
+ end = '"';
116
+ patterns = (
117
+ { name = 'constant.character.escape.untitled';
118
+ match = '\\.';
119
+ }
120
+ );
121
+ },
122
+ );
123
+ }
124
+ ----------------------------------
125
+
126
+ But Textpow is not able to parse text pfiles. However, in practice this is not a problem,
127
+ since it is possible to convert both text and binary pfiles to an XML format. Indeed, all
128
+ the syntaxes in the Textmate syntax [repository http://macromates.com/svn/Bundles/trunk/Bundles/]
129
+ are in XML format:
130
+
131
+ ---------hl xml---------
132
+ <?xml version="1.0" encoding="UTF-8"?>
133
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
134
+ <plist version="1.0">
135
+ <dict>
136
+ <key>scopeName</key>
137
+ <string>source.untitled</string>
138
+ <key>fileTypes</key>
139
+ <array>
140
+ <string>txt</string>
141
+ </array>
142
+ <key>foldingStartMarker</key>
143
+ <string>\{\s*$</string>
144
+ <key>foldingStopMarker</key>
145
+ <string>^\s*\}</string>
146
+ <key>patterns</key>
147
+ <array>
148
+ <dict>
149
+ <key>name</key>
150
+ <string>keyword.control.untitled</string>
151
+ <key>match</key>
152
+ <string>\b(if|while|for|return)\b</string>
153
+ </dict>
154
+ <dict>
155
+ <key>name</key>
156
+ <string>string.quoted.double.untitled</string>
157
+ <key>begin</key>
158
+ <string>"</string>
159
+ <key>end</key>
160
+ <string>"</string>
161
+ <key>patterns</key>
162
+ <array>
163
+ <dict>
164
+ <key>name</key>
165
+ <string>constant.character.escape.untitled</string>
166
+ <key>match</key>
167
+ <string>\\.</string>
168
+ </dict>
169
+ </array>
170
+ </dict>
171
+ </array>
172
+ </dict>
173
+ ------------------------
174
+
175
+ Of course, most people find XML both ugly and cumbersome. Fortunately, it is
176
+ also possible to store syntax files in YAML format, which is much easier to
177
+ read:
178
+
179
+ -------------hl yaml---------------
180
+ ---
181
+ fileTypes:
182
+ - txt
183
+ scopeName: source.untitled
184
+ foldingStartMarker: \{\s*$
185
+ foldingStopMarker: ^\s*\}
186
+ patterns:
187
+ - name: keyword.control.untitled
188
+ match: \b(if|while|for|return)\b
189
+ - name: string.quoted.double.untitled
190
+ begin: '"'
191
+ end: '"'
192
+ patterns:
193
+ - name: constant.character.escape.untitled
194
+ match: \\.
195
+ -----------------------------------
196
+
197
+ == Processors == | processors
198
+
199
+ Until now we have talked about the parsing process without explaining what
200
+ it is exactly. Basically, parsing consists in reading text from a string or
201
+ file and applying tags to parts of the text according to what has been
202
+ specified in the [syntax file > syntax_files].
203
+
204
+ In textpow, the process takes place line by line, from the beginning to the
205
+ end and from left to right for every line. As the text is parsed, events are
206
+ sent to a /processor/ object when a tag is open or closed and so on.
207
+ A processor is any object which implements one or more of the following
208
+ methods:
209
+
210
+ ------------hl ruby--------------
211
+ class Processor
212
+ def open_tag name, position
213
+ end
214
+
215
+ def close_tag name, position
216
+ end
217
+
218
+ def new_line line
219
+ end
220
+
221
+ def start_parsing
222
+ end
223
+
224
+ def end_parsing
225
+ end
226
+ end
227
+ ---------------------------------
228
+
229
+ * `open_tag`. Is called when a new tag is opened, it receives the tag's name and
230
+ its position (relative to the current line).
231
+ * `close_tag`. The same that `open_tag`, but it is called when a tag is closed.
232
+ * `new_line`. Is called every time that a new line is processed, it receives the
233
+ line's contents.
234
+ * `start_parsing`. Is called once at the beginning of the parsing process.
235
+ * `end_parsing`. Is called once after all the input text has been parsed.
236
+
237
+ Textpow ensures that the methods are called in parsing order, thus,
238
+ for example, if there are two subsequent calls to `open_tag`, the first
239
+ having `name="text.string", position=10` and the second having
240
+ `name="markup.string", position=10`, it should be understood that the
241
+ `"markup.string"` tag is /inside/ the `"text.string"` tag.
242
+
243
+ # Links #
244
+
245
+
246
+ = Rubyforge project page =
247
+
248
+ * [Textpow http://rubyforge.org/projects/textpow].
249
+
250
+ = Documentation =
251
+
252
+ * [Textmate documentation http://macromates.com/textmate/manual/].
253
+ * [YAML cookbook http://yaml4r.sourceforge.net/cookbook/].
254
+
255
+ = Requirements =
256
+
257
+ * [Oniguruma http://www.geocities.jp/kosako3/oniguruma/].
258
+ * [Oniguruma for ruby http://rubyforge.org/projects/oniguruma/].
259
+
260
+ = Projects using Textpow =
261
+
262
+ * [Ultraviolet Syntax Highlighting Engine http://ultraviolet.rubyforge.org/].
263
+ * [Macaronic markup engine http://mama.rubyforge.org/].
@@ -0,0 +1,24 @@
1
+ require 'textpow'
2
+ require 'test/unit'
3
+
4
+ class ScoreManagerTest < Test::Unit::TestCase
5
+ include Textpow
6
+
7
+ def test_score
8
+ sp = ScoreManager.new
9
+ reference_scope = 'text.html.basic source.php.embedded.html string.quoted.double.php'
10
+
11
+ assert_not_equal( 0, sp.score( 'source.php string', reference_scope ) )
12
+ assert_not_equal( 0, sp.score( 'text.html source.php', reference_scope ) )
13
+ assert_equal( 0, sp.score( 'string source.php', reference_scope ) )
14
+ assert_equal( 0, sp.score( 'source.php text.html', reference_scope ) )
15
+
16
+ assert_equal( 0, sp.score( 'text.html source.php - string', reference_scope ) )
17
+ assert_not_equal( 0, sp.score( 'text.html source.php - ruby', reference_scope ) )
18
+
19
+ assert( sp.score( 'string', reference_scope ) > sp.score( 'source.php', reference_scope ) )
20
+ assert( sp.score( 'string.quoted', reference_scope ) > sp.score( 'source.php', reference_scope ) )
21
+ assert( sp.score( 'text source string', reference_scope ) > sp.score( 'source string', reference_scope ) )
22
+ end
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: textpow
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.9.0
7
+ date: 2007-05-12 00:00:00 +02:00
8
+ summary: An engine for parsing Textmate bundles
9
+ require_paths:
10
+ - lib
11
+ email: dichodaemon@gmail.com
12
+ homepage: http://textpow.rubyforge.org
13
+ rubyforge_project: textpow
14
+ description: A library for parsing TextMate[http://macromates.com/] bundles.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Dizan Vasquez
31
+ files:
32
+ - bin/plist2yaml
33
+ - bin/plist2syntax
34
+ - lib/textpow.rb
35
+ - lib/textpow
36
+ - lib/textpow/syntax.rb
37
+ - lib/textpow/score_manager.rb
38
+ - lib/textpow/debug_processor.rb
39
+ - mm/manual.mm
40
+ - test/test_textpow.rb
41
+ - History.txt
42
+ - Rakefile
43
+ - Manifest.txt
44
+ - README.txt
45
+ test_files:
46
+ - test/test_textpow.rb
47
+ rdoc_options: []
48
+
49
+ extra_rdoc_files: []
50
+
51
+ executables:
52
+ - plist2yaml
53
+ - plist2syntax
54
+ extensions: []
55
+
56
+ requirements: []
57
+
58
+ dependencies:
59
+ - !ruby/object:Gem::Dependency
60
+ name: oniguruma
61
+ version_requirement:
62
+ version_requirements: !ruby/object:Gem::Version::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 1.1.0
67
+ version:
68
+ - !ruby/object:Gem::Dependency
69
+ name: plist
70
+ version_requirement:
71
+ version_requirements: !ruby/object:Gem::Version::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 3.0.0
76
+ version: