Repository: threedaymonk/htmlbeautifier Branch: master Commit: 3d75d9b4e099 Files: 23 Total size: 44.5 KB Directory structure: gitextract_6uwmr_5x/ ├── .gitignore ├── .rspec ├── .rubocop.yml ├── COPYING.txt ├── Gemfile ├── History.txt ├── README.md ├── Rakefile ├── bin/ │ └── htmlbeautifier ├── contributors.txt ├── htmlbeautifier.gemspec ├── lib/ │ ├── htmlbeautifier/ │ │ ├── builder.rb │ │ ├── html_parser.rb │ │ ├── parser.rb │ │ ├── ruby_indenter.rb │ │ └── version.rb │ └── htmlbeautifier.rb └── spec/ ├── .rubocop.yml ├── behavior_spec.rb ├── documents_spec.rb ├── executable_spec.rb ├── parser_spec.rb └── spec_helper.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.gem Gemfile.lock /tmp .ruby-version ================================================ FILE: .rspec ================================================ --color --require spec_helper -I lib ================================================ FILE: .rubocop.yml ================================================ inherit_mode: merge: - Exclude require: - standard - rubocop-rake inherit_gem: standard: config/base.yml AllCops: NewCops: disable SuggestExtensions: false TargetRubyVersion: 3.2 Exclude: - '**/*.gemspec' - '**/Rakefile' ================================================ FILE: COPYING.txt ================================================ Copyright (c) 2007-2015 Paul Battley Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Gemfile ================================================ # frozen_string_literal: true source "https://rubygems.org" gemspec ================================================ FILE: History.txt ================================================ == 1.3.1 (2017-05-04) * Fix erroneous additional indentation being applied to code in node. == 1.0.2 (2015-02-23) * Allow '<' in attributes in order to support AngularJS. == 1.0.1 (2015-02-22) * Improve help output of command-line tool. == 1.0.0 (2015-01-19) * Improve and document the API. * Specify Ruby support: >= 1.9.2. * Move tests to RSpec. * Stop breaking on excessive outdenting by default. == 0.0.12 (2014-12-30) * Add new lines after
and around
.
* Add HTML5 block elements and remove those deprecated in HTML 4.0.
* Fix breakage in command-line tool.
* Command-line tool is now tested.
* No longer hangs on certain large files.

== 0.0.11 (2014-12-29)
* Preserve formatting inside 
.
* Add new lines after block-like elements.

== 0.0.10 (2014-09-28)
* Set tab width via CLI option.

== 0.0.9 (2013-12-29)
* Support 
etc. without /. * Make element names case-insensitive. == 0.0.8 (2013-08-27) * Avoid wiping output file on error when working in place. * Report filename when an error occurs. * Clarify licence (with contributor permission): MIT. == 0.0.7 (2012-07-10) * Modernise gem structure. * Document Beautifier. * Improve outdent reporting. == 0.0.6 (2010-07-01) * Fix new line at end of output when modifying file. == 0.0.5 (2010-07-01) * Add option to modify file in place. * Report source line when outdenting too far. == 0.0.4 (2009-10-13) * Outdent 'else' correctly. == 0.0.3 (2009-10-13) * Support <%- ... -%> * Eliminated dependency on hoe. == 0.0.2 (2009-10-11) * Move from a single file to multiple files. * Fix parsing of standalone element immediately after closing element. * Don't break on empty script elements. * Emit new line at end of output. * Parse IE conditional comments. * Release as a gem. ================================================ FILE: README.md ================================================ # HTML Beautifier A normaliser/beautifier for HTML that also understands embedded Ruby. Ideal for tidying up Rails templates. ## What it does * Normalises hard tabs to spaces (or vice versa) * Removes trailing spaces * Indents after opening HTML elements * Outdents before closing elements * Collapses multiple whitespace * Indents after block-opening embedded Ruby (if, do etc.) * Outdents before closing Ruby blocks * Outdents elsif and then indents again * Indents the left-hand margin of JavaScript and CSS blocks to match the indentation level of the code ## Usage ### From the command line To update files in-place: ``` sh $ htmlbeautifier file1.html.erb [file2.html.erb ...] ``` or to operate on standard input and output: ``` sh $ htmlbeautifier < untidy.html.erb > formatted.html.erb ``` ### In your code ```ruby require 'htmlbeautifier' beautiful = HtmlBeautifier.beautify(untify_html_string) ``` You can also specify how to indent (the default is two spaces): ```ruby beautiful = HtmlBeautifier.beautify(untidy_html_string, indent: "\t") ``` ## Installation This is a Ruby gem. To install the command-line tool (you may need `sudo`): ```sh $ gem install htmlbeautifier ``` To use the gem with Bundler, add to your `Gemfile`: ```ruby gem 'htmlbeautifier' ``` ## Contributing 1. Follow [these guidelines][git-commit] when writing commit messages (briefly, the first line should begin with a capital letter, use the imperative mood, be no more than 50 characters, and not end with a period). 2. Include tests. [git-commit]:http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html ================================================ FILE: Rakefile ================================================ require "rspec/core/rake_task" desc "Run the specs." RSpec::Core::RakeTask.new do |t| t.pattern = "spec/**/*_spec.rb" t.verbose = false end task :default => [:spec] if Gem.loaded_specs.key?('rubocop') require 'rubocop/rake_task' RuboCop::RakeTask.new task(:default).prerequisites << task(:rubocop) end ================================================ FILE: bin/htmlbeautifier ================================================ #!/usr/bin/env ruby # frozen_string_literal: true require "htmlbeautifier" require "optparse" require "fileutils" require "stringio" def beautify(name, input, output, options) output.puts HtmlBeautifier.beautify(input, options) rescue => e raise "Error parsing #{name}: #{e}" end executable = File.basename(__FILE__) options = {indent: " "} parser = OptionParser.new do |opts| opts.banner = "Usage: #{executable} [options] [file ...]" opts.separator <<~STRING #{executable} has two modes of operation: 1. If no files are listed, it will read from standard input and write to standard output. 2. If files are listed, it will modify each file in place, overwriting it with the beautified output. The following options are available: STRING opts.on( "-t", "--tab-stops NUMBER", Integer, "Set number of spaces per indent (default #{options[:tab_stops]})" ) do |num| options[:indent] = " " * num end opts.on( "-T", "--tab", "Indent using tabs" ) do options[:indent] = "\t" end opts.on( "-i", "--indent-by NUMBER", Integer, "Indent the output by NUMBER steps (default 0)." ) do |num| options[:initial_level] = num end opts.on( "-e", "--stop-on-errors", "Stop when invalid nesting is encountered in the input" ) do |num| options[:stop_on_errors] = num end opts.on( "-b", "--keep-blank-lines NUMBER", Integer, "Set number of consecutive blank lines" ) do |num| options[:keep_blank_lines] = num end opts.on( "-l", "--lint-only", "Lint only, error on files which would be modified", "This is not available when reading from standard input" ) do |num| options[:lint_only] = num end opts.on( "-v", "--version", "Display version and exit" ) do puts HtmlBeautifier::VERSION::STRING exit end opts.on( "-h", "--help", "Display this help message and exit" ) do puts opts exit end end parser.parse! if ARGV.any? failures = [] ARGV.each do |path| input = File.read(path) if options[:lint_only] output = StringIO.new beautify path, input, output, options failures << path unless input == output.string else temppath = "#{path}.tmp" File.open(temppath, "w") do |file| beautify path, input, file, options end FileUtils.mv temppath, path end end unless failures.empty? warn [ "Lint failed - files would be modified:", *failures ].join("\n") exit 1 end else beautify "standard input", $stdin.read, $stdout, options end ================================================ FILE: contributors.txt ================================================ Paul Battley Chris Berkhout Matti Lehtonen Jakub Jirutka Joe Rossi Mike Kozono Nicholas Rutherford Alexander Daniel Huizhe Wang Manoj Krishnan ================================================ FILE: htmlbeautifier.gemspec ================================================ require File.expand_path("../lib/htmlbeautifier/version", __FILE__) spec = Gem::Specification.new do |s| s.name = "htmlbeautifier" s.version = HtmlBeautifier::VERSION::STRING s.summary = "HTML/ERB beautifier" s.description = "A normaliser/beautifier for HTML that also understands embedded Ruby." s.author = "Paul Battley" s.email = "pbattley@gmail.com" s.homepage = "http://github.com/threedaymonk/htmlbeautifier" s.license = "MIT" s.files = %w(Rakefile README.md) + Dir.glob("{bin,test,lib}/**/*") s.executables = Dir["bin/**"].map { |f| File.basename(f) } s.require_paths = ["lib"] s.required_ruby_version = '>= 2.6.0' s.add_development_dependency "rake", "~> 13" s.add_development_dependency "rspec", "~> 3" s.add_development_dependency "standard", "~> 1.33" s.add_development_dependency "rubocop-rspec", "~> 2" s.add_development_dependency "rubocop-rake", "~> 0.6" end ================================================ FILE: lib/htmlbeautifier/builder.rb ================================================ # frozen_string_literal: true require "htmlbeautifier/parser" require "htmlbeautifier/ruby_indenter" module HtmlBeautifier class Builder DEFAULT_OPTIONS = { indent: " ", initial_level: 0, stop_on_errors: false, keep_blank_lines: 0 }.freeze def initialize(output, options = {}) options = DEFAULT_OPTIONS.merge(options) @tab = options[:indent] @stop_on_errors = options[:stop_on_errors] @level = options[:initial_level] @keep_blank_lines = options[:keep_blank_lines] @new_line = false @empty = true @ie_cc_levels = [] @output = output @embedded_indenter = RubyIndenter.new end private def error(text) return unless @stop_on_errors raise text end def indent @level += 1 end def outdent error "Extraneous closing tag" if @level == 0 @level = [@level - 1, 0].max end def emit(*strings) strings_join = strings.join("") @output << "\n" if @new_line && !@empty @output << (@tab * @level) if @new_line && !strings_join.strip.empty? @output << strings_join @new_line = false @empty = false end def new_line @new_line = true end def embed(opening, code, closing) lines = code.split(%r{\n}).map(&:strip) outdent if @embedded_indenter.outdent?(lines) emit opening, code, closing indent if @embedded_indenter.indent?(lines) end def foreign_block(opening, code, closing) emit opening emit_reindented_block_content code unless code.strip.empty? emit closing end def emit_reindented_block_content(code) lines = code.strip.split(%r{\n}) indentation = foreign_block_indentation(code) indent new_line lines.each do |line| emit line.rstrip.sub(%r{^#{indentation}}, "") new_line end outdent end def foreign_block_indentation(code) code.split(%r{\n}).find { |ln| !ln.strip.empty? }[%r{^\s+}] end def preformatted_block(opening, content, closing) new_line emit opening, content, closing new_line end def standalone_element(elem) emit elem new_line if elem =~ %r{^|[^>])* }mx HTML_VOID_ELEMENTS = %r{(?: area | base | br | col | command | embed | hr | img | input | keygen | link | meta | param | source | track | wbr )}mix HTML_BLOCK_ELEMENTS = %r{(?: address | article | aside | audio | blockquote | canvas | dd | details | dir | div | dl | dt | fieldset | figcaption | figure | footer | form | h1 | h2 | h3 | h4 | h5 | h6 | header | hr | li | menu | noframes | noscript | ol | p | pre | section | table | tbody | td | tfoot | th | thead | tr | ul | video )}mix MAPPINGS = [ [%r{(<%-?=?)(.*?)(-?%>)}om, :embed], [%r{}om, :close_ie_cc], [%r{}om, :standalone_element], [%r{}om, :standalone_element], [%r{()(.*?)()}omi, :foreign_block], [%r{()(.*?)()}omi, :foreign_block], [%r{()(.*?)(
)}omi, :preformatted_block], [%r{()(.*?)()}omi, :preformatted_block], [%r{<#{HTML_VOID_ELEMENTS}(?: #{ELEMENT_CONTENT})?/?>}om, :standalone_element], [%r{}om, :close_block_element], [%r{<#{HTML_BLOCK_ELEMENTS}(?: #{ELEMENT_CONTENT})?>}om, :open_block_element], [%r{}om, :close_element], [%r{<#{ELEMENT_CONTENT}[^/]>}om, :open_element], [%r{<[\w\-]+(?: #{ELEMENT_CONTENT})?/>}om, :standalone_element], [%r{(\s*\r?\n\s*)+}om, :new_lines], [%r{[^<\n]+}, :text] ].freeze def initialize super do |p| MAPPINGS.each do |regexp, method| p.map regexp, method end end end end end ================================================ FILE: lib/htmlbeautifier/parser.rb ================================================ # frozen_string_literal: true require "strscan" module HtmlBeautifier class Parser def initialize @maps = [] yield self if block_given? end def map(pattern, method) @maps << [pattern, method] end def scan(subject, receiver) @scanner = StringScanner.new(subject) dispatch(receiver) until @scanner.eos? end def source_so_far @scanner.string[0...@scanner.pos] end def source_line_number [source_so_far.chomp.split(%r{\n}).count, 1].max end private def dispatch(receiver) _, method = @maps.find { |pattern, _| @scanner.scan(pattern) } raise "Unmatched sequence" unless method receiver.__send__(method, *extract_params(@scanner)) rescue => e raise "#{e.message} on line #{source_line_number}" end def extract_params(scanner) return [scanner[0]] unless scanner[1] params = [] i = 1 while scanner[i] params << scanner[i] i += 1 end params end end end ================================================ FILE: lib/htmlbeautifier/ruby_indenter.rb ================================================ # frozen_string_literal: true module HtmlBeautifier class RubyIndenter INDENT_KEYWORDS = %w[if elsif else unless while until begin for case when].freeze OUTDENT_KEYWORDS = %w[elsif else end when].freeze RUBY_INDENT = %r{ ^ ( #{INDENT_KEYWORDS.join("|")} )\b | \b ( do | \{ ) ( \s* \| [^|]+ \| )? $ }xo RUBY_OUTDENT = %r{ ^ ( #{OUTDENT_KEYWORDS.join("|")} | \} ) \b }xo def outdent?(lines) lines.first =~ RUBY_OUTDENT end def indent?(lines) lines.last =~ RUBY_INDENT end end end ================================================ FILE: lib/htmlbeautifier/version.rb ================================================ # frozen_string_literal: true module HtmlBeautifier # :nodoc: module VERSION # :nodoc: MAJOR = 1 MINOR = 4 TINY = 2 STRING = [MAJOR, MINOR, TINY].join(".") end end ================================================ FILE: lib/htmlbeautifier.rb ================================================ # frozen_string_literal: true require "htmlbeautifier/builder" require "htmlbeautifier/html_parser" require "htmlbeautifier/version" module HtmlBeautifier # # Returns a beautified HTML/HTML+ERB document as a String. # html must be an object that responds to +#to_s+. # # Available options are: # tab_stops - an integer for the number of spaces to indent, default 2. # Deprecated: see indent. # indent - what to indent with (" ", "\t" etc.), default " " # stop_on_errors - raise an exception on a badly-formed document. Default # is false, i.e. continue to process the rest of the document. # initial_level - The entire output will be indented by this number of steps. # Default is 0. # keep_blank_lines - an integer for the number of consecutive empty lines # to keep in output. # def self.beautify(html, options = {}) if options[:tab_stops] options[:indent] = " " * options[:tab_stops] end String.new.tap { |output| HtmlParser.new.scan html.to_s, Builder.new(output, options) } end end ================================================ FILE: spec/.rubocop.yml ================================================ inherit_from: - ../.rubocop.yml require: - rubocop-rspec # I can't always fit test data in 80 chars Layout/LineLength: Enabled: false Metrics/MethodLength: Enabled: false # I'd like to enable this, but there's a lot of % interpolation in the specs Style/FormatStringToken: Enabled: false # By its nature this is not a class or module RSpec/DescribeClass: Exclude: - executable_spec.rb # Pragmatic relaxation to avoid shelling out too often RSpec/MultipleExpectations: Exclude: - executable_spec.rb # To be revisited RSpec/ExampleLength: Enabled: false RSpec/FilePath: Enabled: false # Enable these new cops RSpec/ExcessiveDocstringSpacing: Enabled: true RSpec/IdenticalEqualityAssertion: Enabled: true RSpec/SubjectDeclaration: Enabled: true RSpec/Rails/AvoidSetupHook: Enabled: true ================================================ FILE: spec/behavior_spec.rb ================================================ # frozen_string_literal: true require "htmlbeautifier" describe HtmlBeautifier do it "ignores HTML fragments in embedded ERB" do source = code <<~HTML
<%= a[:b].gsub("\n","
\n") %>
HTML expected = code <<~HTML
<%= a[:b].gsub("\n","
\n") %>
HTML expect(described_class.beautify(source)).to eq(expected) end it "allows < in an attribute" do source = code <<~HTML

Hello

HTML expected = code <<~HTML

Hello

HTML expect(described_class.beautify(source)).to eq(expected) end it "allows > in an attribute" do source = code <<~HTML

Hello

HTML expected = code <<~HTML

Hello

HTML expect(described_class.beautify(source)).to eq(expected) end it "indents within HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "does not indent blank lines in scripts" do source = "" expected = "" expect(described_class.beautify(source)).to eq(expected) end it "handles self-closing HTML fragments in javascript: (bug repro)" do source = code <<-ERB
ERB expect(described_class.beautify(source, stop_on_errors: true)).to eq(source) end it "indents only the first line of code inside HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "retains empty HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "trims blank lines around scripts" do source = code <<~HTML HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "removes trailing space from script lines" do source = "" expected = "" expect(described_class.beautify(source)).to eq(expected) end it "leaves empty scripts as they are" do source = %() expect(described_class.beautify(source)).to eq(source) end it "removes whitespace from script tags containing only whitespace" do source = "" expected = "" expect(described_class.beautify(source)).to eq(expected) end it "ignores case of HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "indents within HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "trims blank lines around styles" do source = code <<~HTML HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "removes trailing space from style lines" do source = "" expected = "" expect(described_class.beautify(source)).to eq(expected) end it "ignores case of HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "indents
s containing standalone elements" do source = code <<~HTML
HTML expected = code <<~HTML
HTML expect(described_class.beautify(source)).to eq(expected) end it "does not break line on embedded code within ) expect(described_class.beautify(source)).to eq(source) end it "does not break line on embedded code within normal element" do source = %(foo) expect(described_class.beautify(source)).to eq(source) end it "outdents else" do source = code <<~ERB <% if @x %> Foo <% else %> Bar <% end %> ERB expected = code <<~ERB <% if @x %> Foo <% else %> Bar <% end %> ERB expect(described_class.beautify(source)).to eq(expected) end it "indents with hyphenated ERB tags" do source = code <<~ERB <%- if @x -%> <%- @ys.each do |y| -%>

Foo

<%- end -%> <%- elsif @z -%>
<%- end -%> ERB expected = code <<~ERB <%- if @x -%> <%- @ys.each do |y| -%>

Foo

<%- end -%> <%- elsif @z -%>
<%- end -%> ERB expect(described_class.beautify(source)).to eq(expected) end it "indents case statements" do source = code <<~ERB
<% case @x %> <% when :a %> a <% when :b %> b <% else %> c <% end %>
ERB expected = code <<~ERB
<% case @x %> <% when :a %> a <% when :b %> b <% else %> c <% end %>
ERB expect(described_class.beautify(source)).to eq(expected) end it "stays indented within
with Boolean attribute handled by ERB" do source = code <<~ERB
> Hello
<% items.each do |item| %> <% end %>
<%= item %>
ERB expect(described_class.beautify(source)).to eq(source) end it "does not indent after comments" do source = code <<~HTML HTML expect(described_class.beautify(source)).to eq(source) end it "does not indent one-line IE conditional comments" do source = code <<~HTML HTML expect(described_class.beautify(source)).to eq(source) end it "indents inside IE conditional comments" do source = code <<~HTML HTML expected = code <<~HTML HTML expect(described_class.beautify(source)).to eq(expected) end it "does not indent after doctype" do source = code <<~HTML HTML expect(described_class.beautify(source)).to eq(source) end it "does not indent after void HTML elements" do source = code <<~HTML
HTML expect(described_class.beautify(source)).to eq(source) end it "ignores case of void elements" do source = code <<~HTML
HTML expect(described_class.beautify(source)).to eq(source) end it "does not treat as standalone" do source = code <<~HTML HTML expect(described_class.beautify(source)).to eq(source) end it "does not modify content of
" do
    source = code <<~HTML
      
   Preformatted   text

                should  not  be 
                      modified,
                ever!

        
HTML expect(described_class.beautify(source)).to eq(source) end it "adds a single newline after block elements" do source = code <<~HTML

Title

Lorem ipsum

  1. First
  2. Second
HTML expected = code <<~HTML

Title

Lorem ipsum

  1. First
  2. Second
HTML expect(described_class.beautify(source)).to eq(expected) end it "does not modify content of
HTML expect(described_class.beautify(source)).to eq(source) end it "adds newlines around
" do
    source = %(
puts "Allons-y!"
) expected = code <<~HTML
puts "Allons-y!"
HTML expect(described_class.beautify(source)).to eq(expected) end it "adds newline after
" do source = %(

Lorem ipsum
dolor sit
amet,
consectetur.

) expected = code <<~HTML

Lorem ipsum
dolor sit
amet,
consectetur.

HTML expect(described_class.beautify(source)).to eq(expected) end it "indents after control expressions without optional `do` keyword" do source = code <<~ERB <% for value in list %> Lorem ipsum <% end %> <% until something %> Lorem ipsum <% end %> <% while something_else %> Lorem ipsum <% end %> ERB expected = code <<~ERB <% for value in list %> Lorem ipsum <% end %> <% until something %> Lorem ipsum <% end %> <% while something_else %> Lorem ipsum <% end %> ERB expect(described_class.beautify(source)).to eq(expected) end it "indents general self-closing tags" do source = code <<~HTML


HTML expected = code <<~HTML


HTML expect(described_class.beautify(source)).to eq(expected) end it "removes excess indentation on next line after text" do source = code <<~HTML Lorem ipsum
Lorem ipsum Lorem ipsum HTML expected = code <<~HTML Lorem ipsum
Lorem ipsum Lorem ipsum HTML expect(described_class.beautify(source)).to eq(expected) end it "indents subsequent lines of multiline text" do source = code <<~HTML

Lorem Lorem Lorem

HTML expected = code <<~HTML

Lorem Lorem Lorem

HTML expect(described_class.beautify(source)).to eq(expected) end context "when keep_blank_lines is 0" do it "removes all blank lines" do source = code <<~HTML

Lorem

Ipsum

HTML expected = code <<~HTML

Lorem

Ipsum

HTML expect(described_class.beautify(source, keep_blank_lines: 0)).to eq(expected) end end context "when keep_blank_lines is 1" do it "removes all blank lines but 1" do source = code <<~HTML

Lorem

Ipsum

HTML expected = code <<~HTML

Lorem

Ipsum

HTML expect(described_class.beautify(source, keep_blank_lines: 1)).to eq(expected) end it "does not add blank lines" do source = code <<~HTML

Lorem

Ipsum

dolor

HTML expect(described_class.beautify(source, keep_blank_lines: 1)).to eq(source) end it "does not indent blank lines" do source = code <<~HTML
Ipsum

dolor

HTML expected = code <<~HTML
Ipsum

dolor

HTML expect(described_class.beautify(source, keep_blank_lines: 1)).to eq(expected) end end context "when keep_blank_lines is 2" do it "removes all blank lines but 2" do source = code <<~HTML

Lorem

Ipsum

HTML expected = code <<~HTML

Lorem

Ipsum

HTML expect(described_class.beautify(source, keep_blank_lines: 2)).to eq(expected) end end end ================================================ FILE: spec/documents_spec.rb ================================================ # frozen_string_literal: true require "htmlbeautifier" describe HtmlBeautifier do it "correctly indents mixed document" do source = code <<~ERB Title Goes Here

Heading 1

Lorem Ipsum

<% if @x %> <% @ys.each do |y| %>

<%= h y %>

<% end %> <% elsif @z %>
<% end %>
First column
Second column
ERB expected = code <<~ERB Title Goes Here

Heading 1

Lorem Ipsum

<% if @x %> <% @ys.each do |y| %>

<%= h y %>

<% end %> <% elsif @z %>
<% end %>
First column
Second column
ERB expect(described_class.beautify(source)).to eq(expected) end context "when stop_on_errors is true" do it "raises an error with the source line of an illegal closing tag" do expect { source = "\n\n" described_class.beautify(source, stop_on_errors: true) }.to raise_error(RuntimeError, "Extraneous closing tag on line 3") end end context "when stop_on_errors is false" do it "processes the rest of the document after the errant closing tag" do source = code <<~HTML
text
HTML expected = code <<~HTML
text
HTML expect(described_class.beautify(source, stop_on_errors: false)) .to eq(expected) end end end ================================================ FILE: spec/executable_spec.rb ================================================ # frozen_string_literal: true require "shellwords" require "fileutils" require "open3" describe "bin/htmlbeautifier" do before do FileUtils.mkdir_p path_to("tmp") end def write(path, content) File.write(path, content) end def read(path) File.read(path) end def path_to(*partial) File.join(File.expand_path("..", __dir__), *partial) end def command "ruby -I%s %s" % [ escape(path_to("lib")), escape(path_to("bin", "htmlbeautifier")) ] end def escape(str) Shellwords.escape(str) end it "beautifies a file in place" do input = "

\nfoo\n

" expected = "

\n foo\n

\n" path = path_to("tmp", "in-place.html") write path, input system "%s %s" % [command, escape(path)] expect(read(path)).to eq(expected) end it "beautifies a file from stdin to stdout" do input = "

\nfoo\n

" expected = "

\n foo\n

\n" in_path = path_to("tmp", "input.html") out_path = path_to("tmp", "output.html") write in_path, input system "%s < %s > %s" % [command, escape(in_path), escape(out_path)] expect(read(out_path)).to eq(expected) end it "displays which files would fail with --lint-only flag" do good_input = "

\n" good_path = path_to("tmp", "good.html") write(good_path, good_input) bad_input = "

\n" bad_path = path_to("tmp", "bad.html") write(bad_path, bad_input) expected_message = "Lint failed - files would be modified:\n/tmp/bad.html\n" _stdout, stderr, status = Open3.capture3( "%s %s %s --lint-only" % [command, escape(good_path), escape(bad_path)] ) stderr.sub!(%r{/.*tmp/}, "/tmp/") expect(status.exitstatus).to eq(1) expect(stderr).to eq(expected_message) end it "does not modify files with --lint-only flag" do good_input = "

\n" good_path = path_to("tmp", "good.html") write(good_path, good_input) bad_input = "

\n" bad_path = path_to("tmp", "bad.html") write(bad_path, bad_input) Open3.capture3( "%s %s %s --lint-only" % [command, escape(good_path), escape(bad_path)] ) expect(read(good_path)).to eq(good_input) expect(read(bad_path)).to eq(bad_input) end it "allows a configurable number of tab stops" do input = "

\nfoo\n

" expected = "

\n foo\n

\n" path = path_to("tmp", "in-place.html") write path, input system "%s --tab-stops=3 %s" % [command, escape(path)] expect(read(path)).to eq(expected) end it "allows indentation with tab instead of spaces" do input = "

\nfoo\n

" expected = "

\n\tfoo\n

\n" path = path_to("tmp", "in-place.html") write path, input system "%s --tab %s" % [command, escape(path)] expect(read(path)).to eq(expected) end it "allows an initial indentation level" do input = "

\nfoo\n

" expected = "

\n foo\n

\n" path = path_to("tmp", "in-place.html") write path, input system "%s --indent-by 3 %s" % [command, escape(path)] expect(read(path)).to eq(expected) end it "ignores closing tag errors by default" do input = "

\n" expected = "

\n" path = path_to("tmp", "in-place.html") write path, input status = system("%s %s" % [command, escape(path)]) expect(read(path)).to eq(expected) expect(status).to be_truthy end it "raises an exception on closing tag errors with --stop-on-errors" do input = "

\n" path = path_to("tmp", "in-place.html") write path, input status = system("%s --stop-on-errors %s 2>/dev/null" % [command, escape(path)]) expect(status).to be_falsey end it "allows a configurable number of consecutive blank lines" do input = "

foo

\n\n\n\n\n

bar

\n" expected = "

foo

\n\n\n

bar

\n" path = path_to("tmp", "in-place.html") write path, input system "%s --keep-blank-lines=2 %s" % [command, escape(path)] expect(read(path)).to eq(expected) end end ================================================ FILE: spec/parser_spec.rb ================================================ # frozen_string_literal: true require "htmlbeautifier/parser" describe HtmlBeautifier::Parser do let(:receiver_class) { Class.new do attr_reader :sequence def initialize @sequence = [] end def method_missing(method, *params) @sequence << [method, params] end def respond_to_missing? true end end } it "dispatches matching sequence" do receiver = receiver_class.new parser = described_class.new { |p| p.map %r{foo}, :foo p.map %r{bar\s*}, :bar p.map %r{\s+}, :whitespace } parser.scan("foo bar ", receiver) expected = [[:foo, ["foo"]], [:whitespace, [" "]], [:bar, ["bar "]]] expect(receiver.sequence).to eq(expected) end it "sends parenthesized components as separate parameters" do receiver = receiver_class.new parser = described_class.new { |p| p.map %r{(foo)\((.*?)\)}, :foo } parser.scan("foo(bar)", receiver) expected = [[:foo, %w[foo bar]]] expect(receiver.sequence).to eq(expected) end context "when tracking source lines" do let(:source_tracking_receiver_class) { Class.new(receiver_class) do attr_reader :sources_so_far attr_reader :source_line_numbers def initialize(parser) @sources_so_far = [] @source_line_numbers = [] @parser = parser super() end def append_new_source_so_far(*) @sources_so_far << @parser.source_so_far end def append_new_source_line_number(*) @source_line_numbers << @parser.source_line_number end end } it "gives source so far" do parser = described_class.new { |p| p.map %r{(M+)}m, :append_new_source_so_far p.map %r{([\s\n]+)}m, :space_or_newline } receiver = source_tracking_receiver_class.new(parser) parser.scan("M MM MMM", receiver) expect(receiver.sources_so_far).to eq(["M", "M MM", "M MM MMM"]) end it "gives source line number" do parser = described_class.new { |p| p.map %r{(M+)}m, :append_new_source_line_number p.map %r{([\s\n]+)}m, :space_or_newline } receiver = source_tracking_receiver_class.new(parser) parser.scan("M \n\nMM\nMMM", receiver) expect(receiver.source_line_numbers).to eq([1, 3, 4]) end end end ================================================ FILE: spec/spec_helper.rb ================================================ # frozen_string_literal: true module HtmlBeautifierSpecUtilities def code(str) str = str.gsub(%r{\A\n|\n\s*\Z}, "") indentation = str[%r{\A +}] lines = str.split(%r{\n}) lines.map { |line| line.sub(%r{^#{indentation}}, "") }.join("\n") end end RSpec.configure do |config| config.include HtmlBeautifierSpecUtilities end