1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
require 'open3'
require 'ripper'
require 'ruby-beautify/version'
module RubyBeautify
extend self
OPEN_BLOCK_START = ["module", "class", "begin", "def", 'if', 'while', 'unless', 'case']
BOTH_BLOCK = ["else", "elsif", 'rescue', 'when']
OPEN_BLOCK_DO = ['do', '{']
CLOSE_BLOCK = ['end', '}']
OPEN_BRACKETS = [:on_lparen, :on_lbracket, :on_lbrace, :on_embexpr_beg, :on_tlambeg]
CLOSE_BRACKETS = [:on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end]
NEW_LINES = [:on_nl, :on_ignored_nl, :on_comment, :on_embdoc_end]
def pretty_string(content, indent_token: "\t", indent_count: 1)
output_string = ""
raise "Bad Syntax" unless syntax_ok? content
lex = ::Ripper.lex(content)
indent_level = 0
line_lex = []
# walk through line tokens
lex.each do |token|
line_lex << token
if NEW_LINES.include? token[1] # if type of this token is a new line
# did we just close something? if so, lets bring it down a level.
if closing_block?(line_lex) || closing_assignment?(line_lex)
indent_level -= 1 if indent_level > 0
end
# print our line, in place.
line_string = line_lex.map {|l| l[2]}.join
output_string += indented_line(indent_level, indent_token, indent_count, line_string)
# oh, we opened something did we? lets indent for the next run.
if opening_block?(line_lex) || opening_assignment?(line_lex)
indent_level += 1
end
line_lex.clear
end
end
return output_string
end
# check the syntax of a string, will pipe it through the ruby bin to see if
# it has a valid syntax.
def syntax_ok?(string)
out, err, status = Open3.capture3("ruby -c -", stdin_data:string )
return false unless err.empty?
return true
end
# same trick as opening_block
def opening_assignment?(line_lex)
opens = opening_assignment_count line_lex
closes = closing_assignment_count line_lex
return false if opens == closes
return true if opens > closes
end
# ...
def closing_assignment?(line_lex)
opens = opening_assignment_count line_lex
closes = closing_assignment_count line_lex
return false if opens == closes
return true if closes > opens
end
# test for assignment from a block
def contains_block_assignment?(line_lex)
compacted_line = line_lex.reject{|x| x[1] == :on_sp} #remove spaces
idx = compacted_line.rindex{|x| ['=', '||='].include? x[2]} #find last equal
if idx
return OPEN_BLOCK_START.include?(compacted_line[idx+1][2]) #check for if/begin block
end
return false
end
# is the first word a key word?
def starts_block?(line_lex)
return true if contains_block_assignment? line_lex
line_lex.each do |x|
# search for a first non-space token
if not x[1] == :on_sp
return x[1] == :on_kw && OPEN_BLOCK_START.include?(x[2])
end
end
end
# is the first word one of our 'both' keywords?
def both_block?(line_lex)
line_lex.each do |x|
# search for a first non-space token
if not x[1] == :on_sp
return x[1] == :on_kw && BOTH_BLOCK.include?(x[2])
end
end
end
# kinda complex, we count open/close to determine if we ultimately have a
# hanging line. Always true if it's a both_block.
def opening_block?(line_lex)
opens = (starts_block?(line_lex) || both_block?(line_lex)) ? 1 : 0
opens += opening_block_count line_lex
closes = closing_block_count line_lex
return false if opens == closes
return true if opens > closes
end
# kinda complex, we count open/close to determine if we ultimately have close a
# hanging line. Always true if it's a both_block.
def closing_block?(line_lex)
return true if both_block? line_lex
opens = starts_block?(line_lex) ? 1 : 0
opens += opening_block_count line_lex
closes = closing_block_count line_lex
return false if opens == closes
return true if opens < closes
end
private
# how many times do we open in this line?
def opening_block_count(line_lex)
line_lex.select {|l| l[1] == :on_kw && OPEN_BLOCK_DO.include?(l[2])}.count
end
# how many times do we close?
def closing_block_count(line_lex)
line_lex.select {|l| l[1] == :on_kw && CLOSE_BLOCK.include?(l[2])}.count
end
# count the amount of opening assignments.
def opening_assignment_count(line_lex)
line_lex.select {|l| OPEN_BRACKETS.include? l[1]}.count
end
# count the amount of closing assignments.
def closing_assignment_count(line_lex)
line_lex.select {|l| CLOSE_BRACKETS.include? l[1]}.count
end
# prepare an indented line. Requires the level, token, count and string.
def indented_line(level, token = "\t", count = 1, string)
output_string = ""
if string =~ /^\n$/
output_string += "\n"
else
indent = (token * count) * level
output_string += "#{indent}#{string.lstrip}"
end
end
# try to find a config and return a modified argv, walks all the way to root
# looking for '.ruby-beautify' and then returns an argv with our new options.
def config_argv
target_dirs = Dir.pwd.split("/")
# sloppy walk method, not well tested but shouldn't get in the way.
while (target_dirs.any?)
target = "#{target_dirs.join("/")}/.ruby-beautify"
break if File.exist?(target)
target_dirs.pop
target = nil
end
return ARGV unless target
lines = open(target).readlines
return ARGV + lines.map {|l| l.chomp}
end
end
|