yegor256/pdd

View on GitHub
bin/pdd

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby
# Copyright (c) 2014-2024 Yegor Bugayenko
#
# 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 NONINFINGEMENT. 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.

$stdout.sync = true

require 'shellwords'
require 'English'
require 'find'
require 'slop'
require 'nokogiri'
require 'rainbow'
require_relative '../lib/pdd'
require_relative '../lib/pdd/version'
require_relative '../lib/pdd/source'

Rainbow.enabled = ENV['PDD_ENV'] == 'development'

begin
  args = []
  if File.exist?('.pdd')
    cfg = File.new('.pdd')
    body = File.read(cfg)
    extra = body.split(/\s+/).map(&:strip)
    args += extra
    puts "Found #{body.split("\n").length} lines in #{File.absolute_path(cfg)}"
  end
  args += ARGV

  begin
    opts = Slop.parse(args, strict: true, help: true) do |o|
      o.banner = "Usage (#{PDD::VERSION}): pdd [options]"
      o.bool '-h', '--help', 'Show these instructions'
      o.bool '-v', '--verbose', 'Enable verbose mode (a lot of logging)'
      o.bool '-q', '--quiet', 'Enable quiet mode (almost no logging)'
      o.bool '--remove', 'Remove all found puzzles from the source code'
      o.bool '--skip-gitignore', 'Don\'t look into .gitignore for excludes'
      o.bool '--skip-errors', 'Suppress error as warning and skip badly
                      formatted puzzles'
      o.bool '-i', '--version', 'Show current version' do
        puts PDD::VERSION
        exit
      end
      o.string '-s', '--source', 'Source directory to parse ("." by default)'
      o.string '-f', '--file', 'File to save report into'
      o.array '-e', '--exclude', 'Glob pattern to exclude, e.g. "**/*.jpg"',
              default: []
      o.array '-n', '--include', 'Glob pattern to include, e.g. "**/*.jpg"',
              default: []
      o.string '-t', '--format', 'Format of the report (xml|html|json)'
      o.array(
        '-r', '--rule', 'Rule to apply (can be used many times)',
        delimiter: ';'
      )
    end
  rescue Slop::Error => e
    raise StandardError, "#{e.message}, try --help"
  end

  if opts.help?
    puts opts
    puts "This is our README to learn more: \
https://github.com/cqfn/pdd/blob/master/README.md"
    exit
  end

  if opts.verbose? && !opts.file?
    raise '-f is mandatory when using -v, try --help for more information'
  end

  if opts['skip-gitignore'] && File.exist?('.gitignore')
    cfg = File.new('.gitignore')
    body = ''
    File.foreach(cfg) { |line| body << line unless line.start_with?('#') }
    extra = body.split(/\s+/).map(&:strip)
    opts['skip-gitignore'] = extra
    PDD.log.info "Found #{body.split("\n").length} lines in #{File.absolute_path(cfg)}"
  end

  Encoding.default_external = Encoding::UTF_8
  Encoding.default_internal = Encoding::UTF_8
  file = opts.file? ? File.new(opts[:file], 'w') : $stdout
  xml = PDD::Base.new(opts).xml
  output = xml
  if opts[:format]
    if opts[:format] == 'html'
      xslt = File.join(
        File.dirname(File.dirname(__FILE__)),
        'assets', 'puzzles.xsl'
      )
      output = Nokogiri::XSLT(File.read(xslt)).transform(Nokogiri::XML(xml))
    elsif opts[:format] == 'json'
      xslt = File.join(
        File.dirname(File.dirname(__FILE__)),
        'assets', 'puzzles_json.xsl'
      )
      # result is not xml, so use apply
      output = Nokogiri::XSLT(File.read(xslt)).apply_to(Nokogiri::XML(xml))
    elsif opts[:format] != 'xml'
      raise 'Invalid format, use html or xml or json'
    end
  end
  file << output
  if opts.remove?
    home = opts[:source] || Dir.pwd
    PDD.log.info "Removing puzzles from #{home}..."
    files = {}
    Nokogiri::XML(xml).xpath('/puzzles/puzzle').each do |p|
      file = p.xpath('file/text()').to_s
      files[file] = [] if files[file].nil?
      files[file] << p.xpath('lines/text()').to_s.split('-').map(&:to_i)
    end
    files.each do |src, all|
      f = File.join(home, src)
      File.write(
        f,
        File.readlines(f).reject.each_with_index do |_t, i|
          all.any? { |pair| i + 1 >= pair[0] && i + 1 <= pair[1] }
        end.join
      )
      PDD.log.info "#{all.count} puzzles removed from #{src}"
    end
  end
rescue SystemExit => e
  puts e.message unless e.success?
  PDD.log.info "Exit code is #{e.status}"
  exit(e.status)
rescue PDD::Error => e
  PDD.log.error "#{Rainbow('ERROR').red}: #{e.message}
If you can't understand the cause of this issue or you don't know \
how to fix it, please submit a GitHub issue, we will try to help you: \
https://github.com/cqfn/pdd/issues. This tool is still in its beta \
version and we will appreciate your feedback. Here is where you can find \
more documentation: https://github.com/cqfn/pdd/blob/master/README.md."
  PDD.log.info 'Exit code is 1'
  exit(1)
rescue StandardError => e
  PDD.log.error "#{Rainbow('ERROR').red} (#{e.class.name}): #{e.message}"
  PDD.log.info 'Exit code is 255'
  exit(255)
end