├── .gitignore ├── .no-sublime-package ├── BeautifyRuby.sublime-settings ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-commands ├── Gemfile ├── Gemfile.lock ├── Main.sublime-menu ├── README.md ├── beautify_ruby.py ├── lib ├── erbbeautify.rb ├── rbeautify.rb └── rbeautify │ ├── block_end.rb │ ├── block_matcher.rb │ ├── block_start.rb │ ├── config │ └── ruby.rb │ ├── language.rb │ ├── line.rb │ └── tab_size.rb ├── license.txt └── spec ├── fixtures └── ruby.yml ├── rbeautify ├── block_matcher_spec.rb ├── block_start_spec.rb ├── config │ └── ruby_spec.rb └── line_spec.rb ├── rbeautify_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | *.pyc -------------------------------------------------------------------------------- /.no-sublime-package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CraigWilliams/BeautifyRuby/43ce3e5b4e7244009f3d396deae179e3f33c24f9/.no-sublime-package -------------------------------------------------------------------------------- /BeautifyRuby.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Would you prefer a tab or two spaces to represent a tab 3 | // The default is two spaces represented by 'space' 4 | // anything else will use one tab character 5 | "file_patterns": ["\\.html\\.erb", "\\.rb", "\\.rake", "Rakefile", "Gemfile", "Vagrantfile"], 6 | "html_erb_patterns": ["\\.html\\.erb"], 7 | "run_on_save": false, 8 | "save_on_beautify": true 9 | } 10 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+k"], "command": "beautify_ruby" } 3 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [{ 2 | "keys": ["ctrl+super+k"], 3 | "command": "beautify_ruby" 4 | }] 5 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["ctrl+alt+k"], "command": "beautify_ruby" } 3 | ] -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "BeautifyRuby", 4 | "command": "beautify_ruby" 5 | } 6 | ] -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec' 4 | gem 'pry' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | coderay (1.1.0) 5 | diff-lcs (1.2.5) 6 | method_source (0.8.2) 7 | pry (0.10.1) 8 | coderay (~> 1.1.0) 9 | method_source (~> 0.8.1) 10 | slop (~> 3.4) 11 | rspec (3.1.0) 12 | rspec-core (~> 3.1.0) 13 | rspec-expectations (~> 3.1.0) 14 | rspec-mocks (~> 3.1.0) 15 | rspec-core (3.1.2) 16 | rspec-support (~> 3.1.0) 17 | rspec-expectations (3.1.0) 18 | diff-lcs (>= 1.2.0, < 2.0) 19 | rspec-support (~> 3.1.0) 20 | rspec-mocks (3.1.0) 21 | rspec-support (~> 3.1.0) 22 | rspec-support (3.1.0) 23 | slop (3.6.0) 24 | 25 | PLATFORMS 26 | ruby 27 | 28 | DEPENDENCIES 29 | pry 30 | rspec 31 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "edit", 4 | "children": 5 | [ 6 | {"id": "wrap"}, 7 | { "command": "beautify_ruby" } 8 | ] 9 | }, 10 | { 11 | "id": "preferences", 12 | "children": 13 | [ 14 | { 15 | "caption": "Package Settings", 16 | "mnemonic": "P", 17 | "id": "package-settings", 18 | "children": 19 | [ 20 | { 21 | "caption": "BeautifyRuby", 22 | "children": 23 | [ 24 | { 25 | "command": "open_file", 26 | "args": {"file": "${packages}/BeautifyRuby/BeautifyRuby.sublime-settings"}, 27 | "caption": "Settings – Default" 28 | }, 29 | { 30 | "command": "open_file", 31 | "args": { "file": "${packages}/User/BeautifyRuby.sublime-settings" }, 32 | "caption": "Settings - User" 33 | }, 34 | { 35 | "command": "open_file", 36 | "args": { 37 | "file": "${packages}/BeautifyRuby/Default (Windows).sublime-keymap", 38 | "platform": "Windows" 39 | }, 40 | "caption": "Key Bindings – Default" 41 | }, 42 | { 43 | "command": "open_file", 44 | "args": { 45 | "file": "${packages}/BeautifyRuby/Default (OSX).sublime-keymap", 46 | "platform": "OSX" 47 | }, 48 | "caption": "Key Bindings – Default" 49 | }, 50 | { 51 | "command": "open_file", 52 | "args": { 53 | "file": "${packages}/BeautifyRuby/Default (Linux).sublime-keymap", 54 | "platform": "Linux" 55 | }, 56 | "caption": "Key Bindings – Default" 57 | }, 58 | { 59 | "command": "open_file", 60 | "args": { 61 | "file": "${packages}/User/Default (Windows).sublime-keymap", 62 | "platform": "Windows" 63 | }, 64 | "caption": "Key Bindings – User" 65 | }, 66 | { 67 | "command": "open_file", 68 | "args": { 69 | "file": "${packages}/User/Default (OSX).sublime-keymap", 70 | "platform": "OSX" 71 | }, 72 | "caption": "Key Bindings – User" 73 | }, 74 | { 75 | "command": "open_file", 76 | "args": { 77 | "file": "${packages}/User/Default (Linux).sublime-keymap", 78 | "platform": "Linux" 79 | }, 80 | "caption": "Key Bindings – User" 81 | }, 82 | { "caption": "-" } 83 | ] 84 | } 85 | ] 86 | } 87 | ] 88 | } 89 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [ ![Codeship Status for CraigWilliams/BeautifyRuby](https://www.codeship.io/projects/09898c30-f89d-0130-ede5-7a7e050a0c1a/status)](https://www.codeship.io/projects/6700) 2 | 3 | # BeautifyRuby 4 | 5 | Erb html templates uses [Paul Battley's htmlbeautifier gem](https://github.com/threedaymonk/htmlbeautifier). This (as well as rubygems) is assumed to be installed as seen by the ruby interpreter. Note that if you beautify an erb file but `htmlbeautifier` is not found, the error message is 'check your ruby interpreter settings', do not be misled. 6 | 7 | ### Interpreter settings 8 | 9 | If an error is encountered while processing the file, Python receives an empty string and the following message is displayed but may have nothing to do with your Ruby settings. 10 | 11 | ``` 12 | check your ruby interpreter settings 13 | ``` 14 | 15 | ### Hooks 16 | 17 | This package offers a pre-save hook, i.e., your ruby and erb files will be reformatted automatically before saving. To activate this feature, set: 18 | 19 | "run_on_save": true, 20 | 21 | The sublime command "beautify_ruby" performs a save after formatting. You can disable this default by setting: 22 | 23 | "save_on_beautify": false 24 | 25 | You can change the file patterns handled by this plugin in the settings: 26 | 27 | "file_patterns": [ "\\.html\\.erb", "\\.rb", "\\.rake", "Rakefile", "Gemfile" ], 28 | "html_erb_patterns": ["\\.html\\.erb"], 29 | 30 | This plugin uses ruby scripts to beautify your buffer, so it needs ruby installed. You can configure your ruby interpreter under Preferences -> Package Settings -> BeautifyRuby -> Settings Default/User [click here for a screenshot](https://user-images.githubusercontent.com/15097447/62330753-f6fc5100-b4fc-11e9-8c41-1015b62ac943.png). Although the default should work on linux and osx, not setting this right is a common problem. 31 | 32 | If you do not use the system ruby, type in your favourite shell: 33 | 34 | ``` 35 | which ruby 36 | ``` 37 | 38 | and place that in the ruby setting. 39 | 40 | On windows, set Preferences -> Package Settings -> BeautifyRuby -> Settings Default 41 | 42 | ``` 43 | "ruby": "ruby" 44 | ``` 45 | 46 | If you use project-specific rubies and gem sets managed with `rvm`, then simply set 47 | 48 | "ruby": "~/.rvm/bin/rvm-auto-ruby", 49 | 50 | and then the `htmlbeautifier` gem is found even if it is only installed for this project. 51 | 52 | If you are using ruby on the Windows Subsystem for Linux, use: 53 | 54 | ``` 55 | "ruby": "wsl ruby" 56 | ``` 57 | 58 | ### Tabs or Spaces 59 | 60 | By default, Sublime does not translate tabs to spaces. If you wish to use tabs you will not need to change your settings. If you wish to use spaces, add the following setting. 61 | 62 | ``` 63 | "translate_tabs_to_spaces": true 64 | ``` 65 | 66 | Or if you wish to force the use of tabs use: 67 | 68 | ``` 69 | "translate_tabs_to_spaces": false 70 | ``` 71 | ### Tab size 72 | 73 | Sublime's default `tab_size` is set to 4. Override this setting to change the number of spaces to use when using spaces instead of tabs. 74 | 75 | ``` 76 | "tab_size": 2 77 | ``` 78 | 79 | ### Key Binding 80 | 81 | ``` 82 | ctrl + cmd + k on OS X, or ctrl + alt + k on Windows 83 | ``` 84 | 85 | # Installation 86 | 87 | ### Package Control 88 | Using [Package Control](http://wbond.net/sublime_packages/package_control), a 89 | package manager for Sublime Text 2. 90 | 91 | In ST2, press "cmd + shift + p" and then type "install". 92 | 93 | Once you see "Package Control: Install Package", enter. 94 | 95 | When the packages load, another selection window will appear. Type 96 | 97 | BeautifyRuby and enter. All done! 98 | 99 | ### Manual Installation 100 | 101 | ```bash 102 | cd "~/Library/Application Support/Sublime Text 2/Packages/" 103 | git clone git://github.com/CraigWilliams/BeautifyRuby.git 104 | ``` 105 | -------------------------------------------------------------------------------- /beautify_ruby.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sublime, sublime_plugin, sys, re 3 | import subprocess 4 | 5 | class BeautifyRubyOnSave(sublime_plugin.EventListener): 6 | def on_pre_save(self, view): 7 | self.settings = sublime.load_settings('BeautifyRuby.sublime-settings') 8 | if self.settings.get('run_on_save'): 9 | view.run_command("beautify_ruby", {"save": False, "error": False}) 10 | 11 | class BeautifyRubyCommand(sublime_plugin.TextCommand): 12 | def run(self, edit, error=True, save=True): 13 | self.load_settings() 14 | 15 | for s in ['translate_tabs_to_spaces', 'tab_size', 'keep_blank_lines']: 16 | self.view.settings().set(s, self.settings.get(s)) 17 | 18 | self.filename = self.view.file_name() 19 | self.fname = os.path.basename(self.filename) 20 | self.erb = self.is_erb_file() 21 | 22 | try: 23 | if self.erb or self.is_ruby_file(): 24 | self.beautify_buffer(edit) 25 | if save and self.settings.get('save_on_beautify'): 26 | self.view.run_command('save') 27 | else: 28 | if error: 29 | raise Exception("This is not a Ruby file.") 30 | except: 31 | msg = "Error: {0}".format(sys.exc_info()[1]) 32 | sublime.error_message(msg) 33 | 34 | def beautify_buffer(self, edit): 35 | buffer_region = sublime.Region(0, self.view.size()) 36 | buffer_text = self.view.substr(buffer_region) 37 | if buffer_text == "": 38 | return 39 | self.save_viewport_state() 40 | beautified_buffer = self.pipe(self.cmd(), buffer_text) 41 | fix_lines = beautified_buffer.replace(os.linesep,'\n') 42 | self.check_valid_output(fix_lines) 43 | self.view.replace(edit, buffer_region, fix_lines) 44 | self.reset_viewport_state() 45 | 46 | def check_valid_output(self, text): 47 | if text == "": 48 | msg = "invalid output. Check your ruby interpreter settings" 49 | raise Exception(msg) 50 | 51 | def cmd(self, path = "-"): 52 | if self.erb: 53 | script_name = 'erbbeautify.rb' 54 | else: 55 | script_name = 'rbeautify.rb' 56 | 57 | ruby_interpreter = self.settings.get('ruby') or self.which('ruby.exe') or self.which('ruby') 58 | ruby_script = os.path.join(sublime.packages_path(), 'BeautifyRuby', 'lib', script_name) 59 | 60 | if ruby_interpreter is None: 61 | msg = "ruby interpreter not found, set PATH environment variable or set ruby in settings" 62 | raise Exception(msg) 63 | 64 | if not os.path.exists(ruby_script): 65 | msg = "script: '" + ruby_script + "' not found." 66 | raise Exception(msg) 67 | 68 | args = ["'" + str(path) + "'"] + self.config_params() 69 | 70 | # Use translated path when wsl ruby 71 | if self.settings.get('ruby') == 'wsl ruby': 72 | ruby_script = subprocess.check_output("wsl wslpath '" + ruby_script + "'", shell=True).decode("utf-8").rstrip() 73 | 74 | return ruby_interpreter + " '" + ruby_script + "' " + ' '.join(args) 75 | 76 | def finalize_output(self, text): 77 | lines = text.splitlines() 78 | finalized_output = "\n".join(lines) 79 | if self.view.settings().get("ensure_newline_at_eof_on_save") and not text.endswith("\n"): 80 | text += "\n" 81 | return finalized_output 82 | 83 | def config_params(self): 84 | def create_parameter(name): 85 | return ['--'+name.replace('_','-'), str(self.view.settings().get(name))] 86 | 87 | result = [] 88 | targets = ["tab_size","translate_tabs_to_spaces",'default_line_ending','keep_blank_lines'] 89 | for target in targets: 90 | result += create_parameter(target) 91 | 92 | return result 93 | 94 | def load_settings(self): 95 | self.settings = sublime.load_settings('BeautifyRuby.sublime-settings') 96 | 97 | def save_viewport_state(self): 98 | self.previous_selection = [(region.a, region.b) for region in self.view.sel()] 99 | self.previous_position = self.view.viewport_position() 100 | 101 | def reset_viewport_state(self): 102 | self.view.set_viewport_position((0, 0,), False) 103 | self.view.set_viewport_position(self.previous_position, False) 104 | self.view.sel().clear() 105 | for a, b in self.previous_selection: 106 | self.view.sel().add(sublime.Region(a, b)) 107 | 108 | def is_ruby_file(self): 109 | file_patterns = self.settings.get('file_patterns') or ['\.rb', '\.rake'] 110 | return self.match_pattern(file_patterns) 111 | 112 | def is_erb_file(self): 113 | file_patterns = self.settings.get('html_erb_patterns') or ['\.html\.erb'] 114 | return self.match_pattern(file_patterns) 115 | 116 | def match_pattern(self, file_patterns): 117 | patterns = re.compile(r'\b(?:%s)\b' % '|'.join(file_patterns)) 118 | return patterns.search(self.fname) 119 | 120 | def pipe(self, cmd, text): 121 | cwd = os.path.dirname(self.filename) 122 | beautifier = subprocess.Popen(cmd, shell=True, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 123 | out = beautifier.communicate(text.encode("utf-8"))[0] 124 | return out.decode('utf8') 125 | 126 | # http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python/377028#377028 127 | def which(self,program): 128 | import os 129 | def is_exe(fpath): 130 | return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 131 | 132 | fpath, fname = os.path.split(program) 133 | if fpath: 134 | if is_exe(program): 135 | return program 136 | else: 137 | for path in os.environ["PATH"].split(os.pathsep): 138 | path = path.strip('"') 139 | exe_file = os.path.join(path, program) 140 | if is_exe(exe_file): 141 | return exe_file 142 | 143 | return None 144 | -------------------------------------------------------------------------------- /lib/erbbeautify.rb: -------------------------------------------------------------------------------- 1 | require 'htmlbeautifier' 2 | module ERBeautify 3 | 4 | class << self 5 | def beautify(input, output, options = {}) 6 | output.write(HtmlBeautifier.beautify(input, options)) 7 | end 8 | 9 | def main 10 | if(!ARGV[0]) 11 | STDERR.puts "usage: Ruby filenames or \"-\" for stdin." 12 | exit 0 13 | else 14 | path = ARGV.shift 15 | config = generate_config(ARGV) 16 | options = Hash.new 17 | 18 | if config['translate_tabs_to_spaces'] == 'False' 19 | options[:indent] = '\t' 20 | end 21 | 22 | if config['tab_size'].to_i != 0 and config['translate_tabs_to_spaces'] != 'False' 23 | options[:tab_stops] = config['tab_size'].to_i 24 | end 25 | 26 | if config['keep_blank_lines'].to_i > 0 27 | options[:keep_blank_lines] = config['keep_blank_lines'].to_i 28 | end 29 | 30 | beautify $stdin.read.force_encoding('utf-8'), $stdout, options 31 | end 32 | end 33 | 34 | def generate_config args 35 | args.each_slice(2).with_object({}) do |parameter, result| 36 | result[parameter.first.gsub('--','').gsub('-','_')] = parameter.last 37 | end 38 | end 39 | end 40 | end 41 | 42 | # if launched as a standalone program, not loaded as a module 43 | if __FILE__ == $0 44 | ERBeautify.main 45 | end 46 | -------------------------------------------------------------------------------- /lib/rbeautify.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby -w 2 | 3 | =begin 4 | /*************************************************************************** 5 | * Copyright (C) 2008, Joel Chippindale, Paul Lutus * 6 | * * 7 | * This program is free software: you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation, either version 3 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | * This program is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | * GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see . * 19 | * * 20 | ***************************************************************************/ 21 | =end 22 | 23 | require_relative './rbeautify/block_start.rb' 24 | require_relative './rbeautify/block_end.rb' 25 | require_relative './rbeautify/block_matcher.rb' 26 | require_relative './rbeautify/language.rb' 27 | require_relative './rbeautify/line.rb' 28 | require_relative './rbeautify/tab_size.rb' 29 | require_relative './rbeautify/config/ruby.rb' 30 | 31 | module RBeautify 32 | 33 | class << self 34 | def beautify_string(language, source, config) 35 | dest = "" 36 | block = nil 37 | 38 | unless language.is_a? RBeautify::Language 39 | language = RBeautify::Language.language(language) 40 | end 41 | 42 | config['tab_size'] = RBeautify::TabSize.new(config).tab_size 43 | language.indent_size = config['tab_size'] 44 | 45 | source.force_encoding("UTF-8").split("\n").each_with_index do |line_content, line_number| 46 | line = RBeautify::Line.new(language, line_content, line_number, block, config) 47 | dest += line.format + "\n" 48 | block = line.block 49 | end 50 | 51 | return dest 52 | end 53 | 54 | def beautify_file(path, config) 55 | backup = config["backup"] == 'True' 56 | 57 | if(path == '-') # stdin source 58 | source = STDIN.read 59 | print beautify_string(:ruby, source, config) 60 | else # named file source 61 | source = File.read(path) 62 | dest = beautify_string(:ruby, source, config) 63 | if(source != dest) 64 | if backup 65 | # make a backup copy 66 | File.open(path + "~","w") { |f| f.write(source) } 67 | end 68 | # overwrite the original 69 | File.open(path,"w") { |f| f.write(dest) } 70 | end 71 | return source != dest 72 | end 73 | end 74 | 75 | def main 76 | if(!ARGV[0]) 77 | STDERR.puts "usage: Ruby filenames or \"-\" for stdin." 78 | exit 0 79 | else 80 | path = ARGV.shift 81 | config = generate_config(ARGV) 82 | beautify_file(path,config) 83 | end 84 | end 85 | 86 | def generate_config args 87 | args.each_slice(2).with_object({}) do |parameter, result| 88 | result[parameter.first.gsub('--','').gsub('-','_')] = parameter.last 89 | end 90 | end 91 | 92 | end 93 | end 94 | 95 | # if launched as a standalone program, not loaded as a module 96 | if __FILE__ == $0 97 | RBeautify.main 98 | end 99 | -------------------------------------------------------------------------------- /lib/rbeautify/block_end.rb: -------------------------------------------------------------------------------- 1 | module RBeautify 2 | 3 | class BlockEnd 4 | 5 | attr_accessor :block_start, :offset, :match, :after_match 6 | 7 | def initialize(block_start, offset, match, after_match) 8 | self.block_start = block_start 9 | self.offset = offset 10 | self.match = match 11 | self.after_match = after_match 12 | end 13 | 14 | def end_offset 15 | offset + match.length 16 | end 17 | 18 | def end_can_also_be_start? 19 | block_start.block_matcher.end_can_also_be_start? 20 | end 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lib/rbeautify/block_matcher.rb: -------------------------------------------------------------------------------- 1 | module RBeautify 2 | 3 | class BlockMatcher 4 | 5 | attr_reader :language, :name, :starts, :ends, :options 6 | 7 | def initialize(language, name, starts, ends, options = {}) 8 | @language = language 9 | @name = name 10 | @starts = starts 11 | @ends = ends.nil? ? starts : ends 12 | @options = options 13 | end 14 | 15 | class << self 16 | def parse(language, original_block, line_number, string, current_offset) 17 | block_end = original_block && original_block.parse_block_end(string, current_offset) 18 | 19 | if (block_start = first_block_start(language, 20 | original_block, 21 | line_number, 22 | string, 23 | current_offset, 24 | block_end.nil? ? nil : block_end.offset)) 25 | 26 | block_end = nil 27 | end 28 | 29 | if block_end 30 | # Check whether the section of the line which is a block end is also a block start 31 | if block_end.end_can_also_be_start? && 32 | (block_start_candidate = first_block_start(language, block_end.block_start.parent, line_number, string, current_offset)) && 33 | block_start_candidate.offset == block_end.offset 34 | block_start = block_start_candidate 35 | end 36 | 37 | end 38 | 39 | if block_start 40 | if debug 41 | puts "MATCH: '#{string.slice(0, string.length - block_start.match.length - block_start.after_match.length)}#{block_start.match}#{block_start.after_match}'" 42 | end 43 | parse(language, block_start, line_number, block_start.after_match, block_start.end_offset) 44 | elsif block_end 45 | if debug 46 | puts "MATCH: '#{string.slice(0, string.length - block_end.match.length - block_end.after_match.length)}#{block_end.match}#{block_end.after_match}'" 47 | end 48 | parse(language, block_end.block_start.parent, line_number, block_end.after_match, block_end.end_offset) 49 | else 50 | original_block 51 | end 52 | end 53 | 54 | def debug=(value) 55 | @debug = value 56 | end 57 | 58 | def debug 59 | @debug 60 | end 61 | 62 | private 63 | def first_block_start(language, parent_block, line_number, string, offset, maximum_offset = nil) 64 | first_block_start = nil 65 | language.matchers.each do |matcher| 66 | if matcher.can_nest?(parent_block) 67 | if (block_start_candidate = matcher.parse_block_start(string, parent_block, offset, line_number)) && 68 | (maximum_offset.nil? || maximum_offset > block_start_candidate.offset) 69 | first_block_start = block_start_candidate 70 | maximum_offset = first_block_start.offset 71 | end 72 | end 73 | end 74 | first_block_start 75 | end 76 | end 77 | 78 | def parse_block_start(string, parent_block, offset, line_number) 79 | if !string.empty? && (match = starts.match(string)) 80 | bs = RBeautify::BlockStart.new(self, parent_block, line_number, offset + match.begin(0), match[0], match.post_match) 81 | end 82 | end 83 | 84 | def indent_end_line?(block) 85 | evaluate_option_for_block(:indent_end_line, block) 86 | end 87 | 88 | def indent_size(block) 89 | evaluate_option_for_block(:indent_size, block) || language.indent_size 90 | end 91 | 92 | # Look for blocks within the content of this one 93 | def parse_content? 94 | options[:parse_content] != false 95 | end 96 | 97 | # Indent the content of this block 98 | def format_content? 99 | options[:format_content] != false 100 | end 101 | 102 | def can_nest?(parent_block) 103 | return false unless parent_block.nil? || parent_block.parse_content? 104 | 105 | if options[:nest_only] 106 | parent_block && options[:nest_only].include?(parent_block.name) 107 | else 108 | parent_block.nil? || 109 | options[:nest_except].nil? || !options[:nest_except].include?(parent_block.name) 110 | end 111 | end 112 | 113 | def ends? 114 | ends != false 115 | end 116 | 117 | def end_is_implicit? 118 | options[:end] == :implicit 119 | end 120 | 121 | def end_can_also_be_start? 122 | if ends == starts 123 | options[:end_can_also_be_start] == true 124 | else 125 | options[:end_can_also_be_start] != false 126 | end 127 | end 128 | 129 | def negate_ends_match? 130 | options[:negate_ends_match] 131 | end 132 | 133 | # True if blocks can contain the escape character \ which needs to be 134 | # checked for on end match 135 | def escape_character? 136 | options[:escape_character] == true 137 | end 138 | 139 | def inspect 140 | name 141 | end 142 | 143 | private 144 | def evaluate_option_for_block(key, block) 145 | if options[key] && options[key].respond_to?(:call) 146 | options[key].call(block) 147 | else 148 | options[key] 149 | end 150 | end 151 | 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/rbeautify/block_start.rb: -------------------------------------------------------------------------------- 1 | module RBeautify 2 | 3 | class BlockStart 4 | 5 | attr_reader :block_matcher, :parent, :offset, :match, :after_match, :line_number 6 | 7 | class << self 8 | def first_common_ancestor(first, second) 9 | if first.nil? || second.nil? 10 | nil 11 | else 12 | (first.ancestors & second.ancestors).last 13 | end 14 | end 15 | end 16 | 17 | def initialize(block_matcher, parent, line_number, offset, match, after_match) 18 | @block_matcher = block_matcher 19 | @parent = parent 20 | @offset = offset 21 | @match = match 22 | @after_match = after_match 23 | @line_number = line_number 24 | end 25 | 26 | def end_offset 27 | offset + match.length 28 | end 29 | 30 | def parse_block_end(string, offset) 31 | block_end = parse_explicit_block_end(string, offset) 32 | 33 | # Handle case where end is implicit 34 | if block_end.nil? && end_is_implicit? && parent 35 | block_end = parent.parse_block_end(string, offset) 36 | end 37 | 38 | block_end 39 | end 40 | 41 | def format_content? 42 | block_matcher.format_content? 43 | end 44 | 45 | def parse_content? 46 | block_matcher.parse_content? 47 | end 48 | 49 | def indent_end_line? 50 | block_matcher.indent_end_line?(self) 51 | end 52 | 53 | def total_indent_size 54 | parent.nil? ? indent_size : parent.total_indent_size + indent_size 55 | end 56 | 57 | def indent_size 58 | block_matcher.indent_size(self) 59 | end 60 | 61 | def end_is_implicit? 62 | block_matcher.end_is_implicit? 63 | end 64 | 65 | def name 66 | block_matcher.name 67 | end 68 | 69 | # Returns true if strict ancestor of 70 | def strict_ancestor_of?(block_start) 71 | block_start && block_start.parent && (self == block_start.parent || strict_ancestor_of?(block_start.parent)) 72 | end 73 | 74 | def ancestors 75 | if parent 76 | parent.ancestors + [self] 77 | else 78 | [self] 79 | end 80 | end 81 | 82 | private 83 | def ends? 84 | block_matcher.ends? 85 | end 86 | 87 | def negate_ends_match? 88 | block_matcher.negate_ends_match? 89 | end 90 | 91 | def escape_character? 92 | block_matcher.escape_character? 93 | end 94 | 95 | def parse_explicit_block_end(string, offset) 96 | block_end = nil 97 | 98 | if ends? 99 | 100 | if match = block_matcher.ends.match(string) 101 | unless negate_ends_match? 102 | if escape_character? && 103 | ((escape_chars = match.pre_match.match(/\\*$/)) && (escape_chars[0].size % 2 == 1)) 104 | # If there are an odd number of escape characters just before 105 | # the match then this match should be skipped 106 | return parse_explicit_block_end(match.post_match, offset + escape_chars[0].size + match[0].length) 107 | else 108 | return RBeautify::BlockEnd.new(self, offset + match.begin(0), match[0], match.post_match) 109 | end 110 | end 111 | elsif negate_ends_match? 112 | return RBeautify::BlockEnd.new(self, offset, '', string) 113 | end 114 | 115 | end 116 | end 117 | 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/rbeautify/config/ruby.rb: -------------------------------------------------------------------------------- 1 | # define ruby language 2 | 3 | unless RBeautify::Language.language(:ruby) 4 | 5 | ruby = RBeautify::Language.add_language(:ruby) 6 | 7 | pre_keyword_boundary = '(^|[^a-z0-9A-Z:._])' # like \b but with : , . _ all added to list of exceptions 8 | start_statement_boundary = '(^|(;|=)\s*)' 9 | continue_statement_boundary = '(^|;\s*)' 10 | ruby.indent_size ||= 2 11 | 12 | ruby.add_matcher(:program_end, /^__END__$/, false, :format_content => false, :parse_content => false) 13 | 14 | ruby.add_matcher(:multiline_comment, /^=begin/, /^=end/, :format_content => false, :parse_content => false) 15 | 16 | ruby.add_matcher(:double_quote, 17 | /"/, 18 | /"/, 19 | :parse_content => true, 20 | :format_content => false, 21 | :escape_character => true, 22 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 23 | 24 | # NEED TO MODIFY DOUBLE QUOTE TO BE FORMATTED to get this to work 25 | ruby.add_matcher(:interpolation, 26 | /#\{/, 27 | /\}/, 28 | :nest_only => [:double_quote, :regex, :backtick]) 29 | 30 | ruby.add_matcher(:single_quote, 31 | /'/, 32 | /'/, 33 | :parse_content => false, 34 | :format_content => false, 35 | :escape_character => true, 36 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 37 | 38 | ruby.add_matcher(:regex, 39 | /(^|((,|=|~)\s*))\//, # Try to distinguish it from division sign 40 | /\//, 41 | :format_content => false, 42 | :escape_character => true, 43 | :end_can_also_be_start => false, 44 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 45 | 46 | ruby.add_matcher(:back_tick, 47 | /`/, 48 | /`/, 49 | :format_content => false, 50 | :escape_character => true, 51 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 52 | 53 | ruby.add_matcher(:percent_square, 54 | /%\w?\[/, 55 | /\]/, 56 | :format_content => false, 57 | :escape_character => true, 58 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 59 | 60 | ruby.add_matcher(:percent_curly, 61 | /%\w?\{/, 62 | /\}/, 63 | :format_content => false, 64 | :escape_character => true, 65 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 66 | 67 | ruby.add_matcher(:percent_round, 68 | /%\w?\(/, 69 | /\)/, 70 | :format_content => false, 71 | :escape_character => true, 72 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 73 | 74 | ruby.add_matcher(:standard, 75 | /((#{start_statement_boundary}(module|class(?!:)|def))|#{pre_keyword_boundary}do)\b/, 76 | /(((^|;|\s)end)|#{continue_statement_boundary}(rescue|ensure))\b/, 77 | :nest_except => [:double_quote, :regex, :backtick]) 78 | 79 | ruby.add_matcher(:more, 80 | /#{start_statement_boundary}(until|for|while)\b/, 81 | /(((^|;|\s)end)|#{continue_statement_boundary}(rescue|ensure))\b/, 82 | :nest_except => [:double_quote, :regex, :backtick]) 83 | 84 | ruby.add_matcher(:begin, 85 | /((#{start_statement_boundary}begin)|(#{continue_statement_boundary}(ensure|rescue)))\b/, 86 | /(((^|;|\s)end)|#{continue_statement_boundary}(rescue|ensure|else))\b/, 87 | :nest_except => [:double_quote, :regex, :backtick]) 88 | 89 | ruby.add_matcher(:if, 90 | /((#{start_statement_boundary}(if|unless)(?!:))|#{continue_statement_boundary}(then|elsif|else))\b/, 91 | /(((^|;|\s)end)|(#{continue_statement_boundary}(then|elsif|else)))\b/, 92 | :nest_except => [:case, :double_quote, :regex, :backtick]) 93 | 94 | ruby.add_matcher(:case, 95 | /#{pre_keyword_boundary}case\b/, 96 | /(^|;|\s)end\b/, 97 | :nest_except => [:double_quote, :regex, :backtick], 98 | :indent_size => 0) 99 | 100 | ruby.add_matcher(:inner_case, 101 | /#{continue_statement_boundary}(when|else|then)\b/, 102 | /#{continue_statement_boundary}(when|else|then)\b/, 103 | :nest_only => [:case], 104 | :end => :implicit, 105 | :end_can_also_be_start => true, 106 | :nest_except => [:double_quote, :regex, :backtick]) 107 | 108 | # TODO: Improve the check that this is not a block with arguments. Will 109 | # currently match any bracket followed by spaces and |. 110 | bracket_indent_end_line_proc = Proc.new { |block| !block.after_match.empty? && !block.after_match.match(/^\|/) } 111 | bracket_indent_size_proc = Proc.new do |block| 112 | if bracket_indent_end_line_proc.call(block) 113 | strict_ancestors_on_same_line = block.ancestors.select { |a| a != block && a.line_number == block.line_number } 114 | block.end_offset - strict_ancestors_on_same_line.inject(0) { |sum, a| sum + a.indent_size } 115 | end 116 | end 117 | 118 | ruby.add_matcher(:curly_bracket, 119 | /\{\s*/, 120 | /\}/, 121 | :indent_end_line => bracket_indent_end_line_proc, 122 | :indent_size => bracket_indent_size_proc, 123 | :nest_except => [:double_quote, :regex, :backtick]) 124 | 125 | ruby.add_matcher(:round_bracket, 126 | /\(\s*/, 127 | /\)/, 128 | :indent_end_line => bracket_indent_end_line_proc, 129 | :indent_size => bracket_indent_size_proc, 130 | :nest_except => [:double_quote, :regex, :backtick]) 131 | 132 | ruby.add_matcher(:square_bracket, 133 | /\[\s*/, 134 | /\]/, 135 | :indent_end_line => bracket_indent_end_line_proc, 136 | :indent_size => bracket_indent_size_proc, 137 | :nest_except => [:double_quote, :regex, :backtick]) 138 | 139 | ruby.add_matcher(:comment, /(\s*)?#/, 140 | /$/, 141 | :parse_content => false, 142 | :format_content => false, 143 | :nest_except => [:double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 144 | 145 | ruby.add_matcher(:continuing_line, 146 | /(,|\.|\+|-|=\>|=|&&|\|\||\\|==|\s\?|:|<<)(\s*)?(#.*)?$/, 147 | /(^|(,|\.|\+|-|=\>|=|&&|\|\||\\|==|\s\?|:|<<)(\s*)?)(#.*)?$/, 148 | :indent_end_line => true, 149 | :negate_ends_match => true, 150 | :nest_except => [:continuing_line, :curly_bracket, :round_bracket, :square_bracket, :double_quote, :single_quote, :regex, :back_tick, :percent_square, :percent_curly, :percent_round]) 151 | 152 | end 153 | -------------------------------------------------------------------------------- /lib/rbeautify/language.rb: -------------------------------------------------------------------------------- 1 | module RBeautify 2 | class Language 3 | 4 | @@languages = {} 5 | 6 | attr_reader :matchers 7 | attr_accessor :indent_size 8 | 9 | class << self 10 | 11 | def language(name) 12 | languages[name] 13 | end 14 | 15 | def languages 16 | @@languages 17 | end 18 | 19 | def add_language(name) 20 | languages[name] = new() 21 | end 22 | end 23 | 24 | def initialize 25 | @matchers = [] 26 | end 27 | 28 | def add_matcher(name, starts, ends, options = {}) 29 | self.matchers << BlockMatcher.new(self, name, starts, ends, options) 30 | end 31 | 32 | def matcher(name) 33 | matchers.detect { |matcher| matcher.name == name} 34 | end 35 | 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/rbeautify/line.rb: -------------------------------------------------------------------------------- 1 | module RBeautify 2 | class Line 3 | 4 | attr_reader :language, :content, :line_number, :original_block, :block, :indent_character 5 | 6 | def initialize(language, content, line_number, original_block = nil, config) 7 | @tab_size = config["tab_size"] 8 | @use_tabs = config["translate_tabs_to_spaces"] == 'False' 9 | @language = language 10 | @content = content 11 | @original_block = original_block 12 | @indent_character = @use_tabs ? "\t" : " " * @tab_size 13 | @block = BlockMatcher.parse(language, original_block, line_number, stripped, 0) 14 | end 15 | 16 | def format 17 | if @formatted.nil? 18 | if format? 19 | if stripped.length == 0 20 | @formatted = "" 21 | elsif !content.match(/\s*?^=/).nil? 22 | @formatted = stripped 23 | else 24 | @formatted = tab_string + stripped 25 | end 26 | else 27 | @formatted = content 28 | end 29 | end 30 | 31 | @formatted 32 | end 33 | 34 | private 35 | def format? 36 | original_block.nil? || original_block.format_content? 37 | end 38 | 39 | def indent_size 40 | if (block.nil? || block.strict_ancestor_of?(original_block)) && (original_block && original_block.indent_end_line?) 41 | original_block.total_indent_size 42 | else 43 | common_ancestor = BlockStart.first_common_ancestor(original_block, block) 44 | common_ancestor.nil? ? 0 : common_ancestor.total_indent_size 45 | end 46 | end 47 | 48 | def tab_string 49 | indent_character * (indent_size / @tab_size ) + (indent_size.odd? ? ' ' : '') 50 | end 51 | 52 | def stripped 53 | @stripped = content.strip 54 | end 55 | 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/rbeautify/tab_size.rb: -------------------------------------------------------------------------------- 1 | module RBeautify 2 | class TabSize 3 | DEFAULT_TAB_SIZE = 2 4 | 5 | def initialize(config) 6 | @tab_size = config['tab_size'].to_i 7 | end 8 | 9 | def tab_size 10 | @tab_size == 0 ? DEFAULT_TAB_SIZE : @tab_size 11 | end 12 | 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | All of BeautifyRuby is licensed under the MIT license. 2 | 3 | Copyright (c) 2011 Craig Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/fixtures/ruby.yml: -------------------------------------------------------------------------------- 1 | - name: indent if else end statement 2 | input: | 3 | if foo 4 | bar = 1 5 | elsif foo2 6 | bar = 2 7 | else 8 | bar = 3 9 | end 10 | output: | 11 | if foo 12 | bar = 1 13 | elsif foo2 14 | bar = 2 15 | else 16 | bar = 3 17 | end 18 | 19 | - name: handle case where if begins and ends on same line 20 | input: | 21 | foo do 22 | if a then b = 1 else b = 2 end 23 | end 24 | output: | 25 | foo do 26 | if a then b = 1 else b = 2 end 27 | end 28 | 29 | - name: indent not double indent case/when statement 30 | input: | 31 | case foo 32 | when 1 33 | bar = 'some string' 34 | when 2 35 | bar = 'some other string' 36 | when 3 then bar = '3' 37 | else 38 | bar = '4' 39 | end 40 | output: | 41 | case foo 42 | when 1 43 | bar = 'some string' 44 | when 2 45 | bar = 'some other string' 46 | when 3 then bar = '3' 47 | else 48 | bar = '4' 49 | end 50 | 51 | - name: indent while statement 52 | input: | 53 | def foo 54 | bar = 1 55 | while bar < 3 56 | puts bar 57 | bar = bar.next 58 | end 59 | end 60 | output: | 61 | def foo 62 | bar = 1 63 | while bar < 3 64 | puts bar 65 | bar = bar.next 66 | end 67 | end 68 | 69 | - name: ignore code after end of line comment 70 | input: | 71 | def method_containing_end_of_line_comment 72 | a = b # Comment containing do 73 | end 74 | output: | 75 | def method_containing_end_of_line_comment 76 | a = b # Comment containing do 77 | end 78 | 79 | - name :not indent multineline comment: 80 | input: | 81 | =begin 82 | Comment 83 | =end 84 | foo 85 | 86 | - name: indent lines after first of multiline code 87 | input: | 88 | def method_with_multiline_method_call 89 | multiline_method_call \ 90 | first_arg, 91 | second_arg, 92 | third_arg 93 | end 94 | output: | 95 | def method_with_multiline_method_call 96 | multiline_method_call \ 97 | first_arg, 98 | second_arg, 99 | third_arg 100 | end 101 | 102 | - name: indent method call with bracketed multiline arguments 103 | input: | 104 | def method_with_multiline_method_call 105 | multiline_method_call(foo, 106 | bar, 107 | foobar) 108 | end 109 | output: | 110 | def method_with_multiline_method_call 111 | multiline_method_call(foo, 112 | bar, 113 | foobar) 114 | end 115 | 116 | - name: indent method call with bracketed multiline arguments_even_if_not_first_block_on_line 117 | input: | 118 | if (foo = bar(first_arg, 119 | second_arg)) 120 | do_something 121 | end 122 | output: | 123 | if (foo = bar(first_arg, 124 | second_arg)) 125 | do_something 126 | end 127 | 128 | - name: indent method call with multiline arguments (implicit brackets) 129 | input: | 130 | def method_with_multiline_method_call 131 | multiline_method_call first_arg, 132 | second_arg, 133 | # Comment in the middle of all this 134 | third_arg 135 | 136 | another_method_call 137 | end 138 | output: | 139 | def method_with_multiline_method_call 140 | multiline_method_call first_arg, 141 | second_arg, 142 | # Comment in the middle of all this 143 | third_arg 144 | 145 | another_method_call 146 | end 147 | 148 | - name: should indent multiline method call chains 149 | input: | 150 | def method_with_multiline_method_call_chain 151 | multiline_method_call. 152 | foo. 153 | bar 154 | 155 | another_method_call 156 | end 157 | output: | 158 | def method_with_multiline_method_call_chain 159 | multiline_method_call. 160 | foo. 161 | bar 162 | 163 | another_method_call 164 | end 165 | 166 | - name: handle multiline code with escaped quotes in strings 167 | input: | 168 | def method_containing_multiline_code_with_strings 169 | a = "foo #{method}" + 170 | "bar" 171 | end 172 | output: | 173 | def method_containing_multiline_code_with_strings 174 | a = "foo #{method}" + 175 | "bar" 176 | end 177 | 178 | - name: not change the indentation of multiline strings 179 | input: | 180 | def method_containing_long_string 181 | a = " 182 | Some text across multiple lines 183 | And another line 184 | " 185 | b = 5 186 | end 187 | output: | 188 | def method_containing_long_string 189 | a = " 190 | Some text across multiple lines 191 | And another line 192 | " 193 | b = 5 194 | end 195 | 196 | - name: not treat divison as start of regex 197 | input: | 198 | def foo 199 | a = 1/2 200 | b = :foo 201 | end 202 | output: | 203 | def foo 204 | a = 1/2 205 | b = :foo 206 | end 207 | 208 | - name: not indent multiline string even if it uses non quote delimiter 209 | pending: implementation of block matcher for non quote delimited strings 210 | input: | 211 | foo = < 1, :bar => 2 268 | :c => 3, :d => 4 } 269 | end 270 | output: | 271 | class Foo 272 | @@bar = { :foo => 1, :bar => 2 273 | :c => 3, :d => 4 } 274 | end 275 | 276 | - name: indent multiline block delimited with curly brackets 277 | input: | 278 | foo = bar.collect { |paragraph| 279 | paragraph.strip 280 | }.join("\n") 281 | output: | 282 | foo = bar.collect { |paragraph| 283 | paragraph.strip 284 | }.join("\n") 285 | 286 | - name: indent method call with bracketed multiline arguments including continuing statements 287 | pending: Implementation of support for continuing statements in bracketed multline arguments 288 | input: | 289 | def foo 290 | bar(first_arg, 291 | second_part_a + 292 | second_part_b, 293 | third_arg) 294 | end 295 | output: | 296 | def foo 297 | bar(first_arg, 298 | second_part_a + 299 | second_part_b, 300 | third_arg) 301 | end 302 | 303 | - name: indent continuing lines ending in = and + 304 | input: | 305 | def foo 306 | @bar ||= 307 | 1 + 308 | 2 309 | end 310 | output: | 311 | def foo 312 | @bar ||= 313 | 1 + 314 | 2 315 | end 316 | 317 | - name: indent block within implicit brackets 318 | pending: Implementation of support for this feature 319 | input: | 320 | def foo 321 | bar first_arg, 322 | second_arg, 323 | [ 324 | 1, 325 | 2, 326 | 3 327 | ], 328 | last_arg 329 | end 330 | output: | 331 | def foo 332 | bar first_arg, 333 | second_arg, 334 | [ 335 | 1, 336 | 2, 337 | 3 338 | ], 339 | last_arg 340 | end 341 | 342 | - name: "should not treat ':', '_' or '.' as word boundaries before keywords" 343 | input: | 344 | case params[:do] 345 | when "update_if" 346 | update_if.if 347 | when "update_do" 348 | update_do.do 349 | end 350 | output: | 351 | case params[:do] 352 | when "update_if" 353 | update_if.if 354 | when "update_do" 355 | update_do.do 356 | end 357 | 358 | - name: should recognize interpolation and not match other content within double quotes 359 | input: | 360 | def foo 361 | return "Foo#{" (#{bar})"}" 362 | end 363 | output: | 364 | def foo 365 | return "Foo#{" (#{bar})"}" 366 | end 367 | 368 | - name: should not non interpolated content within regexes and strings and backticks 369 | input: | 370 | def foo 371 | @foo = [ 372 | /" if bar/, 373 | `ls if`, 374 | "if fun =", 375 | 'else' 376 | ] 377 | end 378 | output: | 379 | def foo 380 | @foo = [ 381 | /" if bar/, 382 | `ls if`, 383 | "if fun =", 384 | 'else' 385 | ] 386 | end 387 | 388 | - name: should handle one liners that include an end statement correctly 389 | input: | 390 | def foo; puts 'foo' end 391 | def bar; puts 'bar' end 392 | output: | 393 | def foo; puts 'foo' end 394 | def bar; puts 'bar' end 395 | 396 | - name: should handle if and unless statements that are terminated implicitly 397 | input: | 398 | def foo 399 | bar = (1 + 2) if foobar 400 | baz unless foobaz 401 | Boo.new(:boo) rescue raise('Houston we have a problem') 402 | end 403 | output: | 404 | def foo 405 | bar = (1 + 2) if foobar 406 | baz unless foobaz 407 | Boo.new(:boo) rescue raise('Houston we have a problem') 408 | end 409 | 410 | - name: should use user specified 4 spaces 411 | spaces: 4 412 | input: | 413 | def foo 414 | h = {one: 'one'} 415 | end 416 | output: | 417 | def foo 418 | h = {one: 'one'} 419 | end 420 | -------------------------------------------------------------------------------- /spec/rbeautify/block_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative './../spec_helper.rb' 2 | 3 | describe RBeautify::BlockMatcher do 4 | 5 | describe 'class' do 6 | describe '#parse' do 7 | before(:each) do 8 | @ruby = RBeautify::Language.language(:ruby) 9 | end 10 | 11 | it 'should not match de' do 12 | expect(RBeautify::BlockMatcher.parse(@ruby, nil, 0, 'de foo', 0)).to eq(nil) 13 | end 14 | 15 | it 'should match def' do 16 | block = RBeautify::BlockMatcher.parse(@ruby, nil, 0, 'def foo', 0) 17 | expect(block).to_not eq(nil) 18 | expect(block.name).to eq(:standard) 19 | end 20 | 21 | it 'should be nested block' do 22 | block = RBeautify::BlockMatcher.parse(@ruby, nil, 0, 'if {', 0) 23 | expect(block).to_not eq(nil) 24 | expect(block.name).to eq(:curly_bracket) 25 | expect(block.parent).to_not eq(nil) 26 | expect(block.parent.name).to eq(:if) 27 | end 28 | 29 | it 'should be nested block (taking into account ends)' do 30 | block = RBeautify::BlockMatcher.parse(@ruby, nil, 0, 'if {}', 0) 31 | expect(block).to_not eq(nil) 32 | expect(block.name).to eq(:if) 33 | end 34 | 35 | it 'should be deeply nested block (taking into account ends)' do 36 | block = RBeautify::BlockMatcher.parse(@ruby, nil, 0, 'def foo(bar = {})', 0) 37 | expect(block).to_not eq(nil) 38 | expect(block.name).to eq(:standard) 39 | expect(block.parent).to eq(nil) 40 | end 41 | 42 | it 'should current block if no started or ended blocks' do 43 | block = RBeautify::BlockStart.new(@ruby.matcher(:standard), nil, 0, 0, 'def', ' foo') 44 | expect(RBeautify::BlockMatcher.parse(@ruby, block, 0, 'a = 3', 0)).to eq(block) 45 | end 46 | 47 | it 'should be newly started block if ends and starts' do 48 | current_block = RBeautify::BlockStart.new(@ruby.matcher(:if), nil, 0, 0, 'if', ' foo') 49 | block = RBeautify::BlockMatcher.parse(@ruby, current_block, 0, 'else', 0) 50 | expect(block).to_not eq(nil) 51 | expect(block.name).to eq(:if) 52 | expect(block.parent).to eq(nil) 53 | end 54 | 55 | it 'should be parent block if current block ends' do 56 | parent_block = RBeautify::BlockStart.new(@ruby.matcher(:standard), nil, 0, 0, 'class', ' Foo') 57 | child_block = RBeautify::BlockStart.new(@ruby.matcher(:standard), parent_block, 0, 0, 'def', ' foo') 58 | expect(RBeautify::BlockMatcher.parse(@ruby, child_block, 0, 'end', 0)).to eq(parent_block) 59 | end 60 | 61 | it 'should remove two blocks if top of stack ends implicitly' do 62 | parent_block = RBeautify::BlockStart.new(@ruby.matcher(:case), nil, 0, 0, 'case', ' foo') 63 | child_block = RBeautify::BlockStart.new(@ruby.matcher(:inner_case), parent_block, 0, 0, 'when', '2') 64 | expect(RBeautify::BlockMatcher.parse(@ruby, child_block, 0, 'end', 0)).to eq(nil) 65 | end 66 | end 67 | end 68 | 69 | describe '#can_nest?' do 70 | before(:each) do 71 | @language = double(RBeautify::Language) 72 | end 73 | 74 | it { 75 | expect(RBeautify::BlockMatcher.new(@language, :foo, /foo/, /bar/)).to be_can_nest(nil) 76 | } 77 | 78 | it { 79 | expect(RBeautify::BlockMatcher.new(@language, :foo, /foo/, /bar/)).to be_can_nest(double('block_start', :parse_content? => true)) 80 | } 81 | 82 | it { 83 | expect(RBeautify::BlockMatcher.new(@language, :foo, /foo/, /bar/)).to_not be_can_nest(double('block_start', :parse_content? => false)) 84 | } 85 | 86 | it { 87 | expect(RBeautify::BlockMatcher.new(@language, :foo, /foo/, /bar/, :nest_except => [:bar])).to be_can_nest(nil) 88 | } 89 | 90 | it { 91 | expect(RBeautify::BlockMatcher.new(@language, :foo, /foo/, /bar/, :nest_except => [:foo])).to be_can_nest(double('block_start', :name => :bar, :parse_content? => true)) 92 | } 93 | 94 | it { 95 | expect(RBeautify::BlockMatcher.new(@language, :foo, /foo/, /bar/, :nest_except => [:foo])).to_not be_can_nest(double('block_start', :name => :bar, :parse_content? => false)) 96 | } 97 | 98 | it { 99 | expect(RBeautify::BlockMatcher.new(@language, :foo, /foo/, /bar/, :nest_except => [:bar])).to_not be_can_nest(double('block_start', :name => :bar, :parse_content? => true)) 100 | } 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /spec/rbeautify/block_start_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative './../spec_helper.rb' 2 | 3 | describe RBeautify::BlockStart do 4 | before(:each) do 5 | @ruby = RBeautify::Language.language(:ruby) 6 | end 7 | 8 | describe 'class' do 9 | describe '#first_common_ancestor' do 10 | before(:each) do 11 | @grand_parent = RBeautify::BlockStart.new(@ruby.matcher(:standard), nil, 0, 0, 'class', ' Foo') 12 | @parent = RBeautify::BlockStart.new(@ruby.matcher(:standard), @grand_parent, 0, 0, 'def', ' foo') 13 | @first = RBeautify::BlockStart.new(@ruby.matcher(:standard), @parent, 0, 0, 'def', ' foo') 14 | @second = RBeautify::BlockStart.new(@ruby.matcher(:standard), @parent, 0, 0, 'def', ' bar') 15 | @unrelated = RBeautify::BlockStart.new(@ruby.matcher(:standard), nil, 0, 0, 'class', ' Bar') 16 | end 17 | 18 | it { expect(RBeautify::BlockStart.first_common_ancestor(@grand_parent, nil)).to eq(nil) } 19 | it { expect(RBeautify::BlockStart.first_common_ancestor(@grand_parent, @unrelated)).to be_nil } 20 | it { expect(RBeautify::BlockStart.first_common_ancestor(@grand_parent, @parent)).to eq(@grand_parent ) } 21 | it { expect(RBeautify::BlockStart.first_common_ancestor(@grand_parent, @first)).to eq(@grand_parent ) } 22 | it { expect(RBeautify::BlockStart.first_common_ancestor(@first, @second)).to eq(@parent) } 23 | end 24 | end 25 | 26 | describe '#strict_ancestor_of?' do 27 | before(:each) do 28 | @block = RBeautify::BlockStart.new(@ruby.matcher(:standard), nil, 0, 'def', ' foo', 0) 29 | end 30 | 31 | it { expect(@block).to_not be_strict_ancestor_of(nil) } 32 | it { expect(@block).to_not be_strict_ancestor_of(@block) } 33 | it { expect(@block).to_not be_strict_ancestor_of(RBeautify::BlockStart.new(@ruby.matcher(:if), nil, 0, 0, 'if', ' foo') ) } 34 | it { expect(@block).to be_strict_ancestor_of(RBeautify::BlockStart.new(@ruby.matcher(:if), @block, 0, 0, 'if', ' foo') ) } 35 | end 36 | 37 | describe '#total_indent_size' do 38 | before(:each) do 39 | @block = RBeautify::BlockStart.new(@ruby.matcher(:standard), nil, 0, 'def', ' foo', 0) 40 | end 41 | 42 | it { 43 | expect(RBeautify::BlockStart.new(@ruby.matcher(:standard), nil, 0, 0, 'def', ' foo').total_indent_size).to eq(2) 44 | } 45 | 46 | it 'should sum with parents total indent size' do 47 | parent = double('parent_start_block', :total_indent_size => 4) 48 | expect(RBeautify::BlockStart.new(@ruby.matcher(:standard), parent, 0, 0, 'def', ' foo').total_indent_size).to eq(6) 49 | end 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /spec/rbeautify/config/ruby_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative './../../spec_helper.rb' 2 | 3 | run_fixtures_for_language(:ruby) 4 | 5 | describe 'Ruby' do 6 | 7 | describe 'matchers' do 8 | before(:each) do 9 | @ruby = RBeautify::Language.language(:ruby) 10 | end 11 | 12 | describe 'standard' do 13 | before(:each) do 14 | @matcher = @ruby.matcher(:standard) 15 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, '', '') 16 | end 17 | 18 | it { expect(@matcher.parse_block_start('class Foo; end', nil, 0, 0)).to be_block_start_like(:standard, 0, 'class', ' Foo; end') } 19 | it { expect(@matcher.parse_block_start('module Foo', nil, 0, 0)).to be_block_start_like(:standard, 0, 'module', ' Foo') } 20 | it { expect(@matcher.parse_block_start('def foo()', nil, 0, 0)).to be_block_start_like(:standard, 0, 'def', ' foo()') } 21 | it { expect(@matcher.parse_block_start('end Foo', nil, 0, 0)).to be_nil } 22 | 23 | it { expect(@current_block.parse_block_end('end', 0)).to be_block_end_like(@current_block, 0, 'end', '') } 24 | it { expect(@current_block.parse_block_end(';end', 0)).to be_block_end_like(@current_block, 0, ';end', '') } 25 | it { expect(@current_block.parse_block_end('; end', 0)).to be_block_end_like(@current_block, 1, ' end', '') } 26 | it { expect(@current_block.parse_block_end('rescue', 0)).to be_block_end_like(@current_block, 0, 'rescue', '') } 27 | it { expect(@current_block.parse_block_end('ensure', 0)).to be_block_end_like(@current_block, 0, 'ensure', '') } 28 | it { expect(@current_block.parse_block_end('}', 0)).to be_nil } 29 | end 30 | 31 | describe 'if' do 32 | before(:each) do 33 | @matcher = @ruby.matcher(:if) 34 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, 'if', ' foo') 35 | end 36 | 37 | it { expect(@matcher).to be_end_can_also_be_start} 38 | 39 | it { expect(@matcher.parse_block_start('if foo', nil, 0, 0)).to be_block_start_like(:if, 0, 'if', ' foo') } 40 | it { expect(@matcher.parse_block_start('then foo = bar', nil, 0, 0)).to be_block_start_like(:if, 0, 'then', ' foo = bar') } 41 | it { expect(@matcher.parse_block_start('if_foo', nil, 0, 0)).to be_nil } 42 | it { expect(@matcher.parse_block_start('if: foo', nil, 0, 0)).to be_nil } 43 | 44 | it { expect(@current_block.parse_block_end('end', 0)).to be_block_end_like(@current_block, 0, 'end', '') } 45 | it { expect(@current_block.parse_block_end('then', 0)).to be_block_end_like(@current_block, 0, 'then', '') } 46 | it { expect(@current_block.parse_block_end('else', 0)).to be_block_end_like(@current_block, 0, 'else', '') } 47 | it { expect(@current_block.parse_block_end('a = 3', 0)).to be_nil } 48 | end 49 | 50 | describe 'curly_bracket' do 51 | before(:each) do 52 | @matcher = @ruby.matcher(:curly_bracket) 53 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, '{', '') 54 | end 55 | 56 | it { expect(@matcher.parse_block_start('{', nil, 0, 0)).to be_block_start_like(:curly_bracket, 0, '{', '') } 57 | it { expect(@matcher.parse_block_start('end', nil, 0, 0)).to be_nil } 58 | 59 | it { expect(@current_block.parse_block_end('}', 0)).to be_block_end_like(@current_block, 0, '}', '') } 60 | it { expect(@current_block.parse_block_end('end', 0)).to be_nil } 61 | 62 | end 63 | 64 | describe 'double_quote' do 65 | before(:each) do 66 | @matcher = @ruby.matcher(:double_quote) 67 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, '"', 'foo"') 68 | end 69 | 70 | it { expect(@matcher.parse_block_start('a = "foo"', nil, 0, 0)).to be_block_start_like(:double_quote, 4, '"', 'foo"') } 71 | it { expect(@matcher.parse_block_start('a = 2', nil, 0, 0)).to be_nil } 72 | 73 | it { expect(@current_block.parse_block_end(' bar"', 0)).to be_block_end_like(@current_block, 4, '"', '') } 74 | it { expect(@current_block.parse_block_end(' " + bar + "', 0)).to be_block_end_like(@current_block, 1, '"', ' + bar + "') } 75 | it { expect(@current_block.parse_block_end('\\\\"', 0)).to be_block_end_like(@current_block, 2, '"', '') } 76 | it { expect(@current_block.parse_block_end('\\" more string"', 0)).to be_block_end_like(@current_block, 14, '"', '') } 77 | it { expect(@current_block.parse_block_end('a = 2', 0)).to be_nil } 78 | it { expect(@current_block.parse_block_end('\\"', 0)).to be_nil } 79 | it { expect(@current_block.parse_block_end('\\\\\\"', 0)).to be_nil } 80 | end 81 | 82 | describe 'single_quote' do 83 | before(:each) do 84 | @matcher = @ruby.matcher(:single_quote) 85 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, "'", "foo'") 86 | end 87 | 88 | it { expect(@matcher).not_to be_end_can_also_be_start} 89 | it { expect(@matcher.parse_block_start("describe '#foo?' do", nil, 0, 0)).to be_block_start_like(:single_quote, 9, "'", "#foo?' do") } 90 | it { expect(@matcher.parse_block_start('a = 2', nil, 0, 0)).to be_nil } 91 | 92 | it { expect(@current_block.parse_block_end("#foo?' do", 9)).to be_block_end_like(@current_block, 14, "'", ' do')} 93 | it { expect(@current_block.parse_block_end('a = 2', 0)).to be_nil } 94 | end 95 | 96 | describe 'continuing_line' do 97 | before(:each) do 98 | @matcher = @ruby.matcher(:continuing_line) 99 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 8, ',', '') 100 | end 101 | 102 | it { expect(@matcher.parse_block_start('foo :bar,', nil, 0, 0)).to be_block_start_like(:continuing_line, 8, ',', '') } 103 | it { expect(@matcher.parse_block_start('a = 3 &&', nil, 0, 0)).to be_block_start_like(:continuing_line, 6, '&&', '') } 104 | it { expect(@matcher.parse_block_start('a = 3 ||', nil, 0, 0)).to be_block_start_like(:continuing_line, 6, '||', '') } 105 | it { expect(@matcher.parse_block_start('a = 3 +', nil, 0, 0)).to be_block_start_like(:continuing_line, 6, '+', '') } 106 | it { expect(@matcher.parse_block_start('a = 3 -', nil, 0, 0)).to be_block_start_like(:continuing_line, 6, '-', '') } 107 | it { expect(@matcher.parse_block_start("a \\", nil, 0, 0)).to be_block_start_like(:continuing_line, 2, '\\', '') } 108 | it { expect(@matcher.parse_block_start('a ?', nil, 0, 0)).to be_block_start_like(:continuing_line, 1, ' ?', '') } 109 | it { expect(@matcher.parse_block_start('a ? # some comment', nil, 0, 0)).to be_block_start_like(:continuing_line, 1, ' ? # some comment', '') } 110 | it { expect(@matcher.parse_block_start('a = 3', nil, 0, 0)).to be_nil } 111 | it { expect(@matcher.parse_block_start('a = foo.bar?', nil, 0, 0)).to be_nil } 112 | it { expect(@matcher.parse_block_start('# just a comment', nil, 0, 0)).to be_nil } 113 | 114 | it { expect(@current_block.parse_block_end('a = 3', 0)).to be_block_end_like(@current_block, 0, '', 'a = 3') } 115 | it { expect(@current_block.parse_block_end('foo :bar,', 0)).to be_nil } 116 | it { expect(@current_block.parse_block_end('a = 3 &&', 0)).to be_nil } 117 | it { expect(@current_block.parse_block_end('a = 3 +', 0)).to be_nil } 118 | it { expect(@current_block.parse_block_end("a \\", 0)).to be_nil } 119 | it { expect(@current_block.parse_block_end('# just a comment', 0)).to be_nil } 120 | it { expect(@current_block.parse_block_end('#', 0)).to be_nil } 121 | end 122 | 123 | describe 'round_bracket' do 124 | before(:each) do 125 | @matcher = @ruby.matcher(:round_bracket) 126 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, '(', '') 127 | end 128 | 129 | it { expect(@matcher.parse_block_start('a = (foo,', nil, 0, 0)).to be_block_start_like(:round_bracket, 4, '(', 'foo,') } 130 | it { expect(@matcher.parse_block_start('anything else', nil, 0, 0)).to be_nil } 131 | 132 | it { expect(@current_block.parse_block_end('foo)', 0)).to be_block_end_like(@current_block, 3, ')', '') } 133 | it { expect(@current_block.parse_block_end('a = 3', 0)).to be_nil } 134 | end 135 | 136 | describe 'comment' do 137 | before(:each) do 138 | @matcher = @ruby.matcher(:comment) 139 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 8, '#', ' comment') 140 | end 141 | 142 | it { expect(@matcher.parse_block_start('anything # comment', nil, 0, 0)).to be_block_start_like(:comment, 8, ' #', ' comment') } 143 | it { expect(@matcher.parse_block_start('#', nil, 0, 0)).to be_block_start_like(:comment, 0, '#', '') } 144 | it { expect(@matcher.parse_block_start('anything else', nil, 0, 0)).to be_nil } 145 | 146 | it { expect(@current_block.parse_block_end('anything', 0)).to be_block_end_like(@current_block, 8, '', '') } 147 | it { expect(@current_block.parse_block_end('', 0)).to be_block_end_like(@current_block, 0, '', '') } 148 | end 149 | 150 | describe 'begin' do 151 | before(:each) do 152 | @matcher = @ruby.matcher(:begin) 153 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, 'begin', '') 154 | end 155 | 156 | it { expect(@matcher.parse_block_start('begin', nil, 0, 0)).to be_block_start_like(:begin, 0, 'begin', '') } 157 | it { expect(@matcher.parse_block_start('beginning', nil, 0, 0)).to be_nil } 158 | 159 | it { expect(@current_block.parse_block_end('rescue', 0)).to be_block_end_like(@current_block, 0, 'rescue', '') } 160 | it { expect(@current_block.parse_block_end('ensure', 0)).to be_block_end_like(@current_block, 0, 'ensure', '') } 161 | it { expect(@current_block.parse_block_end('else', 0)).to be_block_end_like(@current_block, 0, 'else', '') } 162 | it { expect(@current_block.parse_block_end('end', 0)).to be_block_end_like(@current_block, 0, 'end', '') } 163 | end 164 | 165 | describe 'regex' do 166 | before(:each) do 167 | @matcher = @ruby.matcher(:regex) 168 | @current_block = RBeautify::BlockStart.new(@matcher, nil, 0, 0, '/', 'foo/') 169 | end 170 | 171 | it { expect(@matcher.parse_block_start('/foo/', nil, 0, 0)).to be_block_start_like(:regex, 0, '/', 'foo/') } 172 | it { expect(@matcher.parse_block_start(', /foo/', nil, 0, 0)).to be_block_start_like(:regex, 0, ', /', 'foo/') } 173 | it { expect(@matcher.parse_block_start('foo = /bar/', nil, 0, 0)).to be_block_start_like(:regex, 4, '= /', 'bar/') } 174 | it { expect(@matcher.parse_block_start('foo =~ /bar/', nil, 0, 0)).to be_block_start_like(:regex, 5, '~ /', 'bar/') } 175 | it { expect(@matcher.parse_block_start('1/2', nil, 0, 0)).to be_nil } 176 | it { expect(@matcher.parse_block_start('anything else', nil, 0, 0)).to be_nil } 177 | 178 | it { expect(@current_block.parse_block_end('foo/', 0)).to be_block_end_like(@current_block, 3, '/', '') } 179 | it { expect(@current_block.parse_block_end('foo/', 0)).to be_block_end_like(@current_block, 3, '/', '') } 180 | it { expect(@current_block.parse_block_end('', 0)).to be_nil } 181 | 182 | end 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /spec/rbeautify/line_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative './../spec_helper.rb' 2 | 3 | describe RBeautify::Line do 4 | 5 | describe '#format' do 6 | let(:config) do 7 | { 8 | 'tab_size' => 2, 9 | 'translate_tabs_to_spaces' => 'true' 10 | } 11 | end 12 | 13 | before(:each) do 14 | @language = double(RBeautify::Language) 15 | end 16 | 17 | it 'should just strip with empty stack' do 18 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(nil) 19 | expect(RBeautify::Line.new(@language, ' a = 3 ', 0, config).format).to eq("a = 3") 20 | end 21 | 22 | it 'should indent with existing indent' do 23 | current_block = double('block_start', 24 | :total_indent_size => 2, 25 | :format_content? => true, 26 | :strict_ancestor_of? => false 27 | ) 28 | expect(RBeautify::BlockStart).to receive(:first_common_ancestor).at_least(1).and_return(current_block) 29 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(current_block) 30 | expect(RBeautify::Line.new(@language, ' a = 3 ', 0, current_block, config).format).to eq(' a = 3') 31 | end 32 | 33 | it 'leave empty lines blank' do 34 | current_block = double('block_start', :format_content? => true) 35 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(current_block) 36 | expect(RBeautify::Line.new(@language, ' ', 0, current_block, config).format).to eq('') 37 | end 38 | 39 | it 'should remove indent with match to end of block' do 40 | current_block = double('block_start', 41 | :format_content? => true, 42 | :indent_end_line? => false 43 | ) 44 | expect(RBeautify::BlockStart).to receive(:first_common_ancestor).at_least(1).and_return(nil) 45 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(nil) 46 | expect(RBeautify::Line.new(@language, ' end ', 0, current_block, config).format).to eq('end') 47 | end 48 | 49 | it 'should not remove indent with match to end of block if indent_end_line? is true' do 50 | current_block = double('block_start', 51 | :total_indent_size => 2, 52 | :format_content? => true, 53 | :indent_end_line? => true 54 | ) 55 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(nil) 56 | expect(RBeautify::Line.new(@language, ' end ', 0, current_block, config).format).to eq(' end') 57 | end 58 | 59 | it 'should leave indent at old stack level with match of new block' do 60 | current_block = double('current_block_start', 61 | :total_indent_size => 2, 62 | :format_content? => true 63 | ) 64 | new_block = double('new_block_start', 65 | :format_content? => true, 66 | :strict_ancestor_of? => false 67 | ) 68 | expect(RBeautify::BlockStart).to receive(:first_common_ancestor).at_least(1).and_return(current_block) 69 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(new_block) 70 | expect(RBeautify::Line.new(@language, 'class Foo', 0, current_block, config).format).to eq(' class Foo') 71 | end 72 | 73 | it 'should remove indent if a block ends and starts' do 74 | current_block = double('current_block_start', 75 | :total_indent_size => 0, 76 | :format_content? => true 77 | ) 78 | new_block = double('new_block_start', 79 | :format_content? => true, 80 | :strict_ancestor_of? => false 81 | ) 82 | expect(RBeautify::BlockStart).to receive(:first_common_ancestor).at_least(1).and_return(current_block) 83 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(new_block) 84 | expect(RBeautify::Line.new(@language, ' else ', 0, current_block, config).format).to eq('else') 85 | end 86 | 87 | it 'should not change when format is false' do 88 | current_block = double('block_start', :format_content? => false) 89 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(current_block) 90 | expect( 91 | RBeautify::Line.new( 92 | @language, 93 | ' some content after program has finished. ', 0, 94 | current_block, 95 | config 96 | ).format 97 | ).to eq(" some content after program has finished. ") 98 | end 99 | 100 | it 'should leave indent with match to end of block (but no format)' do 101 | current_block = double('block_start', 102 | :format_content? => false 103 | ) 104 | expect(RBeautify::BlockMatcher).to receive(:parse).and_return(nil) 105 | expect(RBeautify::Line.new(@language, ' "', 0, current_block, config).format).to eq(' "') 106 | end 107 | 108 | end 109 | 110 | end 111 | -------------------------------------------------------------------------------- /spec/rbeautify_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative './spec_helper.rb' 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | unless ENV['CI'] == true 2 | require 'pry' 3 | end 4 | 5 | require 'yaml' 6 | require_relative './../lib/rbeautify.rb' 7 | 8 | module RBeautifyMatchers 9 | # Adds more descriptive failure messages to the dynamic be_valid matcher 10 | class BeBlockStartLike #:nodoc: 11 | def initialize(block_matcher_name, offset, match, after_match) 12 | # triggers the standard Spec::Matchers::Be magic, as if the original Spec::Matchers#method_missing had fired 13 | @block_matcher_name = block_matcher_name 14 | @offset = offset 15 | @match = match 16 | @after_match = after_match 17 | end 18 | 19 | def matches?(target_block) 20 | @target_block = target_block 21 | return !target_block.nil? && 22 | (expected_string == got_string) 23 | end 24 | 25 | def failure_message 26 | "expected\n#{expected_string} but got\n#{got_string}" 27 | end 28 | 29 | def failure_message_when_negated 30 | "expected to be different from #{expected_string}" 31 | end 32 | 33 | def expected_string 34 | "name: #{@block_matcher_name}, offset: #{@offset}, match: '#{@match}', after_match: '#{@after_match}'" 35 | end 36 | 37 | def got_string 38 | "name: #{@target_block.block_matcher.name}, offset: #{@target_block.offset}, match: '#{@target_block.match}', after_match: '#{@target_block.after_match}'" 39 | end 40 | 41 | def description 42 | "block start with" 43 | end 44 | end 45 | 46 | class BeBlockEndLike #:nodoc: 47 | def initialize(block_start, offset, match, after_match) 48 | # triggers the standard Spec::Matchers::Be magic, as if the original Spec::Matchers#method_missing had fired 49 | @block_start = block_start 50 | @offset = offset 51 | @match = match 52 | @after_match = after_match 53 | end 54 | 55 | def matches?(target_block) 56 | @target_block = target_block 57 | expected_string == got_string 58 | end 59 | 60 | def failure_message 61 | "expected\n#{expected_string} but got\n#{got_string}" 62 | end 63 | 64 | def failure_message_when_negated 65 | "expected to be different from #{expected_string}" 66 | end 67 | 68 | def expected_string 69 | "block_end: #{@block_start.name}, offset: #{@offset}, match: '#{@match}', after_match: '#{@after_match}'" 70 | end 71 | 72 | def got_string 73 | if @target_block.nil? 74 | 'nil' 75 | else 76 | "block_end: #{@target_block.block_start.name}, offset: #{@target_block.offset}, match: '#{@target_block.match}', after_match: '#{@target_block.after_match}'" 77 | end 78 | end 79 | 80 | def description 81 | "block end with" 82 | end 83 | 84 | end 85 | 86 | def be_block_start_like(block_matcher, offset, match, after_match) 87 | BeBlockStartLike.new(block_matcher, offset, match, after_match) 88 | end 89 | 90 | def be_block_end_like(block_start, offset, match, after_match) 91 | BeBlockEndLike.new(block_start, offset, match, after_match) 92 | end 93 | end 94 | 95 | RSpec.configure do |config| 96 | config.include(RBeautifyMatchers) 97 | end 98 | 99 | def run_fixtures_for_language(language) 100 | fixtures = YAML.load_file(File.dirname(__FILE__) + "/fixtures/#{language}.yml") 101 | 102 | describe language do 103 | let(:config) do 104 | { 105 | 'tab_size' => 2, 106 | 'translate_tabs_to_spaces' => 'true' 107 | } 108 | end 109 | 110 | fixtures.each do |fixture| 111 | it "should #{fixture['name']}" do 112 | input = fixture['input'] 113 | output = fixture['output'] || input 114 | debug = fixture['debug'] || false 115 | 116 | config['tab_size'] = fixture.fetch('spaces', 2) 117 | 118 | if fixture['pending'] 119 | next 120 | pending fixture['pending'] do 121 | expect(RBeautify.beautify_string(language, input, config)).to eq(output) 122 | end 123 | else 124 | RBeautify::BlockMatcher.debug = debug 125 | expect(RBeautify.beautify_string(language, input, config)).to eq(output) 126 | end 127 | end 128 | end 129 | end 130 | 131 | end 132 | --------------------------------------------------------------------------------