eden 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/CHANGELOG +4 -0
  2. data/LICENSE +20 -0
  3. data/README.md +48 -0
  4. data/Rakefile +10 -0
  5. data/bin/eden +132 -0
  6. data/lib/eden.rb +10 -0
  7. data/lib/eden/defaults.rb +26 -0
  8. data/lib/eden/formatter.rb +25 -0
  9. data/lib/eden/formatters/block_formatter.rb +45 -0
  10. data/lib/eden/formatters/indenter.rb +91 -0
  11. data/lib/eden/formatters/white_space_cleaner.rb +14 -0
  12. data/lib/eden/line.rb +65 -0
  13. data/lib/eden/source_file.rb +32 -0
  14. data/lib/eden/token.rb +62 -0
  15. data/lib/eden/tokenizer.rb +259 -0
  16. data/lib/eden/tokenizers/basic_tokenizer.rb +167 -0
  17. data/lib/eden/tokenizers/delimited_literal_tokenizer.rb +38 -0
  18. data/lib/eden/tokenizers/number_tokenizer.rb +68 -0
  19. data/lib/eden/tokenizers/operator_tokenizer.rb +211 -0
  20. data/lib/eden/tokenizers/regex_tokenizer.rb +37 -0
  21. data/lib/eden/tokenizers/string_tokenizer.rb +149 -0
  22. data/test/array_literal_tokenization_test.rb +43 -0
  23. data/test/basic_tokenization_test.rb +29 -0
  24. data/test/block_formatter_test.rb +47 -0
  25. data/test/class_var_token_test.rb +21 -0
  26. data/test/identifier_token_test.rb +140 -0
  27. data/test/indenter_test.rb +314 -0
  28. data/test/instance_var_token_test.rb +48 -0
  29. data/test/number_tokenization_test.rb +83 -0
  30. data/test/operator_tokenization_test.rb +180 -0
  31. data/test/regex_tokenization_test.rb +68 -0
  32. data/test/single_character_tokenization_test.rb +87 -0
  33. data/test/string_tokenization_test.rb +291 -0
  34. data/test/symbol_tokenization_test.rb +64 -0
  35. data/test/test_helper.rb +13 -0
  36. data/test/white_space_cleaner_test.rb +35 -0
  37. data/test/whitespace_token_test.rb +63 -0
  38. metadata +108 -0
data/CHANGELOG ADDED
@@ -0,0 +1,4 @@
1
+ 0.1.1
2
+ - Fixed banner message displayed when no params passed to eden.
3
+
4
+ 0.1.0 -- Initial Release
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
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib'
7
+ test_files = FileList['test/**/*_test.rb']
8
+ t.test_files = test_files
9
+ t.verbose = true
10
+ end
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,10 @@
1
+ require 'eden/token'
2
+ require 'eden/tokenizer'
3
+ require 'eden/source_file'
4
+ require 'eden/line'
5
+
6
+ require 'eden/formatter'
7
+
8
+
9
+
10
+
@@ -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