raldred-coderay 0.9.0 → 0.9.339
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/lib/README +128 -0
- data/lib/coderay.rb +319 -0
- data/lib/coderay/duo.rb +85 -0
- data/lib/coderay/encoder.rb +187 -0
- data/lib/coderay/encoders/_map.rb +9 -0
- data/lib/coderay/encoders/count.rb +21 -0
- data/lib/coderay/encoders/debug.rb +49 -0
- data/lib/coderay/encoders/div.rb +20 -0
- data/lib/coderay/encoders/html.rb +306 -0
- data/lib/coderay/encoders/html/css.rb +70 -0
- data/lib/coderay/encoders/html/numerization.rb +133 -0
- data/lib/coderay/encoders/html/output.rb +206 -0
- data/lib/coderay/encoders/json.rb +19 -0
- data/lib/coderay/encoders/null.rb +26 -0
- data/lib/coderay/encoders/page.rb +21 -0
- data/lib/coderay/encoders/span.rb +20 -0
- data/lib/coderay/encoders/statistic.rb +77 -0
- data/lib/coderay/encoders/term.rb +114 -0
- data/lib/coderay/encoders/text.rb +32 -0
- data/lib/coderay/encoders/tokens.rb +44 -0
- data/lib/coderay/encoders/xml.rb +71 -0
- data/lib/coderay/encoders/yaml.rb +22 -0
- data/lib/coderay/for_redcloth.rb +73 -0
- data/lib/coderay/helpers/file_type.rb +226 -0
- data/lib/coderay/helpers/gzip_simple.rb +123 -0
- data/lib/coderay/helpers/plugin.rb +339 -0
- data/lib/coderay/helpers/word_list.rb +124 -0
- data/lib/coderay/scanner.rb +271 -0
- data/lib/coderay/scanners/_map.rb +21 -0
- data/lib/coderay/scanners/c.rb +166 -0
- data/lib/coderay/scanners/css.rb +202 -0
- data/lib/coderay/scanners/debug.rb +61 -0
- data/lib/coderay/scanners/delphi.rb +150 -0
- data/lib/coderay/scanners/diff.rb +104 -0
- data/lib/coderay/scanners/groovy.rb +271 -0
- data/lib/coderay/scanners/html.rb +175 -0
- data/lib/coderay/scanners/java.rb +173 -0
- data/lib/coderay/scanners/java/builtin_types.rb +419 -0
- data/lib/coderay/scanners/java_script.rb +195 -0
- data/lib/coderay/scanners/json.rb +107 -0
- data/lib/coderay/scanners/nitro_xhtml.rb +132 -0
- data/lib/coderay/scanners/php.rb +404 -0
- data/lib/coderay/scanners/plaintext.rb +18 -0
- data/lib/coderay/scanners/python.rb +232 -0
- data/lib/coderay/scanners/rhtml.rb +71 -0
- data/lib/coderay/scanners/ruby.rb +386 -0
- data/lib/coderay/scanners/ruby/patterns.rb +232 -0
- data/lib/coderay/scanners/scheme.rb +142 -0
- data/lib/coderay/scanners/sql.rb +162 -0
- data/lib/coderay/scanners/xml.rb +17 -0
- data/lib/coderay/scanners/yaml.rb +142 -0
- data/lib/coderay/style.rb +20 -0
- data/lib/coderay/styles/_map.rb +7 -0
- data/lib/coderay/styles/cycnus.rb +151 -0
- data/lib/coderay/styles/murphy.rb +132 -0
- data/lib/coderay/token_classes.rb +86 -0
- data/lib/coderay/tokens.rb +387 -0
- metadata +59 -1
@@ -0,0 +1,226 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module CodeRay
|
3
|
+
|
4
|
+
# = FileType
|
5
|
+
#
|
6
|
+
# A simple filetype recognizer.
|
7
|
+
#
|
8
|
+
# Copyright (c) 2006 by murphy (Kornelius Kalnbach) <murphy rubychan de>
|
9
|
+
#
|
10
|
+
# License:: LGPL / ask the author
|
11
|
+
# Version:: 0.1 (2005-09-01)
|
12
|
+
#
|
13
|
+
# == Documentation
|
14
|
+
#
|
15
|
+
# # determine the type of the given
|
16
|
+
# lang = FileType[ARGV.first]
|
17
|
+
#
|
18
|
+
# # return :plaintext if the file type is unknown
|
19
|
+
# lang = FileType.fetch ARGV.first, :plaintext
|
20
|
+
#
|
21
|
+
# # try the shebang line, too
|
22
|
+
# lang = FileType.fetch ARGV.first, :plaintext, true
|
23
|
+
module FileType
|
24
|
+
|
25
|
+
UnknownFileType = Class.new Exception
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
# Try to determine the file type of the file.
|
30
|
+
#
|
31
|
+
# +filename+ is a relative or absolute path to a file.
|
32
|
+
#
|
33
|
+
# The file itself is only accessed when +read_shebang+ is set to true.
|
34
|
+
# That means you can get filetypes from files that don't exist.
|
35
|
+
def [] filename, read_shebang = false
|
36
|
+
name = File.basename filename
|
37
|
+
ext = File.extname(name).sub(/^\./, '') # from last dot, delete the leading dot
|
38
|
+
ext2 = filename[/\.(.*)/, 1] # from first dot
|
39
|
+
|
40
|
+
type =
|
41
|
+
TypeFromExt[ext.downcase] ||
|
42
|
+
(TypeFromExt[ext2.downcase] if ext2) ||
|
43
|
+
TypeFromName[name] ||
|
44
|
+
TypeFromName[name.downcase]
|
45
|
+
type ||= shebang(filename) if read_shebang
|
46
|
+
|
47
|
+
type
|
48
|
+
end
|
49
|
+
|
50
|
+
def shebang filename
|
51
|
+
begin
|
52
|
+
File.open filename, 'r' do |f|
|
53
|
+
if first_line = f.gets
|
54
|
+
if type = first_line[TypeFromShebang]
|
55
|
+
type.to_sym
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
rescue IOError
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# This works like Hash#fetch.
|
65
|
+
#
|
66
|
+
# If the filetype cannot be found, the +default+ value
|
67
|
+
# is returned.
|
68
|
+
def fetch filename, default = nil, read_shebang = false
|
69
|
+
if default and block_given?
|
70
|
+
warn 'block supersedes default value argument'
|
71
|
+
end
|
72
|
+
|
73
|
+
unless type = self[filename, read_shebang]
|
74
|
+
return yield if block_given?
|
75
|
+
return default if default
|
76
|
+
raise UnknownFileType, 'Could not determine type of %p.' % filename
|
77
|
+
end
|
78
|
+
type
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
TypeFromExt = {
|
84
|
+
'c' => :c,
|
85
|
+
'cpp' => :c,
|
86
|
+
'css' => :css,
|
87
|
+
'diff' => :diff,
|
88
|
+
'groovy' => :groovy,
|
89
|
+
'gvy' => :groovy,
|
90
|
+
'h' => :c,
|
91
|
+
'htm' => :html,
|
92
|
+
'html' => :html,
|
93
|
+
'html.erb' => :rhtml,
|
94
|
+
'java' => :java,
|
95
|
+
'js' => :java_script,
|
96
|
+
'json' => :json,
|
97
|
+
'mab' => :ruby,
|
98
|
+
'patch' => :diff,
|
99
|
+
'php' => :php,
|
100
|
+
'php3' => :php,
|
101
|
+
'php4' => :php,
|
102
|
+
'php5' => :php,
|
103
|
+
'py' => :python,
|
104
|
+
'py3' => :python,
|
105
|
+
'pyw' => :python,
|
106
|
+
'rake' => :ruby,
|
107
|
+
'raydebug' => :debug,
|
108
|
+
'rb' => :ruby,
|
109
|
+
'rbw' => :ruby,
|
110
|
+
'rhtml' => :rhtml,
|
111
|
+
'sch' => :scheme,
|
112
|
+
'sql' => :sql,
|
113
|
+
'ss' => :scheme,
|
114
|
+
'xhtml' => :xhtml,
|
115
|
+
'xml' => :xml,
|
116
|
+
'yaml' => :yaml,
|
117
|
+
'yml' => :yaml,
|
118
|
+
}
|
119
|
+
|
120
|
+
TypeFromShebang = /\b(?:ruby|perl|python|sh)\b/
|
121
|
+
|
122
|
+
TypeFromName = {
|
123
|
+
'Rakefile' => :ruby,
|
124
|
+
'Rantfile' => :ruby,
|
125
|
+
}
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
if $0 == __FILE__
|
132
|
+
$VERBOSE = true
|
133
|
+
eval DATA.read, nil, $0, __LINE__ + 4
|
134
|
+
end
|
135
|
+
|
136
|
+
__END__
|
137
|
+
require 'test/unit'
|
138
|
+
|
139
|
+
class TC_FileType < Test::Unit::TestCase
|
140
|
+
|
141
|
+
include CodeRay
|
142
|
+
|
143
|
+
def test_fetch
|
144
|
+
assert_raise FileType::UnknownFileType do
|
145
|
+
FileType.fetch ''
|
146
|
+
end
|
147
|
+
|
148
|
+
assert_throws :not_found do
|
149
|
+
FileType.fetch '.' do
|
150
|
+
throw :not_found
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
assert_equal :default, FileType.fetch('c', :default)
|
155
|
+
|
156
|
+
stderr, fake_stderr = $stderr, Object.new
|
157
|
+
$err = ''
|
158
|
+
def fake_stderr.write x
|
159
|
+
$err << x
|
160
|
+
end
|
161
|
+
$stderr = fake_stderr
|
162
|
+
FileType.fetch('c', :default) { }
|
163
|
+
assert_equal "block supersedes default value argument\n", $err
|
164
|
+
$stderr = stderr
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_ruby
|
168
|
+
assert_equal :ruby, FileType['test.rb']
|
169
|
+
assert_equal :ruby, FileType['C:\\Program Files\\x\\y\\c\\test.rbw']
|
170
|
+
assert_equal :ruby, FileType['/usr/bin/something/Rakefile']
|
171
|
+
assert_equal :ruby, FileType['~/myapp/gem/Rantfile']
|
172
|
+
assert_equal :ruby, FileType['./lib/tasks\repository.rake']
|
173
|
+
assert_not_equal :ruby, FileType['test_rb']
|
174
|
+
assert_not_equal :ruby, FileType['Makefile']
|
175
|
+
assert_not_equal :ruby, FileType['set.rb/set']
|
176
|
+
assert_not_equal :ruby, FileType['~/projects/blabla/rb']
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_c
|
180
|
+
assert_equal :c, FileType['test.c']
|
181
|
+
assert_equal :c, FileType['C:\\Program Files\\x\\y\\c\\test.h']
|
182
|
+
assert_not_equal :c, FileType['test_c']
|
183
|
+
assert_not_equal :c, FileType['Makefile']
|
184
|
+
assert_not_equal :c, FileType['set.h/set']
|
185
|
+
assert_not_equal :c, FileType['~/projects/blabla/c']
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_html
|
189
|
+
assert_equal :html, FileType['test.htm']
|
190
|
+
assert_equal :xhtml, FileType['test.xhtml']
|
191
|
+
assert_equal :xhtml, FileType['test.html.xhtml']
|
192
|
+
assert_equal :rhtml, FileType['_form.rhtml']
|
193
|
+
assert_equal :rhtml, FileType['_form.html.erb']
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_yaml
|
197
|
+
assert_equal :yaml, FileType['test.yml']
|
198
|
+
assert_equal :yaml, FileType['test.yaml']
|
199
|
+
assert_equal :yaml, FileType['my.html.yaml']
|
200
|
+
assert_not_equal :yaml, FileType['YAML']
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_no_shebang
|
204
|
+
dir = './test'
|
205
|
+
if File.directory? dir
|
206
|
+
Dir.chdir dir do
|
207
|
+
assert_equal :c, FileType['test.c']
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_shebang_empty_file
|
213
|
+
require 'tmpdir'
|
214
|
+
tmpfile = File.join(Dir.tmpdir, 'bla')
|
215
|
+
File.open(tmpfile, 'w') { } # touch
|
216
|
+
assert_equal nil, FileType[tmpfile]
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_shebang
|
220
|
+
require 'tmpdir'
|
221
|
+
tmpfile = File.join(Dir.tmpdir, 'bla')
|
222
|
+
File.open(tmpfile, 'w') { |f| f.puts '#!/usr/bin/env ruby' }
|
223
|
+
assert_equal :ruby, FileType[tmpfile, true]
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# =GZip Simple
|
2
|
+
#
|
3
|
+
# A simplified interface to the gzip library +zlib+ (from the Ruby Standard Library.)
|
4
|
+
#
|
5
|
+
# Author: murphy (mail to murphy rubychan de)
|
6
|
+
#
|
7
|
+
# Version: 0.2 (2005.may.28)
|
8
|
+
#
|
9
|
+
# ==Documentation
|
10
|
+
#
|
11
|
+
# See +GZip+ module and the +String+ extensions.
|
12
|
+
#
|
13
|
+
module GZip
|
14
|
+
|
15
|
+
require 'zlib'
|
16
|
+
|
17
|
+
# The default zipping level. 7 zips good and fast.
|
18
|
+
DEFAULT_GZIP_LEVEL = 7
|
19
|
+
|
20
|
+
# Unzips the given string +s+.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
# require 'gzip_simple'
|
24
|
+
# print GZip.gunzip(File.read('adresses.gz'))
|
25
|
+
def GZip.gunzip s
|
26
|
+
Zlib::Inflate.inflate s
|
27
|
+
end
|
28
|
+
|
29
|
+
# Zips the given string +s+.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
# require 'gzip_simple'
|
33
|
+
# File.open('adresses.gz', 'w') do |file
|
34
|
+
# file.write GZip.gzip('Mum: 0123 456 789', 9)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# If you provide a +level+, you can control how strong
|
38
|
+
# the string is compressed:
|
39
|
+
# - 0: no compression, only convert to gzip format
|
40
|
+
# - 1: compress fast
|
41
|
+
# - 7: compress more, but still fast (default)
|
42
|
+
# - 8: compress more, slower
|
43
|
+
# - 9: compress best, very slow
|
44
|
+
def GZip.gzip s, level = DEFAULT_GZIP_LEVEL
|
45
|
+
Zlib::Deflate.new(level).deflate s, Zlib::FINISH
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# String extensions to use the GZip module.
|
51
|
+
#
|
52
|
+
# The methods gzip and gunzip provide an even more simple
|
53
|
+
# interface to the ZLib:
|
54
|
+
#
|
55
|
+
# # create a big string
|
56
|
+
# x = 'a' * 1000
|
57
|
+
#
|
58
|
+
# # zip it
|
59
|
+
# x_gz = x.gzip
|
60
|
+
#
|
61
|
+
# # test the result
|
62
|
+
# puts 'Zipped %d bytes to %d bytes.' % [x.size, x_gz.size]
|
63
|
+
# #-> Zipped 1000 bytes to 19 bytes.
|
64
|
+
#
|
65
|
+
# # unzipping works
|
66
|
+
# p x_gz.gunzip == x #-> true
|
67
|
+
class String
|
68
|
+
# Returns the string, unzipped.
|
69
|
+
# See GZip.gunzip
|
70
|
+
def gunzip
|
71
|
+
GZip.gunzip self
|
72
|
+
end
|
73
|
+
# Replaces the string with its unzipped value.
|
74
|
+
# See GZip.gunzip
|
75
|
+
def gunzip!
|
76
|
+
replace gunzip
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the string, zipped.
|
80
|
+
# +level+ is the gzip compression level, see GZip.gzip.
|
81
|
+
def gzip level = GZip::DEFAULT_GZIP_LEVEL
|
82
|
+
GZip.gzip self, level
|
83
|
+
end
|
84
|
+
# Replaces the string with its zipped value.
|
85
|
+
# See GZip.gzip.
|
86
|
+
def gzip!(*args)
|
87
|
+
replace gzip(*args)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if $0 == __FILE__
|
92
|
+
eval DATA.read, nil, $0, __LINE__+4
|
93
|
+
end
|
94
|
+
|
95
|
+
__END__
|
96
|
+
#CODE
|
97
|
+
|
98
|
+
# Testing / Benchmark
|
99
|
+
x = 'a' * 1000
|
100
|
+
x_gz = x.gzip
|
101
|
+
puts 'Zipped %d bytes to %d bytes.' % [x.size, x_gz.size] #-> Zipped 1000 bytes to 19 bytes.
|
102
|
+
p x_gz.gunzip == x #-> true
|
103
|
+
|
104
|
+
require 'benchmark'
|
105
|
+
|
106
|
+
INFO = 'packed to %0.3f%%' # :nodoc:
|
107
|
+
|
108
|
+
x = Array.new(100000) { rand(255).chr + 'aaaaaaaaa' + rand(255).chr }.join
|
109
|
+
Benchmark.bm(10) do |bm|
|
110
|
+
for level in 0..9
|
111
|
+
bm.report "zip #{level}" do
|
112
|
+
$x = x.gzip level
|
113
|
+
end
|
114
|
+
puts INFO % [100.0 * $x.size / x.size]
|
115
|
+
end
|
116
|
+
bm.report 'zip' do
|
117
|
+
$x = x.gzip
|
118
|
+
end
|
119
|
+
puts INFO % [100.0 * $x.size / x.size]
|
120
|
+
bm.report 'unzip' do
|
121
|
+
$x.gunzip
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,339 @@
|
|
1
|
+
module CodeRay
|
2
|
+
|
3
|
+
# = PluginHost
|
4
|
+
#
|
5
|
+
# A simple subclass plugin system.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
# class Generators < PluginHost
|
9
|
+
# plugin_path 'app/generators'
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class Generator
|
13
|
+
# extend Plugin
|
14
|
+
# PLUGIN_HOST = Generators
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# class FancyGenerator < Generator
|
18
|
+
# register_for :fancy
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Generators[:fancy] #-> FancyGenerator
|
22
|
+
# # or
|
23
|
+
# CodeRay.require_plugin 'Generators/fancy'
|
24
|
+
module PluginHost
|
25
|
+
|
26
|
+
# Raised if Encoders::[] fails because:
|
27
|
+
# * a file could not be found
|
28
|
+
# * the requested Encoder is not registered
|
29
|
+
PluginNotFound = Class.new Exception
|
30
|
+
HostNotFound = Class.new Exception
|
31
|
+
|
32
|
+
PLUGIN_HOSTS = []
|
33
|
+
PLUGIN_HOSTS_BY_ID = {} # dummy hash
|
34
|
+
|
35
|
+
# Loads all plugins using list and load.
|
36
|
+
def load_all
|
37
|
+
for plugin in list
|
38
|
+
load plugin
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the Plugin for +id+.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
# yaml_plugin = MyPluginHost[:yaml]
|
46
|
+
def [] id, *args, &blk
|
47
|
+
plugin = validate_id(id)
|
48
|
+
begin
|
49
|
+
plugin = plugin_hash.[] plugin, *args, &blk
|
50
|
+
end while plugin.is_a? Symbol
|
51
|
+
plugin
|
52
|
+
end
|
53
|
+
|
54
|
+
# Alias for +[]+.
|
55
|
+
alias load []
|
56
|
+
|
57
|
+
def require_helper plugin_id, helper_name
|
58
|
+
path = path_to File.join(plugin_id, helper_name)
|
59
|
+
require path
|
60
|
+
end
|
61
|
+
|
62
|
+
class << self
|
63
|
+
|
64
|
+
# Adds the module/class to the PLUGIN_HOSTS list.
|
65
|
+
def extended mod
|
66
|
+
PLUGIN_HOSTS << mod
|
67
|
+
end
|
68
|
+
|
69
|
+
# Warns you that you should not #include this module.
|
70
|
+
def included mod
|
71
|
+
warn "#{name} should not be included. Use extend."
|
72
|
+
end
|
73
|
+
|
74
|
+
# Find the PluginHost for host_id.
|
75
|
+
def host_by_id host_id
|
76
|
+
unless PLUGIN_HOSTS_BY_ID.default_proc
|
77
|
+
ph = Hash.new do |h, a_host_id|
|
78
|
+
for host in PLUGIN_HOSTS
|
79
|
+
h[host.host_id] = host
|
80
|
+
end
|
81
|
+
h.fetch a_host_id, nil
|
82
|
+
end
|
83
|
+
PLUGIN_HOSTS_BY_ID.replace ph
|
84
|
+
end
|
85
|
+
PLUGIN_HOSTS_BY_ID[host_id]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
# The path where the plugins can be found.
|
91
|
+
def plugin_path *args
|
92
|
+
unless args.empty?
|
93
|
+
@plugin_path = File.expand_path File.join(*args)
|
94
|
+
load_map
|
95
|
+
end
|
96
|
+
@plugin_path
|
97
|
+
end
|
98
|
+
|
99
|
+
# The host's ID.
|
100
|
+
#
|
101
|
+
# If PLUGIN_HOST_ID is not set, it is simply the class name.
|
102
|
+
def host_id
|
103
|
+
if self.const_defined? :PLUGIN_HOST_ID
|
104
|
+
self::PLUGIN_HOST_ID
|
105
|
+
else
|
106
|
+
name
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Map a plugin_id to another.
|
111
|
+
#
|
112
|
+
# Usage: Put this in a file plugin_path/_map.rb.
|
113
|
+
#
|
114
|
+
# class MyColorHost < PluginHost
|
115
|
+
# map :navy => :dark_blue,
|
116
|
+
# :maroon => :brown,
|
117
|
+
# :luna => :moon
|
118
|
+
# end
|
119
|
+
def map hash
|
120
|
+
for from, to in hash
|
121
|
+
from = validate_id from
|
122
|
+
to = validate_id to
|
123
|
+
plugin_hash[from] = to unless plugin_hash.has_key? from
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Define the default plugin to use when no plugin is found
|
128
|
+
# for a given id.
|
129
|
+
#
|
130
|
+
# See also map.
|
131
|
+
#
|
132
|
+
# class MyColorHost < PluginHost
|
133
|
+
# map :navy => :dark_blue
|
134
|
+
# default :gray
|
135
|
+
# end
|
136
|
+
def default id = nil
|
137
|
+
if id
|
138
|
+
id = validate_id id
|
139
|
+
plugin_hash[nil] = id
|
140
|
+
else
|
141
|
+
plugin_hash[nil]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Every plugin must register itself for one or more
|
146
|
+
# +ids+ by calling register_for, which calls this method.
|
147
|
+
#
|
148
|
+
# See Plugin#register_for.
|
149
|
+
def register plugin, *ids
|
150
|
+
for id in ids
|
151
|
+
unless id.is_a? Symbol
|
152
|
+
raise ArgumentError,
|
153
|
+
"id must be a Symbol, but it was a #{id.class}"
|
154
|
+
end
|
155
|
+
plugin_hash[validate_id(id)] = plugin
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# A Hash of plugion_id => Plugin pairs.
|
160
|
+
def plugin_hash
|
161
|
+
@plugin_hash ||= create_plugin_hash
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns an array of all .rb files in the plugin path.
|
165
|
+
#
|
166
|
+
# The extension .rb is not included.
|
167
|
+
def list
|
168
|
+
Dir[path_to('*')].select do |file|
|
169
|
+
File.basename(file)[/^(?!_)\w+\.rb$/]
|
170
|
+
end.map do |file|
|
171
|
+
File.basename file, '.rb'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Makes a map of all loaded plugins.
|
176
|
+
def inspect
|
177
|
+
map = plugin_hash.dup
|
178
|
+
map.each do |id, plugin|
|
179
|
+
map[id] = plugin.to_s[/(?>[\w_]+)$/]
|
180
|
+
end
|
181
|
+
"#{name}[#{host_id}]#{map.inspect}"
|
182
|
+
end
|
183
|
+
|
184
|
+
protected
|
185
|
+
# Created a new plugin list and stores it to @plugin_hash.
|
186
|
+
def create_plugin_hash
|
187
|
+
@plugin_hash =
|
188
|
+
Hash.new do |h, plugin_id|
|
189
|
+
id = validate_id(plugin_id)
|
190
|
+
path = path_to id
|
191
|
+
begin
|
192
|
+
require path
|
193
|
+
rescue LoadError => boom
|
194
|
+
if h.has_key? nil # default plugin
|
195
|
+
h[id] = h[nil]
|
196
|
+
else
|
197
|
+
raise PluginNotFound, 'Could not load plugin %p: %s' % [id, boom]
|
198
|
+
end
|
199
|
+
else
|
200
|
+
# Plugin should have registered by now
|
201
|
+
unless h.has_key? id
|
202
|
+
raise PluginNotFound,
|
203
|
+
"No #{self.name} plugin for #{id.inspect} found in #{path}."
|
204
|
+
end
|
205
|
+
end
|
206
|
+
h[id]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Loads the map file (see map).
|
211
|
+
#
|
212
|
+
# This is done automatically when plugin_path is called.
|
213
|
+
def load_map
|
214
|
+
mapfile = path_to '_map'
|
215
|
+
if File.exist? mapfile
|
216
|
+
require mapfile
|
217
|
+
elsif $DEBUG
|
218
|
+
warn 'no _map.rb found for %s' % name
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Returns the Plugin for +id+.
|
223
|
+
# Use it like Hash#fetch.
|
224
|
+
#
|
225
|
+
# Example:
|
226
|
+
# yaml_plugin = MyPluginHost[:yaml, :default]
|
227
|
+
def fetch id, *args, &blk
|
228
|
+
plugin_hash.fetch validate_id(id), *args, &blk
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns the expected path to the plugin file for the given id.
|
232
|
+
def path_to plugin_id
|
233
|
+
File.join plugin_path, "#{plugin_id}.rb"
|
234
|
+
end
|
235
|
+
|
236
|
+
# Converts +id+ to a Symbol if it is a String,
|
237
|
+
# or returns +id+ if it already is a Symbol.
|
238
|
+
#
|
239
|
+
# Raises +ArgumentError+ for all other objects, or if the
|
240
|
+
# given String includes non-alphanumeric characters (\W).
|
241
|
+
def validate_id id
|
242
|
+
if id.is_a? Symbol or id.nil?
|
243
|
+
id
|
244
|
+
elsif id.is_a? String
|
245
|
+
if id[/\w+/] == id
|
246
|
+
id.downcase.to_sym
|
247
|
+
else
|
248
|
+
raise ArgumentError, "Invalid id: '#{id}' given."
|
249
|
+
end
|
250
|
+
else
|
251
|
+
raise ArgumentError,
|
252
|
+
"String or Symbol expected, but #{id.class} given."
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# = Plugin
|
260
|
+
#
|
261
|
+
# Plugins have to include this module.
|
262
|
+
#
|
263
|
+
# IMPORTANT: use extend for this module.
|
264
|
+
#
|
265
|
+
# Example: see PluginHost.
|
266
|
+
module Plugin
|
267
|
+
|
268
|
+
def included mod
|
269
|
+
warn "#{name} should not be included. Use extend."
|
270
|
+
end
|
271
|
+
|
272
|
+
# Register this class for the given langs.
|
273
|
+
# Example:
|
274
|
+
# class MyPlugin < PluginHost::BaseClass
|
275
|
+
# register_for :my_id
|
276
|
+
# ...
|
277
|
+
# end
|
278
|
+
#
|
279
|
+
# See PluginHost.register.
|
280
|
+
def register_for *ids
|
281
|
+
plugin_host.register self, *ids
|
282
|
+
end
|
283
|
+
|
284
|
+
# The host for this Plugin class.
|
285
|
+
def plugin_host host = nil
|
286
|
+
if host and not host.is_a? PluginHost
|
287
|
+
raise ArgumentError,
|
288
|
+
"PluginHost expected, but #{host.class} given."
|
289
|
+
end
|
290
|
+
self.const_set :PLUGIN_HOST, host if host
|
291
|
+
self::PLUGIN_HOST
|
292
|
+
end
|
293
|
+
|
294
|
+
# Require some helper files.
|
295
|
+
#
|
296
|
+
# Example:
|
297
|
+
#
|
298
|
+
# class MyPlugin < PluginHost::BaseClass
|
299
|
+
# register_for :my_id
|
300
|
+
# helper :my_helper
|
301
|
+
#
|
302
|
+
# The above example loads the file myplugin/my_helper.rb relative to the
|
303
|
+
# file in which MyPlugin was defined.
|
304
|
+
#
|
305
|
+
# You can also load a helper from a different plugin:
|
306
|
+
#
|
307
|
+
# helper 'other_plugin/other_helper'
|
308
|
+
def helper *helpers
|
309
|
+
for helper in helpers
|
310
|
+
if helper.is_a?(String) && helper[/\//]
|
311
|
+
self::PLUGIN_HOST.require_helper $`, $'
|
312
|
+
else
|
313
|
+
self::PLUGIN_HOST.require_helper plugin_id, helper.to_s
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# Returns the pulgin id used by the engine.
|
319
|
+
def plugin_id
|
320
|
+
name[/[\w_]+$/].downcase
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
# Convenience method for plugin loading.
|
326
|
+
# The syntax used is:
|
327
|
+
#
|
328
|
+
# CodeRay.require_plugin '<Host ID>/<Plugin ID>'
|
329
|
+
#
|
330
|
+
# Returns the loaded plugin.
|
331
|
+
def self.require_plugin path
|
332
|
+
host_id, plugin_id = path.split '/', 2
|
333
|
+
host = PluginHost.host_by_id(host_id)
|
334
|
+
raise PluginHost::HostNotFound,
|
335
|
+
"No host for #{host_id.inspect} found." unless host
|
336
|
+
host.load plugin_id
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|