eden 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +4 -0
- data/LICENSE +20 -0
- data/README.md +48 -0
- data/Rakefile +10 -0
- data/bin/eden +132 -0
- data/lib/eden.rb +10 -0
- data/lib/eden/defaults.rb +26 -0
- data/lib/eden/formatter.rb +25 -0
- data/lib/eden/formatters/block_formatter.rb +45 -0
- data/lib/eden/formatters/indenter.rb +91 -0
- data/lib/eden/formatters/white_space_cleaner.rb +14 -0
- data/lib/eden/line.rb +65 -0
- data/lib/eden/source_file.rb +32 -0
- data/lib/eden/token.rb +62 -0
- data/lib/eden/tokenizer.rb +259 -0
- data/lib/eden/tokenizers/basic_tokenizer.rb +167 -0
- data/lib/eden/tokenizers/delimited_literal_tokenizer.rb +38 -0
- data/lib/eden/tokenizers/number_tokenizer.rb +68 -0
- data/lib/eden/tokenizers/operator_tokenizer.rb +211 -0
- data/lib/eden/tokenizers/regex_tokenizer.rb +37 -0
- data/lib/eden/tokenizers/string_tokenizer.rb +149 -0
- data/test/array_literal_tokenization_test.rb +43 -0
- data/test/basic_tokenization_test.rb +29 -0
- data/test/block_formatter_test.rb +47 -0
- data/test/class_var_token_test.rb +21 -0
- data/test/identifier_token_test.rb +140 -0
- data/test/indenter_test.rb +314 -0
- data/test/instance_var_token_test.rb +48 -0
- data/test/number_tokenization_test.rb +83 -0
- data/test/operator_tokenization_test.rb +180 -0
- data/test/regex_tokenization_test.rb +68 -0
- data/test/single_character_tokenization_test.rb +87 -0
- data/test/string_tokenization_test.rb +291 -0
- data/test/symbol_tokenization_test.rb +64 -0
- data/test/test_helper.rb +13 -0
- data/test/white_space_cleaner_test.rb +35 -0
- data/test/whitespace_token_test.rb +63 -0
- metadata +108 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Jason Langenauer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Eden
|
2
|
+
|
3
|
+
Eden is a source-code formatter for Ruby. It's designed around a robust lexical analyzer able to handle
|
4
|
+
most of the dark corners of Ruby syntax, and able to be easily modified and extended.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Eden is available as a gem. To install it, simply run:
|
9
|
+
|
10
|
+
gem install eden
|
11
|
+
|
12
|
+
## Configuration
|
13
|
+
|
14
|
+
Eden's formatter modules are able to be configured using a Ruby configuration DSL. Eden loads defaults when it
|
15
|
+
starts, but if you wish to override options on a project-specific basis, you can do so by creating
|
16
|
+
a file `config/eden.rb`. For example, if you wish to use tabs instead of spaces to indent a project's code, put
|
17
|
+
the following in `config/eden.rb`:
|
18
|
+
|
19
|
+
Indenter.configure do |i|
|
20
|
+
i.indent_characters_per_step 1
|
21
|
+
i.indent_character "\t"
|
22
|
+
end
|
23
|
+
|
24
|
+
Refer to `lib/eden/defaults.rb` for full configuration options.
|
25
|
+
|
26
|
+
## Using Eden
|
27
|
+
|
28
|
+
Eden is designed to be used with a source-control system, so when it formats Ruby source files, it writes changes
|
29
|
+
to the file in place. The basic format for running Eden is:
|
30
|
+
|
31
|
+
eden command filenames
|
32
|
+
|
33
|
+
Eden understands 2 commands:
|
34
|
+
|
35
|
+
* `colorize` - Displays a ANSI colorized version of the source. This is mainly used for debugging the lexer.
|
36
|
+
* `rewrite` - Rewrites the source files in place to be correctly formatted
|
37
|
+
|
38
|
+
Examples:
|
39
|
+
|
40
|
+
eden rewrite source_file.rb
|
41
|
+
|
42
|
+
will rewrite source_file.rb
|
43
|
+
|
44
|
+
eden rewrite ./**/*.rb
|
45
|
+
|
46
|
+
will rewrite all the .rb files in the current directory and any subdirectories.
|
47
|
+
|
48
|
+
Eden was created by Jason Langenauer (jason@jasonlangenauer.com or @jasonlangenauer on Twitter). Any feedback/comments/suggestions gratefully received.
|
data/Rakefile
ADDED
data/bin/eden
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'eden'
|
6
|
+
|
7
|
+
# Automatically load all the formatters
|
8
|
+
formatters = []
|
9
|
+
|
10
|
+
# Taken from ActiveSupport
|
11
|
+
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
12
|
+
if first_letter_in_uppercase
|
13
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
14
|
+
else
|
15
|
+
lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Load all the formatters
|
20
|
+
Dir.glob([File.dirname(__FILE__) + "/../lib/eden/formatters/*.rb"] ) do |file|
|
21
|
+
require "#{file}"
|
22
|
+
const_name = camelize( File.basename(file, ".rb"))
|
23
|
+
formatters << Object.const_get( const_name )
|
24
|
+
end
|
25
|
+
|
26
|
+
# Setup defaults
|
27
|
+
require 'eden/defaults'
|
28
|
+
|
29
|
+
# Load formatting customizations
|
30
|
+
if File.exists?("./config/eden.rb")
|
31
|
+
require './config/eden.rb'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Displays a source file on STDOUT using ANSI escape codes for
|
35
|
+
# syntax highlighting
|
36
|
+
def colorize( sf )
|
37
|
+
sf.lines.each do |line|
|
38
|
+
print "[#{line.line_no}] "
|
39
|
+
line.tokens.flatten.each do |t|
|
40
|
+
case t.type
|
41
|
+
when :regex
|
42
|
+
print "\033[32m"
|
43
|
+
print t.content
|
44
|
+
print "\033[0m"
|
45
|
+
when :double_q_string, :single_q_string
|
46
|
+
print "\033[0;36m" + t.content + "\033[0m"
|
47
|
+
when :symbol
|
48
|
+
print "\033[31m" + t.content + "\033[0m"
|
49
|
+
when :instancevar
|
50
|
+
print "\033[1;34m" + t.content + "\033[0m"
|
51
|
+
when :comment
|
52
|
+
print "\033[1;30m" + t.content + "\033[0m"
|
53
|
+
else
|
54
|
+
if t.keyword?
|
55
|
+
print "\033[33m" + t.content + "\033[0m"
|
56
|
+
else
|
57
|
+
print t.content
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def debug( source_file )
|
65
|
+
source_file.lines.each do |l|
|
66
|
+
puts l.tokens.inspect
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Load default options
|
72
|
+
options = {}
|
73
|
+
|
74
|
+
# Command-line options parser
|
75
|
+
opts = OptionParser.new do |opts|
|
76
|
+
options[:recurse] = false
|
77
|
+
opts.banner = <<BANNER
|
78
|
+
usage: eden command [options] source_files
|
79
|
+
--
|
80
|
+
Valid command options:
|
81
|
+
colorize - display source file with syntax highlighting
|
82
|
+
rewrite - rewrite the source file in place with adjusted formatting
|
83
|
+
|
84
|
+
Options:
|
85
|
+
BANNER
|
86
|
+
opts.on('-R', '--recursive', "Recursively search subdirectories") do
|
87
|
+
options[:recurse] = true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
source_files = []
|
92
|
+
|
93
|
+
# Parse the command line, and find out what we want to do
|
94
|
+
opts.parse!
|
95
|
+
cmd = ARGV.shift
|
96
|
+
|
97
|
+
unless cmd
|
98
|
+
puts opts
|
99
|
+
exit
|
100
|
+
end
|
101
|
+
|
102
|
+
cmd = cmd.downcase.to_sym
|
103
|
+
|
104
|
+
unless [:colorize, :analyse, :rewrite, :debug].include?(cmd)
|
105
|
+
puts opts
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
|
109
|
+
sf = nil
|
110
|
+
|
111
|
+
begin
|
112
|
+
ARGV.each do |f|
|
113
|
+
sf = Eden::SourceFile.new( f )
|
114
|
+
sf.load!
|
115
|
+
sf.tokenize!
|
116
|
+
formatters.each do |formatter|
|
117
|
+
formatter.format( sf )
|
118
|
+
end
|
119
|
+
source_files << sf
|
120
|
+
case cmd
|
121
|
+
when :colorize then colorize( sf )
|
122
|
+
when :rewrite then sf.rewrite!
|
123
|
+
when :analyse then analyse( sf )
|
124
|
+
when :debug then debug( sf )
|
125
|
+
end
|
126
|
+
end
|
127
|
+
rescue => e
|
128
|
+
puts e
|
129
|
+
sf.lines.each { |l| puts l.joined_tokens }
|
130
|
+
end
|
131
|
+
|
132
|
+
|
data/lib/eden.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
WhiteSpaceCleaner.configure do |w|
|
2
|
+
# Remove any white space after the last non-whitespace token?
|
3
|
+
w.remove_trailing_whitespace true
|
4
|
+
end
|
5
|
+
|
6
|
+
Indenter.configure do |i|
|
7
|
+
# Make any change to code indents at all?
|
8
|
+
i.adjust_indents true
|
9
|
+
|
10
|
+
# What character should be used to pad the indents
|
11
|
+
i.indent_character ' '
|
12
|
+
|
13
|
+
# How many of the indent_character should be used at each level of indent
|
14
|
+
i.indent_characters_per_step 2
|
15
|
+
end
|
16
|
+
|
17
|
+
BlockFormatter.configure do |b|
|
18
|
+
# Modify block spacing at all?
|
19
|
+
b.adjust_blocks true
|
20
|
+
|
21
|
+
# What charater to pad blocks with?
|
22
|
+
b.padding_character " "
|
23
|
+
|
24
|
+
# How many padding characters to use at each end of the inline block?
|
25
|
+
b.padding_character_count 1
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Eden
|
2
|
+
# Formatter
|
3
|
+
#
|
4
|
+
# A base class providing configuration behaviour for Eden's source code
|
5
|
+
# formatters.
|
6
|
+
class Formatter
|
7
|
+
class << self
|
8
|
+
attr_accessor :options
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.configure
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.format( source_file )
|
16
|
+
raise "Format function not implmented."
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.method_missing(name, *args, &block)
|
20
|
+
self.options ||= {}
|
21
|
+
self.options[name.to_sym] = *args[0]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# BlockFormatter
|
2
|
+
#
|
3
|
+
# Formats inline blocks ( including string interpolations ) by formatting the correct spacing
|
4
|
+
# around the content of the block.
|
5
|
+
#
|
6
|
+
# Options:
|
7
|
+
#
|
8
|
+
# - adjust_blocks - (true|false) Make any changes at all to blocks?
|
9
|
+
# - padding_character - (string) character to use to space between the content of the block and its braces
|
10
|
+
# - padding_character_count - (integer) number of spaces to use after/before each brace. Can be 0.
|
11
|
+
|
12
|
+
class BlockFormatter < Eden::Formatter
|
13
|
+
def self.format( source_file )
|
14
|
+
return unless options[:adjust_blocks]
|
15
|
+
|
16
|
+
space = (options[:padding_character] * options[:padding_character_count])
|
17
|
+
use_space = space.length != 0
|
18
|
+
|
19
|
+
source_file.each_line do |line|
|
20
|
+
line.tokens.each do |token|
|
21
|
+
if token.is?( :lcurly )
|
22
|
+
next_token = line.next_token( token )
|
23
|
+
if next_token && next_token.content != space
|
24
|
+
if next_token.is?(:whitespace)
|
25
|
+
next_token.content == space
|
26
|
+
else
|
27
|
+
new_space_token = Eden::Token.new(:whitespace, space)
|
28
|
+
line.insert_token_after(token, new_space_token)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
elsif token.is?( :rcurly )
|
32
|
+
prev_token = line.previous_token( token )
|
33
|
+
if prev_token && prev_token.content != space
|
34
|
+
if prev_token.is?( :whitespace )
|
35
|
+
prev_token.content == space
|
36
|
+
else
|
37
|
+
new_space_token = Eden::Token.new(:whitespace, space)
|
38
|
+
line.insert_token_before(token, new_space_token)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class Indenter < Eden::Formatter
|
2
|
+
def self.format( source_file )
|
3
|
+
return unless options[:adjust_indents]
|
4
|
+
@current_indent = 0
|
5
|
+
source_file.each_line do |line|
|
6
|
+
next if line.tokens[0] && line.tokens[0].is?(:heredoc_body)
|
7
|
+
calculate_pre_indent(line)
|
8
|
+
adjust_indent(line)
|
9
|
+
calculate_post_indent(line)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
# Calculates changes in indent relative to the previous line due to tokens in the current line
|
15
|
+
def self.calculate_pre_indent( line )
|
16
|
+
line.tokens.each do |t|
|
17
|
+
if [:end, :rescue, :else, :elsif].include?(t.type)
|
18
|
+
@current_indent -= 1
|
19
|
+
line.tokens.each do |tok|
|
20
|
+
increase_indent! if [:class, :def, :module].include?(tok.type)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if t.is?( :when )
|
25
|
+
decrease_indent!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@current_indent = 0 if @current_indent < 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.calculate_post_indent( line )
|
32
|
+
line.tokens.each do |t|
|
33
|
+
if [:class, :def, :module, :do, :begin, :rescue, :if, :else, :elsif, :case, :unless].include?(t.type)
|
34
|
+
increase_indent!
|
35
|
+
|
36
|
+
# Handle suffix conditionals
|
37
|
+
if [:if, :unless].include?(t.type)
|
38
|
+
prev_token = line.previous_non_whitespace_token(t)
|
39
|
+
decrease_indent! if !prev_token.nil? || (prev_token && prev_token.type == :equals)
|
40
|
+
end
|
41
|
+
|
42
|
+
line.tokens.each do |tok|
|
43
|
+
decrease_indent! if tok.is?( :end )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if [:for, :until, :while].include?(t.type)
|
48
|
+
increase_indent!
|
49
|
+
|
50
|
+
if [:until, :while].include?(t.type)
|
51
|
+
prev_token = line.previous_non_whitespace_token(t)
|
52
|
+
decrease_indent! if !prev_token.nil? || (prev_token && prev_token.type == :equals)
|
53
|
+
end
|
54
|
+
|
55
|
+
line.tokens.each do |tok|
|
56
|
+
decrease_indent! if tok.is?( :do )
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if t.is?(:when)
|
61
|
+
increase_indent!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.adjust_indent( line )
|
67
|
+
return unless line.tokens[0]
|
68
|
+
if @current_indent == 0
|
69
|
+
line.tokens.delete_at(0) if line.tokens[0].type == :whitespace
|
70
|
+
else
|
71
|
+
if line.tokens[0].is?( :whitespace )
|
72
|
+
line.tokens[0].content = indent_content
|
73
|
+
else
|
74
|
+
indent_token = Eden::Token.new( :whitespace, indent_content )
|
75
|
+
line.tokens.unshift(indent_token)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.indent_content
|
81
|
+
options[:indent_character] * (options[:indent_characters_per_step] * @current_indent)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.increase_indent!
|
85
|
+
@current_indent += 1
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.decrease_indent!
|
89
|
+
@current_indent -=1 if @current_indent > 0
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class WhiteSpaceCleaner < Eden::Formatter
|
2
|
+
def self.format( source_file )
|
3
|
+
return unless options[:remove_trailing_whitespace]
|
4
|
+
|
5
|
+
source_file.each_line do |l|
|
6
|
+
i = l.tokens.size - 1
|
7
|
+
while( i >= 0 )
|
8
|
+
break unless l.tokens[i].is?( :whitespace ) || l.tokens[i].is?( :newline )
|
9
|
+
l.tokens.delete_at(i) if l.tokens[i].is?( :whitespace )
|
10
|
+
i -= 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/eden/line.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Eden
|
2
|
+
class Line
|
3
|
+
attr_accessor :line_no, :tokens
|
4
|
+
|
5
|
+
def initialize( line_no )
|
6
|
+
@line_no = line_no
|
7
|
+
@tokens = []
|
8
|
+
@warnings = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def flatten!
|
12
|
+
@tokens.flatten!
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def last_token_is_space?
|
17
|
+
@tokens[-1].type == :whitespace
|
18
|
+
end
|
19
|
+
|
20
|
+
def joined_tokens
|
21
|
+
tokens.map { |t| t.content }.join('')
|
22
|
+
end
|
23
|
+
|
24
|
+
def previous_token( token )
|
25
|
+
token_index = tokens.index( token )
|
26
|
+
return nil if token_index.nil? || token_index == 0
|
27
|
+
return tokens[token_index-1]
|
28
|
+
end
|
29
|
+
|
30
|
+
def previous_non_whitespace_token( token )
|
31
|
+
token_index = tokens.index( token )
|
32
|
+
return nil if token_index.nil? || token_index == 0
|
33
|
+
token_index -= 1
|
34
|
+
while( token_index != 0 )
|
35
|
+
return tokens[token_index] if tokens[token_index].type != :whitespace
|
36
|
+
token_index -= 1
|
37
|
+
end
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def next_token( token )
|
42
|
+
token_index = tokens.index( token )
|
43
|
+
return nil if token_index.nil? || token_index == tokens.length
|
44
|
+
return tokens[token_index+1]
|
45
|
+
end
|
46
|
+
|
47
|
+
def insert_token_after( token, new_token )
|
48
|
+
token_index = tokens.index( token )
|
49
|
+
if token_index.nil?
|
50
|
+
tokens.push( new_token )
|
51
|
+
else
|
52
|
+
tokens.insert( token_index+1, new_token )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def insert_token_before( token, new_token )
|
57
|
+
token_index = tokens.index( token )
|
58
|
+
if token_index.nil?
|
59
|
+
tokens.unshift( new_token )
|
60
|
+
else
|
61
|
+
tokens.insert( token_index, new_token )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|