bin/pdd
#!/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