textpow 0.9.0

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/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: