Update CodeRay version to 1.0 final (#4264).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@7618 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Etienne Massip
2011-10-08 13:34:30 +00:00
parent 6ebb5e834c
commit d1efb4f148
115 changed files with 3482 additions and 17507 deletions

View File

@@ -0,0 +1,141 @@
module CodeRay
# = FileType
#
# A simple filetype recognizer.
#
# == Usage
#
# # determine the type of the given
# lang = FileType[file_name]
#
# # return :text if the file type is unknown
# lang = FileType.fetch file_name, :text
#
# # try the shebang line, too
# lang = FileType.fetch file_name, :text, true
module FileType
UnknownFileType = Class.new Exception
class << self
# Try to determine the file type of the file.
#
# +filename+ is a relative or absolute path to a file.
#
# The file itself is only accessed when +read_shebang+ is set to true.
# That means you can get filetypes from files that don't exist.
def [] filename, read_shebang = false
name = File.basename filename
ext = File.extname(name).sub(/^\./, '') # from last dot, delete the leading dot
ext2 = filename.to_s[/\.(.*)/, 1] # from first dot
type =
TypeFromExt[ext] ||
TypeFromExt[ext.downcase] ||
(TypeFromExt[ext2] if ext2) ||
(TypeFromExt[ext2.downcase] if ext2) ||
TypeFromName[name] ||
TypeFromName[name.downcase]
type ||= shebang(filename) if read_shebang
type
end
# This works like Hash#fetch.
#
# If the filetype cannot be found, the +default+ value
# is returned.
def fetch filename, default = nil, read_shebang = false
if default && block_given?
warn 'Block supersedes default value argument; use either.'
end
if type = self[filename, read_shebang]
type
else
return yield if block_given?
return default if default
raise UnknownFileType, 'Could not determine type of %p.' % filename
end
end
protected
def shebang filename
return unless File.exist? filename
File.open filename, 'r' do |f|
if first_line = f.gets
if type = first_line[TypeFromShebang]
type.to_sym
end
end
end
end
end
TypeFromExt = {
'c' => :c,
'cfc' => :xml,
'cfm' => :xml,
'clj' => :clojure,
'css' => :css,
'diff' => :diff,
'dpr' => :delphi,
'gemspec' => :ruby,
'groovy' => :groovy,
'gvy' => :groovy,
'h' => :c,
'haml' => :haml,
'htm' => :page,
'html' => :page,
'html.erb' => :erb,
'java' => :java,
'js' => :java_script,
'json' => :json,
'mab' => :ruby,
'pas' => :delphi,
'patch' => :diff,
'php' => :php,
'php3' => :php,
'php4' => :php,
'php5' => :php,
'prawn' => :ruby,
'py' => :python,
'py3' => :python,
'pyw' => :python,
'rake' => :ruby,
'raydebug' => :raydebug,
'rb' => :ruby,
'rbw' => :ruby,
'rhtml' => :erb,
'rjs' => :ruby,
'rpdf' => :ruby,
'ru' => :ruby,
'rxml' => :ruby,
# 'sch' => :scheme,
'sql' => :sql,
# 'ss' => :scheme,
'xhtml' => :page,
'xml' => :xml,
'yaml' => :yaml,
'yml' => :yaml,
}
for cpp_alias in %w[cc cpp cp cxx c++ C hh hpp h++ cu]
TypeFromExt[cpp_alias] = :cpp
end
TypeFromShebang = /\b(?:ruby|perl|python|sh)\b/
TypeFromName = {
'Capfile' => :ruby,
'Rakefile' => :ruby,
'Rantfile' => :ruby,
'Gemfile' => :ruby,
}
end
end

View File

@@ -0,0 +1,41 @@
module CodeRay
# A simplified interface to the gzip library +zlib+ (from the Ruby Standard Library.)
module GZip
require 'zlib'
# The default zipping level. 7 zips good and fast.
DEFAULT_GZIP_LEVEL = 7
# Unzips the given string +s+.
#
# Example:
# require 'gzip_simple'
# print GZip.gunzip(File.read('adresses.gz'))
def GZip.gunzip s
Zlib::Inflate.inflate s
end
# Zips the given string +s+.
#
# Example:
# require 'gzip_simple'
# File.open('adresses.gz', 'w') do |file
# file.write GZip.gzip('Mum: 0123 456 789', 9)
# end
#
# If you provide a +level+, you can control how strong
# the string is compressed:
# - 0: no compression, only convert to gzip format
# - 1: compress fast
# - 7: compress more, but still fast (default)
# - 8: compress more, slower
# - 9: compress best, very slow
def GZip.gzip s, level = DEFAULT_GZIP_LEVEL
Zlib::Deflate.new(level).deflate s, Zlib::FINISH
end
end
end

View File

@@ -0,0 +1,284 @@
module CodeRay
# = PluginHost
#
# A simple subclass/subfolder plugin system.
#
# Example:
# class Generators
# extend PluginHost
# plugin_path 'app/generators'
# end
#
# class Generator
# extend Plugin
# PLUGIN_HOST = Generators
# end
#
# class FancyGenerator < Generator
# register_for :fancy
# end
#
# Generators[:fancy] #-> FancyGenerator
# # or
# CodeRay.require_plugin 'Generators/fancy'
# # or
# Generators::Fancy
module PluginHost
# Raised if Encoders::[] fails because:
# * a file could not be found
# * the requested Plugin is not registered
PluginNotFound = Class.new LoadError
HostNotFound = Class.new LoadError
PLUGIN_HOSTS = []
PLUGIN_HOSTS_BY_ID = {} # dummy hash
# Loads all plugins using list and load.
def load_all
for plugin in list
load plugin
end
end
# Returns the Plugin for +id+.
#
# Example:
# yaml_plugin = MyPluginHost[:yaml]
def [] id, *args, &blk
plugin = validate_id(id)
begin
plugin = plugin_hash.[] plugin, *args, &blk
end while plugin.is_a? Symbol
plugin
end
alias load []
# Tries to +load+ the missing plugin by translating +const+ to the
# underscore form (eg. LinesOfCode becomes lines_of_code).
def const_missing const
id = const.to_s.
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
downcase
load id
end
class << self
# Adds the module/class to the PLUGIN_HOSTS list.
def extended mod
PLUGIN_HOSTS << mod
end
end
# The path where the plugins can be found.
def plugin_path *args
unless args.empty?
@plugin_path = File.expand_path File.join(*args)
end
@plugin_path ||= ''
end
# Map a plugin_id to another.
#
# Usage: Put this in a file plugin_path/_map.rb.
#
# class MyColorHost < PluginHost
# map :navy => :dark_blue,
# :maroon => :brown,
# :luna => :moon
# end
def map hash
for from, to in hash
from = validate_id from
to = validate_id to
plugin_hash[from] = to unless plugin_hash.has_key? from
end
end
# Define the default plugin to use when no plugin is found
# for a given id, or return the default plugin.
#
# See also map.
#
# class MyColorHost < PluginHost
# map :navy => :dark_blue
# default :gray
# end
#
# MyColorHost.default # loads and returns the Gray plugin
def default id = nil
if id
id = validate_id id
raise "The default plugin can't be named \"default\"." if id == :default
plugin_hash[:default] = id
else
load :default
end
end
# Every plugin must register itself for +id+ by calling register_for,
# which calls this method.
#
# See Plugin#register_for.
def register plugin, id
plugin_hash[validate_id(id)] = plugin
end
# A Hash of plugion_id => Plugin pairs.
def plugin_hash
@plugin_hash ||= make_plugin_hash
end
# Returns an array of all .rb files in the plugin path.
#
# The extension .rb is not included.
def list
Dir[path_to('*')].select do |file|
File.basename(file)[/^(?!_)\w+\.rb$/]
end.map do |file|
File.basename(file, '.rb').to_sym
end
end
# Returns an array of all Plugins.
#
# Note: This loads all plugins using load_all.
def all_plugins
load_all
plugin_hash.values.grep(Class)
end
# Loads the map file (see map).
#
# This is done automatically when plugin_path is called.
def load_plugin_map
mapfile = path_to '_map'
@plugin_map_loaded = true
if File.exist? mapfile
require mapfile
true
else
false
end
end
protected
# Return a plugin hash that automatically loads plugins.
def make_plugin_hash
@plugin_map_loaded ||= false
Hash.new do |h, plugin_id|
id = validate_id(plugin_id)
path = path_to id
begin
raise LoadError, "#{path} not found" unless File.exist? path
require path
rescue LoadError => boom
if @plugin_map_loaded
if h.has_key?(:default)
warn '%p could not load plugin %p; falling back to %p' % [self, id, h[:default]]
h[:default]
else
raise PluginNotFound, '%p could not load plugin %p: %s' % [self, id, boom]
end
else
load_plugin_map
h[plugin_id]
end
else
# Plugin should have registered by now
if h.has_key? id
h[id]
else
raise PluginNotFound, "No #{self.name} plugin for #{id.inspect} found in #{path}."
end
end
end
end
# Returns the expected path to the plugin file for the given id.
def path_to plugin_id
File.join plugin_path, "#{plugin_id}.rb"
end
# Converts +id+ to a Symbol if it is a String,
# or returns +id+ if it already is a Symbol.
#
# Raises +ArgumentError+ for all other objects, or if the
# given String includes non-alphanumeric characters (\W).
def validate_id id
if id.is_a? Symbol or id.nil?
id
elsif id.is_a? String
if id[/\w+/] == id
id.downcase.to_sym
else
raise ArgumentError, "Invalid id given: #{id}"
end
else
raise ArgumentError, "String or Symbol expected, but #{id.class} given."
end
end
end
# = Plugin
#
# Plugins have to include this module.
#
# IMPORTANT: Use extend for this module.
#
# See CodeRay::PluginHost for examples.
module Plugin
attr_reader :plugin_id
# Register this class for the given +id+.
#
# Example:
# class MyPlugin < PluginHost::BaseClass
# register_for :my_id
# ...
# end
#
# See PluginHost.register.
def register_for id
@plugin_id = id
plugin_host.register self, id
end
# Returns the title of the plugin, or sets it to the
# optional argument +title+.
def title title = nil
if title
@title = title.to_s
else
@title ||= name[/([^:]+)$/, 1]
end
end
# The PluginHost for this Plugin class.
def plugin_host host = nil
if host.is_a? PluginHost
const_set :PLUGIN_HOST, host
end
self::PLUGIN_HOST
end
def aliases
plugin_host.load_plugin_map
plugin_host.plugin_hash.inject [] do |aliases, (key, _)|
aliases << key if plugin_host[key] == self
aliases
end
end
end
end

View File

@@ -0,0 +1,77 @@
module CodeRay
# = WordList
#
# <b>A Hash subclass designed for mapping word lists to token types.</b>
#
# Copyright (c) 2006-2011 by murphy (Kornelius Kalnbach) <murphy rubychan de>
#
# License:: LGPL / ask the author
# Version:: 2.0 (2011-05-08)
#
# A WordList is a Hash with some additional features.
# It is intended to be used for keyword recognition.
#
# WordList is optimized to be used in Scanners,
# typically to decide whether a given ident is a special token.
#
# For case insensitive words use WordList::CaseIgnoring.
#
# Example:
#
# # define word arrays
# RESERVED_WORDS = %w[
# asm break case continue default do else
# ]
#
# PREDEFINED_TYPES = %w[
# int long short char void
# ]
#
# # make a WordList
# IDENT_KIND = WordList.new(:ident).
# add(RESERVED_WORDS, :reserved).
# add(PREDEFINED_TYPES, :predefined_type)
#
# ...
#
# def scan_tokens tokens, options
# ...
#
# elsif scan(/[A-Za-z_][A-Za-z_0-9]*/)
# # use it
# kind = IDENT_KIND[match]
# ...
class WordList < Hash
# Create a new WordList with +default+ as default value.
def initialize default = false
super default
end
# Add words to the list and associate them with +value+.
#
# Returns +self+, so you can concat add calls.
def add words, value = true
words.each { |word| self[word] = value }
self
end
end
# A CaseIgnoring WordList is like a WordList, only that
# keys are compared case-insensitively (normalizing keys using +downcase+).
class WordList::CaseIgnoring < WordList
def [] key
super key.downcase
end
def []= key, value
super key.downcase, value
end
end
end