import os import re from difflib import ndiff import sublime import sublime_plugin # As evidenced here, the indent test code is based on code in this imported # module from the Default package. from Default.run_syntax_tests import package_relative_path, PACKAGES_FILE_REGEX from Default.run_syntax_tests import show_panel_on_build, append ### --------------------------------------------------------------------------- class RunIndentTestsCommand(sublime_plugin.WindowCommand): """ A custom command that can be executed from a build system target for testing indent rules. The current file needs to have a known name prefix to be considered, and must have a header line that tells the tester what syntax it is. The test inserts the data unindented into a view and then runs the reindent command to indent it, comparing the results to what the input file looked like to verify the indent level. """ def run(self, find_all=False, **kwargs): if not hasattr(self, 'output_view'): # Try not to call get_output_panel until the regexes are assigned self.output_view = self.window.create_output_panel('exec') settings = self.output_view.settings() settings.set('result_file_regex', PACKAGES_FILE_REGEX) settings.set('result_base_dir', sublime.packages_path()) settings.set('word_wrap', True) settings.set('line_numbers', False) settings.set('gutter', False) settings.set('scroll_past_end', False) # Call create_output_panel a second time after assigning the above # settings, so that it'll be picked up as a result buffer self.window.create_output_panel('exec') if find_all: tests = sublime.find_resources('indentation_test*') else: # Can't test files that are not in the packages folder. pkg_path = package_relative_path(self.window.active_view()) if not pkg_path: return # We can only test files that we know are indent tests. file_name = os.path.basename(pkg_path) if not file_name.startswith("indentation_test"): return sublime.error_message( 'The current file is not an indentation test') # Get the syntax for the current file out of the header. syntax = self.syntax_for_file(pkg_path) if syntax is None: return sublime.error_message( 'The indentation test header is missing'); # At a minimum, we are going to test this file. tests = [pkg_path] # Find all other files that are using this syntax and add them to # the test list. Realistically this should only happen if the # current file was a tmPreferences file, but there is currently no # good way to get the appropriate syntax from such a file. for file in sublime.find_resources('indentation_test*'): if file != pkg_path and self.syntax_for_file(file) == syntax: tests.append(file) show_panel_on_build(self.window) total_lines = 0 failed_lines = 0 for test in tests: lines, failures = self.run_indent_test(test) total_lines += lines if len(failures) > 0: failed_lines += len(failures) for line in failures: append(self.output_view, line + '\n') if failed_lines > 0: message = 'FAILED: {} of {} lines in {} files failed\n' params = (failed_lines, total_lines, len(tests)) else: message = 'Success: {} lines in {} files passed\n' params = (total_lines, len(tests)) append(self.output_view, message.format(*params)) append(self.output_view, '[Finished]') def run_indent_test(self, package_file): syntax = self.syntax_for_file(package_file) if syntax is None: return (0, []) input_file = sublime.load_resource(package_file) input_lines = input_file.splitlines() view = self.window.create_output_panel("indent_test", False) view.assign_syntax(syntax) view.run_command("select_all") view.run_command("left_delete") for line in input_lines: view.run_command("append", {"characters": line.lstrip() + "\n"}) view.run_command("select_all") view.run_command("reindent") output_file = view.substr(sublime.Region(0, view.size())) output_lines = output_file.splitlines() if input_file == output_file: return (len(input_lines), []) diff = ndiff(input_file.splitlines(), output_file.splitlines()) line_num = 0 errors = [] for line in diff: prefix = line[:2] line_num += 1 if prefix in (" ", "+ ") else 0 if prefix == "+ ": msg = "{}:{}:1: Indent Failure: {}".format(package_file, line_num, line[2:]) errors.append(msg) # self.window.run_command("show_panel", {"panel": "output.indent_test"}) return (len(input_lines), errors) def syntax_for_file(self, package_file): first_line = sublime.load_resource(package_file).splitlines()[0] match = re.match('^.*INDENT TEST "(.*?)"', first_line) if not match: return None return match.group(1)