git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@7618 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/1.3.0
@@ -55,7 +55,7 @@ Rails::Initializer.run do |config| | |||
config.action_mailer.perform_deliveries = false | |||
config.gem 'rubytree', :lib => 'tree' | |||
config.gem 'coderay', :version => '~>0.9.7' | |||
config.gem 'coderay', :version => '~>1.0.0' | |||
# Load any local configuration that is kept out of source control | |||
# (e.g. gems, patches). |
@@ -46,7 +46,7 @@ module Redmine | |||
# Highlights +text+ using +language+ syntax | |||
# Should not return outer pre tag | |||
def highlight_by_language(text, language) | |||
::CodeRay.scan(text, language).html(:line_numbers => :inline, :wrap => :span) | |||
::CodeRay.scan(text, language).html(:line_numbers => :inline, :line_number_anchors => false, :wrap => :span) | |||
end | |||
end | |||
end |
@@ -18,20 +18,22 @@ | |||
} | |||
a.new { color: #b73535; } | |||
.CodeRay .c { color:#666; } | |||
.CodeRay .cl { color:#B06; font-weight:bold } | |||
.CodeRay .dl { color:black } | |||
.CodeRay .fu { color:#06B; font-weight:bold } | |||
.CodeRay .il { background: #eee } | |||
.CodeRay .il .idl { font-weight: bold; color: #888 } | |||
.CodeRay .iv { color:#33B } | |||
.CodeRay .r { color:#080; font-weight:bold } | |||
.CodeRay .s { background-color:#fff0f0 } | |||
.CodeRay .s .dl { color:#710 } | |||
.syntaxhl .line-numbers { padding: 2px 4px 2px 4px; background-color: #eee; margin:0 } | |||
.syntaxhl .comment { color:#666; } | |||
.syntaxhl .class { color:#B06; font-weight:bold } | |||
.syntaxhl .delimiter { color:black } | |||
.syntaxhl .function { color:#06B; font-weight:bold } | |||
.syntaxhl .inline { background: #eee } | |||
.syntaxhl .inline .inline-delimiter { font-weight: bold; color: #888 } | |||
.syntaxhl .instance-variable { color:#33B } | |||
.syntaxhl .reserved { color:#080; font-weight:bold } | |||
.syntaxhl .string { background-color:#fff0f0; color: #D20; } | |||
.syntaxhl .string .delimiter { color:#710 } | |||
</style> | |||
</head> | |||
@@ -242,7 +244,7 @@ To go live, all you need to add is a database and a web server. | |||
<h2><a name="13" class="wiki-page"></a>Code highlighting</h2> | |||
<p>Code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p> | |||
<p>Default code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p> | |||
<p>You can highlight code in your wiki page using this syntax:</p> | |||
@@ -254,17 +256,16 @@ To go live, all you need to add is a database and a web server. | |||
<p>Example:</p> | |||
<pre><code class="ruby CodeRay"><span class="no"> 1</span> <span class="c"># The Greeter class</span> | |||
<span class="no"> 2</span> <span class="r">class</span> <span class="cl">Greeter</span> | |||
<span class="no"> 3</span> <span class="r">def</span> <span class="fu">initialize</span>(name) | |||
<span class="no"> 4</span> <span class="iv">@name</span> = name.capitalize | |||
<span class="no"> 5</span> <span class="r">end</span> | |||
<span class="no"> 6</span> | |||
<span class="no"> 7</span> <span class="r">def</span> <span class="fu">salute</span> | |||
<span class="no"> 8</span> puts <span class="s"><span class="dl">"</span><span class="k">Hello </span><span class="il"><span class="idl">#{</span><span class="iv">@name</span><span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span> | |||
<span class="no"> 9</span> <span class="r">end</span> | |||
<span class="no"><strong>10</strong></span> <span class="r">end</span> | |||
</code> | |||
<pre><code class="ruby syntaxhl"><span class="line-numbers"> 1</span> <span class="comment"># The Greeter class</span> | |||
<span class="line-numbers"> 2</span> <span class="reserved">class</span> <span class="class">Greeter</span> | |||
<span class="line-numbers"> 3</span> <span class="reserved">def</span> <span class="function">initialize</span>(name) | |||
<span class="line-numbers"> 4</span> <span class="instance-variable">@name</span> = name.capitalize | |||
<span class="line-numbers"> 5</span> <span class="reserved">end</span> | |||
<span class="line-numbers"> 6</span> | |||
<span class="line-numbers"> 7</span> <span class="reserved">def</span> <span class="function">salute</span> | |||
<span class="line-numbers"> 8</span> puts <span class="string"><span class="delimiter">"</span><span class="content">Hello </span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">@name</span><span class="inline-delimiter">}</span></span><span class="content">!</span><span class="delimiter">"</span></span> | |||
<span class="line-numbers"> 9</span> <span class="reserved">end</span> | |||
<span class="line-numbers"><strong>10</strong></span> <span class="reserved">end</span></code> | |||
</pre> | |||
</body> | |||
</html> |
@@ -91,105 +91,97 @@ div.action_A { background: #bfb } | |||
/************* CodeRay styles *************/ | |||
.syntaxhl div {display: inline;} | |||
.syntaxhl .no { padding: 2px 4px 2px 4px; background-color: #eee; margin:0 } | |||
.syntaxhl .line-numbers { padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px; } | |||
.syntaxhl .code pre { overflow: auto } | |||
.syntaxhl .debug { color:white ! important; background:blue ! important; } | |||
.syntaxhl .af { color:#00C } | |||
.syntaxhl .an { color:#007 } | |||
.syntaxhl .at { color:#f08 } | |||
.syntaxhl .av { color:#700 } | |||
.syntaxhl .aw { color:#C00 } | |||
.syntaxhl .bi { color:#509; font-weight:bold } | |||
.syntaxhl .c { color:#888; } | |||
.syntaxhl .ch { color:#04D } | |||
.syntaxhl .ch .k { color:#04D } | |||
.syntaxhl .ch .dl { color:#039 } | |||
.syntaxhl .cl { color:#B06; font-weight:bold } | |||
.syntaxhl .cm { color:#A08; font-weight:bold } | |||
.syntaxhl .co { color:#036; font-weight:bold } | |||
.syntaxhl .cr { color:#0A0 } | |||
.syntaxhl .cv { color:#369 } | |||
.syntaxhl .de { color:#B0B; } | |||
.syntaxhl .df { color:#099; font-weight:bold } | |||
.syntaxhl .di { color:#088; font-weight:bold } | |||
.syntaxhl .dl { color:black } | |||
.syntaxhl .do { color:#970 } | |||
.syntaxhl .dt { color:#34b } | |||
.syntaxhl .ds { color:#D42; font-weight:bold } | |||
.syntaxhl .e { color:#666; font-weight:bold } | |||
.syntaxhl .en { color:#800; font-weight:bold } | |||
.syntaxhl .er { color:#F00; background-color:#FAA } | |||
.syntaxhl .ex { color:#C00; font-weight:bold } | |||
.syntaxhl .fl { color:#60E; font-weight:bold } | |||
.syntaxhl .fu { color:#06B; font-weight:bold } | |||
.syntaxhl .gv { color:#d70; font-weight:bold } | |||
.syntaxhl .hx { color:#058; font-weight:bold } | |||
.syntaxhl .i { color:#00D; font-weight:bold } | |||
.syntaxhl .ic { color:#B44; font-weight:bold } | |||
.syntaxhl .il { background: #ddd; color: black } | |||
.syntaxhl .il .il { background: #ccc } | |||
.syntaxhl .il .il .il { background: #bbb } | |||
.syntaxhl .il .idl { background: #ddd; font-weight: bold; color: #666 } | |||
.syntaxhl .idl { background-color: #bbb; font-weight: bold; color: #666; } | |||
.syntaxhl .im { color:#f00; } | |||
.syntaxhl .in { color:#B2B; font-weight:bold } | |||
.syntaxhl .iv { color:#33B } | |||
.syntaxhl .la { color:#970; font-weight:bold } | |||
.syntaxhl .lv { color:#963 } | |||
.syntaxhl .oc { color:#40E; font-weight:bold } | |||
.syntaxhl .of { color:#000; font-weight:bold } | |||
.syntaxhl .op { } | |||
.syntaxhl .pc { color:#038; font-weight:bold } | |||
.syntaxhl .pd { color:#369; font-weight:bold } | |||
.syntaxhl .pp { color:#579; } | |||
.syntaxhl .ps { color:#00C; font-weight:bold } | |||
.syntaxhl .pt { color:#074; font-weight:bold } | |||
.syntaxhl .r, .kw { color:#080; font-weight:bold } | |||
.syntaxhl .ke { color: #808; } | |||
.syntaxhl .ke .dl { color: #606; } | |||
.syntaxhl .ke .ch { color: #80f; } | |||
.syntaxhl .vl { color: #088; } | |||
.syntaxhl .rx { background-color:#fff0ff } | |||
.syntaxhl .rx .k { color:#808 } | |||
.syntaxhl .rx .dl { color:#404 } | |||
.syntaxhl .rx .mod { color:#C2C } | |||
.syntaxhl .rx .fu { color:#404; font-weight: bold } | |||
.syntaxhl .s { background-color:#fff0f0; color: #D20; } | |||
.syntaxhl .s .s { background-color:#ffe0e0 } | |||
.syntaxhl .s .s .s { background-color:#ffd0d0 } | |||
.syntaxhl .s .k { } | |||
.syntaxhl .s .ch { color: #b0b; } | |||
.syntaxhl .s .dl { color: #710; } | |||
.syntaxhl .sh { background-color:#f0fff0; color:#2B2 } | |||
.syntaxhl .sh .k { } | |||
.syntaxhl .sh .dl { color:#161 } | |||
.syntaxhl .sy { color:#A60 } | |||
.syntaxhl .sy .k { color:#A60 } | |||
.syntaxhl .sy .dl { color:#630 } | |||
.syntaxhl .ta { color:#070 } | |||
.syntaxhl .tf { color:#070; font-weight:bold } | |||
.syntaxhl .ts { color:#D70; font-weight:bold } | |||
.syntaxhl .ty { color:#339; font-weight:bold } | |||
.syntaxhl .v { color:#036 } | |||
.syntaxhl .xt { color:#444 } | |||
.syntaxhl .ins { background: #cfc; } | |||
.syntaxhl .del { background: #fcc; } | |||
.syntaxhl .chg { color: #aaf; background: #007; } | |||
.syntaxhl .attribute-name { color:#007 } | |||
.syntaxhl .annotation { color:#f08 } | |||
.syntaxhl .attribute-value { color:#700 } | |||
.syntaxhl .bin { color:#509; font-weight:bold } | |||
.syntaxhl .comment { color:#888; } | |||
.syntaxhl .char { color:#04D } | |||
.syntaxhl .char .content { color:#04D } | |||
.syntaxhl .char .delimiter { color:#039 } | |||
.syntaxhl .class { color:#B06; font-weight:bold } | |||
.syntaxhl .complex { color:#A08; font-weight:bold } | |||
.syntaxhl .constant { color:#036; font-weight:bold } | |||
.syntaxhl .color { color:#0A0 } | |||
.syntaxhl .class-variable { color:#369 } | |||
.syntaxhl .decorator { color:#B0B; } | |||
.syntaxhl .definition { color:#099; font-weight:bold } | |||
.syntaxhl .directive { color:#088; font-weight:bold } | |||
.syntaxhl .delimiter { color:black } | |||
.syntaxhl .doc { color:#970 } | |||
.syntaxhl .doctype { color:#34b } | |||
.syntaxhl .doc-string { color:#D42; font-weight:bold } | |||
.syntaxhl .escape { color:#666; font-weight:bold } | |||
.syntaxhl .entity { color:#800; font-weight:bold } | |||
.syntaxhl .error { color:#F00; background-color:#FAA } | |||
.syntaxhl .exception { color:#C00; font-weight:bold } | |||
.syntaxhl .float { color:#60E; font-weight:bold } | |||
.syntaxhl .function { color:#06B; font-weight:bold } | |||
.syntaxhl .global-variable { color:#d70; font-weight:bold } | |||
.syntaxhl .hex { color:#058; font-weight:bold } | |||
.syntaxhl .integer { color:#00D; font-weight:bold } | |||
.syntaxhl .include { color:#B44; font-weight:bold } | |||
.syntaxhl .inline { background: #ddd; color: black } | |||
.syntaxhl .inline .inline { background: #ccc } | |||
.syntaxhl .inline .inline .inline { background: #bbb } | |||
.syntaxhl .inline .inline-delimiter { background: #ddd; font-weight: bold; color: #666 } | |||
.syntaxhl .inline-delimiter { background-color: #bbb; font-weight: bold; color: #666; } | |||
.syntaxhl .important { color:#f00; } | |||
.syntaxhl .instance-variable { color:#33B } | |||
.syntaxhl .label { color:#970; font-weight:bold } | |||
.syntaxhl .local-variable { color:#963 } | |||
.syntaxhl .octal { color:#40E; font-weight:bold } | |||
.syntaxhl .predefined-constant { color:#038; font-weight:bold } | |||
.syntaxhl .predefined { color:#369; font-weight:bold } | |||
.syntaxhl .preprocessor { color:#579; } | |||
.syntaxhl .pseudo-class { color:#00C; font-weight:bold } | |||
.syntaxhl .predefined-type { color:#074; font-weight:bold } | |||
.syntaxhl .reserved, .keyword { color:#080; font-weight:bold } | |||
.syntaxhl .key { color: #808; } | |||
.syntaxhl .key .delimiter { color: #606; } | |||
.syntaxhl .key .char { color: #80f; } | |||
.syntaxhl .value { color: #088; } | |||
.syntaxhl .regexp { background-color:#fff0ff } | |||
.syntaxhl .regexp .content { color:#808 } | |||
.syntaxhl .regexp .delimiter { color:#404 } | |||
.syntaxhl .regexp .modifier { color:#C2C } | |||
.syntaxhl .regexp .function { color:#404; font-weight: bold } | |||
.syntaxhl .string { background-color:#fff0f0; color: #D20; } | |||
.syntaxhl .string .string { background-color:#ffe0e0 } | |||
.syntaxhl .string .string .string { background-color:#ffd0d0 } | |||
.syntaxhl .string .content { } | |||
.syntaxhl .string .char { color: #b0b; } | |||
.syntaxhl .string .delimiter { color: #710; } | |||
.syntaxhl .shell { background-color:#f0fff0; color:#2B2 } | |||
.syntaxhl .shell .content { } | |||
.syntaxhl .shell .delimiter { color:#161 } | |||
.syntaxhl .symbol { color:#A60 } | |||
.syntaxhl .symbol .content { color:#A60 } | |||
.syntaxhl .symbol .delimiter { color:#630 } | |||
.syntaxhl .tag { color:#070 } | |||
.syntaxhl .type { color:#339; font-weight:bold } | |||
.syntaxhl .variable { color:#036 } | |||
.syntaxhl .insert { background: #cfc; } | |||
.syntaxhl .delete { background: #fcc; } | |||
.syntaxhl .change { color: #aaf; background: #007; } | |||
.syntaxhl .head { color: #f8f; background: #505 } | |||
.syntaxhl .ins .ins { color: #080; font-weight:bold } | |||
.syntaxhl .del .del { color: #800; font-weight:bold } | |||
.syntaxhl .chg .chg { color: #66f; } | |||
.syntaxhl .insert .insert { color: #080; font-weight:bold } | |||
.syntaxhl .delete .delete { color: #800; font-weight:bold } | |||
.syntaxhl .change .change { color: #66f; } | |||
.syntaxhl .head .head { color: #f4f; } |
@@ -22,7 +22,7 @@ Index: app/views/common/_diff.rhtml | |||
+<% diff.each do |table_file| -%> | |||
<div class="autoscroll"> | |||
<% if diff_type == 'sbs' -%> | |||
<table class="filecontent CodeRay"> | |||
<table class="filecontent syntaxhl"> | |||
@@ -62,3 +63,5 @@ | |||
</div> |
@@ -499,7 +499,7 @@ EXPECTED | |||
RAW | |||
expected = <<-EXPECTED | |||
<pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span> | |||
<pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span> <span class="comment"># Some ruby code here</span></span> | |||
</code></pre> | |||
EXPECTED | |||
@@ -1,155 +0,0 @@ | |||
--- !ruby/object:Gem::Specification | |||
name: coderay | |||
version: !ruby/object:Gem::Version | |||
hash: 53 | |||
prerelease: false | |||
segments: | |||
- 0 | |||
- 9 | |||
- 7 | |||
version: 0.9.7 | |||
platform: ruby | |||
authors: | |||
- murphy | |||
autorequire: | |||
bindir: bin | |||
cert_chain: [] | |||
date: 2011-01-15 00:00:00 +01:00 | |||
default_executable: | |||
dependencies: [] | |||
description: | | |||
Fast and easy syntax highlighting for selected languages, written in Ruby. | |||
Comes with RedCloth integration and LOC counter. | |||
email: murphy@rubychan.de | |||
executables: | |||
- coderay | |||
- coderay_stylesheet | |||
extensions: [] | |||
extra_rdoc_files: | |||
- lib/README | |||
- FOLDERS | |||
files: | |||
- ./lib/coderay/duo.rb | |||
- ./lib/coderay/encoder.rb | |||
- ./lib/coderay/encoders/_map.rb | |||
- ./lib/coderay/encoders/comment_filter.rb | |||
- ./lib/coderay/encoders/count.rb | |||
- ./lib/coderay/encoders/debug.rb | |||
- ./lib/coderay/encoders/div.rb | |||
- ./lib/coderay/encoders/filter.rb | |||
- ./lib/coderay/encoders/html/css.rb | |||
- ./lib/coderay/encoders/html/numerization.rb | |||
- ./lib/coderay/encoders/html/output.rb | |||
- ./lib/coderay/encoders/html.rb | |||
- ./lib/coderay/encoders/json.rb | |||
- ./lib/coderay/encoders/lines_of_code.rb | |||
- ./lib/coderay/encoders/null.rb | |||
- ./lib/coderay/encoders/page.rb | |||
- ./lib/coderay/encoders/span.rb | |||
- ./lib/coderay/encoders/statistic.rb | |||
- ./lib/coderay/encoders/term.rb | |||
- ./lib/coderay/encoders/text.rb | |||
- ./lib/coderay/encoders/token_class_filter.rb | |||
- ./lib/coderay/encoders/xml.rb | |||
- ./lib/coderay/encoders/yaml.rb | |||
- ./lib/coderay/for_redcloth.rb | |||
- ./lib/coderay/helpers/file_type.rb | |||
- ./lib/coderay/helpers/gzip_simple.rb | |||
- ./lib/coderay/helpers/plugin.rb | |||
- ./lib/coderay/helpers/word_list.rb | |||
- ./lib/coderay/scanner.rb | |||
- ./lib/coderay/scanners/_map.rb | |||
- ./lib/coderay/scanners/c.rb | |||
- ./lib/coderay/scanners/cpp.rb | |||
- ./lib/coderay/scanners/css.rb | |||
- ./lib/coderay/scanners/debug.rb | |||
- ./lib/coderay/scanners/delphi.rb | |||
- ./lib/coderay/scanners/diff.rb | |||
- ./lib/coderay/scanners/groovy.rb | |||
- ./lib/coderay/scanners/html.rb | |||
- ./lib/coderay/scanners/java/builtin_types.rb | |||
- ./lib/coderay/scanners/java.rb | |||
- ./lib/coderay/scanners/java_script-0.9.6.rb | |||
- ./lib/coderay/scanners/java_script.rb | |||
- ./lib/coderay/scanners/json.rb | |||
- ./lib/coderay/scanners/nitro_xhtml.rb | |||
- ./lib/coderay/scanners/php.rb | |||
- ./lib/coderay/scanners/plaintext.rb | |||
- ./lib/coderay/scanners/python.rb | |||
- ./lib/coderay/scanners/rhtml.rb | |||
- ./lib/coderay/scanners/ruby/patterns.rb | |||
- ./lib/coderay/scanners/ruby.rb | |||
- ./lib/coderay/scanners/scheme.rb | |||
- ./lib/coderay/scanners/sql.rb | |||
- ./lib/coderay/scanners/xml.rb | |||
- ./lib/coderay/scanners/yaml.rb | |||
- ./lib/coderay/style.rb | |||
- ./lib/coderay/styles/_map.rb | |||
- ./lib/coderay/styles/cycnus.rb | |||
- ./lib/coderay/styles/murphy.rb | |||
- ./lib/coderay/token_classes.rb | |||
- ./lib/coderay/tokens.rb | |||
- ./lib/coderay.rb | |||
- ./Rakefile | |||
- ./test/functional/basic.rb | |||
- ./test/functional/basic.rbc | |||
- ./test/functional/for_redcloth.rb | |||
- ./test/functional/for_redcloth.rbc | |||
- ./test/functional/load_plugin_scanner.rb | |||
- ./test/functional/load_plugin_scanner.rbc | |||
- ./test/functional/suite.rb | |||
- ./test/functional/suite.rbc | |||
- ./test/functional/vhdl.rb | |||
- ./test/functional/vhdl.rbc | |||
- ./test/functional/word_list.rb | |||
- ./test/functional/word_list.rbc | |||
- ./lib/README | |||
- ./LICENSE | |||
- lib/README | |||
- FOLDERS | |||
- bin/coderay | |||
- bin/coderay_stylesheet | |||
has_rdoc: true | |||
homepage: http://coderay.rubychan.de | |||
licenses: [] | |||
post_install_message: | |||
rdoc_options: | |||
- -SNw2 | |||
- -mlib/README | |||
- -t CodeRay Documentation | |||
require_paths: | |||
- lib | |||
required_ruby_version: !ruby/object:Gem::Requirement | |||
none: false | |||
requirements: | |||
- - ">=" | |||
- !ruby/object:Gem::Version | |||
hash: 51 | |||
segments: | |||
- 1 | |||
- 8 | |||
- 2 | |||
version: 1.8.2 | |||
required_rubygems_version: !ruby/object:Gem::Requirement | |||
none: false | |||
requirements: | |||
- - ">=" | |||
- !ruby/object:Gem::Version | |||
hash: 3 | |||
segments: | |||
- 0 | |||
version: "0" | |||
requirements: [] | |||
rubyforge_project: coderay | |||
rubygems_version: 1.3.7 | |||
signing_key: | |||
specification_version: 3 | |||
summary: Fast syntax highlighting for selected languages. | |||
test_files: | |||
- ./test/functional/suite.rb |
@@ -1,53 +0,0 @@ | |||
= CodeRay - Trunk folder structure | |||
== bench - Benchmarking system | |||
All benchmarking stuff goes here. | |||
Test inputs are stored in files named <code>example.<lang></code>. | |||
Test outputs go to <code>bench/test.<encoder-default-file-extension></code>. | |||
Run <code>bench/bench.rb</code> to get a usage description. | |||
Run <code>rake bench</code> to perform an example benchmark. | |||
== bin - Scripts | |||
Executional files for CodeRay. | |||
== demo - Demos and functional tests | |||
Demonstrational scripts to show of CodeRay's features. | |||
Run them as functional tests with <code>rake test:demos</code>. | |||
== etc - Lots of stuff | |||
Some addidtional files for CodeRay, mainly graphics and Vim scripts. | |||
== gem_server - Gem output folder | |||
For <code>rake gem</code>. | |||
== lib - CodeRay library code | |||
This is the base directory for the CodeRay library. | |||
== rake_helpers - Rake helper libraries | |||
Some files to enhance Rake, including the Autumnal Rdoc template and some scripts. | |||
== test - Tests | |||
Tests for the scanners. | |||
Each language has its own subfolder and sub-suite. | |||
Run with <code>rake test</code>. |
@@ -1,86 +0,0 @@ | |||
#!/usr/bin/env ruby | |||
# CodeRay Executable | |||
# | |||
# Version: 0.2 | |||
# Author: murphy | |||
require 'coderay' | |||
if ARGV.empty? | |||
$stderr.puts <<-USAGE | |||
CodeRay #{CodeRay::VERSION} (http://coderay.rubychan.de) | |||
Usage: | |||
coderay file [-<format>] | |||
coderay -<lang> [-<format>] [< file] [> output] | |||
Defaults: | |||
lang: based on file extension | |||
format: ANSI colorized output for terminal, HTML page for files | |||
Examples: | |||
coderay foo.rb # colorized output to terminal, based on file extension | |||
coderay foo.rb -loc # print LOC count, based on file extension and format | |||
coderay foo.rb > foo.html # HTML page output to file, based on extension | |||
coderay -ruby < foo.rb # colorized output to terminal, based on lang | |||
coderay -ruby -loc < foo.rb # print LOC count, based on lang | |||
coderay -ruby -page foo.rb # HTML page output to terminal, based on lang and format | |||
coderay -ruby -page foo.rb > foo.html # HTML page output to file, based on lang and format | |||
USAGE | |||
end | |||
first, second = ARGV | |||
def read | |||
file = ARGV.grep(/^(?!-)/).last | |||
if file | |||
if File.exist?(file) | |||
File.read file | |||
else | |||
$stderr.puts "No such file: #{file}" | |||
end | |||
else | |||
$stdin.read | |||
end | |||
end | |||
if first | |||
if first[/-(\w+)/] == first | |||
lang = $1 | |||
input = read | |||
tokens = :scan | |||
else | |||
file = first | |||
unless File.exist? file | |||
$stderr.puts "No such file: #{file}" | |||
exit 2 | |||
end | |||
tokens = CodeRay.scan_file file | |||
end | |||
else | |||
$stderr.puts 'No lang/file given.' | |||
exit 1 | |||
end | |||
if second | |||
if second[/-(\w+)/] == second | |||
format = $1.to_sym | |||
else | |||
raise 'invalid format (must be -xxx)' | |||
end | |||
else | |||
if $stdout.tty? | |||
format = :term | |||
else | |||
$stderr.puts 'No format given; setting to default (HTML Page).' | |||
format = :page | |||
end | |||
end | |||
if tokens == :scan | |||
output = CodeRay::Duo[lang => format].highlight input | |||
else | |||
output = tokens.encode format | |||
end | |||
out = $stdout | |||
out.puts output |
@@ -1,4 +0,0 @@ | |||
#!/usr/bin/env ruby | |||
require 'coderay' | |||
puts CodeRay::Encoders[:html]::CSS.new.stylesheet |
@@ -1,322 +0,0 @@ | |||
# = CodeRay Library | |||
# | |||
# CodeRay is a Ruby library for syntax highlighting. | |||
# | |||
# I try to make CodeRay easy to use and intuitive, but at the same time fully featured, complete, | |||
# fast and efficient. | |||
# | |||
# See README. | |||
# | |||
# It consists mainly of | |||
# * the main engine: CodeRay (Scanners::Scanner, Tokens/TokenStream, Encoders::Encoder), PluginHost | |||
# * the scanners in CodeRay::Scanners | |||
# * the encoders in CodeRay::Encoders | |||
# | |||
# Here's a fancy graphic to light up this gray docu: | |||
# | |||
# http://cycnus.de/raindark/coderay/scheme.png | |||
# | |||
# == Documentation | |||
# | |||
# See CodeRay, Encoders, Scanners, Tokens. | |||
# | |||
# == Usage | |||
# | |||
# Remember you need RubyGems to use CodeRay, unless you have it in your load path. Run Ruby with | |||
# -rubygems option if required. | |||
# | |||
# === Highlight Ruby code in a string as html | |||
# | |||
# require 'coderay' | |||
# print CodeRay.scan('puts "Hello, world!"', :ruby).html | |||
# | |||
# # prints something like this: | |||
# puts <span class="s">"Hello, world!"</span> | |||
# | |||
# | |||
# === Highlight C code from a file in a html div | |||
# | |||
# require 'coderay' | |||
# print CodeRay.scan(File.read('ruby.h'), :c).div | |||
# print CodeRay.scan_file('ruby.h').html.div | |||
# | |||
# You can include this div in your page. The used CSS styles can be printed with | |||
# | |||
# % coderay_stylesheet | |||
# | |||
# === Highlight without typing too much | |||
# | |||
# If you are one of the hasty (or lazy, or extremely curious) people, just run this file: | |||
# | |||
# % ruby -rubygems /path/to/coderay/coderay.rb > example.html | |||
# | |||
# and look at the file it created in your browser. | |||
# | |||
# = CodeRay Module | |||
# | |||
# The CodeRay module provides convenience methods for the engine. | |||
# | |||
# * The +lang+ and +format+ arguments select Scanner and Encoder to use. These are | |||
# simply lower-case symbols, like <tt>:python</tt> or <tt>:html</tt>. | |||
# * All methods take an optional hash as last parameter, +options+, that is send to | |||
# the Encoder / Scanner. | |||
# * Input and language are always sorted in this order: +code+, +lang+. | |||
# (This is in alphabetical order, if you need a mnemonic ;) | |||
# | |||
# You should be able to highlight everything you want just using these methods; | |||
# so there is no need to dive into CodeRay's deep class hierarchy. | |||
# | |||
# The examples in the demo directory demonstrate common cases using this interface. | |||
# | |||
# = Basic Access Ways | |||
# | |||
# Read this to get a general view what CodeRay provides. | |||
# | |||
# == Scanning | |||
# | |||
# Scanning means analysing an input string, splitting it up into Tokens. | |||
# Each Token knows about what type it is: string, comment, class name, etc. | |||
# | |||
# Each +lang+ (language) has its own Scanner; for example, <tt>:ruby</tt> code is | |||
# handled by CodeRay::Scanners::Ruby. | |||
# | |||
# CodeRay.scan:: Scan a string in a given language into Tokens. | |||
# This is the most common method to use. | |||
# CodeRay.scan_file:: Scan a file and guess the language using FileType. | |||
# | |||
# The Tokens object you get from these methods can encode itself; see Tokens. | |||
# | |||
# == Encoding | |||
# | |||
# Encoding means compiling Tokens into an output. This can be colored HTML or | |||
# LaTeX, a textual statistic or just the number of non-whitespace tokens. | |||
# | |||
# Each Encoder provides output in a specific +format+, so you select Encoders via | |||
# formats like <tt>:html</tt> or <tt>:statistic</tt>. | |||
# | |||
# CodeRay.encode:: Scan and encode a string in a given language. | |||
# CodeRay.encode_tokens:: Encode the given tokens. | |||
# CodeRay.encode_file:: Scan a file, guess the language using FileType and encode it. | |||
# | |||
# == Streaming | |||
# | |||
# Streaming saves RAM by running Scanner and Encoder in some sort of | |||
# pipe mode; see TokenStream. | |||
# | |||
# CodeRay.scan_stream:: Scan in stream mode. | |||
# | |||
# == All-in-One Encoding | |||
# | |||
# CodeRay.encode:: Highlight a string with a given input and output format. | |||
# | |||
# == Instanciating | |||
# | |||
# You can use an Encoder instance to highlight multiple inputs. This way, the setup | |||
# for this Encoder must only be done once. | |||
# | |||
# CodeRay.encoder:: Create an Encoder instance with format and options. | |||
# CodeRay.scanner:: Create an Scanner instance for lang, with '' as default code. | |||
# | |||
# To make use of CodeRay.scanner, use CodeRay::Scanner::code=. | |||
# | |||
# The scanning methods provide more flexibility; we recommend to use these. | |||
# | |||
# == Reusing Scanners and Encoders | |||
# | |||
# If you want to re-use scanners and encoders (because that is faster), see | |||
# CodeRay::Duo for the most convenient (and recommended) interface. | |||
module CodeRay | |||
$CODERAY_DEBUG ||= false | |||
# Version: Major.Minor.Teeny[.Revision] | |||
# Major: 0 for pre-stable, 1 for stable | |||
# Minor: feature milestone | |||
# Teeny: development state, 0 for pre-release | |||
# Revision: Subversion Revision number (generated on rake gem:make) | |||
VERSION = '0.9.7' | |||
require 'coderay/tokens' | |||
require 'coderay/token_classes' | |||
require 'coderay/scanner' | |||
require 'coderay/encoder' | |||
require 'coderay/duo' | |||
require 'coderay/style' | |||
class << self | |||
# Scans the given +code+ (a String) with the Scanner for +lang+. | |||
# | |||
# This is a simple way to use CodeRay. Example: | |||
# require 'coderay' | |||
# page = CodeRay.scan("puts 'Hello, world!'", :ruby).html | |||
# | |||
# See also demo/demo_simple. | |||
def scan code, lang, options = {}, &block | |||
scanner = Scanners[lang].new code, options, &block | |||
scanner.tokenize | |||
end | |||
# Scans +filename+ (a path to a code file) with the Scanner for +lang+. | |||
# | |||
# If +lang+ is :auto or omitted, the CodeRay::FileType module is used to | |||
# determine it. If it cannot find out what type it is, it uses | |||
# CodeRay::Scanners::Plaintext. | |||
# | |||
# Calls CodeRay.scan. | |||
# | |||
# Example: | |||
# require 'coderay' | |||
# page = CodeRay.scan_file('some_c_code.c').html | |||
def scan_file filename, lang = :auto, options = {}, &block | |||
file = IO.read filename | |||
if lang == :auto | |||
require 'coderay/helpers/file_type' | |||
lang = FileType.fetch filename, :plaintext, true | |||
end | |||
scan file, lang, options = {}, &block | |||
end | |||
# Scan the +code+ (a string) with the scanner for +lang+. | |||
# | |||
# Calls scan. | |||
# | |||
# See CodeRay.scan. | |||
def scan_stream code, lang, options = {}, &block | |||
options[:stream] = true | |||
scan code, lang, options, &block | |||
end | |||
# Encode a string in Streaming mode. | |||
# | |||
# This starts scanning +code+ with the the Scanner for +lang+ | |||
# while encodes the output with the Encoder for +format+. | |||
# +options+ will be passed to the Encoder. | |||
# | |||
# See CodeRay::Encoder.encode_stream | |||
def encode_stream code, lang, format, options = {} | |||
encoder(format, options).encode_stream code, lang, options | |||
end | |||
# Encode a string. | |||
# | |||
# This scans +code+ with the the Scanner for +lang+ and then | |||
# encodes it with the Encoder for +format+. | |||
# +options+ will be passed to the Encoder. | |||
# | |||
# See CodeRay::Encoder.encode | |||
def encode code, lang, format, options = {} | |||
encoder(format, options).encode code, lang, options | |||
end | |||
# Highlight a string into a HTML <div>. | |||
# | |||
# CSS styles use classes, so you have to include a stylesheet | |||
# in your output. | |||
# | |||
# See encode. | |||
def highlight code, lang, options = { :css => :class }, format = :div | |||
encode code, lang, format, options | |||
end | |||
# Encode pre-scanned Tokens. | |||
# Use this together with CodeRay.scan: | |||
# | |||
# require 'coderay' | |||
# | |||
# # Highlight a short Ruby code example in a HTML span | |||
# tokens = CodeRay.scan '1 + 2', :ruby | |||
# puts CodeRay.encode_tokens(tokens, :span) | |||
# | |||
def encode_tokens tokens, format, options = {} | |||
encoder(format, options).encode_tokens tokens, options | |||
end | |||
# Encodes +filename+ (a path to a code file) with the Scanner for +lang+. | |||
# | |||
# See CodeRay.scan_file. | |||
# Notice that the second argument is the output +format+, not the input language. | |||
# | |||
# Example: | |||
# require 'coderay' | |||
# page = CodeRay.encode_file 'some_c_code.c', :html | |||
def encode_file filename, format, options = {} | |||
tokens = scan_file filename, :auto, get_scanner_options(options) | |||
encode_tokens tokens, format, options | |||
end | |||
# Highlight a file into a HTML <div>. | |||
# | |||
# CSS styles use classes, so you have to include a stylesheet | |||
# in your output. | |||
# | |||
# See encode. | |||
def highlight_file filename, options = { :css => :class }, format = :div | |||
encode_file filename, format, options | |||
end | |||
# Finds the Encoder class for +format+ and creates an instance, passing | |||
# +options+ to it. | |||
# | |||
# Example: | |||
# require 'coderay' | |||
# | |||
# stats = CodeRay.encoder(:statistic) | |||
# stats.encode("puts 17 + 4\n", :ruby) | |||
# | |||
# puts '%d out of %d tokens have the kind :integer.' % [ | |||
# stats.type_stats[:integer].count, | |||
# stats.real_token_count | |||
# ] | |||
# #-> 2 out of 4 tokens have the kind :integer. | |||
def encoder format, options = {} | |||
Encoders[format].new options | |||
end | |||
# Finds the Scanner class for +lang+ and creates an instance, passing | |||
# +options+ to it. | |||
# | |||
# See Scanner.new. | |||
def scanner lang, options = {} | |||
Scanners[lang].new '', options | |||
end | |||
# Extract the options for the scanner from the +options+ hash. | |||
# | |||
# Returns an empty Hash if <tt>:scanner_options</tt> is not set. | |||
# | |||
# This is used if a method like CodeRay.encode has to provide options | |||
# for Encoder _and_ scanner. | |||
def get_scanner_options options | |||
options.fetch :scanner_options, {} | |||
end | |||
end | |||
# This Exception is raised when you try to stream with something that is not | |||
# capable of streaming. | |||
class NotStreamableError < Exception | |||
def initialize obj | |||
@obj = obj | |||
end | |||
def to_s | |||
'%s is not Streamable!' % @obj.class | |||
end | |||
end | |||
# A dummy module that is included by subclasses of CodeRay::Scanner an CodeRay::Encoder | |||
# to show that they are able to handle streams. | |||
module Streamable | |||
end | |||
end | |||
# Run a test script. | |||
if $0 == __FILE__ | |||
$stderr.print 'Press key to print demo.'; gets | |||
# Just use this file as an example of Ruby code. | |||
code = File.read(__FILE__)[/module CodeRay.*/m] | |||
print CodeRay.scan(code, :ruby).html | |||
end |
@@ -1,12 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
map \ | |||
:loc => :lines_of_code, | |||
:plain => :text, | |||
:stats => :statistic, | |||
:terminal => :term, | |||
:tex => :latex | |||
end | |||
end |
@@ -1,43 +0,0 @@ | |||
($:.unshift '../..'; require 'coderay') unless defined? CodeRay | |||
module CodeRay | |||
module Encoders | |||
load :token_class_filter | |||
class CommentFilter < TokenClassFilter | |||
register_for :comment_filter | |||
DEFAULT_OPTIONS = superclass::DEFAULT_OPTIONS.merge \ | |||
:exclude => [:comment] | |||
end | |||
end | |||
end | |||
if $0 == __FILE__ | |||
$VERBOSE = true | |||
$: << File.join(File.dirname(__FILE__), '..') | |||
eval DATA.read, nil, $0, __LINE__ + 4 | |||
end | |||
__END__ | |||
require 'test/unit' | |||
class CommentFilterTest < Test::Unit::TestCase | |||
def test_filtering_comments | |||
tokens = CodeRay.scan <<-RUBY, :ruby | |||
#!/usr/bin/env ruby | |||
# a minimal Ruby program | |||
puts "Hello world!" | |||
RUBY | |||
assert_equal <<-RUBY_FILTERED, tokens.comment_filter.text | |||
#!/usr/bin/env ruby | |||
puts "Hello world!" | |||
RUBY_FILTERED | |||
end | |||
end |
@@ -1,21 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
class Count < Encoder | |||
include Streamable | |||
register_for :count | |||
protected | |||
def setup options | |||
@out = 0 | |||
end | |||
def token text, kind | |||
@out += 1 | |||
end | |||
end | |||
end | |||
end |
@@ -1,19 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
load :html | |||
class Div < HTML | |||
FILE_EXTENSION = 'div.html' | |||
register_for :div | |||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ | |||
:css => :style, | |||
:wrap => :div | |||
end | |||
end | |||
end |
@@ -1,75 +0,0 @@ | |||
($:.unshift '../..'; require 'coderay') unless defined? CodeRay | |||
module CodeRay | |||
module Encoders | |||
class Filter < Encoder | |||
register_for :filter | |||
protected | |||
def setup options | |||
@out = Tokens.new | |||
end | |||
def text_token text, kind | |||
[text, kind] if include_text_token? text, kind | |||
end | |||
def include_text_token? text, kind | |||
true | |||
end | |||
def block_token action, kind | |||
[action, kind] if include_block_token? action, kind | |||
end | |||
def include_block_token? action, kind | |||
true | |||
end | |||
end | |||
end | |||
end | |||
if $0 == __FILE__ | |||
$VERBOSE = true | |||
$: << File.join(File.dirname(__FILE__), '..') | |||
eval DATA.read, nil, $0, __LINE__ + 4 | |||
end | |||
__END__ | |||
require 'test/unit' | |||
class FilterTest < Test::Unit::TestCase | |||
def test_creation | |||
assert CodeRay::Encoders::Filter < CodeRay::Encoders::Encoder | |||
filter = nil | |||
assert_nothing_raised do | |||
filter = CodeRay.encoder :filter | |||
end | |||
assert_kind_of CodeRay::Encoders::Encoder, filter | |||
end | |||
def test_filtering_text_tokens | |||
tokens = CodeRay::Tokens.new | |||
10.times do |i| | |||
tokens << [i.to_s, :index] | |||
end | |||
assert_equal tokens, CodeRay::Encoders::Filter.new.encode_tokens(tokens) | |||
assert_equal tokens, tokens.filter | |||
end | |||
def test_filtering_block_tokens | |||
tokens = CodeRay::Tokens.new | |||
10.times do |i| | |||
tokens << [:open, :index] | |||
tokens << [i.to_s, :content] | |||
tokens << [:close, :index] | |||
end | |||
assert_equal tokens, CodeRay::Encoders::Filter.new.encode_tokens(tokens) | |||
assert_equal tokens, tokens.filter | |||
end | |||
end |
@@ -1,309 +0,0 @@ | |||
require 'set' | |||
module CodeRay | |||
module Encoders | |||
# = HTML Encoder | |||
# | |||
# This is CodeRay's most important highlighter: | |||
# It provides save, fast XHTML generation and CSS support. | |||
# | |||
# == Usage | |||
# | |||
# require 'coderay' | |||
# puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page | |||
# puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span) | |||
# #-> <span class="CodeRay"><span class="co">Some</span> /code/</span> | |||
# puts CodeRay.scan('Some /code/', :ruby).span #-> the same | |||
# | |||
# puts CodeRay.scan('Some code', :ruby).html( | |||
# :wrap => nil, | |||
# :line_numbers => :inline, | |||
# :css => :style | |||
# ) | |||
# #-> <span class="no">1</span> <span style="color:#036; font-weight:bold;">Some</span> code | |||
# | |||
# == Options | |||
# | |||
# === :tab_width | |||
# Convert \t characters to +n+ spaces (a number.) | |||
# Default: 8 | |||
# | |||
# === :css | |||
# How to include the styles; can be :class or :style. | |||
# | |||
# Default: :class | |||
# | |||
# === :wrap | |||
# Wrap in :page, :div, :span or nil. | |||
# | |||
# You can also use Encoders::Div and Encoders::Span. | |||
# | |||
# Default: nil | |||
# | |||
# === :title | |||
# | |||
# The title of the HTML page (works only when :wrap is set to :page.) | |||
# | |||
# Default: 'CodeRay output' | |||
# | |||
# === :line_numbers | |||
# Include line numbers in :table, :inline, :list or nil (no line numbers) | |||
# | |||
# Default: nil | |||
# | |||
# === :line_number_start | |||
# Where to start with line number counting. | |||
# | |||
# Default: 1 | |||
# | |||
# === :bold_every | |||
# Make every +n+-th number appear bold. | |||
# | |||
# Default: 10 | |||
# | |||
# === :highlight_lines | |||
# | |||
# Highlights certain line numbers. | |||
# Can be any Enumerable, typically just an Array or Range, of numbers. | |||
# | |||
# Bolding is deactivated when :highlight_lines is set. It only makes sense | |||
# in combination with :line_numbers. | |||
# | |||
# Default: nil | |||
# | |||
# === :hint | |||
# Include some information into the output using the title attribute. | |||
# Can be :info (show token type on mouse-over), :info_long (with full path) | |||
# or :debug (via inspect). | |||
# | |||
# Default: false | |||
class HTML < Encoder | |||
include Streamable | |||
register_for :html | |||
FILE_EXTENSION = 'html' | |||
DEFAULT_OPTIONS = { | |||
:tab_width => 8, | |||
:css => :class, | |||
:style => :cycnus, | |||
:wrap => nil, | |||
:title => 'CodeRay output', | |||
:line_numbers => nil, | |||
:line_number_start => 1, | |||
:bold_every => 10, | |||
:highlight_lines => nil, | |||
:hint => false, | |||
} | |||
helper :output, :css | |||
attr_reader :css | |||
protected | |||
HTML_ESCAPE = { #:nodoc: | |||
'&' => '&', | |||
'"' => '"', | |||
'>' => '>', | |||
'<' => '<', | |||
} | |||
# This was to prevent illegal HTML. | |||
# Strange chars should still be avoided in codes. | |||
evil_chars = Array(0x00...0x20) - [?\n, ?\t, ?\s] | |||
evil_chars.each { |i| HTML_ESCAPE[i.chr] = ' ' } | |||
#ansi_chars = Array(0x7f..0xff) | |||
#ansi_chars.each { |i| HTML_ESCAPE[i.chr] = '&#%d;' % i } | |||
# \x9 (\t) and \xA (\n) not included | |||
#HTML_ESCAPE_PATTERN = /[\t&"><\0-\x8\xB-\x1f\x7f-\xff]/ | |||
HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1f]/ | |||
TOKEN_KIND_TO_INFO = Hash.new { |h, kind| | |||
h[kind] = | |||
case kind | |||
when :pre_constant | |||
'Predefined constant' | |||
else | |||
kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize } | |||
end | |||
} | |||
TRANSPARENT_TOKEN_KINDS = [ | |||
:delimiter, :modifier, :content, :escape, :inline_delimiter, | |||
].to_set | |||
# Generate a hint about the given +classes+ in a +hint+ style. | |||
# | |||
# +hint+ may be :info, :info_long or :debug. | |||
def self.token_path_to_hint hint, classes | |||
title = | |||
case hint | |||
when :info | |||
TOKEN_KIND_TO_INFO[classes.first] | |||
when :info_long | |||
classes.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/') | |||
when :debug | |||
classes.inspect | |||
end | |||
title ? " title=\"#{title}\"" : '' | |||
end | |||
def setup options | |||
super | |||
@HTML_ESCAPE = HTML_ESCAPE.dup | |||
@HTML_ESCAPE["\t"] = ' ' * options[:tab_width] | |||
@opened = [nil] | |||
@css = CSS.new options[:style] | |||
hint = options[:hint] | |||
if hint and not [:debug, :info, :info_long].include? hint | |||
raise ArgumentError, "Unknown value %p for :hint; \ | |||
expected :info, :debug, false, or nil." % hint | |||
end | |||
case options[:css] | |||
when :class | |||
@css_style = Hash.new do |h, k| | |||
c = CodeRay::Tokens::ClassOfKind[k.first] | |||
if c == :NO_HIGHLIGHT and not hint | |||
h[k.dup] = false | |||
else | |||
title = if hint | |||
HTML.token_path_to_hint(hint, k[1..-1] << k.first) | |||
else | |||
'' | |||
end | |||
if c == :NO_HIGHLIGHT | |||
h[k.dup] = '<span%s>' % [title] | |||
else | |||
h[k.dup] = '<span%s class="%s">' % [title, c] | |||
end | |||
end | |||
end | |||
when :style | |||
@css_style = Hash.new do |h, k| | |||
if k.is_a? ::Array | |||
styles = k.dup | |||
else | |||
styles = [k] | |||
end | |||
type = styles.first | |||
classes = styles.map { |c| Tokens::ClassOfKind[c] } | |||
if classes.first == :NO_HIGHLIGHT and not hint | |||
h[k] = false | |||
else | |||
styles.shift if TRANSPARENT_TOKEN_KINDS.include? styles.first | |||
title = HTML.token_path_to_hint hint, styles | |||
style = @css[*classes] | |||
h[k] = | |||
if style | |||
'<span%s style="%s">' % [title, style] | |||
else | |||
false | |||
end | |||
end | |||
end | |||
else | |||
raise ArgumentError, "Unknown value %p for :css." % options[:css] | |||
end | |||
end | |||
def finish options | |||
not_needed = @opened.shift | |||
@out << '</span>' * @opened.size | |||
unless @opened.empty? | |||
warn '%d tokens still open: %p' % [@opened.size, @opened] | |||
end | |||
@out.extend Output | |||
@out.css = @css | |||
@out.numerize! options[:line_numbers], options | |||
@out.wrap! options[:wrap] | |||
@out.apply_title! options[:title] | |||
super | |||
end | |||
def token text, type = :plain | |||
case text | |||
when nil | |||
# raise 'Token with nil as text was given: %p' % [[text, type]] | |||
when String | |||
if text =~ /#{HTML_ESCAPE_PATTERN}/o | |||
text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] } | |||
end | |||
@opened[0] = type | |||
if text != "\n" && style = @css_style[@opened] | |||
@out << style << text << '</span>' | |||
else | |||
@out << text | |||
end | |||
# token groups, eg. strings | |||
when :open | |||
@opened[0] = type | |||
@out << (@css_style[@opened] || '<span>') | |||
@opened << type | |||
when :close | |||
if @opened.empty? | |||
# nothing to close | |||
else | |||
if $CODERAY_DEBUG and (@opened.size == 1 or @opened.last != type) | |||
raise 'Malformed token stream: Trying to close a token (%p) \ | |||
that is not open. Open are: %p.' % [type, @opened[1..-1]] | |||
end | |||
@out << '</span>' | |||
@opened.pop | |||
end | |||
# whole lines to be highlighted, eg. a deleted line in a diff | |||
when :begin_line | |||
@opened[0] = type | |||
if style = @css_style[@opened] | |||
if style['class="'] | |||
@out << style.sub('class="', 'class="line ') | |||
else | |||
@out << style.sub('>', ' class="line">') | |||
end | |||
else | |||
@out << '<span class="line">' | |||
end | |||
@opened << type | |||
when :end_line | |||
if @opened.empty? | |||
# nothing to close | |||
else | |||
if $CODERAY_DEBUG and (@opened.size == 1 or @opened.last != type) | |||
raise 'Malformed token stream: Trying to close a line (%p) \ | |||
that is not open. Open are: %p.' % [type, @opened[1..-1]] | |||
end | |||
@out << '</span>' | |||
@opened.pop | |||
end | |||
else | |||
raise 'unknown token kind: %p' % [text] | |||
end | |||
end | |||
end | |||
end | |||
end |
@@ -1,70 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
class HTML | |||
class CSS | |||
attr :stylesheet | |||
def CSS.load_stylesheet style = nil | |||
CodeRay::Styles[style] | |||
end | |||
def initialize style = :default | |||
@classes = Hash.new | |||
style = CSS.load_stylesheet style | |||
@stylesheet = [ | |||
style::CSS_MAIN_STYLES, | |||
style::TOKEN_COLORS.gsub(/^(?!$)/, '.CodeRay ') | |||
].join("\n") | |||
parse style::TOKEN_COLORS | |||
end | |||
def [] *styles | |||
cl = @classes[styles.first] | |||
return '' unless cl | |||
style = '' | |||
1.upto(styles.size) do |offset| | |||
break if style = cl[styles[offset .. -1]] | |||
end | |||
# warn 'Style not found: %p' % [styles] if style.empty? | |||
return style | |||
end | |||
private | |||
CSS_CLASS_PATTERN = / | |||
( # $1 = selectors | |||
(?: | |||
(?: \s* \. [-\w]+ )+ | |||
\s* ,? | |||
)+ | |||
) | |||
\s* \{ \s* | |||
( [^\}]+ )? # $2 = style | |||
\s* \} \s* | |||
| | |||
( . ) # $3 = error | |||
/mx | |||
def parse stylesheet | |||
stylesheet.scan CSS_CLASS_PATTERN do |selectors, style, error| | |||
raise "CSS parse error: '#{error.inspect}' not recognized" if error | |||
for selector in selectors.split(',') | |||
classes = selector.scan(/[-\w]+/) | |||
cl = classes.pop | |||
@classes[cl] ||= Hash.new | |||
@classes[cl][classes] = style.to_s.strip.delete(' ').chomp(';') | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
if $0 == __FILE__ | |||
require 'pp' | |||
pp CodeRay::Encoders::HTML::CSS.new | |||
end |
@@ -1,133 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
class HTML | |||
module Output | |||
def numerize *args | |||
clone.numerize!(*args) | |||
end | |||
=begin NUMERIZABLE_WRAPPINGS = { | |||
:table => [:div, :page, nil], | |||
:inline => :all, | |||
:list => [:div, :page, nil] | |||
} | |||
NUMERIZABLE_WRAPPINGS.default = :all | |||
=end | |||
def numerize! mode = :table, options = {} | |||
return self unless mode | |||
options = DEFAULT_OPTIONS.merge options | |||
start = options[:line_number_start] | |||
unless start.is_a? Integer | |||
raise ArgumentError, "Invalid value %p for :line_number_start; Integer expected." % start | |||
end | |||
#allowed_wrappings = NUMERIZABLE_WRAPPINGS[mode] | |||
#unless allowed_wrappings == :all or allowed_wrappings.include? options[:wrap] | |||
# raise ArgumentError, "Can't numerize, :wrap must be in %p, but is %p" % [NUMERIZABLE_WRAPPINGS, options[:wrap]] | |||
#end | |||
bold_every = options[:bold_every] | |||
highlight_lines = options[:highlight_lines] | |||
bolding = | |||
if bold_every == false && highlight_lines == nil | |||
proc { |line| line.to_s } | |||
elsif highlight_lines.is_a? Enumerable | |||
highlight_lines = highlight_lines.to_set | |||
proc do |line| | |||
if highlight_lines.include? line | |||
"<strong class=\"highlighted\">#{line}</strong>" # highlighted line numbers in bold | |||
else | |||
line.to_s | |||
end | |||
end | |||
elsif bold_every.is_a? Integer | |||
raise ArgumentError, ":bolding can't be 0." if bold_every == 0 | |||
proc do |line| | |||
if line % bold_every == 0 | |||
"<strong>#{line}</strong>" # every bold_every-th number in bold | |||
else | |||
line.to_s | |||
end | |||
end | |||
else | |||
raise ArgumentError, 'Invalid value %p for :bolding; false or Integer expected.' % bold_every | |||
end | |||
case mode | |||
when :inline | |||
max_width = (start + line_count).to_s.size | |||
line_number = start | |||
gsub!(/^/) do | |||
line_number_text = bolding.call line_number | |||
indent = ' ' * (max_width - line_number.to_s.size) # TODO: Optimize (10^x) | |||
res = "<span class=\"no\">#{indent}#{line_number_text}</span> " | |||
line_number += 1 | |||
res | |||
end | |||
when :table | |||
# This is really ugly. | |||
# Because even monospace fonts seem to have different heights when bold, | |||
# I make the newline bold, both in the code and the line numbers. | |||
# FIXME Still not working perfect for Mr. Internet Exploder | |||
line_numbers = (start ... start + line_count).to_a.map(&bolding).join("\n") | |||
line_numbers << "\n" # also for Mr. MS Internet Exploder :-/ | |||
line_numbers.gsub!(/\n/) { "<tt>\n</tt>" } | |||
line_numbers_table_tpl = TABLE.apply('LINE_NUMBERS', line_numbers) | |||
gsub!("</div>\n", '</div>') | |||
gsub!("\n", "<tt>\n</tt>") | |||
wrap_in! line_numbers_table_tpl | |||
@wrapped_in = :div | |||
when :list | |||
opened_tags = [] | |||
gsub!(/^.*$\n?/) do |line| | |||
line.chomp! | |||
open = opened_tags.join | |||
line.scan(%r!<(/)?span[^>]*>?!) do |close,| | |||
if close | |||
opened_tags.pop | |||
else | |||
opened_tags << $& | |||
end | |||
end | |||
close = '</span>' * opened_tags.size | |||
"<li>#{open}#{line}#{close}</li>\n" | |||
end | |||
chomp!("\n") | |||
wrap_in! LIST | |||
@wrapped_in = :div | |||
else | |||
raise ArgumentError, 'Unknown value %p for mode: expected one of %p' % | |||
[mode, [:table, :list, :inline]] | |||
end | |||
self | |||
end | |||
def line_count | |||
line_count = count("\n") | |||
position_of_last_newline = rindex(?\n) | |||
if position_of_last_newline | |||
after_last_newline = self[position_of_last_newline + 1 .. -1] | |||
ends_with_newline = after_last_newline[/\A(?:<\/span>)*\z/] | |||
line_count += 1 if not ends_with_newline | |||
end | |||
line_count | |||
end | |||
end | |||
end | |||
end | |||
end |
@@ -1,206 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
class HTML | |||
# This module is included in the output String from thew HTML Encoder. | |||
# | |||
# It provides methods like wrap, div, page etc. | |||
# | |||
# Remember to use #clone instead of #dup to keep the modules the object was | |||
# extended with. | |||
# | |||
# TODO: more doc. | |||
module Output | |||
require 'coderay/encoders/html/numerization.rb' | |||
attr_accessor :css | |||
class << self | |||
# This makes Output look like a class. | |||
# | |||
# Example: | |||
# | |||
# a = Output.new '<span class="co">Code</span>' | |||
# a.wrap! :page | |||
def new string, css = CSS.new, element = nil | |||
output = string.clone.extend self | |||
output.wrapped_in = element | |||
output.css = css | |||
output | |||
end | |||
# Raises an exception if an object that doesn't respond to to_str is extended by Output, | |||
# to prevent users from misuse. Use Module#remove_method to disable. | |||
def extended o | |||
warn "The Output module is intended to extend instances of String, not #{o.class}." unless o.respond_to? :to_str | |||
end | |||
def make_stylesheet css, in_tag = false | |||
sheet = css.stylesheet | |||
sheet = <<-CSS if in_tag | |||
<style type="text/css"> | |||
#{sheet} | |||
</style> | |||
CSS | |||
sheet | |||
end | |||
def page_template_for_css css | |||
sheet = make_stylesheet css | |||
PAGE.apply 'CSS', sheet | |||
end | |||
# Define a new wrapper. This is meta programming. | |||
def wrapper *wrappers | |||
wrappers.each do |wrapper| | |||
define_method wrapper do |*args| | |||
wrap wrapper, *args | |||
end | |||
define_method "#{wrapper}!".to_sym do |*args| | |||
wrap! wrapper, *args | |||
end | |||
end | |||
end | |||
end | |||
wrapper :div, :span, :page | |||
def wrapped_in? element | |||
wrapped_in == element | |||
end | |||
def wrapped_in | |||
@wrapped_in ||= nil | |||
end | |||
attr_writer :wrapped_in | |||
def wrap_in template | |||
clone.wrap_in! template | |||
end | |||
def wrap_in! template | |||
Template.wrap! self, template, 'CONTENT' | |||
self | |||
end | |||
def apply_title! title | |||
self.sub!(/(<title>)(<\/title>)/) { $1 + title + $2 } | |||
self | |||
end | |||
def wrap! element, *args | |||
return self if not element or element == wrapped_in | |||
case element | |||
when :div | |||
raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil | |||
wrap_in! DIV | |||
when :span | |||
raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? nil | |||
wrap_in! SPAN | |||
when :page | |||
wrap! :div if wrapped_in? nil | |||
raise "Can't wrap %p in %p" % [wrapped_in, element] unless wrapped_in? :div | |||
wrap_in! Output.page_template_for_css(@css) | |||
if args.first.is_a?(Hash) && title = args.first[:title] | |||
apply_title! title | |||
end | |||
self | |||
when nil | |||
return self | |||
else | |||
raise "Unknown value %p for :wrap" % element | |||
end | |||
@wrapped_in = element | |||
self | |||
end | |||
def wrap *args | |||
clone.wrap!(*args) | |||
end | |||
def stylesheet in_tag = false | |||
Output.make_stylesheet @css, in_tag | |||
end | |||
class Template < String | |||
def self.wrap! str, template, target | |||
target = Regexp.new(Regexp.escape("<%#{target}%>")) | |||
if template =~ target | |||
str[0,0] = $` | |||
str << $' | |||
else | |||
raise "Template target <%%%p%%> not found" % target | |||
end | |||
end | |||
def apply target, replacement | |||
target = Regexp.new(Regexp.escape("<%#{target}%>")) | |||
if self =~ target | |||
Template.new($` + replacement + $') | |||
else | |||
raise "Template target <%%%p%%> not found" % target | |||
end | |||
end | |||
module Simple | |||
def ` str #` <-- for stupid editors | |||
Template.new str | |||
end | |||
end | |||
end | |||
extend Template::Simple | |||
#-- don't include the templates in docu | |||
SPAN = `<span class="CodeRay"><%CONTENT%></span>` | |||
DIV = <<-`DIV` | |||
<div class="CodeRay"> | |||
<div class="code"><pre><%CONTENT%></pre></div> | |||
</div> | |||
DIV | |||
TABLE = <<-`TABLE` | |||
<table class="CodeRay"><tr> | |||
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre><%LINE_NUMBERS%></pre></td> | |||
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><%CONTENT%></pre></td> | |||
</tr></table> | |||
TABLE | |||
# title="double click to expand" | |||
LIST = <<-`LIST` | |||
<ol class="CodeRay"> | |||
<%CONTENT%> | |||
</ol> | |||
LIST | |||
PAGE = <<-`PAGE` | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | |||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="de"> | |||
<head> | |||
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |||
<title></title> | |||
<style type="text/css"> | |||
<%CSS%> | |||
</style> | |||
</head> | |||
<body style="background-color: white;"> | |||
<%CONTENT%> | |||
</body> | |||
</html> | |||
PAGE | |||
end | |||
end | |||
end | |||
end |
@@ -1,69 +0,0 @@ | |||
($:.unshift '../..'; require 'coderay') unless defined? CodeRay | |||
module CodeRay | |||
module Encoders | |||
# = JSON Encoder | |||
class JSON < Encoder | |||
register_for :json | |||
FILE_EXTENSION = 'json' | |||
protected | |||
def setup options | |||
begin | |||
require 'json' | |||
rescue LoadError | |||
require 'rubygems' | |||
require 'json' | |||
end | |||
@out = [] | |||
end | |||
def text_token text, kind | |||
{ :type => 'text', :text => text, :kind => kind } | |||
end | |||
def block_token action, kind | |||
{ :type => 'block', :action => action, :kind => kind } | |||
end | |||
def finish options | |||
@out.to_json | |||
end | |||
end | |||
end | |||
end | |||
if $0 == __FILE__ | |||
$VERBOSE = true | |||
$: << File.join(File.dirname(__FILE__), '..') | |||
eval DATA.read, nil, $0, __LINE__ + 4 | |||
end | |||
__END__ | |||
require 'test/unit' | |||
$:.delete '.' | |||
require 'rubygems' if RUBY_VERSION < '1.9' | |||
class JSONEncoderTest < Test::Unit::TestCase | |||
def test_json_output | |||
tokens = CodeRay.scan <<-RUBY, :ruby | |||
puts "Hello world!" | |||
RUBY | |||
require 'json' | |||
assert_equal [ | |||
{"type"=>"text", "text"=>"puts", "kind"=>"ident"}, | |||
{"type"=>"text", "text"=>" ", "kind"=>"space"}, | |||
{"type"=>"block", "action"=>"open", "kind"=>"string"}, | |||
{"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, | |||
{"type"=>"text", "text"=>"Hello world!", "kind"=>"content"}, | |||
{"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, | |||
{"type"=>"block", "action"=>"close", "kind"=>"string"}, | |||
{"type"=>"text", "text"=>"\n", "kind"=>"space"} | |||
], JSON.load(tokens.json) | |||
end | |||
end |
@@ -1,90 +0,0 @@ | |||
($:.unshift '../..'; require 'coderay') unless defined? CodeRay | |||
module CodeRay | |||
module Encoders | |||
# Counts the LoC (Lines of Code). Returns an Integer >= 0. | |||
# | |||
# Alias: :loc | |||
# | |||
# Everything that is not comment, markup, doctype/shebang, or an empty line, | |||
# is considered to be code. | |||
# | |||
# For example, | |||
# * HTML files not containing JavaScript have 0 LoC | |||
# * in a Java class without comments, LoC is the number of non-empty lines | |||
# | |||
# A Scanner class should define the token kinds that are not code in the | |||
# KINDS_NOT_LOC constant, which defaults to [:comment, :doctype]. | |||
class LinesOfCode < Encoder | |||
register_for :lines_of_code | |||
NON_EMPTY_LINE = /^\s*\S.*$/ | |||
def compile tokens, options | |||
if scanner = tokens.scanner | |||
kinds_not_loc = scanner.class::KINDS_NOT_LOC | |||
else | |||
warn ArgumentError, 'Tokens have no scanner.' if $VERBOSE | |||
kinds_not_loc = CodeRay::Scanners::Scanner::KINDS_NOT_LOC | |||
end | |||
code = tokens.token_class_filter :exclude => kinds_not_loc | |||
@loc = code.text.scan(NON_EMPTY_LINE).size | |||
end | |||
def finish options | |||
@loc | |||
end | |||
end | |||
end | |||
end | |||
if $0 == __FILE__ | |||
$VERBOSE = true | |||
$: << File.join(File.dirname(__FILE__), '..') | |||
eval DATA.read, nil, $0, __LINE__ + 4 | |||
end | |||
__END__ | |||
require 'test/unit' | |||
class LinesOfCodeTest < Test::Unit::TestCase | |||
def test_creation | |||
assert CodeRay::Encoders::LinesOfCode < CodeRay::Encoders::Encoder | |||
filter = nil | |||
assert_nothing_raised do | |||
filter = CodeRay.encoder :loc | |||
end | |||
assert_kind_of CodeRay::Encoders::LinesOfCode, filter | |||
assert_nothing_raised do | |||
filter = CodeRay.encoder :lines_of_code | |||
end | |||
assert_kind_of CodeRay::Encoders::LinesOfCode, filter | |||
end | |||
def test_lines_of_code | |||
tokens = CodeRay.scan <<-RUBY, :ruby | |||
#!/usr/bin/env ruby | |||
# a minimal Ruby program | |||
puts "Hello world!" | |||
RUBY | |||
assert_equal 1, CodeRay::Encoders::LinesOfCode.new.encode_tokens(tokens) | |||
assert_equal 1, tokens.lines_of_code | |||
assert_equal 1, tokens.loc | |||
end | |||
def test_filtering_block_tokens | |||
tokens = CodeRay::Tokens.new | |||
tokens << ["Hello\n", :world] | |||
tokens << ["Hello\n", :space] | |||
tokens << ["Hello\n", :comment] | |||
assert_equal 2, CodeRay::Encoders::LinesOfCode.new.encode_tokens(tokens) | |||
assert_equal 2, tokens.lines_of_code | |||
assert_equal 2, tokens.loc | |||
end | |||
end |
@@ -1,20 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
load :html | |||
class Page < HTML | |||
FILE_EXTENSION = 'html' | |||
register_for :page | |||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ | |||
:css => :class, | |||
:wrap => :page, | |||
:line_numbers => :table | |||
end | |||
end | |||
end |
@@ -1,19 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
load :html | |||
class Span < HTML | |||
FILE_EXTENSION = 'span.html' | |||
register_for :span | |||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ | |||
:css => :style, | |||
:wrap => :span | |||
end | |||
end | |||
end |
@@ -1,158 +0,0 @@ | |||
# encoders/term.rb | |||
# By Rob Aldred (http://robaldred.co.uk) | |||
# Based on idea by Nathan Weizenbaum (http://nex-3.com) | |||
# MIT License (http://www.opensource.org/licenses/mit-license.php) | |||
# | |||
# A CodeRay encoder that outputs code highlighted for a color terminal. | |||
# Check out http://robaldred.co.uk | |||
module CodeRay | |||
module Encoders | |||
class Term < Encoder | |||
register_for :term | |||
TOKEN_COLORS = { | |||
:annotation => '35', | |||
:attribute_name => '33', | |||
:attribute_name_fat => '33', | |||
:attribute_value => '31', | |||
:attribute_value_fat => '31', | |||
:bin => '1;35', | |||
:char => {:self => '36', :delimiter => '34'}, | |||
:class => '1;35', | |||
:class_variable => '36', | |||
:color => '32', | |||
:comment => '37', | |||
:complex => '34', | |||
:constant => ['34', '4'], | |||
:decoration => '35', | |||
:definition => '1;32', | |||
:directive => ['32', '4'], | |||
:doc => '46', | |||
:doctype => '1;30', | |||
:doc_string => ['31', '4'], | |||
:entity => '33', | |||
:error => ['1;33', '41'], | |||
:exception => '1;31', | |||
:float => '1;35', | |||
:function => '1;34', | |||
:global_variable => '42', | |||
:hex => '1;36', | |||
:important => '1;31', | |||
:include => '33', | |||
:integer => '1;34', | |||
:interpreted => '1;35', | |||
:key => '35', | |||
:label => '1;4', | |||
:local_variable => '33', | |||
:oct => '1;35', | |||
:operator_name => '1;29', | |||
:pre_constant => '1;36', | |||
:pre_type => '1;30', | |||
:predefined => ['4', '1;34'], | |||
:preprocessor => '36', | |||
:pseudo_class => '34', | |||
:regexp => { | |||
:content => '31', | |||
:delimiter => '1;29', | |||
:modifier => '35', | |||
:function => '1;29' | |||
}, | |||
:reserved => '1;31', | |||
:shell => { | |||
:self => '42', | |||
:content => '1;29', | |||
:delimiter => '37', | |||
}, | |||
:string => { | |||
:self => '32', | |||
:modifier => '1;32', | |||
:escape => '1;36', | |||
:delimiter => '1;32', | |||
}, | |||
:symbol => '1;32', | |||
:tag => '34', | |||
:tag_fat => '1;34', | |||
:tag_special => ['34', '4'], | |||
:type => '1;34', | |||
:value => '36', | |||
:variable => '34', | |||
:insert => '42', | |||
:delete => '41', | |||
:change => '44', | |||
:head => '45', | |||
} | |||
TOKEN_COLORS[:keyword] = TOKEN_COLORS[:reserved] | |||
TOKEN_COLORS[:method] = TOKEN_COLORS[:function] | |||
TOKEN_COLORS[:imaginary] = TOKEN_COLORS[:complex] | |||
TOKEN_COLORS[:open] = TOKEN_COLORS[:close] = TOKEN_COLORS[:nesting_delimiter] = TOKEN_COLORS[:escape] = TOKEN_COLORS[:delimiter] | |||
protected | |||
def setup(options) | |||
@out = '' | |||
@opened = [nil] | |||
@subcolors = nil | |||
end | |||
def finish(options) | |||
super | |||
end | |||
def token text, type = :plain | |||
case text | |||
when nil | |||
# raise 'Token with nil as text was given: %p' % [[text, type]] | |||
when String | |||
if color = (@subcolors || TOKEN_COLORS)[type] | |||
color = color[:self] || return if Hash === color | |||
@out << col(color) + text.gsub("\n", col(0) + "\n" + col(color)) + col(0) | |||
@out << col(@subcolors[:self]) if @subcolors && @subcolors[:self] | |||
else | |||
@out << text | |||
end | |||
# token groups, eg. strings | |||
when :open | |||
@opened[0] = type | |||
if color = TOKEN_COLORS[type] | |||
if Hash === color | |||
@subcolors = color | |||
@out << col(color[:self]) if color[:self] | |||
else | |||
@subcolors = {} | |||
@out << col(color) | |||
end | |||
end | |||
@opened << type | |||
when :close | |||
if @opened.empty? | |||
# nothing to close | |||
else | |||
@out << col(0) if (@subcolors || {})[:self] | |||
@subcolors = nil | |||
@opened.pop | |||
end | |||
# whole lines to be highlighted, eg. a added/modified/deleted lines in a diff | |||
when :begin_line | |||
when :end_line | |||
else | |||
raise 'unknown token kind: %p' % [text] | |||
end | |||
end | |||
private | |||
def col(color) | |||
Array(color).map { |c| "\e[#{c}m" }.join | |||
end | |||
end | |||
end | |||
end |
@@ -1,32 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
class Text < Encoder | |||
include Streamable | |||
register_for :text | |||
FILE_EXTENSION = 'txt' | |||
DEFAULT_OPTIONS = { | |||
:separator => '' | |||
} | |||
protected | |||
def setup options | |||
super | |||
@sep = options[:separator] | |||
end | |||
def text_token text, kind | |||
text + @sep | |||
end | |||
def finish options | |||
super.chomp @sep | |||
end | |||
end | |||
end | |||
end |
@@ -1,84 +0,0 @@ | |||
($:.unshift '../..'; require 'coderay') unless defined? CodeRay | |||
module CodeRay | |||
module Encoders | |||
load :filter | |||
class TokenClassFilter < Filter | |||
include Streamable | |||
register_for :token_class_filter | |||
DEFAULT_OPTIONS = { | |||
:exclude => [], | |||
:include => :all | |||
} | |||
protected | |||
def setup options | |||
super | |||
@exclude = options[:exclude] | |||
@exclude = Array(@exclude) unless @exclude == :all | |||
@include = options[:include] | |||
@include = Array(@include) unless @include == :all | |||
end | |||
def include_text_token? text, kind | |||
(@include == :all || @include.include?(kind)) && | |||
!(@exclude == :all || @exclude.include?(kind)) | |||
end | |||
end | |||
end | |||
end | |||
if $0 == __FILE__ | |||
$VERBOSE = true | |||
$: << File.join(File.dirname(__FILE__), '..') | |||
eval DATA.read, nil, $0, __LINE__ + 4 | |||
end | |||
__END__ | |||
require 'test/unit' | |||
class TokenClassFilterTest < Test::Unit::TestCase | |||
def test_creation | |||
assert CodeRay::Encoders::TokenClassFilter < CodeRay::Encoders::Encoder | |||
assert CodeRay::Encoders::TokenClassFilter < CodeRay::Encoders::Filter | |||
filter = nil | |||
assert_nothing_raised do | |||
filter = CodeRay.encoder :token_class_filter | |||
end | |||
assert_instance_of CodeRay::Encoders::TokenClassFilter, filter | |||
end | |||
def test_filtering_text_tokens | |||
tokens = CodeRay::Tokens.new | |||
for i in 1..10 | |||
tokens << [i.to_s, :index] | |||
tokens << [' ', :space] if i < 10 | |||
end | |||
assert_equal 10, CodeRay::Encoders::TokenClassFilter.new.encode_tokens(tokens, :exclude => :space).size | |||
assert_equal 10, tokens.token_class_filter(:exclude => :space).size | |||
assert_equal 9, CodeRay::Encoders::TokenClassFilter.new.encode_tokens(tokens, :include => :space).size | |||
assert_equal 9, tokens.token_class_filter(:include => :space).size | |||
assert_equal 0, CodeRay::Encoders::TokenClassFilter.new.encode_tokens(tokens, :exclude => :all).size | |||
assert_equal 0, tokens.token_class_filter(:exclude => :all).size | |||
end | |||
def test_filtering_block_tokens | |||
tokens = CodeRay::Tokens.new | |||
10.times do |i| | |||
tokens << [:open, :index] | |||
tokens << [i.to_s, :content] | |||
tokens << [:close, :index] | |||
end | |||
assert_equal 20, CodeRay::Encoders::TokenClassFilter.new.encode_tokens(tokens, :include => :blubb).size | |||
assert_equal 20, tokens.token_class_filter(:include => :blubb).size | |||
assert_equal 30, CodeRay::Encoders::TokenClassFilter.new.encode_tokens(tokens, :exclude => :index).size | |||
assert_equal 30, tokens.token_class_filter(:exclude => :index).size | |||
end | |||
end |
@@ -1,22 +0,0 @@ | |||
module CodeRay | |||
module Encoders | |||
# = YAML Encoder | |||
# | |||
# Slow. | |||
class YAML < Encoder | |||
register_for :yaml | |||
FILE_EXTENSION = 'yaml' | |||
protected | |||
def compile tokens, options | |||
require 'yaml' | |||
@out = tokens.to_a.to_yaml | |||
end | |||
end | |||
end | |||
end |
@@ -1,255 +0,0 @@ | |||
#!/usr/bin/env ruby | |||
module CodeRay | |||
# = FileType | |||
# | |||
# A simple filetype recognizer. | |||
# | |||
# Copyright (c) 2006 by murphy (Kornelius Kalnbach) <murphy rubychan de> | |||
# | |||
# License:: LGPL / ask the author | |||
# Version:: 0.1 (2005-09-01) | |||
# | |||
# == Documentation | |||
# | |||
# # determine the type of the given | |||
# lang = FileType[ARGV.first] | |||
# | |||
# # return :plaintext if the file type is unknown | |||
# lang = FileType.fetch ARGV.first, :plaintext | |||
# | |||
# # try the shebang line, too | |||
# lang = FileType.fetch ARGV.first, :plaintext, 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 | |||
def shebang filename | |||
begin | |||
File.open filename, 'r' do |f| | |||
if first_line = f.gets | |||
if type = first_line[TypeFromShebang] | |||
type.to_sym | |||
end | |||
end | |||
end | |||
rescue IOError | |||
nil | |||
end | |||
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 and block_given? | |||
warn 'block supersedes default value argument' | |||
end | |||
unless type = self[filename, read_shebang] | |||
return yield if block_given? | |||
return default if default | |||
raise UnknownFileType, 'Could not determine type of %p.' % filename | |||
end | |||
type | |||
end | |||
end | |||
TypeFromExt = { | |||
'c' => :c, | |||
'css' => :css, | |||
'diff' => :diff, | |||
'dpr' => :delphi, | |||
'groovy' => :groovy, | |||
'gvy' => :groovy, | |||
'h' => :c, | |||
'htm' => :html, | |||
'html' => :html, | |||
'html.erb' => :rhtml, | |||
'java' => :java, | |||
'js' => :java_script, | |||
'json' => :json, | |||
'mab' => :ruby, | |||
'pas' => :delphi, | |||
'patch' => :diff, | |||
'php' => :php, | |||
'php3' => :php, | |||
'php4' => :php, | |||
'php5' => :php, | |||
'py' => :python, | |||
'py3' => :python, | |||
'pyw' => :python, | |||
'rake' => :ruby, | |||
'raydebug' => :debug, | |||
'rb' => :ruby, | |||
'rbw' => :ruby, | |||
'rhtml' => :rhtml, | |||
'rxml' => :ruby, | |||
'sch' => :scheme, | |||
'sql' => :sql, | |||
'ss' => :scheme, | |||
'xhtml' => :xhtml, | |||
'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 = { | |||
'Rakefile' => :ruby, | |||
'Rantfile' => :ruby, | |||
} | |||
end | |||
end | |||
if $0 == __FILE__ | |||
$VERBOSE = true | |||
eval DATA.read, nil, $0, __LINE__ + 4 | |||
end | |||
__END__ | |||
require 'test/unit' | |||
class FileTypeTests < Test::Unit::TestCase | |||
include CodeRay | |||
def test_fetch | |||
assert_raise FileType::UnknownFileType do | |||
FileType.fetch '' | |||
end | |||
assert_throws :not_found do | |||
FileType.fetch '.' do | |||
throw :not_found | |||
end | |||
end | |||
assert_equal :default, FileType.fetch('c', :default) | |||
stderr, fake_stderr = $stderr, Object.new | |||
$err = '' | |||
def fake_stderr.write x | |||
$err << x | |||
end | |||
$stderr = fake_stderr | |||
FileType.fetch('c', :default) { } | |||
assert_equal "block supersedes default value argument\n", $err | |||
$stderr = stderr | |||
end | |||
def test_ruby | |||
assert_equal :ruby, FileType['test.rb'] | |||
assert_equal :ruby, FileType['test.java.rb'] | |||
assert_equal :java, FileType['test.rb.java'] | |||
assert_equal :ruby, FileType['C:\\Program Files\\x\\y\\c\\test.rbw'] | |||
assert_equal :ruby, FileType['/usr/bin/something/Rakefile'] | |||
assert_equal :ruby, FileType['~/myapp/gem/Rantfile'] | |||
assert_equal :ruby, FileType['./lib/tasks\repository.rake'] | |||
assert_not_equal :ruby, FileType['test_rb'] | |||
assert_not_equal :ruby, FileType['Makefile'] | |||
assert_not_equal :ruby, FileType['set.rb/set'] | |||
assert_not_equal :ruby, FileType['~/projects/blabla/rb'] | |||
end | |||
def test_c | |||
assert_equal :c, FileType['test.c'] | |||
assert_equal :c, FileType['C:\\Program Files\\x\\y\\c\\test.h'] | |||
assert_not_equal :c, FileType['test_c'] | |||
assert_not_equal :c, FileType['Makefile'] | |||
assert_not_equal :c, FileType['set.h/set'] | |||
assert_not_equal :c, FileType['~/projects/blabla/c'] | |||
end | |||
def test_cpp | |||
assert_equal :cpp, FileType['test.c++'] | |||
assert_equal :cpp, FileType['test.cxx'] | |||
assert_equal :cpp, FileType['test.hh'] | |||
assert_equal :cpp, FileType['test.hpp'] | |||
assert_equal :cpp, FileType['test.cu'] | |||
assert_equal :cpp, FileType['test.C'] | |||
assert_not_equal :cpp, FileType['test.c'] | |||
assert_not_equal :cpp, FileType['test.h'] | |||
end | |||
def test_html | |||
assert_equal :html, FileType['test.htm'] | |||
assert_equal :xhtml, FileType['test.xhtml'] | |||
assert_equal :xhtml, FileType['test.html.xhtml'] | |||
assert_equal :rhtml, FileType['_form.rhtml'] | |||
assert_equal :rhtml, FileType['_form.html.erb'] | |||
end | |||
def test_yaml | |||
assert_equal :yaml, FileType['test.yml'] | |||
assert_equal :yaml, FileType['test.yaml'] | |||
assert_equal :yaml, FileType['my.html.yaml'] | |||
assert_not_equal :yaml, FileType['YAML'] | |||
end | |||
def test_pathname | |||
require 'pathname' | |||
pn = Pathname.new 'test.rb' | |||
assert_equal :ruby, FileType[pn] | |||
dir = Pathname.new '/etc/var/blubb' | |||
assert_equal :ruby, FileType[dir + pn] | |||
assert_equal :cpp, FileType[dir + 'test.cpp'] | |||
end | |||
def test_no_shebang | |||
dir = './test' | |||
if File.directory? dir | |||
Dir.chdir dir do | |||
assert_equal :c, FileType['test.c'] | |||
end | |||
end | |||
end | |||
def test_shebang_empty_file | |||
require 'tmpdir' | |||
tmpfile = File.join(Dir.tmpdir, 'bla') | |||
File.open(tmpfile, 'w') { } # touch | |||
assert_equal nil, FileType[tmpfile] | |||
end | |||
def test_shebang | |||
require 'tmpdir' | |||
tmpfile = File.join(Dir.tmpdir, 'bla') | |||
File.open(tmpfile, 'w') { |f| f.puts '#!/usr/bin/env ruby' } | |||
assert_equal :ruby, FileType[tmpfile, true] | |||
end | |||
end |
@@ -1,123 +0,0 @@ | |||
# =GZip Simple | |||
# | |||
# A simplified interface to the gzip library +zlib+ (from the Ruby Standard Library.) | |||
# | |||
# Author: murphy (mail to murphy rubychan de) | |||
# | |||
# Version: 0.2 (2005.may.28) | |||
# | |||
# ==Documentation | |||
# | |||
# See +GZip+ module and the +String+ extensions. | |||
# | |||
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 | |||
# String extensions to use the GZip module. | |||
# | |||
# The methods gzip and gunzip provide an even more simple | |||
# interface to the ZLib: | |||
# | |||
# # create a big string | |||
# x = 'a' * 1000 | |||
# | |||
# # zip it | |||
# x_gz = x.gzip | |||
# | |||
# # test the result | |||
# puts 'Zipped %d bytes to %d bytes.' % [x.size, x_gz.size] | |||
# #-> Zipped 1000 bytes to 19 bytes. | |||
# | |||
# # unzipping works | |||
# p x_gz.gunzip == x #-> true | |||
class String | |||
# Returns the string, unzipped. | |||
# See GZip.gunzip | |||
def gunzip | |||
GZip.gunzip self | |||
end | |||
# Replaces the string with its unzipped value. | |||
# See GZip.gunzip | |||
def gunzip! | |||
replace gunzip | |||
end | |||
# Returns the string, zipped. | |||
# +level+ is the gzip compression level, see GZip.gzip. | |||
def gzip level = GZip::DEFAULT_GZIP_LEVEL | |||
GZip.gzip self, level | |||
end | |||
# Replaces the string with its zipped value. | |||
# See GZip.gzip. | |||
def gzip!(*args) | |||
replace gzip(*args) | |||
end | |||
end | |||
if $0 == __FILE__ | |||
eval DATA.read, nil, $0, __LINE__+4 | |||
end | |||
__END__ | |||
#CODE | |||
# Testing / Benchmark | |||
x = 'a' * 1000 | |||
x_gz = x.gzip | |||
puts 'Zipped %d bytes to %d bytes.' % [x.size, x_gz.size] #-> Zipped 1000 bytes to 19 bytes. | |||
p x_gz.gunzip == x #-> true | |||
require 'benchmark' | |||
INFO = 'packed to %0.3f%%' # :nodoc: | |||
x = Array.new(100000) { rand(255).chr + 'aaaaaaaaa' + rand(255).chr }.join | |||
Benchmark.bm(10) do |bm| | |||
for level in 0..9 | |||
bm.report "zip #{level}" do | |||
$x = x.gzip level | |||
end | |||
puts INFO % [100.0 * $x.size / x.size] | |||
end | |||
bm.report 'zip' do | |||
$x = x.gzip | |||
end | |||
puts INFO % [100.0 * $x.size / x.size] | |||
bm.report 'unzip' do | |||
$x.gunzip | |||
end | |||
end |
@@ -1,349 +0,0 @@ | |||
module CodeRay | |||
# = PluginHost | |||
# | |||
# A simple subclass plugin system. | |||
# | |||
# Example: | |||
# class Generators < 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' | |||
module PluginHost | |||
# Raised if Encoders::[] fails because: | |||
# * a file could not be found | |||
# * the requested Encoder is not registered | |||
PluginNotFound = Class.new Exception | |||
HostNotFound = Class.new Exception | |||
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 for +[]+. | |||
alias load [] | |||
def require_helper plugin_id, helper_name | |||
path = path_to File.join(plugin_id, helper_name) | |||
require path | |||
end | |||
class << self | |||
# Adds the module/class to the PLUGIN_HOSTS list. | |||
def extended mod | |||
PLUGIN_HOSTS << mod | |||
end | |||
# Warns you that you should not #include this module. | |||
def included mod | |||
warn "#{name} should not be included. Use extend." | |||
end | |||
# Find the PluginHost for host_id. | |||
def host_by_id host_id | |||
unless PLUGIN_HOSTS_BY_ID.default_proc | |||
ph = Hash.new do |h, a_host_id| | |||
for host in PLUGIN_HOSTS | |||
h[host.host_id] = host | |||
end | |||
h.fetch a_host_id, nil | |||
end | |||
PLUGIN_HOSTS_BY_ID.replace ph | |||
end | |||
PLUGIN_HOSTS_BY_ID[host_id] | |||
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) | |||
load_map | |||
end | |||
@plugin_path | |||
end | |||
# The host's ID. | |||
# | |||
# If PLUGIN_HOST_ID is not set, it is simply the class name. | |||
def host_id | |||
if self.const_defined? :PLUGIN_HOST_ID | |||
self::PLUGIN_HOST_ID | |||
else | |||
name | |||
end | |||
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. | |||
# | |||
# See also map. | |||
# | |||
# class MyColorHost < PluginHost | |||
# map :navy => :dark_blue | |||
# default :gray | |||
# end | |||
def default id = nil | |||
if id | |||
id = validate_id id | |||
plugin_hash[nil] = id | |||
else | |||
plugin_hash[nil] | |||
end | |||
end | |||
# Every plugin must register itself for one or more | |||
# +ids+ by calling register_for, which calls this method. | |||
# | |||
# See Plugin#register_for. | |||
def register plugin, *ids | |||
for id in ids | |||
unless id.is_a? Symbol | |||
raise ArgumentError, | |||
"id must be a Symbol, but it was a #{id.class}" | |||
end | |||
plugin_hash[validate_id(id)] = plugin | |||
end | |||
end | |||
# A Hash of plugion_id => Plugin pairs. | |||
def plugin_hash | |||
@plugin_hash ||= create_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' | |||
end | |||
end | |||
# Makes a map of all loaded plugins. | |||
def inspect | |||
map = plugin_hash.dup | |||
map.each do |id, plugin| | |||
map[id] = plugin.to_s[/(?>\w+)$/] | |||
end | |||
"#{name}[#{host_id}]#{map.inspect}" | |||
end | |||
protected | |||
# Created a new plugin list and stores it to @plugin_hash. | |||
def create_plugin_hash | |||
@plugin_hash = | |||
Hash.new do |h, plugin_id| | |||
id = validate_id(plugin_id) | |||
path = path_to id | |||
begin | |||
require path | |||
rescue LoadError => boom | |||
if h.has_key? nil # default plugin | |||
h[id] = h[nil] | |||
else | |||
raise PluginNotFound, 'Could not load plugin %p: %s' % [id, boom] | |||
end | |||
else | |||
# Plugin should have registered by now | |||
unless h.has_key? id | |||
raise PluginNotFound, | |||
"No #{self.name} plugin for #{id.inspect} found in #{path}." | |||
end | |||
end | |||
h[id] | |||
end | |||
end | |||
# Loads the map file (see map). | |||
# | |||
# This is done automatically when plugin_path is called. | |||
def load_map | |||
mapfile = path_to '_map' | |||
if File.exist? mapfile | |||
require mapfile | |||
elsif $VERBOSE | |||
warn 'no _map.rb found for %s' % name | |||
end | |||
end | |||
# Returns the Plugin for +id+. | |||
# Use it like Hash#fetch. | |||
# | |||
# Example: | |||
# yaml_plugin = MyPluginHost[:yaml, :default] | |||
def fetch id, *args, &blk | |||
plugin_hash.fetch validate_id(id), *args, &blk | |||
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: '#{id}' given." | |||
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. | |||
# | |||
# Example: see PluginHost. | |||
module Plugin | |||
def included mod | |||
warn "#{name} should not be included. Use extend." | |||
end | |||
# Register this class for the given langs. | |||
# Example: | |||
# class MyPlugin < PluginHost::BaseClass | |||
# register_for :my_id | |||
# ... | |||
# end | |||
# | |||
# See PluginHost.register. | |||
def register_for *ids | |||
plugin_host.register self, *ids | |||
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 host for this Plugin class. | |||
def plugin_host host = nil | |||
if host and not host.is_a? PluginHost | |||
raise ArgumentError, | |||
"PluginHost expected, but #{host.class} given." | |||
end | |||
self.const_set :PLUGIN_HOST, host if host | |||
self::PLUGIN_HOST | |||
end | |||
# Require some helper files. | |||
# | |||
# Example: | |||
# | |||
# class MyPlugin < PluginHost::BaseClass | |||
# register_for :my_id | |||
# helper :my_helper | |||
# | |||
# The above example loads the file myplugin/my_helper.rb relative to the | |||
# file in which MyPlugin was defined. | |||
# | |||
# You can also load a helper from a different plugin: | |||
# | |||
# helper 'other_plugin/helper_name' | |||
def helper *helpers | |||
for helper in helpers | |||
if helper.is_a?(String) && helper[/\//] | |||
self::PLUGIN_HOST.require_helper $`, $' | |||
else | |||
self::PLUGIN_HOST.require_helper plugin_id, helper.to_s | |||
end | |||
end | |||
end | |||
# Returns the pulgin id used by the engine. | |||
def plugin_id | |||
name[/\w+$/].downcase | |||
end | |||
end | |||
# Convenience method for plugin loading. | |||
# The syntax used is: | |||
# | |||
# CodeRay.require_plugin '<Host ID>/<Plugin ID>' | |||
# | |||
# Returns the loaded plugin. | |||
def self.require_plugin path | |||
host_id, plugin_id = path.split '/', 2 | |||
host = PluginHost.host_by_id(host_id) | |||
raise PluginHost::HostNotFound, | |||
"No host for #{host_id.inspect} found." unless host | |||
host.load plugin_id | |||
end | |||
end |
@@ -1,138 +0,0 @@ | |||
module CodeRay | |||
# = WordList | |||
# | |||
# <b>A Hash subclass designed for mapping word lists to token types.</b> | |||
# | |||
# Copyright (c) 2006 by murphy (Kornelius Kalnbach) <murphy rubychan de> | |||
# | |||
# License:: LGPL / ask the author | |||
# Version:: 1.1 (2006-Oct-19) | |||
# | |||
# A WordList is a Hash with some additional features. | |||
# It is intended to be used for keyword recognition. | |||
# | |||
# WordList is highly optimized to be used in Scanners, | |||
# typically to decide whether a given ident is a special token. | |||
# | |||
# For case insensitive words use CaseIgnoringWordList. | |||
# | |||
# Example: | |||
# | |||
# # define word arrays | |||
# RESERVED_WORDS = %w[ | |||
# asm break case continue default do else | |||
# ... | |||
# ] | |||
# | |||
# PREDEFINED_TYPES = %w[ | |||
# int long short char void | |||
# ... | |||
# ] | |||
# | |||
# PREDEFINED_CONSTANTS = %w[ | |||
# EOF NULL ... | |||
# ] | |||
# | |||
# # make a WordList | |||
# IDENT_KIND = WordList.new(:ident). | |||
# add(RESERVED_WORDS, :reserved). | |||
# add(PREDEFINED_TYPES, :pre_type). | |||
# add(PREDEFINED_CONSTANTS, :pre_constant) | |||
# | |||
# ... | |||
# | |||
# def scan_tokens tokens, options | |||
# ... | |||
# | |||
# elsif scan(/[A-Za-z_][A-Za-z_0-9]*/) | |||
# # use it | |||
# kind = IDENT_KIND[match] | |||
# ... | |||
class WordList < Hash | |||
# Creates a new WordList with +default+ as default value. | |||
# | |||
# You can activate +caching+ to store the results for every [] request. | |||
# | |||
# With caching, methods like +include?+ or +delete+ may no longer behave | |||
# as you expect. Therefore, it is recommended to use the [] method only. | |||
def initialize default = false, caching = false, &block | |||
if block | |||
raise ArgumentError, 'Can\'t combine block with caching.' if caching | |||
super(&block) | |||
else | |||
if caching | |||
super() do |h, k| | |||
h[k] = h.fetch k, default | |||
end | |||
else | |||
super default | |||
end | |||
end | |||
end | |||
# Add words to the list and associate them with +kind+. | |||
# | |||
# Returns +self+, so you can concat add calls. | |||
def add words, kind = true | |||
words.each do |word| | |||
self[word] = kind | |||
end | |||
self | |||
end | |||
end | |||
# A CaseIgnoringWordList is like a WordList, only that | |||
# keys are compared case-insensitively. | |||
# | |||
# Ignoring the text case is realized by sending the +downcase+ message to | |||
# all keys. | |||
# | |||
# Caching usually makes a CaseIgnoringWordList faster, but it has to be | |||
# activated explicitely. | |||
class CaseIgnoringWordList < WordList | |||
# Creates a new case-insensitive WordList with +default+ as default value. | |||
# | |||
# You can activate caching to store the results for every [] request. | |||
# This speeds up subsequent lookups for the same word, but also | |||
# uses memory. | |||
def initialize default = false, caching = false | |||
if caching | |||
super(default, false) do |h, k| | |||
h[k] = h.fetch k.downcase, default | |||
end | |||
else | |||
super(default, false) | |||
extend Uncached | |||
end | |||
end | |||
module Uncached # :nodoc: | |||
def [] key | |||
super(key.downcase) | |||
end | |||
end | |||
# Add +words+ to the list and associate them with +kind+. | |||
def add words, kind = true | |||
words.each do |word| | |||
self[word.downcase] = kind | |||
end | |||
self | |||
end | |||
end | |||
end | |||
__END__ | |||
# check memory consumption | |||
END { | |||
ObjectSpace.each_object(CodeRay::CaseIgnoringWordList) do |wl| | |||
p wl.inject(0) { |memo, key, value| memo + key.size + 24 } | |||
end | |||
} |
@@ -1,298 +0,0 @@ | |||
module CodeRay | |||
require 'coderay/helpers/plugin' | |||
# = Scanners | |||
# | |||
# This module holds the Scanner class and its subclasses. | |||
# For example, the Ruby scanner is named CodeRay::Scanners::Ruby | |||
# can be found in coderay/scanners/ruby. | |||
# | |||
# Scanner also provides methods and constants for the register | |||
# mechanism and the [] method that returns the Scanner class | |||
# belonging to the given lang. | |||
# | |||
# See PluginHost. | |||
module Scanners | |||
extend PluginHost | |||
plugin_path File.dirname(__FILE__), 'scanners' | |||
require 'strscan' | |||
# = Scanner | |||
# | |||
# The base class for all Scanners. | |||
# | |||
# It is a subclass of Ruby's great +StringScanner+, which | |||
# makes it easy to access the scanning methods inside. | |||
# | |||
# It is also +Enumerable+, so you can use it like an Array of | |||
# Tokens: | |||
# | |||
# require 'coderay' | |||
# | |||
# c_scanner = CodeRay::Scanners[:c].new "if (*p == '{') nest++;" | |||
# | |||
# for text, kind in c_scanner | |||
# puts text if kind == :operator | |||
# end | |||
# | |||
# # prints: (*==)++; | |||
# | |||
# OK, this is a very simple example :) | |||
# You can also use +map+, +any?+, +find+ and even +sort_by+, | |||
# if you want. | |||
class Scanner < StringScanner | |||
extend Plugin | |||
plugin_host Scanners | |||
# Raised if a Scanner fails while scanning | |||
ScanError = Class.new(Exception) | |||
require 'coderay/helpers/word_list' | |||
# The default options for all scanner classes. | |||
# | |||
# Define @default_options for subclasses. | |||
DEFAULT_OPTIONS = { :stream => false } | |||
KINDS_NOT_LOC = [:comment, :doctype] | |||
class << self | |||
# Returns if the Scanner can be used in streaming mode. | |||
def streamable? | |||
is_a? Streamable | |||
end | |||
def normify code | |||
code = code.to_s | |||
if code.respond_to?(:encoding) && (code.encoding.name != 'UTF-8' || !code.valid_encoding?) | |||
code = code.dup | |||
original_encoding = code.encoding | |||
code.force_encoding 'Windows-1252' | |||
unless code.valid_encoding? | |||
code.force_encoding original_encoding | |||
if code.encoding.name == 'UTF-8' | |||
code.encode! 'UTF-16BE', :invalid => :replace, :undef => :replace, :replace => '?' | |||
end | |||
code.encode! 'UTF-8', :invalid => :replace, :undef => :replace, :replace => '?' | |||
end | |||
end | |||
code.to_unix | |||
end | |||
def file_extension extension = nil | |||
if extension | |||
@file_extension = extension.to_s | |||
else | |||
@file_extension ||= plugin_id.to_s | |||
end | |||
end | |||
end | |||
=begin | |||
## Excluded for speed reasons; protected seems to make methods slow. | |||
# Save the StringScanner methods from being called. | |||
# This would not be useful for highlighting. | |||
strscan_public_methods = | |||
StringScanner.instance_methods - | |||
StringScanner.ancestors[1].instance_methods | |||
protected(*strscan_public_methods) | |||
=end | |||
# Create a new Scanner. | |||
# | |||
# * +code+ is the input String and is handled by the superclass | |||
# StringScanner. | |||
# * +options+ is a Hash with Symbols as keys. | |||
# It is merged with the default options of the class (you can | |||
# overwrite default options here.) | |||
# * +block+ is the callback for streamed highlighting. | |||
# | |||
# If you set :stream to +true+ in the options, the Scanner uses a | |||
# TokenStream with the +block+ as callback to handle the tokens. | |||
# | |||
# Else, a Tokens object is used. | |||
def initialize code='', options = {}, &block | |||
raise "I am only the basic Scanner class. I can't scan "\ | |||
"anything. :( Use my subclasses." if self.class == Scanner | |||
@options = self.class::DEFAULT_OPTIONS.merge options | |||
super Scanner.normify(code) | |||
@tokens = options[:tokens] | |||
if @options[:stream] | |||
warn "warning in CodeRay::Scanner.new: :stream is set, "\ | |||
"but no block was given" unless block_given? | |||
raise NotStreamableError, self unless kind_of? Streamable | |||
@tokens ||= TokenStream.new(&block) | |||
else | |||
warn "warning in CodeRay::Scanner.new: Block given, "\ | |||
"but :stream is #{@options[:stream]}" if block_given? | |||
@tokens ||= Tokens.new | |||
end | |||
@tokens.scanner = self | |||
setup | |||
end | |||
def reset | |||
super | |||
reset_instance | |||
end | |||
def string= code | |||
code = Scanner.normify(code) | |||
if defined?(RUBY_DESCRIPTION) && RUBY_DESCRIPTION['rubinius 1.0.1'] | |||
reset_state | |||
@string = code | |||
else | |||
super code | |||
end | |||
reset_instance | |||
end | |||
# More mnemonic accessor name for the input string. | |||
alias code string | |||
alias code= string= | |||
# Returns the Plugin ID for this scanner. | |||
def lang | |||
self.class.plugin_id | |||
end | |||
# Scans the code and returns all tokens in a Tokens object. | |||
def tokenize new_string=nil, options = {} | |||
options = @options.merge(options) | |||
self.string = new_string if new_string | |||
@cached_tokens = | |||
if @options[:stream] # :stream must have been set already | |||
reset unless new_string | |||
scan_tokens @tokens, options | |||
@tokens | |||
else | |||
scan_tokens @tokens, options | |||
end | |||
end | |||
def tokens | |||
@cached_tokens ||= tokenize | |||
end | |||
# Whether the scanner is in streaming mode. | |||
def streaming? | |||
!!@options[:stream] | |||
end | |||
# Traverses the tokens. | |||
def each &block | |||
raise ArgumentError, | |||
'Cannot traverse TokenStream.' if @options[:stream] | |||
tokens.each(&block) | |||
end | |||
include Enumerable | |||
# The current line position of the scanner. | |||
# | |||
# Beware, this is implemented inefficiently. It should be used | |||
# for debugging only. | |||
def line | |||
string[0..pos].count("\n") + 1 | |||
end | |||
def column pos = self.pos | |||
return 0 if pos <= 0 | |||
string = string() | |||
if string.respond_to?(:bytesize) && (defined?(@bin_string) || string.bytesize != string.size) | |||
@bin_string ||= string.dup.force_encoding('binary') | |||
string = @bin_string | |||
end | |||
pos - (string.rindex(?\n, pos) || 0) | |||
end | |||
def marshal_dump | |||
@options | |||
end | |||
def marshal_load options | |||
@options = options | |||
end | |||
protected | |||
# Can be implemented by subclasses to do some initialization | |||
# that has to be done once per instance. | |||
# | |||
# Use reset for initialization that has to be done once per | |||
# scan. | |||
def setup | |||
end | |||
# This is the central method, and commonly the only one a | |||
# subclass implements. | |||
# | |||
# Subclasses must implement this method; it must return +tokens+ | |||
# and must only use Tokens#<< for storing scanned tokens! | |||
def scan_tokens tokens, options | |||
raise NotImplementedError, | |||
"#{self.class}#scan_tokens not implemented." | |||
end | |||
def reset_instance | |||
@tokens.clear unless @options[:keep_tokens] | |||
@cached_tokens = nil | |||
@bin_string = nil if defined? @bin_string | |||
end | |||
# Scanner error with additional status information | |||
def raise_inspect msg, tokens, state = 'No state given!', ambit = 30 | |||
raise ScanError, <<-EOE % [ | |||
***ERROR in %s: %s (after %d tokens) | |||
tokens: | |||
%s | |||
current line: %d column: %d pos: %d | |||
matched: %p state: %p | |||
bol? = %p, eos? = %p | |||
surrounding code: | |||
%p ~~ %p | |||
***ERROR*** | |||
EOE | |||
File.basename(caller[0]), | |||
msg, | |||
tokens.size, | |||
tokens.last(10).map { |t| t.inspect }.join("\n"), | |||
line, column, pos, | |||
matched, state, bol?, eos?, | |||
string[pos - ambit, ambit], | |||
string[pos, ambit], | |||
] | |||
end | |||
end | |||
end | |||
end | |||
class String | |||
# I love this hack. It seems to silence all dos/unix/mac newline problems. | |||
def to_unix | |||
if index ?\r | |||
gsub(/\r\n?/, "\n") | |||
else | |||
self | |||
end | |||
end | |||
end |
@@ -1,23 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
map \ | |||
:h => :c, | |||
:cplusplus => :cpp, | |||
:'c++' => :cpp, | |||
:ecma => :java_script, | |||
:ecmascript => :java_script, | |||
:ecma_script => :java_script, | |||
:irb => :ruby, | |||
:javascript => :java_script, | |||
:js => :java_script, | |||
:nitro => :nitro_xhtml, | |||
:pascal => :delphi, | |||
:plain => :plaintext, | |||
:xhtml => :html, | |||
:yml => :yaml | |||
default :plain | |||
end | |||
end |
@@ -1,203 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class C < Scanner | |||
include Streamable | |||
register_for :c | |||
file_extension 'c' | |||
RESERVED_WORDS = [ | |||
'asm', 'break', 'case', 'continue', 'default', 'do', | |||
'else', 'enum', 'for', 'goto', 'if', 'return', | |||
'sizeof', 'struct', 'switch', 'typedef', 'union', 'while', | |||
'restrict', # added in C99 | |||
] | |||
PREDEFINED_TYPES = [ | |||
'int', 'long', 'short', 'char', | |||
'signed', 'unsigned', 'float', 'double', | |||
'bool', 'complex', # added in C99 | |||
] | |||
PREDEFINED_CONSTANTS = [ | |||
'EOF', 'NULL', | |||
'true', 'false', # added in C99 | |||
] | |||
DIRECTIVES = [ | |||
'auto', 'extern', 'register', 'static', 'void', | |||
'const', 'volatile', # added in C89 | |||
'inline', # added in C99 | |||
] | |||
IDENT_KIND = WordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_TYPES, :pre_type). | |||
add(DIRECTIVES, :directive). | |||
add(PREDEFINED_CONSTANTS, :pre_constant) | |||
ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x | |||
def scan_tokens tokens, options | |||
state = :initial | |||
label_expected = true | |||
case_expected = false | |||
label_expected_before_preproc_line = nil | |||
in_preproc_line = false | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if match = scan(/ \s+ | \\\n /x) | |||
if in_preproc_line && match != "\\\n" && match.index(?\n) | |||
in_preproc_line = false | |||
label_expected = label_expected_before_preproc_line | |||
end | |||
tokens << [match, :space] | |||
next | |||
elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) | |||
kind = :comment | |||
elsif match = scan(/ \# \s* if \s* 0 /x) | |||
match << scan_until(/ ^\# (?:elif|else|endif) .*? $ | \z /xm) unless eos? | |||
kind = :comment | |||
elsif match = scan(/ [-+*=<>?:;,!&^|()\[\]{}~%]+ | \/=? | \.(?!\d) /x) | |||
label_expected = match =~ /[;\{\}]/ | |||
if case_expected | |||
label_expected = true if match == ':' | |||
case_expected = false | |||
end | |||
kind = :operator | |||
elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) | |||
kind = IDENT_KIND[match] | |||
if kind == :ident && label_expected && !in_preproc_line && scan(/:(?!:)/) | |||
kind = :label | |||
match << matched | |||
else | |||
label_expected = false | |||
if kind == :reserved | |||
case match | |||
when 'case', 'default' | |||
case_expected = true | |||
end | |||
end | |||
end | |||
elsif scan(/\$/) | |||
kind = :ident | |||
elsif match = scan(/L?"/) | |||
tokens << [:open, :string] | |||
if match[0] == ?L | |||
tokens << ['L', :modifier] | |||
match = '"' | |||
end | |||
state = :string | |||
kind = :delimiter | |||
elsif scan(/#[ \t]*(\w*)/) | |||
kind = :preprocessor | |||
in_preproc_line = true | |||
label_expected_before_preproc_line = label_expected | |||
state = :include_expected if self[1] == 'include' | |||
elsif scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox) | |||
label_expected = false | |||
kind = :char | |||
elsif scan(/0[xX][0-9A-Fa-f]+/) | |||
label_expected = false | |||
kind = :hex | |||
elsif scan(/(?:0[0-7]+)(?![89.eEfF])/) | |||
label_expected = false | |||
kind = :oct | |||
elsif scan(/(?:\d+)(?![.eEfF])L?L?/) | |||
label_expected = false | |||
kind = :integer | |||
elsif scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) | |||
label_expected = false | |||
kind = :float | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string | |||
if scan(/[^\\\n"]+/) | |||
kind = :content | |||
elsif scan(/"/) | |||
tokens << ['"', :delimiter] | |||
tokens << [:close, :string] | |||
state = :initial | |||
label_expected = false | |||
next | |||
elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
kind = :char | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, :string] | |||
kind = :error | |||
state = :initial | |||
label_expected = false | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
when :include_expected | |||
if scan(/<[^>\n]+>?|"[^"\n\\]*(?:\\.[^"\n\\]*)*"?/) | |||
kind = :include | |||
state = :initial | |||
elsif match = scan(/\s+/) | |||
kind = :space | |||
state = :initial if match.index ?\n | |||
else | |||
state = :initial | |||
next | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
if state == :string | |||
tokens << [:close, :string] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,228 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class CPlusPlus < Scanner | |||
include Streamable | |||
register_for :cpp | |||
file_extension 'cpp' | |||
title 'C++' | |||
# http://www.cppreference.com/wiki/keywords/start | |||
RESERVED_WORDS = [ | |||
'and', 'and_eq', 'asm', 'bitand', 'bitor', 'break', | |||
'case', 'catch', 'class', 'compl', 'const_cast', | |||
'continue', 'default', 'delete', 'do', 'dynamic_cast', 'else', | |||
'enum', 'export', 'for', 'goto', 'if', 'namespace', 'new', | |||
'not', 'not_eq', 'or', 'or_eq', 'reinterpret_cast', 'return', | |||
'sizeof', 'static_cast', 'struct', 'switch', 'template', | |||
'throw', 'try', 'typedef', 'typeid', 'typename', 'union', | |||
'while', 'xor', 'xor_eq' | |||
] | |||
PREDEFINED_TYPES = [ | |||
'bool', 'char', 'double', 'float', 'int', 'long', | |||
'short', 'signed', 'unsigned', 'wchar_t', 'string' | |||
] | |||
PREDEFINED_CONSTANTS = [ | |||
'false', 'true', | |||
'EOF', 'NULL', | |||
] | |||
PREDEFINED_VARIABLES = [ | |||
'this' | |||
] | |||
DIRECTIVES = [ | |||
'auto', 'const', 'explicit', 'extern', 'friend', 'inline', 'mutable', 'operator', | |||
'private', 'protected', 'public', 'register', 'static', 'using', 'virtual', 'void', | |||
'volatile' | |||
] | |||
IDENT_KIND = WordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_TYPES, :pre_type). | |||
add(PREDEFINED_VARIABLES, :local_variable). | |||
add(DIRECTIVES, :directive). | |||
add(PREDEFINED_CONSTANTS, :pre_constant) | |||
ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x | |||
def scan_tokens tokens, options | |||
state = :initial | |||
label_expected = true | |||
case_expected = false | |||
label_expected_before_preproc_line = nil | |||
in_preproc_line = false | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if match = scan(/ \s+ | \\\n /x) | |||
if in_preproc_line && match != "\\\n" && match.index(?\n) | |||
in_preproc_line = false | |||
label_expected = label_expected_before_preproc_line | |||
end | |||
tokens << [match, :space] | |||
next | |||
elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) | |||
kind = :comment | |||
elsif match = scan(/ \# \s* if \s* 0 /x) | |||
match << scan_until(/ ^\# (?:elif|else|endif) .*? $ | \z /xm) unless eos? | |||
kind = :comment | |||
elsif match = scan(/ [-+*=<>?:;,!&^|()\[\]{}~%]+ | \/=? | \.(?!\d) /x) | |||
label_expected = match =~ /[;\{\}]/ | |||
if case_expected | |||
label_expected = true if match == ':' | |||
case_expected = false | |||
end | |||
kind = :operator | |||
elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) | |||
kind = IDENT_KIND[match] | |||
if kind == :ident && label_expected && !in_preproc_line && scan(/:(?!:)/) | |||
kind = :label | |||
match << matched | |||
else | |||
label_expected = false | |||
if kind == :reserved | |||
case match | |||
when 'class' | |||
state = :class_name_expected | |||
when 'case', 'default' | |||
case_expected = true | |||
end | |||
end | |||
end | |||
elsif scan(/\$/) | |||
kind = :ident | |||
elsif match = scan(/L?"/) | |||
tokens << [:open, :string] | |||
if match[0] == ?L | |||
tokens << ['L', :modifier] | |||
match = '"' | |||
end | |||
state = :string | |||
kind = :delimiter | |||
elsif scan(/#[ \t]*(\w*)/) | |||
kind = :preprocessor | |||
in_preproc_line = true | |||
label_expected_before_preproc_line = label_expected | |||
state = :include_expected if self[1] == 'include' | |||
elsif scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox) | |||
label_expected = false | |||
kind = :char | |||
elsif scan(/0[xX][0-9A-Fa-f]+/) | |||
label_expected = false | |||
kind = :hex | |||
elsif scan(/(?:0[0-7]+)(?![89.eEfF])/) | |||
label_expected = false | |||
kind = :oct | |||
elsif scan(/(?:\d+)(?![.eEfF])L?L?/) | |||
label_expected = false | |||
kind = :integer | |||
elsif scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) | |||
label_expected = false | |||
kind = :float | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string | |||
if scan(/[^\\"]+/) | |||
kind = :content | |||
elsif scan(/"/) | |||
tokens << ['"', :delimiter] | |||
tokens << [:close, :string] | |||
state = :initial | |||
label_expected = false | |||
next | |||
elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
kind = :char | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, :string] | |||
kind = :error | |||
state = :initial | |||
label_expected = false | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
when :include_expected | |||
if scan(/<[^>\n]+>?|"[^"\n\\]*(?:\\.[^"\n\\]*)*"?/) | |||
kind = :include | |||
state = :initial | |||
elsif match = scan(/\s+/) | |||
kind = :space | |||
state = :initial if match.index ?\n | |||
else | |||
state = :initial | |||
next | |||
end | |||
when :class_name_expected | |||
if scan(/ [A-Za-z_][A-Za-z_0-9]* /x) | |||
kind = :class | |||
state = :initial | |||
elsif match = scan(/\s+/) | |||
kind = :space | |||
else | |||
getch | |||
kind = :error | |||
state = :initial | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
if state == :string | |||
tokens << [:close, :string] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,209 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class CSS < Scanner | |||
register_for :css | |||
KINDS_NOT_LOC = [ | |||
:comment, | |||
:class, :pseudo_class, :type, | |||
:constant, :directive, | |||
:key, :value, :operator, :color, :float, | |||
:error, :important, | |||
] | |||
module RE | |||
Hex = /[0-9a-fA-F]/ | |||
Unicode = /\\#{Hex}{1,6}(?:\r\n|\s)?/ # differs from standard because it allows uppercase hex too | |||
Escape = /#{Unicode}|\\[^\r\n\f0-9a-fA-F]/ | |||
NMChar = /[-_a-zA-Z0-9]|#{Escape}/ | |||
NMStart = /[_a-zA-Z]|#{Escape}/ | |||
NL = /\r\n|\r|\n|\f/ | |||
String1 = /"(?:[^\n\r\f\\"]|\\#{NL}|#{Escape})*"?/ # FIXME: buggy regexp | |||
String2 = /'(?:[^\n\r\f\\']|\\#{NL}|#{Escape})*'?/ # FIXME: buggy regexp | |||
String = /#{String1}|#{String2}/ | |||
HexColor = /#(?:#{Hex}{6}|#{Hex}{3})/ | |||
Color = /#{HexColor}/ | |||
Num = /-?(?:[0-9]+|[0-9]*\.[0-9]+)/ | |||
Name = /#{NMChar}+/ | |||
Ident = /-?#{NMStart}#{NMChar}*/ | |||
AtKeyword = /@#{Ident}/ | |||
Percentage = /#{Num}%/ | |||
reldimensions = %w[em ex px] | |||
absdimensions = %w[in cm mm pt pc] | |||
Unit = Regexp.union(*(reldimensions + absdimensions)) | |||
Dimension = /#{Num}#{Unit}/ | |||
Comment = %r! /\* (?: .*? \*/ | .* ) !mx | |||
Function = /(?:url|alpha)\((?:[^)\n\r\f]|\\\))*\)?/ | |||
Id = /##{Name}/ | |||
Class = /\.#{Name}/ | |||
PseudoClass = /:#{Name}/ | |||
AttributeSelector = /\[[^\]]*\]?/ | |||
end | |||
def scan_tokens tokens, options | |||
value_expected = nil | |||
states = [:initial] | |||
until eos? | |||
kind = nil | |||
match = nil | |||
if scan(/\s+/) | |||
kind = :space | |||
elsif case states.last | |||
when :initial, :media | |||
if scan(/(?>#{RE::Ident})(?!\()|\*/ox) | |||
kind = :type | |||
elsif scan RE::Class | |||
kind = :class | |||
elsif scan RE::Id | |||
kind = :constant | |||
elsif scan RE::PseudoClass | |||
kind = :pseudo_class | |||
elsif match = scan(RE::AttributeSelector) | |||
# TODO: Improve highlighting inside of attribute selectors. | |||
tokens << [:open, :string] | |||
tokens << [match[0,1], :delimiter] | |||
tokens << [match[1..-2], :content] if match.size > 2 | |||
tokens << [match[-1,1], :delimiter] if match[-1] == ?] | |||
tokens << [:close, :string] | |||
next | |||
elsif match = scan(/@media/) | |||
kind = :directive | |||
states.push :media_before_name | |||
end | |||
when :block | |||
if scan(/(?>#{RE::Ident})(?!\()/ox) | |||
if value_expected | |||
kind = :value | |||
else | |||
kind = :key | |||
end | |||
end | |||
when :media_before_name | |||
if scan RE::Ident | |||
kind = :type | |||
states[-1] = :media_after_name | |||
end | |||
when :media_after_name | |||
if scan(/\{/) | |||
kind = :operator | |||
states[-1] = :media | |||
end | |||
when :comment | |||
if scan(/(?:[^*\s]|\*(?!\/))+/) | |||
kind = :comment | |||
elsif scan(/\*\//) | |||
kind = :comment | |||
states.pop | |||
elsif scan(/\s+/) | |||
kind = :space | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
elsif scan(/\/\*/) | |||
kind = :comment | |||
states.push :comment | |||
elsif scan(/\{/) | |||
value_expected = false | |||
kind = :operator | |||
states.push :block | |||
elsif scan(/\}/) | |||
value_expected = false | |||
if states.last == :block || states.last == :media | |||
kind = :operator | |||
states.pop | |||
else | |||
kind = :error | |||
end | |||
elsif match = scan(/#{RE::String}/o) | |||
tokens << [:open, :string] | |||
tokens << [match[0, 1], :delimiter] | |||
tokens << [match[1..-2], :content] if match.size > 2 | |||
tokens << [match[-1, 1], :delimiter] if match.size >= 2 | |||
tokens << [:close, :string] | |||
next | |||
elsif match = scan(/#{RE::Function}/o) | |||
tokens << [:open, :string] | |||
start = match[/^\w+\(/] | |||
tokens << [start, :delimiter] | |||
if match[-1] == ?) | |||
tokens << [match[start.size..-2], :content] | |||
tokens << [')', :delimiter] | |||
else | |||
tokens << [match[start.size..-1], :content] | |||
end | |||
tokens << [:close, :string] | |||
next | |||
elsif scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox) | |||
kind = :float | |||
elsif scan(/#{RE::Color}/o) | |||
kind = :color | |||
elsif scan(/! *important/) | |||
kind = :important | |||
elsif scan(/rgb\([^()\n]*\)?/) | |||
kind = :color | |||
elsif scan(/#{RE::AtKeyword}/o) | |||
kind = :directive | |||
elsif match = scan(/ [+>:;,.=()\/] /x) | |||
if match == ':' | |||
value_expected = true | |||
elsif match == ';' | |||
value_expected = false | |||
end | |||
kind = :operator | |||
else | |||
getch | |||
kind = :error | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,62 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
# = Debug Scanner | |||
class Debug < Scanner | |||
include Streamable | |||
register_for :debug | |||
file_extension 'raydebug' | |||
title 'CodeRay Token Dump' | |||
protected | |||
def scan_tokens tokens, options | |||
opened_tokens = [] | |||
until eos? | |||
kind = nil | |||
match = nil | |||
if scan(/\s+/) | |||
tokens << [matched, :space] | |||
next | |||
elsif scan(/ (\w+) \( ( [^\)\\]* ( \\. [^\)\\]* )* ) \) /x) | |||
kind = self[1].to_sym | |||
match = self[2].gsub(/\\(.)/, '\1') | |||
elsif scan(/ (\w+) < /x) | |||
kind = self[1].to_sym | |||
opened_tokens << kind | |||
match = :open | |||
elsif !opened_tokens.empty? && scan(/ > /x) | |||
kind = opened_tokens.pop || :error | |||
match = :close | |||
else | |||
kind = :error | |||
getch | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,150 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class Delphi < Scanner | |||
register_for :delphi | |||
file_extension 'pas' | |||
RESERVED_WORDS = [ | |||
'and', 'array', 'as', 'at', 'asm', 'at', 'begin', 'case', 'class', | |||
'const', 'constructor', 'destructor', 'dispinterface', 'div', 'do', | |||
'downto', 'else', 'end', 'except', 'exports', 'file', 'finalization', | |||
'finally', 'for', 'function', 'goto', 'if', 'implementation', 'in', | |||
'inherited', 'initialization', 'inline', 'interface', 'is', 'label', | |||
'library', 'mod', 'nil', 'not', 'object', 'of', 'or', 'out', 'packed', | |||
'procedure', 'program', 'property', 'raise', 'record', 'repeat', | |||
'resourcestring', 'set', 'shl', 'shr', 'string', 'then', 'threadvar', | |||
'to', 'try', 'type', 'unit', 'until', 'uses', 'var', 'while', 'with', | |||
'xor', 'on' | |||
] | |||
DIRECTIVES = [ | |||
'absolute', 'abstract', 'assembler', 'at', 'automated', 'cdecl', | |||
'contains', 'deprecated', 'dispid', 'dynamic', 'export', | |||
'external', 'far', 'forward', 'implements', 'local', | |||
'near', 'nodefault', 'on', 'overload', 'override', | |||
'package', 'pascal', 'platform', 'private', 'protected', 'public', | |||
'published', 'read', 'readonly', 'register', 'reintroduce', | |||
'requires', 'resident', 'safecall', 'stdcall', 'stored', 'varargs', | |||
'virtual', 'write', 'writeonly' | |||
] | |||
IDENT_KIND = CaseIgnoringWordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(DIRECTIVES, :directive) | |||
NAME_FOLLOWS = CaseIgnoringWordList.new(false). | |||
add(%w(procedure function .)) | |||
private | |||
def scan_tokens tokens, options | |||
state = :initial | |||
last_token = '' | |||
until eos? | |||
kind = nil | |||
match = nil | |||
if state == :initial | |||
if scan(/ \s+ /x) | |||
tokens << [matched, :space] | |||
next | |||
elsif scan(%r! \{ \$ [^}]* \}? | \(\* \$ (?: .*? \*\) | .* ) !mx) | |||
tokens << [matched, :preprocessor] | |||
next | |||
elsif scan(%r! // [^\n]* | \{ [^}]* \}? | \(\* (?: .*? \*\) | .* ) !mx) | |||
tokens << [matched, :comment] | |||
next | |||
elsif match = scan(/ <[>=]? | >=? | :=? | [-+=*\/;,@\^|\(\)\[\]] | \.\. /x) | |||
kind = :operator | |||
elsif match = scan(/\./) | |||
kind = :operator | |||
if last_token == 'end' | |||
tokens << [match, kind] | |||
next | |||
end | |||
elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) | |||
kind = NAME_FOLLOWS[last_token] ? :ident : IDENT_KIND[match] | |||
elsif match = scan(/ ' ( [^\n']|'' ) (?:'|$) /x) | |||
tokens << [:open, :char] | |||
tokens << ["'", :delimiter] | |||
tokens << [self[1], :content] | |||
tokens << ["'", :delimiter] | |||
tokens << [:close, :char] | |||
next | |||
elsif match = scan(/ ' /x) | |||
tokens << [:open, :string] | |||
state = :string | |||
kind = :delimiter | |||
elsif scan(/ \# (?: \d+ | \$[0-9A-Fa-f]+ ) /x) | |||
kind = :char | |||
elsif scan(/ \$ [0-9A-Fa-f]+ /x) | |||
kind = :hex | |||
elsif scan(/ (?: \d+ ) (?![eE]|\.[^.]) /x) | |||
kind = :integer | |||
elsif scan(/ \d+ (?: \.\d+ (?: [eE][+-]? \d+ )? | [eE][+-]? \d+ ) /x) | |||
kind = :float | |||
else | |||
kind = :error | |||
getch | |||
end | |||
elsif state == :string | |||
if scan(/[^\n']+/) | |||
kind = :content | |||
elsif scan(/''/) | |||
kind = :char | |||
elsif scan(/'/) | |||
tokens << ["'", :delimiter] | |||
tokens << [:close, :string] | |||
state = :initial | |||
next | |||
elsif scan(/\n/) | |||
tokens << [:close, :string] | |||
kind = :error | |||
state = :initial | |||
else | |||
raise "else case \' reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise 'else-case reached', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens, state | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
last_token = match | |||
tokens << [match, kind] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,110 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class Diff < Scanner | |||
register_for :diff | |||
title 'diff output' | |||
def scan_tokens tokens, options | |||
line_kind = nil | |||
state = :initial | |||
until eos? | |||
kind = match = nil | |||
if match = scan(/\n/) | |||
if line_kind | |||
tokens << [:end_line, line_kind] | |||
line_kind = nil | |||
end | |||
tokens << [match, :space] | |||
next | |||
end | |||
case state | |||
when :initial | |||
if match = scan(/--- |\+\+\+ |=+|_+/) | |||
tokens << [:begin_line, line_kind = :head] | |||
tokens << [match, :head] | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
elsif match = scan(/Index: |Property changes on: /) | |||
tokens << [:begin_line, line_kind = :head] | |||
tokens << [match, :head] | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
elsif match = scan(/Added: /) | |||
tokens << [:begin_line, line_kind = :head] | |||
tokens << [match, :head] | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
state = :added | |||
elsif match = scan(/\\ /) | |||
tokens << [:begin_line, line_kind = :change] | |||
tokens << [match, :change] | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
elsif match = scan(/@@(?>[^@\n]*)@@/) | |||
if check(/\n|$/) | |||
tokens << [:begin_line, line_kind = :change] | |||
else | |||
tokens << [:open, :change] | |||
end | |||
tokens << [match[0,2], :change] | |||
tokens << [match[2...-2], :plain] | |||
tokens << [match[-2,2], :change] | |||
tokens << [:close, :change] unless line_kind | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
elsif match = scan(/\+/) | |||
tokens << [:begin_line, line_kind = :insert] | |||
tokens << [match, :insert] | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
elsif match = scan(/-/) | |||
tokens << [:begin_line, line_kind = :delete] | |||
tokens << [match, :delete] | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
elsif scan(/ .*/) | |||
kind = :comment | |||
elsif scan(/.+/) | |||
tokens << [:begin_line, line_kind = :comment] | |||
kind = :plain | |||
else | |||
raise_inspect 'else case rached' | |||
end | |||
when :added | |||
if match = scan(/ \+/) | |||
tokens << [:begin_line, line_kind = :insert] | |||
tokens << [match, :insert] | |||
next unless match = scan(/.+/) | |||
kind = :plain | |||
else | |||
state = :initial | |||
next | |||
end | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
tokens << [:end_line, line_kind] if line_kind | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,264 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
load :java | |||
class Groovy < Java | |||
include Streamable | |||
register_for :groovy | |||
# TODO: Check this! | |||
GROOVY_KEYWORDS = %w[ | |||
as assert def in | |||
] | |||
KEYWORDS_EXPECTING_VALUE = WordList.new.add %w[ | |||
case instanceof new return throw typeof while as assert in | |||
] | |||
GROOVY_MAGIC_VARIABLES = %w[ it ] | |||
IDENT_KIND = Java::IDENT_KIND.dup. | |||
add(GROOVY_KEYWORDS, :keyword). | |||
add(GROOVY_MAGIC_VARIABLES, :local_variable) | |||
ESCAPE = / [bfnrtv$\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} /x # no 4-byte unicode chars? U[a-fA-F0-9]{8} | |||
REGEXP_ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} | \d | [bBdDsSwW\/] /x | |||
# TODO: interpretation inside ', ", / | |||
STRING_CONTENT_PATTERN = { | |||
"'" => /(?>\\[^\\'\n]+|[^\\'\n]+)+/, | |||
'"' => /[^\\$"\n]+/, | |||
"'''" => /(?>[^\\']+|'(?!''))+/, | |||
'"""' => /(?>[^\\$"]+|"(?!""))+/, | |||
'/' => /[^\\$\/\n]+/, | |||
} | |||
def scan_tokens tokens, options | |||
state = :initial | |||
inline_block_stack = [] | |||
inline_block_paren_depth = nil | |||
string_delimiter = nil | |||
import_clause = class_name_follows = last_token = after_def = false | |||
value_expected = true | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if match = scan(/ \s+ | \\\n /x) | |||
tokens << [match, :space] | |||
if match.index ?\n | |||
import_clause = after_def = false | |||
value_expected = true unless value_expected | |||
end | |||
next | |||
elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) | |||
value_expected = true | |||
after_def = false | |||
kind = :comment | |||
elsif bol? && scan(/ \#!.* /x) | |||
kind = :doctype | |||
elsif import_clause && scan(/ (?!as) #{IDENT} (?: \. #{IDENT} )* (?: \.\* )? /ox) | |||
after_def = value_expected = false | |||
kind = :include | |||
elsif match = scan(/ #{IDENT} | \[\] /ox) | |||
kind = IDENT_KIND[match] | |||
value_expected = (kind == :keyword) && KEYWORDS_EXPECTING_VALUE[match] | |||
if last_token == '.' | |||
kind = :ident | |||
elsif class_name_follows | |||
kind = :class | |||
class_name_follows = false | |||
elsif after_def && check(/\s*[({]/) | |||
kind = :method | |||
after_def = false | |||
elsif kind == :ident && last_token != '?' && check(/:/) | |||
kind = :key | |||
else | |||
class_name_follows = true if match == 'class' || (import_clause && match == 'as') | |||
import_clause = match == 'import' | |||
after_def = true if match == 'def' | |||
end | |||
elsif scan(/;/) | |||
import_clause = after_def = false | |||
value_expected = true | |||
kind = :operator | |||
elsif scan(/\{/) | |||
class_name_follows = after_def = false | |||
value_expected = true | |||
kind = :operator | |||
if !inline_block_stack.empty? | |||
inline_block_paren_depth += 1 | |||
end | |||
# TODO: ~'...', ~"..." and ~/.../ style regexps | |||
elsif match = scan(/ \.\.<? | \*?\.(?!\d)@? | \.& | \?:? | [,?:(\[] | -[->] | \+\+ | | |||
&& | \|\| | \*\*=? | ==?~ | <=?>? | [-+*%^~&|>=!]=? | <<<?=? | >>>?=? /x) | |||
value_expected = true | |||
value_expected = :regexp if match == '~' | |||
after_def = false | |||
kind = :operator | |||
elsif match = scan(/ [)\]}] /x) | |||
value_expected = after_def = false | |||
if !inline_block_stack.empty? && match == '}' | |||
inline_block_paren_depth -= 1 | |||
if inline_block_paren_depth == 0 # closing brace of inline block reached | |||
tokens << [match, :inline_delimiter] | |||
tokens << [:close, :inline] | |||
state, string_delimiter, inline_block_paren_depth = inline_block_stack.pop | |||
next | |||
end | |||
end | |||
kind = :operator | |||
elsif check(/[\d.]/) | |||
after_def = value_expected = false | |||
if scan(/0[xX][0-9A-Fa-f]+/) | |||
kind = :hex | |||
elsif scan(/(?>0[0-7]+)(?![89.eEfF])/) | |||
kind = :oct | |||
elsif scan(/\d+[fFdD]|\d*\.\d+(?:[eE][+-]?\d+)?[fFdD]?|\d+[eE][+-]?\d+[fFdD]?/) | |||
kind = :float | |||
elsif scan(/\d+[lLgG]?/) | |||
kind = :integer | |||
end | |||
elsif match = scan(/'''|"""/) | |||
after_def = value_expected = false | |||
state = :multiline_string | |||
tokens << [:open, :string] | |||
string_delimiter = match | |||
kind = :delimiter | |||
# TODO: record.'name' | |||
elsif match = scan(/["']/) | |||
after_def = value_expected = false | |||
state = match == '/' ? :regexp : :string | |||
tokens << [:open, state] | |||
string_delimiter = match | |||
kind = :delimiter | |||
elsif value_expected && (match = scan(/\//)) | |||
after_def = value_expected = false | |||
tokens << [:open, :regexp] | |||
state = :regexp | |||
string_delimiter = '/' | |||
kind = :delimiter | |||
elsif scan(/ @ #{IDENT} /ox) | |||
after_def = value_expected = false | |||
kind = :annotation | |||
elsif scan(/\//) | |||
after_def = false | |||
value_expected = true | |||
kind = :operator | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string, :regexp, :multiline_string | |||
if scan(STRING_CONTENT_PATTERN[string_delimiter]) | |||
kind = :content | |||
elsif match = scan(state == :multiline_string ? /'''|"""/ : /["'\/]/) | |||
tokens << [match, :delimiter] | |||
if state == :regexp | |||
# TODO: regexp modifiers? s, m, x, i? | |||
modifiers = scan(/[ix]+/) | |||
tokens << [modifiers, :modifier] if modifiers && !modifiers.empty? | |||
end | |||
state = :string if state == :multiline_string | |||
tokens << [:close, state] | |||
string_delimiter = nil | |||
after_def = value_expected = false | |||
state = :initial | |||
next | |||
elsif (state == :string || state == :multiline_string) && | |||
(match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) | |||
if string_delimiter[0] == ?' && !(match == "\\\\" || match == "\\'") | |||
kind = :content | |||
else | |||
kind = :char | |||
end | |||
elsif state == :regexp && scan(/ \\ (?: #{REGEXP_ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
kind = :char | |||
elsif match = scan(/ \$ #{IDENT} /mox) | |||
tokens << [:open, :inline] | |||
tokens << ['$', :inline_delimiter] | |||
match = match[1..-1] | |||
tokens << [match, IDENT_KIND[match]] | |||
tokens << [:close, :inline] | |||
next | |||
elsif match = scan(/ \$ \{ /x) | |||
tokens << [:open, :inline] | |||
tokens << ['${', :inline_delimiter] | |||
inline_block_stack << [state, string_delimiter, inline_block_paren_depth] | |||
inline_block_paren_depth = 1 | |||
state = :initial | |||
next | |||
elsif scan(/ \$ /mx) | |||
kind = :content | |||
elsif scan(/ \\. /mx) | |||
kind = :content | |||
elsif scan(/ \\ | \n /x) | |||
tokens << [:close, state] | |||
kind = :error | |||
after_def = value_expected = false | |||
state = :initial | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
last_token = match unless [:space, :comment, :doctype].include? kind | |||
tokens << [match, kind] | |||
end | |||
if [:multiline_string, :string, :regexp].include? state | |||
tokens << [:close, state] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,182 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
# HTML Scanner | |||
class HTML < Scanner | |||
include Streamable | |||
register_for :html | |||
KINDS_NOT_LOC = [ | |||
:comment, :doctype, :preprocessor, | |||
:tag, :attribute_name, :operator, | |||
:attribute_value, :delimiter, :content, | |||
:plain, :entity, :error | |||
] | |||
ATTR_NAME = /[\w.:-]+/ | |||
ATTR_VALUE_UNQUOTED = ATTR_NAME | |||
TAG_END = /\/?>/ | |||
HEX = /[0-9a-fA-F]/ | |||
ENTITY = / | |||
& | |||
(?: | |||
\w+ | |||
| | |||
\# | |||
(?: | |||
\d+ | |||
| | |||
x#{HEX}+ | |||
) | |||
) | |||
; | |||
/ox | |||
PLAIN_STRING_CONTENT = { | |||
"'" => /[^&'>\n]+/, | |||
'"' => /[^&">\n]+/, | |||
} | |||
def reset | |||
super | |||
@state = :initial | |||
end | |||
private | |||
def setup | |||
@state = :initial | |||
@plain_string_content = nil | |||
end | |||
def scan_tokens tokens, options | |||
state = @state | |||
plain_string_content = @plain_string_content | |||
until eos? | |||
kind = nil | |||
match = nil | |||
if scan(/\s+/m) | |||
kind = :space | |||
else | |||
case state | |||
when :initial | |||
if scan(/<!--.*?-->/m) | |||
kind = :comment | |||
elsif scan(/<!DOCTYPE.*?>/m) | |||
kind = :doctype | |||
elsif scan(/<\?xml.*?\?>/m) | |||
kind = :preprocessor | |||
elsif scan(/<\?.*?\?>|<%.*?%>/m) | |||
kind = :comment | |||
elsif scan(/<\/[-\w.:]*>/m) | |||
kind = :tag | |||
elsif match = scan(/<[-\w.:]+>?/m) | |||
kind = :tag | |||
state = :attribute unless match[-1] == ?> | |||
elsif scan(/[^<>&]+/) | |||
kind = :plain | |||
elsif scan(/#{ENTITY}/ox) | |||
kind = :entity | |||
elsif scan(/[<>&]/) | |||
kind = :error | |||
else | |||
raise_inspect '[BUG] else-case reached with state %p' % [state], tokens | |||
end | |||
when :attribute | |||
if scan(/#{TAG_END}/o) | |||
kind = :tag | |||
state = :initial | |||
elsif scan(/#{ATTR_NAME}/o) | |||
kind = :attribute_name | |||
state = :attribute_equal | |||
else | |||
kind = :error | |||
getch | |||
end | |||
when :attribute_equal | |||
if scan(/=/) | |||
kind = :operator | |||
state = :attribute_value | |||
elsif scan(/#{ATTR_NAME}/o) | |||
kind = :attribute_name | |||
elsif scan(/#{TAG_END}/o) | |||
kind = :tag | |||
state = :initial | |||
elsif scan(/./) | |||
kind = :error | |||
state = :attribute | |||
end | |||
when :attribute_value | |||
if scan(/#{ATTR_VALUE_UNQUOTED}/o) | |||
kind = :attribute_value | |||
state = :attribute | |||
elsif match = scan(/["']/) | |||
tokens << [:open, :string] | |||
state = :attribute_value_string | |||
plain_string_content = PLAIN_STRING_CONTENT[match] | |||
kind = :delimiter | |||
elsif scan(/#{TAG_END}/o) | |||
kind = :tag | |||
state = :initial | |||
else | |||
kind = :error | |||
getch | |||
end | |||
when :attribute_value_string | |||
if scan(plain_string_content) | |||
kind = :content | |||
elsif scan(/['"]/) | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :string] | |||
state = :attribute | |||
next | |||
elsif scan(/#{ENTITY}/ox) | |||
kind = :entity | |||
elsif scan(/&/) | |||
kind = :content | |||
elsif scan(/[\n>]/) | |||
tokens << [:close, :string] | |||
kind = :error | |||
state = :initial | |||
end | |||
else | |||
raise_inspect 'Unknown state: %p' % [state], tokens | |||
end | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens, state | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
if options[:keep_state] | |||
@state = state | |||
@plain_string_content = plain_string_content | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,176 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class Java < Scanner | |||
include Streamable | |||
register_for :java | |||
helper :builtin_types | |||
# http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html | |||
KEYWORDS = %w[ | |||
assert break case catch continue default do else | |||
finally for if instanceof import new package | |||
return switch throw try typeof while | |||
debugger export | |||
] | |||
RESERVED = %w[ const goto ] | |||
CONSTANTS = %w[ false null true ] | |||
MAGIC_VARIABLES = %w[ this super ] | |||
TYPES = %w[ | |||
boolean byte char class double enum float int interface long | |||
short void | |||
] << '[]' # because int[] should be highlighted as a type | |||
DIRECTIVES = %w[ | |||
abstract extends final implements native private protected public | |||
static strictfp synchronized throws transient volatile | |||
] | |||
IDENT_KIND = WordList.new(:ident). | |||
add(KEYWORDS, :keyword). | |||
add(RESERVED, :reserved). | |||
add(CONSTANTS, :pre_constant). | |||
add(MAGIC_VARIABLES, :local_variable). | |||
add(TYPES, :type). | |||
add(BuiltinTypes::List, :pre_type). | |||
add(BuiltinTypes::List.select { |builtin| builtin[/(Error|Exception)$/] }, :exception). | |||
add(DIRECTIVES, :directive) | |||
ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x | |||
STRING_CONTENT_PATTERN = { | |||
"'" => /[^\\']+/, | |||
'"' => /[^\\"]+/, | |||
'/' => /[^\\\/]+/, | |||
} | |||
IDENT = /[a-zA-Z_][A-Za-z_0-9]*/ | |||
def scan_tokens tokens, options | |||
state = :initial | |||
string_delimiter = nil | |||
import_clause = class_name_follows = last_token_dot = false | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if match = scan(/ \s+ | \\\n /x) | |||
tokens << [match, :space] | |||
next | |||
elsif match = scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) | |||
tokens << [match, :comment] | |||
next | |||
elsif import_clause && scan(/ #{IDENT} (?: \. #{IDENT} )* /ox) | |||
kind = :include | |||
elsif match = scan(/ #{IDENT} | \[\] /ox) | |||
kind = IDENT_KIND[match] | |||
if last_token_dot | |||
kind = :ident | |||
elsif class_name_follows | |||
kind = :class | |||
class_name_follows = false | |||
else | |||
import_clause = true if match == 'import' | |||
class_name_follows = true if match == 'class' || match == 'interface' | |||
end | |||
elsif scan(/ \.(?!\d) | [,?:()\[\]}] | -- | \+\+ | && | \|\| | \*\*=? | [-+*\/%^~&|<>=!]=? | <<<?=? | >>>?=? /x) | |||
kind = :operator | |||
elsif scan(/;/) | |||
import_clause = false | |||
kind = :operator | |||
elsif scan(/\{/) | |||
class_name_follows = false | |||
kind = :operator | |||
elsif check(/[\d.]/) | |||
if scan(/0[xX][0-9A-Fa-f]+/) | |||
kind = :hex | |||
elsif scan(/(?>0[0-7]+)(?![89.eEfF])/) | |||
kind = :oct | |||
elsif scan(/\d+[fFdD]|\d*\.\d+(?:[eE][+-]?\d+)?[fFdD]?|\d+[eE][+-]?\d+[fFdD]?/) | |||
kind = :float | |||
elsif scan(/\d+[lL]?/) | |||
kind = :integer | |||
end | |||
elsif match = scan(/["']/) | |||
tokens << [:open, :string] | |||
state = :string | |||
string_delimiter = match | |||
kind = :delimiter | |||
elsif scan(/ @ #{IDENT} /ox) | |||
kind = :annotation | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string | |||
if scan(STRING_CONTENT_PATTERN[string_delimiter]) | |||
kind = :content | |||
elsif match = scan(/["'\/]/) | |||
tokens << [match, :delimiter] | |||
tokens << [:close, state] | |||
string_delimiter = nil | |||
state = :initial | |||
next | |||
elsif state == :string && (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) | |||
if string_delimiter == "'" && !(match == "\\\\" || match == "\\'") | |||
kind = :content | |||
else | |||
kind = :char | |||
end | |||
elsif scan(/\\./m) | |||
kind = :content | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, state] | |||
kind = :error | |||
state = :initial | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
last_token_dot = match == '.' | |||
tokens << [match, kind] | |||
end | |||
if state == :string | |||
tokens << [:close, state] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,224 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class JavaScript < Scanner | |||
include Streamable | |||
register_for :java_script | |||
file_extension 'js' | |||
# The actual JavaScript keywords. | |||
KEYWORDS = %w[ | |||
break case catch continue default delete do else | |||
finally for function if in instanceof new | |||
return switch throw try typeof var void while with | |||
] | |||
PREDEFINED_CONSTANTS = %w[ | |||
false null true undefined | |||
] | |||
MAGIC_VARIABLES = %w[ this arguments ] # arguments was introduced in JavaScript 1.4 | |||
KEYWORDS_EXPECTING_VALUE = WordList.new.add %w[ | |||
case delete in instanceof new return throw typeof with | |||
] | |||
# Reserved for future use. | |||
RESERVED_WORDS = %w[ | |||
abstract boolean byte char class debugger double enum export extends | |||
final float goto implements import int interface long native package | |||
private protected public short static super synchronized throws transient | |||
volatile | |||
] | |||
IDENT_KIND = WordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_CONSTANTS, :pre_constant). | |||
add(MAGIC_VARIABLES, :local_variable). | |||
add(KEYWORDS, :keyword) | |||
ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x | |||
REGEXP_ESCAPE = / [bBdDsSwW] /x | |||
STRING_CONTENT_PATTERN = { | |||
"'" => /[^\\']+/, | |||
'"' => /[^\\"]+/, | |||
'/' => /[^\\\/]+/, | |||
} | |||
KEY_CHECK_PATTERN = { | |||
"'" => / [^\\']* (?: \\.? [^\\']* )* '? \s* : /x, | |||
'"' => / [^\\"]* (?: \\.? [^\\"]* )* "? \s* : /x, | |||
} | |||
def scan_tokens tokens, options | |||
state = :initial | |||
string_delimiter = nil | |||
value_expected = true | |||
key_expected = false | |||
function_expected = false | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if match = scan(/ \s+ | \\\n /x) | |||
value_expected = true if !value_expected && match.index(?\n) | |||
tokens << [match, :space] | |||
next | |||
elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) | |||
value_expected = true | |||
kind = :comment | |||
elsif check(/\.?\d/) | |||
key_expected = value_expected = false | |||
if scan(/0[xX][0-9A-Fa-f]+/) | |||
kind = :hex | |||
elsif scan(/(?>0[0-7]+)(?![89.eEfF])/) | |||
kind = :oct | |||
elsif scan(/\d+[fF]|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) | |||
kind = :float | |||
elsif scan(/\d+/) | |||
kind = :integer | |||
end | |||
elsif value_expected && match = scan(/<([[:alpha:]]\w*) (?: [^\/>]*\/> | .*?<\/\1>)/xim) | |||
# FIXME: scan over nested tags | |||
xml_scanner.tokenize match | |||
value_expected = false | |||
next | |||
elsif match = scan(/ [-+*=<>?:;,!&^|(\[{~%]+ | \.(?!\d) /x) | |||
value_expected = true | |||
last_operator = match[-1] | |||
key_expected = (last_operator == ?{) || (last_operator == ?,) | |||
function_expected = false | |||
kind = :operator | |||
elsif scan(/ [)\]}]+ /x) | |||
function_expected = key_expected = value_expected = false | |||
kind = :operator | |||
elsif match = scan(/ [$a-zA-Z_][A-Za-z_0-9$]* /x) | |||
kind = IDENT_KIND[match] | |||
value_expected = (kind == :keyword) && KEYWORDS_EXPECTING_VALUE[match] | |||
# TODO: labels | |||
if kind == :ident | |||
if match.index(?$) # $ allowed inside an identifier | |||
kind = :predefined | |||
elsif function_expected | |||
kind = :function | |||
elsif check(/\s*[=:]\s*function\b/) | |||
kind = :function | |||
elsif key_expected && check(/\s*:/) | |||
kind = :key | |||
end | |||
end | |||
function_expected = (kind == :keyword) && (match == 'function') | |||
key_expected = false | |||
elsif match = scan(/["']/) | |||
if key_expected && check(KEY_CHECK_PATTERN[match]) | |||
state = :key | |||
else | |||
state = :string | |||
end | |||
tokens << [:open, state] | |||
string_delimiter = match | |||
kind = :delimiter | |||
elsif value_expected && (match = scan(/\/(?=\S)/)) | |||
tokens << [:open, :regexp] | |||
state = :regexp | |||
string_delimiter = '/' | |||
kind = :delimiter | |||
elsif scan(/ \/ /x) | |||
value_expected = true | |||
key_expected = false | |||
kind = :operator | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string, :regexp, :key | |||
if scan(STRING_CONTENT_PATTERN[string_delimiter]) | |||
kind = :content | |||
elsif match = scan(/["'\/]/) | |||
tokens << [match, :delimiter] | |||
if state == :regexp | |||
modifiers = scan(/[gim]+/) | |||
tokens << [modifiers, :modifier] if modifiers && !modifiers.empty? | |||
end | |||
tokens << [:close, state] | |||
string_delimiter = nil | |||
key_expected = value_expected = false | |||
state = :initial | |||
next | |||
elsif state != :regexp && (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) | |||
if string_delimiter == "'" && !(match == "\\\\" || match == "\\'") | |||
kind = :content | |||
else | |||
kind = :char | |||
end | |||
elsif state == :regexp && scan(/ \\ (?: #{ESCAPE} | #{REGEXP_ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
kind = :char | |||
elsif scan(/\\./m) | |||
kind = :content | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, state] | |||
kind = :error | |||
key_expected = value_expected = false | |||
state = :initial | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
if [:string, :regexp].include? state | |||
tokens << [:close, state] | |||
end | |||
tokens | |||
end | |||
protected | |||
def reset_instance | |||
super | |||
@xml_scanner.reset if defined? @xml_scanner | |||
end | |||
def xml_scanner | |||
@xml_scanner ||= CodeRay.scanner :xml, :tokens => @tokens, :keep_tokens => true, :keep_state => false | |||
end | |||
end | |||
end | |||
end |
@@ -1,224 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class JavaScript < Scanner | |||
include Streamable | |||
register_for :java_script | |||
file_extension 'js' | |||
# The actual JavaScript keywords. | |||
KEYWORDS = %w[ | |||
break case catch continue default delete do else | |||
finally for function if in instanceof new | |||
return switch throw try typeof var void while with | |||
] | |||
PREDEFINED_CONSTANTS = %w[ | |||
false null true undefined | |||
] | |||
MAGIC_VARIABLES = %w[ this arguments ] # arguments was introduced in JavaScript 1.4 | |||
KEYWORDS_EXPECTING_VALUE = WordList.new.add %w[ | |||
case delete in instanceof new return throw typeof with | |||
] | |||
# Reserved for future use. | |||
RESERVED_WORDS = %w[ | |||
abstract boolean byte char class debugger double enum export extends | |||
final float goto implements import int interface long native package | |||
private protected public short static super synchronized throws transient | |||
volatile | |||
] | |||
IDENT_KIND = WordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_CONSTANTS, :pre_constant). | |||
add(MAGIC_VARIABLES, :local_variable). | |||
add(KEYWORDS, :keyword) | |||
ESCAPE = / [bfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x | |||
REGEXP_ESCAPE = / [bBdDsSwW] /x | |||
STRING_CONTENT_PATTERN = { | |||
"'" => /[^\\']+/, | |||
'"' => /[^\\"]+/, | |||
'/' => /[^\\\/]+/, | |||
} | |||
KEY_CHECK_PATTERN = { | |||
"'" => / (?> [^\\']* (?: \\. [^\\']* )* ) ' \s* : /mx, | |||
'"' => / (?> [^\\"]* (?: \\. [^\\"]* )* ) " \s* : /mx, | |||
} | |||
def scan_tokens tokens, options | |||
state = :initial | |||
string_delimiter = nil | |||
value_expected = true | |||
key_expected = false | |||
function_expected = false | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if match = scan(/ \s+ | \\\n /x) | |||
value_expected = true if !value_expected && match.index(?\n) | |||
tokens << [match, :space] | |||
next | |||
elsif scan(%r! // [^\n\\]* (?: \\. [^\n\\]* )* | /\* (?: .*? \*/ | .* ) !mx) | |||
value_expected = true | |||
kind = :comment | |||
elsif check(/\.?\d/) | |||
key_expected = value_expected = false | |||
if scan(/0[xX][0-9A-Fa-f]+/) | |||
kind = :hex | |||
elsif scan(/(?>0[0-7]+)(?![89.eEfF])/) | |||
kind = :oct | |||
elsif scan(/\d+[fF]|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) | |||
kind = :float | |||
elsif scan(/\d+/) | |||
kind = :integer | |||
end | |||
elsif value_expected && match = scan(/<([[:alpha:]]\w*) (?: [^\/>]*\/> | .*?<\/\1>)/xim) | |||
# FIXME: scan over nested tags | |||
xml_scanner.tokenize match | |||
value_expected = false | |||
next | |||
elsif match = scan(/ [-+*=<>?:;,!&^|(\[{~%]+ | \.(?!\d) /x) | |||
value_expected = true | |||
last_operator = match[-1] | |||
key_expected = (last_operator == ?{) || (last_operator == ?,) | |||
function_expected = false | |||
kind = :operator | |||
elsif scan(/ [)\]}]+ /x) | |||
function_expected = key_expected = value_expected = false | |||
kind = :operator | |||
elsif match = scan(/ [$a-zA-Z_][A-Za-z_0-9$]* /x) | |||
kind = IDENT_KIND[match] | |||
value_expected = (kind == :keyword) && KEYWORDS_EXPECTING_VALUE[match] | |||
# TODO: labels | |||
if kind == :ident | |||
if match.index(?$) # $ allowed inside an identifier | |||
kind = :predefined | |||
elsif function_expected | |||
kind = :function | |||
elsif check(/\s*[=:]\s*function\b/) | |||
kind = :function | |||
elsif key_expected && check(/\s*:/) | |||
kind = :key | |||
end | |||
end | |||
function_expected = (kind == :keyword) && (match == 'function') | |||
key_expected = false | |||
elsif match = scan(/["']/) | |||
if key_expected && check(KEY_CHECK_PATTERN[match]) | |||
state = :key | |||
else | |||
state = :string | |||
end | |||
tokens << [:open, state] | |||
string_delimiter = match | |||
kind = :delimiter | |||
elsif value_expected && (match = scan(/\/(?=\S)/)) | |||
tokens << [:open, :regexp] | |||
state = :regexp | |||
string_delimiter = '/' | |||
kind = :delimiter | |||
elsif scan(/ \/ /x) | |||
value_expected = true | |||
key_expected = false | |||
kind = :operator | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string, :regexp, :key | |||
if scan(STRING_CONTENT_PATTERN[string_delimiter]) | |||
kind = :content | |||
elsif match = scan(/["'\/]/) | |||
tokens << [match, :delimiter] | |||
if state == :regexp | |||
modifiers = scan(/[gim]+/) | |||
tokens << [modifiers, :modifier] if modifiers && !modifiers.empty? | |||
end | |||
tokens << [:close, state] | |||
string_delimiter = nil | |||
key_expected = value_expected = false | |||
state = :initial | |||
next | |||
elsif state != :regexp && (match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)) | |||
if string_delimiter == "'" && !(match == "\\\\" || match == "\\'") | |||
kind = :content | |||
else | |||
kind = :char | |||
end | |||
elsif state == :regexp && scan(/ \\ (?: #{ESCAPE} | #{REGEXP_ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
kind = :char | |||
elsif scan(/\\./m) | |||
kind = :content | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, state] | |||
kind = :error | |||
key_expected = value_expected = false | |||
state = :initial | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
if [:string, :regexp].include? state | |||
tokens << [:close, state] | |||
end | |||
tokens | |||
end | |||
protected | |||
def reset_instance | |||
super | |||
@xml_scanner.reset if defined? @xml_scanner | |||
end | |||
def xml_scanner | |||
@xml_scanner ||= CodeRay.scanner :xml, :tokens => @tokens, :keep_tokens => true, :keep_state => false | |||
end | |||
end | |||
end | |||
end |
@@ -1,108 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class JSON < Scanner | |||
include Streamable | |||
register_for :json | |||
file_extension 'json' | |||
KINDS_NOT_LOC = [ | |||
:float, :char, :content, :delimiter, | |||
:error, :integer, :operator, :value, | |||
] | |||
ESCAPE = / [bfnrt\\"\/] /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} /x | |||
def scan_tokens tokens, options | |||
state = :initial | |||
stack = [] | |||
key_expected = false | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if match = scan(/ \s+ | \\\n /x) | |||
tokens << [match, :space] | |||
next | |||
elsif match = scan(/ [:,\[{\]}] /x) | |||
kind = :operator | |||
case match | |||
when '{' then stack << :object; key_expected = true | |||
when '[' then stack << :array | |||
when ':' then key_expected = false | |||
when ',' then key_expected = true if stack.last == :object | |||
when '}', ']' then stack.pop # no error recovery, but works for valid JSON | |||
end | |||
elsif match = scan(/ true | false | null /x) | |||
kind = :value | |||
elsif match = scan(/-?(?:0|[1-9]\d*)/) | |||
kind = :integer | |||
if scan(/\.\d+(?:[eE][-+]?\d+)?|[eE][-+]?\d+/) | |||
match << matched | |||
kind = :float | |||
end | |||
elsif match = scan(/"/) | |||
state = key_expected ? :key : :string | |||
tokens << [:open, state] | |||
kind = :delimiter | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string, :key | |||
if scan(/[^\\"]+/) | |||
kind = :content | |||
elsif scan(/"/) | |||
tokens << ['"', :delimiter] | |||
tokens << [:close, state] | |||
state = :initial | |||
next | |||
elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
kind = :char | |||
elsif scan(/\\./m) | |||
kind = :content | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, state] | |||
kind = :error | |||
state = :initial | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
if [:string, :key].include? state | |||
tokens << [:close, state] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,136 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
load :html | |||
load :ruby | |||
# Nitro XHTML Scanner | |||
class NitroXHTML < Scanner | |||
include Streamable | |||
register_for :nitro_xhtml | |||
file_extension :xhtml | |||
title 'Nitro XHTML' | |||
KINDS_NOT_LOC = HTML::KINDS_NOT_LOC | |||
NITRO_RUBY_BLOCK = / | |||
<\?r | |||
(?> | |||
[^\?]* | |||
(?> \?(?!>) [^\?]* )* | |||
) | |||
(?: \?> )? | |||
| | |||
<ruby> | |||
(?> | |||
[^<]* | |||
(?> <(?!\/ruby>) [^<]* )* | |||
) | |||
(?: <\/ruby> )? | |||
| | |||
<% | |||
(?> | |||
[^%]* | |||
(?> %(?!>) [^%]* )* | |||
) | |||
(?: %> )? | |||
/mx | |||
NITRO_VALUE_BLOCK = / | |||
\# | |||
(?: | |||
\{ | |||
[^{}]* | |||
(?> | |||
\{ [^}]* \} | |||
(?> [^{}]* ) | |||
)* | |||
\}? | |||
| \| [^|]* \|? | |||
| \( [^)]* \)? | |||
| \[ [^\]]* \]? | |||
| \\ [^\\]* \\? | |||
) | |||
/x | |||
NITRO_ENTITY = / | |||
% (?: \#\d+ | \w+ ) ; | |||
/ | |||
START_OF_RUBY = / | |||
(?=[<\#%]) | |||
< (?: \?r | % | ruby> ) | |||
| \# [{(|] | |||
| % (?: \#\d+ | \w+ ) ; | |||
/x | |||
CLOSING_PAREN = Hash.new do |h, p| | |||
h[p] = p | |||
end.update( { | |||
'(' => ')', | |||
'[' => ']', | |||
'{' => '}', | |||
} ) | |||
private | |||
def setup | |||
@ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true | |||
@html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true, :keep_state => true | |||
end | |||
def reset_instance | |||
super | |||
@html_scanner.reset | |||
end | |||
def scan_tokens tokens, options | |||
until eos? | |||
if (match = scan_until(/(?=#{START_OF_RUBY})/o) || scan_until(/\z/)) and not match.empty? | |||
@html_scanner.tokenize match | |||
elsif match = scan(/#{NITRO_VALUE_BLOCK}/o) | |||
start_tag = match[0,2] | |||
delimiter = CLOSING_PAREN[start_tag[1,1]] | |||
end_tag = match[-1,1] == delimiter ? delimiter : '' | |||
tokens << [:open, :inline] | |||
tokens << [start_tag, :inline_delimiter] | |||
code = match[start_tag.size .. -1 - end_tag.size] | |||
@ruby_scanner.tokenize code | |||
tokens << [end_tag, :inline_delimiter] unless end_tag.empty? | |||
tokens << [:close, :inline] | |||
elsif match = scan(/#{NITRO_RUBY_BLOCK}/o) | |||
start_tag = '<?r' | |||
end_tag = match[-2,2] == '?>' ? '?>' : '' | |||
tokens << [:open, :inline] | |||
tokens << [start_tag, :inline_delimiter] | |||
code = match[start_tag.size .. -(end_tag.size)-1] | |||
@ruby_scanner.tokenize code | |||
tokens << [end_tag, :inline_delimiter] unless end_tag.empty? | |||
tokens << [:close, :inline] | |||
elsif entity = scan(/#{NITRO_ENTITY}/o) | |||
tokens << [entity, :entity] | |||
elsif scan(/%/) | |||
tokens << [matched, :error] | |||
else | |||
raise_inspect 'else-case reached!', tokens | |||
end | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,533 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
load :html | |||
# Original by Stefan Walk. | |||
class PHP < Scanner | |||
register_for :php | |||
file_extension 'php' | |||
KINDS_NOT_LOC = HTML::KINDS_NOT_LOC | |||
def setup | |||
@html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true, :keep_state => true | |||
end | |||
def reset_instance | |||
super | |||
@html_scanner.reset | |||
end | |||
module Words | |||
# according to http://www.php.net/manual/en/reserved.keywords.php | |||
KEYWORDS = %w[ | |||
abstract and array as break case catch class clone const continue declare default do else elseif | |||
enddeclare endfor endforeach endif endswitch endwhile extends final for foreach function global | |||
goto if implements interface instanceof namespace new or private protected public static switch | |||
throw try use var while xor | |||
cfunction old_function | |||
] | |||
TYPES = %w[ int integer float double bool boolean string array object resource ] | |||
LANGUAGE_CONSTRUCTS = %w[ | |||
die echo empty exit eval include include_once isset list | |||
require require_once return print unset | |||
] | |||
CLASSES = %w[ Directory stdClass __PHP_Incomplete_Class exception php_user_filter Closure ] | |||
# according to http://php.net/quickref.php on 2009-04-21; | |||
# all functions with _ excluded (module functions) and selected additional functions | |||
BUILTIN_FUNCTIONS = %w[ | |||
abs acos acosh addcslashes addslashes aggregate array arsort ascii2ebcdic asin asinh asort assert atan atan2 | |||
atanh basename bcadd bccomp bcdiv bcmod bcmul bcpow bcpowmod bcscale bcsqrt bcsub bin2hex bindec | |||
bindtextdomain bzclose bzcompress bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite | |||
calculhmac ceil chdir checkdate checkdnsrr chgrp chmod chop chown chr chroot clearstatcache closedir closelog | |||
compact constant copy cos cosh count crc32 crypt current date dcgettext dcngettext deaggregate decbin dechex | |||
decoct define defined deg2rad delete dgettext die dirname diskfreespace dl dngettext doubleval each | |||
ebcdic2ascii echo empty end ereg eregi escapeshellarg escapeshellcmd eval exec exit exp explode expm1 extract | |||
fclose feof fflush fgetc fgetcsv fgets fgetss file fileatime filectime filegroup fileinode filemtime fileowner | |||
fileperms filepro filesize filetype floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv | |||
fputs fread frenchtojd fscanf fseek fsockopen fstat ftell ftok ftruncate fwrite getallheaders getcwd getdate | |||
getenv gethostbyaddr gethostbyname gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid | |||
getmyuid getopt getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext | |||
gettimeofday gettype glob gmdate gmmktime gmstrftime gregoriantojd gzclose gzcompress gzdecode gzdeflate | |||
gzencode gzeof gzfile gzgetc gzgets gzgetss gzinflate gzopen gzpassthru gzputs gzread gzrewind gzseek gztell | |||
gzuncompress gzwrite hash header hebrev hebrevc hexdec htmlentities htmlspecialchars hypot iconv idate | |||
implode include intval ip2long iptcembed iptcparse isset | |||
jddayofweek jdmonthname jdtofrench jdtogregorian jdtojewish jdtojulian jdtounix jewishtojd join jpeg2wbmp | |||
juliantojd key krsort ksort lcfirst lchgrp lchown levenshtein link linkinfo list localeconv localtime log | |||
log10 log1p long2ip lstat ltrim mail main max md5 metaphone mhash microtime min mkdir mktime msql natcasesort | |||
natsort next ngettext nl2br nthmac octdec opendir openlog | |||
ord overload pack passthru pathinfo pclose pfsockopen phpcredits phpinfo phpversion pi png2wbmp popen pos pow | |||
prev print printf putenv quotemeta rad2deg rand range rawurldecode rawurlencode readdir readfile readgzfile | |||
readline readlink realpath recode rename require reset rewind rewinddir rmdir round rsort rtrim scandir | |||
serialize setcookie setlocale setrawcookie settype sha1 shuffle signeurlpaiement sin sinh sizeof sleep snmpget | |||
snmpgetnext snmprealwalk snmpset snmpwalk snmpwalkoid sort soundex split spliti sprintf sqrt srand sscanf stat | |||
strcasecmp strchr strcmp strcoll strcspn strftime stripcslashes stripos stripslashes stristr strlen | |||
strnatcasecmp strnatcmp strncasecmp strncmp strpbrk strpos strptime strrchr strrev strripos strrpos strspn | |||
strstr strtok strtolower strtotime strtoupper strtr strval substr symlink syslog system tan tanh tempnam | |||
textdomain time tmpfile touch trim uasort ucfirst ucwords uksort umask uniqid unixtojd unlink unpack | |||
unserialize unset urldecode urlencode usleep usort vfprintf virtual vprintf vsprintf wordwrap | |||
array_change_key_case array_chunk array_combine array_count_values array_diff array_diff_assoc | |||
array_diff_key array_diff_uassoc array_diff_ukey array_fill array_fill_keys array_filter array_flip | |||
array_intersect array_intersect_assoc array_intersect_key array_intersect_uassoc array_intersect_ukey | |||
array_key_exists array_keys array_map array_merge array_merge_recursive array_multisort array_pad | |||
array_pop array_product array_push array_rand array_reduce array_reverse array_search array_shift | |||
array_slice array_splice array_sum array_udiff array_udiff_assoc array_udiff_uassoc array_uintersect | |||
array_uintersect_assoc array_uintersect_uassoc array_unique array_unshift array_values array_walk | |||
array_walk_recursive | |||
assert_options base_convert base64_decode base64_encode | |||
chunk_split class_exists class_implements class_parents | |||
count_chars debug_backtrace debug_print_backtrace debug_zval_dump | |||
error_get_last error_log error_reporting extension_loaded | |||
file_exists file_get_contents file_put_contents load_file | |||
func_get_arg func_get_args func_num_args function_exists | |||
get_browser get_called_class get_cfg_var get_class get_class_methods get_class_vars | |||
get_current_user get_declared_classes get_declared_interfaces get_defined_constants | |||
get_defined_functions get_defined_vars get_extension_funcs get_headers get_html_translation_table | |||
get_include_path get_included_files get_loaded_extensions get_magic_quotes_gpc get_magic_quotes_runtime | |||
get_meta_tags get_object_vars get_parent_class get_required_filesget_resource_type | |||
gc_collect_cycles gc_disable gc_enable gc_enabled | |||
halt_compiler headers_list headers_sent highlight_file highlight_string | |||
html_entity_decode htmlspecialchars_decode | |||
in_array include_once inclued_get_data | |||
is_a is_array is_binary is_bool is_buffer is_callable is_dir is_double is_executable is_file is_finite | |||
is_float is_infinite is_int is_integer is_link is_long is_nan is_null is_numeric is_object is_readable | |||
is_real is_resource is_scalar is_soap_fault is_string is_subclass_of is_unicode is_uploaded_file | |||
is_writable is_writeable | |||
locale_get_default locale_set_default | |||
number_format override_function parse_str parse_url | |||
php_check_syntax php_ini_loaded_file php_ini_scanned_files php_logo_guid php_sapi_name | |||
php_strip_whitespace php_uname | |||
preg_filter preg_grep preg_last_error preg_match preg_match_all preg_quote preg_replace | |||
preg_replace_callback preg_split print_r | |||
require_once register_shutdown_function register_tick_function | |||
set_error_handler set_exception_handler set_file_buffer set_include_path | |||
set_magic_quotes_runtime set_time_limit shell_exec | |||
str_getcsv str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split str_word_count | |||
strip_tags substr_compare substr_count substr_replace | |||
time_nanosleep time_sleep_until | |||
token_get_all token_name trigger_error | |||
unregister_tick_function use_soap_error_handler user_error | |||
utf8_decode utf8_encode var_dump var_export | |||
version_compare | |||
zend_logo_guid zend_thread_id zend_version | |||
create_function call_user_func_array | |||
posix_access posix_ctermid posix_get_last_error posix_getcwd posix_getegid | |||
posix_geteuid posix_getgid posix_getgrgid posix_getgrnam posix_getgroups | |||
posix_getlogin posix_getpgid posix_getpgrp posix_getpid posix_getppid | |||
posix_getpwnam posix_getpwuid posix_getrlimit posix_getsid posix_getuid | |||
posix_initgroups posix_isatty posix_kill posix_mkfifo posix_mknod | |||
posix_setegid posix_seteuid posix_setgid posix_setpgid posix_setsid | |||
posix_setuid posix_strerror posix_times posix_ttyname posix_uname | |||
pcntl_alarm pcntl_exec pcntl_fork pcntl_getpriority pcntl_setpriority | |||
pcntl_signal pcntl_signal_dispatch pcntl_sigprocmask pcntl_sigtimedwait | |||
pcntl_sigwaitinfo pcntl_wait pcntl_waitpid pcntl_wexitstatus pcntl_wifexited | |||
pcntl_wifsignaled pcntl_wifstopped pcntl_wstopsig pcntl_wtermsig | |||
] | |||
# TODO: more built-in PHP functions? | |||
EXCEPTIONS = %w[ | |||
E_ERROR E_WARNING E_PARSE E_NOTICE E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING | |||
E_USER_ERROR E_USER_WARNING E_USER_NOTICE E_DEPRECATED E_USER_DEPRECATED E_ALL E_STRICT | |||
] | |||
CONSTANTS = %w[ | |||
null true false self parent | |||
__LINE__ __DIR__ __FILE__ __LINE__ | |||
__CLASS__ __NAMESPACE__ __METHOD__ __FUNCTION__ | |||
PHP_VERSION PHP_MAJOR_VERSION PHP_MINOR_VERSION PHP_RELEASE_VERSION PHP_VERSION_ID PHP_EXTRA_VERSION PHP_ZTS | |||
PHP_DEBUG PHP_MAXPATHLEN PHP_OS PHP_SAPI PHP_EOL PHP_INT_MAX PHP_INT_SIZE DEFAULT_INCLUDE_PATH | |||
PEAR_INSTALL_DIR PEAR_EXTENSION_DIR PHP_EXTENSION_DIR PHP_PREFIX PHP_BINDIR PHP_LIBDIR PHP_DATADIR | |||
PHP_SYSCONFDIR PHP_LOCALSTATEDIR PHP_CONFIG_FILE_PATH PHP_CONFIG_FILE_SCAN_DIR PHP_SHLIB_SUFFIX | |||
PHP_OUTPUT_HANDLER_START PHP_OUTPUT_HANDLER_CONT PHP_OUTPUT_HANDLER_END | |||
__COMPILER_HALT_OFFSET__ | |||
EXTR_OVERWRITE EXTR_SKIP EXTR_PREFIX_SAME EXTR_PREFIX_ALL EXTR_PREFIX_INVALID EXTR_PREFIX_IF_EXISTS | |||
EXTR_IF_EXISTS SORT_ASC SORT_DESC SORT_REGULAR SORT_NUMERIC SORT_STRING CASE_LOWER CASE_UPPER COUNT_NORMAL | |||
COUNT_RECURSIVE ASSERT_ACTIVE ASSERT_CALLBACK ASSERT_BAIL ASSERT_WARNING ASSERT_QUIET_EVAL CONNECTION_ABORTED | |||
CONNECTION_NORMAL CONNECTION_TIMEOUT INI_USER INI_PERDIR INI_SYSTEM INI_ALL M_E M_LOG2E M_LOG10E M_LN2 M_LN10 | |||
M_PI M_PI_2 M_PI_4 M_1_PI M_2_PI M_2_SQRTPI M_SQRT2 M_SQRT1_2 CRYPT_SALT_LENGTH CRYPT_STD_DES CRYPT_EXT_DES | |||
CRYPT_MD5 CRYPT_BLOWFISH DIRECTORY_SEPARATOR SEEK_SET SEEK_CUR SEEK_END LOCK_SH LOCK_EX LOCK_UN LOCK_NB | |||
HTML_SPECIALCHARS HTML_ENTITIES ENT_COMPAT ENT_QUOTES ENT_NOQUOTES INFO_GENERAL INFO_CREDITS | |||
INFO_CONFIGURATION INFO_MODULES INFO_ENVIRONMENT INFO_VARIABLES INFO_LICENSE INFO_ALL CREDITS_GROUP | |||
CREDITS_GENERAL CREDITS_SAPI CREDITS_MODULES CREDITS_DOCS CREDITS_FULLPAGE CREDITS_QA CREDITS_ALL STR_PAD_LEFT | |||
STR_PAD_RIGHT STR_PAD_BOTH PATHINFO_DIRNAME PATHINFO_BASENAME PATHINFO_EXTENSION PATH_SEPARATOR CHAR_MAX | |||
LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_ALL LC_MESSAGES ABDAY_1 ABDAY_2 ABDAY_3 ABDAY_4 ABDAY_5 | |||
ABDAY_6 ABDAY_7 DAY_1 DAY_2 DAY_3 DAY_4 DAY_5 DAY_6 DAY_7 ABMON_1 ABMON_2 ABMON_3 ABMON_4 ABMON_5 ABMON_6 | |||
ABMON_7 ABMON_8 ABMON_9 ABMON_10 ABMON_11 ABMON_12 MON_1 MON_2 MON_3 MON_4 MON_5 MON_6 MON_7 MON_8 MON_9 | |||
MON_10 MON_11 MON_12 AM_STR PM_STR D_T_FMT D_FMT T_FMT T_FMT_AMPM ERA ERA_YEAR ERA_D_T_FMT ERA_D_FMT ERA_T_FMT | |||
ALT_DIGITS INT_CURR_SYMBOL CURRENCY_SYMBOL CRNCYSTR MON_DECIMAL_POINT MON_THOUSANDS_SEP MON_GROUPING | |||
POSITIVE_SIGN NEGATIVE_SIGN INT_FRAC_DIGITS FRAC_DIGITS P_CS_PRECEDES P_SEP_BY_SPACE N_CS_PRECEDES | |||
N_SEP_BY_SPACE P_SIGN_POSN N_SIGN_POSN DECIMAL_POINT RADIXCHAR THOUSANDS_SEP THOUSEP GROUPING YESEXPR NOEXPR | |||
YESSTR NOSTR CODESET LOG_EMERG LOG_ALERT LOG_CRIT LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG LOG_KERN | |||
LOG_USER LOG_MAIL LOG_DAEMON LOG_AUTH LOG_SYSLOG LOG_LPR LOG_NEWS LOG_UUCP LOG_CRON LOG_AUTHPRIV LOG_LOCAL0 | |||
LOG_LOCAL1 LOG_LOCAL2 LOG_LOCAL3 LOG_LOCAL4 LOG_LOCAL5 LOG_LOCAL6 LOG_LOCAL7 LOG_PID LOG_CONS LOG_ODELAY | |||
LOG_NDELAY LOG_NOWAIT LOG_PERROR | |||
] | |||
PREDEFINED = %w[ | |||
$GLOBALS $_SERVER $_GET $_POST $_FILES $_REQUEST $_SESSION $_ENV | |||
$_COOKIE $php_errormsg $HTTP_RAW_POST_DATA $http_response_header | |||
$argc $argv | |||
] | |||
IDENT_KIND = CaseIgnoringWordList.new(:ident). | |||
add(KEYWORDS, :reserved). | |||
add(TYPES, :pre_type). | |||
add(LANGUAGE_CONSTRUCTS, :reserved). | |||
add(BUILTIN_FUNCTIONS, :predefined). | |||
add(CLASSES, :pre_constant). | |||
add(EXCEPTIONS, :exception). | |||
add(CONSTANTS, :pre_constant) | |||
VARIABLE_KIND = WordList.new(:local_variable). | |||
add(PREDEFINED, :predefined) | |||
end | |||
module RE | |||
PHP_START = / | |||
<script\s+[^>]*?language\s*=\s*"php"[^>]*?> | | |||
<script\s+[^>]*?language\s*=\s*'php'[^>]*?> | | |||
<\?php\d? | | |||
<\?(?!xml) | |||
/xi | |||
PHP_END = %r! | |||
</script> | | |||
\?> | |||
!xi | |||
HTML_INDICATOR = /<!DOCTYPE html|<(?:html|body|div|p)[> ]/i | |||
IDENTIFIER = /[a-z_\x7f-\xFF][a-z0-9_\x7f-\xFF]*/i | |||
VARIABLE = /\$#{IDENTIFIER}/ | |||
OPERATOR = / | |||
\.(?!\d)=? | # dot that is not decimal point, string concatenation | |||
&& | \|\| | # logic | |||
:: | -> | => | # scope, member, dictionary | |||
\\(?!\n) | # namespace | |||
\+\+ | -- | # increment, decrement | |||
[,;?:()\[\]{}] | # simple delimiters | |||
[-+*\/%&|^]=? | # ordinary math, binary logic, assignment shortcuts | |||
[~$] | # whatever | |||
=& | # reference assignment | |||
[=!]=?=? | <> | # comparison and assignment | |||
<<=? | >>=? | [<>]=? # comparison and shift | |||
/x | |||
end | |||
def scan_tokens tokens, options | |||
if string.respond_to?(:encoding) | |||
unless string.encoding == Encoding::ASCII_8BIT | |||
self.string = string.encode Encoding::ASCII_8BIT, | |||
:invalid => :replace, :undef => :replace, :replace => '?' | |||
end | |||
end | |||
if check(RE::PHP_START) || # starts with <? | |||
(match?(/\s*<\S/) && exist?(RE::PHP_START)) || # starts with tag and contains <? | |||
exist?(RE::HTML_INDICATOR) || | |||
check(/.{1,100}#{RE::PHP_START}/om) # PHP start after max 100 chars | |||
# is HTML with embedded PHP, so start with HTML | |||
states = [:initial] | |||
else | |||
# is just PHP, so start with PHP surrounded by HTML | |||
states = [:initial, :php] | |||
end | |||
label_expected = true | |||
case_expected = false | |||
heredoc_delimiter = nil | |||
delimiter = nil | |||
modifier = nil | |||
until eos? | |||
match = nil | |||
kind = nil | |||
case states.last | |||
when :initial # HTML | |||
if scan RE::PHP_START | |||
kind = :inline_delimiter | |||
label_expected = true | |||
states << :php | |||
else | |||
match = scan_until(/(?=#{RE::PHP_START})/o) || scan_until(/\z/) | |||
@html_scanner.tokenize match unless match.empty? | |||
next | |||
end | |||
when :php | |||
if match = scan(/\s+/) | |||
tokens << [match, :space] | |||
next | |||
elsif scan(%r! (?m: \/\* (?: .*? \*\/ | .* ) ) | (?://|\#) .*? (?=#{RE::PHP_END}|$) !xo) | |||
kind = :comment | |||
elsif match = scan(RE::IDENTIFIER) | |||
kind = Words::IDENT_KIND[match] | |||
if kind == :ident && label_expected && check(/:(?!:)/) | |||
kind = :label | |||
label_expected = true | |||
else | |||
label_expected = false | |||
if kind == :ident && match =~ /^[A-Z]/ | |||
kind = :constant | |||
elsif kind == :reserved | |||
case match | |||
when 'class' | |||
states << :class_expected | |||
when 'function' | |||
states << :function_expected | |||
when 'case', 'default' | |||
case_expected = true | |||
end | |||
elsif match == 'b' && check(/['"]/) # binary string literal | |||
modifier = match | |||
next | |||
end | |||
end | |||
elsif scan(/(?:\d+\.\d*|\d*\.\d+)(?:e[-+]?\d+)?|\d+e[-+]?\d+/i) | |||
label_expected = false | |||
kind = :float | |||
elsif scan(/0x[0-9a-fA-F]+/) | |||
label_expected = false | |||
kind = :hex | |||
elsif scan(/\d+/) | |||
label_expected = false | |||
kind = :integer | |||
elsif scan(/'/) | |||
tokens << [:open, :string] | |||
if modifier | |||
tokens << [modifier, :modifier] | |||
modifier = nil | |||
end | |||
kind = :delimiter | |||
states.push :sqstring | |||
elsif match = scan(/["`]/) | |||
tokens << [:open, :string] | |||
if modifier | |||
tokens << [modifier, :modifier] | |||
modifier = nil | |||
end | |||
delimiter = match | |||
kind = :delimiter | |||
states.push :dqstring | |||
elsif match = scan(RE::VARIABLE) | |||
label_expected = false | |||
kind = Words::VARIABLE_KIND[match] | |||
elsif scan(/\{/) | |||
kind = :operator | |||
label_expected = true | |||
states.push :php | |||
elsif scan(/\}/) | |||
if states.size == 1 | |||
kind = :error | |||
else | |||
states.pop | |||
if states.last.is_a?(::Array) | |||
delimiter = states.last[1] | |||
states[-1] = states.last[0] | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :inline] | |||
next | |||
else | |||
kind = :operator | |||
label_expected = true | |||
end | |||
end | |||
elsif scan(/@/) | |||
label_expected = false | |||
kind = :exception | |||
elsif scan RE::PHP_END | |||
kind = :inline_delimiter | |||
states = [:initial] | |||
elsif match = scan(/<<<(?:(#{RE::IDENTIFIER})|"(#{RE::IDENTIFIER})"|'(#{RE::IDENTIFIER})')/o) | |||
tokens << [:open, :string] | |||
warn 'heredoc in heredoc?' if heredoc_delimiter | |||
heredoc_delimiter = Regexp.escape(self[1] || self[2] || self[3]) | |||
kind = :delimiter | |||
states.push self[3] ? :sqstring : :dqstring | |||
heredoc_delimiter = /#{heredoc_delimiter}(?=;?$)/ | |||
elsif match = scan(/#{RE::OPERATOR}/o) | |||
label_expected = match == ';' | |||
if case_expected | |||
label_expected = true if match == ':' | |||
case_expected = false | |||
end | |||
kind = :operator | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :sqstring | |||
if scan(heredoc_delimiter ? /[^\\\n]+/ : /[^'\\]+/) | |||
kind = :content | |||
elsif !heredoc_delimiter && scan(/'/) | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :string] | |||
delimiter = nil | |||
label_expected = false | |||
states.pop | |||
next | |||
elsif heredoc_delimiter && match = scan(/\n/) | |||
kind = :content | |||
if scan heredoc_delimiter | |||
tokens << ["\n", :content] | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :string] | |||
heredoc_delimiter = nil | |||
label_expected = false | |||
states.pop | |||
next | |||
end | |||
elsif scan(heredoc_delimiter ? /\\\\/ : /\\[\\'\n]/) | |||
kind = :char | |||
elsif scan(/\\./m) | |||
kind = :content | |||
elsif scan(/\\/) | |||
kind = :error | |||
end | |||
when :dqstring | |||
if scan(heredoc_delimiter ? /[^${\\\n]+/ : (delimiter == '"' ? /[^"${\\]+/ : /[^`${\\]+/)) | |||
kind = :content | |||
elsif !heredoc_delimiter && scan(delimiter == '"' ? /"/ : /`/) | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :string] | |||
delimiter = nil | |||
label_expected = false | |||
states.pop | |||
next | |||
elsif heredoc_delimiter && match = scan(/\n/) | |||
kind = :content | |||
if scan heredoc_delimiter | |||
tokens << ["\n", :content] | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :string] | |||
heredoc_delimiter = nil | |||
label_expected = false | |||
states.pop | |||
next | |||
end | |||
elsif scan(/\\(?:x[0-9A-Fa-f]{1,2}|[0-7]{1,3})/) | |||
kind = :char | |||
elsif scan(heredoc_delimiter ? /\\[nrtvf\\$]/ : (delimiter == '"' ? /\\[nrtvf\\$"]/ : /\\[nrtvf\\$`]/)) | |||
kind = :char | |||
elsif scan(/\\./m) | |||
kind = :content | |||
elsif scan(/\\/) | |||
kind = :error | |||
elsif match = scan(/#{RE::VARIABLE}/o) | |||
kind = :local_variable | |||
if check(/\[#{RE::IDENTIFIER}\]/o) | |||
tokens << [:open, :inline] | |||
tokens << [match, :local_variable] | |||
tokens << [scan(/\[/), :operator] | |||
tokens << [scan(/#{RE::IDENTIFIER}/o), :ident] | |||
tokens << [scan(/\]/), :operator] | |||
tokens << [:close, :inline] | |||
next | |||
elsif check(/\[/) | |||
match << scan(/\[['"]?#{RE::IDENTIFIER}?['"]?\]?/o) | |||
kind = :error | |||
elsif check(/->#{RE::IDENTIFIER}/o) | |||
tokens << [:open, :inline] | |||
tokens << [match, :local_variable] | |||
tokens << [scan(/->/), :operator] | |||
tokens << [scan(/#{RE::IDENTIFIER}/o), :ident] | |||
tokens << [:close, :inline] | |||
next | |||
elsif check(/->/) | |||
match << scan(/->/) | |||
kind = :error | |||
end | |||
elsif match = scan(/\{/) | |||
if check(/\$/) | |||
kind = :delimiter | |||
states[-1] = [states.last, delimiter] | |||
delimiter = nil | |||
states.push :php | |||
tokens << [:open, :inline] | |||
else | |||
kind = :string | |||
end | |||
elsif scan(/\$\{#{RE::IDENTIFIER}\}/o) | |||
kind = :local_variable | |||
elsif scan(/\$/) | |||
kind = :content | |||
end | |||
when :class_expected | |||
if scan(/\s+/) | |||
kind = :space | |||
elsif match = scan(/#{RE::IDENTIFIER}/o) | |||
kind = :class | |||
states.pop | |||
else | |||
states.pop | |||
next | |||
end | |||
when :function_expected | |||
if scan(/\s+/) | |||
kind = :space | |||
elsif scan(/&/) | |||
kind = :operator | |||
elsif match = scan(/#{RE::IDENTIFIER}/o) | |||
kind = :function | |||
states.pop | |||
else | |||
states.pop | |||
next | |||
end | |||
else | |||
raise_inspect 'Unknown state!', tokens, states | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens, states | |||
end | |||
raise_inspect 'Empty token', tokens, states unless match | |||
tokens << [match, kind] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,21 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
class Plaintext < Scanner | |||
register_for :plaintext, :plain | |||
title 'Plain text' | |||
include Streamable | |||
KINDS_NOT_LOC = [:plain] | |||
def scan_tokens tokens, options | |||
text = (scan_until(/\z/) || '') | |||
tokens << [text, :plain] | |||
end | |||
end | |||
end | |||
end |
@@ -1,285 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
# Bases on pygments' PythonLexer, see | |||
# http://dev.pocoo.org/projects/pygments/browser/pygments/lexers/agile.py. | |||
class Python < Scanner | |||
include Streamable | |||
register_for :python | |||
file_extension 'py' | |||
KEYWORDS = [ | |||
'and', 'as', 'assert', 'break', 'class', 'continue', 'def', | |||
'del', 'elif', 'else', 'except', 'finally', 'for', | |||
'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', | |||
'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield', | |||
'nonlocal', # new in Python 3 | |||
] | |||
OLD_KEYWORDS = [ | |||
'exec', 'print', # gone in Python 3 | |||
] | |||
PREDEFINED_METHODS_AND_TYPES = %w[ | |||
__import__ abs all any apply basestring bin bool buffer | |||
bytearray bytes callable chr classmethod cmp coerce compile | |||
complex delattr dict dir divmod enumerate eval execfile exit | |||
file filter float frozenset getattr globals hasattr hash hex id | |||
input int intern isinstance issubclass iter len list locals | |||
long map max min next object oct open ord pow property range | |||
raw_input reduce reload repr reversed round set setattr slice | |||
sorted staticmethod str sum super tuple type unichr unicode | |||
vars xrange zip | |||
] | |||
PREDEFINED_EXCEPTIONS = %w[ | |||
ArithmeticError AssertionError AttributeError | |||
BaseException DeprecationWarning EOFError EnvironmentError | |||
Exception FloatingPointError FutureWarning GeneratorExit IOError | |||
ImportError ImportWarning IndentationError IndexError KeyError | |||
KeyboardInterrupt LookupError MemoryError NameError | |||
NotImplemented NotImplementedError OSError OverflowError | |||
OverflowWarning PendingDeprecationWarning ReferenceError | |||
RuntimeError RuntimeWarning StandardError StopIteration | |||
SyntaxError SyntaxWarning SystemError SystemExit TabError | |||
TypeError UnboundLocalError UnicodeDecodeError | |||
UnicodeEncodeError UnicodeError UnicodeTranslateError | |||
UnicodeWarning UserWarning ValueError Warning ZeroDivisionError | |||
] | |||
PREDEFINED_VARIABLES_AND_CONSTANTS = [ | |||
'False', 'True', 'None', # "keywords" since Python 3 | |||
'self', 'Ellipsis', 'NotImplemented', | |||
] | |||
IDENT_KIND = WordList.new(:ident). | |||
add(KEYWORDS, :keyword). | |||
add(OLD_KEYWORDS, :old_keyword). | |||
add(PREDEFINED_METHODS_AND_TYPES, :predefined). | |||
add(PREDEFINED_VARIABLES_AND_CONSTANTS, :pre_constant). | |||
add(PREDEFINED_EXCEPTIONS, :exception) | |||
NAME = / [^\W\d] \w* /x | |||
ESCAPE = / [abfnrtv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} | N\{[-\w ]+\} /x | |||
OPERATOR = / | |||
\.\.\. | # ellipsis | |||
\.(?!\d) | # dot but not decimal point | |||
[,;:()\[\]{}] | # simple delimiters | |||
\/\/=? | \*\*=? | # special math | |||
[-+*\/%&|^]=? | # ordinary math and binary logic | |||
[~`] | # binary complement and inspection | |||
<<=? | >>=? | [<>=]=? | != # comparison and assignment | |||
/x | |||
STRING_DELIMITER_REGEXP = Hash.new do |h, delimiter| | |||
h[delimiter] = Regexp.union delimiter | |||
end | |||
STRING_CONTENT_REGEXP = Hash.new do |h, delimiter| | |||
h[delimiter] = / [^\\\n]+? (?= \\ | $ | #{Regexp.escape(delimiter)} ) /x | |||
end | |||
DEF_NEW_STATE = WordList.new(:initial). | |||
add(%w(def), :def_expected). | |||
add(%w(import from), :include_expected). | |||
add(%w(class), :class_expected) | |||
DESCRIPTOR = / | |||
#{NAME} | |||
(?: \. #{NAME} )* | |||
| \* | |||
/x | |||
def scan_tokens tokens, options | |||
state = :initial | |||
string_delimiter = nil | |||
string_raw = false | |||
import_clause = class_name_follows = last_token_dot = false | |||
unicode = string.respond_to?(:encoding) && string.encoding.name == 'UTF-8' | |||
from_import_state = [] | |||
until eos? | |||
kind = nil | |||
match = nil | |||
if state == :string | |||
if scan(STRING_DELIMITER_REGEXP[string_delimiter]) | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :string] | |||
state = :initial | |||
next | |||
elsif string_delimiter.size == 3 && scan(/\n/) | |||
kind = :content | |||
elsif scan(STRING_CONTENT_REGEXP[string_delimiter]) | |||
kind = :content | |||
elsif !string_raw && scan(/ \\ #{ESCAPE} /ox) | |||
kind = :char | |||
elsif scan(/ \\ #{UNICODE_ESCAPE} /ox) | |||
kind = :char | |||
elsif scan(/ \\ . /x) | |||
kind = :content | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, :string] | |||
kind = :error | |||
state = :initial | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens, state | |||
end | |||
elsif match = scan(/ [ \t]+ | \\\n /x) | |||
tokens << [match, :space] | |||
next | |||
elsif match = scan(/\n/) | |||
tokens << [match, :space] | |||
state = :initial if state == :include_expected | |||
next | |||
elsif match = scan(/ \# [^\n]* /mx) | |||
tokens << [match, :comment] | |||
next | |||
elsif state == :initial | |||
if scan(/#{OPERATOR}/o) | |||
kind = :operator | |||
elsif match = scan(/(u?r?|b)?("""|"|'''|')/i) | |||
tokens << [:open, :string] | |||
string_delimiter = self[2] | |||
string_raw = false | |||
modifiers = self[1] | |||
unless modifiers.empty? | |||
string_raw = !!modifiers.index(?r) | |||
tokens << [modifiers, :modifier] | |||
match = string_delimiter | |||
end | |||
state = :string | |||
kind = :delimiter | |||
# TODO: backticks | |||
elsif match = scan(unicode ? /#{NAME}/uo : /#{NAME}/o) | |||
kind = IDENT_KIND[match] | |||
# TODO: keyword arguments | |||
kind = :ident if last_token_dot | |||
if kind == :old_keyword | |||
kind = check(/\(/) ? :ident : :keyword | |||
elsif kind == :predefined && check(/ *=/) | |||
kind = :ident | |||
elsif kind == :keyword | |||
state = DEF_NEW_STATE[match] | |||
from_import_state << match.to_sym if state == :include_expected | |||
end | |||
elsif scan(/@[a-zA-Z0-9_.]+[lL]?/) | |||
kind = :decorator | |||
elsif scan(/0[xX][0-9A-Fa-f]+[lL]?/) | |||
kind = :hex | |||
elsif scan(/0[bB][01]+[lL]?/) | |||
kind = :bin | |||
elsif match = scan(/(?:\d*\.\d+|\d+\.\d*)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+/) | |||
kind = :float | |||
if scan(/[jJ]/) | |||
match << matched | |||
kind = :imaginary | |||
end | |||
elsif scan(/0[oO][0-7]+|0[0-7]+(?![89.eE])[lL]?/) | |||
kind = :oct | |||
elsif match = scan(/\d+([lL])?/) | |||
kind = :integer | |||
if self[1] == nil && scan(/[jJ]/) | |||
match << matched | |||
kind = :imaginary | |||
end | |||
else | |||
getch | |||
kind = :error | |||
end | |||
elsif state == :def_expected | |||
state = :initial | |||
if match = scan(unicode ? /#{NAME}/uo : /#{NAME}/o) | |||
kind = :method | |||
else | |||
next | |||
end | |||
elsif state == :class_expected | |||
state = :initial | |||
if match = scan(unicode ? /#{NAME}/uo : /#{NAME}/o) | |||
kind = :class | |||
else | |||
next | |||
end | |||
elsif state == :include_expected | |||
if match = scan(unicode ? /#{DESCRIPTOR}/uo : /#{DESCRIPTOR}/o) | |||
kind = :include | |||
if match == 'as' | |||
kind = :keyword | |||
from_import_state << :as | |||
elsif from_import_state.first == :from && match == 'import' | |||
kind = :keyword | |||
from_import_state << :import | |||
elsif from_import_state.last == :as | |||
# kind = match[0,1][unicode ? /[[:upper:]]/u : /[[:upper:]]/] ? :class : :method | |||
kind = :ident | |||
from_import_state.pop | |||
elsif IDENT_KIND[match] == :keyword | |||
unscan | |||
match = nil | |||
state = :initial | |||
next | |||
end | |||
elsif match = scan(/,/) | |||
from_import_state.pop if from_import_state.last == :as | |||
kind = :operator | |||
else | |||
from_import_state = [] | |||
state = :initial | |||
next | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens, state | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens, state | |||
end | |||
raise_inspect 'Empty token', tokens, state unless match | |||
last_token_dot = match == '.' | |||
tokens << [match, kind] | |||
end | |||
if state == :string | |||
tokens << [:close, :string] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,78 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
load :html | |||
load :ruby | |||
# RHTML Scanner | |||
class RHTML < Scanner | |||
include Streamable | |||
register_for :rhtml | |||
title 'HTML ERB Template' | |||
KINDS_NOT_LOC = HTML::KINDS_NOT_LOC | |||
ERB_RUBY_BLOCK = / | |||
<%(?!%)[=-]? | |||
(?> | |||
[^\-%]* # normal* | |||
(?> # special | |||
(?: %(?!>) | -(?!%>) ) | |||
[^\-%]* # normal* | |||
)* | |||
) | |||
(?: -?%> )? | |||
/x | |||
START_OF_ERB = / | |||
<%(?!%) | |||
/x | |||
private | |||
def setup | |||
@ruby_scanner = CodeRay.scanner :ruby, :tokens => @tokens, :keep_tokens => true | |||
@html_scanner = CodeRay.scanner :html, :tokens => @tokens, :keep_tokens => true, :keep_state => true | |||
end | |||
def reset_instance | |||
super | |||
@html_scanner.reset | |||
end | |||
def scan_tokens tokens, options | |||
until eos? | |||
if (match = scan_until(/(?=#{START_OF_ERB})/o) || scan_until(/\z/)) and not match.empty? | |||
@html_scanner.tokenize match | |||
elsif match = scan(/#{ERB_RUBY_BLOCK}/o) | |||
start_tag = match[/\A<%[-=#]?/] | |||
end_tag = match[/-?%?>?\z/] | |||
tokens << [:open, :inline] | |||
tokens << [start_tag, :inline_delimiter] | |||
code = match[start_tag.size .. -1 - end_tag.size] | |||
if start_tag == '<%#' | |||
tokens << [code, :comment] | |||
else | |||
@ruby_scanner.tokenize code | |||
end | |||
tokens << [end_tag, :inline_delimiter] unless end_tag.empty? | |||
tokens << [:close, :inline] | |||
else | |||
raise_inspect 'else-case reached!', tokens | |||
end | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,444 +0,0 @@ | |||
# encoding: utf-8 | |||
module CodeRay | |||
module Scanners | |||
# This scanner is really complex, since Ruby _is_ a complex language! | |||
# | |||
# It tries to highlight 100% of all common code, | |||
# and 90% of strange codes. | |||
# | |||
# It is optimized for HTML highlighting, and is not very useful for | |||
# parsing or pretty printing. | |||
# | |||
# For now, I think it's better than the scanners in VIM or Syntax, or | |||
# any highlighter I was able to find, except Caleb's RubyLexer. | |||
# | |||
# I hope it's also better than the rdoc/irb lexer. | |||
class Ruby < Scanner | |||
include Streamable | |||
register_for :ruby | |||
file_extension 'rb' | |||
helper :patterns | |||
if not defined? EncodingError | |||
EncodingError = Class.new Exception | |||
end | |||
private | |||
def scan_tokens tokens, options | |||
if string.respond_to?(:encoding) | |||
unless string.encoding == Encoding::UTF_8 | |||
self.string = string.encode Encoding::UTF_8, | |||
:invalid => :replace, :undef => :replace, :replace => '?' | |||
end | |||
unicode = false | |||
else | |||
unicode = exist?(/[^\x00-\x7f]/) | |||
end | |||
last_token_dot = false | |||
value_expected = true | |||
heredocs = nil | |||
last_state = nil | |||
state = :initial | |||
depth = nil | |||
inline_block_stack = [] | |||
patterns = Patterns # avoid constant lookup | |||
until eos? | |||
match = nil | |||
kind = nil | |||
if state.instance_of? patterns::StringState | |||
# {{{ | |||
match = scan_until(state.pattern) || scan_until(/\z/) | |||
tokens << [match, :content] unless match.empty? | |||
break if eos? | |||
if state.heredoc and self[1] # end of heredoc | |||
match = getch.to_s | |||
match << scan_until(/$/) unless eos? | |||
tokens << [match, :delimiter] | |||
tokens << [:close, state.type] | |||
state = state.next_state | |||
next | |||
end | |||
case match = getch | |||
when state.delim | |||
if state.paren | |||
state.paren_depth -= 1 | |||
if state.paren_depth > 0 | |||
tokens << [match, :nesting_delimiter] | |||
next | |||
end | |||
end | |||
tokens << [match, :delimiter] | |||
if state.type == :regexp and not eos? | |||
modifiers = scan(/#{patterns::REGEXP_MODIFIERS}/ox) | |||
tokens << [modifiers, :modifier] unless modifiers.empty? | |||
end | |||
tokens << [:close, state.type] | |||
value_expected = false | |||
state = state.next_state | |||
when '\\' | |||
if state.interpreted | |||
if esc = scan(/ #{patterns::ESCAPE} /ox) | |||
tokens << [match + esc, :char] | |||
else | |||
tokens << [match, :error] | |||
end | |||
else | |||
case m = getch | |||
when state.delim, '\\' | |||
tokens << [match + m, :char] | |||
when nil | |||
tokens << [match, :error] | |||
else | |||
tokens << [match + m, :content] | |||
end | |||
end | |||
when '#' | |||
case peek(1) | |||
when '{' | |||
inline_block_stack << [state, depth, heredocs] | |||
value_expected = true | |||
state = :initial | |||
depth = 1 | |||
tokens << [:open, :inline] | |||
tokens << [match + getch, :inline_delimiter] | |||
when '$', '@' | |||
tokens << [match, :escape] | |||
last_state = state # scan one token as normal code, then return here | |||
state = :initial | |||
else | |||
raise_inspect 'else-case # reached; #%p not handled' % peek(1), tokens | |||
end | |||
when state.paren | |||
state.paren_depth += 1 | |||
tokens << [match, :nesting_delimiter] | |||
when /#{patterns::REGEXP_SYMBOLS}/ox | |||
tokens << [match, :function] | |||
else | |||
raise_inspect 'else-case " reached; %p not handled, state = %p' % [match, state], tokens | |||
end | |||
next | |||
# }}} | |||
else | |||
# {{{ | |||
if match = scan(/[ \t\f]+/) | |||
kind = :space | |||
match << scan(/\s*/) unless eos? || heredocs | |||
value_expected = true if match.index(?\n) | |||
tokens << [match, kind] | |||
next | |||
elsif match = scan(/\\?\n/) | |||
kind = :space | |||
if match == "\n" | |||
value_expected = true | |||
state = :initial if state == :undef_comma_expected | |||
end | |||
if heredocs | |||
unscan # heredoc scanning needs \n at start | |||
state = heredocs.shift | |||
tokens << [:open, state.type] | |||
heredocs = nil if heredocs.empty? | |||
next | |||
else | |||
match << scan(/\s*/) unless eos? | |||
end | |||
tokens << [match, kind] | |||
next | |||
elsif bol? && match = scan(/\#!.*/) | |||
tokens << [match, :doctype] | |||
next | |||
elsif match = scan(/\#.*/) or | |||
( bol? and match = scan(/#{patterns::RUBYDOC_OR_DATA}/o) ) | |||
kind = :comment | |||
tokens << [match, kind] | |||
next | |||
elsif state == :initial | |||
# IDENTS # | |||
if match = scan(unicode ? /#{patterns::METHOD_NAME}/uo : | |||
/#{patterns::METHOD_NAME}/o) | |||
if last_token_dot | |||
kind = if match[/^[A-Z]/] and not match?(/\(/) then :constant else :ident end | |||
else | |||
if value_expected != :expect_colon && scan(/:(?= )/) | |||
tokens << [match, :key] | |||
match = ':' | |||
kind = :operator | |||
else | |||
kind = patterns::IDENT_KIND[match] | |||
if kind == :ident | |||
if match[/\A[A-Z]/] and not match[/[!?]$/] and not match?(/\(/) | |||
kind = :constant | |||
end | |||
elsif kind == :reserved | |||
state = patterns::DEF_NEW_STATE[match] | |||
value_expected = :set if patterns::KEYWORDS_EXPECTING_VALUE[match] | |||
end | |||
end | |||
end | |||
value_expected = :set if check(/#{patterns::VALUE_FOLLOWS}/o) | |||
elsif last_token_dot and match = scan(/#{patterns::METHOD_NAME_OPERATOR}|\(/o) | |||
kind = :ident | |||
value_expected = :set if check(unicode ? /#{patterns::VALUE_FOLLOWS}/uo : | |||
/#{patterns::VALUE_FOLLOWS}/o) | |||
# OPERATORS # | |||
elsif not last_token_dot and match = scan(/ \.\.\.? | (?:\.|::)() | [,\(\)\[\]\{\}] | ==?=? /x) | |||
if match !~ / [.\)\]\}] /x or match =~ /\.\.\.?/ | |||
value_expected = :set | |||
end | |||
last_token_dot = :set if self[1] | |||
kind = :operator | |||
unless inline_block_stack.empty? | |||
case match | |||
when '{' | |||
depth += 1 | |||
when '}' | |||
depth -= 1 | |||
if depth == 0 # closing brace of inline block reached | |||
state, depth, heredocs = inline_block_stack.pop | |||
heredocs = nil if heredocs && heredocs.empty? | |||
tokens << [match, :inline_delimiter] | |||
kind = :inline | |||
match = :close | |||
end | |||
end | |||
end | |||
elsif match = scan(/ ['"] /mx) | |||
tokens << [:open, :string] | |||
kind = :delimiter | |||
state = patterns::StringState.new :string, match == '"', match # important for streaming | |||
elsif match = scan(unicode ? /#{patterns::INSTANCE_VARIABLE}/uo : | |||
/#{patterns::INSTANCE_VARIABLE}/o) | |||
kind = :instance_variable | |||
elsif value_expected and match = scan(/\//) | |||
tokens << [:open, :regexp] | |||
kind = :delimiter | |||
interpreted = true | |||
state = patterns::StringState.new :regexp, interpreted, match | |||
# elsif match = scan(/[-+]?#{patterns::NUMERIC}/o) | |||
elsif match = value_expected ? scan(/[-+]?#{patterns::NUMERIC}/o) : scan(/#{patterns::NUMERIC}/o) | |||
kind = self[1] ? :float : :integer | |||
elsif match = scan(unicode ? /#{patterns::SYMBOL}/uo : | |||
/#{patterns::SYMBOL}/o) | |||
case delim = match[1] | |||
when ?', ?" | |||
tokens << [:open, :symbol] | |||
tokens << [':', :symbol] | |||
match = delim.chr | |||
kind = :delimiter | |||
state = patterns::StringState.new :symbol, delim == ?", match | |||
else | |||
kind = :symbol | |||
end | |||
elsif match = scan(/ -[>=]? | [+!~^]=? | [*|&]{1,2}=? | >>? /x) | |||
value_expected = :set | |||
kind = :operator | |||
elsif value_expected and match = scan(unicode ? /#{patterns::HEREDOC_OPEN}/uo : | |||
/#{patterns::HEREDOC_OPEN}/o) | |||
indented = self[1] == '-' | |||
quote = self[3] | |||
delim = self[quote ? 4 : 2] | |||
kind = patterns::QUOTE_TO_TYPE[quote] | |||
tokens << [:open, kind] | |||
tokens << [match, :delimiter] | |||
match = :close | |||
heredoc = patterns::StringState.new kind, quote != '\'', delim, (indented ? :indented : :linestart ) | |||
heredocs ||= [] # create heredocs if empty | |||
heredocs << heredoc | |||
elsif value_expected and match = scan(/#{patterns::FANCY_START_CORRECT}/o) | |||
kind, interpreted = *patterns::FancyStringType.fetch(self[1]) do | |||
raise_inspect 'Unknown fancy string: %%%p' % k, tokens | |||
end | |||
tokens << [:open, kind] | |||
state = patterns::StringState.new kind, interpreted, self[2] | |||
kind = :delimiter | |||
elsif value_expected and match = scan(unicode ? /#{patterns::CHARACTER}/uo : | |||
/#{patterns::CHARACTER}/o) | |||
kind = :integer | |||
elsif match = scan(/ [\/%]=? | <(?:<|=>?)? | [?:;] /x) | |||
value_expected = :set | |||
kind = :operator | |||
elsif match = scan(/`/) | |||
if last_token_dot | |||
kind = :operator | |||
else | |||
tokens << [:open, :shell] | |||
kind = :delimiter | |||
state = patterns::StringState.new :shell, true, match | |||
end | |||
elsif match = scan(unicode ? /#{patterns::GLOBAL_VARIABLE}/uo : | |||
/#{patterns::GLOBAL_VARIABLE}/o) | |||
kind = :global_variable | |||
elsif match = scan(unicode ? /#{patterns::CLASS_VARIABLE}/uo : | |||
/#{patterns::CLASS_VARIABLE}/o) | |||
kind = :class_variable | |||
else | |||
if !unicode && !string.respond_to?(:encoding) | |||
# check for unicode | |||
debug, $DEBUG = $DEBUG, false | |||
begin | |||
if check(/./mu).size > 1 | |||
# seems like we should try again with unicode | |||
unicode = true | |||
end | |||
rescue | |||
# bad unicode char; use getch | |||
ensure | |||
$DEBUG = debug | |||
end | |||
next if unicode | |||
end | |||
kind = :error | |||
match = scan(unicode ? /./mu : /./m) | |||
end | |||
elsif state == :def_expected | |||
state = :initial | |||
if scan(/self\./) | |||
tokens << ['self', :pre_constant] | |||
tokens << ['.', :operator] | |||
end | |||
if match = scan(unicode ? /(?>#{patterns::METHOD_NAME_EX})(?!\.|::)/uo : | |||
/(?>#{patterns::METHOD_NAME_EX})(?!\.|::)/o) | |||
kind = :method | |||
else | |||
next | |||
end | |||
elsif state == :module_expected | |||
if match = scan(/<</) | |||
kind = :operator | |||
else | |||
state = :initial | |||
if match = scan(unicode ? /(?:#{patterns::IDENT}::)*#{patterns::IDENT}/uo : | |||
/(?:#{patterns::IDENT}::)*#{patterns::IDENT}/o) | |||
kind = :class | |||
else | |||
next | |||
end | |||
end | |||
elsif state == :undef_expected | |||
state = :undef_comma_expected | |||
if match = scan(unicode ? /#{patterns::METHOD_NAME_EX}/uo : | |||
/#{patterns::METHOD_NAME_EX}/o) | |||
kind = :method | |||
elsif match = scan(unicode ? /#{patterns::SYMBOL}/uo : | |||
/#{patterns::SYMBOL}/o) | |||
case delim = match[1] | |||
when ?', ?" | |||
tokens << [:open, :symbol] | |||
tokens << [':', :symbol] | |||
match = delim.chr | |||
kind = :delimiter | |||
state = patterns::StringState.new :symbol, delim == ?", match | |||
state.next_state = :undef_comma_expected | |||
else | |||
kind = :symbol | |||
end | |||
else | |||
state = :initial | |||
next | |||
end | |||
elsif state == :alias_expected | |||
match = scan(unicode ? /(#{patterns::METHOD_NAME_OR_SYMBOL})([ \t]+)(#{patterns::METHOD_NAME_OR_SYMBOL})/uo : | |||
/(#{patterns::METHOD_NAME_OR_SYMBOL})([ \t]+)(#{patterns::METHOD_NAME_OR_SYMBOL})/o) | |||
if match | |||
tokens << [self[1], (self[1][0] == ?: ? :symbol : :method)] | |||
tokens << [self[2], :space] | |||
tokens << [self[3], (self[3][0] == ?: ? :symbol : :method)] | |||
end | |||
state = :initial | |||
next | |||
elsif state == :undef_comma_expected | |||
if match = scan(/,/) | |||
kind = :operator | |||
state = :undef_expected | |||
else | |||
state = :initial | |||
next | |||
end | |||
end | |||
# }}} | |||
unless kind == :error | |||
if value_expected = value_expected == :set | |||
value_expected = :expect_colon if match == '?' || match == 'when' | |||
end | |||
last_token_dot = last_token_dot == :set | |||
end | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens, state | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
if last_state | |||
state = last_state | |||
last_state = nil | |||
end | |||
end | |||
end | |||
inline_block_stack << [state] if state.is_a? patterns::StringState | |||
until inline_block_stack.empty? | |||
this_block = inline_block_stack.pop | |||
tokens << [:close, :inline] if this_block.size > 1 | |||
state = this_block.first | |||
tokens << [:close, state.type] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end | |||
# vim:fdm=marker |
@@ -1,252 +0,0 @@ | |||
# encoding: utf-8 | |||
module CodeRay | |||
module Scanners | |||
module Ruby::Patterns # :nodoc: | |||
RESERVED_WORDS = %w[ | |||
and def end in or unless begin | |||
defined? ensure module redo super until | |||
BEGIN break do next rescue then | |||
when END case else for retry | |||
while alias class elsif if not return | |||
undef yield | |||
] | |||
DEF_KEYWORDS = %w[ def ] | |||
UNDEF_KEYWORDS = %w[ undef ] | |||
ALIAS_KEYWORDS = %w[ alias ] | |||
MODULE_KEYWORDS = %w[ class module ] | |||
DEF_NEW_STATE = WordList.new(:initial). | |||
add(DEF_KEYWORDS, :def_expected). | |||
add(UNDEF_KEYWORDS, :undef_expected). | |||
add(ALIAS_KEYWORDS, :alias_expected). | |||
add(MODULE_KEYWORDS, :module_expected) | |||
PREDEFINED_CONSTANTS = %w[ | |||
nil true false self | |||
DATA ARGV ARGF | |||
__FILE__ __LINE__ __ENCODING__ | |||
] | |||
IDENT_KIND = WordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_CONSTANTS, :pre_constant) | |||
if /\w/u === '∑' | |||
# MRI 1.8.6, 1.8.7 | |||
IDENT = /[^\W\d]\w*/ | |||
else | |||
if //.respond_to? :encoding | |||
# MRI 1.9.1, 1.9.2 | |||
IDENT = Regexp.new '[\p{L}\p{M}\p{Pc}\p{Sm}&&[^\x00-\x40\x5b-\x5e\x60\x7b-\x7f]][\p{L}\p{M}\p{N}\p{Pc}\p{Sm}&&[^\x00-\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\x7f]]*' | |||
else | |||
# JRuby, Rubinius | |||
IDENT = /[^\x00-\x40\x5b-\x5e\x60\x7b-\x7f][^\x00-\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\x7f]*/ | |||
end | |||
end | |||
METHOD_NAME = / #{IDENT} [?!]? /ox | |||
METHOD_NAME_OPERATOR = / | |||
\*\*? # multiplication and power | |||
| [-+~]@? # plus, minus, tilde with and without at sign | |||
| [\/%&|^`] # division, modulo or format strings, and, or, xor, system | |||
| \[\]=? # array getter and setter | |||
| << | >> # append or shift left, shift right | |||
| <=?>? | >=? # comparison, rocket operator | |||
| ===? | =~ # simple equality, case equality, match | |||
| ![~=@]? # negation with and without at sign, not-equal and not-match | |||
/ox | |||
METHOD_NAME_EX = / #{IDENT} (?:[?!]|=(?!>))? | #{METHOD_NAME_OPERATOR} /ox | |||
INSTANCE_VARIABLE = / @ #{IDENT} /ox | |||
CLASS_VARIABLE = / @@ #{IDENT} /ox | |||
OBJECT_VARIABLE = / @@? #{IDENT} /ox | |||
GLOBAL_VARIABLE = / \$ (?: #{IDENT} | [1-9]\d* | 0\w* | [~&+`'=\/,;_.<>!@$?*":\\] | -[a-zA-Z_0-9] ) /ox | |||
PREFIX_VARIABLE = / #{GLOBAL_VARIABLE} | #{OBJECT_VARIABLE} /ox | |||
VARIABLE = / @?@? #{IDENT} | #{GLOBAL_VARIABLE} /ox | |||
QUOTE_TO_TYPE = { | |||
'`' => :shell, | |||
'/'=> :regexp, | |||
} | |||
QUOTE_TO_TYPE.default = :string | |||
REGEXP_MODIFIERS = /[mixounse]*/ | |||
REGEXP_SYMBOLS = /[|?*+(){}\[\].^$]/ | |||
DECIMAL = /\d+(?:_\d+)*/ | |||
OCTAL = /0_?[0-7]+(?:_[0-7]+)*/ | |||
HEXADECIMAL = /0x[0-9A-Fa-f]+(?:_[0-9A-Fa-f]+)*/ | |||
BINARY = /0b[01]+(?:_[01]+)*/ | |||
EXPONENT = / [eE] [+-]? #{DECIMAL} /ox | |||
FLOAT_SUFFIX = / #{EXPONENT} | \. #{DECIMAL} #{EXPONENT}? /ox | |||
FLOAT_OR_INT = / #{DECIMAL} (?: #{FLOAT_SUFFIX} () )? /ox | |||
NUMERIC = / (?: (?=0) (?: #{OCTAL} | #{HEXADECIMAL} | #{BINARY} ) | #{FLOAT_OR_INT} ) /ox | |||
SYMBOL = / | |||
: | |||
(?: | |||
#{METHOD_NAME_EX} | |||
| #{PREFIX_VARIABLE} | |||
| ['"] | |||
) | |||
/ox | |||
METHOD_NAME_OR_SYMBOL = / #{METHOD_NAME_EX} | #{SYMBOL} /ox | |||
SIMPLE_ESCAPE = / | |||
[abefnrstv] | |||
| [0-7]{1,3} | |||
| x[0-9A-Fa-f]{1,2} | |||
| .? | |||
/mx | |||
CONTROL_META_ESCAPE = / | |||
(?: M-|C-|c ) | |||
(?: \\ (?: M-|C-|c ) )* | |||
(?: [^\\] | \\ #{SIMPLE_ESCAPE} )? | |||
/mox | |||
ESCAPE = / | |||
#{CONTROL_META_ESCAPE} | #{SIMPLE_ESCAPE} | |||
/mox | |||
CHARACTER = / | |||
\? | |||
(?: | |||
[^\s\\] | |||
| \\ #{ESCAPE} | |||
) | |||
/mox | |||
# NOTE: This is not completely correct, but | |||
# nobody needs heredoc delimiters ending with \n. | |||
# Also, delimiters starting with numbers are allowed. | |||
# but they are more often than not a false positive. | |||
HEREDOC_OPEN = / | |||
<< (-)? # $1 = float | |||
(?: | |||
( #{IDENT} ) # $2 = delim | |||
| | |||
( ["'`\/] ) # $3 = quote, type | |||
( [^\n]*? ) \3 # $4 = delim | |||
) | |||
/mx | |||
RUBYDOC = / | |||
=begin (?!\S) | |||
.*? | |||
(?: \Z | ^=end (?!\S) [^\n]* ) | |||
/mx | |||
DATA = / | |||
__END__$ | |||
.*? | |||
(?: \Z | (?=^\#CODE) ) | |||
/mx | |||
# Checks for a valid value to follow. This enables | |||
# value_expected in method calls without parentheses. | |||
VALUE_FOLLOWS = / | |||
(?>[ \t\f\v]+) | |||
(?: | |||
[%\/][^\s=] | |||
| <<-?\S | |||
| [-+] \d | |||
| #{CHARACTER} | |||
) | |||
/x | |||
KEYWORDS_EXPECTING_VALUE = WordList.new.add(%w[ | |||
and end in or unless begin | |||
defined? ensure redo super until | |||
break do next rescue then | |||
when case else for retry | |||
while elsif if not return | |||
yield | |||
]) | |||
RUBYDOC_OR_DATA = / #{RUBYDOC} | #{DATA} /xo | |||
RDOC_DATA_START = / ^=begin (?!\S) | ^__END__$ /x | |||
FANCY_START_CORRECT = / % ( [qQwWxsr] | (?![a-zA-Z0-9]) ) ([^a-zA-Z0-9]) /mx | |||
FancyStringType = { | |||
'q' => [:string, false], | |||
'Q' => [:string, true], | |||
'r' => [:regexp, true], | |||
's' => [:symbol, false], | |||
'x' => [:shell, true] | |||
} | |||
FancyStringType['w'] = FancyStringType['q'] | |||
FancyStringType['W'] = FancyStringType[''] = FancyStringType['Q'] | |||
class StringState < Struct.new :type, :interpreted, :delim, :heredoc, | |||
:paren, :paren_depth, :pattern, :next_state | |||
CLOSING_PAREN = Hash[ *%w[ | |||
( ) | |||
[ ] | |||
< > | |||
{ } | |||
] ] | |||
CLOSING_PAREN.each { |k,v| k.freeze; v.freeze } # debug, if I try to change it with << | |||
OPENING_PAREN = CLOSING_PAREN.invert | |||
STRING_PATTERN = Hash.new do |h, k| | |||
delim, interpreted = *k | |||
delim_pattern = Regexp.escape(delim.dup) # dup: workaround for old Ruby | |||
if closing_paren = CLOSING_PAREN[delim] | |||
delim_pattern = delim_pattern[0..-1] if defined? JRUBY_VERSION # JRuby fix | |||
delim_pattern << Regexp.escape(closing_paren) | |||
end | |||
delim_pattern << '\\\\' unless delim == '\\' | |||
special_escapes = | |||
case interpreted | |||
when :regexp_symbols | |||
'| ' + REGEXP_SYMBOLS.source | |||
when :words | |||
'| \s' | |||
end | |||
h[k] = | |||
if interpreted and not delim == '#' | |||
/ (?= [#{delim_pattern}] | \# [{$@] #{special_escapes} ) /mx | |||
else | |||
/ (?= [#{delim_pattern}] #{special_escapes} ) /mx | |||
end | |||
end | |||
HEREDOC_PATTERN = Hash.new do |h, k| | |||
delim, interpreted, indented = *k | |||
delim_pattern = Regexp.escape(delim.dup) # dup: workaround for old Ruby | |||
delim_pattern = / \n #{ '(?>[\ \t]*)' if indented } #{ Regexp.new delim_pattern } $ /x | |||
h[k] = | |||
if interpreted | |||
/ (?= #{delim_pattern}() | \\ | \# [{$@] ) /mx # $1 set == end of heredoc | |||
else | |||
/ (?= #{delim_pattern}() | \\ ) /mx | |||
end | |||
end | |||
def initialize kind, interpreted, delim, heredoc = false | |||
if heredoc | |||
pattern = HEREDOC_PATTERN[ [delim, interpreted, heredoc == :indented] ] | |||
delim = nil | |||
else | |||
pattern = STRING_PATTERN[ [delim, interpreted] ] | |||
if paren = CLOSING_PAREN[delim] | |||
delim, paren = paren, delim | |||
paren_depth = 1 | |||
end | |||
end | |||
super kind, interpreted, delim, heredoc, paren, paren_depth, pattern, :initial | |||
end | |||
end unless defined? StringState | |||
end | |||
end | |||
end |
@@ -1,145 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
# Scheme scanner for CodeRay (by closure). | |||
# Thanks to murphy for putting CodeRay into public. | |||
class Scheme < Scanner | |||
# TODO: function defs | |||
# TODO: built-in functions | |||
register_for :scheme | |||
file_extension 'scm' | |||
CORE_FORMS = %w[ | |||
lambda let let* letrec syntax-case define-syntax let-syntax | |||
letrec-syntax begin define quote if or and cond case do delay | |||
quasiquote set! cons force call-with-current-continuation call/cc | |||
] | |||
IDENT_KIND = CaseIgnoringWordList.new(:ident). | |||
add(CORE_FORMS, :reserved) | |||
#IDENTIFIER_INITIAL = /[a-z!@\$%&\*\/\:<=>\?~_\^]/i | |||
#IDENTIFIER_SUBSEQUENT = /#{IDENTIFIER_INITIAL}|\d|\.|\+|-/ | |||
#IDENTIFIER = /#{IDENTIFIER_INITIAL}#{IDENTIFIER_SUBSEQUENT}*|\+|-|\.{3}/ | |||
IDENTIFIER = /[a-zA-Z!@$%&*\/:<=>?~_^][\w!@$%&*\/:<=>?~^.+\-]*|[+-]|\.\.\./ | |||
DIGIT = /\d/ | |||
DIGIT10 = DIGIT | |||
DIGIT16 = /[0-9a-f]/i | |||
DIGIT8 = /[0-7]/ | |||
DIGIT2 = /[01]/ | |||
RADIX16 = /\#x/i | |||
RADIX8 = /\#o/i | |||
RADIX2 = /\#b/i | |||
RADIX10 = /\#d/i | |||
EXACTNESS = /#i|#e/i | |||
SIGN = /[\+-]?/ | |||
EXP_MARK = /[esfdl]/i | |||
EXP = /#{EXP_MARK}#{SIGN}#{DIGIT}+/ | |||
SUFFIX = /#{EXP}?/ | |||
PREFIX10 = /#{RADIX10}?#{EXACTNESS}?|#{EXACTNESS}?#{RADIX10}?/ | |||
PREFIX16 = /#{RADIX16}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX16}/ | |||
PREFIX8 = /#{RADIX8}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX8}/ | |||
PREFIX2 = /#{RADIX2}#{EXACTNESS}?|#{EXACTNESS}?#{RADIX2}/ | |||
UINT10 = /#{DIGIT10}+#*/ | |||
UINT16 = /#{DIGIT16}+#*/ | |||
UINT8 = /#{DIGIT8}+#*/ | |||
UINT2 = /#{DIGIT2}+#*/ | |||
DECIMAL = /#{DIGIT10}+#+\.#*#{SUFFIX}|#{DIGIT10}+\.#{DIGIT10}*#*#{SUFFIX}|\.#{DIGIT10}+#*#{SUFFIX}|#{UINT10}#{EXP}/ | |||
UREAL10 = /#{UINT10}\/#{UINT10}|#{DECIMAL}|#{UINT10}/ | |||
UREAL16 = /#{UINT16}\/#{UINT16}|#{UINT16}/ | |||
UREAL8 = /#{UINT8}\/#{UINT8}|#{UINT8}/ | |||
UREAL2 = /#{UINT2}\/#{UINT2}|#{UINT2}/ | |||
REAL10 = /#{SIGN}#{UREAL10}/ | |||
REAL16 = /#{SIGN}#{UREAL16}/ | |||
REAL8 = /#{SIGN}#{UREAL8}/ | |||
REAL2 = /#{SIGN}#{UREAL2}/ | |||
IMAG10 = /i|#{UREAL10}i/ | |||
IMAG16 = /i|#{UREAL16}i/ | |||
IMAG8 = /i|#{UREAL8}i/ | |||
IMAG2 = /i|#{UREAL2}i/ | |||
COMPLEX10 = /#{REAL10}@#{REAL10}|#{REAL10}\+#{IMAG10}|#{REAL10}-#{IMAG10}|\+#{IMAG10}|-#{IMAG10}|#{REAL10}/ | |||
COMPLEX16 = /#{REAL16}@#{REAL16}|#{REAL16}\+#{IMAG16}|#{REAL16}-#{IMAG16}|\+#{IMAG16}|-#{IMAG16}|#{REAL16}/ | |||
COMPLEX8 = /#{REAL8}@#{REAL8}|#{REAL8}\+#{IMAG8}|#{REAL8}-#{IMAG8}|\+#{IMAG8}|-#{IMAG8}|#{REAL8}/ | |||
COMPLEX2 = /#{REAL2}@#{REAL2}|#{REAL2}\+#{IMAG2}|#{REAL2}-#{IMAG2}|\+#{IMAG2}|-#{IMAG2}|#{REAL2}/ | |||
NUM10 = /#{PREFIX10}?#{COMPLEX10}/ | |||
NUM16 = /#{PREFIX16}#{COMPLEX16}/ | |||
NUM8 = /#{PREFIX8}#{COMPLEX8}/ | |||
NUM2 = /#{PREFIX2}#{COMPLEX2}/ | |||
NUM = /#{NUM10}|#{NUM16}|#{NUM8}|#{NUM2}/ | |||
private | |||
def scan_tokens tokens,options | |||
state = :initial | |||
ident_kind = IDENT_KIND | |||
until eos? | |||
kind = match = nil | |||
case state | |||
when :initial | |||
if scan(/ \s+ | \\\n /x) | |||
kind = :space | |||
elsif scan(/['\(\[\)\]]|#\(/) | |||
kind = :operator_fat | |||
elsif scan(/;.*/) | |||
kind = :comment | |||
elsif scan(/#\\(?:newline|space|.?)/) | |||
kind = :char | |||
elsif scan(/#[ft]/) | |||
kind = :pre_constant | |||
elsif scan(/#{IDENTIFIER}/o) | |||
kind = ident_kind[matched] | |||
elsif scan(/\./) | |||
kind = :operator | |||
elsif scan(/"/) | |||
tokens << [:open, :string] | |||
state = :string | |||
tokens << ['"', :delimiter] | |||
next | |||
elsif scan(/#{NUM}/o) and not matched.empty? | |||
kind = :integer | |||
elsif getch | |||
kind = :error | |||
end | |||
when :string | |||
if scan(/[^"\\]+/) or scan(/\\.?/) | |||
kind = :content | |||
elsif scan(/"/) | |||
tokens << ['"', :delimiter] | |||
tokens << [:close, :string] | |||
state = :initial | |||
next | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), | |||
tokens, state | |||
end | |||
else | |||
raise "else case reached" | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens, state unless match | |||
tokens << [match, kind] | |||
end # until eos | |||
if state == :string | |||
tokens << [:close, :string] | |||
end | |||
tokens | |||
end #scan_tokens | |||
end #class | |||
end #module scanners | |||
end #module coderay |
@@ -1,162 +0,0 @@ | |||
module CodeRay module Scanners | |||
# by Josh Goebel | |||
class SQL < Scanner | |||
register_for :sql | |||
RESERVED_WORDS = %w( | |||
create database table index trigger drop primary key set select | |||
insert update delete replace into | |||
on from values before and or if exists case when | |||
then else as group order by avg where | |||
join inner outer union engine not | |||
like end using collate show columns begin | |||
) | |||
PREDEFINED_TYPES = %w( | |||
char varchar enum binary text tinytext mediumtext | |||
longtext blob tinyblob mediumblob longblob timestamp | |||
date time datetime year double decimal float int | |||
integer tinyint mediumint bigint smallint unsigned bit | |||
bool boolean hex bin oct | |||
) | |||
PREDEFINED_FUNCTIONS = %w( sum cast abs pi count min max avg ) | |||
DIRECTIVES = %w( auto_increment unique default charset ) | |||
PREDEFINED_CONSTANTS = %w( null true false ) | |||
IDENT_KIND = CaseIgnoringWordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_TYPES, :pre_type). | |||
add(PREDEFINED_CONSTANTS, :pre_constant). | |||
add(PREDEFINED_FUNCTIONS, :predefined). | |||
add(DIRECTIVES, :directive) | |||
ESCAPE = / [rbfntv\n\\\/'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} | . /mx | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x | |||
STRING_PREFIXES = /[xnb]|_\w+/i | |||
def scan_tokens tokens, options | |||
state = :initial | |||
string_type = nil | |||
string_content = '' | |||
until eos? | |||
kind = nil | |||
match = nil | |||
if state == :initial | |||
if scan(/ \s+ | \\\n /x) | |||
kind = :space | |||
elsif scan(/(?:--\s?|#).*/) | |||
kind = :comment | |||
elsif scan(%r! /\* (?: .*? \*/ | .* ) !mx) | |||
kind = :comment | |||
elsif scan(/ [-+*\/=<>;,!&^|()\[\]{}~%] | \.(?!\d) /x) | |||
kind = :operator | |||
elsif scan(/(#{STRING_PREFIXES})?([`"'])/o) | |||
prefix = self[1] | |||
string_type = self[2] | |||
tokens << [:open, :string] | |||
tokens << [prefix, :modifier] if prefix | |||
match = string_type | |||
state = :string | |||
kind = :delimiter | |||
elsif match = scan(/ @? [A-Za-z_][A-Za-z_0-9]* /x) | |||
kind = match[0] == ?@ ? :variable : IDENT_KIND[match.downcase] | |||
elsif scan(/0[xX][0-9A-Fa-f]+/) | |||
kind = :hex | |||
elsif scan(/0[0-7]+(?![89.eEfF])/) | |||
kind = :oct | |||
elsif scan(/(?>\d+)(?![.eEfF])/) | |||
kind = :integer | |||
elsif scan(/\d[fF]|\d*\.\d+(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+/) | |||
kind = :float | |||
else | |||
getch | |||
kind = :error | |||
end | |||
elsif state == :string | |||
if match = scan(/[^\\"'`]+/) | |||
string_content << match | |||
next | |||
elsif match = scan(/["'`]/) | |||
if string_type == match | |||
if peek(1) == string_type # doubling means escape | |||
string_content << string_type << getch | |||
next | |||
end | |||
unless string_content.empty? | |||
tokens << [string_content, :content] | |||
string_content = '' | |||
end | |||
tokens << [matched, :delimiter] | |||
tokens << [:close, :string] | |||
state = :initial | |||
string_type = nil | |||
next | |||
else | |||
string_content << match | |||
end | |||
next | |||
elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
unless string_content.empty? | |||
tokens << [string_content, :content] | |||
string_content = '' | |||
end | |||
kind = :char | |||
elsif match = scan(/ \\ . /mox) | |||
string_content << match | |||
next | |||
elsif scan(/ \\ | $ /x) | |||
unless string_content.empty? | |||
tokens << [string_content, :content] | |||
string_content = '' | |||
end | |||
kind = :error | |||
state = :initial | |||
else | |||
raise "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise 'else-case reached', tokens | |||
end | |||
match ||= matched | |||
unless kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens, state | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
tokens | |||
end | |||
end | |||
end end |
@@ -1,17 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
load :html | |||
# XML Scanner | |||
# | |||
# Currently this is the same scanner as Scanners::HTML. | |||
class XML < HTML | |||
register_for :xml | |||
file_extension 'xml' | |||
end | |||
end | |||
end |
@@ -1,140 +0,0 @@ | |||
module CodeRay | |||
module Scanners | |||
# YAML Scanner | |||
# | |||
# Based on the YAML scanner from Syntax by Jamis Buck. | |||
class YAML < Scanner | |||
register_for :yaml | |||
file_extension 'yml' | |||
KINDS_NOT_LOC = :all | |||
def scan_tokens tokens, options | |||
value_expected = nil | |||
state = :initial | |||
key_indent = indent = 0 | |||
until eos? | |||
kind = nil | |||
match = nil | |||
key_indent = nil if bol? | |||
if match = scan(/ +[\t ]*/) | |||
kind = :space | |||
elsif match = scan(/\n+/) | |||
kind = :space | |||
state = :initial if match.index(?\n) | |||
elsif match = scan(/#.*/) | |||
kind = :comment | |||
elsif bol? and case | |||
when match = scan(/---|\.\.\./) | |||
tokens << [:open, :head] | |||
tokens << [match, :head] | |||
tokens << [:close, :head] | |||
next | |||
when match = scan(/%.*/) | |||
tokens << [match, :doctype] | |||
next | |||
end | |||
elsif state == :value and case | |||
when !check(/(?:"[^"]*")(?=: |:$)/) && scan(/"/) | |||
tokens << [:open, :string] | |||
tokens << [matched, :delimiter] | |||
tokens << [matched, :content] if scan(/ [^"\\]* (?: \\. [^"\\]* )* /mx) | |||
tokens << [matched, :delimiter] if scan(/"/) | |||
tokens << [:close, :string] | |||
next | |||
when match = scan(/[|>][-+]?/) | |||
tokens << [:open, :string] | |||
tokens << [match, :delimiter] | |||
string_indent = key_indent || column(pos - match.size - 1) | |||
tokens << [matched, :content] if scan(/(?:\n+ {#{string_indent + 1}}.*)+/) | |||
tokens << [:close, :string] | |||
next | |||
when match = scan(/(?![!"*&]).+?(?=$|\s+#)/) | |||
tokens << [match, :string] | |||
string_indent = key_indent || column(pos - match.size - 1) | |||
tokens << [matched, :string] if scan(/(?:\n+ {#{string_indent + 1}}.*)+/) | |||
next | |||
end | |||
elsif case | |||
when match = scan(/[-:](?= |$)/) | |||
state = :value if state == :colon && (match == ':' || match == '-') | |||
state = :value if state == :initial && match == '-' | |||
kind = :operator | |||
when match = scan(/[,{}\[\]]/) | |||
kind = :operator | |||
when state == :initial && match = scan(/[\w.() ]*\S(?=: |:$)/) | |||
kind = :key | |||
key_indent = column(pos - match.size - 1) | |||
# tokens << [key_indent.inspect, :debug] | |||
state = :colon | |||
when match = scan(/(?:"[^"\n]*"|'[^'\n]*')(?=: |:$)/) | |||
tokens << [:open, :key] | |||
tokens << [match[0,1], :delimiter] | |||
tokens << [match[1..-2], :content] | |||
tokens << [match[-1,1], :delimiter] | |||
tokens << [:close, :key] | |||
key_indent = column(pos - match.size - 1) | |||
# tokens << [key_indent.inspect, :debug] | |||
state = :colon | |||
next | |||
when scan(/(![\w\/]+)(:([\w:]+))?/) | |||
tokens << [self[1], :type] | |||
if self[2] | |||
tokens << [':', :operator] | |||
tokens << [self[3], :class] | |||
end | |||
next | |||
when scan(/&\S+/) | |||
kind = :variable | |||
when scan(/\*\w+/) | |||
kind = :global_variable | |||
when scan(/<</) | |||
kind = :class_variable | |||
when scan(/\d\d:\d\d:\d\d/) | |||
kind = :oct | |||
when scan(/\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d+)? [-+]\d\d:\d\d/) | |||
kind = :oct | |||
when scan(/:\w+/) | |||
kind = :symbol | |||
when scan(/[^:\s]+(:(?! |$)[^:\s]*)* .*/) | |||
kind = :error | |||
when scan(/[^:\s]+(:(?! |$)[^:\s]*)*/) | |||
kind = :error | |||
end | |||
else | |||
getch | |||
kind = :error | |||
end | |||
match ||= matched | |||
if $CODERAY_DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens, state | |||
end | |||
raise_inspect 'Empty token', tokens, state unless match | |||
tokens << [match, kind] | |||
end | |||
tokens | |||
end | |||
end | |||
end | |||
end |
@@ -1,152 +0,0 @@ | |||
module CodeRay | |||
module Styles | |||
class Cycnus < Style | |||
register_for :cycnus | |||
code_background = '#f8f8f8' | |||
numbers_background = '#def' | |||
border_color = 'silver' | |||
normal_color = '#000' | |||
CSS_MAIN_STYLES = <<-MAIN | |||
.CodeRay { | |||
background-color: #{code_background}; | |||
border: 1px solid #{border_color}; | |||
font-family: 'Courier New', 'Terminal', monospace; | |||
color: #{normal_color}; | |||
} | |||
.CodeRay pre { margin: 0px } | |||
div.CodeRay { } | |||
span.CodeRay { white-space: pre; border: 0px; padding: 2px } | |||
table.CodeRay { border-collapse: collapse; width: 100%; padding: 2px } | |||
table.CodeRay td { padding: 2px 4px; vertical-align: top } | |||
.CodeRay .line_numbers, .CodeRay .no { | |||
background-color: #{numbers_background}; | |||
color: gray; | |||
text-align: right; | |||
} | |||
.CodeRay .line_numbers tt { font-weight: bold } | |||
.CodeRay .line_numbers .highlighted { color: red } | |||
.CodeRay .line { display: block; float: left; width: 100%; } | |||
.CodeRay .no { padding: 0px 4px } | |||
.CodeRay .code { width: 100% } | |||
ol.CodeRay { font-size: 10pt } | |||
ol.CodeRay li { white-space: pre } | |||
.CodeRay .code pre { overflow: auto } | |||
MAIN | |||
TOKEN_COLORS = <<-'TOKENS' | |||
.debug { color:white ! important; background:blue ! important; } | |||
.af { color:#00C } | |||
.an { color:#007 } | |||
.at { color:#f08 } | |||
.av { color:#700 } | |||
.aw { color:#C00 } | |||
.bi { color:#509; font-weight:bold } | |||
.c { color:#888; } | |||
.ch { color:#04D } | |||
.ch .k { color:#04D } | |||
.ch .dl { color:#039 } | |||
.cl { color:#B06; font-weight:bold } | |||
.cm { color:#A08; font-weight:bold } | |||
.co { color:#036; font-weight:bold } | |||
.cr { color:#0A0 } | |||
.cv { color:#369 } | |||
.de { color:#B0B; } | |||
.df { color:#099; font-weight:bold } | |||
.di { color:#088; font-weight:bold } | |||
.dl { color:black } | |||
.do { color:#970 } | |||
.dt { color:#34b } | |||
.ds { color:#D42; font-weight:bold } | |||
.e { color:#666; font-weight:bold } | |||
.en { color:#800; font-weight:bold } | |||
.er { color:#F00; background-color:#FAA } | |||
.ex { color:#C00; font-weight:bold } | |||
.fl { color:#60E; font-weight:bold } | |||
.fu { color:#06B; font-weight:bold } | |||
.gv { color:#d70; font-weight:bold } | |||
.hx { color:#058; font-weight:bold } | |||
.i { color:#00D; font-weight:bold } | |||
.ic { color:#B44; font-weight:bold } | |||
.il { background: #ddd; color: black } | |||
.il .il { background: #ccc } | |||
.il .il .il { background: #bbb } | |||
.il .idl { background: #ddd; font-weight: bold; color: #666 } | |||
.idl { background-color: #bbb; font-weight: bold; color: #666; } | |||
.im { color:#f00; } | |||
.in { color:#B2B; font-weight:bold } | |||
.iv { color:#33B } | |||
.la { color:#970; font-weight:bold } | |||
.lv { color:#963 } | |||
.oc { color:#40E; font-weight:bold } | |||
.of { color:#000; font-weight:bold } | |||
.op { } | |||
.pc { color:#038; font-weight:bold } | |||
.pd { color:#369; font-weight:bold } | |||
.pp { color:#579; } | |||
.ps { color:#00C; font-weight:bold } | |||
.pt { color:#074; font-weight:bold } | |||
.r, .kw { color:#080; font-weight:bold } | |||
.ke { color: #808; } | |||
.ke .dl { color: #606; } | |||
.ke .ch { color: #80f; } | |||
.vl { color: #088; } | |||
.rx { background-color:#fff0ff } | |||
.rx .k { color:#808 } | |||
.rx .dl { color:#404 } | |||
.rx .mod { color:#C2C } | |||
.rx .fu { color:#404; font-weight: bold } | |||
.s { background-color:#fff0f0; color: #D20; } | |||
.s .s { background-color:#ffe0e0 } | |||
.s .s .s { background-color:#ffd0d0 } | |||
.s .k { } | |||
.s .ch { color: #b0b; } | |||
.s .dl { color: #710; } | |||
.sh { background-color:#f0fff0; color:#2B2 } | |||
.sh .k { } | |||
.sh .dl { color:#161 } | |||
.sy { color:#A60 } | |||
.sy .k { color:#A60 } | |||
.sy .dl { color:#630 } | |||
.ta { color:#070 } | |||
.tf { color:#070; font-weight:bold } | |||
.ts { color:#D70; font-weight:bold } | |||
.ty { color:#339; font-weight:bold } | |||
.v { color:#036 } | |||
.xt { color:#444 } | |||
.ins { background: #afa; } | |||
.del { background: #faa; } | |||
.chg { color: #aaf; background: #007; } | |||
.head { color: #f8f; background: #505 } | |||
.ins .ins { color: #080; font-weight:bold } | |||
.del .del { color: #800; font-weight:bold } | |||
.chg .chg { color: #66f; } | |||
.head .head { color: #f4f; } | |||
TOKENS | |||
end | |||
end | |||
end |
@@ -1,134 +0,0 @@ | |||
module CodeRay | |||
module Styles | |||
class Murphy < Style | |||
register_for :murphy | |||
code_background = '#001129' | |||
numbers_background = code_background | |||
border_color = 'silver' | |||
normal_color = '#C0C0C0' | |||
CSS_MAIN_STYLES = <<-MAIN | |||
.CodeRay { | |||
background-color: #{code_background}; | |||
border: 1px solid #{border_color}; | |||
font-family: 'Courier New', 'Terminal', monospace; | |||
color: #{normal_color}; | |||
} | |||
.CodeRay pre { margin: 0px; } | |||
div.CodeRay { } | |||
span.CodeRay { white-space: pre; border: 0px; padding: 2px; } | |||
table.CodeRay { border-collapse: collapse; width: 100%; padding: 2px; } | |||
table.CodeRay td { padding: 2px 4px; vertical-align: top; } | |||
.CodeRay .line_numbers, .CodeRay .no { | |||
background-color: #{numbers_background}; | |||
color: gray; | |||
text-align: right; | |||
} | |||
.CodeRay .line_numbers tt { font-weight: bold; } | |||
.CodeRay .line_numbers .highlighted { color: red } | |||
.CodeRay .line { display: block; float: left; width: 100%; } | |||
.CodeRay .no { padding: 0px 4px; } | |||
.CodeRay .code { width: 100%; } | |||
ol.CodeRay { font-size: 10pt; } | |||
ol.CodeRay li { white-space: pre; } | |||
.CodeRay .code pre { overflow: auto; } | |||
MAIN | |||
TOKEN_COLORS = <<-'TOKENS' | |||
.af { color:#00C; } | |||
.an { color:#007; } | |||
.av { color:#700; } | |||
.aw { color:#C00; } | |||
.bi { color:#509; font-weight:bold; } | |||
.c { color:#555; background-color: black; } | |||
.ch { color:#88F; } | |||
.ch .k { color:#04D; } | |||
.ch .dl { color:#039; } | |||
.cl { color:#e9e; font-weight:bold; } | |||
.co { color:#5ED; font-weight:bold; } | |||
.cr { color:#0A0; } | |||
.cv { color:#ccf; } | |||
.df { color:#099; font-weight:bold; } | |||
.di { color:#088; font-weight:bold; } | |||
.dl { color:black; } | |||
.do { color:#970; } | |||
.ds { color:#D42; font-weight:bold; } | |||
.e { color:#666; font-weight:bold; } | |||
.er { color:#F00; background-color:#FAA; } | |||
.ex { color:#F00; font-weight:bold; } | |||
.fl { color:#60E; font-weight:bold; } | |||
.fu { color:#5ed; font-weight:bold; } | |||
.gv { color:#f84; } | |||
.hx { color:#058; font-weight:bold; } | |||
.i { color:#66f; font-weight:bold; } | |||
.ic { color:#B44; font-weight:bold; } | |||
.il { } | |||
.in { color:#B2B; font-weight:bold; } | |||
.iv { color:#aaf; } | |||
.la { color:#970; font-weight:bold; } | |||
.lv { color:#963; } | |||
.oc { color:#40E; font-weight:bold; } | |||
.of { color:#000; font-weight:bold; } | |||
.op { } | |||
.pc { color:#08f; font-weight:bold; } | |||
.pd { color:#369; font-weight:bold; } | |||
.pp { color:#579; } | |||
.pt { color:#66f; font-weight:bold; } | |||
.r { color:#5de; font-weight:bold; } | |||
.r, .kw { color:#5de; font-weight:bold } | |||
.ke { color: #808; } | |||
.rx { background-color:#221133; } | |||
.rx .k { color:#f8f; } | |||
.rx .dl { color:#f0f; } | |||
.rx .mod { color:#f0b; } | |||
.rx .fu { color:#404; font-weight: bold; } | |||
.s { background-color:#331122; } | |||
.s .s { background-color:#ffe0e0; } | |||
.s .s .s { background-color:#ffd0d0; } | |||
.s .k { color:#F88; } | |||
.s .dl { color:#f55; } | |||
.sh { background-color:#f0fff0; } | |||
.sh .k { color:#2B2; } | |||
.sh .dl { color:#161; } | |||
.sy { color:#Fc8; } | |||
.sy .k { color:#Fc8; } | |||
.sy .dl { color:#F84; } | |||
.ta { color:#070; } | |||
.tf { color:#070; font-weight:bold; } | |||
.ts { color:#D70; font-weight:bold; } | |||
.ty { color:#339; font-weight:bold; } | |||
.v { color:#036; } | |||
.xt { color:#444; } | |||
.ins { background: #afa; } | |||
.del { background: #faa; } | |||
.chg { color: #aaf; background: #007; } | |||
.head { color: #f8f; background: #505 } | |||
.ins .ins { color: #080; font-weight:bold } | |||
.del .del { color: #800; font-weight:bold } | |||
.chg .chg { color: #66f; } | |||
.head .head { color: #f4f; } | |||
TOKENS | |||
end | |||
end | |||
end |
@@ -1,86 +0,0 @@ | |||
module CodeRay | |||
class Tokens | |||
ClassOfKind = Hash.new do |h, k| | |||
h[k] = k.to_s | |||
end | |||
ClassOfKind.update with = { | |||
:annotation => 'at', | |||
:attribute_name => 'an', | |||
:attribute_name_fat => 'af', | |||
:attribute_value => 'av', | |||
:attribute_value_fat => 'aw', | |||
:bin => 'bi', | |||
:char => 'ch', | |||
:class => 'cl', | |||
:class_variable => 'cv', | |||
:color => 'cr', | |||
:comment => 'c', | |||
:complex => 'cm', | |||
:constant => 'co', | |||
:content => 'k', | |||
:decorator => 'de', | |||
:definition => 'df', | |||
:delimiter => 'dl', | |||
:directive => 'di', | |||
:doc => 'do', | |||
:doctype => 'dt', | |||
:doc_string => 'ds', | |||
:entity => 'en', | |||
:error => 'er', | |||
:escape => 'e', | |||
:exception => 'ex', | |||
:float => 'fl', | |||
:function => 'fu', | |||
:global_variable => 'gv', | |||
:hex => 'hx', | |||
:imaginary => 'cm', | |||
:important => 'im', | |||
:include => 'ic', | |||
:inline => 'il', | |||
:inline_delimiter => 'idl', | |||
:instance_variable => 'iv', | |||
:integer => 'i', | |||
:interpreted => 'in', | |||
:keyword => 'kw', | |||
:key => 'ke', | |||
:label => 'la', | |||
:local_variable => 'lv', | |||
:modifier => 'mod', | |||
:oct => 'oc', | |||
:operator_fat => 'of', | |||
:pre_constant => 'pc', | |||
:pre_type => 'pt', | |||
:predefined => 'pd', | |||
:preprocessor => 'pp', | |||
:pseudo_class => 'ps', | |||
:regexp => 'rx', | |||
:reserved => 'r', | |||
:shell => 'sh', | |||
:string => 's', | |||
:symbol => 'sy', | |||
:tag => 'ta', | |||
:tag_fat => 'tf', | |||
:tag_special => 'ts', | |||
:type => 'ty', | |||
:variable => 'v', | |||
:value => 'vl', | |||
:xml_text => 'xt', | |||
:insert => 'ins', | |||
:delete => 'del', | |||
:change => 'chg', | |||
:head => 'head', | |||
:ident => :NO_HIGHLIGHT, # 'id' | |||
#:operator => 'op', | |||
:operator => :NO_HIGHLIGHT, # 'op' | |||
:space => :NO_HIGHLIGHT, # 'sp' | |||
:plain => :NO_HIGHLIGHT, | |||
} | |||
ClassOfKind[:method] = ClassOfKind[:function] | |||
ClassOfKind[:open] = ClassOfKind[:close] = ClassOfKind[:delimiter] | |||
ClassOfKind[:nesting_delimiter] = ClassOfKind[:delimiter] | |||
ClassOfKind[:escape] = ClassOfKind[:delimiter] | |||
#ClassOfKind.default = ClassOfKind[:error] or raise 'no class found for :error!' | |||
end | |||
end |
@@ -1,390 +0,0 @@ | |||
module CodeRay | |||
# = Tokens | |||
# | |||
# The Tokens class represents a list of tokens returnd from | |||
# a Scanner. | |||
# | |||
# A token is not a special object, just a two-element Array | |||
# consisting of | |||
# * the _token_ _text_ (the original source of the token in a String) or | |||
# a _token_ _action_ (:open, :close, :begin_line, :end_line) | |||
# * the _token_ _kind_ (a Symbol representing the type of the token) | |||
# | |||
# A token looks like this: | |||
# | |||
# ['# It looks like this', :comment] | |||
# ['3.1415926', :float] | |||
# ['$^', :error] | |||
# | |||
# Some scanners also yield sub-tokens, represented by special | |||
# token actions, namely :open and :close. | |||
# | |||
# The Ruby scanner, for example, splits "a string" into: | |||
# | |||
# [ | |||
# [:open, :string], | |||
# ['"', :delimiter], | |||
# ['a string', :content], | |||
# ['"', :delimiter], | |||
# [:close, :string] | |||
# ] | |||
# | |||
# Tokens is the interface between Scanners and Encoders: | |||
# The input is split and saved into a Tokens object. The Encoder | |||
# then builds the output from this object. | |||
# | |||
# Thus, the syntax below becomes clear: | |||
# | |||
# CodeRay.scan('price = 2.59', :ruby).html | |||
# # the Tokens object is here -------^ | |||
# | |||
# See how small it is? ;) | |||
# | |||
# Tokens gives you the power to handle pre-scanned code very easily: | |||
# You can convert it to a webpage, a YAML file, or dump it into a gzip'ed string | |||
# that you put in your DB. | |||
# | |||
# It also allows you to generate tokens directly (without using a scanner), | |||
# to load them from a file, and still use any Encoder that CodeRay provides. | |||
# | |||
# Tokens' subclass TokenStream allows streaming to save memory. | |||
class Tokens < Array | |||
# The Scanner instance that created the tokens. | |||
attr_accessor :scanner | |||
# Whether the object is a TokenStream. | |||
# | |||
# Returns false. | |||
def stream? | |||
false | |||
end | |||
# Iterates over all tokens. | |||
# | |||
# If a filter is given, only tokens of that kind are yielded. | |||
def each kind_filter = nil, &block | |||
unless kind_filter | |||
super(&block) | |||
else | |||
super() do |text, kind| | |||
next unless kind == kind_filter | |||
yield text, kind | |||
end | |||
end | |||
end | |||
# Iterates over all text tokens. | |||
# Range tokens like [:open, :string] are left out. | |||
# | |||
# Example: | |||
# tokens.each_text_token { |text, kind| text.replace html_escape(text) } | |||
def each_text_token | |||
each do |text, kind| | |||
next unless text.is_a? ::String | |||
yield text, kind | |||
end | |||
end | |||
# Encode the tokens using encoder. | |||
# | |||
# encoder can be | |||
# * a symbol like :html oder :statistic | |||
# * an Encoder class | |||
# * an Encoder object | |||
# | |||
# options are passed to the encoder. | |||
def encode encoder, options = {} | |||
unless encoder.is_a? Encoders::Encoder | |||
unless encoder.is_a? Class | |||
encoder_class = Encoders[encoder] | |||
end | |||
encoder = encoder_class.new options | |||
end | |||
encoder.encode_tokens self, options | |||
end | |||
# Turn into a string using Encoders::Text. | |||
# | |||
# +options+ are passed to the encoder if given. | |||
def to_s options = {} | |||
encode :text, options | |||
end | |||
# Redirects unknown methods to encoder calls. | |||
# | |||
# For example, if you call +tokens.html+, the HTML encoder | |||
# is used to highlight the tokens. | |||
def method_missing meth, options = {} | |||
Encoders[meth].new(options).encode_tokens self | |||
end | |||
# Returns the tokens compressed by joining consecutive | |||
# tokens of the same kind. | |||
# | |||
# This can not be undone, but should yield the same output | |||
# in most Encoders. It basically makes the output smaller. | |||
# | |||
# Combined with dump, it saves space for the cost of time. | |||
# | |||
# If the scanner is written carefully, this is not required - | |||
# for example, consecutive //-comment lines could already be | |||
# joined in one comment token by the Scanner. | |||
def optimize | |||
last_kind = last_text = nil | |||
new = self.class.new | |||
for text, kind in self | |||
if text.is_a? String | |||
if kind == last_kind | |||
last_text << text | |||
else | |||
new << [last_text, last_kind] if last_kind | |||
last_text = text | |||
last_kind = kind | |||
end | |||
else | |||
new << [last_text, last_kind] if last_kind | |||
last_kind = last_text = nil | |||
new << [text, kind] | |||
end | |||
end | |||
new << [last_text, last_kind] if last_kind | |||
new | |||
end | |||
# Compact the object itself; see optimize. | |||
def optimize! | |||
replace optimize | |||
end | |||
# Ensure that all :open tokens have a correspondent :close one. | |||
# | |||
# TODO: Test this! | |||
def fix | |||
tokens = self.class.new | |||
# Check token nesting using a stack of kinds. | |||
opened = [] | |||
for type, kind in self | |||
case type | |||
when :open | |||
opened.push [:close, kind] | |||
when :begin_line | |||
opened.push [:end_line, kind] | |||
when :close, :end_line | |||
expected = opened.pop | |||
if [type, kind] != expected | |||
# Unexpected :close; decide what to do based on the kind: | |||
# - token was never opened: delete the :close (just skip it) | |||
next unless opened.rindex expected | |||
# - token was opened earlier: also close tokens in between | |||
tokens << token until (token = opened.pop) == expected | |||
end | |||
end | |||
tokens << [type, kind] | |||
end | |||
# Close remaining opened tokens | |||
tokens << token while token = opened.pop | |||
tokens | |||
end | |||
def fix! | |||
replace fix | |||
end | |||
# TODO: Scanner#split_into_lines | |||
# | |||
# Makes sure that: | |||
# - newlines are single tokens | |||
# (which means all other token are single-line) | |||
# - there are no open tokens at the end the line | |||
# | |||
# This makes it simple for encoders that work line-oriented, | |||
# like HTML with list-style numeration. | |||
def split_into_lines | |||
raise NotImplementedError | |||
end | |||
def split_into_lines! | |||
replace split_into_lines | |||
end | |||
# Dumps the object into a String that can be saved | |||
# in files or databases. | |||
# | |||
# The dump is created with Marshal.dump; | |||
# In addition, it is gzipped using GZip.gzip. | |||
# | |||
# The returned String object includes Undumping | |||
# so it has an #undump method. See Tokens.load. | |||
# | |||
# You can configure the level of compression, | |||
# but the default value 7 should be what you want | |||
# in most cases as it is a good compromise between | |||
# speed and compression rate. | |||
# | |||
# See GZip module. | |||
def dump gzip_level = 7 | |||
require 'coderay/helpers/gzip_simple' | |||
dump = Marshal.dump self | |||
dump = dump.gzip gzip_level | |||
dump.extend Undumping | |||
end | |||
# The total size of the tokens. | |||
# Should be equal to the input size before | |||
# scanning. | |||
def text_size | |||
size = 0 | |||
each_text_token do |t, k| | |||
size + t.size | |||
end | |||
size | |||
end | |||
# Return all text tokens joined into a single string. | |||
def text | |||
map { |t, k| t if t.is_a? ::String }.join | |||
end | |||
# Include this module to give an object an #undump | |||
# method. | |||
# | |||
# The string returned by Tokens.dump includes Undumping. | |||
module Undumping | |||
# Calls Tokens.load with itself. | |||
def undump | |||
Tokens.load self | |||
end | |||
end | |||
# Undump the object using Marshal.load, then | |||
# unzip it using GZip.gunzip. | |||
# | |||
# The result is commonly a Tokens object, but | |||
# this is not guaranteed. | |||
def Tokens.load dump | |||
require 'coderay/helpers/gzip_simple' | |||
dump = dump.gunzip | |||
@dump = Marshal.load dump | |||
end | |||
end | |||
# = TokenStream | |||
# | |||
# The TokenStream class is a fake Array without elements. | |||
# | |||
# It redirects the method << to a block given at creation. | |||
# | |||
# This allows scanners and Encoders to use streaming (no | |||
# tokens are saved, the input is highlighted the same time it | |||
# is scanned) with the same code. | |||
# | |||
# See CodeRay.encode_stream and CodeRay.scan_stream | |||
class TokenStream < Tokens | |||
# Whether the object is a TokenStream. | |||
# | |||
# Returns true. | |||
def stream? | |||
true | |||
end | |||
# The Array is empty, but size counts the tokens given by <<. | |||
attr_reader :size | |||
# Creates a new TokenStream that calls +block+ whenever | |||
# its << method is called. | |||
# | |||
# Example: | |||
# | |||
# require 'coderay' | |||
# | |||
# token_stream = CodeRay::TokenStream.new do |text, kind| | |||
# puts 'kind: %s, text size: %d.' % [kind, text.size] | |||
# end | |||
# | |||
# token_stream << ['/\d+/', :regexp] | |||
# #-> kind: rexpexp, text size: 5. | |||
# | |||
def initialize &block | |||
raise ArgumentError, 'Block expected for streaming.' unless block | |||
@callback = block | |||
@size = 0 | |||
end | |||
# Calls +block+ with +token+ and increments size. | |||
# | |||
# Returns self. | |||
def << token | |||
@callback.call(*token) | |||
@size += 1 | |||
self | |||
end | |||
# This method is not implemented due to speed reasons. Use Tokens. | |||
def text_size | |||
raise NotImplementedError, | |||
'This method is not implemented due to speed reasons.' | |||
end | |||
# A TokenStream cannot be dumped. Use Tokens. | |||
def dump | |||
raise NotImplementedError, 'A TokenStream cannot be dumped.' | |||
end | |||
# A TokenStream cannot be optimized. Use Tokens. | |||
def optimize | |||
raise NotImplementedError, 'A TokenStream cannot be optimized.' | |||
end | |||
end | |||
end | |||
if $0 == __FILE__ | |||
$VERBOSE = true | |||
$: << File.join(File.dirname(__FILE__), '..') | |||
eval DATA.read, nil, $0, __LINE__ + 4 | |||
end | |||
__END__ | |||
require 'test/unit' | |||
class TokensTest < Test::Unit::TestCase | |||
def test_creation | |||
assert CodeRay::Tokens < Array | |||
tokens = nil | |||
assert_nothing_raised do | |||
tokens = CodeRay::Tokens.new | |||
end | |||
assert_kind_of Array, tokens | |||
end | |||
def test_adding_tokens | |||
tokens = CodeRay::Tokens.new | |||
assert_nothing_raised do | |||
tokens << ['string', :type] | |||
tokens << ['()', :operator] | |||
end | |||
assert_equal tokens.size, 2 | |||
end | |||
def test_dump_undump | |||
tokens = CodeRay::Tokens.new | |||
assert_nothing_raised do | |||
tokens << ['string', :type] | |||
tokens << ['()', :operator] | |||
end | |||
tokens2 = nil | |||
assert_nothing_raised do | |||
tokens2 = tokens.dump.undump | |||
end | |||
assert_equal tokens, tokens2 | |||
end | |||
end |
@@ -1,122 +0,0 @@ | |||
require 'test/unit' | |||
require 'coderay' | |||
class BasicTest < Test::Unit::TestCase | |||
def test_version | |||
assert_nothing_raised do | |||
assert_match(/\A\d\.\d\.\d\z/, CodeRay::VERSION) | |||
end | |||
end | |||
RUBY_TEST_CODE = 'puts "Hello, World!"' | |||
RUBY_TEST_TOKENS = [ | |||
['puts', :ident], | |||
[' ', :space], | |||
[:open, :string], | |||
['"', :delimiter], | |||
['Hello, World!', :content], | |||
['"', :delimiter], | |||
[:close, :string] | |||
] | |||
def test_simple_scan | |||
assert_nothing_raised do | |||
assert_equal RUBY_TEST_TOKENS, CodeRay.scan(RUBY_TEST_CODE, :ruby).to_ary | |||
end | |||
end | |||
RUBY_TEST_HTML = 'puts <span class="s"><span class="dl">"</span>' + | |||
'<span class="k">Hello, World!</span><span class="dl">"</span></span>' | |||
def test_simple_highlight | |||
assert_nothing_raised do | |||
assert_equal RUBY_TEST_HTML, CodeRay.scan(RUBY_TEST_CODE, :ruby).html | |||
end | |||
end | |||
def test_duo | |||
assert_equal(RUBY_TEST_CODE, | |||
CodeRay::Duo[:plain, :plain].highlight(RUBY_TEST_CODE)) | |||
assert_equal(RUBY_TEST_CODE, | |||
CodeRay::Duo[:plain => :plain].highlight(RUBY_TEST_CODE)) | |||
end | |||
def test_duo_stream | |||
assert_equal(RUBY_TEST_CODE, | |||
CodeRay::Duo[:plain, :plain].highlight(RUBY_TEST_CODE, :stream => true)) | |||
end | |||
def test_comment_filter | |||
assert_equal <<-EXPECTED, CodeRay.scan(<<-INPUT, :ruby).comment_filter.text | |||
#!/usr/bin/env ruby | |||
code | |||
more code | |||
EXPECTED | |||
#!/usr/bin/env ruby | |||
=begin | |||
A multi-line comment. | |||
=end | |||
code | |||
# A single-line comment. | |||
more code # and another comment, in-line. | |||
INPUT | |||
end | |||
def test_lines_of_code | |||
assert_equal 2, CodeRay.scan(<<-INPUT, :ruby).lines_of_code | |||
#!/usr/bin/env ruby | |||
=begin | |||
A multi-line comment. | |||
=end | |||
code | |||
# A single-line comment. | |||
more code # and another comment, in-line. | |||
INPUT | |||
rHTML = <<-RHTML | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | |||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head> | |||
<meta http-equiv="content-type" content="text/html;charset=UTF-8" /> | |||
<title><%= controller.controller_name.titleize %>: <%= controller.action_name %></title> | |||
<%= stylesheet_link_tag 'scaffold' %> | |||
</head> | |||
<body> | |||
<p style="color: green"><%= flash[:notice] %></p> | |||
<div id="main"> | |||
<%= yield %> | |||
</div> | |||
</body> | |||
</html> | |||
RHTML | |||
assert_equal 0, CodeRay.scan(rHTML, :html).lines_of_code | |||
assert_equal 0, CodeRay.scan(rHTML, :php).lines_of_code | |||
assert_equal 0, CodeRay.scan(rHTML, :yaml).lines_of_code | |||
assert_equal 4, CodeRay.scan(rHTML, :rhtml).lines_of_code | |||
end | |||
def test_rubygems_not_loaded | |||
assert_equal nil, defined? Gem | |||
end if ENV['check_rubygems'] && RUBY_VERSION < '1.9' | |||
def test_list_of_encoders | |||
assert_kind_of(Array, CodeRay::Encoders.list) | |||
assert CodeRay::Encoders.list.include?('count') | |||
end | |||
def test_list_of_scanners | |||
assert_kind_of(Array, CodeRay::Scanners.list) | |||
assert CodeRay::Scanners.list.include?('plaintext') | |||
end | |||
def test_scan_a_frozen_string | |||
CodeRay.scan RUBY_VERSION, :ruby | |||
end | |||
end |
@@ -1,11 +0,0 @@ | |||
require 'test/unit' | |||
require 'coderay' | |||
class PluginScannerTest < Test::Unit::TestCase | |||
def test_load | |||
require File.join(File.dirname(__FILE__), 'vhdl') | |||
assert_equal 'VHDL', CodeRay.scanner(:vhdl).class.name | |||
end | |||
end |
@@ -1,317 +0,0 @@ | |||
!RBIX | |||
0 | |||
x | |||
M | |||
1 | |||
n | |||
n | |||
x | |||
10 | |||
__script__ | |||
i | |||
53 | |||
5 | |||
7 | |||
0 | |||
64 | |||
47 | |||
49 | |||
1 | |||
1 | |||
15 | |||
5 | |||
7 | |||
2 | |||
64 | |||
47 | |||
49 | |||
1 | |||
1 | |||
15 | |||
99 | |||
7 | |||
3 | |||
45 | |||
4 | |||
5 | |||
43 | |||
6 | |||
43 | |||
7 | |||
65 | |||
49 | |||
8 | |||
3 | |||
13 | |||
99 | |||
12 | |||
7 | |||
9 | |||
12 | |||
7 | |||
10 | |||
12 | |||
65 | |||
12 | |||
49 | |||
11 | |||
4 | |||
15 | |||
49 | |||
9 | |||
0 | |||
15 | |||
2 | |||
11 | |||
I | |||
6 | |||
I | |||
0 | |||
I | |||
0 | |||
I | |||
0 | |||
n | |||
p | |||
12 | |||
s | |||
9 | |||
test/unit | |||
x | |||
7 | |||
require | |||
s | |||
7 | |||
coderay | |||
x | |||
17 | |||
PluginScannerTest | |||
x | |||
4 | |||
Test | |||
n | |||
x | |||
4 | |||
Unit | |||
x | |||
8 | |||
TestCase | |||
x | |||
10 | |||
open_class | |||
x | |||
14 | |||
__class_init__ | |||
M | |||
1 | |||
n | |||
n | |||
x | |||
17 | |||
PluginScannerTest | |||
i | |||
16 | |||
5 | |||
66 | |||
99 | |||
7 | |||
0 | |||
7 | |||
1 | |||
65 | |||
67 | |||
49 | |||
2 | |||
0 | |||
49 | |||
3 | |||
4 | |||
11 | |||
I | |||
5 | |||
I | |||
0 | |||
I | |||
0 | |||
I | |||
0 | |||
n | |||
p | |||
4 | |||
x | |||
9 | |||
test_load | |||
M | |||
1 | |||
n | |||
n | |||
x | |||
9 | |||
test_load | |||
i | |||
48 | |||
5 | |||
45 | |||
0 | |||
1 | |||
45 | |||
0 | |||
2 | |||
65 | |||
49 | |||
3 | |||
0 | |||
49 | |||
4 | |||
1 | |||
7 | |||
5 | |||
64 | |||
49 | |||
6 | |||
2 | |||
47 | |||
49 | |||
7 | |||
1 | |||
15 | |||
5 | |||
7 | |||
8 | |||
64 | |||
45 | |||
9 | |||
10 | |||
7 | |||
11 | |||
49 | |||
12 | |||
1 | |||
49 | |||
13 | |||
0 | |||
49 | |||
14 | |||
0 | |||
47 | |||
49 | |||
15 | |||
2 | |||
11 | |||
I | |||
4 | |||
I | |||
0 | |||
I | |||
0 | |||
I | |||
0 | |||
n | |||
p | |||
16 | |||
x | |||
4 | |||
File | |||
n | |||
n | |||
x | |||
11 | |||
active_path | |||
x | |||
7 | |||
dirname | |||
s | |||
4 | |||
vhdl | |||
x | |||
4 | |||
join | |||
x | |||
7 | |||
require | |||
s | |||
4 | |||
VHDL | |||
x | |||
7 | |||
CodeRay | |||
n | |||
x | |||
4 | |||
vhdl | |||
x | |||
7 | |||
scanner | |||
x | |||
5 | |||
class | |||
x | |||
4 | |||
name | |||
x | |||
12 | |||
assert_equal | |||
p | |||
7 | |||
I | |||
0 | |||
I | |||
6 | |||
I | |||
0 | |||
I | |||
7 | |||
I | |||
19 | |||
I | |||
8 | |||
I | |||
30 | |||
x | |||
69 | |||
/Users/murphy/ruby/coderay-0.9/test/functional/load_plugin_scanner.rb | |||
p | |||
0 | |||
x | |||
17 | |||
method_visibility | |||
x | |||
15 | |||
add_defn_method | |||
p | |||
3 | |||
I | |||
2 | |||
I | |||
6 | |||
I | |||
10 | |||
x | |||
69 | |||
/Users/murphy/ruby/coderay-0.9/test/functional/load_plugin_scanner.rb | |||
p | |||
0 | |||
x | |||
13 | |||
attach_method | |||
p | |||
7 | |||
I | |||
0 | |||
I | |||
1 | |||
I | |||
9 | |||
I | |||
2 | |||
I | |||
12 | |||
I | |||
4 | |||
I | |||
35 | |||
x | |||
69 | |||
/Users/murphy/ruby/coderay-0.9/test/functional/load_plugin_scanner.rb | |||
p | |||
0 |
@@ -1,12 +0,0 @@ | |||
require 'test/unit' | |||
MYDIR = File.dirname(__FILE__) | |||
$:.unshift 'lib' | |||
require 'coderay' | |||
puts "Running basic CodeRay #{CodeRay::VERSION} tests..." | |||
suite = %w(basic load_plugin_scanner word_list) | |||
for test_case in suite | |||
load File.join(MYDIR, test_case + '.rb') | |||
end |
@@ -1,322 +0,0 @@ | |||
!RBIX | |||
0 | |||
x | |||
M | |||
1 | |||
n | |||
n | |||
x | |||
10 | |||
__script__ | |||
i | |||
95 | |||
5 | |||
7 | |||
0 | |||
64 | |||
47 | |||
49 | |||
1 | |||
1 | |||
15 | |||
65 | |||
7 | |||
2 | |||
45 | |||
3 | |||
4 | |||
65 | |||
49 | |||
5 | |||
0 | |||
49 | |||
6 | |||
1 | |||
49 | |||
7 | |||
2 | |||
15 | |||
99 | |||
43 | |||
8 | |||
7 | |||
9 | |||
49 | |||
10 | |||
1 | |||
7 | |||
11 | |||
64 | |||
49 | |||
12 | |||
1 | |||
15 | |||
5 | |||
7 | |||
13 | |||
64 | |||
47 | |||
49 | |||
1 | |||
1 | |||
15 | |||
5 | |||
7 | |||
14 | |||
45 | |||
15 | |||
16 | |||
43 | |||
17 | |||
47 | |||
49 | |||
18 | |||
0 | |||
7 | |||
19 | |||
63 | |||
3 | |||
47 | |||
49 | |||
20 | |||
1 | |||
15 | |||
7 | |||
21 | |||
64 | |||
7 | |||
22 | |||
64 | |||
7 | |||
23 | |||
64 | |||
35 | |||
3 | |||
19 | |||
0 | |||
15 | |||
20 | |||
0 | |||
56 | |||
24 | |||
50 | |||
25 | |||
0 | |||
15 | |||
2 | |||
11 | |||
I | |||
6 | |||
I | |||
2 | |||
I | |||
0 | |||
I | |||
0 | |||
n | |||
p | |||
26 | |||
s | |||
9 | |||
test/unit | |||
x | |||
7 | |||
require | |||
x | |||
5 | |||
MYDIR | |||
x | |||
4 | |||
File | |||
n | |||
x | |||
11 | |||
active_path | |||
x | |||
7 | |||
dirname | |||
x | |||
9 | |||
const_set | |||
x | |||
7 | |||
Globals | |||
x | |||
2 | |||
$: | |||
x | |||
2 | |||
[] | |||
s | |||
3 | |||
lib | |||
x | |||
2 | |||
<< | |||
s | |||
7 | |||
coderay | |||
s | |||
22 | |||
Running basic CodeRay | |||
x | |||
7 | |||
CodeRay | |||
n | |||
x | |||
7 | |||
VERSION | |||
x | |||
4 | |||
to_s | |||
s | |||
9 | |||
tests... | |||
x | |||
4 | |||
puts | |||
s | |||
5 | |||
basic | |||
s | |||
19 | |||
load_plugin_scanner | |||
s | |||
9 | |||
word_list | |||
M | |||
1 | |||
p | |||
2 | |||
x | |||
9 | |||
for_block | |||
t | |||
n | |||
x | |||
9 | |||
__block__ | |||
i | |||
28 | |||
57 | |||
22 | |||
1 | |||
1 | |||
15 | |||
5 | |||
45 | |||
0 | |||
1 | |||
45 | |||
2 | |||
3 | |||
21 | |||
1 | |||
1 | |||
7 | |||
4 | |||
64 | |||
81 | |||
5 | |||
49 | |||
6 | |||
2 | |||
47 | |||
49 | |||
7 | |||
1 | |||
11 | |||
I | |||
6 | |||
I | |||
0 | |||
I | |||
1 | |||
I | |||
1 | |||
n | |||
p | |||
8 | |||
x | |||
4 | |||
File | |||
n | |||
x | |||
5 | |||
MYDIR | |||
n | |||
s | |||
3 | |||
.rb | |||
x | |||
1 | |||
+ | |||
x | |||
4 | |||
join | |||
x | |||
4 | |||
load | |||
p | |||
5 | |||
I | |||
0 | |||
I | |||
a | |||
I | |||
5 | |||
I | |||
b | |||
I | |||
1c | |||
x | |||
55 | |||
/Users/murphy/ruby/coderay-0.9/test/functional/suite.rb | |||
p | |||
0 | |||
x | |||
4 | |||
each | |||
p | |||
15 | |||
I | |||
0 | |||
I | |||
1 | |||
I | |||
9 | |||
I | |||
3 | |||
I | |||
1a | |||
I | |||
5 | |||
I | |||
29 | |||
I | |||
6 | |||
I | |||
32 | |||
I | |||
7 | |||
I | |||
47 | |||
I | |||
9 | |||
I | |||
55 | |||
I | |||
a | |||
I | |||
5f | |||
x | |||
55 | |||
/Users/murphy/ruby/coderay-0.9/test/functional/suite.rb | |||
p | |||
2 | |||
x | |||
5 | |||
suite | |||
x | |||
9 | |||
test_case |
@@ -1,126 +0,0 @@ | |||
class VHDL < CodeRay::Scanners::Scanner | |||
register_for :vhdl | |||
RESERVED_WORDS = [ | |||
'access','after','alias','all','assert','architecture','begin', | |||
'block','body','buffer','bus','case','component','configuration','constant', | |||
'disconnect','downto','else','elsif','end','entity','exit','file','for', | |||
'function','generate','generic','group','guarded','if','impure','in', | |||
'inertial','inout','is','label','library','linkage','literal','loop', | |||
'map','new','next','null','of','on','open','others','out','package', | |||
'port','postponed','procedure','process','pure','range','record','register', | |||
'reject','report','return','select','severity','signal','shared','subtype', | |||
'then','to','transport','type','unaffected','units','until','use','variable', | |||
'wait','when','while','with','note','warning','error','failure','and', | |||
'or','xor','not','nor', | |||
'array' | |||
] | |||
PREDEFINED_TYPES = [ | |||
'bit','bit_vector','character','boolean','integer','real','time','string', | |||
'severity_level','positive','natural','signed','unsigned','line','text', | |||
'std_logic','std_logic_vector','std_ulogic','std_ulogic_vector','qsim_state', | |||
'qsim_state_vector','qsim_12state','qsim_12state_vector','qsim_strength', | |||
'mux_bit','mux_vector','reg_bit','reg_vector','wor_bit','wor_vector' | |||
] | |||
PREDEFINED_CONSTANTS = [ | |||
] | |||
IDENT_KIND = CodeRay::CaseIgnoringWordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_TYPES, :pre_type). | |||
add(PREDEFINED_CONSTANTS, :pre_constant) | |||
ESCAPE = / [rbfntv\n\\'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} /x | |||
UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x | |||
def scan_tokens tokens, options | |||
state = :initial | |||
until eos? | |||
kind = nil | |||
match = nil | |||
case state | |||
when :initial | |||
if scan(/ \s+ | \\\n /x) | |||
kind = :space | |||
elsif scan(/-- .*/x) | |||
kind = :comment | |||
elsif scan(/ [-+*\/=<>?:;,!&^|()\[\]{}~%]+ | \.(?!\d) /x) | |||
kind = :operator | |||
elsif match = scan(/ [A-Za-z_][A-Za-z_0-9]* /x) | |||
kind = IDENT_KIND[match.downcase] | |||
elsif match = scan(/[a-z]?"/i) | |||
tokens << [:open, :string] | |||
state = :string | |||
kind = :delimiter | |||
elsif scan(/ L?' (?: [^\'\n\\] | \\ #{ESCAPE} )? '? /ox) | |||
kind = :char | |||
elsif scan(/(?:\d+)(?![.eEfF])/) | |||
kind = :integer | |||
elsif scan(/\d[fF]?|\d*\.\d+(?:[eE][+-]?\d+)?[fF]?|\d+[eE][+-]?\d+[fF]?/) | |||
kind = :float | |||
else | |||
getch | |||
kind = :error | |||
end | |||
when :string | |||
if scan(/[^\\\n"]+/) | |||
kind = :content | |||
elsif scan(/"/) | |||
tokens << ['"', :delimiter] | |||
tokens << [:close, :string] | |||
state = :initial | |||
next | |||
elsif scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox) | |||
kind = :char | |||
elsif scan(/ \\ | $ /x) | |||
tokens << [:close, :string] | |||
kind = :error | |||
state = :initial | |||
else | |||
raise_inspect "else case \" reached; %p not handled." % peek(1), tokens | |||
end | |||
else | |||
raise_inspect 'Unknown state', tokens | |||
end | |||
match ||= matched | |||
if $DEBUG and not kind | |||
raise_inspect 'Error token %p in line %d' % | |||
[[match, kind], line], tokens | |||
end | |||
raise_inspect 'Empty token', tokens unless match | |||
tokens << [match, kind] | |||
end | |||
if state == :string | |||
tokens << [:close, :string] | |||
end | |||
tokens | |||
end | |||
end |
@@ -1,79 +0,0 @@ | |||
require 'test/unit' | |||
require 'coderay' | |||
class WordListTest < Test::Unit::TestCase | |||
include CodeRay | |||
# define word arrays | |||
RESERVED_WORDS = %w[ | |||
asm break case continue default do else | |||
... | |||
] | |||
PREDEFINED_TYPES = %w[ | |||
int long short char void | |||
... | |||
] | |||
PREDEFINED_CONSTANTS = %w[ | |||
EOF NULL ... | |||
] | |||
# make a WordList | |||
IDENT_KIND = WordList.new(:ident). | |||
add(RESERVED_WORDS, :reserved). | |||
add(PREDEFINED_TYPES, :pre_type). | |||
add(PREDEFINED_CONSTANTS, :pre_constant) | |||
def test_word_list_example | |||
assert_equal :pre_type, IDENT_KIND['void'] | |||
# assert_equal :pre_constant, IDENT_KIND['...'] # not specified | |||
end | |||
def test_word_list | |||
list = WordList.new(:ident).add(['foobar'], :reserved) | |||
assert_equal :reserved, list['foobar'] | |||
assert_equal :ident, list['FooBar'] | |||
end | |||
def test_word_list_cached | |||
list = WordList.new(:ident, true).add(['foobar'], :reserved) | |||
assert_equal :reserved, list['foobar'] | |||
assert_equal :ident, list['FooBar'] | |||
end | |||
def test_case_ignoring_word_list | |||
list = CaseIgnoringWordList.new(:ident).add(['foobar'], :reserved) | |||
assert_equal :ident, list['foo'] | |||
assert_equal :reserved, list['foobar'] | |||
assert_equal :reserved, list['FooBar'] | |||
list = CaseIgnoringWordList.new(:ident).add(['FooBar'], :reserved) | |||
assert_equal :ident, list['foo'] | |||
assert_equal :reserved, list['foobar'] | |||
assert_equal :reserved, list['FooBar'] | |||
end | |||
def test_case_ignoring_word_list_cached | |||
list = CaseIgnoringWordList.new(:ident, true).add(['foobar'], :reserved) | |||
assert_equal :ident, list['foo'] | |||
assert_equal :reserved, list['foobar'] | |||
assert_equal :reserved, list['FooBar'] | |||
list = CaseIgnoringWordList.new(:ident, true).add(['FooBar'], :reserved) | |||
assert_equal :ident, list['foo'] | |||
assert_equal :reserved, list['foobar'] | |||
assert_equal :reserved, list['FooBar'] | |||
end | |||
def test_dup | |||
list = WordList.new(:ident).add(['foobar'], :reserved) | |||
assert_equal :reserved, list['foobar'] | |||
list2 = list.dup | |||
list2.add(%w[foobar], :keyword) | |||
assert_equal :keyword, list2['foobar'] | |||
assert_equal :reserved, list['foobar'] | |||
end | |||
end |
@@ -1,57 +1,45 @@ | |||
= CodeRay | |||
[- Tired of blue'n'gray? Try the original version of this documentation on | |||
coderay.rubychan.de[http://coderay.rubychan.de/doc/] (use Ctrl+Click to open it in its own frame.) -] | |||
Tired of blue'n'gray? Try the original version of this documentation on | |||
coderay.rubychan.de[http://coderay.rubychan.de/doc/] :-) | |||
== About | |||
CodeRay is a Ruby library for syntax highlighting. | |||
Syntax highlighting means: You put your code in, and you get it back colored; | |||
Keywords, strings, floats, comments - all in different colors. | |||
And with line numbers. | |||
You put your code in, and you get it back colored; Keywords, strings, | |||
floats, comments - all in different colors. And with line numbers. | |||
*Syntax* *Highlighting*... | |||
* makes code easier to read and maintain | |||
* lets you detect syntax errors faster | |||
* helps you to understand the syntax of a language | |||
* looks nice | |||
* is what everybody should have on their website | |||
* is what everybody wants to have on their website | |||
* solves all your problems and makes the girls run after you | |||
Version: 0.9.7 | |||
Author:: murphy (Kornelius Kalnbach) | |||
Contact:: murphy rubychan de | |||
Website:: coderay.rubychan.de[http://coderay.rubychan.de] | |||
License:: GNU LGPL; see LICENSE file in the main directory. | |||
== Installation | |||
You need RubyGems[http://rubyforge.org/frs/?group_id=126]. | |||
% gem install coderay | |||
=== Dependencies | |||
CodeRay needs Ruby 1.8.6 or later. It also runs with Ruby 1.9.1+ and JRuby 1.1+. | |||
CodeRay needs Ruby 1.8.7+ or 1.9.2+. It also runs on Rubinius and JRuby. | |||
== Example Usage | |||
(Forgive me, but this is not highlighted.) | |||
require 'coderay' | |||
tokens = CodeRay.scan "puts 'Hello, world!'", :ruby | |||
page = tokens.html :line_numbers => :inline, :wrap => :page | |||
puts page | |||
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div(:line_numbers => :table) | |||
== Documentation | |||
See CodeRay. | |||
Please report errors in this documentation to <murphy rubychan de>. | |||
== Credits | |||
@@ -94,7 +82,6 @@ Please report errors in this documentation to <murphy rubychan de>. | |||
* Rob Aldred for the terminal encoder | |||
* Trans for pointing out $DEBUG dependencies | |||
* Flameeyes for finding that Term::ANSIColor was obsolete | |||
* Etienne Massip for reporting a serious bug in JavaScript scanner | |||
* matz and all Ruby gods and gurus | |||
* The inventors of: the computer, the internet, the true color display, HTML & | |||
CSS, VIM, Ruby, pizza, microwaves, guitars, scouting, programming, anime, | |||
@@ -124,6 +111,8 @@ Where would we be without all those people? | |||
less useless | |||
* Term::ANSIColor[http://term-ansicolor.rubyforge.org/] | |||
* PLEAC[http://pleac.sourceforge.net/] code examples | |||
* Github | |||
* Travis CI (http://travis-ci.org/rubychan/github) | |||
=== Free | |||
@@ -1,8 +1,7 @@ | |||
require 'rake/rdoctask' | |||
$:.unshift File.dirname(__FILE__) unless $:.include? '.' | |||
ROOT = '.' | |||
LIB_ROOT = File.join ROOT, 'lib' | |||
EXTRA_RDOC_FILES = %w(lib/README FOLDERS) | |||
task :default => :test | |||
@@ -15,20 +14,21 @@ if File.directory? 'rake_tasks' | |||
else | |||
# fallback tasks when rake_tasks folder is not present | |||
# fallback tasks when rake_tasks folder is not present (eg. in the distribution package) | |||
desc 'Run CodeRay tests (basic)' | |||
task :test do | |||
ruby './test/functional/suite.rb' | |||
ruby './test/functional/for_redcloth.rb' | |||
end | |||
gem 'rdoc' if defined? gem | |||
require 'rdoc/task' | |||
desc 'Generate documentation for CodeRay' | |||
Rake::RDocTask.new :doc do |rd| | |||
rd.title = 'CodeRay Documentation' | |||
rd.main = 'lib/README' | |||
rd.main = 'README_INDEX.rdoc' | |||
rd.rdoc_files.add Dir['lib'] | |||
rd.rdoc_files.add 'lib/README' | |||
rd.rdoc_files.add 'FOLDERS' | |||
rd.rdoc_files.add rd.main | |||
rd.rdoc_dir = 'doc' | |||
end | |||
@@ -21,10 +21,7 @@ module CodeRay | |||
# Create a new Duo, holding a lang and a format to highlight code. | |||
# | |||
# simple: | |||
# CodeRay::Duo[:ruby, :page].highlight 'bla 42' | |||
# | |||
# streaming: | |||
# CodeRay::Duo[:ruby, :page].highlight 'bar 23', :stream => true | |||
# CodeRay::Duo[:ruby, :html].highlight 'bla 42' | |||
# | |||
# with options: | |||
# CodeRay::Duo[:ruby, :html, :hint => :debug].highlight '????::??' | |||
@@ -38,7 +35,7 @@ module CodeRay | |||
# The options are forwarded to scanner and encoder | |||
# (see CodeRay.get_scanner_options). | |||
def initialize lang = nil, format = nil, options = {} | |||
if format == nil and lang.is_a? Hash and lang.size == 1 | |||
if format.nil? && lang.is_a?(Hash) && lang.size == 1 | |||
@lang = lang.keys.first | |||
@format = lang[@lang] | |||
else | |||
@@ -47,12 +44,12 @@ module CodeRay | |||
end | |||
@options = options | |||
end | |||
class << self | |||
# To allow calls like Duo[:ruby, :html].highlight. | |||
alias [] new | |||
end | |||
# The scanner of the duo. Only created once. | |||
def scanner | |||
@scanner ||= CodeRay.scanner @lang, CodeRay.get_scanner_options(@options) | |||
@@ -64,22 +61,21 @@ module CodeRay | |||
end | |||
# Tokenize and highlight the code using +scanner+ and +encoder+. | |||
# | |||
# If the :stream option is set, the Duo will go into streaming mode, | |||
# saving memory for the cost of time. | |||
def encode code, options = { :stream => false } | |||
stream = options.delete :stream | |||
def encode code, options = {} | |||
options = @options.merge options | |||
if stream | |||
encoder.encode_stream(code, @lang, options) | |||
else | |||
scanner.code = code | |||
encoder.encode_tokens(scanner.tokenize, options) | |||
end | |||
encoder.encode(code, @lang, options) | |||
end | |||
alias highlight encode | |||
# Allows to use Duo like a proc object: | |||
# | |||
# CodeRay::Duo[:python => :yaml].call(code) | |||
# | |||
# or, in Ruby 1.9 and later: | |||
# | |||
# CodeRay::Duo[:python => :yaml].(code) | |||
alias call encode | |||
end | |||
end | |||
@@ -1,5 +1,5 @@ | |||
module CodeRay | |||
# This module holds the Encoder class and its subclasses. | |||
# For example, the HTML encoder is named CodeRay::Encoders::HTML | |||
# can be found in coderay/encoders/html. | |||
@@ -8,9 +8,10 @@ module CodeRay | |||
# mechanism and the [] method that returns the Encoder class | |||
# belonging to the given format. | |||
module Encoders | |||
extend PluginHost | |||
plugin_path File.dirname(__FILE__), 'encoders' | |||
# = Encoder | |||
# | |||
# The Encoder base class. Together with Scanner and | |||
@@ -26,34 +27,32 @@ module CodeRay | |||
class Encoder | |||
extend Plugin | |||
plugin_host Encoders | |||
attr_reader :token_stream | |||
class << self | |||
# Returns if the Encoder can be used in streaming mode. | |||
def streamable? | |||
is_a? Streamable | |||
end | |||
# If FILE_EXTENSION isn't defined, this method returns the | |||
# downcase class name instead. | |||
def const_missing sym | |||
if sym == :FILE_EXTENSION | |||
plugin_id | |||
(defined?(@plugin_id) && @plugin_id || name[/\w+$/].downcase).to_s | |||
else | |||
super | |||
end | |||
end | |||
# The default file extension for output file of this encoder class. | |||
def file_extension | |||
self::FILE_EXTENSION | |||
end | |||
end | |||
# Subclasses are to store their default options in this constant. | |||
DEFAULT_OPTIONS = { :stream => false } | |||
DEFAULT_OPTIONS = { } | |||
# The options you gave the Encoder at creating. | |||
attr_accessor :options | |||
attr_accessor :options, :scanner | |||
# Creates a new Encoder. | |||
# +options+ is saved and used for all encode operations, as long | |||
# as you don't overwrite it there by passing additional options. | |||
@@ -61,153 +60,142 @@ module CodeRay | |||
# Encoder objects provide three encode methods: | |||
# - encode simply takes a +code+ string and a +lang+ | |||
# - encode_tokens expects a +tokens+ object instead | |||
# - encode_stream is like encode, but uses streaming mode. | |||
# | |||
# Each method has an optional +options+ parameter. These are | |||
# added to the options you passed at creation. | |||
def initialize options = {} | |||
@options = self.class::DEFAULT_OPTIONS.merge options | |||
raise "I am only the basic Encoder class. I can't encode "\ | |||
"anything. :( Use my subclasses." if self.class == Encoder | |||
@@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = false | |||
end | |||
# Encode a Tokens object. | |||
def encode_tokens tokens, options = {} | |||
options = @options.merge options | |||
@scanner = tokens.scanner if tokens.respond_to? :scanner | |||
setup options | |||
compile tokens, options | |||
finish options | |||
end | |||
# Encode the given +code+ after tokenizing it using the Scanner | |||
# for +lang+. | |||
# Encode the given +code+ using the Scanner for +lang+. | |||
def encode code, lang, options = {} | |||
options = @options.merge options | |||
scanner_options = CodeRay.get_scanner_options(options) | |||
tokens = CodeRay.scan code, lang, scanner_options | |||
encode_tokens tokens, options | |||
@scanner = Scanners[lang].new code, CodeRay.get_scanner_options(options).update(:tokens => self) | |||
setup options | |||
@scanner.tokenize | |||
finish options | |||
end | |||
# You can use highlight instead of encode, if that seems | |||
# more clear to you. | |||
alias highlight encode | |||
# Encode the given +code+ using the Scanner for +lang+ in | |||
# streaming mode. | |||
def encode_stream code, lang, options = {} | |||
raise NotStreamableError, self unless kind_of? Streamable | |||
options = @options.merge options | |||
setup options | |||
scanner_options = CodeRay.get_scanner_options options | |||
@token_stream = | |||
CodeRay.scan_stream code, lang, scanner_options, &self | |||
finish options | |||
end | |||
# Behave like a proc. The token method is converted to a proc. | |||
def to_proc | |||
method(:token).to_proc | |||
end | |||
# Return the default file extension for outputs of this encoder. | |||
# The default file extension for this encoder. | |||
def file_extension | |||
self.class::FILE_EXTENSION | |||
self.class.file_extension | |||
end | |||
protected | |||
# Called with merged options before encoding starts. | |||
# Sets @out to an empty string. | |||
# | |||
# See the HTML Encoder for an example of option caching. | |||
def setup options | |||
@out = '' | |||
def << token | |||
unless @@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN | |||
warn 'Using old Tokens#<< interface.' | |||
@@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN = true | |||
end | |||
self.token(*token) | |||
end | |||
# Called with +content+ and +kind+ of the currently scanned token. | |||
# For simple scanners, it's enougth to implement this method. | |||
# | |||
# By default, it calls text_token or block_token, depending on | |||
# whether +content+ is a String. | |||
# By default, it calls text_token, begin_group, end_group, begin_line, | |||
# or end_line, depending on the +content+. | |||
def token content, kind | |||
encoded_token = | |||
if content.is_a? ::String | |||
text_token content, kind | |||
elsif content.is_a? ::Symbol | |||
block_token content, kind | |||
else | |||
raise 'Unknown token content type: %p' % [content] | |||
end | |||
append_encoded_token_to_output encoded_token | |||
end | |||
def append_encoded_token_to_output encoded_token | |||
@out << encoded_token if encoded_token && defined?(@out) && @out | |||
end | |||
# Called for each text token ([text, kind]), where text is a String. | |||
def text_token text, kind | |||
end | |||
# Called for each block (non-text) token ([action, kind]), | |||
# where +action+ is a Symbol. | |||
# | |||
# Calls open_token, close_token, begin_line, and end_line according to | |||
# the value of +action+. | |||
def block_token action, kind | |||
case action | |||
when :open | |||
open_token kind | |||
when :close | |||
close_token kind | |||
case content | |||
when String | |||
text_token content, kind | |||
when :begin_group | |||
begin_group kind | |||
when :end_group | |||
end_group kind | |||
when :begin_line | |||
begin_line kind | |||
when :end_line | |||
end_line kind | |||
else | |||
raise 'unknown block action: %p' % action | |||
raise ArgumentError, 'Unknown token content type: %p, kind = %p' % [content, kind] | |||
end | |||
end | |||
# Called for each block token at the start of the block ([:open, kind]). | |||
def open_token kind | |||
# Called for each text token ([text, kind]), where text is a String. | |||
def text_token text, kind | |||
@out << text | |||
end | |||
# Called for each block token end of the block ([:close, kind]). | |||
def close_token kind | |||
# Starts a token group with the given +kind+. | |||
def begin_group kind | |||
end | |||
# Called for each line token block at the start of the line ([:begin_line, kind]). | |||
# Ends a token group with the given +kind+. | |||
def end_group kind | |||
end | |||
# Starts a new line token group with the given +kind+. | |||
def begin_line kind | |||
end | |||
# Called for each line token block at the end of the line ([:end_line, kind]). | |||
# Ends a new line token group with the given +kind+. | |||
def end_line kind | |||
end | |||
protected | |||
# Called with merged options before encoding starts. | |||
# Sets @out to an empty string. | |||
# | |||
# See the HTML Encoder for an example of option caching. | |||
def setup options | |||
@out = get_output(options) | |||
end | |||
def get_output options | |||
options[:out] || '' | |||
end | |||
# Append data.to_s to the output. Returns the argument. | |||
def output data | |||
@out << data.to_s | |||
data | |||
end | |||
# Called with merged options after encoding starts. | |||
# The return value is the result of encoding, typically @out. | |||
def finish options | |||
@out | |||
end | |||
# Do the encoding. | |||
# | |||
# The already created +tokens+ object must be used; it can be a | |||
# TokenStream or a Tokens object. | |||
if RUBY_VERSION >= '1.9' | |||
def compile tokens, options | |||
for text, kind in tokens | |||
token text, kind | |||
# The already created +tokens+ object must be used; it must be a | |||
# Tokens object. | |||
def compile tokens, options = {} | |||
content = nil | |||
for item in tokens | |||
if item.is_a? Array | |||
raise ArgumentError, 'Two-element array tokens are no longer supported.' | |||
end | |||
if content | |||
token content, item | |||
content = nil | |||
else | |||
content = item | |||
end | |||
end | |||
else | |||
def compile tokens, options | |||
tokens.each(&self) | |||
end | |||
raise 'odd number list for Tokens' if content | |||
end | |||
alias tokens compile | |||
public :tokens | |||
end | |||
end | |||
end |
@@ -0,0 +1,17 @@ | |||
module CodeRay | |||
module Encoders | |||
map \ | |||
:loc => :lines_of_code, | |||
:plain => :text, | |||
:plaintext => :text, | |||
:remove_comments => :comment_filter, | |||
:stats => :statistic, | |||
:term => :terminal, | |||
:tty => :terminal, | |||
:yml => :yaml | |||
# No default because Tokens#nonsense should raise NoMethodError. | |||
end | |||
end |
@@ -0,0 +1,25 @@ | |||
module CodeRay | |||
module Encoders | |||
load :token_kind_filter | |||
# A simple Filter that removes all tokens of the :comment kind. | |||
# | |||
# Alias: +remove_comments+ | |||
# | |||
# Usage: | |||
# CodeRay.scan('print # foo', :ruby).comment_filter.text | |||
# #-> "print " | |||
# | |||
# See also: TokenKindFilter, LinesOfCode | |||
class CommentFilter < TokenKindFilter | |||
register_for :comment_filter | |||
DEFAULT_OPTIONS = superclass::DEFAULT_OPTIONS.merge \ | |||
:exclude => [:comment, :docstring] | |||
end | |||
end | |||
end |
@@ -0,0 +1,39 @@ | |||
module CodeRay | |||
module Encoders | |||
# Returns the number of tokens. | |||
# | |||
# Text and block tokens are counted. | |||
class Count < Encoder | |||
register_for :count | |||
protected | |||
def setup options | |||
super | |||
@count = 0 | |||
end | |||
def finish options | |||
output @count | |||
end | |||
public | |||
def text_token text, kind | |||
@count += 1 | |||
end | |||
def begin_group kind | |||
@count += 1 | |||
end | |||
alias end_group begin_group | |||
alias begin_line begin_group | |||
alias end_line begin_group | |||
end | |||
end | |||
end |
@@ -1,6 +1,6 @@ | |||
module CodeRay | |||
module Encoders | |||
# = Debug Encoder | |||
# | |||
# Fast encoder producing simple debug output. | |||
@@ -10,40 +10,52 @@ module Encoders | |||
# You cannot fully restore the tokens information from the | |||
# output, because consecutive :space tokens are merged. | |||
# Use Tokens#dump for caching purposes. | |||
# | |||
# See also: Scanners::Debug | |||
class Debug < Encoder | |||
include Streamable | |||
register_for :debug | |||
FILE_EXTENSION = 'raydebug' | |||
protected | |||
def initialize options = {} | |||
super | |||
@opened = [] | |||
end | |||
def text_token text, kind | |||
if kind == :space | |||
text | |||
@out << text | |||
else | |||
# TODO: Escape ( | |||
text = text.gsub(/[)\\]/, '\\\\\0') # escape ) and \ | |||
"#{kind}(#{text})" | |||
@out << kind.to_s << '(' << text << ')' | |||
end | |||
end | |||
def open_token kind | |||
"#{kind}<" | |||
def begin_group kind | |||
@opened << kind | |||
@out << kind.to_s << '<' | |||
end | |||
def close_token kind | |||
">" | |||
def end_group kind | |||
if @opened.last != kind | |||
puts @out | |||
raise "we are inside #{@opened.inspect}, not #{kind}" | |||
end | |||
@opened.pop | |||
@out << '>' | |||
end | |||
def begin_line kind | |||
"#{kind}[" | |||
@out << kind.to_s << '[' | |||
end | |||
def end_line kind | |||
"]" | |||
@out << ']' | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,23 @@ | |||
module CodeRay | |||
module Encoders | |||
load :html | |||
# Wraps HTML output into a DIV element, using inline styles by default. | |||
# | |||
# See Encoders::HTML for available options. | |||
class Div < HTML | |||
FILE_EXTENSION = 'div.html' | |||
register_for :div | |||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ | |||
:css => :style, | |||
:wrap => :div, | |||
:line_numbers => false | |||
end | |||
end | |||
end |
@@ -0,0 +1,58 @@ | |||
module CodeRay | |||
module Encoders | |||
# A Filter encoder has another Tokens instance as output. | |||
# It can be subclass to select, remove, or modify tokens in the stream. | |||
# | |||
# Subclasses of Filter are called "Filters" and can be chained. | |||
# | |||
# == Options | |||
# | |||
# === :tokens | |||
# | |||
# The Tokens object which will receive the output. | |||
# | |||
# Default: Tokens.new | |||
# | |||
# See also: TokenKindFilter | |||
class Filter < Encoder | |||
register_for :filter | |||
protected | |||
def setup options | |||
super | |||
@tokens = options[:tokens] || Tokens.new | |||
end | |||
def finish options | |||
output @tokens | |||
end | |||
public | |||
def text_token text, kind # :nodoc: | |||
@tokens.text_token text, kind | |||
end | |||
def begin_group kind # :nodoc: | |||
@tokens.begin_group kind | |||
end | |||
def begin_line kind # :nodoc: | |||
@tokens.begin_line kind | |||
end | |||
def end_group kind # :nodoc: | |||
@tokens.end_group kind | |||
end | |||
def end_line kind # :nodoc: | |||
@tokens.end_line kind | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,302 @@ | |||
require 'set' | |||
module CodeRay | |||
module Encoders | |||
# = HTML Encoder | |||
# | |||
# This is CodeRay's most important highlighter: | |||
# It provides save, fast XHTML generation and CSS support. | |||
# | |||
# == Usage | |||
# | |||
# require 'coderay' | |||
# puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page | |||
# puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span) | |||
# #-> <span class="CodeRay"><span class="co">Some</span> /code/</span> | |||
# puts CodeRay.scan('Some /code/', :ruby).span #-> the same | |||
# | |||
# puts CodeRay.scan('Some code', :ruby).html( | |||
# :wrap => nil, | |||
# :line_numbers => :inline, | |||
# :css => :style | |||
# ) | |||
# | |||
# == Options | |||
# | |||
# === :tab_width | |||
# Convert \t characters to +n+ spaces (a number.) | |||
# | |||
# Default: 8 | |||
# | |||
# === :css | |||
# How to include the styles; can be :class or :style. | |||
# | |||
# Default: :class | |||
# | |||
# === :wrap | |||
# Wrap in :page, :div, :span or nil. | |||
# | |||
# You can also use Encoders::Div and Encoders::Span. | |||
# | |||
# Default: nil | |||
# | |||
# === :title | |||
# | |||
# The title of the HTML page (works only when :wrap is set to :page.) | |||
# | |||
# Default: 'CodeRay output' | |||
# | |||
# === :line_numbers | |||
# Include line numbers in :table, :inline, or nil (no line numbers) | |||
# | |||
# Default: nil | |||
# | |||
# === :line_number_anchors | |||
# Adds anchors and links to the line numbers. Can be false (off), true (on), | |||
# or a prefix string that will be prepended to the anchor name. | |||
# | |||
# The prefix must consist only of letters, digits, and underscores. | |||
# | |||
# Default: true, default prefix name: "line" | |||
# | |||
# === :line_number_start | |||
# Where to start with line number counting. | |||
# | |||
# Default: 1 | |||
# | |||
# === :bold_every | |||
# Make every +n+-th number appear bold. | |||
# | |||
# Default: 10 | |||
# | |||
# === :highlight_lines | |||
# | |||
# Highlights certain line numbers. | |||
# Can be any Enumerable, typically just an Array or Range, of numbers. | |||
# | |||
# Bolding is deactivated when :highlight_lines is set. It only makes sense | |||
# in combination with :line_numbers. | |||
# | |||
# Default: nil | |||
# | |||
# === :hint | |||
# Include some information into the output using the title attribute. | |||
# Can be :info (show token kind on mouse-over), :info_long (with full path) | |||
# or :debug (via inspect). | |||
# | |||
# Default: false | |||
class HTML < Encoder | |||
register_for :html | |||
FILE_EXTENSION = 'snippet.html' | |||
DEFAULT_OPTIONS = { | |||
:tab_width => 8, | |||
:css => :class, | |||
:style => :alpha, | |||
:wrap => nil, | |||
:title => 'CodeRay output', | |||
:line_numbers => nil, | |||
:line_number_anchors => 'n', | |||
:line_number_start => 1, | |||
:bold_every => 10, | |||
:highlight_lines => nil, | |||
:hint => false, | |||
} | |||
autoload :Output, 'coderay/encoders/html/output' | |||
autoload :CSS, 'coderay/encoders/html/css' | |||
autoload :Numbering, 'coderay/encoders/html/numbering' | |||
attr_reader :css | |||
protected | |||
HTML_ESCAPE = { #:nodoc: | |||
'&' => '&', | |||
'"' => '"', | |||
'>' => '>', | |||
'<' => '<', | |||
} | |||
# This was to prevent illegal HTML. | |||
# Strange chars should still be avoided in codes. | |||
evil_chars = Array(0x00...0x20) - [?\n, ?\t, ?\s] | |||
evil_chars.each { |i| HTML_ESCAPE[i.chr] = ' ' } | |||
#ansi_chars = Array(0x7f..0xff) | |||
#ansi_chars.each { |i| HTML_ESCAPE[i.chr] = '&#%d;' % i } | |||
# \x9 (\t) and \xA (\n) not included | |||
#HTML_ESCAPE_PATTERN = /[\t&"><\0-\x8\xB-\x1f\x7f-\xff]/ | |||
HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1f]/ | |||
TOKEN_KIND_TO_INFO = Hash.new do |h, kind| | |||
h[kind] = kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize } | |||
end | |||
TRANSPARENT_TOKEN_KINDS = Set[ | |||
:delimiter, :modifier, :content, :escape, :inline_delimiter, | |||
] | |||
# Generate a hint about the given +kinds+ in a +hint+ style. | |||
# | |||
# +hint+ may be :info, :info_long or :debug. | |||
def self.token_path_to_hint hint, kinds | |||
kinds = Array kinds | |||
title = | |||
case hint | |||
when :info | |||
kinds = kinds[1..-1] if TRANSPARENT_TOKEN_KINDS.include? kinds.first | |||
TOKEN_KIND_TO_INFO[kinds.first] | |||
when :info_long | |||
kinds.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/') | |||
when :debug | |||
kinds.inspect | |||
end | |||
title ? " title=\"#{title}\"" : '' | |||
end | |||
def setup options | |||
super | |||
if options[:wrap] || options[:line_numbers] | |||
@real_out = @out | |||
@out = '' | |||
end | |||
@HTML_ESCAPE = HTML_ESCAPE.dup | |||
@HTML_ESCAPE["\t"] = ' ' * options[:tab_width] | |||
@opened = [] | |||
@last_opened = nil | |||
@css = CSS.new options[:style] | |||
hint = options[:hint] | |||
if hint && ![:debug, :info, :info_long].include?(hint) | |||
raise ArgumentError, "Unknown value %p for :hint; \ | |||
expected :info, :info_long, :debug, false, or nil." % hint | |||
end | |||
css_classes = TokenKinds | |||
case options[:css] | |||
when :class | |||
@span_for_kind = Hash.new do |h, k| | |||
if k.is_a? ::Symbol | |||
kind = k_dup = k | |||
else | |||
kind = k.first | |||
k_dup = k.dup | |||
end | |||
if kind != :space && (hint || css_class = css_classes[kind]) | |||
title = HTML.token_path_to_hint hint, k if hint | |||
css_class ||= css_classes[kind] | |||
h[k_dup] = "<span#{title}#{" class=\"#{css_class}\"" if css_class}>" | |||
else | |||
h[k_dup] = nil | |||
end | |||
end | |||
when :style | |||
@span_for_kind = Hash.new do |h, k| | |||
kind = k.is_a?(Symbol) ? k : k.first | |||
h[k.is_a?(Symbol) ? k : k.dup] = | |||
if kind != :space && (hint || css_classes[kind]) | |||
title = HTML.token_path_to_hint hint, k if hint | |||
style = @css.get_style Array(k).map { |c| css_classes[c] } | |||
"<span#{title}#{" style=\"#{style}\"" if style}>" | |||
end | |||
end | |||
else | |||
raise ArgumentError, "Unknown value %p for :css." % options[:css] | |||
end | |||
@set_last_opened = options[:hint] || options[:css] == :style | |||
end | |||
def finish options | |||
unless @opened.empty? | |||
warn '%d tokens still open: %p' % [@opened.size, @opened] if $CODERAY_DEBUG | |||
@out << '</span>' while @opened.pop | |||
@last_opened = nil | |||
end | |||
@out.extend Output | |||
@out.css = @css | |||
if options[:line_numbers] | |||
Numbering.number! @out, options[:line_numbers], options | |||
end | |||
@out.wrap! options[:wrap] | |||
@out.apply_title! options[:title] | |||
if defined?(@real_out) && @real_out | |||
@real_out << @out | |||
@out = @real_out | |||
end | |||
super | |||
end | |||
public | |||
def text_token text, kind | |||
if text =~ /#{HTML_ESCAPE_PATTERN}/o | |||
text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] } | |||
end | |||
if style = @span_for_kind[@last_opened ? [kind, *@opened] : kind] | |||
@out << style << text << '</span>' | |||
else | |||
@out << text | |||
end | |||
end | |||
# token groups, eg. strings | |||
def begin_group kind | |||
@out << (@span_for_kind[@last_opened ? [kind, *@opened] : kind] || '<span>') | |||
@opened << kind | |||
@last_opened = kind if @set_last_opened | |||
end | |||
def end_group kind | |||
if $CODERAY_DEBUG && (@opened.empty? || @opened.last != kind) | |||
warn 'Malformed token stream: Trying to close a token (%p) ' \ | |||
'that is not open. Open are: %p.' % [kind, @opened[1..-1]] | |||
end | |||
if @opened.pop | |||
@out << '</span>' | |||
@last_opened = @opened.last if @last_opened | |||
end | |||
end | |||
# whole lines to be highlighted, eg. a deleted line in a diff | |||
def begin_line kind | |||
if style = @span_for_kind[@last_opened ? [kind, *@opened] : kind] | |||
if style['class="'] | |||
@out << style.sub('class="', 'class="line ') | |||
else | |||
@out << style.sub('>', ' class="line">') | |||
end | |||
else | |||
@out << '<span class="line">' | |||
end | |||
@opened << kind | |||
@last_opened = kind if @options[:css] == :style | |||
end | |||
def end_line kind | |||
if $CODERAY_DEBUG && (@opened.empty? || @opened.last != kind) | |||
warn 'Malformed token stream: Trying to close a line (%p) ' \ | |||
'that is not open. Open are: %p.' % [kind, @opened[1..-1]] | |||
end | |||
if @opened.pop | |||
@out << '</span>' | |||
@last_opened = @opened.last if @last_opened | |||
end | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,83 @@ | |||
module CodeRay | |||
module Encoders | |||
# A simple JSON Encoder. | |||
# | |||
# Example: | |||
# CodeRay.scan('puts "Hello world!"', :ruby).json | |||
# yields | |||
# [ | |||
# {"type"=>"text", "text"=>"puts", "kind"=>"ident"}, | |||
# {"type"=>"text", "text"=>" ", "kind"=>"space"}, | |||
# {"type"=>"block", "action"=>"open", "kind"=>"string"}, | |||
# {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, | |||
# {"type"=>"text", "text"=>"Hello world!", "kind"=>"content"}, | |||
# {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"}, | |||
# {"type"=>"block", "action"=>"close", "kind"=>"string"}, | |||
# ] | |||
class JSON < Encoder | |||
begin | |||
require 'json' | |||
rescue LoadError | |||
begin | |||
require 'rubygems' unless defined? Gem | |||
gem 'json' | |||
require 'json' | |||
rescue LoadError | |||
$stderr.puts "The JSON encoder needs the JSON library.\n" \ | |||
"Please gem install json." | |||
raise | |||
end | |||
end | |||
register_for :json | |||
FILE_EXTENSION = 'json' | |||
protected | |||
def setup options | |||
super | |||
@first = true | |||
@out << '[' | |||
end | |||
def finish options | |||
@out << ']' | |||
end | |||
def append data | |||
if @first | |||
@first = false | |||
else | |||
@out << ',' | |||
end | |||
@out << data.to_json | |||
end | |||
public | |||
def text_token text, kind | |||
append :type => 'text', :text => text, :kind => kind | |||
end | |||
def begin_group kind | |||
append :type => 'block', :action => 'open', :kind => kind | |||
end | |||
def end_group kind | |||
append :type => 'block', :action => 'close', :kind => kind | |||
end | |||
def begin_line kind | |||
append :type => 'block', :action => 'begin_line', :kind => kind | |||
end | |||
def end_line kind | |||
append :type => 'block', :action => 'end_line', :kind => kind | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,45 @@ | |||
module CodeRay | |||
module Encoders | |||
# Counts the LoC (Lines of Code). Returns an Integer >= 0. | |||
# | |||
# Alias: +loc+ | |||
# | |||
# Everything that is not comment, markup, doctype/shebang, or an empty line, | |||
# is considered to be code. | |||
# | |||
# For example, | |||
# * HTML files not containing JavaScript have 0 LoC | |||
# * in a Java class without comments, LoC is the number of non-empty lines | |||
# | |||
# A Scanner class should define the token kinds that are not code in the | |||
# KINDS_NOT_LOC constant, which defaults to [:comment, :doctype]. | |||
class LinesOfCode < TokenKindFilter | |||
register_for :lines_of_code | |||
NON_EMPTY_LINE = /^\s*\S.*$/ | |||
protected | |||
def setup options | |||
if scanner | |||
kinds_not_loc = scanner.class::KINDS_NOT_LOC | |||
else | |||
warn "Tokens have no associated scanner, counting all nonempty lines." if $VERBOSE | |||
kinds_not_loc = CodeRay::Scanners::Scanner::KINDS_NOT_LOC | |||
end | |||
options[:exclude] = kinds_not_loc | |||
super options | |||
end | |||
def finish options | |||
output @tokens.text.scan(NON_EMPTY_LINE).size | |||
end | |||
end | |||
end | |||
end |
@@ -1,26 +1,18 @@ | |||
module CodeRay | |||
module Encoders | |||
# = Null Encoder | |||
# | |||
# Does nothing and returns an empty string. | |||
class Null < Encoder | |||
include Streamable | |||
register_for :null | |||
# Defined for faster processing | |||
def to_proc | |||
proc {} | |||
end | |||
protected | |||
def token(*) | |||
def text_token text, kind | |||
# do nothing | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,24 @@ | |||
module CodeRay | |||
module Encoders | |||
load :html | |||
# Wraps the output into a HTML page, using CSS classes and | |||
# line numbers in the table format by default. | |||
# | |||
# See Encoders::HTML for available options. | |||
class Page < HTML | |||
FILE_EXTENSION = 'html' | |||
register_for :page | |||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ | |||
:css => :class, | |||
:wrap => :page, | |||
:line_numbers => :table | |||
end | |||
end | |||
end |
@@ -0,0 +1,23 @@ | |||
module CodeRay | |||
module Encoders | |||
load :html | |||
# Wraps HTML output into a SPAN element, using inline styles by default. | |||
# | |||
# See Encoders::HTML for available options. | |||
class Span < HTML | |||
FILE_EXTENSION = 'span.html' | |||
register_for :span | |||
DEFAULT_OPTIONS = HTML::DEFAULT_OPTIONS.merge \ | |||
:css => :style, | |||
:wrap => :span, | |||
:line_numbers => false | |||
end | |||
end | |||
end |
@@ -1,43 +1,27 @@ | |||
module CodeRay | |||
module Encoders | |||
# Makes a statistic for the given tokens. | |||
# | |||
# Alias: +stats+ | |||
class Statistic < Encoder | |||
include Streamable | |||
register_for :stats, :statistic | |||
attr_reader :type_stats, :real_token_count | |||
register_for :statistic | |||
attr_reader :type_stats, :real_token_count # :nodoc: | |||
TypeStats = Struct.new :count, :size # :nodoc: | |||
protected | |||
TypeStats = Struct.new :count, :size | |||
def setup options | |||
super | |||
@type_stats = Hash.new { |h, k| h[k] = TypeStats.new 0, 0 } | |||
@real_token_count = 0 | |||
end | |||
def generate tokens, options | |||
@tokens = tokens | |||
super | |||
end | |||
def text_token text, kind | |||
@real_token_count += 1 unless kind == :space | |||
@type_stats[kind].count += 1 | |||
@type_stats[kind].size += text.size | |||
@type_stats['TOTAL'].size += text.size | |||
@type_stats['TOTAL'].count += 1 | |||
end | |||
# TODO Hierarchy handling | |||
def block_token action, kind | |||
@type_stats['TOTAL'].count += 1 | |||
@type_stats['open/close'].count += 1 | |||
end | |||
STATS = <<-STATS | |||
STATS = <<-STATS # :nodoc: | |||
Code Statistics | |||
@@ -49,12 +33,12 @@ Token Types (%d): | |||
type count ratio size (average) | |||
------------------------------------------------------------- | |||
%s | |||
STATS | |||
# space 12007 33.81 % 1.7 | |||
TOKEN_TYPES_ROW = <<-TKR | |||
STATS | |||
TOKEN_TYPES_ROW = <<-TKR # :nodoc: | |||
%-20s %8d %6.2f %% %5.1f | |||
TKR | |||
TKR | |||
def finish options | |||
all = @type_stats['TOTAL'] | |||
all_count, all_size = all.count, all.size | |||
@@ -64,14 +48,49 @@ Token Types (%d): | |||
types_stats = @type_stats.sort_by { |k, v| [-v.count, k.to_s] }.map do |k, v| | |||
TOKEN_TYPES_ROW % [k, v.count, 100.0 * v.count / all_count, v.size] | |||
end.join | |||
STATS % [ | |||
@out << STATS % [ | |||
all_count, @real_token_count, all_size, | |||
@type_stats.delete_if { |k, v| k.is_a? String }.size, | |||
types_stats | |||
] | |||
super | |||
end | |||
public | |||
def text_token text, kind | |||
@real_token_count += 1 unless kind == :space | |||
@type_stats[kind].count += 1 | |||
@type_stats[kind].size += text.size | |||
@type_stats['TOTAL'].size += text.size | |||
@type_stats['TOTAL'].count += 1 | |||
end | |||
# TODO Hierarchy handling | |||
def begin_group kind | |||
block_token ':begin_group', kind | |||
end | |||
def end_group kind | |||
block_token ':end_group', kind | |||
end | |||
def begin_line kind | |||
block_token ':begin_line', kind | |||
end | |||
def end_line kind | |||
block_token ':end_line', kind | |||
end | |||
def block_token action, kind | |||
@type_stats['TOTAL'].count += 1 | |||
@type_stats[action].count += 1 | |||
@type_stats[kind].count += 1 | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,179 @@ | |||
module CodeRay | |||
module Encoders | |||
# Outputs code highlighted for a color terminal. | |||
# | |||
# Note: This encoder is in beta. It currently doesn't use the Styles. | |||
# | |||
# Alias: +term+ | |||
# | |||
# == Authors & License | |||
# | |||
# By Rob Aldred (http://robaldred.co.uk) | |||
# | |||
# Based on idea by Nathan Weizenbaum (http://nex-3.com) | |||
# | |||
# MIT License (http://www.opensource.org/licenses/mit-license.php) | |||
class Terminal < Encoder | |||
register_for :terminal | |||
TOKEN_COLORS = { | |||
:annotation => '35', | |||
:attribute_name => '33', | |||
:attribute_value => '31', | |||
:binary => '1;35', | |||
:char => { | |||
:self => '36', :delimiter => '34' | |||
}, | |||
:class => '1;35', | |||
:class_variable => '36', | |||
:color => '32', | |||
:comment => '37', | |||
:complex => '34', | |||
:constant => ['34', '4'], | |||
:decoration => '35', | |||
:definition => '1;32', | |||
:directive => ['32', '4'], | |||
:doc => '46', | |||
:doctype => '1;30', | |||
:doc_string => ['31', '4'], | |||
:entity => '33', | |||
:error => ['1;33', '41'], | |||
:exception => '1;31', | |||
:float => '1;35', | |||
:function => '1;34', | |||
:global_variable => '42', | |||
:hex => '1;36', | |||
:include => '33', | |||
:integer => '1;34', | |||
:key => '35', | |||
:label => '1;15', | |||
:local_variable => '33', | |||
:octal => '1;35', | |||
:operator_name => '1;29', | |||
:predefined_constant => '1;36', | |||
:predefined_type => '1;30', | |||
:predefined => ['4', '1;34'], | |||
:preprocessor => '36', | |||
:pseudo_class => '34', | |||
:regexp => { | |||
:self => '31', | |||
:content => '31', | |||
:delimiter => '1;29', | |||
:modifier => '35', | |||
:function => '1;29' | |||
}, | |||
:reserved => '1;31', | |||
:shell => { | |||
:self => '42', | |||
:content => '1;29', | |||
:delimiter => '37', | |||
}, | |||
:string => { | |||
:self => '32', | |||
:modifier => '1;32', | |||
:escape => '1;36', | |||
:delimiter => '1;32', | |||
}, | |||
:symbol => '1;32', | |||
:tag => '34', | |||
:type => '1;34', | |||
:value => '36', | |||
:variable => '34', | |||
:insert => '42', | |||
:delete => '41', | |||
:change => '44', | |||
:head => '45' | |||
} | |||
TOKEN_COLORS[:keyword] = TOKEN_COLORS[:reserved] | |||
TOKEN_COLORS[:method] = TOKEN_COLORS[:function] | |||
TOKEN_COLORS[:imaginary] = TOKEN_COLORS[:complex] | |||
TOKEN_COLORS[:begin_group] = TOKEN_COLORS[:end_group] = | |||
TOKEN_COLORS[:escape] = TOKEN_COLORS[:delimiter] | |||
protected | |||
def setup(options) | |||
super | |||
@opened = [] | |||
@subcolors = nil | |||
end | |||
public | |||
def text_token text, kind | |||
if color = (@subcolors || TOKEN_COLORS)[kind] | |||
if Hash === color | |||
if color[:self] | |||
color = color[:self] | |||
else | |||
@out << text | |||
return | |||
end | |||
end | |||
@out << ansi_colorize(color) | |||
@out << text.gsub("\n", ansi_clear + "\n" + ansi_colorize(color)) | |||
@out << ansi_clear | |||
@out << ansi_colorize(@subcolors[:self]) if @subcolors && @subcolors[:self] | |||
else | |||
@out << text | |||
end | |||
end | |||
def begin_group kind | |||
@opened << kind | |||
@out << open_token(kind) | |||
end | |||
alias begin_line begin_group | |||
def end_group kind | |||
if @opened.empty? | |||
# nothing to close | |||
else | |||
@opened.pop | |||
@out << ansi_clear | |||
@out << open_token(@opened.last) | |||
end | |||
end | |||
def end_line kind | |||
if @opened.empty? | |||
# nothing to close | |||
else | |||
@opened.pop | |||
# whole lines to be highlighted, | |||
# eg. added/modified/deleted lines in a diff | |||
@out << "\t" * 100 + ansi_clear | |||
@out << open_token(@opened.last) | |||
end | |||
end | |||
private | |||
def open_token kind | |||
if color = TOKEN_COLORS[kind] | |||
if Hash === color | |||
@subcolors = color | |||
ansi_colorize(color[:self]) if color[:self] | |||
else | |||
@subcolors = {} | |||
ansi_colorize(color) | |||
end | |||
else | |||
@subcolors = nil | |||
'' | |||
end | |||
end | |||
def ansi_colorize(color) | |||
Array(color).map { |c| "\e[#{c}m" }.join | |||
end | |||
def ansi_clear | |||
ansi_colorize(0) | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,46 @@ | |||
module CodeRay | |||
module Encoders | |||
# Concats the tokens into a single string, resulting in the original | |||
# code string if no tokens were removed. | |||
# | |||
# Alias: +plain+, +plaintext+ | |||
# | |||
# == Options | |||
# | |||
# === :separator | |||
# A separator string to join the tokens. | |||
# | |||
# Default: empty String | |||
class Text < Encoder | |||
register_for :text | |||
FILE_EXTENSION = 'txt' | |||
DEFAULT_OPTIONS = { | |||
:separator => nil | |||
} | |||
def text_token text, kind | |||
super | |||
if @first | |||
@first = false | |||
else | |||
@out << @sep | |||
end if @sep | |||
end | |||
protected | |||
def setup options | |||
super | |||
@first = true | |||
@sep = options[:separator] | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,111 @@ | |||
module CodeRay | |||
module Encoders | |||
load :filter | |||
# A Filter that selects tokens based on their token kind. | |||
# | |||
# == Options | |||
# | |||
# === :exclude | |||
# | |||
# One or many symbols (in an Array) which shall be excluded. | |||
# | |||
# Default: [] | |||
# | |||
# === :include | |||
# | |||
# One or many symbols (in an array) which shall be included. | |||
# | |||
# Default: :all, which means all tokens are included. | |||
# | |||
# Exclusion wins over inclusion. | |||
# | |||
# See also: CommentFilter | |||
class TokenKindFilter < Filter | |||
register_for :token_kind_filter | |||
DEFAULT_OPTIONS = { | |||
:exclude => [], | |||
:include => :all | |||
} | |||
protected | |||
def setup options | |||
super | |||
@group_excluded = false | |||
@exclude = options[:exclude] | |||
@exclude = Array(@exclude) unless @exclude == :all | |||
@include = options[:include] | |||
@include = Array(@include) unless @include == :all | |||
end | |||
def include_text_token? text, kind | |||
include_group? kind | |||
end | |||
def include_group? kind | |||
(@include == :all || @include.include?(kind)) && | |||
!(@exclude == :all || @exclude.include?(kind)) | |||
end | |||
public | |||
# Add the token to the output stream if +kind+ matches the conditions. | |||
def text_token text, kind | |||
super if !@group_excluded && include_text_token?(text, kind) | |||
end | |||
# Add the token group to the output stream if +kind+ matches the | |||
# conditions. | |||
# | |||
# If it does not, all tokens inside the group are excluded from the | |||
# stream, even if their kinds match. | |||
def begin_group kind | |||
if @group_excluded | |||
@group_excluded += 1 | |||
elsif include_group? kind | |||
super | |||
else | |||
@group_excluded = 1 | |||
end | |||
end | |||
# See +begin_group+. | |||
def begin_line kind | |||
if @group_excluded | |||
@group_excluded += 1 | |||
elsif include_group? kind | |||
super | |||
else | |||
@group_excluded = 1 | |||
end | |||
end | |||
# Take care of re-enabling the delegation of tokens to the output stream | |||
# if an exluded group has ended. | |||
def end_group kind | |||
if @group_excluded | |||
@group_excluded -= 1 | |||
@group_excluded = false if @group_excluded.zero? | |||
else | |||
super | |||
end | |||
end | |||
# See +end_group+. | |||
def end_line kind | |||
if @group_excluded | |||
@group_excluded -= 1 | |||
@group_excluded = false if @group_excluded.zero? | |||
else | |||
super | |||
end | |||
end | |||
end | |||
end | |||
end |
@@ -1,39 +1,40 @@ | |||
module CodeRay | |||
module Encoders | |||
# = XML Encoder | |||
# | |||
# Uses REXML. Very slow. | |||
class XML < Encoder | |||
include Streamable | |||
register_for :xml | |||
FILE_EXTENSION = 'xml' | |||
require 'rexml/document' | |||
autoload :REXML, 'rexml/document' | |||
DEFAULT_OPTIONS = { | |||
:tab_width => 8, | |||
:pretty => -1, | |||
:transitive => false, | |||
} | |||
protected | |||
def setup options | |||
super | |||
@doc = REXML::Document.new | |||
@doc << REXML::XMLDecl.new | |||
@tab_width = options[:tab_width] | |||
@root = @node = @doc.add_element('coderay-tokens') | |||
end | |||
def finish options | |||
@out = '' | |||
@doc.write @out, options[:pretty], options[:transitive], true | |||
@out | |||
super | |||
end | |||
public | |||
def text_token text, kind | |||
if kind == :space | |||
token = @node | |||
@@ -53,19 +54,19 @@ module Encoders | |||
end | |||
end | |||
end | |||
def open_token kind | |||
def begin_group kind | |||
@node = @node.add_element kind.to_s | |||
end | |||
def close_token kind | |||
def end_group kind | |||
if @node == @root | |||
raise 'no token to close!' | |||
end | |||
@node = @node.parent | |||
end | |||
end | |||
end | |||
end |
@@ -0,0 +1,50 @@ | |||
autoload :YAML, 'yaml' | |||
module CodeRay | |||
module Encoders | |||
# = YAML Encoder | |||
# | |||
# Slow. | |||
class YAML < Encoder | |||
register_for :yaml | |||
FILE_EXTENSION = 'yaml' | |||
protected | |||
def setup options | |||
super | |||
@data = [] | |||
end | |||
def finish options | |||
output ::YAML.dump(@data) | |||
end | |||
public | |||
def text_token text, kind | |||
@data << [text, kind] | |||
end | |||
def begin_group kind | |||
@data << [:begin_group, kind] | |||
end | |||
def end_group kind | |||
@data << [:end_group, kind] | |||
end | |||
def begin_line kind | |||
@data << [:begin_line, kind] | |||
end | |||
def end_line kind | |||
@data << [:end_line, kind] | |||
end | |||
end | |||
end | |||
end |
@@ -30,7 +30,7 @@ module CodeRay | |||
end | |||
RedCloth::TextileDoc.send :include, ForRedCloth::TextileDoc | |||
RedCloth::Formatters::HTML.module_eval do | |||
def unescape(html) | |||
def unescape(html) # :nodoc: | |||
replacements = { | |||
'&' => '&', | |||
'"' => '"', | |||
@@ -45,7 +45,7 @@ module CodeRay | |||
if !opts[:lang] && RedCloth::VERSION.to_s >= '4.2.0' | |||
# simulating pre-4.2 behavior | |||
if opts[:text].sub!(/\A\[(\w+)\]/, '') | |||
if CodeRay::Scanners[$1].plugin_id == 'plaintext' | |||
if CodeRay::Scanners[$1].lang == :text | |||
opts[:text] = $& + opts[:text] | |||
else | |||
opts[:lang] = $1 | |||
@@ -57,7 +57,7 @@ module CodeRay | |||
@in_bc ||= nil | |||
format = @in_bc ? :div : :span | |||
opts[:text] = unescape(opts[:text]) unless @in_bc | |||
highlighted_code = CodeRay.encode opts[:text], opts[:lang], format, :stream => true | |||
highlighted_code = CodeRay.encode opts[:text], opts[:lang], format | |||
highlighted_code.sub!(/\A<(span|div)/) { |m| m + pba(@in_bc || opts) } | |||
highlighted_code | |||
else | |||
@@ -74,7 +74,7 @@ module CodeRay | |||
@in_bc = nil | |||
opts[:lang] ? '' : "</pre>\n" | |||
end | |||
def escape_pre(text) | |||
def escape_pre(text) # :nodoc: | |||
if @in_bc ||= nil | |||
text | |||
else |
@@ -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 |
@@ -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 |
@@ -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 |